~jameinel/juju-core/api-registry-tracks-type

« back to all changes in this revision

Viewing changes to environs/maas/environ_test.go

Merge trunk and resolve conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package maas
 
2
 
 
3
import (
 
4
        "encoding/base64"
 
5
        . "launchpad.net/gocheck"
 
6
        "launchpad.net/gomaasapi"
 
7
        "launchpad.net/goyaml"
 
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"
 
16
)
 
17
 
 
18
type EnvironSuite struct {
 
19
        ProviderSuite
 
20
}
 
21
 
 
22
var _ = Suite(new(EnvironSuite))
 
23
 
 
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{}{
 
27
                "name":            name,
 
28
                "maas-server":     server,
 
29
                "maas-oauth":      oauth,
 
30
                "admin-secret":    secret,
 
31
                "authorized-keys": "I-am-not-a-real-key",
 
32
        })
 
33
        if err != nil {
 
34
                panic(err)
 
35
        }
 
36
        return ecfg.Config
 
37
}
 
38
 
 
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(),
 
44
                "type":            "maas",
 
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
 
51
                // fixing there.
 
52
                "ca-cert":        testing.CACert,
 
53
                "ca-private-key": testing.CAKey,
 
54
        })
 
55
        if err != nil {
 
56
                panic(err)
 
57
        }
 
58
        env, err := NewEnviron(config)
 
59
        if err != nil {
 
60
                panic(err)
 
61
        }
 
62
        return env
 
63
}
 
64
 
 
65
func (suite *EnvironSuite) setupFakeProviderStateFile(c *C) {
 
66
        suite.testMAASObject.TestServer.NewFile("provider-state", []byte("test file content"))
 
67
}
 
68
 
 
69
func (suite *EnvironSuite) setupFakeTools(c *C) {
 
70
        storage := NewStorage(suite.environ)
 
71
        envtesting.UploadFakeTools(c, storage)
 
72
}
 
73
 
 
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"
 
78
        oauth := "a:b:c"
 
79
        secret := "pssst"
 
80
        oldCfg := getTestConfig("old-name", server, oauth, secret)
 
81
        newCfg := getTestConfig("new-name", server, oauth, secret)
 
82
        env, err := NewEnviron(oldCfg)
 
83
        c.Assert(err, IsNil)
 
84
 
 
85
        // SetConfig() fails, even though both the old and the new config are
 
86
        // individually valid.
 
87
        err = env.SetConfig(newCfg)
 
88
        c.Assert(err, NotNil)
 
89
        c.Check(err, ErrorMatches, ".*cannot change name.*")
 
90
 
 
91
        // The old config is still in place.  The new config never took effect.
 
92
        c.Check(env.Name(), Equals, "old-name")
 
93
}
 
94
 
 
95
func (EnvironSuite) TestSetConfigUpdatesConfig(c *C) {
 
96
        name := "test env"
 
97
        cfg := getTestConfig(name, "http://maas2.example.com", "a:b:c", "secret")
 
98
        env, err := NewEnviron(cfg)
 
99
        c.Check(err, IsNil)
 
100
        c.Check(env.name, Equals, "test env")
 
101
 
 
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)
 
113
}
 
114
 
 
115
func (EnvironSuite) TestNewEnvironSetsConfig(c *C) {
 
116
        name := "test env"
 
117
        cfg := getTestConfig(name, "http://maas.example.com", "a:b:c", "secret")
 
118
 
 
119
        env, err := NewEnviron(cfg)
 
120
 
 
121
        c.Check(err, IsNil)
 
122
        c.Check(env.name, Equals, name)
 
123
}
 
124
 
 
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)}
 
130
 
 
131
        instances, err := suite.environ.Instances(instanceIds)
 
132
 
 
133
        c.Check(err, IsNil)
 
134
        c.Check(len(instances), Equals, 1)
 
135
        c.Check(string(instances[0].Id()), Equals, resourceURI)
 
136
}
 
137
 
 
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{})
 
143
 
 
144
        c.Check(err, IsNil)
 
145
        c.Check(instances, IsNil)
 
146
}
 
147
 
 
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)
 
153
 
 
154
        c.Check(err, IsNil)
 
155
        c.Check(instances, IsNil)
 
156
}
 
157
 
 
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")
 
162
 
 
163
        instances, err := suite.environ.AllInstances()
 
164
 
 
165
        c.Check(err, IsNil)
 
166
        c.Check(len(instances), Equals, 1)
 
167
        c.Check(string(instances[0].Id()), Equals, resourceURI)
 
168
}
 
169
 
 
170
func (suite *EnvironSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *C) {
 
171
        instances, err := suite.environ.AllInstances()
 
172
 
 
173
        c.Check(err, IsNil)
 
174
        c.Check(len(instances), Equals, 0)
 
175
}
 
176
 
 
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}
 
