launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/provider/maas/environ_whitebox_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "bytes" 8 "encoding/base64" 9 "fmt" 10 "io/ioutil" 11 "net/url" 12 13 gc "launchpad.net/gocheck" 14 "launchpad.net/goyaml" 15 16 "launchpad.net/juju-core/constraints" 17 "launchpad.net/juju-core/environs" 18 "launchpad.net/juju-core/environs/bootstrap" 19 "launchpad.net/juju-core/environs/config" 20 "launchpad.net/juju-core/environs/imagemetadata" 21 "launchpad.net/juju-core/environs/simplestreams" 22 "launchpad.net/juju-core/environs/storage" 23 envtesting "launchpad.net/juju-core/environs/testing" 24 envtools "launchpad.net/juju-core/environs/tools" 25 "launchpad.net/juju-core/errors" 26 "launchpad.net/juju-core/instance" 27 "launchpad.net/juju-core/juju/testing" 28 coretesting "launchpad.net/juju-core/testing" 29 jc "launchpad.net/juju-core/testing/checkers" 30 "launchpad.net/juju-core/tools" 31 "launchpad.net/juju-core/utils" 32 "launchpad.net/juju-core/version" 33 ) 34 35 type environSuite struct { 36 providerSuite 37 } 38 39 const ( 40 allocatedNode = `{"system_id": "test-allocated"}` 41 ) 42 43 var _ = gc.Suite(&environSuite{}) 44 45 // getTestConfig creates a customized sample MAAS provider configuration. 46 func getTestConfig(name, server, oauth, secret string) *config.Config { 47 ecfg, err := newConfig(map[string]interface{}{ 48 "name": name, 49 "maas-server": server, 50 "maas-oauth": oauth, 51 "admin-secret": secret, 52 "authorized-keys": "I-am-not-a-real-key", 53 }) 54 if err != nil { 55 panic(err) 56 } 57 return ecfg.Config 58 } 59 60 func (suite *environSuite) setupFakeProviderStateFile(c *gc.C) { 61 suite.testMAASObject.TestServer.NewFile(bootstrap.StateFile, []byte("test file content")) 62 } 63 64 func (suite *environSuite) setupFakeTools(c *gc.C) { 65 stor := NewStorage(suite.makeEnviron()) 66 envtesting.UploadFakeTools(c, stor) 67 } 68 69 func (suite *environSuite) addNode(jsonText string) instance.Id { 70 node := suite.testMAASObject.TestServer.NewNode(jsonText) 71 resourceURI, _ := node.GetField("resource_uri") 72 return instance.Id(resourceURI) 73 } 74 75 func (suite *environSuite) TestInstancesReturnsInstances(c *gc.C) { 76 id := suite.addNode(allocatedNode) 77 instances, err := suite.makeEnviron().Instances([]instance.Id{id}) 78 79 c.Check(err, gc.IsNil) 80 c.Assert(instances, gc.HasLen, 1) 81 c.Assert(instances[0].Id(), gc.Equals, id) 82 } 83 84 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) { 85 suite.addNode(allocatedNode) 86 instances, err := suite.makeEnviron().Instances([]instance.Id{}) 87 88 c.Check(err, gc.Equals, environs.ErrNoInstances) 89 c.Check(instances, gc.IsNil) 90 } 91 92 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) { 93 suite.addNode(allocatedNode) 94 instances, err := suite.makeEnviron().Instances(nil) 95 96 c.Check(err, gc.Equals, environs.ErrNoInstances) 97 c.Check(instances, gc.IsNil) 98 } 99 100 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) { 101 instances, err := suite.makeEnviron().Instances([]instance.Id{"unknown"}) 102 c.Check(err, gc.Equals, environs.ErrNoInstances) 103 c.Check(instances, gc.IsNil) 104 } 105 106 func (suite *environSuite) TestAllInstances(c *gc.C) { 107 id := suite.addNode(allocatedNode) 108 instances, err := suite.makeEnviron().AllInstances() 109 110 c.Check(err, gc.IsNil) 111 c.Assert(instances, gc.HasLen, 1) 112 c.Assert(instances[0].Id(), gc.Equals, id) 113 } 114 115 func (suite *environSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) { 116 instances, err := suite.makeEnviron().AllInstances() 117 118 c.Check(err, gc.IsNil) 119 c.Check(instances, gc.HasLen, 0) 120 } 121 122 func (suite *environSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) { 123 known := suite.addNode(allocatedNode) 124 suite.addNode(`{"system_id": "test2"}`) 125 unknown := instance.Id("unknown systemID") 126 instances, err := suite.makeEnviron().Instances([]instance.Id{known, unknown}) 127 128 c.Check(err, gc.Equals, environs.ErrPartialInstances) 129 c.Assert(instances, gc.HasLen, 2) 130 c.Check(instances[0].Id(), gc.Equals, known) 131 c.Check(instances[1], gc.IsNil) 132 } 133 134 func (suite *environSuite) TestStorageReturnsStorage(c *gc.C) { 135 env := suite.makeEnviron() 136 stor := env.Storage() 137 c.Check(stor, gc.NotNil) 138 // The Storage object is really a maasStorage. 139 specificStorage := stor.(*maasStorage) 140 // Its environment pointer refers back to its environment. 141 c.Check(specificStorage.environUnlocked, gc.Equals, env) 142 } 143 144 func decodeUserData(userData string) ([]byte, error) { 145 data, err := base64.StdEncoding.DecodeString(userData) 146 if err != nil { 147 return []byte(""), err 148 } 149 return utils.Gunzip(data) 150 } 151 152 func bootstrapContext(c *gc.C) environs.BootstrapContext { 153 return envtesting.NewBootstrapContext(coretesting.Context(c)) 154 } 155 156 func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) { 157 suite.setupFakeTools(c) 158 env := suite.makeEnviron() 159 // Create node 0: it will be used as the bootstrap node. 160 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 161 err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) 162 c.Assert(err, gc.IsNil) 163 // The bootstrap node has been acquired and started. 164 operations := suite.testMAASObject.TestServer.NodeOperations() 165 actions, found := operations["node0"] 166 c.Check(found, gc.Equals, true) 167 c.Check(actions, gc.DeepEquals, []string{"acquire", "start"}) 168 169 // Test the instance id is correctly recorded for the bootstrap node. 170 // Check that the state holds the id of the bootstrap machine. 171 stateData, err := bootstrap.LoadState(env.Storage()) 172 c.Assert(err, gc.IsNil) 173 c.Assert(stateData.StateInstances, gc.HasLen, 1) 174 insts, err := env.AllInstances() 175 c.Assert(err, gc.IsNil) 176 c.Assert(insts, gc.HasLen, 1) 177 c.Check(insts[0].Id(), gc.Equals, stateData.StateInstances[0]) 178 179 // Create node 1: it will be used as instance number 1. 180 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node1", "hostname": "host1"}`) 181 // TODO(wallyworld) - test instance metadata 182 instance, _ := testing.AssertStartInstance(c, env, "1") 183 c.Assert(err, gc.IsNil) 184 c.Check(instance, gc.NotNil) 185 186 // The instance number 1 has been acquired and started. 187 actions, found = operations["node1"] 188 c.Assert(found, gc.Equals, true) 189 c.Check(actions, gc.DeepEquals, []string{"acquire", "start"}) 190 191 // The value of the "user data" parameter used when starting the node 192 // contains the run cmd used to write the machine information onto 193 // the node's filesystem. 194 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 195 nodeRequestValues, found := requestValues["node1"] 196 c.Assert(found, gc.Equals, true) 197 c.Assert(len(nodeRequestValues), gc.Equals, 2) 198 userData := nodeRequestValues[1].Get("user_data") 199 decodedUserData, err := decodeUserData(userData) 200 c.Assert(err, gc.IsNil) 201 info := machineInfo{"host1"} 202 cloudinitRunCmd, err := info.cloudinitRunCmd() 203 c.Assert(err, gc.IsNil) 204 data, err := goyaml.Marshal(cloudinitRunCmd) 205 c.Assert(err, gc.IsNil) 206 c.Check(string(decodedUserData), gc.Matches, "(.|\n)*"+string(data)+"(\n|.)*") 207 208 // Trash the tools and try to start another instance. 209 envtesting.RemoveTools(c, env.Storage()) 210 instance, _, err = testing.StartInstance(env, "2") 211 c.Check(instance, gc.IsNil) 212 c.Check(err, jc.Satisfies, errors.IsNotFoundError) 213 } 214 215 func uint64p(val uint64) *uint64 { 216 return &val 217 } 218 219 func stringp(val string) *string { 220 return &val 221 } 222 223 func (suite *environSuite) TestAcquireNode(c *gc.C) { 224 stor := NewStorage(suite.makeEnviron()) 225 fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0] 226 env := suite.makeEnviron() 227 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 228 229 _, _, err := env.acquireNode(constraints.Value{}, tools.List{fakeTools}) 230 231 c.Check(err, gc.IsNil) 232 operations := suite.testMAASObject.TestServer.NodeOperations() 233 actions, found := operations["node0"] 234 c.Assert(found, gc.Equals, true) 235 c.Check(actions, gc.DeepEquals, []string{"acquire"}) 236 } 237 238 func (suite *environSuite) TestAcquireNodeTakesConstraintsIntoAccount(c *gc.C) { 239 stor := NewStorage(suite.makeEnviron()) 240 fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0] 241 env := suite.makeEnviron() 242 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 243 constraints := constraints.Value{Arch: stringp("arm"), Mem: uint64p(1024)} 244 245 _, _, err := env.acquireNode(constraints, tools.List{fakeTools}) 246 247 c.Check(err, gc.IsNil) 248 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 249 nodeRequestValues, found := requestValues["node0"] 250 c.Assert(found, gc.Equals, true) 251 c.Assert(nodeRequestValues[0].Get("arch"), gc.Equals, "arm") 252 c.Assert(nodeRequestValues[0].Get("mem"), gc.Equals, "1024") 253 } 254 255 func (suite *environSuite) TestAcquireNodePassedAgentName(c *gc.C) { 256 stor := NewStorage(suite.makeEnviron()) 257 fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0] 258 env := suite.makeEnviron() 259 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 260 261 _, _, err := env.acquireNode(constraints.Value{}, tools.List{fakeTools}) 262 263 c.Check(err, gc.IsNil) 264 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 265 nodeRequestValues, found := requestValues["node0"] 266 c.Assert(found, gc.Equals, true) 267 c.Assert(nodeRequestValues[0].Get("agent_name"), gc.Equals, exampleAgentName) 268 } 269 270 func (*environSuite) TestConvertConstraints(c *gc.C) { 271 var testValues = []struct { 272 constraints constraints.Value 273 expectedResult url.Values 274 }{ 275 {constraints.Value{Arch: stringp("arm")}, url.Values{"arch": {"arm"}}}, 276 {constraints.Value{CpuCores: uint64p(4)}, url.Values{"cpu_count": {"4"}}}, 277 {constraints.Value{Mem: uint64p(1024)}, url.Values{"mem": {"1024"}}}, 278 // CpuPower is ignored. 279 {constraints.Value{CpuPower: uint64p(1024)}, url.Values{}}, 280 // RootDisk is ignored. 281 {constraints.Value{RootDisk: uint64p(8192)}, url.Values{}}, 282 {constraints.Value{Tags: &[]string{"foo", "bar"}}, url.Values{"tags": {"foo,bar"}}}, 283 {constraints.Value{Arch: stringp("arm"), CpuCores: uint64p(4), Mem: uint64p(1024), CpuPower: uint64p(1024), RootDisk: uint64p(8192), Tags: &[]string{"foo", "bar"}}, url.Values{"arch": {"arm"}, "cpu_count": {"4"}, "mem": {"1024"}, "tags": {"foo,bar"}}}, 284 } 285 for _, test := range testValues { 286 c.Check(convertConstraints(test.constraints), gc.DeepEquals, test.expectedResult) 287 } 288 } 289 290 func (suite *environSuite) getInstance(systemId string) *maasInstance { 291 input := `{"system_id": "` + systemId + `"}` 292 node := suite.testMAASObject.TestServer.NewNode(input) 293 return &maasInstance{maasObject: &node, environ: suite.makeEnviron()} 294 } 295 296 func (suite *environSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) { 297 suite.getInstance("test1") 298 299 err := suite.makeEnviron().StopInstances([]instance.Instance{}) 300 c.Check(err, gc.IsNil) 301 operations := suite.testMAASObject.TestServer.NodeOperations() 302 c.Check(operations, gc.DeepEquals, map[string][]string{}) 303 } 304 305 func (suite *environSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) { 306 instance1 := suite.getInstance("test1") 307 instance2 := suite.getInstance("test2") 308 suite.getInstance("test3") 309 instances := []instance.Instance{instance1, instance2} 310 311 err := suite.makeEnviron().StopInstances(instances) 312 313 c.Check(err, gc.IsNil) 314 operations := suite.testMAASObject.TestServer.NodeOperations() 315 expectedOperations := map[string][]string{"test1": {"release"}, "test2": {"release"}} 316 c.Check(operations, gc.DeepEquals, expectedOperations) 317 } 318 319 func (suite *environSuite) TestStateInfo(c *gc.C) { 320 env := suite.makeEnviron() 321 hostname := "test" 322 input := `{"system_id": "system_id", "hostname": "` + hostname + `"}` 323 node := suite.testMAASObject.TestServer.NewNode(input) 324 testInstance := &maasInstance{maasObject: &node, environ: suite.makeEnviron()} 325 err := bootstrap.SaveState( 326 env.Storage(), 327 &bootstrap.BootstrapState{StateInstances: []instance.Id{testInstance.Id()}}) 328 c.Assert(err, gc.IsNil) 329 330 stateInfo, apiInfo, err := env.StateInfo() 331 c.Assert(err, gc.IsNil) 332 333 cfg := env.Config() 334 statePortSuffix := fmt.Sprintf(":%d", cfg.StatePort()) 335 apiPortSuffix := fmt.Sprintf(":%d", cfg.APIPort()) 336 c.Assert(stateInfo.Addrs, gc.DeepEquals, []string{hostname + statePortSuffix}) 337 c.Assert(apiInfo.Addrs, gc.DeepEquals, []string{hostname + apiPortSuffix}) 338 } 339 340 func (suite *environSuite) TestStateInfoFailsIfNoStateInstances(c *gc.C) { 341 env := suite.makeEnviron() 342 343 _, _, err := env.StateInfo() 344 345 c.Check(err, gc.Equals, environs.ErrNotBootstrapped) 346 } 347 348 func (suite *environSuite) TestDestroy(c *gc.C) { 349 env := suite.makeEnviron() 350 suite.getInstance("test1") 351 data := makeRandomBytes(10) 352 suite.testMAASObject.TestServer.NewFile("filename", data) 353 stor := env.Storage() 354 355 err := env.Destroy() 356 c.Check(err, gc.IsNil) 357 358 // Instances have been stopped. 359 operations := suite.testMAASObject.TestServer.NodeOperations() 360 expectedOperations := map[string][]string{"test1": {"release"}} 361 c.Check(operations, gc.DeepEquals, expectedOperations) 362 // Files have been cleaned up. 363 listing, err := storage.List(stor, "") 364 c.Assert(err, gc.IsNil) 365 c.Check(listing, gc.DeepEquals, []string{}) 366 } 367 368 // It would be nice if we could unit-test Bootstrap() in more detail, but 369 // at the time of writing that would require more support from gomaasapi's 370 // testing service than we have. 371 func (suite *environSuite) TestBootstrapSucceeds(c *gc.C) { 372 suite.setupFakeTools(c) 373 env := suite.makeEnviron() 374 suite.testMAASObject.TestServer.NewNode(`{"system_id": "thenode", "hostname": "host"}`) 375 err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) 376 c.Assert(err, gc.IsNil) 377 } 378 379 func (suite *environSuite) TestBootstrapFailsIfNoTools(c *gc.C) { 380 suite.setupFakeTools(c) 381 env := suite.makeEnviron() 382 // Can't RemoveAllTools, no public storage. 383 envtesting.RemoveTools(c, env.Storage()) 384 // Disable auto-uploading by setting the agent version. 385 cfg, err := env.Config().Apply(map[string]interface{}{ 386 "agent-version": version.Current.Number.String(), 387 }) 388 c.Assert(err, gc.IsNil) 389 err = env.SetConfig(cfg) 390 c.Assert(err, gc.IsNil) 391 err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) 392 c.Check(err, gc.ErrorMatches, "cannot find bootstrap tools.*") 393 } 394 395 func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) { 396 suite.setupFakeTools(c) 397 env := suite.makeEnviron() 398 err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) 399 // Since there are no nodes, the attempt to allocate one returns a 400 // 409: Conflict. 401 c.Check(err, gc.ErrorMatches, ".*409.*") 402 } 403 404 func (suite *environSuite) TestBootstrapIntegratesWithEnvirons(c *gc.C) { 405 suite.setupFakeTools(c) 406 env := suite.makeEnviron() 407 suite.testMAASObject.TestServer.NewNode(`{"system_id": "bootstrapnode", "hostname": "host"}`) 408 409 // bootstrap.Bootstrap calls Environ.Bootstrap. This works. 410 err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) 411 c.Assert(err, gc.IsNil) 412 } 413 414 func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) { 415 rc, _, err := source.Fetch(filename) 416 c.Assert(err, gc.IsNil) 417 defer rc.Close() 418 retrieved, err := ioutil.ReadAll(rc) 419 c.Assert(err, gc.IsNil) 420 c.Assert(retrieved, gc.DeepEquals, content) 421 } 422 423 func (suite *environSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) { 424 // Make an env configured with the stream. 425 testAttrs := maasEnvAttrs 426 testAttrs = testAttrs.Merge(coretesting.Attrs{ 427 "maas-server": suite.testMAASObject.TestServer.URL, 428 }) 429 if stream != "" { 430 testAttrs = testAttrs.Merge(coretesting.Attrs{ 431 "image-stream": stream, 432 }) 433 } 434 attrs := coretesting.FakeConfig().Merge(testAttrs) 435 cfg, err := config.New(config.NoDefaults, attrs) 436 c.Assert(err, gc.IsNil) 437 env, err := NewEnviron(cfg) 438 c.Assert(err, gc.IsNil) 439 440 // Add a dummy file to storage so we can use that to check the 441 // obtained source later. 442 data := makeRandomBytes(10) 443 stor := NewStorage(env) 444 err = stor.Put("images/filename", bytes.NewBuffer([]byte(data)), int64(len(data))) 445 c.Assert(err, gc.IsNil) 446 sources, err := imagemetadata.GetMetadataSources(env) 447 c.Assert(err, gc.IsNil) 448 c.Assert(len(sources), gc.Equals, 2) 449 assertSourceContents(c, sources[0], "filename", data) 450 url, err := sources[1].URL("") 451 c.Assert(err, gc.IsNil) 452 c.Assert(url, gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath)) 453 } 454 455 func (suite *environSuite) TestGetImageMetadataSources(c *gc.C) { 456 suite.assertGetImageMetadataSources(c, "", "releases") 457 suite.assertGetImageMetadataSources(c, "released", "releases") 458 suite.assertGetImageMetadataSources(c, "daily", "daily") 459 } 460 461 func (suite *environSuite) TestGetToolsMetadataSources(c *gc.C) { 462 env := suite.makeEnviron() 463 // Add a dummy file to storage so we can use that to check the 464 // obtained source later. 465 data := makeRandomBytes(10) 466 stor := NewStorage(env) 467 err := stor.Put("tools/filename", bytes.NewBuffer([]byte(data)), int64(len(data))) 468 c.Assert(err, gc.IsNil) 469 sources, err := envtools.GetMetadataSources(env) 470 c.Assert(err, gc.IsNil) 471 c.Assert(len(sources), gc.Equals, 1) 472 assertSourceContents(c, sources[0], "filename", data) 473 }