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