5
. "launchpad.net/gocheck"
6
"launchpad.net/gomaasapi"
8
"launchpad.net/juju-core/constraints"
9
"launchpad.net/juju-core/environs"
10
"launchpad.net/juju-core/environs/config"
11
envtesting "launchpad.net/juju-core/environs/testing"
12
"launchpad.net/juju-core/state"
13
"launchpad.net/juju-core/testing"
14
"launchpad.net/juju-core/utils"
15
"launchpad.net/juju-core/version"
18
type EnvironSuite struct {
22
var _ = Suite(new(EnvironSuite))
24
// getTestConfig creates a customized sample MAAS provider configuration.
25
func getTestConfig(name, server, oauth, secret string) *config.Config {
26
ecfg, err := newConfig(map[string]interface{}{
28
"maas-server": server,
30
"admin-secret": secret,
31
"authorized-keys": "I-am-not-a-real-key",
39
// makeEnviron creates a functional maasEnviron for a test. Its configuration
40
// is a bit arbitrary and none of the test code's business.
41
func (suite *EnvironSuite) makeEnviron() *maasEnviron {
42
config, err := config.New(map[string]interface{}{
43
"name": suite.environ.Name(),
45
"admin-secret": "local-secret",
46
"authorized-keys": "foo",
47
"agent-version": version.CurrentNumber().String(),
48
"maas-oauth": "a:b:c",
49
"maas-server": suite.testMAASObject.TestServer.URL,
50
// These are not needed by MAAS, but juju-core breaks without them. Needs
52
"ca-cert": testing.CACert,
53
"ca-private-key": testing.CAKey,
58
env, err := NewEnviron(config)
65
func (suite *EnvironSuite) setupFakeProviderStateFile(c *C) {
66
suite.testMAASObject.TestServer.NewFile("provider-state", []byte("test file content"))
69
func (suite *EnvironSuite) setupFakeTools(c *C) {
70
storage := NewStorage(suite.environ)
71
envtesting.UploadFakeTools(c, storage)
74
func (EnvironSuite) TestSetConfigValidatesFirst(c *C) {
75
// SetConfig() validates the config change and disallows, for example,
76
// changes in the environment name.
77
server := "http://maas.example.com"
80
oldCfg := getTestConfig("old-name", server, oauth, secret)
81
newCfg := getTestConfig("new-name", server, oauth, secret)
82
env, err := NewEnviron(oldCfg)
85
// SetConfig() fails, even though both the old and the new config are
86
// individually valid.
87
err = env.SetConfig(newCfg)
89
c.Check(err, ErrorMatches, ".*cannot change name.*")
91
// The old config is still in place. The new config never took effect.
92
c.Check(env.Name(), Equals, "old-name")
95
func (EnvironSuite) TestSetConfigUpdatesConfig(c *C) {
97
cfg := getTestConfig(name, "http://maas2.example.com", "a:b:c", "secret")
98
env, err := NewEnviron(cfg)
100
c.Check(env.name, Equals, "test env")
102
anotherServer := "http://maas.example.com"
103
anotherOauth := "c:d:e"
104
anotherSecret := "secret2"
105
cfg2 := getTestConfig(name, anotherServer, anotherOauth, anotherSecret)
106
errSetConfig := env.SetConfig(cfg2)
107
c.Check(errSetConfig, IsNil)
108
c.Check(env.name, Equals, name)
109
authClient, _ := gomaasapi.NewAuthenticatedClient(anotherServer, anotherOauth, apiVersion)
110
maas := gomaasapi.NewMAAS(*authClient)
111
MAASServer := env.maasClientUnlocked
112
c.Check(MAASServer, DeepEquals, maas)
115
func (EnvironSuite) TestNewEnvironSetsConfig(c *C) {
117
cfg := getTestConfig(name, "http://maas.example.com", "a:b:c", "secret")
119
env, err := NewEnviron(cfg)
122
c.Check(env.name, Equals, name)
125
func (suite *EnvironSuite) TestInstancesReturnsInstances(c *C) {
126
input := `{"system_id": "test"}`
127
node := suite.testMAASObject.TestServer.NewNode(input)
128
resourceURI, _ := node.GetField("resource_uri")
129
instanceIds := []state.InstanceId{state.InstanceId(resourceURI)}
131
instances, err := suite.environ.Instances(instanceIds)
134
c.Check(len(instances), Equals, 1)
135
c.Check(string(instances[0].Id()), Equals, resourceURI)
138
func (suite *EnvironSuite) TestInstancesReturnsNilIfEmptyParameter(c *C) {
139
// Instances returns nil if the given parameter is empty.
140
input := `{"system_id": "test"}`
141
suite.testMAASObject.TestServer.NewNode(input)
142
instances, err := suite.environ.Instances([]state.InstanceId{})
145
c.Check(instances, IsNil)
148
func (suite *EnvironSuite) TestInstancesReturnsNilIfNilParameter(c *C) {
149
// Instances returns nil if the given parameter is nil.
150
input := `{"system_id": "test"}`
151
suite.testMAASObject.TestServer.NewNode(input)
152
instances, err := suite.environ.Instances(nil)
155
c.Check(instances, IsNil)
158
func (suite *EnvironSuite) TestAllInstancesReturnsAllInstances(c *C) {
159
input := `{"system_id": "test"}`
160
node := suite.testMAASObject.TestServer.NewNode(input)
161
resourceURI, _ := node.GetField("resource_uri")
163
instances, err := suite.environ.AllInstances()
166
c.Check(len(instances), Equals, 1)
167
c.Check(string(instances[0].Id()), Equals, resourceURI)
170
func (suite *EnvironSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *C) {
171
instances, err := suite.environ.AllInstances()
174
c.Check(len(instances), Equals, 0)
177
func (suite *EnvironSuite) TestInstancesReturnsErrorIfPartialInstances(c *C) {
178
input1 := `{"system_id": "test"}`
179
node1 := suite.testMAASObject.TestServer.NewNode(input1)
180
resourceURI1, _ := node1.GetField("resource_uri")
181
input2 := `{"system_id": "test2"}`
182
suite.testMAASObject.TestServer.NewNode(input2)
183
instanceId1 := state.InstanceId(resourceURI1)
184
instanceId2 := state.InstanceId("unknown systemID")
185
instanceIds := []state.InstanceId{instanceId1, instanceId2}
187
instances, err := suite.environ.Instances(instanceIds)
189
c.Check(err, Equals, environs.ErrPartialInstances)
190
c.Check(len(instances), Equals, 1)
191
c.Check(string(instances[0].Id()), Equals, resourceURI1)
194
func (suite *EnvironSuite) TestStorageReturnsStorage(c *C) {
195
env := suite.makeEnviron()
196
storage := env.Storage()
197
c.Check(storage, NotNil)
198
// The Storage object is really a maasStorage.
199
specificStorage := storage.(*maasStorage)
200
// Its environment pointer refers back to its environment.
201
c.Check(specificStorage.environUnlocked, Equals, env)
204
func (suite *EnvironSuite) TestPublicStorageReturnsEmptyStorage(c *C) {
205
env := suite.makeEnviron()
206
storage := env.PublicStorage()
207
c.Assert(storage, NotNil)
208
c.Check(storage, Equals, environs.EmptyStorage)
211
func decodeUserData(userData string) ([]byte, error) {
212
data, err := base64.StdEncoding.DecodeString(userData)
214
return []byte(""), err
216
return utils.Gunzip(data)
219
func (suite *EnvironSuite) TestStartInstanceStartsInstance(c *C) {
220
suite.setupFakeTools(c)
221
env := suite.makeEnviron()
222
// Create node 0: it will be used as the bootstrap node.
223
suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`)
224
err := environs.Bootstrap(env, constraints.Value{})
226
// The bootstrap node has been started.
227
operations := suite.testMAASObject.TestServer.NodeOperations()
228
actions, found := operations["node0"]
229
c.Check(found, Equals, true)
230
c.Check(actions, DeepEquals, []string{"start"})
232
// Create node 1: it will be used as instance number 1.
233
suite.testMAASObject.TestServer.NewNode(`{"system_id": "node1", "hostname": "host1"}`)
234
stateInfo, apiInfo, err := env.StateInfo()
236
stateInfo.Tag = "machine-1"
237
apiInfo.Tag = "machine-1"
238
series := version.Current.Series
240
instance, err := env.StartInstance("1", nonce, series, constraints.Value{}, stateInfo, apiInfo)
242
c.Check(instance, NotNil)
244
// The instance number 1 has been started.
245
actions, found = operations["node1"]
246
c.Assert(found, Equals, true)
247
c.Check(actions, DeepEquals, []string{"start"})
249
// The value of the "user data" parameter used when starting the node
250
// contains the run cmd used to write the machine information onto
251
// the node's filesystem.
252
requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues()
253
nodeRequestValues, found := requestValues["node1"]
254
c.Assert(found, Equals, true)
255
userData := nodeRequestValues[0].Get("user_data")
256
decodedUserData, err := decodeUserData(userData)
258
info := machineInfo{string(instance.Id()), "host1"}
259
cloudinitRunCmd, err := info.cloudinitRunCmd()
261
data, err := goyaml.Marshal(cloudinitRunCmd)
263
c.Check(string(decodedUserData), Matches, "(.|\n)*"+string(data)+"(\n|.)*")
266
func (suite *EnvironSuite) getInstance(systemId string) *maasInstance {
267
input := `{"system_id": "` + systemId + `"}`
268
node := suite.testMAASObject.TestServer.NewNode(input)
269
return &maasInstance{&node, suite.environ}
272
func (suite *EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *C) {
273
suite.getInstance("test1")
275
err := suite.environ.StopInstances([]environs.Instance{})
277
operations := suite.testMAASObject.TestServer.NodeOperations()
278
c.Check(operations, DeepEquals, map[string][]string{})
281
func (suite *EnvironSuite) TestStopInstancesStopsAndReleasesInstances(c *C) {
282
instance1 := suite.getInstance("test1")
283
instance2 := suite.getInstance("test2")
284
suite.getInstance("test3")
285
instances := []environs.Instance{instance1, instance2}
287
err := suite.environ.StopInstances(instances)
290
operations := suite.testMAASObject.TestServer.NodeOperations()
291
expectedOperations := map[string][]string{"test1": {"release"}, "test2": {"release"}}
292
c.Check(operations, DeepEquals, expectedOperations)
295
func (suite *EnvironSuite) TestStateInfo(c *C) {
296
env := suite.makeEnviron()
298
input := `{"system_id": "system_id", "hostname": "` + hostname + `"}`
299
node := suite.testMAASObject.TestServer.NewNode(input)
300
instance := &maasInstance{&node, suite.environ}
301
err := env.saveState(&bootstrapState{StateInstances: []state.InstanceId{instance.Id()}})
304
stateInfo, apiInfo, err := env.StateInfo()
307
c.Assert(stateInfo.Addrs, DeepEquals, []string{hostname + mgoPortSuffix})
308
c.Assert(apiInfo.Addrs, DeepEquals, []string{hostname + apiPortSuffix})
311
func (suite *EnvironSuite) TestStateInfoFailsIfNoStateInstances(c *C) {
312
env := suite.makeEnviron()
314
_, _, err := env.StateInfo()
316
c.Check(err, FitsTypeOf, &environs.NotFoundError{})
319
func (suite *EnvironSuite) TestDestroy(c *C) {
320
env := suite.makeEnviron()
321
suite.getInstance("test1")
322
instance := suite.getInstance("test2")
323
data := makeRandomBytes(10)
324
suite.testMAASObject.TestServer.NewFile("filename", data)
325
storage := env.Storage()
327
err := env.Destroy([]environs.Instance{instance})
330
// Instances have been stopped.
331
operations := suite.testMAASObject.TestServer.NodeOperations()
332
expectedOperations := map[string][]string{"test1": {"release"}, "test2": {"release"}}
333
c.Check(operations, DeepEquals, expectedOperations)
334
// Files have been cleaned up.
335
listing, err := storage.List("")
337
c.Check(listing, DeepEquals, []string{})
340
// It would be nice if we could unit-test Bootstrap() in more detail, but
341
// at the time of writing that would require more support from gomaasapi's
342
// testing service than we have.
343
func (suite *EnvironSuite) TestBootstrapSucceeds(c *C) {
344
suite.setupFakeTools(c)
345
env := suite.makeEnviron()
346
suite.testMAASObject.TestServer.NewNode(`{"system_id": "thenode", "hostname": "host"}`)
347
cert := []byte{1, 2, 3}
348
key := []byte{4, 5, 6}
350
err := env.Bootstrap(constraints.Value{}, cert, key)
354
func (suite *EnvironSuite) TestBootstrapFailsIfNoNodes(c *C) {
355
suite.setupFakeTools(c)
356
env := suite.makeEnviron()
357
cert := []byte{1, 2, 3}
358
key := []byte{4, 5, 6}
359
err := env.Bootstrap(constraints.Value{}, cert, key)
360
// Since there are no nodes, the attempt to allocate one returns a
362
c.Check(err, ErrorMatches, ".*409.*")
365
func (suite *EnvironSuite) TestBootstrapIntegratesWithEnvirons(c *C) {
366
suite.setupFakeTools(c)
367
env := suite.makeEnviron()
368
suite.testMAASObject.TestServer.NewNode(`{"system_id": "bootstrapnode", "hostname": "host"}`)
370
// environs.Bootstrap calls Environ.Bootstrap. This works.
371
err := environs.Bootstrap(env, constraints.Value{})
375
func (suite *EnvironSuite) TestAssignmentPolicy(c *C) {
376
env := suite.makeEnviron()
378
c.Check(env.AssignmentPolicy(), Equals, state.AssignUnused)