github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "net/url" 12 "regexp" 13 14 "github.com/juju/errors" 15 "github.com/juju/gomaasapi" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils" 18 "github.com/juju/utils/arch" 19 "github.com/juju/utils/series" 20 "github.com/juju/version" 21 gc "gopkg.in/check.v1" 22 "gopkg.in/juju/names.v2" 23 goyaml "gopkg.in/yaml.v2" 24 25 "github.com/juju/juju/cloudconfig/cloudinit" 26 "github.com/juju/juju/constraints" 27 "github.com/juju/juju/environs" 28 "github.com/juju/juju/environs/bootstrap" 29 envstorage "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 "github.com/juju/juju/storage" 37 coretesting "github.com/juju/juju/testing" 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 // ifaceInfo describes an interface to be created on the test server. 51 type ifaceInfo struct { 52 DeviceIndex int 53 InterfaceName string 54 Disabled bool 55 } 56 57 func (suite *environSuite) addNode(jsonText string) instance.Id { 58 node := suite.testMAASObject.TestServer.NewNode(jsonText) 59 resourceURI, _ := node.GetField("resource_uri") 60 return instance.Id(resourceURI) 61 } 62 63 func (suite *environSuite) TestInstancesReturnsInstances(c *gc.C) { 64 id := suite.addNode(allocatedNode) 65 instances, err := suite.makeEnviron().Instances([]instance.Id{id}) 66 67 c.Check(err, jc.ErrorIsNil) 68 c.Assert(instances, gc.HasLen, 1) 69 c.Assert(instances[0].Id(), gc.Equals, id) 70 } 71 72 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfEmptyParameter(c *gc.C) { 73 suite.addNode(allocatedNode) 74 instances, err := suite.makeEnviron().Instances([]instance.Id{}) 75 76 c.Check(err, gc.Equals, environs.ErrNoInstances) 77 c.Check(instances, gc.IsNil) 78 } 79 80 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNilParameter(c *gc.C) { 81 suite.addNode(allocatedNode) 82 instances, err := suite.makeEnviron().Instances(nil) 83 84 c.Check(err, gc.Equals, environs.ErrNoInstances) 85 c.Check(instances, gc.IsNil) 86 } 87 88 func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoneFound(c *gc.C) { 89 instances, err := suite.makeEnviron().Instances([]instance.Id{"unknown"}) 90 c.Check(err, gc.Equals, environs.ErrNoInstances) 91 c.Check(instances, gc.IsNil) 92 } 93 94 func (suite *environSuite) TestAllInstances(c *gc.C) { 95 id := suite.addNode(allocatedNode) 96 instances, err := suite.makeEnviron().AllInstances() 97 98 c.Check(err, jc.ErrorIsNil) 99 c.Assert(instances, gc.HasLen, 1) 100 c.Assert(instances[0].Id(), gc.Equals, id) 101 } 102 103 func (suite *environSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *gc.C) { 104 instances, err := suite.makeEnviron().AllInstances() 105 106 c.Check(err, jc.ErrorIsNil) 107 c.Check(instances, gc.HasLen, 0) 108 } 109 110 func (suite *environSuite) TestInstancesReturnsErrorIfPartialInstances(c *gc.C) { 111 known := suite.addNode(allocatedNode) 112 suite.addNode(`{"system_id": "test2"}`) 113 unknown := instance.Id("unknown systemID") 114 instances, err := suite.makeEnviron().Instances([]instance.Id{known, unknown}) 115 116 c.Check(err, gc.Equals, environs.ErrPartialInstances) 117 c.Assert(instances, gc.HasLen, 2) 118 c.Check(instances[0].Id(), gc.Equals, known) 119 c.Check(instances[1], gc.IsNil) 120 } 121 122 func (suite *environSuite) TestStorageReturnsStorage(c *gc.C) { 123 env := suite.makeEnviron() 124 stor := env.Storage() 125 c.Check(stor, gc.NotNil) 126 // The Storage object is really a maasStorage. 127 specificStorage := stor.(*maas1Storage) 128 // Its environment pointer refers back to its environment. 129 c.Check(specificStorage.environ, gc.Equals, env) 130 } 131 132 func decodeUserData(userData string) ([]byte, error) { 133 data, err := base64.StdEncoding.DecodeString(userData) 134 if err != nil { 135 return []byte(""), err 136 } 137 return utils.Gunzip(data) 138 } 139 140 func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) { 141 suite.setupFakeTools(c) 142 env := suite.makeEnviron() 143 // Create node 0: it will be used as the bootstrap node. 144 suite.newNode(c, "node0", "host0", nil) 145 suite.addSubnet(c, 9, 9, "node0") 146 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 147 ControllerConfig: coretesting.FakeControllerConfig(), 148 AdminSecret: testing.AdminSecret, 149 CAPrivateKey: coretesting.CAKey, 150 }) 151 c.Assert(err, jc.ErrorIsNil) 152 // The bootstrap node has been acquired and started. 153 operations := suite.testMAASObject.TestServer.NodeOperations() 154 actions, found := operations["node0"] 155 c.Check(found, jc.IsTrue) 156 c.Check(actions, gc.DeepEquals, []string{"acquire", "start"}) 157 158 // Test the instance id is correctly recorded for the bootstrap node. 159 // Check that ControllerInstances returns the id of the bootstrap machine. 160 instanceIds, err := env.ControllerInstances(suite.controllerUUID) 161 c.Assert(err, jc.ErrorIsNil) 162 c.Assert(instanceIds, gc.HasLen, 1) 163 insts, err := env.AllInstances() 164 c.Assert(err, jc.ErrorIsNil) 165 c.Assert(insts, gc.HasLen, 1) 166 c.Check(insts[0].Id(), gc.Equals, instanceIds[0]) 167 168 // Create node 1: it will be used as instance number 1. 169 suite.newNode(c, "node1", "host1", nil) 170 suite.addSubnet(c, 8, 8, "node1") 171 instance, hc := testing.AssertStartInstance(c, env, suite.controllerUUID, "1") 172 c.Assert(err, jc.ErrorIsNil) 173 c.Check(instance, gc.NotNil) 174 c.Assert(hc, gc.NotNil) 175 c.Check(hc.String(), gc.Equals, fmt.Sprintf("arch=%s cores=1 mem=1024M availability-zone=test_zone", arch.HostArch())) 176 177 // The instance number 1 has been acquired and started. 178 actions, found = operations["node1"] 179 c.Assert(found, jc.IsTrue) 180 c.Check(actions, gc.DeepEquals, []string{"acquire", "start"}) 181 182 // The value of the "user data" parameter used when starting the node 183 // contains the run cmd used to write the machine information onto 184 // the node's filesystem. 185 requestValues := suite.testMAASObject.TestServer.NodeOperationRequestValues() 186 nodeRequestValues, found := requestValues["node1"] 187 c.Assert(found, jc.IsTrue) 188 c.Assert(len(nodeRequestValues), gc.Equals, 2) 189 userData := nodeRequestValues[1].Get("user_data") 190 decodedUserData, err := decodeUserData(userData) 191 c.Assert(err, jc.ErrorIsNil) 192 info := machineInfo{"host1"} 193 cloudcfg, err := cloudinit.New("precise") 194 c.Assert(err, jc.ErrorIsNil) 195 cloudinitRunCmd, err := info.cloudinitRunCmd(cloudcfg) 196 c.Assert(err, jc.ErrorIsNil) 197 data, err := goyaml.Marshal(cloudinitRunCmd) 198 c.Assert(err, jc.ErrorIsNil) 199 c.Check(string(decodedUserData), jc.Contains, string(data)) 200 201 // Trash the tools and try to start another instance. 202 suite.PatchValue(&envtools.DefaultBaseURL, "") 203 instance, _, _, err = testing.StartInstance(env, suite.controllerUUID, "2") 204 c.Check(instance, gc.IsNil) 205 c.Check(err, jc.Satisfies, errors.IsNotFound) 206 } 207 208 func (suite *environSuite) getInstance(systemId string) *maas1Instance { 209 input := fmt.Sprintf(`{"system_id": %q}`, systemId) 210 node := suite.testMAASObject.TestServer.NewNode(input) 211 statusGetter := func(instance.Id) (string, string) { 212 return "unknown", "FAKE" 213 } 214 215 return &maas1Instance{&node, nil, statusGetter} 216 } 217 218 func (suite *environSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) { 219 suite.getInstance("test1") 220 221 err := suite.makeEnviron().StopInstances() 222 c.Check(err, jc.ErrorIsNil) 223 operations := suite.testMAASObject.TestServer.NodeOperations() 224 c.Check(operations, gc.DeepEquals, map[string][]string{}) 225 } 226 227 func (suite *environSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) { 228 suite.getInstance("test1") 229 suite.getInstance("test2") 230 suite.getInstance("test3") 231 // mark test1 and test2 as being allocated, but not test3. 232 // The release operation will ignore test3. 233 suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true 234 suite.testMAASObject.TestServer.OwnedNodes()["test2"] = true 235 236 err := suite.makeEnviron().StopInstances("test1", "test2", "test3") 237 c.Check(err, jc.ErrorIsNil) 238 operations := suite.testMAASObject.TestServer.NodesOperations() 239 c.Check(operations, gc.DeepEquals, []string{"release"}) 240 c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse) 241 c.Assert(suite.testMAASObject.TestServer.OwnedNodes()["test2"], jc.IsFalse) 242 } 243 244 func (suite *environSuite) TestStopInstancesIgnoresConflict(c *gc.C) { 245 releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error { 246 return gomaasapi.ServerError{StatusCode: 409} 247 } 248 suite.PatchValue(&ReleaseNodes, releaseNodes) 249 env := suite.makeEnviron() 250 err := env.StopInstances("test1") 251 c.Assert(err, jc.ErrorIsNil) 252 } 253 254 func (suite *environSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) { 255 attemptedNodes := [][]string{} 256 releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error { 257 attemptedNodes = append(attemptedNodes, ids["nodes"]) 258 return gomaasapi.ServerError{StatusCode: 404} 259 } 260 suite.PatchValue(&ReleaseNodes, releaseNodes) 261 env := suite.makeEnviron() 262 err := env.StopInstances("test1", "test2") 263 c.Assert(err, jc.ErrorIsNil) 264 265 expectedNodes := [][]string{{"test1", "test2"}, {"test1"}, {"test2"}} 266 c.Assert(attemptedNodes, gc.DeepEquals, expectedNodes) 267 } 268 269 func (suite *environSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) { 270 releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error { 271 return gomaasapi.ServerError{StatusCode: 405} 272 } 273 suite.PatchValue(&ReleaseNodes, releaseNodes) 274 env := suite.makeEnviron() 275 err := env.StopInstances("test1") 276 c.Assert(err, gc.NotNil) 277 maasErr, ok := errors.Cause(err).(gomaasapi.ServerError) 278 c.Assert(ok, jc.IsTrue) 279 c.Assert(maasErr.StatusCode, gc.Equals, 405) 280 } 281 282 func (suite *environSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) { 283 releaseNodes := func(nodes gomaasapi.MAASObject, ids url.Values) error { 284 return environs.ErrNoInstances 285 } 286 suite.PatchValue(&ReleaseNodes, releaseNodes) 287 env := suite.makeEnviron() 288 err := env.StopInstances("test1") 289 c.Assert(err, gc.NotNil) 290 c.Assert(errors.Cause(err), gc.Equals, environs.ErrNoInstances) 291 } 292 293 func (suite *environSuite) TestControllerInstances(c *gc.C) { 294 env := suite.makeEnviron() 295 _, err := env.ControllerInstances(suite.controllerUUID) 296 c.Assert(err, gc.Equals, environs.ErrNotBootstrapped) 297 298 tests := [][]instance.Id{{}, {"inst-0"}, {"inst-0", "inst-1"}} 299 for _, expected := range tests { 300 err := common.SaveState(env.Storage(), &common.BootstrapState{ 301 StateInstances: expected, 302 }) 303 c.Assert(err, jc.ErrorIsNil) 304 controllerInstances, err := env.ControllerInstances(suite.controllerUUID) 305 c.Assert(err, jc.ErrorIsNil) 306 c.Assert(controllerInstances, jc.SameContents, expected) 307 } 308 } 309 310 func (suite *environSuite) TestControllerInstancesFailsIfNoStateInstances(c *gc.C) { 311 env := suite.makeEnviron() 312 _, err := env.ControllerInstances(suite.controllerUUID) 313 c.Check(err, gc.Equals, environs.ErrNotBootstrapped) 314 } 315 316 func (suite *environSuite) TestDestroy(c *gc.C) { 317 env := suite.makeEnviron() 318 suite.getInstance("test1") 319 suite.testMAASObject.TestServer.OwnedNodes()["test1"] = true // simulate acquire 320 data := makeRandomBytes(10) 321 suite.testMAASObject.TestServer.NewFile("filename", data) 322 stor := env.Storage() 323 324 err := env.Destroy() 325 c.Check(err, jc.ErrorIsNil) 326 327 // Instances have been stopped. 328 operations := suite.testMAASObject.TestServer.NodesOperations() 329 c.Check(operations, gc.DeepEquals, []string{"release"}) 330 c.Check(suite.testMAASObject.TestServer.OwnedNodes()["test1"], jc.IsFalse) 331 // Files have been cleaned up. 332 listing, err := envstorage.List(stor, "") 333 c.Assert(err, jc.ErrorIsNil) 334 c.Check(listing, gc.DeepEquals, []string{}) 335 } 336 337 func (suite *environSuite) TestBootstrapSucceeds(c *gc.C) { 338 suite.setupFakeTools(c) 339 env := suite.makeEnviron() 340 suite.newNode(c, "thenode", "host", nil) 341 suite.addSubnet(c, 9, 9, "thenode") 342 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 343 ControllerConfig: coretesting.FakeControllerConfig(), 344 AdminSecret: testing.AdminSecret, 345 CAPrivateKey: coretesting.CAKey, 346 }) 347 c.Assert(err, jc.ErrorIsNil) 348 } 349 350 func (suite *environSuite) TestBootstrapNodeNotDeployed(c *gc.C) { 351 suite.setupFakeTools(c) 352 env := suite.makeEnviron() 353 suite.newNode(c, "thenode", "host", nil) 354 suite.addSubnet(c, 9, 9, "thenode") 355 // Ensure node will not be reported as deployed by changing its status. 356 suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "4") 357 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 358 ControllerConfig: coretesting.FakeControllerConfig(), 359 AdminSecret: testing.AdminSecret, 360 CAPrivateKey: coretesting.CAKey, 361 }) 362 c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*") 363 } 364 365 func (suite *environSuite) TestBootstrapNodeFailedDeploy(c *gc.C) { 366 suite.setupFakeTools(c) 367 env := suite.makeEnviron() 368 suite.newNode(c, "thenode", "host", nil) 369 suite.addSubnet(c, 9, 9, "thenode") 370 // Set the node status to "Failed deployment" 371 suite.testMAASObject.TestServer.ChangeNode("thenode", "status", "11") 372 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 373 ControllerConfig: coretesting.FakeControllerConfig(), 374 AdminSecret: testing.AdminSecret, 375 CAPrivateKey: coretesting.CAKey, 376 }) 377 c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state. instance \"/api/.*/nodes/thenode/\" failed to deploy") 378 } 379 380 func (suite *environSuite) TestBootstrapFailsIfNoTools(c *gc.C) { 381 env := suite.makeEnviron() 382 vers := version.MustParse("1.2.3") 383 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 384 ControllerConfig: coretesting.FakeControllerConfig(), 385 AdminSecret: testing.AdminSecret, 386 CAPrivateKey: coretesting.CAKey, 387 // Disable auto-uploading by setting the agent version 388 // to something that's not the current version. 389 AgentVersion: &vers, 390 }) 391 c.Check(err, gc.ErrorMatches, "Juju cannot bootstrap because no agent binaries are available for your model(.|\n)*") 392 } 393 394 func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) { 395 suite.setupFakeTools(c) 396 env := suite.makeEnviron() 397 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 398 ControllerConfig: coretesting.FakeControllerConfig(), 399 AdminSecret: testing.AdminSecret, 400 CAPrivateKey: coretesting.CAKey, 401 }) 402 // Since there are no nodes, the attempt to allocate one returns a 403 // 409: Conflict. 404 c.Check(err, gc.ErrorMatches, ".*409.*") 405 } 406 407 func (suite *environSuite) TestGetToolsMetadataSources(c *gc.C) { 408 env := suite.makeEnviron() 409 // Add a dummy file to storage so we can use that to check the 410 // obtained source later. 411 data := makeRandomBytes(10) 412 stor := NewStorage(env) 413 err := stor.Put("tools/filename", bytes.NewBuffer([]byte(data)), int64(len(data))) 414 c.Assert(err, jc.ErrorIsNil) 415 sources, err := envtools.GetMetadataSources(env) 416 c.Assert(err, jc.ErrorIsNil) 417 c.Assert(sources, gc.HasLen, 0) 418 } 419 420 func (suite *environSuite) TestConstraintsValidator(c *gc.C) { 421 suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`) 422 env := suite.makeEnviron() 423 validator, err := env.ConstraintsValidator() 424 c.Assert(err, jc.ErrorIsNil) 425 cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo virt-type=kvm") 426 unsupported, err := validator.Validate(cons) 427 c.Assert(err, jc.ErrorIsNil) 428 c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type", "virt-type"}) 429 } 430 431 func (suite *environSuite) TestConstraintsValidatorVocab(c *gc.C) { 432 suite.testMAASObject.TestServer.AddBootImage("uuid-0", `{"architecture": "amd64", "release": "trusty"}`) 433 suite.testMAASObject.TestServer.AddBootImage("uuid-1", `{"architecture": "armhf", "release": "precise"}`) 434 env := suite.makeEnviron() 435 validator, err := env.ConstraintsValidator() 436 c.Assert(err, jc.ErrorIsNil) 437 cons := constraints.MustParse("arch=ppc64el") 438 _, err = validator.Validate(cons) 439 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are: \\[amd64 armhf\\]") 440 } 441 442 func (suite *environSuite) TestSupportsNetworking(c *gc.C) { 443 env := suite.makeEnviron() 444 _, supported := environs.SupportsNetworking(env) 445 c.Assert(supported, jc.IsTrue) 446 } 447 448 func (suite *environSuite) TestSupportsSpaces(c *gc.C) { 449 env := suite.makeEnviron() 450 supported, err := env.SupportsSpaces() 451 c.Assert(err, jc.ErrorIsNil) 452 c.Assert(supported, jc.IsTrue) 453 } 454 455 func (suite *environSuite) TestSupportsSpaceDiscovery(c *gc.C) { 456 env := suite.makeEnviron() 457 supported, err := env.SupportsSpaceDiscovery() 458 c.Assert(err, jc.ErrorIsNil) 459 c.Assert(supported, jc.IsTrue) 460 } 461 462 func (suite *environSuite) TestSubnetsWithInstanceIdAndSubnetIds(c *gc.C) { 463 server := suite.testMAASObject.TestServer 464 var subnetIDs []network.Id 465 var uintIDs []uint 466 for _, i := range []uint{1, 2, 3} { 467 server.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: fmt.Sprintf("space-%d", i)})) 468 id := suite.addSubnet(c, i, i, "node1") 469 subnetIDs = append(subnetIDs, network.Id(fmt.Sprintf("%v", id))) 470 uintIDs = append(uintIDs, id) 471 suite.addSubnet(c, i+5, i, "node2") 472 suite.addSubnet(c, i+10, i, "") // not linked to a node 473 } 474 testInstance := suite.getInstance("node1") 475 env := suite.makeEnviron() 476 477 subnetsInfo, err := env.Subnets(testInstance.Id(), subnetIDs) 478 c.Assert(err, jc.ErrorIsNil) 479 expectedInfo := []network.SubnetInfo{ 480 createSubnetInfo(uintIDs[0], 2, 1), 481 createSubnetInfo(uintIDs[1], 3, 2), 482 createSubnetInfo(uintIDs[2], 4, 3), 483 } 484 c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo) 485 486 subnetsInfo, err = env.Subnets(testInstance.Id(), subnetIDs[1:]) 487 c.Assert(err, jc.ErrorIsNil) 488 c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo[1:]) 489 } 490 491 func (suite *environSuite) createTwoSpaces() { 492 server := suite.testMAASObject.TestServer 493 server.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: "space-1"})) 494 server.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: "space-2"})) 495 } 496 497 func (suite *environSuite) TestSubnetsWithInstaceIdNoSubnetIds(c *gc.C) { 498 suite.createTwoSpaces() 499 id1 := suite.addSubnet(c, 1, 1, "node1") 500 id2 := suite.addSubnet(c, 2, 2, "node1") 501 suite.addSubnet(c, 3, 2, "") // not linked to a node 502 suite.addSubnet(c, 4, 2, "node2") // linked to another node 503 testInstance := suite.getInstance("node1") 504 env := suite.makeEnviron() 505 506 subnetsInfo, err := env.Subnets(testInstance.Id(), []network.Id{}) 507 c.Assert(err, jc.ErrorIsNil) 508 expectedInfo := []network.SubnetInfo{ 509 createSubnetInfo(id1, 2, 1), 510 createSubnetInfo(id2, 3, 2), 511 } 512 c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo) 513 514 subnetsInfo, err = env.Subnets(testInstance.Id(), nil) 515 c.Assert(err, jc.ErrorIsNil) 516 c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo) 517 } 518 519 func (suite *environSuite) TestSubnetsInvalidInstaceIdAnySubnetIds(c *gc.C) { 520 suite.createTwoSpaces() 521 suite.addSubnet(c, 1, 1, "node1") 522 suite.addSubnet(c, 2, 2, "node2") 523 524 _, err := suite.makeEnviron().Subnets("invalid", []network.Id{"anything"}) 525 c.Assert(err, gc.ErrorMatches, `instance "invalid" not found`) 526 c.Assert(err, jc.Satisfies, errors.IsNotFound) 527 } 528 529 func (suite *environSuite) TestSubnetsNoInstanceIdWithSubnetIds(c *gc.C) { 530 suite.createTwoSpaces() 531 id1 := suite.addSubnet(c, 1, 1, "node1") 532 id2 := suite.addSubnet(c, 2, 2, "node2") 533 subnetIDs := []network.Id{ 534 network.Id(fmt.Sprintf("%v", id1)), 535 network.Id(fmt.Sprintf("%v", id2)), 536 } 537 538 subnetsInfo, err := suite.makeEnviron().Subnets(instance.UnknownId, subnetIDs) 539 c.Assert(err, jc.ErrorIsNil) 540 expectedInfo := []network.SubnetInfo{ 541 createSubnetInfo(id1, 2, 1), 542 createSubnetInfo(id2, 3, 2), 543 } 544 c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo) 545 } 546 547 func (suite *environSuite) TestSubnetsNoInstanceIdNoSubnetIds(c *gc.C) { 548 suite.createTwoSpaces() 549 id1 := suite.addSubnet(c, 1, 1, "node1") 550 id2 := suite.addSubnet(c, 2, 2, "node2") 551 env := suite.makeEnviron() 552 553 subnetsInfo, err := suite.makeEnviron().Subnets(instance.UnknownId, []network.Id{}) 554 c.Assert(err, jc.ErrorIsNil) 555 expectedInfo := []network.SubnetInfo{ 556 createSubnetInfo(id1, 2, 1), 557 createSubnetInfo(id2, 3, 2), 558 } 559 c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo) 560 561 subnetsInfo, err = env.Subnets(instance.UnknownId, nil) 562 c.Assert(err, jc.ErrorIsNil) 563 c.Assert(subnetsInfo, jc.DeepEquals, expectedInfo) 564 } 565 566 func (suite *environSuite) TestSpaces(c *gc.C) { 567 suite.createTwoSpaces() 568 suite.testMAASObject.TestServer.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: "space-3"})) 569 for _, i := range []uint{1, 2, 3} { 570 suite.addSubnet(c, i, i, "node1") 571 suite.addSubnet(c, i+5, i, "node1") 572 } 573 574 spaces, err := suite.makeEnviron().Spaces() 575 c.Assert(err, jc.ErrorIsNil) 576 expectedSpaces := []network.SpaceInfo{{ 577 Name: "space-1", 578 ProviderId: "2", 579 Subnets: []network.SubnetInfo{ 580 createSubnetInfo(1, 2, 1), 581 createSubnetInfo(2, 2, 6), 582 }, 583 }, { 584 Name: "space-2", 585 ProviderId: "3", 586 Subnets: []network.SubnetInfo{ 587 createSubnetInfo(3, 3, 2), 588 createSubnetInfo(4, 3, 7), 589 }, 590 }, { 591 Name: "space-3", 592 ProviderId: "4", 593 Subnets: []network.SubnetInfo{ 594 createSubnetInfo(5, 4, 3), 595 createSubnetInfo(6, 4, 8), 596 }, 597 }} 598 c.Assert(spaces, jc.DeepEquals, expectedSpaces) 599 } 600 601 func (suite *environSuite) assertSpaces(c *gc.C, numberOfSubnets int, filters []network.Id) { 602 server := suite.testMAASObject.TestServer 603 testInstance := suite.getInstance("node1") 604 systemID := "node1" 605 for i := 1; i <= numberOfSubnets; i++ { 606 server.NewSpace(spaceJSON(gomaasapi.CreateSpace{Name: fmt.Sprintf("space-%d", i)})) 607 // Put most, but not all, of the subnets on node1. 608 if i == 2 { 609 systemID = "node2" 610 } else { 611 systemID = "node1" 612 } 613 suite.addSubnet(c, uint(i), uint(i), systemID) 614 } 615 616 subnets, err := suite.makeEnviron().Subnets(testInstance.Id(), filters) 617 c.Assert(err, jc.ErrorIsNil) 618 expectedSubnets := []network.SubnetInfo{ 619 createSubnetInfo(1, 2, 1), 620 createSubnetInfo(3, 4, 3), 621 } 622 c.Assert(subnets, jc.DeepEquals, expectedSubnets) 623 624 } 625 626 func (suite *environSuite) TestSubnetsAllSubnets(c *gc.C) { 627 suite.assertSpaces(c, 3, []network.Id{}) 628 } 629 630 func (suite *environSuite) TestSubnetsFilteredIds(c *gc.C) { 631 suite.assertSpaces(c, 4, []network.Id{"1", "3"}) 632 } 633 634 func (suite *environSuite) TestSubnetsMissingSubnet(c *gc.C) { 635 testInstance := suite.getInstance("node1") 636 for _, i := range []uint{1, 2} { 637 suite.addSubnet(c, i, i, "node1") 638 } 639 640 _, err := suite.makeEnviron().Subnets(testInstance.Id(), []network.Id{"1", "3", "6"}) 641 errorRe := regexp.MustCompile("failed to find the following subnets: (\\d), (\\d)$") 642 errorText := err.Error() 643 c.Assert(errorRe.MatchString(errorText), jc.IsTrue) 644 matches := errorRe.FindStringSubmatch(errorText) 645 c.Assert(matches, gc.HasLen, 3) 646 c.Assert(matches[1:], jc.SameContents, []string{"3", "6"}) 647 } 648 649 func (s *environSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 650 s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1") 651 env := s.makeEnviron() 652 placement := "zone=zone1" 653 err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) 654 c.Assert(err, jc.ErrorIsNil) 655 } 656 657 func (s *environSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 658 s.testMAASObject.TestServer.AddZone("zone1", "the grass is greener in zone1") 659 env := s.makeEnviron() 660 placement := "zone=zone2" 661 err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) 662 c.Assert(err, gc.ErrorMatches, `invalid availability zone "zone2"`) 663 } 664 665 func (s *environSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) { 666 env := s.makeEnviron() 667 placement := "zone=test-unknown" 668 err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) 669 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 670 } 671 672 func (s *environSuite) TestPrecheckInvalidPlacement(c *gc.C) { 673 env := s.makeEnviron() 674 err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, "notzone=anything") 675 c.Assert(err, gc.ErrorMatches, "unknown placement directive: notzone=anything") 676 } 677 678 func (s *environSuite) TestPrecheckNodePlacement(c *gc.C) { 679 env := s.makeEnviron() 680 err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, "assumed_node_name") 681 c.Assert(err, jc.ErrorIsNil) 682 } 683 684 func (s *environSuite) TestStartInstanceAvailZone(c *gc.C) { 685 // Add a node for the started instance. 686 s.newNode(c, "thenode1", "host1", map[string]interface{}{"zone": "test-available"}) 687 s.addSubnet(c, 1, 1, "thenode1") 688 s.testMAASObject.TestServer.AddZone("test-available", "description") 689 inst, err := s.testStartInstanceAvailZone(c, "test-available") 690 c.Assert(err, jc.ErrorIsNil) 691 zone, err := inst.(maasInstance).zone() 692 c.Assert(err, jc.ErrorIsNil) 693 c.Assert(zone, gc.Equals, "test-available") 694 } 695 696 func (s *environSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 697 s.testMAASObject.TestServer.AddZone("test-available", "description") 698 _, err := s.testStartInstanceAvailZone(c, "test-unknown") 699 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 700 } 701 702 func (s *environSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { 703 env := s.bootstrap(c) 704 params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID, Placement: "zone=" + zone} 705 result, err := testing.StartInstanceWithParams(env, "1", params) 706 if err != nil { 707 return nil, err 708 } 709 return result.Instance, nil 710 } 711 712 func (s *environSuite) TestStartInstanceUnmetConstraints(c *gc.C) { 713 env := s.bootstrap(c) 714 s.newNode(c, "thenode1", "host1", nil) 715 s.addSubnet(c, 1, 1, "thenode1") 716 params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID, Constraints: constraints.MustParse("mem=8G")} 717 _, err := testing.StartInstanceWithParams(env, "1", params) 718 c.Assert(err, gc.ErrorMatches, "cannot run instances:.* 409.*") 719 } 720 721 func (s *environSuite) TestStartInstanceConstraints(c *gc.C) { 722 env := s.bootstrap(c) 723 s.newNode(c, "thenode1", "host1", nil) 724 s.addSubnet(c, 1, 1, "thenode1") 725 s.newNode(c, "thenode2", "host2", map[string]interface{}{"memory": 8192}) 726 s.addSubnet(c, 2, 2, "thenode2") 727 params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID, Constraints: constraints.MustParse("mem=8G")} 728 result, err := testing.StartInstanceWithParams(env, "1", params) 729 c.Assert(err, jc.ErrorIsNil) 730 c.Assert(*result.Hardware.Mem, gc.Equals, uint64(8192)) 731 } 732 733 var nodeStorageAttrs = []map[string]interface{}{ 734 { 735 "name": "sdb", 736 "id": 1, 737 "id_path": "/dev/disk/by-id/id_for_sda", 738 "path": "/dev/sdb", 739 "model": "Samsung_SSD_850_EVO_250GB", 740 "block_size": 4096, 741 "serial": "S21NNSAFC38075L", 742 "size": uint64(250059350016), 743 }, 744 { 745 "name": "sda", 746 "id": 2, 747 "path": "/dev/sda", 748 "model": "Samsung_SSD_850_EVO_250GB", 749 "block_size": 4096, 750 "serial": "XXXX", 751 "size": uint64(250059350016), 752 }, 753 { 754 "name": "sdc", 755 "id": 3, 756 "path": "/dev/sdc", 757 "model": "Samsung_SSD_850_EVO_250GB", 758 "block_size": 4096, 759 "serial": "YYYYYYY", 760 "size": uint64(250059350016), 761 }, 762 } 763 764 var storageConstraintAttrs = map[string]interface{}{ 765 "1": "1", 766 "2": "root", 767 "3": "3", 768 } 769 770 func (s *environSuite) TestStartInstanceStorage(c *gc.C) { 771 env := s.bootstrap(c) 772 s.newNode(c, "thenode1", "host1", map[string]interface{}{ 773 "memory": 8192, 774 "physicalblockdevice_set": nodeStorageAttrs, 775 "constraint_map": storageConstraintAttrs, 776 }) 777 s.addSubnet(c, 1, 1, "thenode1") 778 params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID, 779 Volumes: []storage.VolumeParams{ 780 {Tag: names.NewVolumeTag("1"), Size: 2000000}, 781 {Tag: names.NewVolumeTag("3"), Size: 2000000}, 782 }} 783 result, err := testing.StartInstanceWithParams(env, "1", params) 784 c.Assert(err, jc.ErrorIsNil) 785 c.Check(result.Volumes, jc.DeepEquals, []storage.Volume{ 786 { 787 names.NewVolumeTag("1"), 788 storage.VolumeInfo{ 789 Size: 238475, 790 VolumeId: "volume-1", 791 HardwareId: "id_for_sda", 792 }, 793 }, 794 { 795 names.NewVolumeTag("3"), 796 storage.VolumeInfo{ 797 Size: 238475, 798 VolumeId: "volume-3", 799 HardwareId: "", 800 }, 801 }, 802 }) 803 c.Assert(result.VolumeAttachments, jc.DeepEquals, []storage.VolumeAttachment{ 804 { 805 names.NewVolumeTag("1"), 806 names.NewMachineTag("1"), 807 storage.VolumeAttachmentInfo{ 808 DeviceName: "", 809 ReadOnly: false, 810 }, 811 }, 812 { 813 names.NewVolumeTag("3"), 814 names.NewMachineTag("1"), 815 storage.VolumeAttachmentInfo{ 816 DeviceName: "sdc", 817 ReadOnly: false, 818 }, 819 }, 820 }) 821 } 822 823 func (s *environSuite) TestStartInstanceUnsupportedStorage(c *gc.C) { 824 env := s.bootstrap(c) 825 s.newNode(c, "thenode1", "host1", map[string]interface{}{ 826 "memory": 8192, 827 }) 828 s.addSubnet(c, 1, 1, "thenode1") 829 params := environs.StartInstanceParams{ControllerUUID: s.controllerUUID, 830 Volumes: []storage.VolumeParams{ 831 {Tag: names.NewVolumeTag("1"), Size: 2000000}, 832 {Tag: names.NewVolumeTag("3"), Size: 2000000}, 833 }} 834 _, err := testing.StartInstanceWithParams(env, "1", params) 835 c.Assert(err, gc.ErrorMatches, "requested 2 storage volumes. 0 returned.") 836 operations := s.testMAASObject.TestServer.NodesOperations() 837 c.Check(operations, gc.DeepEquals, []string{"acquire", "acquire", "release"}) 838 c.Assert(s.testMAASObject.TestServer.OwnedNodes()["node0"], jc.IsTrue) 839 c.Assert(s.testMAASObject.TestServer.OwnedNodes()["thenode1"], jc.IsFalse) 840 } 841 842 func (s *environSuite) TestGetAvailabilityZones(c *gc.C) { 843 env := s.makeEnviron() 844 845 zones, err := env.AvailabilityZones() 846 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 847 c.Assert(zones, gc.IsNil) 848 849 s.testMAASObject.TestServer.AddZone("whatever", "andever") 850 zones, err = env.AvailabilityZones() 851 c.Assert(err, jc.ErrorIsNil) 852 c.Assert(zones, gc.HasLen, 1) 853 c.Assert(zones[0].Name(), gc.Equals, "whatever") 854 c.Assert(zones[0].Available(), jc.IsTrue) 855 856 // A successful result is cached, currently for the lifetime 857 // of the Environ. This will change if/when we have long-lived 858 // Environs to cut down repeated IaaS requests. 859 s.testMAASObject.TestServer.AddZone("somewhere", "outthere") 860 zones, err = env.AvailabilityZones() 861 c.Assert(err, jc.ErrorIsNil) 862 c.Assert(zones, gc.HasLen, 1) 863 c.Assert(zones[0].Name(), gc.Equals, "whatever") 864 } 865 866 type mockAvailabilityZoneAllocations struct { 867 group []instance.Id // input param 868 result []common.AvailabilityZoneInstances 869 err error 870 } 871 872 func (m *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations( 873 e common.ZonedEnviron, group []instance.Id, 874 ) ([]common.AvailabilityZoneInstances, error) { 875 m.group = group 876 return m.result, m.err 877 } 878 879 func (s *environSuite) newNode(c *gc.C, nodename, hostname string, attrs map[string]interface{}) { 880 allAttrs := map[string]interface{}{ 881 "system_id": nodename, 882 "hostname": hostname, 883 "architecture": fmt.Sprintf("%s/generic", arch.HostArch()), 884 "memory": 1024, 885 "cpu_count": 1, 886 "zone": map[string]interface{}{"name": "test_zone", "description": "description"}, 887 "interface_set": exampleParsedInterfaceSetJSON, 888 } 889 for k, v := range attrs { 890 allAttrs[k] = v 891 } 892 data, err := json.Marshal(allAttrs) 893 c.Assert(err, jc.ErrorIsNil) 894 s.testMAASObject.TestServer.NewNode(string(data)) 895 lshwXML, err := s.generateHWTemplate(map[string]ifaceInfo{"aa:bb:cc:dd:ee:f0": {0, "eth0", false}}) 896 c.Assert(err, jc.ErrorIsNil) 897 s.testMAASObject.TestServer.AddNodeDetails(nodename, lshwXML) 898 } 899 900 func (s *environSuite) bootstrap(c *gc.C) environs.Environ { 901 s.newNode(c, "node0", "bootstrap-host", nil) 902 s.addSubnet(c, 9, 9, "node0") 903 s.setupFakeTools(c) 904 env := s.makeEnviron() 905 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 906 ControllerConfig: coretesting.FakeControllerConfig(), 907 Placement: "bootstrap-host", 908 AdminSecret: testing.AdminSecret, 909 CAPrivateKey: coretesting.CAKey, 910 }) 911 c.Assert(err, jc.ErrorIsNil) 912 return env 913 } 914 915 func (s *environSuite) TestStartInstanceDistributionParams(c *gc.C) { 916 env := s.bootstrap(c) 917 var mock mockAvailabilityZoneAllocations 918 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 919 920 // no distribution group specified 921 s.newNode(c, "node1", "host1", nil) 922 s.addSubnet(c, 1, 1, "node1") 923 testing.AssertStartInstance(c, env, s.controllerUUID, "1") 924 c.Assert(mock.group, gc.HasLen, 0) 925 926 // distribution group specified: ensure it's passed through to AvailabilityZone. 927 s.newNode(c, "node2", "host2", nil) 928 s.addSubnet(c, 2, 2, "node2") 929 expectedInstances := []instance.Id{"i-0", "i-1"} 930 params := environs.StartInstanceParams{ 931 ControllerUUID: s.controllerUUID, 932 DistributionGroup: func() ([]instance.Id, error) { 933 return expectedInstances, nil 934 }, 935 } 936 _, err := testing.StartInstanceWithParams(env, "1", params) 937 c.Assert(err, jc.ErrorIsNil) 938 c.Assert(mock.group, gc.DeepEquals, expectedInstances) 939 } 940 941 func (s *environSuite) TestStartInstanceDistributionErrors(c *gc.C) { 942 env := s.bootstrap(c) 943 mock := mockAvailabilityZoneAllocations{ 944 err: errors.New("AvailabilityZoneAllocations failed"), 945 } 946 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 947 _, _, _, err := testing.StartInstance(env, s.controllerUUID, "1") 948 c.Assert(err, gc.ErrorMatches, "cannot get availability zone allocations: AvailabilityZoneAllocations failed") 949 950 mock.err = nil 951 dgErr := errors.New("DistributionGroup failed") 952 params := environs.StartInstanceParams{ 953 ControllerUUID: s.controllerUUID, 954 DistributionGroup: func() ([]instance.Id, error) { 955 return nil, dgErr 956 }, 957 } 958 _, err = testing.StartInstanceWithParams(env, "1", params) 959 c.Assert(err, gc.ErrorMatches, "cannot get distribution group: DistributionGroup failed") 960 } 961 962 func (s *environSuite) TestStartInstanceDistribution(c *gc.C) { 963 env := s.bootstrap(c) 964 s.testMAASObject.TestServer.AddZone("test-available", "description") 965 s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "test-available"}) 966 s.addSubnet(c, 1, 1, "node1") 967 inst, _ := testing.AssertStartInstance(c, env, s.controllerUUID, "1") 968 zone, err := inst.(*maas1Instance).zone() 969 c.Assert(err, jc.ErrorIsNil) 970 c.Assert(zone, gc.Equals, "test-available") 971 } 972 973 func (s *environSuite) TestStartInstanceDistributionFailover(c *gc.C) { 974 mock := mockAvailabilityZoneAllocations{ 975 result: []common.AvailabilityZoneInstances{{ 976 ZoneName: "zone1", 977 }, { 978 ZoneName: "zonelord", 979 }, { 980 ZoneName: "zone2", 981 }}, 982 } 983 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 984 s.testMAASObject.TestServer.AddZone("zone1", "description") 985 s.testMAASObject.TestServer.AddZone("zone2", "description") 986 s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"}) 987 s.addSubnet(c, 1, 1, "node2") 988 989 env := s.bootstrap(c) 990 inst, _ := testing.AssertStartInstance(c, env, s.controllerUUID, "1") 991 zone, err := inst.(maasInstance).zone() 992 c.Assert(err, jc.ErrorIsNil) 993 c.Assert(zone, gc.Equals, "zone2") 994 c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{ 995 // one acquire for the bootstrap, three for StartInstance (with zone failover) 996 "acquire", "acquire", "acquire", "acquire", 997 }) 998 c.Assert(s.testMAASObject.TestServer.NodesOperationRequestValues(), gc.DeepEquals, []url.Values{{ 999 "name": []string{"bootstrap-host"}, 1000 "agent_name": []string{env.Config().UUID()}, 1001 }, { 1002 "zone": []string{"zone1"}, 1003 "agent_name": []string{env.Config().UUID()}, 1004 }, { 1005 "zone": []string{"zonelord"}, 1006 "agent_name": []string{env.Config().UUID()}, 1007 }, { 1008 "zone": []string{"zone2"}, 1009 "agent_name": []string{env.Config().UUID()}, 1010 }}) 1011 } 1012 1013 func (s *environSuite) TestStartInstanceDistributionOneAssigned(c *gc.C) { 1014 mock := mockAvailabilityZoneAllocations{ 1015 result: []common.AvailabilityZoneInstances{{ 1016 ZoneName: "zone1", 1017 }, { 1018 ZoneName: "zone2", 1019 }}, 1020 } 1021 s.PatchValue(&availabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1022 s.testMAASObject.TestServer.AddZone("zone1", "description") 1023 s.testMAASObject.TestServer.AddZone("zone2", "description") 1024 s.newNode(c, "node1", "host1", map[string]interface{}{"zone": "zone1"}) 1025 s.addSubnet(c, 1, 1, "node1") 1026 s.newNode(c, "node2", "host2", map[string]interface{}{"zone": "zone2"}) 1027 s.addSubnet(c, 2, 2, "node2") 1028 1029 env := s.bootstrap(c) 1030 testing.AssertStartInstance(c, env, s.controllerUUID, "1") 1031 c.Assert(s.testMAASObject.TestServer.NodesOperations(), gc.DeepEquals, []string{ 1032 // one acquire for the bootstrap, one for StartInstance. 1033 "acquire", "acquire", 1034 }) 1035 } 1036 1037 func (s *environSuite) TestReleaseContainerAddresses(c *gc.C) { 1038 s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{ 1039 SystemId: "device1", 1040 MACAddresses: []string{"mac1"}, 1041 }) 1042 s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{ 1043 SystemId: "device2", 1044 MACAddresses: []string{"mac2"}, 1045 }) 1046 s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{ 1047 SystemId: "device3", 1048 MACAddresses: []string{"mac3"}, 1049 }) 1050 1051 env := s.makeEnviron() 1052 err := env.ReleaseContainerAddresses([]network.ProviderInterfaceInfo{ 1053 {MACAddress: "mac1"}, 1054 {MACAddress: "mac3"}, 1055 {MACAddress: "mac4"}, 1056 }) 1057 c.Assert(err, jc.ErrorIsNil) 1058 1059 var systemIds []string 1060 for systemId, _ := range s.testMAASObject.TestServer.Devices() { 1061 systemIds = append(systemIds, systemId) 1062 } 1063 c.Assert(systemIds, gc.DeepEquals, []string{"device2"}) 1064 } 1065 1066 func (s *environSuite) TestReleaseContainerAddresses_HandlesDupes(c *gc.C) { 1067 s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{ 1068 SystemId: "device1", 1069 MACAddresses: []string{"mac1", "mac2"}, 1070 }) 1071 s.testMAASObject.TestServer.AddDevice(&gomaasapi.TestDevice{ 1072 SystemId: "device3", 1073 MACAddresses: []string{"mac3"}, 1074 }) 1075 1076 env := s.makeEnviron() 1077 err := env.ReleaseContainerAddresses([]network.ProviderInterfaceInfo{ 1078 {MACAddress: "mac1"}, 1079 {MACAddress: "mac2"}, 1080 }) 1081 c.Assert(err, jc.ErrorIsNil) 1082 1083 var systemIds []string 1084 for systemId, _ := range s.testMAASObject.TestServer.Devices() { 1085 systemIds = append(systemIds, systemId) 1086 } 1087 c.Assert(systemIds, gc.DeepEquals, []string{"device3"}) 1088 }