9
. "launchpad.net/gocheck"
11
gc "launchpad.net/gocheck"
10
12
"launchpad.net/gomaasapi"
11
13
"launchpad.net/goyaml"
15
"launchpad.net/juju-core/agent/tools"
12
16
"launchpad.net/juju-core/constraints"
13
17
"launchpad.net/juju-core/environs"
14
18
"launchpad.net/juju-core/environs/config"
15
19
envtesting "launchpad.net/juju-core/environs/testing"
16
"launchpad.net/juju-core/environs/tools"
17
20
"launchpad.net/juju-core/errors"
18
21
"launchpad.net/juju-core/instance"
19
22
"launchpad.net/juju-core/testing"
20
23
"launchpad.net/juju-core/utils"
21
24
"launchpad.net/juju-core/version"
25
27
type EnvironSuite struct {
29
var _ = Suite(new(EnvironSuite))
31
var _ = gc.Suite(&EnvironSuite{})
31
33
// getTestConfig creates a customized sample MAAS provider configuration.
32
34
func getTestConfig(name, server, oauth, secret string) *config.Config {
87
89
oldCfg := getTestConfig("old-name", server, oauth, secret)
88
90
newCfg := getTestConfig("new-name", server, oauth, secret)
89
91
env, err := NewEnviron(oldCfg)
92
c.Assert(err, gc.IsNil)
92
94
// SetConfig() fails, even though both the old and the new config are
93
95
// individually valid.
94
96
err = env.SetConfig(newCfg)
96
c.Check(err, ErrorMatches, ".*cannot change name.*")
97
c.Assert(err, gc.NotNil)
98
c.Check(err, gc.ErrorMatches, ".*cannot change name.*")
98
100
// The old config is still in place. The new config never took effect.
99
c.Check(env.Name(), Equals, "old-name")
101
c.Check(env.Name(), gc.Equals, "old-name")
102
func (*EnvironSuite) TestSetConfigUpdatesConfig(c *C) {
104
func (*EnvironSuite) TestSetConfigUpdatesConfig(c *gc.C) {
103
105
name := "test env"
104
106
cfg := getTestConfig(name, "http://maas2.testing.invalid", "a:b:c", "secret")
105
107
env, err := NewEnviron(cfg)
107
c.Check(env.name, Equals, "test env")
108
c.Check(err, gc.IsNil)
109
c.Check(env.name, gc.Equals, "test env")
109
111
anotherServer := "http://maas.testing.invalid"
110
112
anotherOauth := "c:d:e"
111
113
anotherSecret := "secret2"
112
114
cfg2 := getTestConfig(name, anotherServer, anotherOauth, anotherSecret)
113
115
errSetConfig := env.SetConfig(cfg2)
114
c.Check(errSetConfig, IsNil)
115
c.Check(env.name, Equals, name)
116
c.Check(errSetConfig, gc.IsNil)
117
c.Check(env.name, gc.Equals, name)
116
118
authClient, _ := gomaasapi.NewAuthenticatedClient(anotherServer, anotherOauth, apiVersion)
117
119
maas := gomaasapi.NewMAAS(*authClient)
118
120
MAASServer := env.maasClientUnlocked
119
c.Check(MAASServer, DeepEquals, maas)
121
c.Check(MAASServer, gc.DeepEquals, maas)
122
func (*EnvironSuite) TestNewEnvironSetsConfig(c *C) {
124
func (*EnvironSuite) TestNewEnvironSetsConfig(c *gc.C) {
123
125
name := "test env"
124
126
cfg := getTestConfig(name, "http://maas.testing.invalid", "a:b:c", "secret")
126
128
env, err := NewEnviron(cfg)
129
c.Check(env.name, Equals, name)
130
c.Check(err, gc.IsNil)
131
c.Check(env.name, gc.Equals, name)
132
func (suite *EnvironSuite) TestInstancesReturnsInstances(c *C) {
134
func (suite *EnvironSuite) TestInstancesReturnsInstances(c *gc.C) {
133
135
input := `{"system_id": "test"}`
134
136
node := suite.testMAASObject.TestServer.NewNode(input)
135
137
resourceURI, _ := node.GetField("resource_uri")
138
140
instances, err := suite.environ.Instances(instanceIds)
141
c.Check(len(instances), Equals, 1)
142
c.Check(string(instances[0].Id()), Equals, resourceURI)
142
c.Check(err, gc.IsNil)
143
c.Check(len(instances), gc.Equals, 1)
144
c.Check(string(instances[0].Id()), gc.Equals, resourceURI)
145
func (suite *EnvironSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *C) {
147
func (suite *EnvironSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) {
146
148
input := `{"system_id": "test"}`
147
149
suite.testMAASObject.TestServer.NewNode(input)
148
150
instances, err := suite.environ.Instances([]instance.Id{})
150
c.Check(err, Equals, environs.ErrNoInstances)
151
c.Check(instances, IsNil)
152
c.Check(err, gc.Equals, environs.ErrNoInstances)
153
c.Check(instances, gc.IsNil)
154
func (suite *EnvironSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *C) {
156
func (suite *EnvironSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) {
155
157
input := `{"system_id": "test"}`
156
158
suite.testMAASObject.TestServer.NewNode(input)
157
159
instances, err := suite.environ.Instances(nil)
159
c.Check(err, Equals, environs.ErrNoInstances)
160
c.Check(instances, IsNil)
161
c.Check(err, gc.Equals, environs.ErrNoInstances)
162
c.Check(instances, gc.IsNil)
163
func (suite *EnvironSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *C) {
165
func (suite *EnvironSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) {
164
166
_, err := suite.environ.Instances([]instance.Id{"unknown"})
165
c.Check(err, Equals, environs.ErrNoInstances)
167
c.Check(err, gc.Equals, environs.ErrNoInstances)
168
func (suite *EnvironSuite) TestAllInstancesReturnsAllInstances(c *C) {
170
func (suite *EnvironSuite) TestAllInstancesReturnsAllInstances(c *gc.C) {
169
171
input := `{"system_id": "test"}`
170
172
node := suite.testMAASObject.TestServer.NewNode(input)
171
173
resourceURI, _ := node.GetField("resource_uri")
173
175
instances, err := suite.environ.AllInstances()
176
c.Check(len(instances), Equals, 1)
177
c.Check(string(instances[0].Id()), Equals, resourceURI)
177
c.Check(err, gc.IsNil)
178
c.Check(len(instances), gc.Equals, 1)
179
c.Check(string(instances[0].Id()), gc.Equals, resourceURI)
180
func (suite *EnvironSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *C) {
182
func (suite *EnvironSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) {
181
183
instances, err := suite.environ.AllInstances()
184
c.Check(len(instances), Equals, 0)
185
c.Check(err, gc.IsNil)
186
c.Check(len(instances), gc.Equals, 0)
187
func (suite *EnvironSuite) TestInstancesReturnsErrorIfPartialInstances(c *C) {
189
func (suite *EnvironSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) {
188
190
input1 := `{"system_id": "test"}`
189
191
node1 := suite.testMAASObject.TestServer.NewNode(input1)
190
192
resourceURI1, _ := node1.GetField("resource_uri")
197
199
instances, err := suite.environ.Instances(instanceIds)
199
c.Check(err, Equals, environs.ErrPartialInstances)
200
c.Check(len(instances), Equals, 1)
201
c.Check(string(instances[0].Id()), Equals, resourceURI1)
201
c.Check(err, gc.Equals, environs.ErrPartialInstances)
202
c.Check(len(instances), gc.Equals, 1)
203
c.Check(string(instances[0].Id()), gc.Equals, resourceURI1)
204
func (suite *EnvironSuite) TestStorageReturnsStorage(c *C) {
206
func (suite *EnvironSuite) TestStorageReturnsStorage(c *gc.C) {
205
207
env := suite.makeEnviron()
206
208
storage := env.Storage()
207
c.Check(storage, NotNil)
209
c.Check(storage, gc.NotNil)
208
210
// The Storage object is really a maasStorage.
209
211
specificStorage := storage.(*maasStorage)
210
212
// Its environment pointer refers back to its environment.
211
c.Check(specificStorage.environUnlocked, Equals, env)
213
c.Check(specificStorage.environUnlocked, gc.Equals, env)
214
func (suite *EnvironSuite) TestPublicStorageReturnsEmptyStorage(c *C) {
216
func (suite *EnvironSuite) TestPublicStorageReturnsEmptyStorage(c *gc.C) {
215
217
env := suite.makeEnviron()
216
218
storage := env.PublicStorage()
217
c.Assert(storage, NotNil)
218
c.Check(storage, Equals, environs.EmptyStorage)
219
c.Assert(storage, gc.NotNil)
220
c.Check(storage, gc.Equals, environs.EmptyStorage)
221
223
func decodeUserData(userData string) ([]byte, error) {
226
228
return utils.Gunzip(data)
229
func (suite *EnvironSuite) TestStartInstanceStartsInstance(c *C) {
231
func (suite *EnvironSuite) TestStartInstanceStartsInstance(c *gc.C) {
230
232
suite.setupFakeTools(c)
231
233
env := suite.makeEnviron()
232
234
// Create node 0: it will be used as the bootstrap node.
233
235
suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
234
236
err := environs.Bootstrap(env, constraints.Value{})
237
c.Assert(err, gc.IsNil)
236
238
// The bootstrap node has been acquired and started.
237
239
operations := suite.testMAASObject.TestServer.NodeOperations()
238
240
actions, found := operations["node0"]
239
c.Check(found, Equals, true)
240
c.Check(actions, DeepEquals, []string{"acquire", "start"})
241
c.Check(found, gc.Equals, true)
242
c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
242
244
// Test the instance id is correctly recorded for the bootstrap node.
243
245
// Check that the state holds the id of the bootstrap machine.
244
246
stateData, err := environs.LoadState(env.Storage())
246
c.Assert(stateData.StateInstances, HasLen, 1)
247
c.Assert(err, gc.IsNil)
248
c.Assert(stateData.StateInstances, gc.HasLen, 1)
247
249
insts, err := env.AllInstances()
249
c.Assert(insts, HasLen, 1)
250
c.Check(insts[0].Id(), Equals, stateData.StateInstances[0])
250
c.Assert(err, gc.IsNil)
251
c.Assert(insts, gc.HasLen, 1)
252
c.Check(insts[0].Id(), gc.Equals, stateData.StateInstances[0])
252
254
// Create node 1: it will be used as instance number 1.
253
255
suite.testMAASObject.TestServer.NewNode(`{"system_id": "node1", "hostname": "host1"}`)
254
256
stateInfo, apiInfo, err := env.StateInfo()
257
c.Assert(err, gc.IsNil)
256
258
stateInfo.Tag = "machine-1"
257
259
apiInfo.Tag = "machine-1"
258
260
series := version.Current.Series
260
262
// TODO(wallyworld) - test instance metadata
261
263
instance, _, err := env.StartInstance("1", nonce, series, constraints.Value{}, stateInfo, apiInfo)
263
c.Check(instance, NotNil)
264
c.Assert(err, gc.IsNil)
265
c.Check(instance, gc.NotNil)
265
267
// The instance number 1 has been acquired and started.
266
268
actions, found = operations["node1"]
267
c.Assert(found, Equals, true)
268
c.Check(actions, DeepEquals, []string{"acquire", "start"})
269
c.Assert(found, gc.Equals, true)
270
c.Check(actions, gc.DeepEquals, []string{"acquire", "start"})
270
272
// The value of the "user data" parameter used when starting the node
271
273
// contains the run cmd used to write the machine information onto
272
274
// the node's filesystem.
273
275
requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
274
276
nodeRequestValues, found := requestValues["node1"]
275
c.Assert(found, Equals, true)
276
c.Assert(len(nodeRequestValues), Equals, 2)
277
c.Assert(found, gc.Equals, true)
278
c.Assert(len(nodeRequestValues), gc.Equals, 2)
277
279
userData := nodeRequestValues[1].Get("user_data")
278
280
decodedUserData, err := decodeUserData(userData)
281
c.Assert(err, gc.IsNil)
280
282
info := machineInfo{"host1"}
281
283
cloudinitRunCmd, err := info.cloudinitRunCmd()
284
c.Assert(err, gc.IsNil)
283
285
data, err := goyaml.Marshal(cloudinitRunCmd)
285
c.Check(string(decodedUserData), Matches, "(.|\n)*"+string(data)+"(\n|.)*")
286
c.Assert(err, gc.IsNil)
287
c.Check(string(decodedUserData), gc.Matches, "(.|\n)*"+string(data)+"(\n|.)*")
287
289
// Trash the tools and try to start another instance.
288
290
envtesting.RemoveTools(c, env.Storage())
289
291
instance, _, err = env.StartInstance("2", "fake-nonce", series, constraints.Value{}, stateInfo, apiInfo)
290
c.Check(instance, IsNil)
291
c.Check(err, ErrorMatches, "no tools available")
292
c.Check(err, FitsTypeOf, (*errors.NotFoundError)(nil))
292
c.Check(instance, gc.IsNil)
293
c.Check(err, gc.ErrorMatches, "no tools available")
294
c.Check(err, gc.FitsTypeOf, (*errors.NotFoundError)(nil))
295
297
func uint64p(val uint64) *uint64 {
309
311
_, _, err := env.acquireNode(constraints.Value{}, tools.List{fakeTools})
313
c.Check(err, gc.IsNil)
312
314
operations := suite.testMAASObject.TestServer.NodeOperations()
313
315
actions, found := operations["node0"]
314
c.Assert(found, Equals, true)
315
c.Check(actions, DeepEquals, []string{"acquire"})
316
c.Assert(found, gc.Equals, true)
317
c.Check(actions, gc.DeepEquals, []string{"acquire"})
318
func (suite *EnvironSuite) TestAcquireNodeTakesConstraintsIntoAccount(c *C) {
320
func (suite *EnvironSuite) TestAcquireNodeTakesConstraintsIntoAccount(c *gc.C) {
319
321
storage := NewStorage(suite.environ)
320
322
fakeTools := envtesting.MustUploadFakeToolsVersion(storage, version.Current)
321
323
env := suite.makeEnviron()
325
327
_, _, err := env.acquireNode(constraints, tools.List{fakeTools})
329
c.Check(err, gc.IsNil)
328
330
requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
329
331
nodeRequestValues, found := requestValues["node0"]
330
c.Assert(found, Equals, true)
331
c.Assert(nodeRequestValues[0].Get("arch"), Equals, "arm")
332
c.Assert(nodeRequestValues[0].Get("mem"), Equals, "1024")
332
c.Assert(found, gc.Equals, true)
333
c.Assert(nodeRequestValues[0].Get("arch"), gc.Equals, "arm")
334
c.Assert(nodeRequestValues[0].Get("mem"), gc.Equals, "1024")
335
func (suite *EnvironSuite) TestConvertConstraints(c *C) {
337
func (suite *EnvironSuite) TestConvertConstraints(c *gc.C) {
336
338
var testValues = []struct {
337
339
constraints constraints.Value
338
340
expectedResult url.Values
387
389
err := environs.SaveState(
389
391
&environs.BootstrapState{StateInstances: []instance.Id{testInstance.Id()}})
392
c.Assert(err, gc.IsNil)
392
394
stateInfo, apiInfo, err := env.StateInfo()
395
c.Assert(err, gc.IsNil)
395
397
config := env.Config()
396
398
statePortSuffix := fmt.Sprintf(":%d", config.StatePort())
397
399
apiPortSuffix := fmt.Sprintf(":%d", config.APIPort())
398
c.Assert(stateInfo.Addrs, DeepEquals, []string{hostname + statePortSuffix})
399
c.Assert(apiInfo.Addrs, DeepEquals, []string{hostname + apiPortSuffix})
400
c.Assert(stateInfo.Addrs, gc.DeepEquals, []string{hostname + statePortSuffix})
401
c.Assert(apiInfo.Addrs, gc.DeepEquals, []string{hostname + apiPortSuffix})
402
func (suite *EnvironSuite) TestStateInfoFailsIfNoStateInstances(c *C) {
404
func (suite *EnvironSuite) TestStateInfoFailsIfNoStateInstances(c *gc.C) {
403
405
env := suite.makeEnviron()
405
407
_, _, err := env.StateInfo()
407
c.Check(err, FitsTypeOf, &errors.NotFoundError{})
409
c.Check(err, gc.FitsTypeOf, &errors.NotFoundError{})
410
func (suite *EnvironSuite) TestDestroy(c *C) {
412
func (suite *EnvironSuite) TestDestroy(c *gc.C) {
411
413
env := suite.makeEnviron()
412
414
suite.getInstance("test1")
413
415
testInstance := suite.getInstance("test2")
418
420
err := env.Destroy([]instance.Instance{testInstance})
422
c.Check(err, gc.IsNil)
421
423
// Instances have been stopped.
422
424
operations := suite.testMAASObject.TestServer.NodeOperations()
423
425
expectedOperations := map[string][]string{"test1": {"release"}, "test2": {"release"}}
424
c.Check(operations, DeepEquals, expectedOperations)
426
c.Check(operations, gc.DeepEquals, expectedOperations)
425
427
// Files have been cleaned up.
426
428
listing, err := storage.List("")
428
c.Check(listing, DeepEquals, []string{})
429
c.Assert(err, gc.IsNil)
430
c.Check(listing, gc.DeepEquals, []string{})
431
433
// It would be nice if we could unit-test Bootstrap() in more detail, but
432
434
// at the time of writing that would require more support from gomaasapi's
433
435
// testing service than we have.
434
func (suite *EnvironSuite) TestBootstrapSucceeds(c *C) {
436
func (suite *EnvironSuite) TestBootstrapSucceeds(c *gc.C) {
435
437
suite.setupFakeTools(c)
436
438
env := suite.makeEnviron()
437
439
suite.testMAASObject.TestServer.NewNode(`{"system_id": "thenode", "hostname": "host"}`)
438
440
err := env.Bootstrap(constraints.Value{})
441
c.Assert(err, gc.IsNil)
442
func (suite *EnvironSuite) TestBootstrapFailsIfNoTools(c *C) {
444
func (suite *EnvironSuite) TestBootstrapFailsIfNoTools(c *gc.C) {
443
445
suite.setupFakeTools(c)
444
446
env := suite.makeEnviron()
445
447
// Can't RemoveAllTools, no public storage.
446
448
envtesting.RemoveTools(c, env.Storage())
447
449
err := env.Bootstrap(constraints.Value{})
448
c.Check(err, ErrorMatches, "no tools available")
449
c.Check(err, FitsTypeOf, (*errors.NotFoundError)(nil))
450
c.Check(err, gc.ErrorMatches, "no tools available")
451
c.Check(err, gc.FitsTypeOf, (*errors.NotFoundError)(nil))
452
func (suite *EnvironSuite) TestBootstrapFailsIfNoNodes(c *C) {
454
func (suite *EnvironSuite) TestBootstrapFailsIfNoNodes(c *gc.C) {
453
455
suite.setupFakeTools(c)
454
456
env := suite.makeEnviron()
455
457
err := env.Bootstrap(constraints.Value{})
456
458
// Since there are no nodes, the attempt to allocate one returns a
457
459
// 409: Conflict.
458
c.Check(err, ErrorMatches, ".*409.*")
460
c.Check(err, gc.ErrorMatches, ".*409.*")
461
func (suite *EnvironSuite) TestBootstrapIntegratesWithEnvirons(c *C) {
463
func (suite *EnvironSuite) TestBootstrapIntegratesWithEnvirons(c *gc.C) {
462
464
suite.setupFakeTools(c)
463
465
env := suite.makeEnviron()
464
466
suite.testMAASObject.TestServer.NewNode(`{"system_id": "bootstrapnode", "hostname": "host"}`)
466
468
// environs.Bootstrap calls Environ.Bootstrap. This works.
467
469
err := environs.Bootstrap(env, constraints.Value{})
470
c.Assert(err, gc.IsNil)