github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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 "regexp" 9 "sort" 10 "strings" 11 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils" 14 "launchpad.net/goamz/aws" 15 amzec2 "launchpad.net/goamz/ec2" 16 "launchpad.net/goamz/ec2/ec2test" 17 "launchpad.net/goamz/s3" 18 "launchpad.net/goamz/s3/s3test" 19 gc "launchpad.net/gocheck" 20 "launchpad.net/goyaml" 21 22 "github.com/juju/juju/constraints" 23 "github.com/juju/juju/environs" 24 "github.com/juju/juju/environs/bootstrap" 25 "github.com/juju/juju/environs/config" 26 "github.com/juju/juju/environs/configstore" 27 "github.com/juju/juju/environs/imagemetadata" 28 "github.com/juju/juju/environs/jujutest" 29 "github.com/juju/juju/environs/simplestreams" 30 envtesting "github.com/juju/juju/environs/testing" 31 "github.com/juju/juju/environs/tools" 32 "github.com/juju/juju/instance" 33 "github.com/juju/juju/juju/arch" 34 "github.com/juju/juju/juju/testing" 35 "github.com/juju/juju/network" 36 "github.com/juju/juju/provider/common" 37 "github.com/juju/juju/provider/ec2" 38 coretesting "github.com/juju/juju/testing" 39 "github.com/juju/juju/utils/ssh" 40 "github.com/juju/juju/version" 41 ) 42 43 type ProviderSuite struct { 44 coretesting.BaseSuite 45 } 46 47 var _ = gc.Suite(&ProviderSuite{}) 48 49 func (t *ProviderSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) { 50 // Make an env configured with the stream. 51 envAttrs := localConfigAttrs 52 if stream != "" { 53 envAttrs = envAttrs.Merge(coretesting.Attrs{ 54 "image-stream": stream, 55 }) 56 } 57 cfg, err := config.New(config.NoDefaults, envAttrs) 58 c.Assert(err, gc.IsNil) 59 env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) 60 c.Assert(err, gc.IsNil) 61 c.Assert(env, gc.NotNil) 62 63 sources, err := imagemetadata.GetMetadataSources(env) 64 c.Assert(err, gc.IsNil) 65 c.Assert(len(sources), gc.Equals, 2) 66 var urls = make([]string, len(sources)) 67 for i, source := range sources { 68 url, err := source.URL("") 69 c.Assert(err, gc.IsNil) 70 urls[i] = url 71 } 72 // The control bucket URL contains the bucket name. 73 c.Check(strings.Contains(urls[0], ec2.ControlBucketName(env)+"/images"), jc.IsTrue) 74 c.Assert(urls[1], gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath)) 75 } 76 77 func (t *ProviderSuite) TestGetImageMetadataSources(c *gc.C) { 78 t.assertGetImageMetadataSources(c, "", "releases") 79 t.assertGetImageMetadataSources(c, "released", "releases") 80 t.assertGetImageMetadataSources(c, "daily", "daily") 81 } 82 83 var localConfigAttrs = coretesting.FakeConfig().Merge(coretesting.Attrs{ 84 "name": "sample", 85 "type": "ec2", 86 "region": "test", 87 "control-bucket": "test-bucket", 88 "access-key": "x", 89 "secret-key": "x", 90 "agent-version": version.Current.Number.String(), 91 }) 92 93 func registerLocalTests() { 94 // N.B. Make sure the region we use here 95 // has entries in the images/query txt files. 96 aws.Regions["test"] = aws.Region{ 97 Name: "test", 98 } 99 100 gc.Suite(&localServerSuite{}) 101 gc.Suite(&localLiveSuite{}) 102 gc.Suite(&localNonUSEastSuite{}) 103 } 104 105 // localLiveSuite runs tests from LiveTests using a fake 106 // EC2 server that runs within the test process itself. 107 type localLiveSuite struct { 108 LiveTests 109 srv localServer 110 restoreEC2Patching func() 111 } 112 113 func (t *localLiveSuite) SetUpSuite(c *gc.C) { 114 t.TestConfig = localConfigAttrs 115 t.restoreEC2Patching = patchEC2ForTesting() 116 t.srv.startServer(c) 117 t.LiveTests.SetUpSuite(c) 118 } 119 120 func (t *localLiveSuite) TearDownSuite(c *gc.C) { 121 t.LiveTests.TearDownSuite(c) 122 t.srv.stopServer(c) 123 t.restoreEC2Patching() 124 } 125 126 // localServer represents a fake EC2 server running within 127 // the test process itself. 128 type localServer struct { 129 ec2srv *ec2test.Server 130 s3srv *s3test.Server 131 config *s3test.Config 132 } 133 134 func (srv *localServer) startServer(c *gc.C) { 135 var err error 136 srv.ec2srv, err = ec2test.NewServer() 137 if err != nil { 138 c.Fatalf("cannot start ec2 test server: %v", err) 139 } 140 srv.s3srv, err = s3test.NewServer(srv.config) 141 if err != nil { 142 c.Fatalf("cannot start s3 test server: %v", err) 143 } 144 aws.Regions["test"] = aws.Region{ 145 Name: "test", 146 EC2Endpoint: srv.ec2srv.URL(), 147 S3Endpoint: srv.s3srv.URL(), 148 S3LocationConstraint: true, 149 } 150 s3inst := s3.New(aws.Auth{}, aws.Regions["test"]) 151 storage := ec2.BucketStorage(s3inst.Bucket("juju-dist")) 152 envtesting.UploadFakeTools(c, storage) 153 srv.addSpice(c) 154 155 zones := make([]amzec2.AvailabilityZoneInfo, 3) 156 zones[0].Region = "test" 157 zones[0].Name = "test-available" 158 zones[0].State = "available" 159 zones[1].Region = "test" 160 zones[1].Name = "test-impaired" 161 zones[1].State = "impaired" 162 zones[2].Region = "test" 163 zones[2].Name = "test-unavailable" 164 zones[2].State = "unavailable" 165 srv.ec2srv.SetAvailabilityZones(zones) 166 } 167 168 // addSpice adds some "spice" to the local server 169 // by adding state that may cause tests to fail. 170 func (srv *localServer) addSpice(c *gc.C) { 171 states := []amzec2.InstanceState{ 172 ec2test.ShuttingDown, 173 ec2test.Terminated, 174 ec2test.Stopped, 175 } 176 for _, state := range states { 177 srv.ec2srv.NewInstances(1, "m1.small", "ami-a7f539ce", state, nil) 178 } 179 } 180 181 func (srv *localServer) stopServer(c *gc.C) { 182 srv.ec2srv.Quit() 183 srv.s3srv.Quit() 184 // Clear out the region because the server address is 185 // no longer valid. 186 delete(aws.Regions, "test") 187 } 188 189 // localServerSuite contains tests that run against a fake EC2 server 190 // running within the test process itself. These tests can test things that 191 // would be unreasonably slow or expensive to test on a live Amazon server. 192 // It starts a new local ec2test server for each test. The server is 193 // accessed by using the "test" region, which is changed to point to the 194 // network address of the local server. 195 type localServerSuite struct { 196 coretesting.BaseSuite 197 jujutest.Tests 198 srv localServer 199 restoreEC2Patching func() 200 } 201 202 func (t *localServerSuite) SetUpSuite(c *gc.C) { 203 t.TestConfig = localConfigAttrs 204 t.restoreEC2Patching = patchEC2ForTesting() 205 t.BaseSuite.SetUpSuite(c) 206 } 207 208 func (t *localServerSuite) TearDownSuite(c *gc.C) { 209 t.BaseSuite.TearDownSuite(c) 210 t.restoreEC2Patching() 211 } 212 213 func (t *localServerSuite) SetUpTest(c *gc.C) { 214 t.BaseSuite.SetUpTest(c) 215 t.srv.startServer(c) 216 t.Tests.SetUpTest(c) 217 t.PatchValue(&version.Current, version.Binary{ 218 Number: version.Current.Number, 219 Series: coretesting.FakeDefaultSeries, 220 Arch: arch.AMD64, 221 }) 222 } 223 224 func (t *localServerSuite) TearDownTest(c *gc.C) { 225 t.Tests.TearDownTest(c) 226 t.srv.stopServer(c) 227 t.BaseSuite.TearDownTest(c) 228 } 229 230 func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { 231 env := t.Prepare(c) 232 envtesting.UploadFakeTools(c, env.Storage()) 233 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 234 c.Assert(err, gc.IsNil) 235 236 // check that the state holds the id of the bootstrap machine. 237 bootstrapState, err := bootstrap.LoadState(env.Storage()) 238 c.Assert(err, gc.IsNil) 239 c.Assert(bootstrapState.StateInstances, gc.HasLen, 1) 240 241 insts, err := env.AllInstances() 242 c.Assert(err, gc.IsNil) 243 c.Assert(insts, gc.HasLen, 1) 244 c.Check(insts[0].Id(), gc.Equals, bootstrapState.StateInstances[0]) 245 246 // check that the user data is configured to start zookeeper 247 // and the machine and provisioning agents. 248 // check that the user data is configured to only configure 249 // authorized SSH keys and set the log output; everything 250 // else happens after the machine is brought up. 251 inst := t.srv.ec2srv.Instance(string(insts[0].Id())) 252 c.Assert(inst, gc.NotNil) 253 addresses, err := insts[0].Addresses() 254 c.Assert(err, gc.IsNil) 255 c.Assert(addresses, gc.Not(gc.HasLen), 0) 256 userData, err := utils.Gunzip(inst.UserData) 257 c.Assert(err, gc.IsNil) 258 c.Logf("first instance: UserData: %q", userData) 259 var userDataMap map[interface{}]interface{} 260 err = goyaml.Unmarshal(userData, &userDataMap) 261 c.Assert(err, gc.IsNil) 262 c.Assert(userDataMap, jc.DeepEquals, map[interface{}]interface{}{ 263 "output": map[interface{}]interface{}{ 264 "all": "| tee -a /var/log/cloud-init-output.log", 265 }, 266 "ssh_authorized_keys": splitAuthKeys(env.Config().AuthorizedKeys()), 267 "runcmd": []interface{}{ 268 "set -xe", 269 "install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'", 270 "printf '%s\\n' 'user-admin:bootstrap' > '/var/lib/juju/nonce.txt'", 271 }, 272 }) 273 274 // check that a new instance will be started with a machine agent 275 inst1, hc := testing.AssertStartInstance(c, env, "1") 276 c.Check(*hc.Arch, gc.Equals, "amd64") 277 c.Check(*hc.Mem, gc.Equals, uint64(1740)) 278 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 279 c.Assert(*hc.CpuPower, gc.Equals, uint64(100)) 280 inst = t.srv.ec2srv.Instance(string(inst1.Id())) 281 c.Assert(inst, gc.NotNil) 282 userData, err = utils.Gunzip(inst.UserData) 283 c.Assert(err, gc.IsNil) 284 c.Logf("second instance: UserData: %q", userData) 285 userDataMap = nil 286 err = goyaml.Unmarshal(userData, &userDataMap) 287 c.Assert(err, gc.IsNil) 288 CheckPackage(c, userDataMap, "git", true) 289 CheckPackage(c, userDataMap, "mongodb-server", false) 290 CheckScripts(c, userDataMap, "jujud bootstrap-state", false) 291 CheckScripts(c, userDataMap, "/var/lib/juju/agents/machine-1/agent.conf", true) 292 // TODO check for provisioning agent 293 294 err = env.Destroy() 295 c.Assert(err, gc.IsNil) 296 297 _, err = bootstrap.LoadState(env.Storage()) 298 c.Assert(err, gc.NotNil) 299 } 300 301 // splitAuthKeys splits the given authorized keys 302 // into the form expected to be found in the 303 // user data. 304 func splitAuthKeys(keys string) []interface{} { 305 slines := strings.FieldsFunc(keys, func(r rune) bool { 306 return r == '\n' 307 }) 308 var lines []interface{} 309 for _, line := range slines { 310 lines = append(lines, ssh.EnsureJujuComment(strings.TrimSpace(line))) 311 } 312 return lines 313 } 314 315 func (t *localServerSuite) TestInstanceStatus(c *gc.C) { 316 env := t.Prepare(c) 317 envtesting.UploadFakeTools(c, env.Storage()) 318 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 319 c.Assert(err, gc.IsNil) 320 t.srv.ec2srv.SetInitialInstanceState(ec2test.Terminated) 321 inst, _ := testing.AssertStartInstance(c, env, "1") 322 c.Assert(err, gc.IsNil) 323 c.Assert(inst.Status(), gc.Equals, "terminated") 324 } 325 326 func (t *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { 327 env := t.Prepare(c) 328 envtesting.UploadFakeTools(c, env.Storage()) 329 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 330 c.Assert(err, gc.IsNil) 331 _, hc := testing.AssertStartInstance(c, env, "1") 332 c.Check(*hc.Arch, gc.Equals, "amd64") 333 c.Check(*hc.Mem, gc.Equals, uint64(1740)) 334 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 335 c.Assert(*hc.CpuPower, gc.Equals, uint64(100)) 336 } 337 338 func (t *localServerSuite) TestStartInstanceAvailZone(c *gc.C) { 339 inst, err := t.testStartInstanceAvailZone(c, "test-available") 340 c.Assert(err, gc.IsNil) 341 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available") 342 } 343 344 func (t *localServerSuite) TestStartInstanceAvailZoneImpaired(c *gc.C) { 345 _, err := t.testStartInstanceAvailZone(c, "test-impaired") 346 c.Assert(err, gc.ErrorMatches, `availability zone "test-impaired" is impaired`) 347 } 348 349 func (t *localServerSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 350 _, err := t.testStartInstanceAvailZone(c, "test-unknown") 351 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 352 } 353 354 func (t *localServerSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { 355 env := t.Prepare(c) 356 envtesting.UploadFakeTools(c, env.Storage()) 357 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 358 c.Assert(err, gc.IsNil) 359 360 params := environs.StartInstanceParams{Placement: "zone=" + zone} 361 inst, _, _, err := testing.StartInstanceWithParams(env, "1", params, nil) 362 return inst, err 363 } 364 365 func (t *localServerSuite) TestGetAvailabilityZones(c *gc.C) { 366 var resultZones []amzec2.AvailabilityZoneInfo 367 var resultErr error 368 t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) { 369 resp := &amzec2.AvailabilityZonesResp{ 370 Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...), 371 } 372 return resp, resultErr 373 }) 374 env := t.Prepare(c).(common.ZonedEnviron) 375 376 resultErr = fmt.Errorf("failed to get availability zones") 377 zones, err := env.AvailabilityZones() 378 c.Assert(err, gc.Equals, resultErr) 379 c.Assert(zones, gc.IsNil) 380 381 resultErr = nil 382 resultZones = make([]amzec2.AvailabilityZoneInfo, 1) 383 resultZones[0].Name = "whatever" 384 zones, err = env.AvailabilityZones() 385 c.Assert(err, gc.IsNil) 386 c.Assert(zones, gc.HasLen, 1) 387 c.Assert(zones[0].Name(), gc.Equals, "whatever") 388 389 // A successful result is cached, currently for the lifetime 390 // of the Environ. This will change if/when we have long-lived 391 // Environs to cut down repeated IaaS requests. 392 resultErr = fmt.Errorf("failed to get availability zones") 393 resultZones[0].Name = "andever" 394 zones, err = env.AvailabilityZones() 395 c.Assert(err, gc.IsNil) 396 c.Assert(zones, gc.HasLen, 1) 397 c.Assert(zones[0].Name(), gc.Equals, "whatever") 398 } 399 400 func (t *localServerSuite) TestGetAvailabilityZonesCommon(c *gc.C) { 401 var resultZones []amzec2.AvailabilityZoneInfo 402 t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) { 403 resp := &amzec2.AvailabilityZonesResp{ 404 Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...), 405 } 406 return resp, nil 407 }) 408 env := t.Prepare(c).(common.ZonedEnviron) 409 resultZones = make([]amzec2.AvailabilityZoneInfo, 2) 410 resultZones[0].Name = "az1" 411 resultZones[1].Name = "az2" 412 resultZones[0].State = "available" 413 resultZones[1].State = "impaired" 414 zones, err := env.AvailabilityZones() 415 c.Assert(err, gc.IsNil) 416 c.Assert(zones, gc.HasLen, 2) 417 c.Assert(zones[0].Name(), gc.Equals, resultZones[0].Name) 418 c.Assert(zones[1].Name(), gc.Equals, resultZones[1].Name) 419 c.Assert(zones[0].Available(), jc.IsTrue) 420 c.Assert(zones[1].Available(), jc.IsFalse) 421 } 422 423 type mockBestAvailabilityZoneAllocations struct { 424 group []instance.Id // input param 425 result map[string][]instance.Id 426 err error 427 } 428 429 func (t *mockBestAvailabilityZoneAllocations) BestAvailabilityZoneAllocations( 430 e common.ZonedEnviron, group []instance.Id, 431 ) (map[string][]instance.Id, error) { 432 t.group = group 433 return t.result, t.err 434 } 435 436 func (t *localServerSuite) TestStartInstanceDistributionParams(c *gc.C) { 437 env := t.Prepare(c) 438 envtesting.UploadFakeTools(c, env.Storage()) 439 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 440 c.Assert(err, gc.IsNil) 441 442 var mock mockBestAvailabilityZoneAllocations 443 t.PatchValue(ec2.BestAvailabilityZoneAllocations, mock.BestAvailabilityZoneAllocations) 444 445 // no distribution group specified 446 testing.AssertStartInstance(c, env, "1") 447 c.Assert(mock.group, gc.HasLen, 0) 448 449 // distribution group specified: ensure it's passed through to BestAvailabilityZone. 450 expectedInstances := []instance.Id{"i-0", "i-1"} 451 params := environs.StartInstanceParams{ 452 DistributionGroup: func() ([]instance.Id, error) { 453 return expectedInstances, nil 454 }, 455 } 456 _, _, _, err = testing.StartInstanceWithParams(env, "1", params, nil) 457 c.Assert(err, gc.IsNil) 458 c.Assert(mock.group, gc.DeepEquals, expectedInstances) 459 } 460 461 func (t *localServerSuite) TestStartInstanceDistributionErrors(c *gc.C) { 462 env := t.Prepare(c) 463 envtesting.UploadFakeTools(c, env.Storage()) 464 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 465 c.Assert(err, gc.IsNil) 466 467 mock := mockBestAvailabilityZoneAllocations{ 468 err: fmt.Errorf("BestAvailabilityZoneAllocations failed"), 469 } 470 t.PatchValue(ec2.BestAvailabilityZoneAllocations, mock.BestAvailabilityZoneAllocations) 471 _, _, _, err = testing.StartInstance(env, "1") 472 c.Assert(err, gc.Equals, mock.err) 473 474 mock.err = nil 475 dgErr := fmt.Errorf("DistributionGroup failed") 476 params := environs.StartInstanceParams{ 477 DistributionGroup: func() ([]instance.Id, error) { 478 return nil, dgErr 479 }, 480 } 481 _, _, _, err = testing.StartInstanceWithParams(env, "1", params, nil) 482 c.Assert(err, gc.Equals, dgErr) 483 } 484 485 func (t *localServerSuite) TestStartInstanceDistribution(c *gc.C) { 486 env := t.Prepare(c) 487 envtesting.UploadFakeTools(c, env.Storage()) 488 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 489 c.Assert(err, gc.IsNil) 490 491 // test-available is the only available AZ, so BestAvailabilityZoneAllocations 492 // is guaranteed to return that. 493 inst, _ := testing.AssertStartInstance(c, env, "1") 494 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available") 495 } 496 497 func (t *localServerSuite) TestAddresses(c *gc.C) { 498 env := t.Prepare(c) 499 envtesting.UploadFakeTools(c, env.Storage()) 500 err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{}) 501 c.Assert(err, gc.IsNil) 502 inst, _ := testing.AssertStartInstance(c, env, "1") 503 c.Assert(err, gc.IsNil) 504 addrs, err := inst.Addresses() 505 c.Assert(err, gc.IsNil) 506 // Expected values use Address type but really contain a regexp for 507 // the value rather than a valid ip or hostname. 508 expected := []network.Address{{ 509 Value: "*.testing.invalid", 510 Type: network.HostName, 511 Scope: network.ScopePublic, 512 }, { 513 Value: "*.internal.invalid", 514 Type: network.HostName, 515 Scope: network.ScopeCloudLocal, 516 }, { 517 Value: "8.0.0.*", 518 Type: network.IPv4Address, 519 Scope: network.ScopePublic, 520 }, { 521 Value: "127.0.0.*", 522 Type: network.IPv4Address, 523 Scope: network.ScopeCloudLocal, 524 }} 525 c.Assert(addrs, gc.HasLen, len(expected)) 526 for i, addr := range addrs { 527 c.Check(addr.Value, gc.Matches, expected[i].Value) 528 c.Check(addr.Type, gc.Equals, expected[i].Type) 529 c.Check(addr.Scope, gc.Equals, expected[i].Scope) 530 } 531 } 532 533 func (t *localServerSuite) TestConstraintsValidatorUnsupported(c *gc.C) { 534 env := t.Prepare(c) 535 validator, err := env.ConstraintsValidator() 536 c.Assert(err, gc.IsNil) 537 cons := constraints.MustParse("arch=amd64 tags=foo") 538 unsupported, err := validator.Validate(cons) 539 c.Assert(err, gc.IsNil) 540 c.Assert(unsupported, gc.DeepEquals, []string{"tags"}) 541 } 542 543 func (t *localServerSuite) TestConstraintsValidatorVocab(c *gc.C) { 544 env := t.Prepare(c) 545 validator, err := env.ConstraintsValidator() 546 c.Assert(err, gc.IsNil) 547 cons := constraints.MustParse("arch=ppc64") 548 _, err = validator.Validate(cons) 549 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64\nvalid values are:.*") 550 cons = constraints.MustParse("instance-type=foo") 551 _, err = validator.Validate(cons) 552 c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*") 553 } 554 555 func (t *localServerSuite) TestConstraintsMerge(c *gc.C) { 556 env := t.Prepare(c) 557 validator, err := env.ConstraintsValidator() 558 c.Assert(err, gc.IsNil) 559 consA := constraints.MustParse("arch=amd64 mem=1G cpu-power=10 cpu-cores=2 tags=bar") 560 consB := constraints.MustParse("arch=i386 instance-type=m1.small") 561 cons, err := validator.Merge(consA, consB) 562 c.Assert(err, gc.IsNil) 563 c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=i386 instance-type=m1.small tags=bar")) 564 } 565 566 func (t *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { 567 env := t.Prepare(c) 568 cons := constraints.MustParse("instance-type=m1.small root-disk=1G") 569 placement := "" 570 err := env.PrecheckInstance("precise", cons, placement) 571 c.Assert(err, gc.IsNil) 572 } 573 574 func (t *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { 575 env := t.Prepare(c) 576 cons := constraints.MustParse("instance-type=m1.invalid") 577 placement := "" 578 err := env.PrecheckInstance("precise", cons, placement) 579 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "m1.invalid" specified`) 580 } 581 582 func (t *localServerSuite) TestPrecheckInstanceUnsupportedArch(c *gc.C) { 583 env := t.Prepare(c) 584 cons := constraints.MustParse("instance-type=cc1.4xlarge arch=i386") 585 placement := "" 586 err := env.PrecheckInstance("precise", cons, placement) 587 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "cc1.4xlarge" and arch "i386" specified`) 588 } 589 590 func (t *localServerSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 591 env := t.Prepare(c) 592 placement := "zone=test-available" 593 err := env.PrecheckInstance("precise", constraints.Value{}, placement) 594 c.Assert(err, gc.IsNil) 595 } 596 597 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnavailable(c *gc.C) { 598 env := t.Prepare(c) 599 placement := "zone=test-unavailable" 600 err := env.PrecheckInstance("precise", constraints.Value{}, placement) 601 c.Assert(err, gc.IsNil) 602 } 603 604 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 605 env := t.Prepare(c) 606 placement := "zone=test-unknown" 607 err := env.PrecheckInstance("precise", constraints.Value{}, placement) 608 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 609 } 610 611 func (t *localServerSuite) TestValidateImageMetadata(c *gc.C) { 612 env := t.Prepare(c) 613 params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("test") 614 c.Assert(err, gc.IsNil) 615 params.Series = "precise" 616 params.Endpoint = "https://ec2.endpoint.com" 617 params.Sources, err = imagemetadata.GetMetadataSources(env) 618 c.Assert(err, gc.IsNil) 619 image_ids, _, err := imagemetadata.ValidateImageMetadata(params) 620 c.Assert(err, gc.IsNil) 621 sort.Strings(image_ids) 622 c.Assert(image_ids, gc.DeepEquals, []string{"ami-00000033", "ami-00000034", "ami-00000035"}) 623 } 624 625 func (t *localServerSuite) TestGetToolsMetadataSources(c *gc.C) { 626 env := t.Prepare(c) 627 sources, err := tools.GetMetadataSources(env) 628 c.Assert(err, gc.IsNil) 629 c.Assert(len(sources), gc.Equals, 1) 630 url, err := sources[0].URL("") 631 // The control bucket URL contains the bucket name. 632 c.Assert(strings.Contains(url, ec2.ControlBucketName(env)+"/tools"), jc.IsTrue) 633 } 634 635 func (t *localServerSuite) TestSupportedArchitectures(c *gc.C) { 636 env := t.Prepare(c) 637 a, err := env.SupportedArchitectures() 638 c.Assert(err, gc.IsNil) 639 c.Assert(a, jc.SameContents, []string{"amd64", "i386"}) 640 } 641 642 func (t *localServerSuite) TestSupportNetworks(c *gc.C) { 643 env := t.Prepare(c) 644 c.Assert(env.SupportNetworks(), jc.IsFalse) 645 } 646 647 // localNonUSEastSuite is similar to localServerSuite but the S3 mock server 648 // behaves as if it is not in the us-east region. 649 type localNonUSEastSuite struct { 650 coretesting.BaseSuite 651 restoreEC2Patching func() 652 srv localServer 653 env environs.Environ 654 } 655 656 func (t *localNonUSEastSuite) SetUpSuite(c *gc.C) { 657 t.BaseSuite.SetUpSuite(c) 658 t.restoreEC2Patching = patchEC2ForTesting() 659 } 660 661 func (t *localNonUSEastSuite) TearDownSuite(c *gc.C) { 662 t.restoreEC2Patching() 663 t.BaseSuite.TearDownSuite(c) 664 } 665 666 func (t *localNonUSEastSuite) SetUpTest(c *gc.C) { 667 t.BaseSuite.SetUpTest(c) 668 t.srv.config = &s3test.Config{ 669 Send409Conflict: true, 670 } 671 t.srv.startServer(c) 672 673 cfg, err := config.New(config.NoDefaults, localConfigAttrs) 674 c.Assert(err, gc.IsNil) 675 env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) 676 c.Assert(err, gc.IsNil) 677 t.env = env 678 } 679 680 func (t *localNonUSEastSuite) TearDownTest(c *gc.C) { 681 t.srv.stopServer(c) 682 t.BaseSuite.TearDownTest(c) 683 } 684 685 func patchEC2ForTesting() func() { 686 ec2.UseTestImageData(ec2.TestImagesData) 687 ec2.UseTestInstanceTypeData(ec2.TestInstanceTypeCosts) 688 ec2.UseTestRegionData(ec2.TestRegions) 689 restoreTimeouts := envtesting.PatchAttemptStrategies(ec2.ShortAttempt, ec2.StorageAttempt) 690 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 691 return func() { 692 restoreFinishBootstrap() 693 restoreTimeouts() 694 ec2.UseTestImageData(nil) 695 ec2.UseTestInstanceTypeData(nil) 696 ec2.UseTestRegionData(nil) 697 } 698 } 699 700 // If match is true, CheckScripts checks that at least one script started 701 // by the cloudinit data matches the given regexp pattern, otherwise it 702 // checks that no script matches. It's exported so it can be used by tests 703 // defined in ec2_test. 704 func CheckScripts(c *gc.C, userDataMap map[interface{}]interface{}, pattern string, match bool) { 705 scripts0 := userDataMap["runcmd"] 706 if scripts0 == nil { 707 c.Errorf("cloudinit has no entry for runcmd") 708 return 709 } 710 scripts := scripts0.([]interface{}) 711 re := regexp.MustCompile(pattern) 712 found := false 713 for _, s0 := range scripts { 714 s := s0.(string) 715 if re.MatchString(s) { 716 found = true 717 } 718 } 719 switch { 720 case match && !found: 721 c.Errorf("script %q not found in %q", pattern, scripts) 722 case !match && found: 723 c.Errorf("script %q found but not expected in %q", pattern, scripts) 724 } 725 } 726 727 // CheckPackage checks that the cloudinit will or won't install the given 728 // package, depending on the value of match. It's exported so it can be 729 // used by tests defined outside the ec2 package. 730 func CheckPackage(c *gc.C, userDataMap map[interface{}]interface{}, pkg string, match bool) { 731 pkgs0 := userDataMap["packages"] 732 if pkgs0 == nil { 733 if match { 734 c.Errorf("cloudinit has no entry for packages") 735 } 736 return 737 } 738 739 pkgs := pkgs0.([]interface{}) 740 741 found := false 742 for _, p0 := range pkgs { 743 p := p0.(string) 744 if p == pkg { 745 found = true 746 } 747 } 748 switch { 749 case match && !found: 750 c.Errorf("package %q not found in %v", pkg, pkgs) 751 case !match && found: 752 c.Errorf("%q found but not expected in %v", pkg, pkgs) 753 } 754 }