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