186
 
 
187
        instances, err := suite.environ.Instances(instanceIds)
 
188
 
 
189
        c.Check(err, Equals, environs.ErrPartialInstances)
 
190
        c.Check(len(instances), Equals, 1)
 
191
        c.Check(string(instances[0].Id()), Equals, resourceURI1)
 
192
}
 
193
 
 
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)
 
202
}
 
203
 
 
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)
 
209
}
 
210
 
 
211
func decodeUserData(userData string) ([]byte, error) {
 
212
        data, err := base64.StdEncoding.DecodeString(userData)
 
213
        if err != nil {
 
214
                return []byte(""), err
 
215
        }
 
216
        return utils.Gunzip(data)
 
217
}
 
218
 
 
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{})
 
225
        c.Assert(err, IsNil)
 
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"})
 
231
 
 
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()
 
235
        c.Assert(err, IsNil)
 
236
        stateInfo.Tag = "machine-1"
 
237
        apiInfo.Tag = "machine-1"
 
238
        series := version.Current.Series
 
239
        nonce := "12345"
 
240
        instance, err := env.StartInstance("1", nonce, series, constraints.Value{}, stateInfo, apiInfo)
 
241
        c.Assert(err, IsNil)
 
242
        c.Check(instance, NotNil)
 
243
 
 
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"})
 
248
 
 
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)
 
257
        c.Assert(err, IsNil)
 
258
        info := machineInfo{string(instance.Id()), "host1"}
 
259
        cloudinitRunCmd, err := info.cloudinitRunCmd()
 
260
        c.Assert(err, IsNil)
 
261
        data, err := goyaml.Marshal(cloudinitRunCmd)
 
262
        c.Assert(err, IsNil)
 
263
        c.Check(string(decodedUserData), Matches, "(.|\n)*"+string(data)+"(\n|.)*")
 
264
}
 
265
 
 
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}
 
270
}
 
271
 
 
272
func (suite *EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *C) {
 
273
        suite.getInstance("test1")
 
274
 
 
275
        err := suite.environ.StopInstances([]environs.Instance{})
 
276
        c.Check(err, IsNil)
 
277
        operations := suite.testMAASObject.TestServer.NodeOperations()
 
278
        c.Check(operations, DeepEquals, map[string][]string{})
 
279
}
 
280
 
 
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}
 
286
 
 
287
        err := suite.environ.StopInstances(instances)
 
288
 
 
289
        c.Check(err, IsNil)
 
290
        operations := suite.testMAASObject.TestServer.NodeOperations()
 
291
        expectedOperations := map[string][]string{"test1": {"release"}, "test2": {"release"}}
 
292
        c.Check(operations, DeepEquals, expectedOperations)
 
293
}
 
294
 
 
295
func (suite *EnvironSuite) TestStateInfo(c *C) {
 
296
        env := suite.makeEnviron()
 
297
        hostname := "test"
 
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()}})
 
302
        c.Assert(err, IsNil)
 
303
 
 
304
        stateInfo, apiInfo, err := env.StateInfo()
 
305
 
 
306
        c.Assert(err, IsNil)
 
307
        c.Assert(stateInfo.Addrs, DeepEquals, []string{hostname + mgoPortSuffix})
 
308
        c.Assert(apiInfo.Addrs, DeepEquals, []string{hostname + apiPortSuffix})
 
309
}
 
310
 
 
311
func (suite *EnvironSuite) TestStateInfoFailsIfNoStateInstances(c *C) {
 
312
        env := suite.makeEnviron()
 
313
 
 
314
        _, _, err := env.StateInfo()
 
315
 
 
316
        c.Check(err, FitsTypeOf, &environs.NotFoundError{})
 
317
}
 
318
 
 
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()
 
326
 
 
327
        err := env.Destroy([]environs.Instance{instance})
 
328
 
 
329
        c.Check(err, IsNil)
 
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("")
 
336
        c.Assert(err, IsNil)
 
337
        c.Check(listing, DeepEquals, []string{})
 
338
}
 
339
 
 
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}
 
349
 
 
350
        err := env.Bootstrap(constraints.Value{}, cert, key)
 
351
        c.Assert(err, IsNil)
 
352
}
 
353
 
 
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
 
361
        // 409: Conflict.
 
362
        c.Check(err, ErrorMatches, ".*409.*")
 
363
}
 
364
 
 
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"}`)
 
369
 
 
370
        // environs.Bootstrap calls Environ.Bootstrap.  This works.
 
371
        err := environs.Bootstrap(env, constraints.Value{})
 
372
        c.Assert(err, IsNil)
 
373
}
 
374
 
 
375
func (suite *EnvironSuite) TestAssignmentPolicy(c *C) {
 
376
        env := suite.makeEnviron()
 
377
 
 
378
        c.Check(env.AssignmentPolicy(), Equals, state.AssignUnused)
 
379
}