github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "net" 13 "net/url" 14 "text/template" 15 16 "github.com/juju/errors" 17 "github.com/juju/names" 18 jc "github.com/juju/testing/checkers" 19 "github.com/juju/utils" 20 "github.com/juju/utils/set" 21 gc "gopkg.in/check.v1" 22 goyaml "gopkg.in/yaml.v1" 23 "launchpad.net/gomaasapi" 24 25 "github.com/juju/juju/cloudconfig/cloudinit" 26 "github.com/juju/juju/constraints" 27 "github.com/juju/juju/environs" 28 "github.com/juju/juju/environs/bootstrap" 29 "github.com/juju/juju/environs/config" 30 "github.com/juju/juju/environs/simplestreams" 31 envstorage "github.com/juju/juju/environs/storage" 32 envtesting "github.com/juju/juju/environs/testing" 33 envtools "github.com/juju/juju/environs/tools" 34 "github.com/juju/juju/instance" 35 "github.com/juju/juju/juju/testing" 36 "github.com/juju/juju/network" 37 "github.com/juju/juju/provider/common" 38 "github.com/juju/juju/storage" 39 coretesting "github.com/juju/juju/testing" 40 "github.com/juju/juju/version" 41 ) 42 43 type environSuite struct { 44 providerSuite 45 } 46 47 const ( 48 allocatedNode = `{"system_id": "test-allocated"}` 49 ) 50 51 var _ = gc.Suite(&environSuite{}) 52 53 // getTestConfig creates a customized sample MAAS provider configuration. 54 func getTestConfig(name, server, oauth, secret string) *config.Config { 55 ecfg, err := newConfig(map[string]interface{}{ 56 "name": name, 57 "maas-server": server, 58 "maas-oauth": oauth, 59 "admin-secret": secret, 60 "authorized-keys": "I-am-not-a-real-key", 61 }) 62 if err != nil { 63 panic(err) 64 } 65 return ecfg.Config 66 } 67 68 func (suite *environSuite) setupFakeTools(c *gc.C) { 69 storageDir := c.MkDir() 70 suite.PatchValue(&envtools.DefaultBaseURL, "file://"+storageDir+"/tools") 71 suite.UploadFakeToolsToDirectory(c, storageDir, "released", "released") 72 } 73 74 func (suite *environSuite) addNode(jsonText string) instance.Id { 75 node := suite.testMAASObject.TestServer.NewNode(jsonText) 76 resourceURI, _ := node.GetField("resource_uri") 77 return instance.Id(resourceURI) 78 } 79 80 func (suite *environSuite) TestInstancesReturnsInstances(c *gc.C) { 81 id := suite.addNode(allocatedNode) 82 instances, err := suite.makeEnviron().Instances([]instance.Id{id}) 83 84 c.Check(err, jc.ErrorIsNil) 85 c.Assert(instances, gc.HasLen, 1) 86 c.Assert(instances[0].Id(), gc.Equals, id) 87 } 88 89 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) { 90 suite.addNode(allocatedNode) 91 instances, err := suite.makeEnviron().Instances([]instance.Id{}) 92 93 c.Check(err, gc.Equals, environs.ErrNoInstances) 94 c.Check(instances, gc.IsNil) 95 } 96 97 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) { 98 suite.addNode(allocatedNode) 99 instances, err := suite.makeEnviron().Instances(nil) 100 101 c.Check(err, gc.Equals, environs.ErrNoInstances) 102 c.Check(instances, gc.IsNil) 103 } 104 105 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) { 106 instances, err := suite.makeEnviron().Instances([]instance.Id{"unknown"}) 107 c.Check(err, gc.Equals, environs.ErrNoInstances) 108 c.Check(instances, gc.IsNil) 109 } 110 111 func (suite *environSuite) TestAllInstances(c *gc.C) { 112 id := suite.addNode(allocatedNode) 113 instances, err := suite.makeEnviron().AllInstances() 114 115 c.Check(err, jc.ErrorIsNil) 116 c.Assert(instances, gc.HasLen, 1) 117 c.Assert(instances[0].Id(), gc.Equals, id) 118 } 119 120 func (suite *environSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) { 121 instances, err := suite.makeEnviron().AllInstances() 122 123 c.Check(err, jc.ErrorIsNil) 124 c.Check(instances, gc.HasLen, 0) 125 } 126 127 func (suite *environSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) { 128 known := suite.addNode(allocatedNode) 129 suite.addNode(`{"system_id": "test2"}`) 130 unknown := instance.Id("unknown systemID") 131 instances, err := suite.makeEnviron().Instances([]instance.Id{known, unknown}) 132 133 c.Check(err, gc.Equals, environs.ErrPartialInstances) 134 c.Assert(instances, gc.HasLen, 2) 135 c.Check(instances[0].Id(), gc.Equals, known) 136 c.Check(instances[1], gc.IsNil) 137 } 138 139 func (suite *environSuite) TestStorageReturnsStorage(c *gc.C) { 140 env := suite.makeEnviron() 141 stor := env.Storage() 142 c.Check(stor, gc.NotNil) 143 // The Storage object is really a maasStorage. 144 specificStorage := stor.(*maasStorage) 145 // Its environment pointer refers back to its environment. 146 c.Check(specificStorage.environUnlocked, gc.Equals, env) 147 } 148 149 func decodeUserData(userData string) ([]byte, error) { 150 data, err := base64.StdEncoding.DecodeString(userData) 151 if err != nil { 152 return []byte(""), err 153 } 154 return utils.Gunzip(data) 155 } 156 157 const lshwXMLTemplate = ` 158 <?xml version="1.0" standalone="yes" ?> 159 <!-- generated by lshw-B.02.16 --> 160 <list> 161 <node id="node1" claimed="true" class="system" handle="DMI:0001"> 162 <description>Computer</description> 163 <product>VirtualBox ()</product> 164 <width units="bits">64</width> 165 <node id="core" claimed="true" class="bus" handle="DMI:0008"> 166 <description>Motherboard</description> 167 <node id="pci" claimed="true" class="bridge" handle="PCIBUS:0000:00"> 168 <description>Host bridge</description>{{$list := .}}{{range $mac, $ifi := $list}} 169 <node id="network{{if gt (len $list) 1}}:{{$ifi.DeviceIndex}}{{end}}"{{if $ifi.Disabled}} disabled="true"{{end}} claimed="true" class="network" handle="PCI:0000:00:03.0"> 170 <description>Ethernet interface</description> 171 <product>82540EM Gigabit Ethernet Controller</product> 172 <logicalname>{{$ifi.InterfaceName}}</logicalname> 173 <serial>{{$mac}}</serial> 174 </node>{{end}} 175 </node> 176 </node> 177 </node> 178 </list> 179 ` 180 181 func (suite *environSuite) generateHWTemplate(netMacs map[string]ifaceInfo) (string, error) { 182 tmpl, err := template.New("test").Parse(lshwXMLTemplate) 183 if err != nil { 184 return "", err 185 } 186 var buf bytes.Buffer 187 err = tmpl.Execute(&buf, netMacs) 188 if err != nil { 189 return "", err 190 } 191 return string(buf.Bytes()), nil 192 } 193 194 func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) { 195 suite.setupFakeTools(c) 196 env := suite.makeEnviron() 197 // Create node 0: it will be used as the bootstrap node. 198 suite.testMAASObject.TestServer.NewNode(fmt.Sprintf( 199 `{"system_id": "node0", "hostname": "host0", "architecture": "%s/generic", "memory": 1024, "cpu_count": 1}`, 200 version.Current.Arch), 201 ) 202 lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}}) 203 c.Assert(err, jc.ErrorIsNil) 204 suite.testMAASObject.TestServer.AddNodeDetails("node0", lshwXML) 205 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 206 c.Assert(err, jc.ErrorIsNil) 207 // The bootstrap node has been acquired and started. 208 operations := suite.testMAASObject.TestServer.NodeOperations() 209 actions, found := operations["node0"] 210 c.Check(found, jc.IsTrue) 211 c.Check(actions, gc.DeepEquals, []string{"acquire", "start"}) 212 213 // Test the instance id is correctly recorded for the bootstrap node. 214 // Check that StateServerInstances returns the id of the bootstrap machine. 215 instanceIds, err := env.StateServerInstances() 216 c.Assert(err, jc.ErrorIsNil) 217 c.Assert(instanceIds, gc.HasLen, 1) 218 insts, err := env.AllInstances() 219 c.Assert(err, jc.ErrorIsNil) 220 c.Assert(insts, gc.HasLen, 1) 221 c.Check(insts[0].Id(), gc.Equals, instanceIds[0]) 222 223 // Create node 1: it will be used as instance number 1. 224 suite.testMAASObject.TestServer.NewNode(fmt.Sprintf( 225 `{"system_id": "node1", "hostname": "host1", "architecture": "%s/generic", "memory": 1024, "cpu_count": 1}`, 226 version.Current.Arch), 227 ) 228 lshwXML, err = suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f1": {0, "eth0", false}}) 229 c.Assert(err, jc.ErrorIsNil) 230 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 231 instance, hc := testing.AssertStartInstance(c, env, "1") 232 c.Assert(err, jc.ErrorIsNil) 233 c.Check(instance, gc.NotNil) 234 c.Assert(hc, gc.NotNil) 235 c.Check(hc.String(), gc.Equals, fmt.Sprintf("arch=%s cpu-cores=1 mem=1024M", version.Current.Arch)) 236 237 // The instance number 1 has been acquired and started. 238 actions, found = operations["node1"] 239 c.Assert(found, jc.IsTrue) 240 c.Check(actions, gc.DeepEquals, []string{"acquire", "start"}) 241 242 // The value of the "user data" parameter used when starting the node 243 // contains the run cmd used to write the machine information onto 244 // the node's filesystem. 245 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 246 nodeRequestValues, found := requestValues["node1"] 247 c.Assert(found, jc.IsTrue) 248 c.Assert(len(nodeRequestValues), gc.Equals, 2) 249 userData := nodeRequestValues[1].Get("user_data") 250 decodedUserData, err := decodeUserData(userData) 251 c.Assert(err, jc.ErrorIsNil) 252 info := machineInfo{"host1"} 253 cloudcfg, err := cloudinit.New("precise") 254 c.Assert(err, jc.ErrorIsNil) 255 cloudinitRunCmd, err := info.cloudinitRunCmd(cloudcfg) 256 c.Assert(err, jc.ErrorIsNil) 257 data, err := goyaml.Marshal(cloudinitRunCmd) 258 c.Assert(err, jc.ErrorIsNil) 259 c.Check(string(decodedUserData), jc.Contains, string(data)) 260 261 // Trash the tools and try to start another instance. 262 suite.PatchValue(&envtools.DefaultBaseURL, "") 263 instance, _, _, err = testing.StartInstance(env, "2") 264 c.Check(instance, gc.IsNil) 265 c.Check(err, jc.Satisfies, errors.IsNotFound) 266 } 267 268 func uint64p(val uint64) *uint64 { 269 return &val 270 } 271 272 func stringp(val string) *string { 273 return &val 274 } 275 276 func (suite *environSuite) TestSelectNodeValidZone(c *gc.C) { 277 env := suite.makeEnviron() 278 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0", "zone": "bar"}`) 279 280 snArgs := selectNodeArgs{ 281 AvailabilityZones: []string{"foo", "bar"}, 282 Constraints: constraints.Value{}, 283 IncludeNetworks: nil, 284 ExcludeNetworks: nil, 285 } 286 287 node, err := env.selectNode(snArgs) 288 c.Assert(err, jc.ErrorIsNil) 289 c.Assert(node, gc.NotNil) 290 } 291 292 func (suite *environSuite) TestSelectNodeInvalidZone(c *gc.C) { 293 env := suite.makeEnviron() 294 295 snArgs := selectNodeArgs{ 296 AvailabilityZones: []string{"foo", "bar"}, 297 Constraints: constraints.Value{}, 298 IncludeNetworks: nil, 299 ExcludeNetworks: nil, 300 } 301 302 _, err := env.selectNode(snArgs) 303 c.Assert(fmt.Sprintf("%s", err), gc.Equals, "cannot run instances: gomaasapi: got error back from server: 409 Conflict ()") 304 } 305 306 func (suite *environSuite) TestAcquireNode(c *gc.C) { 307 env := suite.makeEnviron() 308 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 309 310 _, err := env.acquireNode("", "", constraints.Value{}, nil, nil, nil) 311 312 c.Check(err, jc.ErrorIsNil) 313 operations := suite.testMAASObject.TestServer.NodeOperations() 314 actions, found := operations["node0"] 315 c.Assert(found, jc.IsTrue) 316 c.Check(actions, gc.DeepEquals, []string{"acquire"}) 317 318 // no "name" parameter should have been passed through 319 values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0] 320 _, found = values["name"] 321 c.Assert(found, jc.IsFalse) 322 } 323 324 func (suite *environSuite) TestAcquireNodeByName(c *gc.C) { 325 env := suite.makeEnviron() 326 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 327 328 _, err := env.acquireNode("host0", "", constraints.Value{}, nil, nil, nil) 329 330 c.Check(err, jc.ErrorIsNil) 331 operations := suite.testMAASObject.TestServer.NodeOperations() 332 actions, found := operations["node0"] 333 c.Assert(found, jc.IsTrue) 334 c.Check(actions, gc.DeepEquals, []string{"acquire"}) 335 336 // no "name" parameter should have been passed through 337 values := suite.testMAASObject.TestServer.NodeOperationRequestValues()["node0"][0] 338 nodeName := values.Get("name") 339 c.Assert(nodeName, gc.Equals, "host0") 340 } 341 342 func (suite *environSuite) TestAcquireNodeTakesConstraintsIntoAccount(c *gc.C) { 343 env := suite.makeEnviron() 344 suite.testMAASObject.TestServer.NewNode( 345 `{"system_id": "node0", "hostname": "host0", "architecture": "arm/generic", "memory": 2048}`, 346 ) 347 constraints := constraints.Value{Arch: stringp("arm"), Mem: uint64p(1024)} 348 349 _, err := env.acquireNode("", "", constraints, nil, nil, nil) 350 351 c.Check(err, jc.ErrorIsNil) 352 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 353 nodeRequestValues, found := requestValues["node0"] 354 c.Assert(found, jc.IsTrue) 355 c.Assert(nodeRequestValues[0].Get("arch"), gc.Equals, "arm") 356 c.Assert(nodeRequestValues[0].Get("mem"), gc.Equals, "1024") 357 } 358 359 func (suite *environSuite) TestParseTags(c *gc.C) { 360 tests := []struct { 361 about string 362 input []string 363 tags, notTags []string 364 }{{ 365 about: "nil input", 366 input: nil, 367 tags: []string{}, 368 notTags: []string{}, 369 }, { 370 about: "empty input", 371 input: []string{}, 372 tags: []string{}, 373 notTags: []string{}, 374 }, { 375 about: "tag list with embedded spaces", 376 input: []string{" tag1 ", " tag2", " ^ not Tag 3 ", " ", " ", "", "", " ^notTag4 "}, 377 tags: []string{"tag1", "tag2"}, 378 notTags: []string{"notTag3", "notTag4"}, 379 }, { 380 about: "only positive tags", 381 input: []string{"tag1", "tag2", "tag3"}, 382 tags: []string{"tag1", "tag2", "tag3"}, 383 notTags: []string{}, 384 }, { 385 about: "only negative tags", 386 input: []string{"^tag1", "^tag2", "^tag3"}, 387 tags: []string{}, 388 notTags: []string{"tag1", "tag2", "tag3"}, 389 }, { 390 about: "both positive and negative tags", 391 input: []string{"^tag1", "tag2", "^tag3", "tag4"}, 392 tags: []string{"tag2", "tag4"}, 393 notTags: []string{"tag1", "tag3"}, 394 }, { 395 about: "single positive tag", 396 input: []string{"tag1"}, 397 tags: []string{"tag1"}, 398 notTags: []string{}, 399 }, { 400 about: "single negative tag", 401 input: []string{"^tag1"}, 402 tags: []string{}, 403 notTags: []string{"tag1"}, 404 }} 405 for i, test := range tests { 406 c.Logf("test %d: %s", i, test.about) 407 tags, notTags := parseTags(test.input) 408 c.Check(tags, jc.DeepEquals, test.tags) 409 c.Check(notTags, jc.DeepEquals, test.notTags) 410 } 411 } 412 413 func (suite *environSuite) TestAcquireNodePassedAgentName(c *gc.C) { 414 env := suite.makeEnviron() 415 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 416 417 _, err := env.acquireNode("", "", constraints.Value{}, nil, nil, nil) 418 419 c.Check(err, jc.ErrorIsNil) 420 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 421 nodeRequestValues, found := requestValues["node0"] 422 c.Assert(found, jc.IsTrue) 423 c.Assert(nodeRequestValues[0].Get("agent_name"), gc.Equals, exampleAgentName) 424 } 425 426 func (suite *environSuite) TestAcquireNodePassesPositiveAndNegativeTags(c *gc.C) { 427 env := suite.makeEnviron() 428 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0"}`) 429 430 _, err := env.acquireNode( 431 "", "", 432 constraints.Value{Tags: &[]string{"tag1", "^tag2", "tag3", "^tag4"}}, 433 nil, nil, nil, 434 ) 435 436 c.Check(err, jc.ErrorIsNil) 437 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 438 nodeValues, found := requestValues["node0"] 439 c.Assert(found, jc.IsTrue) 440 c.Assert(nodeValues[0].Get("tags"), gc.Equals, "tag1,tag3") 441 c.Assert(nodeValues[0].Get("not_tags"), gc.Equals, "tag2,tag4") 442 } 443 444 func (suite *environSuite) TestAcquireNodeStorage(c *gc.C) { 445 for i, test := range []struct { 446 volumes []volumeInfo 447 expected string 448 }{ 449 { 450 nil, 451 "", 452 }, 453 { 454 []volumeInfo{{"volume-1", 1234, nil}}, 455 "volume-1:1234", 456 }, 457 { 458 []volumeInfo{{"", 1234, []string{"tag1", "tag2"}}}, 459 "1234(tag1,tag2)", 460 }, 461 { 462 []volumeInfo{{"volume-1", 1234, []string{"tag1", "tag2"}}}, 463 "volume-1:1234(tag1,tag2)", 464 }, 465 { 466 []volumeInfo{ 467 {"volume-1", 1234, []string{"tag1", "tag2"}}, 468 {"volume-2", 4567, []string{"tag1", "tag3"}}, 469 }, 470 "volume-1:1234(tag1,tag2),volume-2:4567(tag1,tag3)", 471 }, 472 } { 473 c.Logf("test %d", i) 474 env := suite.makeEnviron() 475 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) 476 _, err := env.acquireNode("", "", constraints.Value{}, nil, nil, test.volumes) 477 c.Check(err, jc.ErrorIsNil) 478 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 479 nodeRequestValues, found := requestValues["node0"] 480 c.Check(found, jc.IsTrue) 481 c.Check(nodeRequestValues[0].Get("storage"), gc.Equals, test.expected) 482 suite.testMAASObject.TestServer.Clear() 483 } 484 } 485 486 var testValues = []struct { 487 constraints constraints.Value 488 expectedResult url.Values 489 }{ 490 {constraints.Value{Arch: stringp("arm")}, url.Values{"arch": {"arm"}}}, 491 {constraints.Value{CpuCores: uint64p(4)}, url.Values{"cpu_count": {"4"}}}, 492 {constraints.Value{Mem: uint64p(1024)}, url.Values{"mem": {"1024"}}}, 493 {constraints.Value{Tags: &[]string{"tag1", "tag2", "^tag3", "^tag4"}}, url.Values{"tags": {"tag1,tag2"}, "not_tags": {"tag3,tag4"}}}, 494 495 // CpuPower is ignored. 496 {constraints.Value{CpuPower: uint64p(1024)}, url.Values{}}, 497 498 // RootDisk is ignored. 499 {constraints.Value{RootDisk: uint64p(8192)}, url.Values{}}, 500 {constraints.Value{Tags: &[]string{"foo", "bar"}}, url.Values{"tags": {"foo,bar"}}}, 501 {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"}}}, 502 } 503 504 func (*environSuite) TestConvertConstraints(c *gc.C) { 505 for _, test := range testValues { 506 c.Check(convertConstraints(test.constraints), gc.DeepEquals, test.expectedResult) 507 } 508 } 509 510 var testNetworkValues = []struct { 511 includeNetworks []string 512 excludeNetworks []string 513 expectedResult url.Values 514 }{ 515 { 516 nil, 517 nil, 518 url.Values{}, 519 }, 520 { 521 []string{"included_net_1"}, 522 nil, 523 url.Values{"networks": {"included_net_1"}}, 524 }, 525 { 526 nil, 527 []string{"excluded_net_1"}, 528 url.Values{"not_networks": {"excluded_net_1"}}, 529 }, 530 { 531 []string{"included_net_1", "included_net_2"}, 532 []string{"excluded_net_1", "excluded_net_2"}, 533 url.Values{ 534 "networks": {"included_net_1", "included_net_2"}, 535 "not_networks": {"excluded_net_1", "excluded_net_2"}, 536 }, 537 }, 538 } 539 540 func (*environSuite) TestConvertNetworks(c *gc.C) { 541 for _, test := range testNetworkValues { 542 var vals = url.Values{} 543 addNetworks(vals, test.includeNetworks, test.excludeNetworks) 544 c.Check(vals, gc.DeepEquals, test.expectedResult) 545 } 546 } 547 548 func (suite *environSuite) getInstance(systemId string) *maasInstance { 549 input := fmt.Sprintf(`{"system_id": %q}`, systemId) 550 node := suite.testMAASObject.TestServer.NewNode(input) 551 return &maasInstance{maasObject: &node, environ: suite.makeEnviron()} 552 } 553 554 func (suite *environSuite) getNetwork(name string, id int, vlanTag int) *gomaasapi.MAASObject { 555 var vlan string 556 if vlanTag == 0 { 557 vlan = "null" 558 } else { 559 vlan = fmt.Sprintf("%d", vlanTag) 560 } 561 var input string 562 input = fmt.Sprintf(`{"name": %q, "ip":"192.168.%d.1", "netmask": "255.255.255.0",`+ 563 `"vlan_tag": %s, "description": "%s_%d_%d" }`, name, id, vlan, name, id, vlanTag) 564 network := suite.testMAASObject.TestServer.NewNetwork(input) 565 return &network 566 } 567 568 func (suite *environSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) { 569 suite.getInstance("test1") 570 571 err := suite.makeEnviron().StopInstances() 572 c.Check(err, jc.ErrorIsNil) 573 operations := suite.testMAASObject.TestServer.NodeOperations() 574 c.Check(operations, gc.DeepEquals, map[string][]string{}) 575 } 576 577 func (suite *environSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) { 578 suite.getInstance("test1") 579 suite.getInstance("test2") 580 suite.getInstance("test3") 581 // mark test1 and test2 as being allocated, but not test3. 582 // The release operation will ignore test3. 583 suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true 584 suite.testMAASObject.TestServer.OwnedNodes()["test2"] = true 585 586 err := suite.makeEnviron().StopInstances("test1", "test2", "test3") 587 c.Check(err, jc.ErrorIsNil) 588 operations := suite.testMAASObject.TestServer.NodesOperations() 589 c.Check(operations, gc.DeepEquals, []string{"release"}) 590 c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse) 591 c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test2"], jc.IsFalse) 592 } 593 594 func (suite *environSuite) TestStopInstancesIgnoresConflict(c *gc.C) { 595 releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error { 596 return gomaasapi.ServerError{StatusCode: 409} 597 } 598 suite.PatchValue(&ReleaseNodes, releaseNodes) 599 env := suite.makeEnviron() 600 err := env.StopInstances("test1") 601 c.Assert(err, jc.ErrorIsNil) 602 } 603 604 func (suite *environSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) { 605 attemptedNodes := [][]string{} 606 releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error { 607 attemptedNodes = append(attemptedNodes, ids["nodes"]) 608 return gomaasapi.ServerError{StatusCode: 404} 609 } 610 suite.PatchValue(&ReleaseNodes, releaseNodes) 611 env := suite.makeEnviron() 612 err := env.StopInstances("test1", "test2") 613 c.Assert(err, jc.ErrorIsNil) 614 615 expectedNodes := [][]string{{"test1", "test2"}, {"test1"}, {"test2"}} 616 c.Assert(attemptedNodes, gc.DeepEquals, expectedNodes) 617 } 618 619 func (suite *environSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) { 620 releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error { 621 return gomaasapi.ServerError{StatusCode: 405} 622 } 623 suite.PatchValue(&ReleaseNodes, releaseNodes) 624 env := suite.makeEnviron() 625 err := env.StopInstances("test1") 626 c.Assert(err, gc.NotNil) 627 maasErr, ok := errors.Cause(err).(gomaasapi.ServerError) 628 c.Assert(ok, jc.IsTrue) 629 c.Assert(maasErr.StatusCode, gc.Equals, 405) 630 } 631 632 func (suite *environSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) { 633 releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error { 634 return environs.ErrNoInstances 635 } 636 suite.PatchValue(&ReleaseNodes, releaseNodes) 637 env := suite.makeEnviron() 638 err := env.StopInstances("test1") 639 c.Assert(err, gc.NotNil) 640 c.Assert(errors.Cause(err), gc.Equals, environs.ErrNoInstances) 641 } 642 643 func (suite *environSuite) TestStateServerInstances(c *gc.C) { 644 env := suite.makeEnviron() 645 _, err := env.StateServerInstances() 646 c.Assert(err, gc.Equals, environs.ErrNotBootstrapped) 647 648 tests := [][]instance.Id{{}, {"inst-0"}, {"inst-0", "inst-1"}} 649 for _, expected := range tests { 650 err := common.SaveState(env.Storage(), &common.BootstrapState{ 651 StateInstances: expected, 652 }) 653 c.Assert(err, jc.ErrorIsNil) 654 stateServerInstances, err := env.StateServerInstances() 655 c.Assert(err, jc.ErrorIsNil) 656 c.Assert(stateServerInstances, jc.SameContents, expected) 657 } 658 } 659 660 func (suite *environSuite) TestStateServerInstancesFailsIfNoStateInstances(c *gc.C) { 661 env := suite.makeEnviron() 662 _, err := env.StateServerInstances() 663 c.Check(err, gc.Equals, environs.ErrNotBootstrapped) 664 } 665 666 func (suite *environSuite) TestDestroy(c *gc.C) { 667 env := suite.makeEnviron() 668 suite.getInstance("test1") 669 suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true // simulate acquire 670 data := makeRandomBytes(10) 671 suite.testMAASObject.TestServer.NewFile("filename", data) 672 stor := env.Storage() 673 674 err := env.Destroy() 675 c.Check(err, jc.ErrorIsNil) 676 677 // Instances have been stopped. 678 operations := suite.testMAASObject.TestServer.NodesOperations() 679 c.Check(operations, gc.DeepEquals, []string{"release"}) 680 c.Check(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse) 681 // Files have been cleaned up. 682 listing, err := envstorage.List(stor, "") 683 c.Assert(err, jc.ErrorIsNil) 684 c.Check(listing, gc.DeepEquals, []string{}) 685 } 686 687 func (suite *environSuite) TestBootstrapSucceeds(c *gc.C) { 688 suite.setupFakeTools(c) 689 env := suite.makeEnviron() 690 suite.testMAASObject.TestServer.NewNode(fmt.Sprintf( 691 `{"system_id": "thenode", "hostname": "host", "architecture": "%s/generic", "memory": 256, "cpu_count": 8}`, 692 version.Current.Arch), 693 ) 694 lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}}) 695 c.Assert(err, jc.ErrorIsNil) 696 suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML) 697 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 698 c.Assert(err, jc.ErrorIsNil) 699 } 700 701 func (suite *environSuite) TestBootstrapNodeNotDeployed(c *gc.C) { 702 suite.setupFakeTools(c) 703 env := suite.makeEnviron() 704 suite.testMAASObject.TestServer.NewNode(fmt.Sprintf( 705 `{"system_id": "thenode", "hostname": "host", "architecture": "%s/generic", "memory": 256, "cpu_count": 8}`, 706 version.Current.Arch), 707 ) 708 lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}}) 709 c.Assert(err, jc.ErrorIsNil) 710 suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML) 711 // Ensure node will not be reported as deployed by changing its status. 712 suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "4") 713 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 714 c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*") 715 } 716 717 func (suite *environSuite) TestBootstrapNodeFailedDeploy(c *gc.C) { 718 suite.setupFakeTools(c) 719 env := suite.makeEnviron() 720 suite.testMAASObject.TestServer.NewNode(fmt.Sprintf( 721 `{"system_id": "thenode", "hostname": "host", "architecture": "%s/generic", "memory": 256, "cpu_count": 8}`, 722 version.Current.Arch), 723 ) 724 lshwXML, err := suite.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}}) 725 c.Assert(err, jc.ErrorIsNil) 726 suite.testMAASObject.TestServer.AddNodeDetails("thenode", lshwXML) 727 // Set the node status to "Failed deployment" 728 suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "11") 729 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 730 c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state. instance \"/api/.*/nodes/thenode/\" failed to deploy") 731 } 732 733 func (suite *environSuite) TestBootstrapFailsIfNoTools(c *gc.C) { 734 env := suite.makeEnviron() 735 // Disable auto-uploading by setting the agent version. 736 cfg, err := env.Config().Apply(map[string]interface{}{ 737 "agent-version": version.Current.Number.String(), 738 }) 739 c.Assert(err, jc.ErrorIsNil) 740 err = env.SetConfig(cfg) 741 c.Assert(err, jc.ErrorIsNil) 742 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 743 c.Check(err, gc.ErrorMatches, "Juju cannot bootstrap because no tools are available for your environment(.|\n)*") 744 } 745 746 func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) { 747 suite.setupFakeTools(c) 748 env := suite.makeEnviron() 749 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 750 // Since there are no nodes, the attempt to allocate one returns a 751 // 409: Conflict. 752 c.Check(err, gc.ErrorMatches, ".*409.*") 753 } 754 755 func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) { 756 rc, _, err := source.Fetch(filename) 757 c.Assert(err, jc.ErrorIsNil) 758 defer rc.Close() 759 retrieved, err := ioutil.ReadAll(rc) 760 c.Assert(err, jc.ErrorIsNil) 761 c.Assert(retrieved, gc.DeepEquals, content) 762 } 763 764 func (suite *environSuite) TestGetToolsMetadataSources(c *gc.C) { 765 env := suite.makeEnviron() 766 // Add a dummy file to storage so we can use that to check the 767 // obtained source later. 768 data := makeRandomBytes(10) 769 stor := NewStorage(env) 770 err := stor.Put("tools/filename", bytes.NewBuffer([]byte(data)), int64(len(data))) 771 c.Assert(err, jc.ErrorIsNil) 772 sources, err := envtools.GetMetadataSources(env) 773 c.Assert(err, jc.ErrorIsNil) 774 c.Assert(sources, gc.HasLen, 0) 775 } 776 777 func (suite *environSuite) TestSupportedArchitectures(c *gc.C) { 778 suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "precise"}`) 779 suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`) 780 suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "amd64", "release": "precise"}`) 781 suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "ppc64el", "release": "trusty"}`) 782 env := suite.makeEnviron() 783 a, err := env.SupportedArchitectures() 784 c.Assert(err, jc.ErrorIsNil) 785 c.Assert(a, jc.SameContents, []string{"amd64", "ppc64el"}) 786 } 787 788 func (suite *environSuite) TestSupportedArchitecturesFallback(c *gc.C) { 789 // If we cannot query boot-images (e.g. MAAS server version 1.4), 790 // then Juju will fall over to listing all the available nodes. 791 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "architecture": "amd64/generic"}`) 792 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node1", "architecture": "armhf"}`) 793 env := suite.makeEnviron() 794 a, err := env.SupportedArchitectures() 795 c.Assert(err, jc.ErrorIsNil) 796 c.Assert(a, jc.SameContents, []string{"amd64", "armhf"}) 797 } 798 799 func (suite *environSuite) TestConstraintsValidator(c *gc.C) { 800 suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`) 801 env := suite.makeEnviron() 802 validator, err := env.ConstraintsValidator() 803 c.Assert(err, jc.ErrorIsNil) 804 cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo") 805 unsupported, err := validator.Validate(cons) 806 c.Assert(err, jc.ErrorIsNil) 807 c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type"}) 808 } 809 810 func (suite *environSuite) TestConstraintsValidatorVocab(c *gc.C) { 811 suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`) 812 suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "armhf", "release": "precise"}`) 813 env := suite.makeEnviron() 814 validator, err := env.ConstraintsValidator() 815 c.Assert(err, jc.ErrorIsNil) 816 cons := constraints.MustParse("arch=ppc64el") 817 _, err = validator.Validate(cons) 818 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are: \\[amd64 armhf\\]") 819 } 820 821 func (suite *environSuite) TestGetNetworkMACs(c *gc.C) { 822 env := suite.makeEnviron() 823 824 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_1"}`) 825 suite.testMAASObject.TestServer.NewNode(`{"system_id": "node_2"}`) 826 suite.testMAASObject.TestServer.NewNetwork( 827 `{"name": "net_1","ip":"0.1.2.0","netmask":"255.255.255.0"}`, 828 ) 829 suite.testMAASObject.TestServer.NewNetwork( 830 `{"name": "net_2","ip":"0.2.2.0","netmask":"255.255.255.0"}`, 831 ) 832 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_2", "aa:bb:cc:dd:ee:22") 833 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_1", "aa:bb:cc:dd:ee:11") 834 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_2", "net_1", "aa:bb:cc:dd:ee:21") 835 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node_1", "net_2", "aa:bb:cc:dd:ee:12") 836 837 networks, err := env.getNetworkMACs("net_1") 838 c.Assert(err, jc.ErrorIsNil) 839 c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:11", "aa:bb:cc:dd:ee:21"}) 840 841 networks, err = env.getNetworkMACs("net_2") 842 c.Assert(err, jc.ErrorIsNil) 843 c.Check(networks, jc.SameContents, []string{"aa:bb:cc:dd:ee:12", "aa:bb:cc:dd:ee:22"}) 844 845 networks, err = env.getNetworkMACs("net_3") 846 c.Check(networks, gc.HasLen, 0) 847 c.Assert(err, jc.ErrorIsNil) 848 } 849 850 func (suite *environSuite) TestGetInstanceNetworks(c *gc.C) { 851 suite.getNetwork("test_network", 123, 321) 852 testInstance := suite.getInstance("instance_for_network") 853 suite.testMAASObject.TestServer.ConnectNodeToNetwork("instance_for_network", "test_network") 854 networks, err := suite.makeEnviron().getInstanceNetworks(testInstance) 855 c.Assert(err, jc.ErrorIsNil) 856 c.Check(networks, gc.DeepEquals, []networkDetails{ 857 {Name: "test_network", IP: "192.168.123.1", Mask: "255.255.255.0", VLANTag: 321, 858 Description: "test_network_123_321"}, 859 }) 860 } 861 862 // A typical lshw XML dump with lots of things left out. 863 const lshwXMLTestExtractInterfaces = ` 864 <?xml version="1.0" standalone="yes" ?> 865 <!-- generated by lshw-B.02.16 --> 866 <list> 867 <node id="machine" claimed="true" class="system" handle="DMI:0001"> 868 <description>Notebook</description> 869 <product>MyMachine</product> 870 <version>1.0</version> 871 <width units="bits">64</width> 872 <node id="core" claimed="true" class="bus" handle="DMI:0002"> 873 <description>Motherboard</description> 874 <node id="cpu" claimed="true" class="processor" handle="DMI:0004"> 875 <description>CPU</description> 876 <node id="pci:2" claimed="true" class="bridge" handle="PCIBUS:0000:03"> 877 <node id="network:0" claimed="true" disabled="true" class="network" handle="PCI:0000:03:00.0"> 878 <logicalname>wlan0</logicalname> 879 <serial>aa:bb:cc:dd:ee:ff</serial> 880 </node> 881 <node id="network:1" claimed="true" class="network" handle="PCI:0000:04:00.0"> 882 <logicalname>eth0</logicalname> 883 <serial>aa:bb:cc:dd:ee:f1</serial> 884 </node> 885 </node> 886 </node> 887 </node> 888 <node id="network:2" claimed="true" class="network" handle=""> 889 <logicalname>vnet1</logicalname> 890 <serial>aa:bb:cc:dd:ee:f2</serial> 891 </node> 892 </node> 893 </list> 894 ` 895 896 // An lshw XML dump with implicit network interface indexes. 897 const lshwXMLTestExtractInterfacesImplicitIndexes = ` 898 <?xml version="1.0" standalone="yes" ?> 899 <!-- generated by lshw-B.02.16 --> 900 <list> 901 <node id="machine" claimed="true" class="system" handle="DMI:0001"> 902 <description>Notebook</description> 903 <product>MyMachine</product> 904 <version>1.0</version> 905 <width units="bits">64</width> 906 <node id="core" claimed="true" class="bus" handle="DMI:0002"> 907 <description>Motherboard</description> 908 <node id="cpu" claimed="true" class="processor" handle="DMI:0004"> 909 <description>CPU</description> 910 <node id="pci:2" claimed="true" class="bridge" handle="PCIBUS:0000:03"> 911 <node id="network" claimed="true" disabled="true" class="network" handle="PCI:0000:03:00.0"> 912 <logicalname>wlan0</logicalname> 913 <serial>aa:bb:cc:dd:ee:ff</serial> 914 </node> 915 <node id="network" claimed="true" class="network" handle="PCI:0000:04:00.0"> 916 <logicalname>eth0</logicalname> 917 <serial>aa:bb:cc:dd:ee:f1</serial> 918 </node> 919 </node> 920 </node> 921 </node> 922 <node id="network" claimed="true" class="network" handle=""> 923 <logicalname>vnet1</logicalname> 924 <serial>aa:bb:cc:dd:ee:f2</serial> 925 </node> 926 </node> 927 </list> 928 ` 929 930 func (suite *environSuite) TestExtractInterfaces(c *gc.C) { 931 rawData := []string{ 932 lshwXMLTestExtractInterfaces, 933 lshwXMLTestExtractInterfacesImplicitIndexes, 934 } 935 for _, data := range rawData { 936 inst := suite.getInstance("testInstance") 937 interfaces, primaryIface, err := extractInterfaces(inst, []byte(data)) 938 c.Assert(err, jc.ErrorIsNil) 939 c.Check(primaryIface, gc.Equals, "eth0") 940 c.Check(interfaces, jc.DeepEquals, map[string]ifaceInfo{ 941 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 942 "aa:bb:cc:dd:ee:f1": {1, "eth0", false}, 943 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 944 }) 945 } 946 } 947 948 func (suite *environSuite) TestGetInstanceNetworkInterfaces(c *gc.C) { 949 inst := suite.getInstance("testInstance") 950 templateInterfaces := map[string]ifaceInfo{ 951 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 952 "aa:bb:cc:dd:ee:f1": {1, "eth0", true}, 953 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 954 } 955 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 956 c.Assert(err, jc.ErrorIsNil) 957 958 suite.testMAASObject.TestServer.AddNodeDetails("testInstance", lshwXML) 959 interfaces, primaryIface, err := inst.environ.getInstanceNetworkInterfaces(inst) 960 c.Assert(err, jc.ErrorIsNil) 961 // Both wlan0 and eth0 are disabled in lshw output. 962 c.Check(primaryIface, gc.Equals, "vnet1") 963 c.Check(interfaces, jc.DeepEquals, templateInterfaces) 964 } 965 966 func (suite *environSuite) TestSetupNetworks(c *gc.C) { 967 testInstance := suite.getInstance("node1") 968 templateInterfaces := map[string]ifaceInfo{ 969 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 970 "aa:bb:cc:dd:ee:f1": {1, "eth0", true}, 971 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 972 } 973 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 974 c.Assert(err, jc.ErrorIsNil) 975 976 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 977 suite.getNetwork("LAN", 2, 42) 978 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1") 979 suite.getNetwork("Virt", 3, 0) 980 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2") 981 suite.getNetwork("WLAN", 1, 0) 982 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff") 983 networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks( 984 testInstance, 985 set.NewStrings("WLAN"), // Disable WLAN only. 986 ) 987 c.Assert(err, jc.ErrorIsNil) 988 989 // Note: order of networks is based on lshwXML 990 c.Check(primaryIface, gc.Equals, "vnet1") 991 // Unfortunately, because network.InterfaceInfo is unhashable 992 // (contains a map) we can't use jc.SameContents here. 993 c.Check(networkInfo, gc.HasLen, 3) 994 for _, info := range networkInfo { 995 switch info.DeviceIndex { 996 case 0: 997 c.Check(info, jc.DeepEquals, network.InterfaceInfo{ 998 MACAddress: "aa:bb:cc:dd:ee:ff", 999 CIDR: "192.168.1.1/24", 1000 NetworkName: "WLAN", 1001 ProviderId: "WLAN", 1002 VLANTag: 0, 1003 DeviceIndex: 0, 1004 InterfaceName: "wlan0", 1005 Disabled: true, // from networksToDisable("WLAN") 1006 }) 1007 case 1: 1008 c.Check(info, jc.DeepEquals, network.InterfaceInfo{ 1009 DeviceIndex: 1, 1010 MACAddress: "aa:bb:cc:dd:ee:f1", 1011 CIDR: "192.168.2.1/24", 1012 NetworkName: "LAN", 1013 ProviderId: "LAN", 1014 VLANTag: 42, 1015 InterfaceName: "eth0", 1016 Disabled: true, // from networksToDisable("WLAN") 1017 }) 1018 case 2: 1019 c.Check(info, jc.DeepEquals, network.InterfaceInfo{ 1020 MACAddress: "aa:bb:cc:dd:ee:f2", 1021 CIDR: "192.168.3.1/24", 1022 NetworkName: "Virt", 1023 ProviderId: "Virt", 1024 VLANTag: 0, 1025 DeviceIndex: 2, 1026 InterfaceName: "vnet1", 1027 Disabled: false, 1028 }) 1029 } 1030 } 1031 } 1032 1033 // The same test, but now "Virt" network does not have matched MAC address 1034 func (suite *environSuite) TestSetupNetworksPartialMatch(c *gc.C) { 1035 testInstance := suite.getInstance("node1") 1036 templateInterfaces := map[string]ifaceInfo{ 1037 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 1038 "aa:bb:cc:dd:ee:f1": {1, "eth0", false}, 1039 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 1040 } 1041 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 1042 c.Assert(err, jc.ErrorIsNil) 1043 1044 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 1045 suite.getNetwork("LAN", 2, 42) 1046 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1") 1047 suite.getNetwork("Virt", 3, 0) 1048 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3") 1049 networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks( 1050 testInstance, 1051 set.NewStrings(), // All enabled. 1052 ) 1053 c.Assert(err, jc.ErrorIsNil) 1054 1055 // Note: order of networks is based on lshwXML 1056 c.Check(primaryIface, gc.Equals, "eth0") 1057 c.Check(networkInfo, jc.DeepEquals, []network.InterfaceInfo{{ 1058 MACAddress: "aa:bb:cc:dd:ee:f1", 1059 CIDR: "192.168.2.1/24", 1060 NetworkName: "LAN", 1061 ProviderId: "LAN", 1062 VLANTag: 42, 1063 DeviceIndex: 1, 1064 InterfaceName: "eth0", 1065 Disabled: false, 1066 }}) 1067 } 1068 1069 // The same test, but now no networks have matched MAC 1070 func (suite *environSuite) TestSetupNetworksNoMatch(c *gc.C) { 1071 testInstance := suite.getInstance("node1") 1072 templateInterfaces := map[string]ifaceInfo{ 1073 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 1074 "aa:bb:cc:dd:ee:f1": {1, "eth0", false}, 1075 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 1076 } 1077 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 1078 c.Assert(err, jc.ErrorIsNil) 1079 1080 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 1081 suite.getNetwork("Virt", 3, 0) 1082 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3") 1083 networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks( 1084 testInstance, 1085 set.NewStrings(), // All enabled. 1086 ) 1087 c.Assert(err, jc.ErrorIsNil) 1088 1089 // Note: order of networks is based on lshwXML 1090 c.Check(primaryIface, gc.Equals, "eth0") 1091 c.Check(networkInfo, gc.HasLen, 0) 1092 } 1093 1094 func (suite *environSuite) TestSupportsNetworking(c *gc.C) { 1095 env := suite.makeEnviron() 1096 _, supported := environs.SupportsNetworking(env) 1097 c.Assert(supported, jc.IsTrue) 1098 } 1099 1100 func (suite *environSuite) TestSupportsAddressAllocation(c *gc.C) { 1101 env := suite.makeEnviron() 1102 supported, err := env.SupportsAddressAllocation("") 1103 c.Assert(err, jc.ErrorIsNil) 1104 c.Assert(supported, jc.IsTrue) 1105 } 1106 1107 func (suite *environSuite) createSubnets(c *gc.C, duplicates bool) instance.Instance { 1108 testInstance := suite.getInstance("node1") 1109 templateInterfaces := map[string]ifaceInfo{ 1110 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 1111 "aa:bb:cc:dd:ee:f1": {1, "eth0", false}, 1112 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 1113 } 1114 if duplicates { 1115 templateInterfaces["aa:bb:cc:dd:ee:f3"] = ifaceInfo{3, "eth1", true} 1116 templateInterfaces["aa:bb:cc:dd:ee:f4"] = ifaceInfo{4, "vnet2", false} 1117 } 1118 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 1119 c.Assert(err, jc.ErrorIsNil) 1120 1121 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 1122 // resulting CIDR 192.168.2.1/24 1123 suite.getNetwork("LAN", 2, 42) 1124 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1") 1125 // resulting CIDR 192.168.3.1/24 1126 suite.getNetwork("Virt", 3, 0) 1127 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2") 1128 // resulting CIDR 192.168.1.1/24 1129 suite.getNetwork("WLAN", 1, 0) 1130 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff") 1131 if duplicates { 1132 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f3") 1133 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f4") 1134 } 1135 1136 // needed for getNodeGroups to work 1137 suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "precise"}`) 1138 suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "amd64", "release": "precise"}`) 1139 1140 jsonText1 := `{ 1141 "ip_range_high": "192.168.2.255", 1142 "ip_range_low": "192.168.2.128", 1143 "broadcast_ip": "192.168.2.255", 1144 "static_ip_range_low": "192.168.2.0", 1145 "name": "eth0", 1146 "ip": "192.168.2.1", 1147 "subnet_mask": "255.255.255.0", 1148 "management": 2, 1149 "static_ip_range_high": "192.168.2.127", 1150 "interface": "eth0" 1151 }` 1152 jsonText2 := `{ 1153 "ip_range_high": "172.16.0.128", 1154 "ip_range_low": "172.16.0.2", 1155 "broadcast_ip": "172.16.0.255", 1156 "static_ip_range_low": "172.16.0.129", 1157 "name": "eth1", 1158 "ip": "172.16.0.2", 1159 "subnet_mask": "255.255.255.0", 1160 "management": 2, 1161 "static_ip_range_high": "172.16.0.255", 1162 "interface": "eth1" 1163 }` 1164 jsonText3 := `{ 1165 "ip_range_high": "192.168.1.128", 1166 "ip_range_low": "192.168.1.2", 1167 "broadcast_ip": "192.168.1.255", 1168 "static_ip_range_low": "192.168.1.129", 1169 "name": "eth2", 1170 "ip": "192.168.1.2", 1171 "subnet_mask": "255.255.255.0", 1172 "management": 2, 1173 "static_ip_range_high": "192.168.1.255", 1174 "interface": "eth2" 1175 }` 1176 jsonText4 := `{ 1177 "ip_range_high": "172.16.8.128", 1178 "ip_range_low": "172.16.8.2", 1179 "broadcast_ip": "172.16.8.255", 1180 "static_ip_range_low": "172.16.0.129", 1181 "name": "eth3", 1182 "ip": "172.16.8.2", 1183 "subnet_mask": "255.255.255.0", 1184 "management": 2, 1185 "static_ip_range_high": "172.16.8.255", 1186 "interface": "eth3" 1187 }` 1188 suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText1) 1189 suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText2) 1190 suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText3) 1191 suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText4) 1192 return testInstance 1193 } 1194 1195 func (suite *environSuite) TestNetworkInterfaces(c *gc.C) { 1196 testInstance := suite.createSubnets(c, false) 1197 1198 netInfo, err := suite.makeEnviron().NetworkInterfaces(testInstance.Id()) 1199 c.Assert(err, jc.ErrorIsNil) 1200 1201 expectedInfo := []network.InterfaceInfo{{ 1202 DeviceIndex: 0, 1203 MACAddress: "aa:bb:cc:dd:ee:ff", 1204 CIDR: "192.168.1.1/24", 1205 ProviderSubnetId: "WLAN", 1206 VLANTag: 0, 1207 InterfaceName: "wlan0", 1208 Disabled: true, 1209 NoAutoStart: true, 1210 ConfigType: network.ConfigDHCP, 1211 ExtraConfig: nil, 1212 GatewayAddress: network.Address{}, 1213 Address: network.NewScopedAddress("192.168.1.1", network.ScopeCloudLocal), 1214 }, { 1215 DeviceIndex: 1, 1216 MACAddress: "aa:bb:cc:dd:ee:f1", 1217 CIDR: "192.168.2.1/24", 1218 ProviderSubnetId: "LAN", 1219 VLANTag: 42, 1220 InterfaceName: "eth0", 1221 Disabled: false, 1222 NoAutoStart: false, 1223 ConfigType: network.ConfigDHCP, 1224 ExtraConfig: nil, 1225 GatewayAddress: network.Address{}, 1226 Address: network.NewScopedAddress("192.168.2.1", network.ScopeCloudLocal), 1227 }, { 1228 DeviceIndex: 2, 1229 MACAddress: "aa:bb:cc:dd:ee:f2", 1230 CIDR: "192.168.3.1/24", 1231 ProviderSubnetId: "Virt", 1232 VLANTag: 0, 1233 InterfaceName: "vnet1", 1234 Disabled: false, 1235 NoAutoStart: false, 1236 ConfigType: network.ConfigDHCP, 1237 ExtraConfig: nil, 1238 GatewayAddress: network.Address{}, 1239 Address: network.NewScopedAddress("192.168.3.1", network.ScopeCloudLocal), 1240 }} 1241 network.SortInterfaceInfo(netInfo) 1242 c.Assert(netInfo, jc.DeepEquals, expectedInfo) 1243 } 1244 1245 func (suite *environSuite) TestSubnets(c *gc.C) { 1246 testInstance := suite.createSubnets(c, false) 1247 1248 netInfo, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"LAN", "Virt", "WLAN"}) 1249 c.Assert(err, jc.ErrorIsNil) 1250 1251 expectedInfo := []network.SubnetInfo{ 1252 {CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")}, 1253 {CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0}, 1254 {CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}} 1255 c.Assert(netInfo, jc.DeepEquals, expectedInfo) 1256 } 1257 1258 func (suite *environSuite) TestSubnetsNoNetIds(c *gc.C) { 1259 testInstance := suite.createSubnets(c, false) 1260 _, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{}) 1261 c.Assert(err, gc.ErrorMatches, "subnetIds must not be empty") 1262 } 1263 1264 func (suite *environSuite) TestSubnetsMissingNetwork(c *gc.C) { 1265 testInstance := suite.createSubnets(c, false) 1266 _, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"WLAN", "Missing"}) 1267 c.Assert(err, gc.ErrorMatches, "failed to find the following subnets: \\[Missing\\]") 1268 } 1269 1270 func (suite *environSuite) TestSubnetsNoDuplicates(c *gc.C) { 1271 testInstance := suite.createSubnets(c, true) 1272 1273 netInfo, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"LAN", "Virt", "WLAN"}) 1274 c.Assert(err, jc.ErrorIsNil) 1275 1276 expectedInfo := []network.SubnetInfo{ 1277 {CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")}, 1278 {CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0}, 1279 {CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}} 1280 c.Assert(netInfo, jc.DeepEquals, expectedInfo) 1281 } 1282 1283 func (suite *environSuite) TestAllocateAddress(c *gc.C) { 1284 testInstance := suite.createSubnets(c, false) 1285 env := suite.makeEnviron() 1286 1287 // note that the default test server always succeeds if we provide a 1288 // valid instance id and net id 1289 err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}) 1290 c.Assert(err, jc.ErrorIsNil) 1291 } 1292 1293 func (suite *environSuite) TestAllocateAddressInvalidInstance(c *gc.C) { 1294 env := suite.makeEnviron() 1295 addr := network.Address{Value: "192.168.2.1"} 1296 instId := instance.Id("foo") 1297 err := env.AllocateAddress(instId, "bar", addr) 1298 expected := fmt.Sprintf("failed to allocate address %q for instance %q.*", addr, instId) 1299 c.Assert(err, gc.ErrorMatches, expected) 1300 } 1301 1302 func (suite *environSuite) TestAllocateAddressMissingSubnet(c *gc.C) { 1303 testInstance := suite.createSubnets(c, false) 1304 env := suite.makeEnviron() 1305 err := env.AllocateAddress(testInstance.Id(), "bar", network.Address{Value: "192.168.2.1"}) 1306 c.Assert(errors.Cause(err), gc.ErrorMatches, "failed to find the following subnets: \\[bar\\]") 1307 } 1308 1309 func (suite *environSuite) TestAllocateAddressIPAddressUnavailable(c *gc.C) { 1310 testInstance := suite.createSubnets(c, false) 1311 env := suite.makeEnviron() 1312 1313 reserveIPAddress := func(ipaddresses gomaasapi.MAASObject, cidr string, addr network.Address) error { 1314 return gomaasapi.ServerError{StatusCode: 404} 1315 } 1316 suite.PatchValue(&ReserveIPAddress, reserveIPAddress) 1317 1318 ipAddress := network.Address{Value: "192.168.2.1"} 1319 err := env.AllocateAddress(testInstance.Id(), "LAN", ipAddress) 1320 c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressUnavailable) 1321 expected := fmt.Sprintf("failed to allocate address %q for instance %q.*", ipAddress, testInstance.Id()) 1322 c.Assert(err, gc.ErrorMatches, expected) 1323 } 1324 1325 func (s *environSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 1326 s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1") 1327 env := s.makeEnviron() 1328 placement := "zone=zone1" 1329 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1330 c.Assert(err, jc.ErrorIsNil) 1331 } 1332 1333 func (suite *environSuite) TestReleaseAddress(c *gc.C) { 1334 testInstance := suite.createSubnets(c, false) 1335 env := suite.makeEnviron() 1336 1337 err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}) 1338 c.Assert(err, jc.ErrorIsNil) 1339 1340 ipAddress := network.Address{Value: "192.168.2.1"} 1341 err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress) 1342 c.Assert(err, jc.ErrorIsNil) 1343 1344 // by releasing again we can test that the first release worked, *and* 1345 // the error handling of ReleaseError 1346 err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress) 1347 expected := fmt.Sprintf("(?s).*failed to release IP address %q from instance %q.*", ipAddress, testInstance.Id()) 1348 c.Assert(err, gc.ErrorMatches, expected) 1349 } 1350 1351 func (s *environSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 1352 s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1") 1353 env := s.makeEnviron() 1354 placement := "zone=zone2" 1355 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1356 c.Assert(err, gc.ErrorMatches, `invalid availability zone "zone2"`) 1357 } 1358 1359 func (s *environSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) { 1360 env := s.makeEnviron() 1361 placement := "zone=test-unknown" 1362 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1363 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 1364 } 1365 1366 func (s *environSuite) TestPrecheckInvalidPlacement(c *gc.C) { 1367 env := s.makeEnviron() 1368 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "notzone=anything") 1369 c.Assert(err, gc.ErrorMatches, "unknown placement directive: notzone=anything") 1370 } 1371 1372 func (s *environSuite) TestPrecheckNodePlacement(c *gc.C) { 1373 env := s.makeEnviron() 1374 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "assumed_node_name") 1375 c.Assert(err, jc.ErrorIsNil) 1376 } 1377 1378 func (s *environSuite) TestStartInstanceAvailZone(c *gc.C) { 1379 // Add a node for the started instance. 1380 s.newNode(c, "thenode1", "host1", map[string]interface{}{"zone": "test-available"}) 1381 s.testMAASObject.TestServer.AddZone("test-available", "description") 1382 inst, err := s.testStartInstanceAvailZone(c, "test-available") 1383 c.Assert(err, jc.ErrorIsNil) 1384 c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available") 1385 } 1386 1387 func (s *environSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 1388 s.testMAASObject.TestServer.AddZone("test-available", "description") 1389 _, err := s.testStartInstanceAvailZone(c, "test-unknown") 1390 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 1391 } 1392 1393 func (s *environSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { 1394 env := s.bootstrap(c) 1395 params := environs.StartInstanceParams{Placement: "zone=" + zone} 1396 result, err := testing.StartInstanceWithParams(env, "1", params, nil) 1397 if err != nil { 1398 return nil, err 1399 } 1400 return result.Instance, nil 1401 } 1402 1403 func (s *environSuite) TestStartInstanceUnmetConstraints(c *gc.C) { 1404 env := s.bootstrap(c) 1405 s.newNode(c, "thenode1", "host1", nil) 1406 params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")} 1407 _, err := testing.StartInstanceWithParams(env, "1", params, nil) 1408 c.Assert(err, gc.ErrorMatches, "cannot run instances:.* 409.*") 1409 } 1410 1411 func (s *environSuite) TestStartInstanceConstraints(c *gc.C) { 1412 env := s.bootstrap(c) 1413 s.newNode(c, "thenode1", "host1", nil) 1414 s.newNode(c, "thenode2", "host2", map[string]interface{}{"memory": 8192}) 1415 params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")} 1416 result, err := testing.StartInstanceWithParams(env, "1", params, nil) 1417 c.Assert(err, jc.ErrorIsNil) 1418 c.Assert(*result.Hardware.Mem, gc.Equals, uint64(8192)) 1419 } 1420 1421 var nodeStorageAttrs = []map[string]interface{}{ 1422 { 1423 "name": "sdb", 1424 "id": 1, 1425 "id_path": "/dev/disk/by-id/id_for_sda", 1426 "path": "/dev/sdb", 1427 "model": "Samsung_SSD_850_EVO_250GB", 1428 "block_size": 4096, 1429 "serial": "S21NNSAFC38075L", 1430 "size": uint64(250059350016), 1431 }, 1432 { 1433 "name": "sda", 1434 "id": 2, 1435 "path": "/dev/sda", 1436 "model": "Samsung_SSD_850_EVO_250GB", 1437 "block_size": 4096, 1438 "serial": "XXXX", 1439 "size": uint64(250059350016), 1440 }, 1441 { 1442 "name": "sdc", 1443 "id": 3, 1444 "path": "/dev/sdc", 1445 "model": "Samsung_SSD_850_EVO_250GB", 1446 "block_size": 4096, 1447 "serial": "YYYYYYY", 1448 "size": uint64(250059350016), 1449 }, 1450 } 1451 1452 var storageConstraintAttrs = map[string]interface{}{ 1453 "1": "1", 1454 "2": "root", 1455 "3": "3", 1456 } 1457 1458 func (s *environSuite) TestStartInstanceStorage(c *gc.C) { 1459 env := s.bootstrap(c) 1460 s.newNode(c, "thenode1", "host1", map[string]interface{}{ 1461 "memory": 8192, 1462 "physicalblockdevice_set": nodeStorageAttrs, 1463 "constraint_map": storageConstraintAttrs, 1464 }) 1465 params := environs.StartInstanceParams{Volumes: []storage.VolumeParams{ 1466 {Tag: names.NewVolumeTag("1"), Size: 2000000}, 1467 {Tag: names.NewVolumeTag("3"), Size: 2000000}, 1468 }} 1469 result, err := testing.StartInstanceWithParams(env, "1", params, nil) 1470 c.Assert(err, jc.ErrorIsNil) 1471 c.Check(result.Volumes, jc.DeepEquals, []storage.Volume{ 1472 { 1473 names.NewVolumeTag("1"), 1474 storage.VolumeInfo{ 1475 Size: 238475, 1476 VolumeId: "volume-1", 1477 HardwareId: "id_for_sda", 1478 }, 1479 }, 1480 { 1481 names.NewVolumeTag("3"), 1482 storage.VolumeInfo{ 1483 Size: 238475, 1484 VolumeId: "volume-3", 1485 HardwareId: "", 1486 }, 1487 }, 1488 }) 1489 c.Assert(result.VolumeAttachments, jc.DeepEquals, []storage.VolumeAttachment{ 1490 { 1491 names.NewVolumeTag("1"), 1492 names.NewMachineTag("1"), 1493 storage.VolumeAttachmentInfo{ 1494 DeviceName: "", 1495 ReadOnly: false, 1496 }, 1497 }, 1498 { 1499 names.NewVolumeTag("3"), 1500 names.NewMachineTag("1"), 1501 storage.VolumeAttachmentInfo{ 1502 DeviceName: "sdc", 1503 ReadOnly: false, 1504 }, 1505 }, 1506 }) 1507 } 1508 1509 func (s *environSuite) TestStartInstanceUnsupportedStorage(c *gc.C) { 1510 env := s.bootstrap(c) 1511 s.newNode(c, "thenode1", "host1", map[string]interface{}{ 1512 "memory": 8192, 1513 }) 1514 params := environs.StartInstanceParams{Volumes: []storage.VolumeParams{ 1515 {Tag: names.NewVolumeTag("1"), Size: 2000000}, 1516 {Tag: names.NewVolumeTag("3"), Size: 2000000}, 1517 }} 1518 _, err := testing.StartInstanceWithParams(env, "1", params, nil) 1519 c.Assert(err, gc.ErrorMatches, "the version of MAAS being used does not support Juju storage") 1520 operations := s.testMAASObject.TestServer.NodesOperations() 1521 c.Check(operations, gc.DeepEquals, []string{"acquire", "acquire", "release"}) 1522 c.Assert(s.testMAASObject.TestServer.OwnedNodes()["node0"], jc.IsTrue) 1523 c.Assert(s.testMAASObject.TestServer.OwnedNodes()["thenode1"], jc.IsFalse) 1524 } 1525 1526 func (s *environSuite) TestGetAvailabilityZones(c *gc.C) { 1527 env := s.makeEnviron() 1528 1529 zones, err := env.AvailabilityZones() 1530 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 1531 c.Assert(zones, gc.IsNil) 1532 1533 s.testMAASObject.TestServer.AddZone("whatever", "andever") 1534 zones, err = env.AvailabilityZones() 1535 c.Assert(err, jc.ErrorIsNil) 1536 c.Assert(zones, gc.HasLen, 1) 1537 c.Assert(zones[0].Name(), gc.Equals, "whatever") 1538 c.Assert(zones[0].Available(), jc.IsTrue) 1539 1540 // A successful result is cached, currently for the lifetime 1541 // of the Environ. This will change if/when we have long-lived 1542 // Environs to cut down repeated IaaS requests. 1543 s.testMAASObject.TestServer.AddZone("somewhere", "outthere") 1544 zones, err = env.AvailabilityZones() 1545 c.Assert(err, jc.ErrorIsNil) 1546 c.Assert(zones, gc.HasLen, 1) 1547 c.Assert(zones[0].Name(), gc.Equals, "whatever") 1548 } 1549 1550 type mockAvailabilityZoneAllocations struct { 1551 group []instance.Id // input param 1552 result []common.AvailabilityZoneInstances 1553 err error 1554 } 1555 1556 func (m *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations( 1557 e common.ZonedEnviron, group []instance.Id, 1558 ) ([]common.AvailabilityZoneInstances, error) { 1559 m.group = group 1560 return m.result, m.err 1561 } 1562 1563 func (s *environSuite) newNode(c *gc.C, nodename, hostname string, attrs map[string]interface{}) { 1564 allAttrs := map[string]interface{}{ 1565 "system_id": nodename, 1566 "hostname": hostname, 1567 "architecture": fmt.Sprintf("%s/generic", version.Current.Arch), 1568 "memory": 1024, 1569 "cpu_count": 1, 1570 } 1571 for k, v := range attrs { 1572 allAttrs[k] = v 1573 } 1574 data, err := json.Marshal(allAttrs) 1575 c.Assert(err, jc.ErrorIsNil) 1576 s.testMAASObject.TestServer.NewNode(string(data)) 1577 lshwXML, err := s.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}}) 1578 c.Assert(err, jc.ErrorIsNil) 1579 s.testMAASObject.TestServer.AddNodeDetails(nodename, lshwXML) 1580 } 1581 1582 func (s *environSuite) bootstrap(c *gc.C) environs.Environ { 1583 s.newNode(c, "node0", "bootstrap-host", nil) 1584 s.setupFakeTools(c) 1585 env := s.makeEnviron() 1586 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 1587 Placement: "bootstrap-host", 1588 }) 1589 c.Assert(err, jc.ErrorIsNil) 1590 return env 1591 } 1592 1593 func (s *environSuite) TestStartInstanceDistributionParams(c *gc.C) { 1594 env := s.bootstrap(c) 1595 var mock mockAvailabilityZoneAllocations 1596 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1597 1598 // no distribution group specified 1599 s.newNode(c, "node1", "host1", nil) 1600 testing.AssertStartInstance(c, env, "1") 1601 c.Assert(mock.group, gc.HasLen, 0) 1602 1603 // distribution group specified: ensure it's passed through to AvailabilityZone. 1604 s.newNode(c, "node2", "host2", nil) 1605 expectedInstances := []instance.Id{"i-0", "i-1"} 1606 params := environs.StartInstanceParams{ 1607 DistributionGroup: func() ([]instance.Id, error) { 1608 return expectedInstances, nil 1609 }, 1610 } 1611 _, err := testing.StartInstanceWithParams(env, "1", params, nil) 1612 c.Assert(err, jc.ErrorIsNil) 1613 c.Assert(mock.group, gc.DeepEquals, expectedInstances) 1614 } 1615 1616 func (s *environSuite) TestStartInstanceDistributionErrors(c *gc.C) { 1617 env := s.bootstrap(c) 1618 mock := mockAvailabilityZoneAllocations{ 1619 err: errors.New("AvailabilityZoneAllocations failed"), 1620 } 1621 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1622 _, _, _, err := testing.StartInstance(env, "1") 1623 c.Assert(err, gc.ErrorMatches, "cannot get availability zone allocations: AvailabilityZoneAllocations failed") 1624 1625 mock.err = nil 1626 dgErr := errors.New("DistributionGroup failed") 1627 params := environs.StartInstanceParams{ 1628 DistributionGroup: func() ([]instance.Id, error) { 1629 return nil, dgErr 1630 }, 1631 } 1632 _, err = testing.StartInstanceWithParams(env, "1", params, nil) 1633 c.Assert(err, gc.ErrorMatches, "cannot get distribution group: DistributionGroup failed") 1634 } 1635 1636 func (s *environSuite) TestStartInstanceDistribution(c *gc.C) { 1637 env := s.bootstrap(c) 1638 s.testMAASObject.TestServer.AddZone("test-available", "description") 1639 s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "test-available"}) 1640 inst, _ := testing.AssertStartInstance(c, env, "1") 1641 c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available") 1642 } 1643 1644 func (s *environSuite) TestStartInstanceDistributionAZNotImplemented(c *gc.C) { 1645 env := s.bootstrap(c) 1646 1647 mock := mockAvailabilityZoneAllocations{err: errors.NotImplementedf("availability zones")} 1648 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1649 1650 // Instance will be created without an availability zone specified. 1651 s.newNode(c, "node1", "host1", nil) 1652 inst, _ := testing.AssertStartInstance(c, env, "1") 1653 c.Assert(inst.(*maasInstance).zone(), gc.Equals, "") 1654 } 1655 1656 func (s *environSuite) TestStartInstanceDistributionFailover(c *gc.C) { 1657 mock := mockAvailabilityZoneAllocations{ 1658 result: []common.AvailabilityZoneInstances{{ 1659 ZoneName: "zone1", 1660 }, { 1661 ZoneName: "zonelord", 1662 }, { 1663 ZoneName: "zone2", 1664 }}, 1665 } 1666 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1667 s.testMAASObject.TestServer.AddZone("zone1", "description") 1668 s.testMAASObject.TestServer.AddZone("zone2", "description") 1669 s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"}) 1670 1671 env := s.bootstrap(c) 1672 inst, _ := testing.AssertStartInstance(c, env, "1") 1673 c.Assert(inst.(*maasInstance).zone(), gc.Equals, "zone2") 1674 c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{ 1675 // one acquire for the bootstrap, three for StartInstance (with zone failover) 1676 "acquire", "acquire", "acquire", "acquire", 1677 }) 1678 c.Assert(s.testMAASObject.TestServer.NodesOperationRequestValues(), gc.DeepEquals, []url.Values{{ 1679 "name": []string{"bootstrap-host"}, 1680 "agent_name": []string{exampleAgentName}, 1681 }, { 1682 "zone": []string{"zone1"}, 1683 "agent_name": []string{exampleAgentName}, 1684 }, { 1685 "zone": []string{"zonelord"}, 1686 "agent_name": []string{exampleAgentName}, 1687 }, { 1688 "zone": []string{"zone2"}, 1689 "agent_name": []string{exampleAgentName}, 1690 }}) 1691 } 1692 1693 func (s *environSuite) TestStartInstanceDistributionOneAssigned(c *gc.C) { 1694 mock := mockAvailabilityZoneAllocations{ 1695 result: []common.AvailabilityZoneInstances{{ 1696 ZoneName: "zone1", 1697 }, { 1698 ZoneName: "zone2", 1699 }}, 1700 } 1701 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1702 s.testMAASObject.TestServer.AddZone("zone1", "description") 1703 s.testMAASObject.TestServer.AddZone("zone2", "description") 1704 s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "zone1"}) 1705 s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"}) 1706 1707 env := s.bootstrap(c) 1708 testing.AssertStartInstance(c, env, "1") 1709 c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{ 1710 // one acquire for the bootstrap, one for StartInstance. 1711 "acquire", "acquire", 1712 }) 1713 }