github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/ec2/local_test.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package ec2_test 5 6 import ( 7 "fmt" 8 "net" 9 "regexp" 10 "sort" 11 "strconv" 12 "strings" 13 14 "github.com/juju/errors" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/utils" 17 "github.com/juju/utils/arch" 18 "github.com/juju/utils/series" 19 "github.com/juju/utils/set" 20 "github.com/juju/utils/ssh" 21 "gopkg.in/amz.v3/aws" 22 amzec2 "gopkg.in/amz.v3/ec2" 23 "gopkg.in/amz.v3/ec2/ec2test" 24 "gopkg.in/amz.v3/s3/s3test" 25 gc "gopkg.in/check.v1" 26 goyaml "gopkg.in/yaml.v2" 27 28 "github.com/juju/juju/cloud" 29 "github.com/juju/juju/constraints" 30 "github.com/juju/juju/environs" 31 "github.com/juju/juju/environs/bootstrap" 32 "github.com/juju/juju/environs/imagemetadata" 33 imagetesting "github.com/juju/juju/environs/imagemetadata/testing" 34 "github.com/juju/juju/environs/jujutest" 35 "github.com/juju/juju/environs/simplestreams" 36 sstesting "github.com/juju/juju/environs/simplestreams/testing" 37 envtesting "github.com/juju/juju/environs/testing" 38 "github.com/juju/juju/environs/tools" 39 "github.com/juju/juju/feature" 40 "github.com/juju/juju/instance" 41 "github.com/juju/juju/juju" 42 "github.com/juju/juju/juju/testing" 43 "github.com/juju/juju/jujuclient/jujuclienttesting" 44 "github.com/juju/juju/network" 45 "github.com/juju/juju/provider/common" 46 "github.com/juju/juju/provider/ec2" 47 coretesting "github.com/juju/juju/testing" 48 jujuversion "github.com/juju/juju/version" 49 ) 50 51 type ProviderSuite struct { 52 coretesting.BaseSuite 53 } 54 55 var _ = gc.Suite(&ProviderSuite{}) 56 57 var localConfigAttrs = coretesting.FakeConfig().Merge(coretesting.Attrs{ 58 "name": "sample", 59 "type": "ec2", 60 "agent-version": coretesting.FakeVersionNumber.String(), 61 }) 62 63 func registerLocalTests() { 64 // N.B. Make sure the region we use here 65 // has entries in the images/query txt files. 66 aws.Regions["test"] = aws.Region{ 67 Name: "test", 68 } 69 70 gc.Suite(&localServerSuite{}) 71 gc.Suite(&localLiveSuite{}) 72 gc.Suite(&localNonUSEastSuite{}) 73 } 74 75 // localLiveSuite runs tests from LiveTests using a fake 76 // EC2 server that runs within the test process itself. 77 type localLiveSuite struct { 78 LiveTests 79 srv localServer 80 restoreEC2Patching func() 81 } 82 83 func (t *localLiveSuite) SetUpSuite(c *gc.C) { 84 t.LiveTests.SetUpSuite(c) 85 t.Credential = cloud.NewCredential( 86 cloud.AccessKeyAuthType, 87 map[string]string{ 88 "access-key": "x", 89 "secret-key": "x", 90 }, 91 ) 92 t.CloudRegion = "test" 93 94 // Upload arches that ec2 supports; add to this 95 // as ec2 coverage expands. 96 t.UploadArches = []string{arch.AMD64, arch.I386} 97 t.TestConfig = localConfigAttrs 98 t.restoreEC2Patching = patchEC2ForTesting(c) 99 imagetesting.PatchOfficialDataSources(&t.BaseSuite.CleanupSuite, "test:") 100 t.BaseSuite.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) 101 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc) 102 t.srv.createRootDisks = true 103 t.srv.startServer(c) 104 } 105 106 func (t *localLiveSuite) TearDownSuite(c *gc.C) { 107 t.LiveTests.TearDownSuite(c) 108 t.srv.stopServer(c) 109 t.restoreEC2Patching() 110 } 111 112 // localServer represents a fake EC2 server running within 113 // the test process itself. 114 type localServer struct { 115 // createRootDisks is used to decide whether or not 116 // the ec2test server will create root disks for 117 // instances. 118 createRootDisks bool 119 120 ec2srv *ec2test.Server 121 s3srv *s3test.Server 122 config *s3test.Config 123 } 124 125 func (srv *localServer) startServer(c *gc.C) { 126 var err error 127 srv.ec2srv, err = ec2test.NewServer() 128 if err != nil { 129 c.Fatalf("cannot start ec2 test server: %v", err) 130 } 131 srv.ec2srv.SetCreateRootDisks(srv.createRootDisks) 132 srv.s3srv, err = s3test.NewServer(srv.config) 133 if err != nil { 134 c.Fatalf("cannot start s3 test server: %v", err) 135 } 136 aws.Regions["test"] = aws.Region{ 137 Name: "test", 138 EC2Endpoint: srv.ec2srv.URL(), 139 S3Endpoint: srv.s3srv.URL(), 140 S3LocationConstraint: true, 141 } 142 srv.addSpice(c) 143 144 zones := make([]amzec2.AvailabilityZoneInfo, 3) 145 zones[0].Region = "test" 146 zones[0].Name = "test-available" 147 zones[0].State = "available" 148 zones[1].Region = "test" 149 zones[1].Name = "test-impaired" 150 zones[1].State = "impaired" 151 zones[2].Region = "test" 152 zones[2].Name = "test-unavailable" 153 zones[2].State = "unavailable" 154 srv.ec2srv.SetAvailabilityZones(zones) 155 srv.ec2srv.SetInitialInstanceState(ec2test.Pending) 156 } 157 158 // addSpice adds some "spice" to the local server 159 // by adding state that may cause tests to fail. 160 func (srv *localServer) addSpice(c *gc.C) { 161 states := []amzec2.InstanceState{ 162 ec2test.ShuttingDown, 163 ec2test.Terminated, 164 ec2test.Stopped, 165 } 166 for _, state := range states { 167 srv.ec2srv.NewInstances(1, "m1.small", "ami-a7f539ce", state, nil) 168 } 169 } 170 171 func (srv *localServer) stopServer(c *gc.C) { 172 srv.ec2srv.Quit() 173 srv.s3srv.Quit() 174 // Clear out the region because the server address is 175 // no longer valid. 176 delete(aws.Regions, "test") 177 } 178 179 // localServerSuite contains tests that run against a fake EC2 server 180 // running within the test process itself. These tests can test things that 181 // would be unreasonably slow or expensive to test on a live Amazon server. 182 // It starts a new local ec2test server for each test. The server is 183 // accessed by using the "test" region, which is changed to point to the 184 // network address of the local server. 185 type localServerSuite struct { 186 coretesting.BaseSuite 187 jujutest.Tests 188 srv localServer 189 restoreEC2Patching func() 190 } 191 192 func (t *localServerSuite) SetUpSuite(c *gc.C) { 193 t.BaseSuite.SetUpSuite(c) 194 t.Credential = cloud.NewCredential( 195 cloud.AccessKeyAuthType, 196 map[string]string{ 197 "access-key": "x", 198 "secret-key": "x", 199 }, 200 ) 201 t.CloudRegion = "test" 202 203 // Upload arches that ec2 supports; add to this 204 // as ec2 coverage expands. 205 t.UploadArches = []string{arch.AMD64, arch.I386} 206 t.TestConfig = localConfigAttrs 207 t.restoreEC2Patching = patchEC2ForTesting(c) 208 imagetesting.PatchOfficialDataSources(&t.BaseSuite.CleanupSuite, "test:") 209 t.BaseSuite.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) 210 t.BaseSuite.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey) 211 t.BaseSuite.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 212 t.BaseSuite.PatchValue(&arch.HostArch, func() string { return arch.AMD64 }) 213 t.BaseSuite.PatchValue(&series.HostSeries, func() string { return coretesting.FakeDefaultSeries }) 214 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc) 215 t.srv.createRootDisks = true 216 t.srv.startServer(c) 217 // TODO(jam) I don't understand why we shouldn't do this. 218 // t.Tests embeds the sstesting.TestDataSuite, but if we call this 219 // SetUpSuite, then all of the tests fail because they go to access 220 // "test:/streams/..." and it isn't found 221 // t.Tests.SetUpSuite(c) 222 } 223 224 func (t *localServerSuite) TearDownSuite(c *gc.C) { 225 t.restoreEC2Patching() 226 t.Tests.TearDownSuite(c) 227 t.BaseSuite.TearDownSuite(c) 228 } 229 230 func (t *localServerSuite) SetUpTest(c *gc.C) { 231 t.BaseSuite.SetUpTest(c) 232 t.SetFeatureFlags(feature.AddressAllocation) 233 t.srv.startServer(c) 234 t.Tests.SetUpTest(c) 235 } 236 237 func (t *localServerSuite) TearDownTest(c *gc.C) { 238 t.Tests.TearDownTest(c) 239 t.srv.stopServer(c) 240 t.BaseSuite.TearDownTest(c) 241 } 242 243 func (t *localServerSuite) prepareEnviron(c *gc.C) environs.NetworkingEnviron { 244 env := t.Prepare(c) 245 netenv, supported := environs.SupportsNetworking(env) 246 c.Assert(supported, jc.IsTrue) 247 return netenv 248 } 249 250 func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { 251 env := t.Prepare(c) 252 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 253 c.Assert(err, jc.ErrorIsNil) 254 255 // check that ControllerInstances returns the id of the bootstrap machine. 256 instanceIds, err := env.ControllerInstances() 257 c.Assert(err, jc.ErrorIsNil) 258 c.Assert(instanceIds, gc.HasLen, 1) 259 260 insts, err := env.AllInstances() 261 c.Assert(err, jc.ErrorIsNil) 262 c.Assert(insts, gc.HasLen, 1) 263 c.Check(insts[0].Id(), gc.Equals, instanceIds[0]) 264 265 // check that the user data is configured to start zookeeper 266 // and the machine and provisioning agents. 267 // check that the user data is configured to only configure 268 // authorized SSH keys and set the log output; everything 269 // else happens after the machine is brought up. 270 inst := t.srv.ec2srv.Instance(string(insts[0].Id())) 271 c.Assert(inst, gc.NotNil) 272 addresses, err := insts[0].Addresses() 273 c.Assert(err, jc.ErrorIsNil) 274 c.Assert(addresses, gc.Not(gc.HasLen), 0) 275 userData, err := utils.Gunzip(inst.UserData) 276 c.Assert(err, jc.ErrorIsNil) 277 c.Assert(string(userData), jc.YAMLEquals, map[interface{}]interface{}{ 278 "output": map[interface{}]interface{}{ 279 "all": "| tee -a /var/log/cloud-init-output.log", 280 }, 281 "users": []interface{}{ 282 map[interface{}]interface{}{ 283 "name": "ubuntu", 284 "lock_passwd": true, 285 "groups": []interface{}{"adm", "audio", 286 "cdrom", "dialout", "dip", "floppy", 287 "netdev", "plugdev", "sudo", "video"}, 288 "shell": "/bin/bash", 289 "sudo": []interface{}{"ALL=(ALL) NOPASSWD:ALL"}, 290 "ssh-authorized-keys": splitAuthKeys(env.Config().AuthorizedKeys()), 291 }, 292 }, 293 "runcmd": []interface{}{ 294 "set -xe", 295 "install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown.conf'", 296 "printf '%s\\n' '\nauthor \"Juju Team <juju@lists.ubuntu.com>\"\ndescription \"Stop all network interfaces on shutdown\"\nstart on runlevel [016]\ntask\nconsole output\n\nexec /sbin/ifdown -a -v --force\n' > '/etc/init/juju-clean-shutdown.conf'", 297 "install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'", 298 "printf '%s\\n' 'user-admin:bootstrap' > '/var/lib/juju/nonce.txt'", 299 }, 300 }) 301 302 // check that a new instance will be started with a machine agent 303 inst1, hc := testing.AssertStartInstance(c, env, "1") 304 c.Check(*hc.Arch, gc.Equals, "amd64") 305 c.Check(*hc.Mem, gc.Equals, uint64(3840)) 306 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 307 c.Assert(*hc.CpuPower, gc.Equals, uint64(300)) 308 inst = t.srv.ec2srv.Instance(string(inst1.Id())) 309 c.Assert(inst, gc.NotNil) 310 userData, err = utils.Gunzip(inst.UserData) 311 c.Assert(err, jc.ErrorIsNil) 312 c.Logf("second instance: UserData: %q", userData) 313 var userDataMap map[interface{}]interface{} 314 err = goyaml.Unmarshal(userData, &userDataMap) 315 c.Assert(err, jc.ErrorIsNil) 316 CheckPackage(c, userDataMap, "curl", true) 317 CheckPackage(c, userDataMap, "mongodb-server", false) 318 CheckScripts(c, userDataMap, "jujud bootstrap-state", false) 319 CheckScripts(c, userDataMap, "/var/lib/juju/agents/machine-1/agent.conf", true) 320 // TODO check for provisioning agent 321 322 err = env.Destroy() 323 c.Assert(err, jc.ErrorIsNil) 324 325 _, err = env.ControllerInstances() 326 c.Assert(err, gc.Equals, environs.ErrNotBootstrapped) 327 } 328 329 func (t *localServerSuite) TestTerminateInstancesIgnoresNotFound(c *gc.C) { 330 env := t.Prepare(c) 331 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 332 c.Assert(err, jc.ErrorIsNil) 333 334 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc) 335 insts, err := env.AllInstances() 336 c.Assert(err, jc.ErrorIsNil) 337 idsToStop := make([]instance.Id, len(insts)+1) 338 for i, one := range insts { 339 idsToStop[i] = one.Id() 340 } 341 idsToStop[len(insts)] = instance.Id("i-am-not-found") 342 343 err = env.StopInstances(idsToStop...) 344 // NotFound should be ignored 345 c.Assert(err, jc.ErrorIsNil) 346 } 347 348 func (t *localServerSuite) TestDestroyErr(c *gc.C) { 349 env := t.Prepare(c) 350 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 351 c.Assert(err, jc.ErrorIsNil) 352 353 msg := "terminate instances error" 354 t.BaseSuite.PatchValue(ec2.TerminateInstancesById, func(ec2inst *amzec2.EC2, ids ...instance.Id) (*amzec2.TerminateInstancesResp, error) { 355 return nil, errors.New(msg) 356 }) 357 358 err = env.Destroy() 359 c.Assert(errors.Cause(err).Error(), jc.Contains, msg) 360 } 361 362 func (t *localServerSuite) TestGetTerminatedInstances(c *gc.C) { 363 env := t.Prepare(c) 364 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 365 c.Assert(err, jc.ErrorIsNil) 366 367 // create another instance to terminate 368 inst1, _ := testing.AssertStartInstance(c, env, "1") 369 inst := t.srv.ec2srv.Instance(string(inst1.Id())) 370 c.Assert(inst, gc.NotNil) 371 t.BaseSuite.PatchValue(ec2.TerminateInstancesById, func(ec2inst *amzec2.EC2, ids ...instance.Id) (*amzec2.TerminateInstancesResp, error) { 372 // Terminate the one destined for termination and 373 // err out to ensure that one instance will be terminated, the other - not. 374 _, err = ec2inst.TerminateInstances([]string{string(inst1.Id())}) 375 c.Assert(err, jc.ErrorIsNil) 376 return nil, errors.New("terminate instances error") 377 }) 378 err = env.Destroy() 379 c.Assert(err, gc.NotNil) 380 381 terminated, err := ec2.TerminatedInstances(env) 382 c.Assert(err, jc.ErrorIsNil) 383 c.Assert(terminated, gc.HasLen, 1) 384 c.Assert(terminated[0].Id(), jc.DeepEquals, inst1.Id()) 385 } 386 387 func (t *localServerSuite) TestInstanceSecurityGroupsWitheInstanceStatusFilter(c *gc.C) { 388 env := t.Prepare(c) 389 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 390 c.Assert(err, jc.ErrorIsNil) 391 392 insts, err := env.AllInstances() 393 c.Assert(err, jc.ErrorIsNil) 394 ids := make([]instance.Id, len(insts)) 395 for i, one := range insts { 396 ids[i] = one.Id() 397 } 398 399 groupsNoInstanceFilter, err := ec2.InstanceSecurityGroups(env, ids) 400 c.Assert(err, jc.ErrorIsNil) 401 // get all security groups for test instances 402 c.Assert(groupsNoInstanceFilter, gc.HasLen, 2) 403 404 groupsFilteredForTerminatedInstances, err := ec2.InstanceSecurityGroups(env, ids, "shutting-down", "terminated") 405 c.Assert(err, jc.ErrorIsNil) 406 // get all security groups for terminated test instances 407 c.Assert(groupsFilteredForTerminatedInstances, gc.HasLen, 0) 408 } 409 410 func (t *localServerSuite) TestDestroySecurityGroupInsistentlyError(c *gc.C) { 411 env := t.Prepare(c) 412 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 413 c.Assert(err, jc.ErrorIsNil) 414 415 called := false 416 msg := "destroy security group error" 417 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, func(inst ec2.SecurityGroupCleaner, group amzec2.SecurityGroup) error { 418 called = true 419 return errors.New(msg) 420 }) 421 422 err = env.Destroy() 423 c.Assert(err, jc.ErrorIsNil) 424 c.Assert(called, jc.IsTrue) 425 c.Assert(c.GetTestLog(), jc.Contains, "WARNING juju.provider.ec2 provider failure: destroy security group error") 426 } 427 428 // splitAuthKeys splits the given authorized keys 429 // into the form expected to be found in the 430 // user data. 431 func splitAuthKeys(keys string) []interface{} { 432 slines := strings.FieldsFunc(keys, func(r rune) bool { 433 return r == '\n' 434 }) 435 var lines []interface{} 436 for _, line := range slines { 437 lines = append(lines, ssh.EnsureJujuComment(strings.TrimSpace(line))) 438 } 439 return lines 440 } 441 442 func (t *localServerSuite) TestInstanceStatus(c *gc.C) { 443 env := t.Prepare(c) 444 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 445 c.Assert(err, jc.ErrorIsNil) 446 t.srv.ec2srv.SetInitialInstanceState(ec2test.Terminated) 447 inst, _ := testing.AssertStartInstance(c, env, "1") 448 c.Assert(err, jc.ErrorIsNil) 449 c.Assert(inst.Status().Message, gc.Equals, "terminated") 450 } 451 452 func (t *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { 453 env := t.Prepare(c) 454 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 455 c.Assert(err, jc.ErrorIsNil) 456 _, hc := testing.AssertStartInstance(c, env, "1") 457 c.Check(*hc.Arch, gc.Equals, "amd64") 458 c.Check(*hc.Mem, gc.Equals, uint64(3840)) 459 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 460 c.Assert(*hc.CpuPower, gc.Equals, uint64(300)) 461 } 462 463 func (t *localServerSuite) TestStartInstanceAvailZone(c *gc.C) { 464 inst, err := t.testStartInstanceAvailZone(c, "test-available") 465 c.Assert(err, jc.ErrorIsNil) 466 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available") 467 } 468 469 func (t *localServerSuite) TestStartInstanceAvailZoneImpaired(c *gc.C) { 470 _, err := t.testStartInstanceAvailZone(c, "test-impaired") 471 c.Assert(err, gc.ErrorMatches, `availability zone "test-impaired" is impaired`) 472 } 473 474 func (t *localServerSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 475 _, err := t.testStartInstanceAvailZone(c, "test-unknown") 476 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 477 } 478 479 func (t *localServerSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { 480 env := t.Prepare(c) 481 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 482 c.Assert(err, jc.ErrorIsNil) 483 484 params := environs.StartInstanceParams{Placement: "zone=" + zone} 485 result, err := testing.StartInstanceWithParams(env, "1", params) 486 if err != nil { 487 return nil, err 488 } 489 return result.Instance, nil 490 } 491 492 func (t *localServerSuite) TestGetAvailabilityZones(c *gc.C) { 493 var resultZones []amzec2.AvailabilityZoneInfo 494 var resultErr error 495 t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) { 496 resp := &amzec2.AvailabilityZonesResp{ 497 Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...), 498 } 499 return resp, resultErr 500 }) 501 env := t.Prepare(c).(common.ZonedEnviron) 502 503 resultErr = fmt.Errorf("failed to get availability zones") 504 zones, err := env.AvailabilityZones() 505 c.Assert(err, gc.Equals, resultErr) 506 c.Assert(zones, gc.IsNil) 507 508 resultErr = nil 509 resultZones = make([]amzec2.AvailabilityZoneInfo, 1) 510 resultZones[0].Name = "whatever" 511 zones, err = env.AvailabilityZones() 512 c.Assert(err, jc.ErrorIsNil) 513 c.Assert(zones, gc.HasLen, 1) 514 c.Assert(zones[0].Name(), gc.Equals, "whatever") 515 516 // A successful result is cached, currently for the lifetime 517 // of the Environ. This will change if/when we have long-lived 518 // Environs to cut down repeated IaaS requests. 519 resultErr = fmt.Errorf("failed to get availability zones") 520 resultZones[0].Name = "andever" 521 zones, err = env.AvailabilityZones() 522 c.Assert(err, jc.ErrorIsNil) 523 c.Assert(zones, gc.HasLen, 1) 524 c.Assert(zones[0].Name(), gc.Equals, "whatever") 525 } 526 527 func (t *localServerSuite) TestGetAvailabilityZonesCommon(c *gc.C) { 528 var resultZones []amzec2.AvailabilityZoneInfo 529 t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) { 530 resp := &amzec2.AvailabilityZonesResp{ 531 Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...), 532 } 533 return resp, nil 534 }) 535 env := t.Prepare(c).(common.ZonedEnviron) 536 resultZones = make([]amzec2.AvailabilityZoneInfo, 2) 537 resultZones[0].Name = "az1" 538 resultZones[1].Name = "az2" 539 resultZones[0].State = "available" 540 resultZones[1].State = "impaired" 541 zones, err := env.AvailabilityZones() 542 c.Assert(err, jc.ErrorIsNil) 543 c.Assert(zones, gc.HasLen, 2) 544 c.Assert(zones[0].Name(), gc.Equals, resultZones[0].Name) 545 c.Assert(zones[1].Name(), gc.Equals, resultZones[1].Name) 546 c.Assert(zones[0].Available(), jc.IsTrue) 547 c.Assert(zones[1].Available(), jc.IsFalse) 548 } 549 550 type mockAvailabilityZoneAllocations struct { 551 group []instance.Id // input param 552 result []common.AvailabilityZoneInstances 553 err error 554 } 555 556 func (t *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations( 557 e common.ZonedEnviron, group []instance.Id, 558 ) ([]common.AvailabilityZoneInstances, error) { 559 t.group = group 560 return t.result, t.err 561 } 562 563 func (t *localServerSuite) TestStartInstanceDistributionParams(c *gc.C) { 564 env := t.Prepare(c) 565 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 566 c.Assert(err, jc.ErrorIsNil) 567 568 mock := mockAvailabilityZoneAllocations{ 569 result: []common.AvailabilityZoneInstances{{ZoneName: "az1"}}, 570 } 571 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 572 573 // no distribution group specified 574 testing.AssertStartInstance(c, env, "1") 575 c.Assert(mock.group, gc.HasLen, 0) 576 577 // distribution group specified: ensure it's passed through to AvailabilityZone. 578 expectedInstances := []instance.Id{"i-0", "i-1"} 579 params := environs.StartInstanceParams{ 580 DistributionGroup: func() ([]instance.Id, error) { 581 return expectedInstances, nil 582 }, 583 } 584 _, err = testing.StartInstanceWithParams(env, "1", params) 585 c.Assert(err, jc.ErrorIsNil) 586 c.Assert(mock.group, gc.DeepEquals, expectedInstances) 587 } 588 589 func (t *localServerSuite) TestStartInstanceDistributionErrors(c *gc.C) { 590 env := t.Prepare(c) 591 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 592 c.Assert(err, jc.ErrorIsNil) 593 594 mock := mockAvailabilityZoneAllocations{ 595 err: fmt.Errorf("AvailabilityZoneAllocations failed"), 596 } 597 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 598 _, _, _, err = testing.StartInstance(env, "1") 599 c.Assert(errors.Cause(err), gc.Equals, mock.err) 600 601 mock.err = nil 602 dgErr := fmt.Errorf("DistributionGroup failed") 603 params := environs.StartInstanceParams{ 604 DistributionGroup: func() ([]instance.Id, error) { 605 return nil, dgErr 606 }, 607 } 608 _, err = testing.StartInstanceWithParams(env, "1", params) 609 c.Assert(errors.Cause(err), gc.Equals, dgErr) 610 } 611 612 func (t *localServerSuite) TestStartInstanceDistribution(c *gc.C) { 613 env := t.Prepare(c) 614 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 615 c.Assert(err, jc.ErrorIsNil) 616 617 // test-available is the only available AZ, so AvailabilityZoneAllocations 618 // is guaranteed to return that. 619 inst, _ := testing.AssertStartInstance(c, env, "1") 620 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available") 621 } 622 623 var azConstrainedErr = &amzec2.Error{ 624 Code: "Unsupported", 625 Message: "The requested Availability Zone is currently constrained etc.", 626 } 627 628 var azVolumeTypeNotAvailableInZoneErr = &amzec2.Error{ 629 Code: "VolumeTypeNotAvailableInZone", 630 Message: "blah blah", 631 } 632 633 var azInsufficientInstanceCapacityErr = &amzec2.Error{ 634 Code: "InsufficientInstanceCapacity", 635 Message: "We currently do not have sufficient m1.small capacity in the " + 636 "Availability Zone you requested (us-east-1d). Our system will " + 637 "be working on provisioning additional capacity. You can currently get m1.small " + 638 "capacity by not specifying an Availability Zone in your request or choosing " + 639 "us-east-1c, us-east-1a.", 640 } 641 642 var azNoDefaultSubnetErr = &amzec2.Error{ 643 Code: "InvalidInput", 644 Message: "No default subnet for availability zone: ''us-east-1e''.", 645 } 646 647 func (t *localServerSuite) TestStartInstanceAvailZoneAllConstrained(c *gc.C) { 648 t.testStartInstanceAvailZoneAllConstrained(c, azConstrainedErr) 649 } 650 651 func (t *localServerSuite) TestStartInstanceVolumeTypeNotAvailable(c *gc.C) { 652 t.testStartInstanceAvailZoneAllConstrained(c, azVolumeTypeNotAvailableInZoneErr) 653 } 654 655 func (t *localServerSuite) TestStartInstanceAvailZoneAllInsufficientInstanceCapacity(c *gc.C) { 656 t.testStartInstanceAvailZoneAllConstrained(c, azInsufficientInstanceCapacityErr) 657 } 658 659 func (t *localServerSuite) TestStartInstanceAvailZoneAllNoDefaultSubnet(c *gc.C) { 660 t.testStartInstanceAvailZoneAllConstrained(c, azNoDefaultSubnetErr) 661 } 662 663 func (t *localServerSuite) testStartInstanceAvailZoneAllConstrained(c *gc.C, runInstancesError *amzec2.Error) { 664 env := t.Prepare(c) 665 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 666 c.Assert(err, jc.ErrorIsNil) 667 668 mock := mockAvailabilityZoneAllocations{ 669 result: []common.AvailabilityZoneInstances{ 670 {ZoneName: "az1"}, {ZoneName: "az2"}, 671 }, 672 } 673 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 674 675 var azArgs []string 676 t.PatchValue(ec2.RunInstances, func(e *amzec2.EC2, ri *amzec2.RunInstances) (*amzec2.RunInstancesResp, error) { 677 azArgs = append(azArgs, ri.AvailZone) 678 return nil, runInstancesError 679 }) 680 _, _, _, err = testing.StartInstance(env, "1") 681 c.Assert(err, gc.ErrorMatches, fmt.Sprintf( 682 "cannot run instances: %s \\(%s\\)", 683 regexp.QuoteMeta(runInstancesError.Message), 684 runInstancesError.Code, 685 )) 686 c.Assert(azArgs, gc.DeepEquals, []string{"az1", "az2"}) 687 } 688 689 // addTestingSubnets adds a testing default VPC with 3 subnets in the EC2 test 690 // server: 2 of the subnets are in the "test-available" AZ, the remaining - in 691 // "test-unavailable". Returns a slice with the IDs of the created subnets. 692 func (t *localServerSuite) addTestingSubnets(c *gc.C) []network.Id { 693 vpc := t.srv.ec2srv.AddVPC(amzec2.VPC{ 694 CIDRBlock: "0.1.0.0/16", 695 IsDefault: true, 696 }) 697 results := make([]network.Id, 3) 698 sub1, err := t.srv.ec2srv.AddSubnet(amzec2.Subnet{ 699 VPCId: vpc.Id, 700 CIDRBlock: "0.1.2.0/24", 701 AvailZone: "test-available", 702 DefaultForAZ: true, 703 }) 704 c.Assert(err, jc.ErrorIsNil) 705 results[0] = network.Id(sub1.Id) 706 sub2, err := t.srv.ec2srv.AddSubnet(amzec2.Subnet{ 707 VPCId: vpc.Id, 708 CIDRBlock: "0.1.3.0/24", 709 AvailZone: "test-available", 710 }) 711 c.Assert(err, jc.ErrorIsNil) 712 results[1] = network.Id(sub2.Id) 713 sub3, err := t.srv.ec2srv.AddSubnet(amzec2.Subnet{ 714 VPCId: vpc.Id, 715 CIDRBlock: "0.1.4.0/24", 716 AvailZone: "test-unavailable", 717 DefaultForAZ: true, 718 }) 719 c.Assert(err, jc.ErrorIsNil) 720 results[2] = network.Id(sub3.Id) 721 return results 722 } 723 724 func (t *localServerSuite) prepareAndBootstrap(c *gc.C) environs.Environ { 725 env := t.Prepare(c) 726 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 727 c.Assert(err, jc.ErrorIsNil) 728 return env 729 } 730 731 func (t *localServerSuite) TestSpaceConstraintsSpaceNotInPlacementZone(c *gc.C) { 732 c.Skip("temporarily disabled") 733 env := t.prepareAndBootstrap(c) 734 subIDs := t.addTestingSubnets(c) 735 736 // Expect an error because zone test-available isn't in SubnetsToZones 737 params := environs.StartInstanceParams{ 738 Placement: "zone=test-available", 739 Constraints: constraints.MustParse("spaces=aaaaaaaaaa"), 740 SubnetsToZones: map[network.Id][]string{ 741 subIDs[0]: []string{"zone2"}, 742 subIDs[1]: []string{"zone3"}, 743 subIDs[2]: []string{"zone4"}, 744 }, 745 } 746 _, err := testing.StartInstanceWithParams(env, "1", params) 747 c.Assert(err, gc.ErrorMatches, `unable to resolve constraints: space and/or subnet unavailable in zones \[test-available\]`) 748 } 749 750 func (t *localServerSuite) TestSpaceConstraintsSpaceInPlacementZone(c *gc.C) { 751 env := t.prepareAndBootstrap(c) 752 subIDs := t.addTestingSubnets(c) 753 754 // Should work - test-available is in SubnetsToZones and in myspace. 755 params := environs.StartInstanceParams{ 756 Placement: "zone=test-available", 757 Constraints: constraints.MustParse("spaces=aaaaaaaaaa"), 758 SubnetsToZones: map[network.Id][]string{ 759 subIDs[0]: []string{"test-available"}, 760 subIDs[1]: []string{"zone3"}, 761 }, 762 } 763 _, err := testing.StartInstanceWithParams(env, "1", params) 764 c.Assert(err, jc.ErrorIsNil) 765 } 766 767 func (t *localServerSuite) TestSpaceConstraintsNoPlacement(c *gc.C) { 768 env := t.prepareAndBootstrap(c) 769 subIDs := t.addTestingSubnets(c) 770 771 // Shoule work because zone is not specified so we can resolve the constraints 772 params := environs.StartInstanceParams{ 773 Constraints: constraints.MustParse("spaces=aaaaaaaaaa"), 774 SubnetsToZones: map[network.Id][]string{ 775 subIDs[0]: []string{"test-available"}, 776 subIDs[1]: []string{"zone3"}, 777 }, 778 } 779 _, err := testing.StartInstanceWithParams(env, "1", params) 780 c.Assert(err, jc.ErrorIsNil) 781 } 782 783 func (t *localServerSuite) TestSpaceConstraintsNoAvailableSubnets(c *gc.C) { 784 c.Skip("temporarily disabled") 785 786 env := t.prepareAndBootstrap(c) 787 subIDs := t.addTestingSubnets(c) 788 789 // We requested a space, but there are no subnets in SubnetsToZones, so we can't resolve 790 // the constraints 791 params := environs.StartInstanceParams{ 792 Constraints: constraints.MustParse("spaces=aaaaaaaaaa"), 793 SubnetsToZones: map[network.Id][]string{ 794 subIDs[0]: []string{""}, 795 }, 796 } 797 _, err := testing.StartInstanceWithParams(env, "1", params) 798 c.Assert(err, gc.ErrorMatches, `unable to resolve constraints: space and/or subnet unavailable in zones \[test-available\]`) 799 } 800 801 func (t *localServerSuite) TestStartInstanceAvailZoneOneConstrained(c *gc.C) { 802 t.testStartInstanceAvailZoneOneConstrained(c, azConstrainedErr) 803 } 804 805 func (t *localServerSuite) TestStartInstanceAvailZoneOneInsufficientInstanceCapacity(c *gc.C) { 806 t.testStartInstanceAvailZoneOneConstrained(c, azInsufficientInstanceCapacityErr) 807 } 808 809 func (t *localServerSuite) TestStartInstanceAvailZoneOneNoDefaultSubnetErr(c *gc.C) { 810 t.testStartInstanceAvailZoneOneConstrained(c, azNoDefaultSubnetErr) 811 } 812 813 func (t *localServerSuite) testStartInstanceAvailZoneOneConstrained(c *gc.C, runInstancesError *amzec2.Error) { 814 env := t.Prepare(c) 815 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 816 c.Assert(err, jc.ErrorIsNil) 817 818 mock := mockAvailabilityZoneAllocations{ 819 result: []common.AvailabilityZoneInstances{ 820 {ZoneName: "az1"}, {ZoneName: "az2"}, 821 }, 822 } 823 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 824 825 // The first call to RunInstances fails with an error indicating the AZ 826 // is constrained. The second attempt succeeds, and so allocates to az2. 827 var azArgs []string 828 realRunInstances := *ec2.RunInstances 829 t.PatchValue(ec2.RunInstances, func(e *amzec2.EC2, ri *amzec2.RunInstances) (*amzec2.RunInstancesResp, error) { 830 azArgs = append(azArgs, ri.AvailZone) 831 if len(azArgs) == 1 { 832 return nil, runInstancesError 833 } 834 return realRunInstances(e, ri) 835 }) 836 inst, hwc := testing.AssertStartInstance(c, env, "1") 837 c.Assert(azArgs, gc.DeepEquals, []string{"az1", "az2"}) 838 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "az2") 839 c.Check(*hwc.AvailabilityZone, gc.Equals, "az2") 840 } 841 842 func (t *localServerSuite) TestAddresses(c *gc.C) { 843 env := t.Prepare(c) 844 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 845 c.Assert(err, jc.ErrorIsNil) 846 inst, _ := testing.AssertStartInstance(c, env, "1") 847 c.Assert(err, jc.ErrorIsNil) 848 addrs, err := inst.Addresses() 849 c.Assert(err, jc.ErrorIsNil) 850 // Expected values use Address type but really contain a regexp for 851 // the value rather than a valid ip or hostname. 852 expected := []network.Address{{ 853 Value: "8.0.0.*", 854 Type: network.IPv4Address, 855 Scope: network.ScopePublic, 856 }, { 857 Value: "127.0.0.*", 858 Type: network.IPv4Address, 859 Scope: network.ScopeCloudLocal, 860 }} 861 c.Assert(addrs, gc.HasLen, len(expected)) 862 for i, addr := range addrs { 863 c.Check(addr.Value, gc.Matches, expected[i].Value) 864 c.Check(addr.Type, gc.Equals, expected[i].Type) 865 c.Check(addr.Scope, gc.Equals, expected[i].Scope) 866 } 867 } 868 869 func (t *localServerSuite) TestConstraintsValidatorUnsupported(c *gc.C) { 870 env := t.Prepare(c) 871 validator, err := env.ConstraintsValidator() 872 c.Assert(err, jc.ErrorIsNil) 873 cons := constraints.MustParse("arch=amd64 tags=foo virt-type=kvm") 874 unsupported, err := validator.Validate(cons) 875 c.Assert(err, jc.ErrorIsNil) 876 c.Assert(unsupported, jc.SameContents, []string{"tags", "virt-type"}) 877 } 878 879 func (t *localServerSuite) TestConstraintsValidatorVocab(c *gc.C) { 880 env := t.Prepare(c) 881 validator, err := env.ConstraintsValidator() 882 c.Assert(err, jc.ErrorIsNil) 883 cons := constraints.MustParse("arch=ppc64el") 884 _, err = validator.Validate(cons) 885 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are:.*") 886 cons = constraints.MustParse("instance-type=foo") 887 _, err = validator.Validate(cons) 888 c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*") 889 } 890 891 func (t *localServerSuite) TestConstraintsMerge(c *gc.C) { 892 env := t.Prepare(c) 893 validator, err := env.ConstraintsValidator() 894 c.Assert(err, jc.ErrorIsNil) 895 consA := constraints.MustParse("arch=amd64 mem=1G cpu-power=10 cpu-cores=2 tags=bar") 896 consB := constraints.MustParse("arch=i386 instance-type=m1.small") 897 cons, err := validator.Merge(consA, consB) 898 c.Assert(err, jc.ErrorIsNil) 899 c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=i386 instance-type=m1.small tags=bar")) 900 } 901 902 func (t *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { 903 env := t.Prepare(c) 904 cons := constraints.MustParse("instance-type=m1.small root-disk=1G") 905 placement := "" 906 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, cons, placement) 907 c.Assert(err, jc.ErrorIsNil) 908 } 909 910 func (t *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { 911 env := t.Prepare(c) 912 cons := constraints.MustParse("instance-type=m1.invalid") 913 placement := "" 914 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, cons, placement) 915 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "m1.invalid" specified`) 916 } 917 918 func (t *localServerSuite) TestPrecheckInstanceUnsupportedArch(c *gc.C) { 919 env := t.Prepare(c) 920 cons := constraints.MustParse("instance-type=cc1.4xlarge arch=i386") 921 placement := "" 922 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, cons, placement) 923 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "cc1.4xlarge" and arch "i386" specified`) 924 } 925 926 func (t *localServerSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 927 env := t.Prepare(c) 928 placement := "zone=test-available" 929 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 930 c.Assert(err, jc.ErrorIsNil) 931 } 932 933 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnavailable(c *gc.C) { 934 env := t.Prepare(c) 935 placement := "zone=test-unavailable" 936 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 937 c.Assert(err, jc.ErrorIsNil) 938 } 939 940 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 941 env := t.Prepare(c) 942 placement := "zone=test-unknown" 943 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 944 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 945 } 946 947 func (t *localServerSuite) TestValidateImageMetadata(c *gc.C) { 948 env := t.Prepare(c) 949 params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("test") 950 c.Assert(err, jc.ErrorIsNil) 951 params.Series = coretesting.FakeDefaultSeries 952 params.Endpoint = "https://ec2.endpoint.com" 953 params.Sources, err = environs.ImageMetadataSources(env) 954 c.Assert(err, jc.ErrorIsNil) 955 image_ids, _, err := imagemetadata.ValidateImageMetadata(params) 956 c.Assert(err, jc.ErrorIsNil) 957 sort.Strings(image_ids) 958 c.Assert(image_ids, gc.DeepEquals, []string{"ami-00000033", "ami-00000034", "ami-00000035", "ami-00000039"}) 959 } 960 961 func (t *localServerSuite) TestGetToolsMetadataSources(c *gc.C) { 962 t.PatchValue(&tools.DefaultBaseURL, "") 963 964 env := t.Prepare(c) 965 sources, err := tools.GetMetadataSources(env) 966 c.Assert(err, jc.ErrorIsNil) 967 c.Assert(sources, gc.HasLen, 0) 968 } 969 970 func (t *localServerSuite) TestSupportedArchitectures(c *gc.C) { 971 env := t.Prepare(c) 972 a, err := env.SupportedArchitectures() 973 c.Assert(err, jc.ErrorIsNil) 974 c.Assert(a, jc.SameContents, []string{"amd64", "i386"}) 975 } 976 977 func (t *localServerSuite) TestSupportsNetworking(c *gc.C) { 978 env := t.Prepare(c) 979 _, supported := environs.SupportsNetworking(env) 980 c.Assert(supported, jc.IsTrue) 981 } 982 983 func (t *localServerSuite) TestAllocateAddressFailureToFindNetworkInterface(c *gc.C) { 984 env := t.prepareEnviron(c) 985 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 986 c.Assert(err, jc.ErrorIsNil) 987 988 instanceIds, err := env.ControllerInstances() 989 c.Assert(err, jc.ErrorIsNil) 990 991 instId := instanceIds[0] 992 addr := network.Address{Value: "8.0.0.4"} 993 994 // Invalid instance found 995 err = env.AllocateAddress(instId+"foo", "", &addr, "foo", "bar") 996 c.Assert(err, gc.ErrorMatches, ".*InvalidInstanceID.NotFound.*") 997 998 // No network interface 999 err = env.AllocateAddress(instId, "", &addr, "foo", "bar") 1000 c.Assert(errors.Cause(err), gc.ErrorMatches, "unexpected AWS response: network interface not found") 1001 1002 // Nil or empty address given. 1003 err = env.AllocateAddress(instId, "", nil, "foo", "bar") 1004 c.Assert(errors.Cause(err), gc.ErrorMatches, "invalid address: nil or empty") 1005 1006 err = env.AllocateAddress(instId, "", &network.Address{Value: ""}, "foo", "bar") 1007 c.Assert(errors.Cause(err), gc.ErrorMatches, "invalid address: nil or empty") 1008 } 1009 1010 func (t *localServerSuite) setUpInstanceWithDefaultVpc(c *gc.C) (environs.NetworkingEnviron, instance.Id) { 1011 // Simulate a default VPC exists. 1012 t.srv.ec2srv.AddDefaultVPCAndSubnets() 1013 1014 env := t.prepareEnviron(c) 1015 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 1016 c.Assert(err, jc.ErrorIsNil) 1017 1018 instanceIds, err := env.ControllerInstances() 1019 c.Assert(err, jc.ErrorIsNil) 1020 return env, instanceIds[0] 1021 } 1022 1023 func (t *localServerSuite) TestAllocateAddress(c *gc.C) { 1024 env, instId := t.setUpInstanceWithDefaultVpc(c) 1025 1026 addr := network.Address{Value: "8.0.0.4"} 1027 var actualAddr network.Address 1028 mockAssign := func(ec2Inst *amzec2.EC2, netId string, addr network.Address) error { 1029 actualAddr = addr 1030 return nil 1031 } 1032 t.PatchValue(&ec2.AssignPrivateIPAddress, mockAssign) 1033 1034 err := env.AllocateAddress(instId, "", &addr, "foo", "bar") 1035 c.Assert(err, jc.ErrorIsNil) 1036 c.Assert(actualAddr, gc.Equals, addr) 1037 } 1038 1039 func (t *localServerSuite) TestAllocateAddressIPAddressInUseOrEmpty(c *gc.C) { 1040 env, instId := t.setUpInstanceWithDefaultVpc(c) 1041 1042 addr := network.Address{Value: "8.0.0.4"} 1043 mockAssign := func(ec2Inst *amzec2.EC2, netId string, addr network.Address) error { 1044 return &amzec2.Error{Code: "InvalidParameterValue"} 1045 } 1046 t.PatchValue(&ec2.AssignPrivateIPAddress, mockAssign) 1047 1048 err := env.AllocateAddress(instId, "", &addr, "foo", "bar") 1049 c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressUnavailable) 1050 } 1051 1052 func (t *localServerSuite) TestAllocateAddressNetworkInterfaceFull(c *gc.C) { 1053 env, instId := t.setUpInstanceWithDefaultVpc(c) 1054 1055 addr := network.Address{Value: "8.0.0.4"} 1056 mockAssign := func(ec2Inst *amzec2.EC2, netId string, addr network.Address) error { 1057 return &amzec2.Error{Code: "PrivateIpAddressLimitExceeded"} 1058 } 1059 t.PatchValue(&ec2.AssignPrivateIPAddress, mockAssign) 1060 1061 err := env.AllocateAddress(instId, "", &addr, "foo", "bar") 1062 c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressesExhausted) 1063 } 1064 1065 func (t *localServerSuite) TestReleaseAddress(c *gc.C) { 1066 env, instId := t.setUpInstanceWithDefaultVpc(c) 1067 addr := network.Address{Value: "8.0.0.4"} 1068 // Allocate the address first so we can release it 1069 err := env.AllocateAddress(instId, "", &addr, "foo", "bar") 1070 c.Assert(err, jc.ErrorIsNil) 1071 1072 err = env.ReleaseAddress(instId, "", addr, "", "") 1073 c.Assert(err, jc.ErrorIsNil) 1074 1075 // Releasing a second time tests that the first call actually released 1076 // it plus tests the error handling of ReleaseAddress 1077 err = env.ReleaseAddress(instId, "", addr, "", "") 1078 msg := fmt.Sprintf(`failed to release address "8\.0\.0\.4" from instance %q.*`, instId) 1079 c.Assert(err, gc.ErrorMatches, msg) 1080 } 1081 1082 func (t *localServerSuite) TestReleaseAddressUnknownInstance(c *gc.C) { 1083 env, _ := t.setUpInstanceWithDefaultVpc(c) 1084 1085 // We should be able to release an address with an unknown instance id 1086 // without it being allocated. 1087 addr := network.Address{Value: "8.0.0.4"} 1088 err := env.ReleaseAddress(instance.UnknownId, "", addr, "", "") 1089 c.Assert(err, jc.ErrorIsNil) 1090 } 1091 1092 func (t *localServerSuite) TestNetworkInterfaces(c *gc.C) { 1093 env, instId := t.setUpInstanceWithDefaultVpc(c) 1094 interfaces, err := env.NetworkInterfaces(instId) 1095 c.Assert(err, jc.ErrorIsNil) 1096 1097 // The CIDR isn't predictable, but it is in the 10.10.x.0/24 format 1098 // The subnet ID is in the form "subnet-x", where x matches the same 1099 // number from the CIDR. The interfaces address is part of the CIDR. 1100 // For these reasons we check that the CIDR is in the expected format 1101 // and derive the expected values for ProviderSubnetId and Address. 1102 c.Assert(interfaces, gc.HasLen, 1) 1103 cidr := interfaces[0].CIDR 1104 re := regexp.MustCompile(`10\.10\.(\d+)\.0/24`) 1105 c.Assert(re.Match([]byte(cidr)), jc.IsTrue) 1106 index := re.FindStringSubmatch(cidr)[1] 1107 addr := fmt.Sprintf("10.10.%s.5", index) 1108 subnetId := network.Id("subnet-" + index) 1109 1110 // AvailabilityZones will either contain "test-available", 1111 // "test-impaired" or "test-unavailable" depending on which subnet is 1112 // picked. Any of these is fine. 1113 zones := interfaces[0].AvailabilityZones 1114 c.Assert(zones, gc.HasLen, 1) 1115 re = regexp.MustCompile("test-available|test-unavailable|test-impaired") 1116 c.Assert(re.Match([]byte(zones[0])), jc.IsTrue) 1117 1118 expectedInterfaces := []network.InterfaceInfo{{ 1119 DeviceIndex: 0, 1120 MACAddress: "20:01:60:cb:27:37", 1121 CIDR: cidr, 1122 ProviderId: "eni-0", 1123 ProviderSubnetId: subnetId, 1124 VLANTag: 0, 1125 InterfaceName: "unsupported0", 1126 Disabled: false, 1127 NoAutoStart: false, 1128 ConfigType: network.ConfigDHCP, 1129 InterfaceType: network.EthernetInterface, 1130 Address: network.NewScopedAddress(addr, network.ScopeCloudLocal), 1131 AvailabilityZones: zones, 1132 }} 1133 c.Assert(interfaces, jc.DeepEquals, expectedInterfaces) 1134 } 1135 1136 func (t *localServerSuite) TestSubnetsWithInstanceId(c *gc.C) { 1137 env, instId := t.setUpInstanceWithDefaultVpc(c) 1138 subnets, err := env.Subnets(instId, nil) 1139 c.Assert(err, jc.ErrorIsNil) 1140 c.Assert(subnets, gc.HasLen, 1) 1141 validateSubnets(c, subnets) 1142 1143 interfaces, err := env.NetworkInterfaces(instId) 1144 c.Assert(err, jc.ErrorIsNil) 1145 c.Assert(interfaces, gc.HasLen, 1) 1146 c.Assert(interfaces[0].ProviderSubnetId, gc.Equals, subnets[0].ProviderId) 1147 } 1148 1149 func (t *localServerSuite) TestSubnetsWithInstanceIdAndSubnetId(c *gc.C) { 1150 env, instId := t.setUpInstanceWithDefaultVpc(c) 1151 interfaces, err := env.NetworkInterfaces(instId) 1152 c.Assert(err, jc.ErrorIsNil) 1153 c.Assert(interfaces, gc.HasLen, 1) 1154 1155 subnets, err := env.Subnets(instId, []network.Id{interfaces[0].ProviderSubnetId}) 1156 c.Assert(err, jc.ErrorIsNil) 1157 c.Assert(subnets, gc.HasLen, 1) 1158 c.Assert(subnets[0].ProviderId, gc.Equals, interfaces[0].ProviderSubnetId) 1159 validateSubnets(c, subnets) 1160 } 1161 1162 func (t *localServerSuite) TestSubnetsWithInstanceIdMissingSubnet(c *gc.C) { 1163 env, instId := t.setUpInstanceWithDefaultVpc(c) 1164 subnets, err := env.Subnets(instId, []network.Id{"missing"}) 1165 c.Assert(err, gc.ErrorMatches, `failed to find the following subnet ids: \[missing\]`) 1166 c.Assert(subnets, gc.HasLen, 0) 1167 } 1168 1169 func validateSubnets(c *gc.C, subnets []network.SubnetInfo) { 1170 // These are defined in the test server for the testing default 1171 // VPC. 1172 defaultSubnets := []network.SubnetInfo{{ 1173 CIDR: "10.10.0.0/24", 1174 ProviderId: "subnet-0", 1175 VLANTag: 0, 1176 AllocatableIPLow: net.ParseIP("10.10.0.4").To4(), 1177 AllocatableIPHigh: net.ParseIP("10.10.0.254").To4(), 1178 AvailabilityZones: []string{"test-available"}, 1179 }, { 1180 CIDR: "10.10.1.0/24", 1181 ProviderId: "subnet-1", 1182 VLANTag: 0, 1183 AllocatableIPLow: net.ParseIP("10.10.1.4").To4(), 1184 AllocatableIPHigh: net.ParseIP("10.10.1.254").To4(), 1185 AvailabilityZones: []string{"test-impaired"}, 1186 }, { 1187 CIDR: "10.10.2.0/24", 1188 ProviderId: "subnet-2", 1189 VLANTag: 0, 1190 AllocatableIPLow: net.ParseIP("10.10.2.4").To4(), 1191 AllocatableIPHigh: net.ParseIP("10.10.2.254").To4(), 1192 AvailabilityZones: []string{"test-unavailable"}, 1193 }} 1194 1195 re := regexp.MustCompile(`10\.10\.(\d+)\.0/24`) 1196 for _, subnet := range subnets { 1197 // We can find the expected data by looking at the CIDR. 1198 // subnets isn't in a predictable order due to the use of maps. 1199 c.Assert(re.Match([]byte(subnet.CIDR)), jc.IsTrue) 1200 index, err := strconv.Atoi(re.FindStringSubmatch(subnet.CIDR)[1]) 1201 c.Assert(err, jc.ErrorIsNil) 1202 // Don't know which AZ the subnet will end up in. 1203 defaultSubnets[index].AvailabilityZones = subnet.AvailabilityZones 1204 c.Assert(subnet, jc.DeepEquals, defaultSubnets[index]) 1205 } 1206 } 1207 1208 func (t *localServerSuite) TestSubnets(c *gc.C) { 1209 env, _ := t.setUpInstanceWithDefaultVpc(c) 1210 1211 subnets, err := env.Subnets(instance.UnknownId, []network.Id{"subnet-0"}) 1212 c.Assert(err, jc.ErrorIsNil) 1213 c.Assert(subnets, gc.HasLen, 1) 1214 validateSubnets(c, subnets) 1215 1216 subnets, err = env.Subnets(instance.UnknownId, nil) 1217 c.Assert(err, jc.ErrorIsNil) 1218 c.Assert(subnets, gc.HasLen, 3) 1219 validateSubnets(c, subnets) 1220 } 1221 1222 func (t *localServerSuite) TestSubnetsMissingSubnet(c *gc.C) { 1223 env, _ := t.setUpInstanceWithDefaultVpc(c) 1224 1225 _, err := env.Subnets("", []network.Id{"subnet-0", "Missing"}) 1226 c.Assert(err, gc.ErrorMatches, `failed to find the following subnet ids: \[Missing\]`) 1227 } 1228 1229 func (t *localServerSuite) TestSupportsAddressAllocationTrue(c *gc.C) { 1230 t.srv.ec2srv.AddDefaultVPCAndSubnets() 1231 env := t.prepareEnviron(c) 1232 result, err := env.SupportsAddressAllocation("") 1233 c.Assert(err, jc.ErrorIsNil) 1234 c.Assert(result, jc.IsTrue) 1235 } 1236 1237 func (t *localServerSuite) TestSupportsAddressAllocationWithNoFeatureFlag(c *gc.C) { 1238 t.SetFeatureFlags() // clear the flags. 1239 env := t.prepareEnviron(c) 1240 result, err := env.SupportsAddressAllocation("") 1241 c.Assert(err, gc.ErrorMatches, "address allocation not supported") 1242 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 1243 c.Assert(result, jc.IsFalse) 1244 } 1245 1246 func (t *localServerSuite) TestAllocateAddressWithNoFeatureFlag(c *gc.C) { 1247 t.SetFeatureFlags() // clear the flags. 1248 env := t.prepareEnviron(c) 1249 addr := network.NewAddresses("1.2.3.4")[0] 1250 err := env.AllocateAddress("i-foo", "net1", &addr, "foo", "bar") 1251 c.Assert(err, gc.ErrorMatches, "address allocation not supported") 1252 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 1253 } 1254 1255 func (t *localServerSuite) TestReleaseAddressWithNoFeatureFlag(c *gc.C) { 1256 t.SetFeatureFlags() // clear the flags. 1257 env := t.prepareEnviron(c) 1258 err := env.ReleaseAddress("i-foo", "net1", network.NewAddress("1.2.3.4"), "", "") 1259 c.Assert(err, gc.ErrorMatches, "address allocation not supported") 1260 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 1261 } 1262 1263 func (t *localServerSuite) TestSupportsAddressAllocationCaches(c *gc.C) { 1264 t.srv.ec2srv.SetAccountAttributes(map[string][]string{ 1265 "default-vpc": {"none"}, 1266 }) 1267 env := t.prepareEnviron(c) 1268 result, err := env.SupportsAddressAllocation("") 1269 c.Assert(err, jc.ErrorIsNil) 1270 c.Assert(result, jc.IsFalse) 1271 1272 // this value won't change normally, the change here is to 1273 // ensure that subsequent calls use the cached value 1274 t.srv.ec2srv.SetAccountAttributes(map[string][]string{ 1275 "default-vpc": {"vpc-xxxxxxx"}, 1276 }) 1277 result, err = env.SupportsAddressAllocation("") 1278 c.Assert(err, jc.ErrorIsNil) 1279 c.Assert(result, jc.IsFalse) 1280 } 1281 1282 func (t *localServerSuite) TestSupportsAddressAllocationFalse(c *gc.C) { 1283 t.srv.ec2srv.SetAccountAttributes(map[string][]string{ 1284 "default-vpc": {"none"}, 1285 }) 1286 env := t.prepareEnviron(c) 1287 result, err := env.SupportsAddressAllocation("") 1288 c.Assert(err, jc.ErrorIsNil) 1289 c.Assert(result, jc.IsFalse) 1290 } 1291 1292 func (t *localServerSuite) TestInstanceTags(c *gc.C) { 1293 env := t.Prepare(c) 1294 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 1295 c.Assert(err, jc.ErrorIsNil) 1296 1297 instances, err := env.AllInstances() 1298 c.Assert(err, jc.ErrorIsNil) 1299 c.Assert(instances, gc.HasLen, 1) 1300 1301 ec2Inst := ec2.InstanceEC2(instances[0]) 1302 c.Assert(ec2Inst.Tags, jc.SameContents, []amzec2.Tag{ 1303 {"Name", "juju-sample-machine-0"}, 1304 {"juju-model-uuid", coretesting.ModelTag.Id()}, 1305 {"juju-controller-uuid", coretesting.ModelTag.Id()}, 1306 {"juju-is-controller", "true"}, 1307 }) 1308 } 1309 1310 func (t *localServerSuite) TestRootDiskTags(c *gc.C) { 1311 env := t.Prepare(c) 1312 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 1313 c.Assert(err, jc.ErrorIsNil) 1314 1315 instances, err := env.AllInstances() 1316 c.Assert(err, jc.ErrorIsNil) 1317 c.Assert(instances, gc.HasLen, 1) 1318 1319 ec2conn := ec2.EnvironEC2(env) 1320 resp, err := ec2conn.Volumes(nil, nil) 1321 c.Assert(err, jc.ErrorIsNil) 1322 c.Assert(resp.Volumes, gc.Not(gc.HasLen), 0) 1323 1324 var found *amzec2.Volume 1325 for _, vol := range resp.Volumes { 1326 if len(vol.Tags) != 0 { 1327 found = &vol 1328 break 1329 } 1330 } 1331 c.Assert(found, gc.NotNil) 1332 c.Assert(found.Tags, jc.SameContents, []amzec2.Tag{ 1333 {"Name", "juju-sample-machine-0-root"}, 1334 {"juju-model-uuid", coretesting.ModelTag.Id()}, 1335 {"juju-controller-uuid", coretesting.ModelTag.Id()}, 1336 }) 1337 } 1338 1339 // localNonUSEastSuite is similar to localServerSuite but the S3 mock server 1340 // behaves as if it is not in the us-east region. 1341 type localNonUSEastSuite struct { 1342 coretesting.BaseSuite 1343 sstesting.TestDataSuite 1344 1345 restoreEC2Patching func() 1346 srv localServer 1347 env environs.Environ 1348 } 1349 1350 func (t *localNonUSEastSuite) SetUpSuite(c *gc.C) { 1351 t.BaseSuite.SetUpSuite(c) 1352 t.TestDataSuite.SetUpSuite(c) 1353 1354 t.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) 1355 t.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey) 1356 1357 t.restoreEC2Patching = patchEC2ForTesting(c) 1358 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc) 1359 } 1360 1361 func (t *localNonUSEastSuite) TearDownSuite(c *gc.C) { 1362 t.restoreEC2Patching() 1363 t.TestDataSuite.TearDownSuite(c) 1364 t.BaseSuite.TearDownSuite(c) 1365 } 1366 1367 func (t *localNonUSEastSuite) SetUpTest(c *gc.C) { 1368 t.BaseSuite.SetUpTest(c) 1369 t.srv.config = &s3test.Config{ 1370 Send409Conflict: true, 1371 } 1372 t.srv.startServer(c) 1373 1374 env, err := environs.Prepare( 1375 envtesting.BootstrapContext(c), 1376 jujuclienttesting.NewMemStore(), 1377 environs.PrepareParams{ 1378 BaseConfig: localConfigAttrs, 1379 Credential: cloud.NewCredential( 1380 cloud.AccessKeyAuthType, 1381 map[string]string{ 1382 "access-key": "x", 1383 "secret-key": "x", 1384 }, 1385 ), 1386 ControllerName: localConfigAttrs["name"].(string), 1387 CloudName: "ec2", 1388 CloudRegion: "test", 1389 }, 1390 ) 1391 c.Assert(err, jc.ErrorIsNil) 1392 t.env = env 1393 } 1394 1395 func (t *localNonUSEastSuite) TearDownTest(c *gc.C) { 1396 t.srv.stopServer(c) 1397 t.BaseSuite.TearDownTest(c) 1398 } 1399 1400 func patchEC2ForTesting(c *gc.C) func() { 1401 ec2.UseTestImageData(c, ec2.TestImagesData) 1402 ec2.UseTestInstanceTypeData(ec2.TestInstanceTypeCosts) 1403 ec2.UseTestRegionData(ec2.TestRegions) 1404 restoreTimeouts := envtesting.PatchAttemptStrategies(ec2.ShortAttempt, ec2.StorageAttempt) 1405 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 1406 return func() { 1407 restoreFinishBootstrap() 1408 restoreTimeouts() 1409 ec2.UseTestImageData(c, nil) 1410 ec2.UseTestInstanceTypeData(nil) 1411 ec2.UseTestRegionData(nil) 1412 } 1413 } 1414 1415 // If match is true, CheckScripts checks that at least one script started 1416 // by the cloudinit data matches the given regexp pattern, otherwise it 1417 // checks that no script matches. It's exported so it can be used by tests 1418 // defined in ec2_test. 1419 func CheckScripts(c *gc.C, userDataMap map[interface{}]interface{}, pattern string, match bool) { 1420 scripts0 := userDataMap["runcmd"] 1421 if scripts0 == nil { 1422 c.Errorf("cloudinit has no entry for runcmd") 1423 return 1424 } 1425 scripts := scripts0.([]interface{}) 1426 re := regexp.MustCompile(pattern) 1427 found := false 1428 for _, s0 := range scripts { 1429 s := s0.(string) 1430 if re.MatchString(s) { 1431 found = true 1432 } 1433 } 1434 switch { 1435 case match && !found: 1436 c.Errorf("script %q not found in %q", pattern, scripts) 1437 case !match && found: 1438 c.Errorf("script %q found but not expected in %q", pattern, scripts) 1439 } 1440 } 1441 1442 // CheckPackage checks that the cloudinit will or won't install the given 1443 // package, depending on the value of match. It's exported so it can be 1444 // used by tests defined outside the ec2 package. 1445 func CheckPackage(c *gc.C, userDataMap map[interface{}]interface{}, pkg string, match bool) { 1446 pkgs0 := userDataMap["packages"] 1447 if pkgs0 == nil { 1448 if match { 1449 c.Errorf("cloudinit has no entry for packages") 1450 } 1451 return 1452 } 1453 1454 pkgs := pkgs0.([]interface{}) 1455 1456 found := false 1457 for _, p0 := range pkgs { 1458 p := p0.(string) 1459 // p might be a space separate list of packages eg 'foo bar qed' so split them up 1460 manyPkgs := set.NewStrings(strings.Split(p, " ")...) 1461 hasPkg := manyPkgs.Contains(pkg) 1462 if p == pkg || hasPkg { 1463 found = true 1464 break 1465 } 1466 } 1467 switch { 1468 case match && !found: 1469 c.Errorf("package %q not found in %v", pkg, pkgs) 1470 case !match && found: 1471 c.Errorf("%q found but not expected in %v", pkg, pkgs) 1472 } 1473 }