github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 "strings" 13 "text/template" 14 15 "github.com/juju/errors" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils" 18 "github.com/juju/utils/set" 19 gc "launchpad.net/gocheck" 20 "launchpad.net/gomaasapi" 21 "launchpad.net/goyaml" 22 23 "github.com/juju/juju/constraints" 24 "github.com/juju/juju/environs" 25 "github.com/juju/juju/environs/bootstrap" 26 "github.com/juju/juju/environs/config" 27 "github.com/juju/juju/environs/imagemetadata" 28 "github.com/juju/juju/environs/network" 29 "github.com/juju/juju/environs/simplestreams" 30 "github.com/juju/juju/environs/storage" 31 envtesting "github.com/juju/juju/environs/testing" 32 envtools "github.com/juju/juju/environs/tools" 33 "github.com/juju/juju/instance" 34 "github.com/juju/juju/juju/testing" 35 coretesting "github.com/juju/juju/testing" 36 "github.com/juju/juju/tools" 37 "github.com/juju/juju/version" 38 ) 39 40 type environSuite struct { 41 providerSuite 42 } 43 44 const ( 45 allocatedNode = `{"system_id": "test-allocated"}` 46 ) 47 48 var _ = gc.Suite(&environSuite{}) 49 50 // getTestConfig creates a customized sample MAAS provider configuration. 51 func getTestConfig(name, server, oauth, secret string) *config.Config { 52 ecfg, err := newConfig(map[string]interface{}{ 53 "name": name, 54 "maas-server": server, 55 "maas-oauth": oauth, 56 "admin-secret": secret, 57 "authorized-keys": "I-am-not-a-real-key", 58 }) 59 if err != nil { 60 panic(err) 61 } 62 return ecfg.Config 63 } 64 65 func (suite *environSuite) setupFakeProviderStateFile(c *gc.C) { 66 suite.testMAASObject.TestServer.NewFile(bootstrap.StateFile, []byte("test file content")) 67 } 68 69 func (suite *environSuite) setupFakeTools(c *gc.C) { 70 stor := NewStorage(suite.makeEnviron()) 71 envtesting.UploadFakeTools(c, stor) 72 } 73 74 func (suite *environSuite) setupFakeImageMetadata(c *gc.C) { 75 stor := NewStorage(suite.makeEnviron()) 76 UseTestImageMetadata(c, stor) 77 } 78 79 func (suite *environSuite) addNode(jsonText string) instance.Id { 80 node := suite.testMAASObject.TestServer.NewNode(jsonText) 81 resourceURI, _ := node.GetField("resource_uri") 82 return instance.Id(resourceURI) 83 } 84 85 func (suite *environSuite) TestInstancesReturnsInstances(c *gc.C) { 86 id := suite.addNode(allocatedNode) 87 instances, err := suite.makeEnviron().Instances([]instance.Id{id}) 88 89 c.Check(err, gc.IsNil) 90 c.Assert(instances, gc.HasLen, 1) 91 c.Assert(instances[0].Id(), gc.Equals, id) 92 } 93 94 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) { 95 suite.addNode(allocatedNode) 96 instances, err := suite.makeEnviron().Instances([]instance.Id{}) 97 98 c.Check(err, gc.Equals, environs.ErrNoInstances) 99 c.Check(instances, gc.IsNil) 100 } 101 102 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) { 103 suite.addNode(allocatedNode) 104 instances, err := suite.makeEnviron().Instances(nil) 105 106 c.Check(err, gc.Equals, environs.ErrNoInstances) 107 c.Check(instances, gc.IsNil) 108 } 109 110 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) { 111 instances, err := suite.makeEnviron().Instances([]instance.Id{"unknown"}) 112 c.Check(err, gc.Equals, environs.ErrNoInstances) 113 c.Check(instances, gc.IsNil) 114 } 115 116 func (suite *environSuite) TestAllInstances(c *gc.C) { 117 id := suite.addNode(allocatedNode) 118 instances, err := suite.makeEnviron().AllInstances() 119 120 c.Check(err, gc.IsNil) 121 c.Assert(instances, gc.HasLen, 1) 122 c.Assert(instances[0].Id(), gc.Equals, id) 123 } 124 125 func (suite *environSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) { 126 instances, err := suite.makeEnviron().AllInstances() 127 128 c.Check(err, gc.IsNil) 129 c.Check(instances, gc.HasLen, 0) 130 } 131 132 func (suite *environSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) { 133 known := suite.addNode(allocatedNode) 134 suite.addNode(`{"system_id": "test2"}`) 135 unknown := instance.Id("unknown systemID") 136 instances, err := suite.makeEnviron().Instances([]instance.Id{known, unknown}) 137 138 c.Check(err, gc.Equals, environs.ErrPartialInstances) 139 c.Assert(instances, gc.HasLen, 2) 140 c.Check(instances[0].Id(), gc.Equals, known) 141 c.Check(instances[1], gc.IsNil) 142 } 143 144 func (suite *environSuite) TestStorageReturnsStorage(c *gc.C) { 145 env := suite.makeEnviron() 146 stor := env.Storage() 147 c.Check(stor, gc.NotNil) 148 // The Storage object is really a maasStorage. 149 specificStorage := stor.(*maasStorage) 150 // Its environment pointer refers back to its environment. 151 c.Check(specificStorage.environUnlocked, gc.Equals, env) 152 } 153 154 func decodeUserData(userData string) ([]byte, error) { 155 data, err := base64.StdEncoding.DecodeString(userData) 156 if err != nil { 157 return []byte(""), err 158 } 159 return utils.Gunzip(data) 160 } 161 162 const lshwXMLTemplate = ` 163 <?xml version="1.0" standalone="yes" ?> 164 <!-- generated by lshw-B.02.16 --> 165 <list> 166 <node id="node1" claimed="true" class="system" handle="DMI:0001"> 167 <description>Computer</description> 168 <product>VirtualBox ()</product> 169 <width units="bits">64</width> 170 <node id="core" claimed="true" class="bus" handle="DMI:0008"> 171 <description>Motherboard</description> 172 <node id="pci" claimed="true" class="bridge" handle="PCIBUS:0000:00"> 173 <description>Host bridge</description>{{range $m, $n := .}} 174 <node id="network:0" claimed="true" class="network" handle="PCI:0000:00:03.0"> 175 <description>Ethernet interface</description> 176 <product>82540EM Gigabit Ethernet Controller</product> 177 <logicalname>{{$n}}</logicalname> 178 <serial>{{$m}}</serial> 179 </node>{{end}} 180 </node> 181 </node> 182 </node> 183 </list> 184 </list> 185 ` 186 187 func (suite *environSuite) generateHWTemplate(netMacs map[string]string) (string, error) { 188 tmpl, err := template.New("test").Parse(lshwXMLTemplate) 189 if err != nil { 190 return "", err 191 } 192 var buf bytes.Buffer 193 err = tmpl.Execute(&buf, netMacs) 194 if err != nil { 195 return "", err 196 } 197 return string(buf.Bytes()), nil 198 } 199 200 func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) { 201 suite.setupFakeTools(c) 202 env := suite.makeEnviron() 203 // Create node 0: it will be used as the bootstrap node. 204 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 205 lshwXML, err := suite.generateHWTemplate(map[string]string{"aa:bb:cc:dd:ee:f0": "eth0"}) 206 c.Assert(err, gc.IsNil) 207 suite.testMAASObject.TestServer.AddNodeDetails("node0", lshwXML) 208 err = bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 209 c.Assert(err, gc.IsNil) 210 // The bootstrap node has been acquired and started. 211 operations := suite.testMAASObject.TestServer.NodeOperations() 212 actions, found := operations["node0"] 213 c.Check(found, gc.Equals, true) 214 c.Check(actions, gc.DeepEquals, []string{"acquire", "start"}) 215 216 // Test the instance id is correctly recorded for the bootstrap node. 217 // Check that the state holds the id of the bootstrap machine. 218 stateData, err := bootstrap.LoadState(env.Storage()) 219 c.Assert(err, gc.IsNil) 220 c.Assert(stateData.StateInstances, gc.HasLen, 1) 221 insts, err := env.AllInstances() 222 c.Assert(err, gc.IsNil) 223 c.Assert(insts, gc.HasLen, 1) 224 c.Check(insts[0].Id(), gc.Equals, stateData.StateInstances[0]) 225 226 // Create node 1: it will be used as instance number 1. 227 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node1", "hostname": "host1"}`) 228 lshwXML, err = suite.generateHWTemplate(map[string]string{"aa:bb:cc:dd:ee:f1": "eth0"}) 229 c.Assert(err, gc.IsNil) 230 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 231 // TODO(wallyworld) - test instance metadata 232 instance, _ := testing.AssertStartInstance(c, env, "1") 233 c.Assert(err, gc.IsNil) 234 c.Check(instance, gc.NotNil) 235 236 // The instance number 1 has been acquired and started. 237 actions, found = operations["node1"] 238 c.Assert(found, gc.Equals, true) 239 c.Check(actions, gc.DeepEquals, []string{"acquire", "start"}) 240 241 // The value of the "user data" parameter used when starting the node 242 // contains the run cmd used to write the machine information onto 243 // the node's filesystem. 244 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 245 nodeRequestValues, found := requestValues["node1"] 246 c.Assert(found, gc.Equals, true) 247 c.Assert(len(nodeRequestValues), gc.Equals, 2) 248 userData := nodeRequestValues[1].Get("user_data") 249 decodedUserData, err := decodeUserData(userData) 250 c.Assert(err, gc.IsNil) 251 info := machineInfo{"host1"} 252 cloudinitRunCmd, err := info.cloudinitRunCmd() 253 c.Assert(err, gc.IsNil) 254 data, err := goyaml.Marshal(cloudinitRunCmd) 255 c.Assert(err, gc.IsNil) 256 c.Check(string(decodedUserData), gc.Matches, "(.|\n)*"+string(data)+"(\n|.)*") 257 258 // Trash the tools and try to start another instance. 259 envtesting.RemoveTools(c, env.Storage()) 260 instance, _, _, err = testing.StartInstance(env, "2") 261 c.Check(instance, gc.IsNil) 262 c.Check(err, jc.Satisfies, errors.IsNotFound) 263 } 264 265 func uint64p(val uint64) *uint64 { 266 return &val 267 } 268 269 func stringp(val string) *string { 270 return &val 271 } 272 273 func (suite *environSuite) TestAcquireNode(c *gc.C) { 274 stor := NewStorage(suite.makeEnviron()) 275 fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0] 276 env := suite.makeEnviron() 277 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 278 279 _, _, err := env.acquireNode("", constraints.Value{}, nil, nil, tools.List{fakeTools}) 280 281 c.Check(err, gc.IsNil) 282 operations := suite.testMAASObject.TestServer.NodeOperations() 283 actions, found := operations["node0"] 284 c.Assert(found, gc.Equals, true) 285 c.Check(actions, gc.DeepEquals, []string{"acquire"}) 286 287 // no "name" parameter should have been passed through 288 values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0] 289 _, found = values["name"] 290 c.Assert(found, jc.IsFalse) 291 } 292 293 func (suite *environSuite) TestAcquireNodeByName(c *gc.C) { 294 stor := NewStorage(suite.makeEnviron()) 295 fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0] 296 env := suite.makeEnviron() 297 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 298 299 _, _, err := env.acquireNode("host0", constraints.Value{}, nil, nil, tools.List{fakeTools}) 300 301 c.Check(err, gc.IsNil) 302 operations := suite.testMAASObject.TestServer.NodeOperations() 303 actions, found := operations["node0"] 304 c.Assert(found, gc.Equals, true) 305 c.Check(actions, gc.DeepEquals, []string{"acquire"}) 306 307 // no "name" parameter should have been passed through 308 values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0] 309 nodeName := values.Get("name") 310 c.Assert(nodeName, gc.Equals, "host0") 311 } 312 313 func (suite *environSuite) TestAcquireNodeTakesConstraintsIntoAccount(c *gc.C) { 314 stor := NewStorage(suite.makeEnviron()) 315 fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0] 316 env := suite.makeEnviron() 317 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 318 constraints := constraints.Value{Arch: stringp("arm"), Mem: uint64p(1024)} 319 320 _, _, err := env.acquireNode("", constraints, nil, nil, tools.List{fakeTools}) 321 322 c.Check(err, gc.IsNil) 323 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 324 nodeRequestValues, found := requestValues["node0"] 325 c.Assert(found, gc.Equals, true) 326 c.Assert(nodeRequestValues[0].Get("arch"), gc.Equals, "arm") 327 c.Assert(nodeRequestValues[0].Get("mem"), gc.Equals, "1024") 328 } 329 330 func (suite *environSuite) TestAcquireNodePassedAgentName(c *gc.C) { 331 stor := NewStorage(suite.makeEnviron()) 332 fakeTools := envtesting.MustUploadFakeToolsVersions(stor, version.Current)[0] 333 env := suite.makeEnviron() 334 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 335 336 _, _, err := env.acquireNode("", constraints.Value{}, nil, nil, tools.List{fakeTools}) 337 338 c.Check(err, gc.IsNil) 339 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 340 nodeRequestValues, found := requestValues["node0"] 341 c.Assert(found, gc.Equals, true) 342 c.Assert(nodeRequestValues[0].Get("agent_name"), gc.Equals, exampleAgentName) 343 } 344 345 var testValues = []struct { 346 constraints constraints.Value 347 expectedResult url.Values 348 }{ 349 {constraints.Value{Arch: stringp("arm")}, url.Values{"arch": {"arm"}}}, 350 {constraints.Value{CpuCores: uint64p(4)}, url.Values{"cpu_count": {"4"}}}, 351 {constraints.Value{Mem: uint64p(1024)}, url.Values{"mem": {"1024"}}}, 352 353 // CpuPower is ignored. 354 {constraints.Value{CpuPower: uint64p(1024)}, url.Values{}}, 355 356 // RootDisk is ignored. 357 {constraints.Value{RootDisk: uint64p(8192)}, url.Values{}}, 358 {constraints.Value{Tags: &[]string{"foo", "bar"}}, url.Values{"tags": {"foo,bar"}}}, 359 {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"}}}, 360 } 361 362 func (*environSuite) TestConvertConstraints(c *gc.C) { 363 for _, test := range testValues { 364 c.Check(convertConstraints(test.constraints), gc.DeepEquals, test.expectedResult) 365 } 366 } 367 368 var testNetworkValues = []struct { 369 includeNetworks []string 370 excludeNetworks []string 371 expectedResult url.Values 372 }{ 373 { 374 nil, 375 nil, 376 url.Values{}, 377 }, 378 { 379 []string{"included_net_1"}, 380 nil, 381 url.Values{"networks": {"included_net_1"}}, 382 }, 383 { 384 nil, 385 []string{"excluded_net_1"}, 386 url.Values{"not_networks": {"excluded_net_1"}}, 387 }, 388 { 389 []string{"included_net_1", "included_net_2"}, 390 []string{"excluded_net_1", "excluded_net_2"}, 391 url.Values{ 392 "networks": {"included_net_1", "included_net_2"}, 393 "not_networks": {"excluded_net_1", "excluded_net_2"}, 394 }, 395 }, 396 } 397 398 func (*environSuite) TestConvertNetworks(c *gc.C) { 399 for _, test := range testNetworkValues { 400 var vals = url.Values{} 401 addNetworks(vals, test.includeNetworks, test.excludeNetworks) 402 c.Check(vals, gc.DeepEquals, test.expectedResult) 403 } 404 } 405 406 func (suite *environSuite) getInstance(systemId string) *maasInstance { 407 input := fmt.Sprintf(`{"system_id": %q}`, systemId) 408 node := suite.testMAASObject.TestServer.NewNode(input) 409 return &maasInstance{maasObject: &node, environ: suite.makeEnviron()} 410 } 411 412 func (suite *environSuite) getNetwork(name string, id int, vlanTag int) *gomaasapi.MAASObject { 413 var vlan string 414 if vlanTag == 0 { 415 vlan = "null" 416 } else { 417 vlan = fmt.Sprintf("%d", vlanTag) 418 } 419 var input string 420 input = fmt.Sprintf(`{"name": %q, "ip":"192.168.%d.1", "netmask": "255.255.255.0",`+ 421 `"vlan_tag": %s, "description": "%s_%d_%d" }`, name, id, vlan, name, id, vlanTag) 422 network := suite.testMAASObject.TestServer.NewNetwork(input) 423 return &network 424 } 425 426 func (suite *environSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) { 427 suite.getInstance("test1") 428 429 err := suite.makeEnviron().StopInstances() 430 c.Check(err, gc.IsNil) 431 operations := suite.testMAASObject.TestServer.NodeOperations() 432 c.Check(operations, gc.DeepEquals, map[string][]string{}) 433 } 434 435 func (suite *environSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) { 436 suite.getInstance("test1") 437 suite.getInstance("test2") 438 suite.getInstance("test3") 439 // mark test1 and test2 as being allocated, but not test3. 440 // The release operation will ignore test3. 441 suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true 442 suite.testMAASObject.TestServer.OwnedNodes()["test2"] = true 443 444 err := suite.makeEnviron().StopInstances("test1", "test2", "test3") 445 c.Check(err, gc.IsNil) 446 operations := suite.testMAASObject.TestServer.NodesOperations() 447 c.Check(operations, gc.DeepEquals, []string{"release"}) 448 c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse) 449 c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test2"], jc.IsFalse) 450 } 451 452 func (suite *environSuite) TestStateInfo(c *gc.C) { 453 env := suite.makeEnviron() 454 hostname := "test" 455 input := `{"system_id": "system_id", "hostname": "` + hostname + `"}` 456 node := suite.testMAASObject.TestServer.NewNode(input) 457 testInstance := &maasInstance{maasObject: &node, environ: suite.makeEnviron()} 458 err := bootstrap.SaveState( 459 env.Storage(), 460 &bootstrap.BootstrapState{StateInstances: []instance.Id{testInstance.Id()}}) 461 c.Assert(err, gc.IsNil) 462 463 stateInfo, apiInfo, err := env.StateInfo() 464 c.Assert(err, gc.IsNil) 465 466 cfg := env.Config() 467 statePortSuffix := fmt.Sprintf(":%d", cfg.StatePort()) 468 apiPortSuffix := fmt.Sprintf(":%d", cfg.APIPort()) 469 c.Assert(stateInfo.Addrs, gc.DeepEquals, []string{hostname + statePortSuffix}) 470 c.Assert(apiInfo.Addrs, gc.DeepEquals, []string{hostname + apiPortSuffix}) 471 } 472 473 func (suite *environSuite) TestStateInfoFailsIfNoStateInstances(c *gc.C) { 474 env := suite.makeEnviron() 475 476 _, _, err := env.StateInfo() 477 478 c.Check(err, gc.Equals, environs.ErrNotBootstrapped) 479 } 480 481 func (suite *environSuite) TestDestroy(c *gc.C) { 482 env := suite.makeEnviron() 483 suite.getInstance("test1") 484 suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true // simulate acquire 485 data := makeRandomBytes(10) 486 suite.testMAASObject.TestServer.NewFile("filename", data) 487 stor := env.Storage() 488 489 err := env.Destroy() 490 c.Check(err, gc.IsNil) 491 492 // Instances have been stopped. 493 operations := suite.testMAASObject.TestServer.NodesOperations() 494 c.Check(operations, gc.DeepEquals, []string{"release"}) 495 c.Check(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse) 496 // Files have been cleaned up. 497 listing, err := storage.List(stor, "") 498 c.Assert(err, gc.IsNil) 499 c.Check(listing, gc.DeepEquals, []string{}) 500 } 501 502 func (suite *environSuite) TestBootstrapSucceeds(c *gc.C) { 503 suite.setupFakeTools(c) 504 env := suite.makeEnviron() 505 suite.testMAASObject.TestServer.NewNode(`{"system_id": "thenode", "hostname": "host"}`) 506 lshwXML, err := suite.generateHWTemplate(map[string]string{"aa:bb:cc:dd:ee:f0": "eth0"}) 507 c.Assert(err, gc.IsNil) 508 suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML) 509 err = bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 510 c.Assert(err, gc.IsNil) 511 } 512 513 func (suite *environSuite) TestBootstrapFailsIfNoTools(c *gc.C) { 514 suite.setupFakeTools(c) 515 env := suite.makeEnviron() 516 // Can't RemoveAllTools, no public storage. 517 envtesting.RemoveTools(c, env.Storage()) 518 // Disable auto-uploading by setting the agent version. 519 cfg, err := env.Config().Apply(map[string]interface{}{ 520 "agent-version": version.Current.Number.String(), 521 }) 522 c.Assert(err, gc.IsNil) 523 err = env.SetConfig(cfg) 524 c.Assert(err, gc.IsNil) 525 err = bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 526 stripped := strings.Replace(err.Error(), "\n", "", -1) 527 c.Check(stripped, 528 gc.Matches, 529 "cannot upload bootstrap tools: Juju cannot bootstrap because no tools are available for your environment.*") 530 } 531 532 func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) { 533 suite.setupFakeTools(c) 534 env := suite.makeEnviron() 535 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 536 // Since there are no nodes, the attempt to allocate one returns a 537 // 409: Conflict. 538 c.Check(err, gc.ErrorMatches, ".*409.*") 539 } 540 541 func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) { 542 rc, _, err := source.Fetch(filename) 543 c.Assert(err, gc.IsNil) 544 defer rc.Close() 545 retrieved, err := ioutil.ReadAll(rc) 546 c.Assert(err, gc.IsNil) 547 c.Assert(retrieved, gc.DeepEquals, content) 548 } 549 550 func (suite *environSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) { 551 // Make an env configured with the stream. 552 testAttrs := maasEnvAttrs 553 testAttrs = testAttrs.Merge(coretesting.Attrs{ 554 "maas-server": suite.testMAASObject.TestServer.URL, 555 }) 556 if stream != "" { 557 testAttrs = testAttrs.Merge(coretesting.Attrs{ 558 "image-stream": stream, 559 }) 560 } 561 attrs := coretesting.FakeConfig().Merge(testAttrs) 562 cfg, err := config.New(config.NoDefaults, attrs) 563 c.Assert(err, gc.IsNil) 564 env, err := NewEnviron(cfg) 565 c.Assert(err, gc.IsNil) 566 567 // Add a dummy file to storage so we can use that to check the 568 // obtained source later. 569 data := makeRandomBytes(10) 570 stor := NewStorage(env) 571 err = stor.Put("images/filename", bytes.NewBuffer([]byte(data)), int64(len(data))) 572 c.Assert(err, gc.IsNil) 573 sources, err := imagemetadata.GetMetadataSources(env) 574 c.Assert(err, gc.IsNil) 575 c.Assert(len(sources), gc.Equals, 2) 576 assertSourceContents(c, sources[0], "filename", data) 577 url, err := sources[1].URL("") 578 c.Assert(err, gc.IsNil) 579 c.Assert(url, gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath)) 580 } 581 582 func (suite *environSuite) TestGetImageMetadataSources(c *gc.C) { 583 suite.assertGetImageMetadataSources(c, "", "releases") 584 suite.assertGetImageMetadataSources(c, "released", "releases") 585 suite.assertGetImageMetadataSources(c, "daily", "daily") 586 } 587 588 func (suite *environSuite) TestGetToolsMetadataSources(c *gc.C) { 589 env := suite.makeEnviron() 590 // Add a dummy file to storage so we can use that to check the 591 // obtained source later. 592 data := makeRandomBytes(10) 593 stor := NewStorage(env) 594 err := stor.Put("tools/filename", bytes.NewBuffer([]byte(data)), int64(len(data))) 595 c.Assert(err, gc.IsNil) 596 sources, err := envtools.GetMetadataSources(env) 597 c.Assert(err, gc.IsNil) 598 c.Assert(len(sources), gc.Equals, 1) 599 assertSourceContents(c, sources[0], "filename", data) 600 } 601 602 func (suite *environSuite) TestSupportedArchitectures(c *gc.C) { 603 suite.setupFakeImageMetadata(c) 604 env := suite.makeEnviron() 605 a, err := env.SupportedArchitectures() 606 c.Assert(err, gc.IsNil) 607 c.Assert(a, jc.SameContents, []string{"amd64"}) 608 } 609 610 func (suite *environSuite) TestConstraintsValidator(c *gc.C) { 611 suite.setupFakeImageMetadata(c) 612 env := suite.makeEnviron() 613 validator, err := env.ConstraintsValidator() 614 c.Assert(err, gc.IsNil) 615 cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo") 616 unsupported, err := validator.Validate(cons) 617 c.Assert(err, gc.IsNil) 618 c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type"}) 619 } 620 621 func (suite *environSuite) TestConstraintsValidatorVocab(c *gc.C) { 622 suite.setupFakeImageMetadata(c) 623 env := suite.makeEnviron() 624 validator, err := env.ConstraintsValidator() 625 c.Assert(err, gc.IsNil) 626 cons := constraints.MustParse("arch=ppc64") 627 _, err = validator.Validate(cons) 628 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64\nvalid values are:.*") 629 } 630 631 func (suite *environSuite) TestGetNetworkMACs(c *gc.C) { 632 suite.setupFakeTools(c) 633 env := suite.makeEnviron() 634 635 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_1"}`) 636 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_2"}`) 637 suite.testMAASObject.TestServer.NewNetwork(`{"name": "net_1"}`) 638 suite.testMAASObject.TestServer.NewNetwork(`{"name": "net_2"}`) 639 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_2", "aa:bb:cc:dd:ee:22") 640 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_1", "aa:bb:cc:dd:ee:11") 641 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_1", "aa:bb:cc:dd:ee:21") 642 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_2", "aa:bb:cc:dd:ee:12") 643 644 networks, err := env.getNetworkMACs("net_1") 645 c.Assert(err, gc.IsNil) 646 c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:11", "aa:bb:cc:dd:ee:21"}) 647 648 networks, err = env.getNetworkMACs("net_2") 649 c.Assert(err, gc.IsNil) 650 c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:12", "aa:bb:cc:dd:ee:22"}) 651 652 networks, err = env.getNetworkMACs("net_3") 653 c.Check(networks, gc.HasLen, 0) 654 c.Assert(err, gc.IsNil) 655 } 656 657 func (suite *environSuite) TestGetInstanceNetworks(c *gc.C) { 658 suite.getNetwork("test_network", 123, 321) 659 test_instance := suite.getInstance("instance_for_network") 660 suite.testMAASObject.TestServer.ConnectNodeToNetwork("instance_for_network", "test_network") 661 networks, err := suite.makeEnviron().getInstanceNetworks(test_instance) 662 c.Assert(err, gc.IsNil) 663 c.Check(networks, gc.DeepEquals, []networkDetails{ 664 {Name: "test_network", IP: "192.168.123.1", Mask: "255.255.255.0", VLANTag: 321, 665 Description: "test_network_123_321"}, 666 }) 667 } 668 669 // A typical lshw XML dump with lots of things left out. 670 const lshwXMLTestExtractInterfaces = ` 671 <?xml version="1.0" standalone="yes" ?> 672 <!-- generated by lshw-B.02.16 --> 673 <list> 674 <node id="machine" claimed="true" class="system" handle="DMI:0001"> 675 <description>Notebook</description> 676 <product>MyMachine</product> 677 <version>1.0</version> 678 <width units="bits">64</width> 679 <node id="core" claimed="true" class="bus" handle="DMI:0002"> 680 <description>Motherboard</description> 681 <node id="cpu" claimed="true" class="processor" handle="DMI:0004"> 682 <description>CPU</description> 683 <node id="pci:2" claimed="true" class="bridge" handle="PCIBUS:0000:03"> 684 <node id="network" claimed="true" class="network" handle="PCI:0000:03:00.0"> 685 <logicalname>wlan0</logicalname> 686 <serial>aa:bb:cc:dd:ee:ff</serial> 687 </node> 688 <node id="network" claimed="true" class="network" handle="PCI:0000:04:00.0"> 689 <logicalname>eth0</logicalname> 690 <serial>aa:bb:cc:dd:ee:f1</serial> 691 </node> 692 </node> 693 </node> 694 </node> 695 <node id="network:0" claimed="true" class="network" handle=""> 696 <logicalname>vnet1</logicalname> 697 <serial>aa:bb:cc:dd:ee:f2</serial> 698 </node> 699 </node> 700 </list> 701 ` 702 703 func (suite *environSuite) TestExtractInterfaces(c *gc.C) { 704 inst := suite.getInstance("testInstance") 705 interfaces, err := extractInterfaces(inst, []byte(lshwXMLTestExtractInterfaces)) 706 c.Assert(err, gc.IsNil) 707 c.Check(interfaces, jc.DeepEquals, map[string]string{ 708 "aa:bb:cc:dd:ee:ff": "wlan0", 709 "aa:bb:cc:dd:ee:f1": "eth0", 710 "aa:bb:cc:dd:ee:f2": "vnet1", 711 }) 712 } 713 714 func (suite *environSuite) TestGetInstanceNetworkInterfaces(c *gc.C) { 715 inst := suite.getInstance("testInstance") 716 templateInterfaces := map[string]string{ 717 "aa:bb:cc:dd:ee:ff": "wlan0", 718 "aa:bb:cc:dd:ee:f1": "eth0", 719 "aa:bb:cc:dd:ee:f2": "vnet1", 720 } 721 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 722 c.Assert(err, gc.IsNil) 723 724 suite.testMAASObject.TestServer.AddNodeDetails("testInstance", lshwXML) 725 interfaces, err := inst.environ.getInstanceNetworkInterfaces(inst) 726 c.Assert(err, gc.IsNil) 727 c.Check(interfaces, jc.DeepEquals, templateInterfaces) 728 } 729 730 func (suite *environSuite) TestSetupNetworks(c *gc.C) { 731 test_instance := suite.getInstance("node1") 732 templateInterfaces := map[string]string{ 733 "aa:bb:cc:dd:ee:ff": "wlan0", 734 "aa:bb:cc:dd:ee:f1": "eth0", 735 "aa:bb:cc:dd:ee:f2": "vnet1", 736 } 737 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 738 c.Assert(err, gc.IsNil) 739 740 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 741 suite.getNetwork("LAN", 2, 42) 742 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1") 743 suite.getNetwork("Virt", 3, 0) 744 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2") 745 suite.getNetwork("WLAN", 1, 0) 746 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff") 747 networkInfo, err := suite.makeEnviron().setupNetworks(test_instance, set.NewStrings("LAN", "Virt")) 748 c.Assert(err, gc.IsNil) 749 750 // Note: order of networks is based on lshwXML 751 c.Check(networkInfo, jc.SameContents, []network.Info{ 752 network.Info{ 753 MACAddress: "aa:bb:cc:dd:ee:ff", 754 CIDR: "192.168.1.1/24", 755 NetworkName: "WLAN", 756 ProviderId: "WLAN", 757 VLANTag: 0, 758 InterfaceName: "wlan0", 759 IsVirtual: false, 760 Disabled: true, 761 }, 762 network.Info{ 763 MACAddress: "aa:bb:cc:dd:ee:f1", 764 CIDR: "192.168.2.1/24", 765 NetworkName: "LAN", 766 ProviderId: "LAN", 767 VLANTag: 42, 768 InterfaceName: "eth0", 769 IsVirtual: true, 770 Disabled: false, 771 }, 772 network.Info{ 773 MACAddress: "aa:bb:cc:dd:ee:f2", 774 CIDR: "192.168.3.1/24", 775 NetworkName: "Virt", 776 ProviderId: "Virt", 777 VLANTag: 0, 778 InterfaceName: "vnet1", 779 IsVirtual: false, 780 Disabled: false, 781 }, 782 }) 783 } 784 785 // The same test, but now "Virt" network does not have matched MAC address 786 func (suite *environSuite) TestSetupNetworksPartialMatch(c *gc.C) { 787 test_instance := suite.getInstance("node1") 788 templateInterfaces := map[string]string{ 789 "aa:bb:cc:dd:ee:ff": "wlan0", 790 "aa:bb:cc:dd:ee:f1": "eth0", 791 "aa:bb:cc:dd:ee:f2": "vnet1", 792 } 793 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 794 c.Assert(err, gc.IsNil) 795 796 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 797 suite.getNetwork("LAN", 2, 42) 798 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1") 799 suite.getNetwork("Virt", 3, 0) 800 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3") 801 networkInfo, err := suite.makeEnviron().setupNetworks(test_instance, set.NewStrings("LAN")) 802 c.Assert(err, gc.IsNil) 803 804 // Note: order of networks is based on lshwXML 805 c.Check(networkInfo, jc.SameContents, []network.Info{ 806 network.Info{ 807 MACAddress: "aa:bb:cc:dd:ee:f1", 808 CIDR: "192.168.2.1/24", 809 NetworkName: "LAN", 810 ProviderId: "LAN", 811 VLANTag: 42, 812 InterfaceName: "eth0", 813 IsVirtual: true, 814 Disabled: false, 815 }, 816 }) 817 } 818 819 // The same test, but now no networks have matched MAC 820 func (suite *environSuite) TestSetupNetworksNoMatch(c *gc.C) { 821 test_instance := suite.getInstance("node1") 822 templateInterfaces := map[string]string{ 823 "aa:bb:cc:dd:ee:ff": "wlan0", 824 "aa:bb:cc:dd:ee:f1": "eth0", 825 "aa:bb:cc:dd:ee:f2": "vnet1", 826 } 827 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 828 c.Assert(err, gc.IsNil) 829 830 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 831 suite.getNetwork("Virt", 3, 0) 832 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3") 833 networkInfo, err := suite.makeEnviron().setupNetworks(test_instance, set.NewStrings("Virt")) 834 c.Assert(err, gc.IsNil) 835 836 // Note: order of networks is based on lshwXML 837 c.Check(networkInfo, gc.HasLen, 0) 838 } 839 840 func (suite *environSuite) TestSupportNetworks(c *gc.C) { 841 env := suite.makeEnviron() 842 c.Assert(env.SupportNetworks(), jc.IsTrue) 843 }