github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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/set" 23 gc "gopkg.in/check.v1" 24 goyaml "gopkg.in/yaml.v1" 25 "launchpad.net/gomaasapi" 26 27 "github.com/juju/juju/cloudconfig/cloudinit" 28 "github.com/juju/juju/constraints" 29 "github.com/juju/juju/environs" 30 "github.com/juju/juju/environs/bootstrap" 31 "github.com/juju/juju/environs/config" 32 "github.com/juju/juju/environs/simplestreams" 33 envstorage "github.com/juju/juju/environs/storage" 34 envtesting "github.com/juju/juju/environs/testing" 35 envtools "github.com/juju/juju/environs/tools" 36 "github.com/juju/juju/instance" 37 "github.com/juju/juju/juju/arch" 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{maasObject: &node, environ: suite.makeEnviron()} 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 interfaces, primaryIface, err := inst.environ.getInstanceNetworkInterfaces(inst) 963 c.Assert(err, jc.ErrorIsNil) 964 // Both wlan0 and eth0 are disabled in lshw output. 965 c.Check(primaryIface, gc.Equals, "vnet1") 966 c.Check(interfaces, jc.DeepEquals, templateInterfaces) 967 } 968 969 func (suite *environSuite) TestSetupNetworks(c *gc.C) { 970 testInstance := suite.getInstance("node1") 971 templateInterfaces := map[string]ifaceInfo{ 972 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 973 "aa:bb:cc:dd:ee:f1": {1, "eth0", true}, 974 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 975 } 976 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 977 c.Assert(err, jc.ErrorIsNil) 978 979 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 980 suite.getNetwork("LAN", 2, 42) 981 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1") 982 suite.getNetwork("Virt", 3, 0) 983 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2") 984 suite.getNetwork("WLAN", 1, 0) 985 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff") 986 networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks( 987 testInstance, 988 set.NewStrings("WLAN"), // Disable WLAN only. 989 ) 990 c.Assert(err, jc.ErrorIsNil) 991 992 // Note: order of networks is based on lshwXML 993 c.Check(primaryIface, gc.Equals, "vnet1") 994 // Unfortunately, because network.InterfaceInfo is unhashable 995 // (contains a map) we can't use jc.SameContents here. 996 c.Check(networkInfo, gc.HasLen, 3) 997 for _, info := range networkInfo { 998 switch info.DeviceIndex { 999 case 0: 1000 c.Check(info, jc.DeepEquals, network.InterfaceInfo{ 1001 MACAddress: "aa:bb:cc:dd:ee:ff", 1002 CIDR: "192.168.1.1/24", 1003 NetworkName: "WLAN", 1004 ProviderId: "WLAN", 1005 VLANTag: 0, 1006 DeviceIndex: 0, 1007 InterfaceName: "wlan0", 1008 Disabled: true, // from networksToDisable("WLAN") 1009 }) 1010 case 1: 1011 c.Check(info, jc.DeepEquals, network.InterfaceInfo{ 1012 DeviceIndex: 1, 1013 MACAddress: "aa:bb:cc:dd:ee:f1", 1014 CIDR: "192.168.2.1/24", 1015 NetworkName: "LAN", 1016 ProviderId: "LAN", 1017 VLANTag: 42, 1018 InterfaceName: "eth0", 1019 Disabled: true, // from networksToDisable("WLAN") 1020 }) 1021 case 2: 1022 c.Check(info, jc.DeepEquals, network.InterfaceInfo{ 1023 MACAddress: "aa:bb:cc:dd:ee:f2", 1024 CIDR: "192.168.3.1/24", 1025 NetworkName: "Virt", 1026 ProviderId: "Virt", 1027 VLANTag: 0, 1028 DeviceIndex: 2, 1029 InterfaceName: "vnet1", 1030 Disabled: false, 1031 }) 1032 } 1033 } 1034 } 1035 1036 // The same test, but now "Virt" network does not have matched MAC address 1037 func (suite *environSuite) TestSetupNetworksPartialMatch(c *gc.C) { 1038 testInstance := suite.getInstance("node1") 1039 templateInterfaces := map[string]ifaceInfo{ 1040 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 1041 "aa:bb:cc:dd:ee:f1": {1, "eth0", false}, 1042 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 1043 } 1044 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 1045 c.Assert(err, jc.ErrorIsNil) 1046 1047 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 1048 suite.getNetwork("LAN", 2, 42) 1049 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1") 1050 suite.getNetwork("Virt", 3, 0) 1051 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3") 1052 networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks( 1053 testInstance, 1054 set.NewStrings(), // All enabled. 1055 ) 1056 c.Assert(err, jc.ErrorIsNil) 1057 1058 // Note: order of networks is based on lshwXML 1059 c.Check(primaryIface, gc.Equals, "eth0") 1060 c.Check(networkInfo, jc.DeepEquals, []network.InterfaceInfo{{ 1061 MACAddress: "aa:bb:cc:dd:ee:f1", 1062 CIDR: "192.168.2.1/24", 1063 NetworkName: "LAN", 1064 ProviderId: "LAN", 1065 VLANTag: 42, 1066 DeviceIndex: 1, 1067 InterfaceName: "eth0", 1068 Disabled: false, 1069 }}) 1070 } 1071 1072 // The same test, but now no networks have matched MAC 1073 func (suite *environSuite) TestSetupNetworksNoMatch(c *gc.C) { 1074 testInstance := suite.getInstance("node1") 1075 templateInterfaces := map[string]ifaceInfo{ 1076 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 1077 "aa:bb:cc:dd:ee:f1": {1, "eth0", false}, 1078 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 1079 } 1080 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 1081 c.Assert(err, jc.ErrorIsNil) 1082 1083 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 1084 suite.getNetwork("Virt", 3, 0) 1085 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f3") 1086 networkInfo, primaryIface, err := suite.makeEnviron().setupNetworks( 1087 testInstance, 1088 set.NewStrings(), // All enabled. 1089 ) 1090 c.Assert(err, jc.ErrorIsNil) 1091 1092 // Note: order of networks is based on lshwXML 1093 c.Check(primaryIface, gc.Equals, "eth0") 1094 c.Check(networkInfo, gc.HasLen, 0) 1095 } 1096 1097 func (suite *environSuite) TestSupportsNetworking(c *gc.C) { 1098 env := suite.makeEnviron() 1099 _, supported := environs.SupportsNetworking(env) 1100 c.Assert(supported, jc.IsTrue) 1101 } 1102 1103 func (suite *environSuite) TestSupportsAddressAllocation(c *gc.C) { 1104 env := suite.makeEnviron() 1105 supported, err := env.SupportsAddressAllocation("") 1106 c.Assert(err, jc.ErrorIsNil) 1107 c.Assert(supported, jc.IsTrue) 1108 } 1109 1110 func (suite *environSuite) createSubnets(c *gc.C, duplicates bool) instance.Instance { 1111 testInstance := suite.getInstance("node1") 1112 templateInterfaces := map[string]ifaceInfo{ 1113 "aa:bb:cc:dd:ee:ff": {0, "wlan0", true}, 1114 "aa:bb:cc:dd:ee:f1": {1, "eth0", false}, 1115 "aa:bb:cc:dd:ee:f2": {2, "vnet1", false}, 1116 } 1117 if duplicates { 1118 templateInterfaces["aa:bb:cc:dd:ee:f3"] = ifaceInfo{3, "eth1", true} 1119 templateInterfaces["aa:bb:cc:dd:ee:f4"] = ifaceInfo{4, "vnet2", false} 1120 } 1121 lshwXML, err := suite.generateHWTemplate(templateInterfaces) 1122 c.Assert(err, jc.ErrorIsNil) 1123 1124 suite.testMAASObject.TestServer.AddNodeDetails("node1", lshwXML) 1125 // resulting CIDR 192.168.2.1/24 1126 suite.getNetwork("LAN", 2, 42) 1127 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f1") 1128 // resulting CIDR 192.168.3.1/24 1129 suite.getNetwork("Virt", 3, 0) 1130 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f2") 1131 // resulting CIDR 192.168.1.1/24 1132 suite.getNetwork("WLAN", 1, 0) 1133 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "WLAN", "aa:bb:cc:dd:ee:ff") 1134 if duplicates { 1135 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "LAN", "aa:bb:cc:dd:ee:f3") 1136 suite.testMAASObject.TestServer.ConnectNodeToNetworkWithMACAddress("node1", "Virt", "aa:bb:cc:dd:ee:f4") 1137 } 1138 1139 // needed for getNodeGroups to work 1140 suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "precise"}`) 1141 suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "amd64", "release": "precise"}`) 1142 1143 jsonText1 := `{ 1144 "ip_range_high": "192.168.2.255", 1145 "ip_range_low": "192.168.2.128", 1146 "broadcast_ip": "192.168.2.255", 1147 "static_ip_range_low": "192.168.2.0", 1148 "name": "eth0", 1149 "ip": "192.168.2.1", 1150 "subnet_mask": "255.255.255.0", 1151 "management": 2, 1152 "static_ip_range_high": "192.168.2.127", 1153 "interface": "eth0" 1154 }` 1155 jsonText2 := `{ 1156 "ip_range_high": "172.16.0.128", 1157 "ip_range_low": "172.16.0.2", 1158 "broadcast_ip": "172.16.0.255", 1159 "static_ip_range_low": "172.16.0.129", 1160 "name": "eth1", 1161 "ip": "172.16.0.2", 1162 "subnet_mask": "255.255.255.0", 1163 "management": 2, 1164 "static_ip_range_high": "172.16.0.255", 1165 "interface": "eth1" 1166 }` 1167 jsonText3 := `{ 1168 "ip_range_high": "192.168.1.128", 1169 "ip_range_low": "192.168.1.2", 1170 "broadcast_ip": "192.168.1.255", 1171 "static_ip_range_low": "192.168.1.129", 1172 "name": "eth2", 1173 "ip": "192.168.1.2", 1174 "subnet_mask": "255.255.255.0", 1175 "management": 2, 1176 "static_ip_range_high": "192.168.1.255", 1177 "interface": "eth2" 1178 }` 1179 jsonText4 := `{ 1180 "ip_range_high": "172.16.8.128", 1181 "ip_range_low": "172.16.8.2", 1182 "broadcast_ip": "172.16.8.255", 1183 "static_ip_range_low": "172.16.0.129", 1184 "name": "eth3", 1185 "ip": "172.16.8.2", 1186 "subnet_mask": "255.255.255.0", 1187 "management": 2, 1188 "static_ip_range_high": "172.16.8.255", 1189 "interface": "eth3" 1190 }` 1191 suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText1) 1192 suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-0", jsonText2) 1193 suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText3) 1194 suite.testMAASObject.TestServer.NewNodegroupInterface("uuid-1", jsonText4) 1195 return testInstance 1196 } 1197 1198 func (suite *environSuite) TestNetworkInterfaces(c *gc.C) { 1199 testInstance := suite.createSubnets(c, false) 1200 1201 netInfo, err := suite.makeEnviron().NetworkInterfaces(testInstance.Id()) 1202 c.Assert(err, jc.ErrorIsNil) 1203 1204 expectedInfo := []network.InterfaceInfo{{ 1205 DeviceIndex: 0, 1206 MACAddress: "aa:bb:cc:dd:ee:ff", 1207 CIDR: "192.168.1.1/24", 1208 ProviderSubnetId: "WLAN", 1209 VLANTag: 0, 1210 InterfaceName: "wlan0", 1211 Disabled: true, 1212 NoAutoStart: true, 1213 ConfigType: network.ConfigDHCP, 1214 ExtraConfig: nil, 1215 GatewayAddress: network.Address{}, 1216 Address: network.NewScopedAddress("192.168.1.1", network.ScopeCloudLocal), 1217 }, { 1218 DeviceIndex: 1, 1219 MACAddress: "aa:bb:cc:dd:ee:f1", 1220 CIDR: "192.168.2.1/24", 1221 ProviderSubnetId: "LAN", 1222 VLANTag: 42, 1223 InterfaceName: "eth0", 1224 Disabled: false, 1225 NoAutoStart: false, 1226 ConfigType: network.ConfigDHCP, 1227 ExtraConfig: nil, 1228 GatewayAddress: network.Address{}, 1229 Address: network.NewScopedAddress("192.168.2.1", network.ScopeCloudLocal), 1230 }, { 1231 DeviceIndex: 2, 1232 MACAddress: "aa:bb:cc:dd:ee:f2", 1233 CIDR: "192.168.3.1/24", 1234 ProviderSubnetId: "Virt", 1235 VLANTag: 0, 1236 InterfaceName: "vnet1", 1237 Disabled: false, 1238 NoAutoStart: false, 1239 ConfigType: network.ConfigDHCP, 1240 ExtraConfig: nil, 1241 GatewayAddress: network.Address{}, 1242 Address: network.NewScopedAddress("192.168.3.1", network.ScopeCloudLocal), 1243 }} 1244 network.SortInterfaceInfo(netInfo) 1245 c.Assert(netInfo, jc.DeepEquals, expectedInfo) 1246 } 1247 1248 func (suite *environSuite) TestSubnets(c *gc.C) { 1249 testInstance := suite.createSubnets(c, false) 1250 1251 netInfo, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"LAN", "Virt", "WLAN"}) 1252 c.Assert(err, jc.ErrorIsNil) 1253 1254 expectedInfo := []network.SubnetInfo{ 1255 {CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")}, 1256 {CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0}, 1257 {CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}} 1258 c.Assert(netInfo, jc.DeepEquals, expectedInfo) 1259 } 1260 1261 func (suite *environSuite) TestSubnetsNoNetIds(c *gc.C) { 1262 testInstance := suite.createSubnets(c, false) 1263 _, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{}) 1264 c.Assert(err, gc.ErrorMatches, "subnetIds must not be empty") 1265 } 1266 1267 func (suite *environSuite) TestSubnetsMissingNetwork(c *gc.C) { 1268 testInstance := suite.createSubnets(c, false) 1269 _, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"WLAN", "Missing"}) 1270 c.Assert(err, gc.ErrorMatches, "failed to find the following subnets: \\[Missing\\]") 1271 } 1272 1273 func (suite *environSuite) TestSubnetsNoDuplicates(c *gc.C) { 1274 testInstance := suite.createSubnets(c, true) 1275 1276 netInfo, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"LAN", "Virt", "WLAN"}) 1277 c.Assert(err, jc.ErrorIsNil) 1278 1279 expectedInfo := []network.SubnetInfo{ 1280 {CIDR: "192.168.2.1/24", ProviderId: "LAN", VLANTag: 42, AllocatableIPLow: net.ParseIP("192.168.2.0"), AllocatableIPHigh: net.ParseIP("192.168.2.127")}, 1281 {CIDR: "192.168.3.1/24", ProviderId: "Virt", VLANTag: 0}, 1282 {CIDR: "192.168.1.1/24", ProviderId: "WLAN", VLANTag: 0, AllocatableIPLow: net.ParseIP("192.168.1.129"), AllocatableIPHigh: net.ParseIP("192.168.1.255")}} 1283 c.Assert(netInfo, jc.DeepEquals, expectedInfo) 1284 } 1285 1286 func (suite *environSuite) TestAllocateAddress(c *gc.C) { 1287 testInstance := suite.createSubnets(c, false) 1288 env := suite.makeEnviron() 1289 1290 // note that the default test server always succeeds if we provide a 1291 // valid instance id and net id 1292 err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar") 1293 c.Assert(err, jc.ErrorIsNil) 1294 } 1295 1296 func (suite *environSuite) TestAllocateAddressDevices(c *gc.C) { 1297 suite.testMAASObject.TestServer.SetVersionJSON(`{"capabilities": ["networks-management","static-ipaddresses", "devices-management"]}`) 1298 testInstance := suite.createSubnets(c, false) 1299 env := suite.makeEnviron() 1300 1301 // note that the default test server always succeeds if we provide a 1302 // valid instance id and net id 1303 err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar") 1304 c.Assert(err, jc.ErrorIsNil) 1305 1306 devicesArray := suite.getDeviceArray(c) 1307 c.Assert(devicesArray, gc.HasLen, 1) 1308 1309 device, err := devicesArray[0].GetMap() 1310 c.Assert(err, jc.ErrorIsNil) 1311 1312 hostname, err := device["hostname"].GetString() 1313 c.Assert(err, jc.ErrorIsNil) 1314 c.Assert(hostname, gc.Equals, "bar") 1315 1316 parent, err := device["parent"].GetString() 1317 c.Assert(err, jc.ErrorIsNil) 1318 trimmedId := strings.TrimRight(string(testInstance.Id()), "/") 1319 split := strings.Split(trimmedId, "/") 1320 maasId := split[len(split)-1] 1321 c.Assert(parent, gc.Equals, maasId) 1322 1323 addressesArray, err := device["ip_addresses"].GetArray() 1324 c.Assert(err, jc.ErrorIsNil) 1325 c.Assert(addressesArray, gc.HasLen, 1) 1326 address, err := addressesArray[0].GetString() 1327 c.Assert(err, jc.ErrorIsNil) 1328 c.Assert(address, gc.Equals, "192.168.2.1") 1329 1330 macArray, err := device["macaddress_set"].GetArray() 1331 c.Assert(err, jc.ErrorIsNil) 1332 c.Assert(macArray, gc.HasLen, 1) 1333 macMap, err := macArray[0].GetMap() 1334 c.Assert(err, jc.ErrorIsNil) 1335 mac, err := macMap["mac_address"].GetString() 1336 c.Assert(err, jc.ErrorIsNil) 1337 c.Assert(mac, gc.Equals, "foo") 1338 } 1339 1340 func (suite *environSuite) getDeviceArray(c *gc.C) []gomaasapi.JSONObject { 1341 devicesURL := "/api/1.0/devices/?op=list" 1342 resp, err := http.Get(suite.testMAASObject.TestServer.Server.URL + devicesURL) 1343 c.Assert(err, jc.ErrorIsNil) 1344 c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) 1345 1346 defer resp.Body.Close() 1347 content, err := ioutil.ReadAll(resp.Body) 1348 c.Assert(err, jc.ErrorIsNil) 1349 result, err := gomaasapi.Parse(gomaasapi.Client{}, content) 1350 c.Assert(err, jc.ErrorIsNil) 1351 1352 devicesArray, err := result.GetArray() 1353 c.Assert(err, jc.ErrorIsNil) 1354 return devicesArray 1355 } 1356 1357 func (suite *environSuite) TestReleaseAddressDeletesDevice(c *gc.C) { 1358 suite.testMAASObject.TestServer.SetVersionJSON(`{"capabilities": ["networks-management","static-ipaddresses", "devices-management"]}`) 1359 testInstance := suite.createSubnets(c, false) 1360 env := suite.makeEnviron() 1361 addr := network.NewAddress("192.168.2.1") 1362 err := env.AllocateAddress(testInstance.Id(), "LAN", addr, "foo", "bar") 1363 c.Assert(err, jc.ErrorIsNil) 1364 1365 devicesArray := suite.getDeviceArray(c) 1366 c.Assert(devicesArray, gc.HasLen, 1) 1367 1368 err = env.ReleaseAddress(testInstance.Id(), "LAN", addr, "foo") 1369 c.Assert(err, jc.ErrorIsNil) 1370 1371 devicesArray = suite.getDeviceArray(c) 1372 c.Assert(devicesArray, gc.HasLen, 0) 1373 } 1374 1375 func (suite *environSuite) TestAllocateAddressInvalidInstance(c *gc.C) { 1376 env := suite.makeEnviron() 1377 addr := network.Address{Value: "192.168.2.1"} 1378 instId := instance.Id("foo") 1379 err := env.AllocateAddress(instId, "bar", addr, "foo", "bar") 1380 expected := fmt.Sprintf("failed to allocate address %q for instance %q.*", addr, instId) 1381 c.Assert(err, gc.ErrorMatches, expected) 1382 } 1383 1384 func (suite *environSuite) TestAllocateAddressMissingSubnet(c *gc.C) { 1385 testInstance := suite.createSubnets(c, false) 1386 env := suite.makeEnviron() 1387 err := env.AllocateAddress(testInstance.Id(), "bar", network.Address{Value: "192.168.2.1"}, "foo", "bar") 1388 c.Assert(errors.Cause(err), gc.ErrorMatches, "failed to find the following subnets: \\[bar\\]") 1389 } 1390 1391 func (suite *environSuite) TestAllocateAddressIPAddressUnavailable(c *gc.C) { 1392 testInstance := suite.createSubnets(c, false) 1393 env := suite.makeEnviron() 1394 1395 reserveIPAddress := func(ipaddresses gomaasapi.MAASObject, cidr string, addr network.Address) error { 1396 return gomaasapi.ServerError{StatusCode: 404} 1397 } 1398 suite.PatchValue(&ReserveIPAddress, reserveIPAddress) 1399 1400 ipAddress := network.Address{Value: "192.168.2.1"} 1401 err := env.AllocateAddress(testInstance.Id(), "LAN", ipAddress, "foo", "bar") 1402 c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressUnavailable) 1403 expected := fmt.Sprintf("failed to allocate address %q for instance %q.*", ipAddress, testInstance.Id()) 1404 c.Assert(err, gc.ErrorMatches, expected) 1405 } 1406 1407 func (s *environSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 1408 s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1") 1409 env := s.makeEnviron() 1410 placement := "zone=zone1" 1411 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1412 c.Assert(err, jc.ErrorIsNil) 1413 } 1414 1415 func (suite *environSuite) TestReleaseAddress(c *gc.C) { 1416 testInstance := suite.createSubnets(c, false) 1417 env := suite.makeEnviron() 1418 1419 err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar") 1420 c.Assert(err, jc.ErrorIsNil) 1421 1422 ipAddress := network.Address{Value: "192.168.2.1"} 1423 macAddress := "foobar" 1424 err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress) 1425 c.Assert(err, jc.ErrorIsNil) 1426 1427 // by releasing again we can test that the first release worked, *and* 1428 // the error handling of ReleaseError 1429 err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress) 1430 expected := fmt.Sprintf("(?s).*failed to release IP address %q from instance %q.*", ipAddress, testInstance.Id()) 1431 c.Assert(err, gc.ErrorMatches, expected) 1432 } 1433 1434 func (suite *environSuite) TestReleaseAddressRetry(c *gc.C) { 1435 // Patch short attempt params. 1436 suite.PatchValue(&shortAttempt, utils.AttemptStrategy{ 1437 Min: 5, 1438 }) 1439 // Patch IP address release call to MAAS. 1440 retries := 0 1441 enoughRetries := 10 1442 suite.PatchValue(&ReleaseIPAddress, func(ipaddresses gomaasapi.MAASObject, addr network.Address) error { 1443 retries++ 1444 if retries < enoughRetries { 1445 return errors.New("ouch") 1446 } 1447 return nil 1448 }) 1449 1450 testInstance := suite.createSubnets(c, false) 1451 env := suite.makeEnviron() 1452 1453 err := env.AllocateAddress(testInstance.Id(), "LAN", network.Address{Value: "192.168.2.1"}, "foo", "bar") 1454 c.Assert(err, jc.ErrorIsNil) 1455 1456 // ReleaseAddress must fail with 5 retries. 1457 ipAddress := network.Address{Value: "192.168.2.1"} 1458 macAddress := "foobar" 1459 err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress) 1460 expected := fmt.Sprintf("(?s).*failed to release IP address %q from instance %q: ouch", ipAddress, testInstance.Id()) 1461 c.Assert(err, gc.ErrorMatches, expected) 1462 c.Assert(retries, gc.Equals, 5) 1463 1464 // Now let it succeed after 3 retries. 1465 retries = 0 1466 enoughRetries = 3 1467 err = env.ReleaseAddress(testInstance.Id(), "bar", ipAddress, macAddress) 1468 c.Assert(err, jc.ErrorIsNil) 1469 c.Assert(retries, gc.Equals, 3) 1470 } 1471 1472 func (s *environSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 1473 s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1") 1474 env := s.makeEnviron() 1475 placement := "zone=zone2" 1476 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1477 c.Assert(err, gc.ErrorMatches, `invalid availability zone "zone2"`) 1478 } 1479 1480 func (s *environSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) { 1481 env := s.makeEnviron() 1482 placement := "zone=test-unknown" 1483 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1484 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 1485 } 1486 1487 func (s *environSuite) TestPrecheckInvalidPlacement(c *gc.C) { 1488 env := s.makeEnviron() 1489 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "notzone=anything") 1490 c.Assert(err, gc.ErrorMatches, "unknown placement directive: notzone=anything") 1491 } 1492 1493 func (s *environSuite) TestPrecheckNodePlacement(c *gc.C) { 1494 env := s.makeEnviron() 1495 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, "assumed_node_name") 1496 c.Assert(err, jc.ErrorIsNil) 1497 } 1498 1499 func (s *environSuite) TestStartInstanceAvailZone(c *gc.C) { 1500 // Add a node for the started instance. 1501 s.newNode(c, "thenode1", "host1", map[string]interface{}{"zone": "test-available"}) 1502 s.testMAASObject.TestServer.AddZone("test-available", "description") 1503 inst, err := s.testStartInstanceAvailZone(c, "test-available") 1504 c.Assert(err, jc.ErrorIsNil) 1505 c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available") 1506 } 1507 1508 func (s *environSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 1509 s.testMAASObject.TestServer.AddZone("test-available", "description") 1510 _, err := s.testStartInstanceAvailZone(c, "test-unknown") 1511 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 1512 } 1513 1514 func (s *environSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { 1515 env := s.bootstrap(c) 1516 params := environs.StartInstanceParams{Placement: "zone=" + zone} 1517 result, err := testing.StartInstanceWithParams(env, "1", params, nil) 1518 if err != nil { 1519 return nil, err 1520 } 1521 return result.Instance, nil 1522 } 1523 1524 func (s *environSuite) TestStartInstanceUnmetConstraints(c *gc.C) { 1525 env := s.bootstrap(c) 1526 s.newNode(c, "thenode1", "host1", nil) 1527 params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")} 1528 _, err := testing.StartInstanceWithParams(env, "1", params, nil) 1529 c.Assert(err, gc.ErrorMatches, "cannot run instances:.* 409.*") 1530 } 1531 1532 func (s *environSuite) TestStartInstanceConstraints(c *gc.C) { 1533 env := s.bootstrap(c) 1534 s.newNode(c, "thenode1", "host1", nil) 1535 s.newNode(c, "thenode2", "host2", map[string]interface{}{"memory": 8192}) 1536 params := environs.StartInstanceParams{Constraints: constraints.MustParse("mem=8G")} 1537 result, err := testing.StartInstanceWithParams(env, "1", params, nil) 1538 c.Assert(err, jc.ErrorIsNil) 1539 c.Assert(*result.Hardware.Mem, gc.Equals, uint64(8192)) 1540 } 1541 1542 var nodeStorageAttrs = []map[string]interface{}{ 1543 { 1544 "name": "sdb", 1545 "id": 1, 1546 "id_path": "/dev/disk/by-id/id_for_sda", 1547 "path": "/dev/sdb", 1548 "model": "Samsung_SSD_850_EVO_250GB", 1549 "block_size": 4096, 1550 "serial": "S21NNSAFC38075L", 1551 "size": uint64(250059350016), 1552 }, 1553 { 1554 "name": "sda", 1555 "id": 2, 1556 "path": "/dev/sda", 1557 "model": "Samsung_SSD_850_EVO_250GB", 1558 "block_size": 4096, 1559 "serial": "XXXX", 1560 "size": uint64(250059350016), 1561 }, 1562 { 1563 "name": "sdc", 1564 "id": 3, 1565 "path": "/dev/sdc", 1566 "model": "Samsung_SSD_850_EVO_250GB", 1567 "block_size": 4096, 1568 "serial": "YYYYYYY", 1569 "size": uint64(250059350016), 1570 }, 1571 } 1572 1573 var storageConstraintAttrs = map[string]interface{}{ 1574 "1": "1", 1575 "2": "root", 1576 "3": "3", 1577 } 1578 1579 func (s *environSuite) TestStartInstanceStorage(c *gc.C) { 1580 env := s.bootstrap(c) 1581 s.newNode(c, "thenode1", "host1", map[string]interface{}{ 1582 "memory": 8192, 1583 "physicalblockdevice_set": nodeStorageAttrs, 1584 "constraint_map": storageConstraintAttrs, 1585 }) 1586 params := environs.StartInstanceParams{Volumes: []storage.VolumeParams{ 1587 {Tag: names.NewVolumeTag("1"), Size: 2000000}, 1588 {Tag: names.NewVolumeTag("3"), Size: 2000000}, 1589 }} 1590 result, err := testing.StartInstanceWithParams(env, "1", params, nil) 1591 c.Assert(err, jc.ErrorIsNil) 1592 c.Check(result.Volumes, jc.DeepEquals, []storage.Volume{ 1593 { 1594 names.NewVolumeTag("1"), 1595 storage.VolumeInfo{ 1596 Size: 238475, 1597 VolumeId: "volume-1", 1598 HardwareId: "id_for_sda", 1599 }, 1600 }, 1601 { 1602 names.NewVolumeTag("3"), 1603 storage.VolumeInfo{ 1604 Size: 238475, 1605 VolumeId: "volume-3", 1606 HardwareId: "", 1607 }, 1608 }, 1609 }) 1610 c.Assert(result.VolumeAttachments, jc.DeepEquals, []storage.VolumeAttachment{ 1611 { 1612 names.NewVolumeTag("1"), 1613 names.NewMachineTag("1"), 1614 storage.VolumeAttachmentInfo{ 1615 DeviceName: "", 1616 ReadOnly: false, 1617 }, 1618 }, 1619 { 1620 names.NewVolumeTag("3"), 1621 names.NewMachineTag("1"), 1622 storage.VolumeAttachmentInfo{ 1623 DeviceName: "sdc", 1624 ReadOnly: false, 1625 }, 1626 }, 1627 }) 1628 } 1629 1630 func (s *environSuite) TestStartInstanceUnsupportedStorage(c *gc.C) { 1631 env := s.bootstrap(c) 1632 s.newNode(c, "thenode1", "host1", map[string]interface{}{ 1633 "memory": 8192, 1634 }) 1635 params := environs.StartInstanceParams{Volumes: []storage.VolumeParams{ 1636 {Tag: names.NewVolumeTag("1"), Size: 2000000}, 1637 {Tag: names.NewVolumeTag("3"), Size: 2000000}, 1638 }} 1639 _, err := testing.StartInstanceWithParams(env, "1", params, nil) 1640 c.Assert(err, gc.ErrorMatches, "the version of MAAS being used does not support Juju storage") 1641 operations := s.testMAASObject.TestServer.NodesOperations() 1642 c.Check(operations, gc.DeepEquals, []string{"acquire", "acquire", "release"}) 1643 c.Assert(s.testMAASObject.TestServer.OwnedNodes()["node0"], jc.IsTrue) 1644 c.Assert(s.testMAASObject.TestServer.OwnedNodes()["thenode1"], jc.IsFalse) 1645 } 1646 1647 func (s *environSuite) TestGetAvailabilityZones(c *gc.C) { 1648 env := s.makeEnviron() 1649 1650 zones, err := env.AvailabilityZones() 1651 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 1652 c.Assert(zones, gc.IsNil) 1653 1654 s.testMAASObject.TestServer.AddZone("whatever", "andever") 1655 zones, err = env.AvailabilityZones() 1656 c.Assert(err, jc.ErrorIsNil) 1657 c.Assert(zones, gc.HasLen, 1) 1658 c.Assert(zones[0].Name(), gc.Equals, "whatever") 1659 c.Assert(zones[0].Available(), jc.IsTrue) 1660 1661 // A successful result is cached, currently for the lifetime 1662 // of the Environ. This will change if/when we have long-lived 1663 // Environs to cut down repeated IaaS requests. 1664 s.testMAASObject.TestServer.AddZone("somewhere", "outthere") 1665 zones, err = env.AvailabilityZones() 1666 c.Assert(err, jc.ErrorIsNil) 1667 c.Assert(zones, gc.HasLen, 1) 1668 c.Assert(zones[0].Name(), gc.Equals, "whatever") 1669 } 1670 1671 type mockAvailabilityZoneAllocations struct { 1672 group []instance.Id // input param 1673 result []common.AvailabilityZoneInstances 1674 err error 1675 } 1676 1677 func (m *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations( 1678 e common.ZonedEnviron, group []instance.Id, 1679 ) ([]common.AvailabilityZoneInstances, error) { 1680 m.group = group 1681 return m.result, m.err 1682 } 1683 1684 func (s *environSuite) newNode(c *gc.C, nodename, hostname string, attrs map[string]interface{}) { 1685 allAttrs := map[string]interface{}{ 1686 "system_id": nodename, 1687 "hostname": hostname, 1688 "architecture": fmt.Sprintf("%s/generic", arch.HostArch()), 1689 "memory": 1024, 1690 "cpu_count": 1, 1691 } 1692 for k, v := range attrs { 1693 allAttrs[k] = v 1694 } 1695 data, err := json.Marshal(allAttrs) 1696 c.Assert(err, jc.ErrorIsNil) 1697 s.testMAASObject.TestServer.NewNode(string(data)) 1698 lshwXML, err := s.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}}) 1699 c.Assert(err, jc.ErrorIsNil) 1700 s.testMAASObject.TestServer.AddNodeDetails(nodename, lshwXML) 1701 } 1702 1703 func (s *environSuite) bootstrap(c *gc.C) environs.Environ { 1704 s.newNode(c, "node0", "bootstrap-host", nil) 1705 s.setupFakeTools(c) 1706 env := s.makeEnviron() 1707 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 1708 Placement: "bootstrap-host", 1709 }) 1710 c.Assert(err, jc.ErrorIsNil) 1711 return env 1712 } 1713 1714 func (s *environSuite) TestStartInstanceDistributionParams(c *gc.C) { 1715 env := s.bootstrap(c) 1716 var mock mockAvailabilityZoneAllocations 1717 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1718 1719 // no distribution group specified 1720 s.newNode(c, "node1", "host1", nil) 1721 testing.AssertStartInstance(c, env, "1") 1722 c.Assert(mock.group, gc.HasLen, 0) 1723 1724 // distribution group specified: ensure it's passed through to AvailabilityZone. 1725 s.newNode(c, "node2", "host2", nil) 1726 expectedInstances := []instance.Id{"i-0", "i-1"} 1727 params := environs.StartInstanceParams{ 1728 DistributionGroup: func() ([]instance.Id, error) { 1729 return expectedInstances, nil 1730 }, 1731 } 1732 _, err := testing.StartInstanceWithParams(env, "1", params, nil) 1733 c.Assert(err, jc.ErrorIsNil) 1734 c.Assert(mock.group, gc.DeepEquals, expectedInstances) 1735 } 1736 1737 func (s *environSuite) TestStartInstanceDistributionErrors(c *gc.C) { 1738 env := s.bootstrap(c) 1739 mock := mockAvailabilityZoneAllocations{ 1740 err: errors.New("AvailabilityZoneAllocations failed"), 1741 } 1742 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1743 _, _, _, err := testing.StartInstance(env, "1") 1744 c.Assert(err, gc.ErrorMatches, "cannot get availability zone allocations: AvailabilityZoneAllocations failed") 1745 1746 mock.err = nil 1747 dgErr := errors.New("DistributionGroup failed") 1748 params := environs.StartInstanceParams{ 1749 DistributionGroup: func() ([]instance.Id, error) { 1750 return nil, dgErr 1751 }, 1752 } 1753 _, err = testing.StartInstanceWithParams(env, "1", params, nil) 1754 c.Assert(err, gc.ErrorMatches, "cannot get distribution group: DistributionGroup failed") 1755 } 1756 1757 func (s *environSuite) TestStartInstanceDistribution(c *gc.C) { 1758 env := s.bootstrap(c) 1759 s.testMAASObject.TestServer.AddZone("test-available", "description") 1760 s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "test-available"}) 1761 inst, _ := testing.AssertStartInstance(c, env, "1") 1762 c.Assert(inst.(*maasInstance).zone(), gc.Equals, "test-available") 1763 } 1764 1765 func (s *environSuite) TestStartInstanceDistributionAZNotImplemented(c *gc.C) { 1766 env := s.bootstrap(c) 1767 1768 mock := mockAvailabilityZoneAllocations{err: errors.NotImplementedf("availability zones")} 1769 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1770 1771 // Instance will be created without an availability zone specified. 1772 s.newNode(c, "node1", "host1", nil) 1773 inst, _ := testing.AssertStartInstance(c, env, "1") 1774 c.Assert(inst.(*maasInstance).zone(), gc.Equals, "") 1775 } 1776 1777 func (s *environSuite) TestStartInstanceDistributionFailover(c *gc.C) { 1778 mock := mockAvailabilityZoneAllocations{ 1779 result: []common.AvailabilityZoneInstances{{ 1780 ZoneName: "zone1", 1781 }, { 1782 ZoneName: "zonelord", 1783 }, { 1784 ZoneName: "zone2", 1785 }}, 1786 } 1787 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1788 s.testMAASObject.TestServer.AddZone("zone1", "description") 1789 s.testMAASObject.TestServer.AddZone("zone2", "description") 1790 s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"}) 1791 1792 env := s.bootstrap(c) 1793 inst, _ := testing.AssertStartInstance(c, env, "1") 1794 c.Assert(inst.(*maasInstance).zone(), gc.Equals, "zone2") 1795 c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{ 1796 // one acquire for the bootstrap, three for StartInstance (with zone failover) 1797 "acquire", "acquire", "acquire", "acquire", 1798 }) 1799 c.Assert(s.testMAASObject.TestServer.NodesOperationRequestValues(), gc.DeepEquals, []url.Values{{ 1800 "name": []string{"bootstrap-host"}, 1801 "agent_name": []string{exampleAgentName}, 1802 }, { 1803 "zone": []string{"zone1"}, 1804 "agent_name": []string{exampleAgentName}, 1805 }, { 1806 "zone": []string{"zonelord"}, 1807 "agent_name": []string{exampleAgentName}, 1808 }, { 1809 "zone": []string{"zone2"}, 1810 "agent_name": []string{exampleAgentName}, 1811 }}) 1812 } 1813 1814 func (s *environSuite) TestStartInstanceDistributionOneAssigned(c *gc.C) { 1815 mock := mockAvailabilityZoneAllocations{ 1816 result: []common.AvailabilityZoneInstances{{ 1817 ZoneName: "zone1", 1818 }, { 1819 ZoneName: "zone2", 1820 }}, 1821 } 1822 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1823 s.testMAASObject.TestServer.AddZone("zone1", "description") 1824 s.testMAASObject.TestServer.AddZone("zone2", "description") 1825 s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "zone1"}) 1826 s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"}) 1827 1828 env := s.bootstrap(c) 1829 testing.AssertStartInstance(c, env, "1") 1830 c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{ 1831 // one acquire for the bootstrap, one for StartInstance. 1832 "acquire", "acquire", 1833 }) 1834 }