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