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