github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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 "launchpad.net/goamz/aws" 13 amzec2 "launchpad.net/goamz/ec2" 14 "launchpad.net/goamz/ec2/ec2test" 15 "launchpad.net/goamz/s3" 16 "launchpad.net/goamz/s3/s3test" 17 gc "launchpad.net/gocheck" 18 "launchpad.net/goyaml" 19 20 "launchpad.net/juju-core/constraints" 21 "launchpad.net/juju-core/environs" 22 "launchpad.net/juju-core/environs/bootstrap" 23 "launchpad.net/juju-core/environs/config" 24 "launchpad.net/juju-core/environs/configstore" 25 "launchpad.net/juju-core/environs/imagemetadata" 26 "launchpad.net/juju-core/environs/jujutest" 27 "launchpad.net/juju-core/environs/simplestreams" 28 envtesting "launchpad.net/juju-core/environs/testing" 29 "launchpad.net/juju-core/environs/tools" 30 "launchpad.net/juju-core/instance" 31 "launchpad.net/juju-core/juju/testing" 32 "launchpad.net/juju-core/provider/ec2" 33 coretesting "launchpad.net/juju-core/testing" 34 jc "launchpad.net/juju-core/testing/checkers" 35 "launchpad.net/juju-core/testing/testbase" 36 "launchpad.net/juju-core/utils" 37 "launchpad.net/juju-core/utils/ssh" 38 "launchpad.net/juju-core/version" 39 ) 40 41 type ProviderSuite struct { 42 testbase.LoggingSuite 43 } 44 45 var _ = gc.Suite(&ProviderSuite{}) 46 47 func (s *ProviderSuite) TestMetadata(c *gc.C) { 48 metadataContent := map[string]string{ 49 "/2011-01-01/meta-data/public-hostname": "public.dummy.address.invalid", 50 "/2011-01-01/meta-data/local-hostname": "private.dummy.address.invalid", 51 } 52 ec2.UseTestMetadata(metadataContent) 53 defer ec2.UseTestMetadata(nil) 54 55 p, err := environs.Provider("ec2") 56 c.Assert(err, gc.IsNil) 57 58 addr, err := p.PublicAddress() 59 c.Assert(err, gc.IsNil) 60 c.Assert(addr, gc.Equals, "public.dummy.address.invalid") 61 62 addr, err = p.PrivateAddress() 63 c.Assert(err, gc.IsNil) 64 c.Assert(addr, gc.Equals, "private.dummy.address.invalid") 65 } 66 67 func (t *ProviderSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) { 68 // Make an env configured with the stream. 69 envAttrs := localConfigAttrs 70 if stream != "" { 71 envAttrs = envAttrs.Merge(coretesting.Attrs{ 72 "image-stream": stream, 73 }) 74 } 75 cfg, err := config.New(config.NoDefaults, envAttrs) 76 c.Assert(err, gc.IsNil) 77 env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) 78 c.Assert(err, gc.IsNil) 79 c.Assert(env, gc.NotNil) 80 81 sources, err := imagemetadata.GetMetadataSources(env) 82 c.Assert(err, gc.IsNil) 83 c.Assert(len(sources), gc.Equals, 2) 84 var urls = make([]string, len(sources)) 85 for i, source := range sources { 86 url, err := source.URL("") 87 c.Assert(err, gc.IsNil) 88 urls[i] = url 89 } 90 // The control bucket URL contains the bucket name. 91 c.Check(strings.Contains(urls[0], ec2.ControlBucketName(env)+"/images"), jc.IsTrue) 92 c.Assert(urls[1], gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath)) 93 } 94 95 func (t *ProviderSuite) TestGetImageMetadataSources(c *gc.C) { 96 t.assertGetImageMetadataSources(c, "", "releases") 97 t.assertGetImageMetadataSources(c, "released", "releases") 98 t.assertGetImageMetadataSources(c, "daily", "daily") 99 } 100 101 var localConfigAttrs = coretesting.FakeConfig().Merge(coretesting.Attrs{ 102 "name": "sample", 103 "type": "ec2", 104 "region": "test", 105 "control-bucket": "test-bucket", 106 "access-key": "x", 107 "secret-key": "x", 108 "agent-version": version.Current.Number.String(), 109 }) 110 111 func registerLocalTests() { 112 // N.B. Make sure the region we use here 113 // has entries in the images/query txt files. 114 aws.Regions["test"] = aws.Region{ 115 Name: "test", 116 } 117 118 gc.Suite(&localServerSuite{}) 119 gc.Suite(&localLiveSuite{}) 120 gc.Suite(&localNonUSEastSuite{}) 121 } 122 123 // localLiveSuite runs tests from LiveTests using a fake 124 // EC2 server that runs within the test process itself. 125 type localLiveSuite struct { 126 LiveTests 127 srv localServer 128 restoreEC2Patching func() 129 } 130 131 func (t *localLiveSuite) SetUpSuite(c *gc.C) { 132 t.TestConfig = localConfigAttrs 133 t.restoreEC2Patching = patchEC2ForTesting() 134 t.srv.startServer(c) 135 t.LiveTests.SetUpSuite(c) 136 } 137 138 func (t *localLiveSuite) TearDownSuite(c *gc.C) { 139 t.LiveTests.TearDownSuite(c) 140 t.srv.stopServer(c) 141 t.restoreEC2Patching() 142 } 143 144 func (t *LiveTests) TestStartInstanceOnUnknownPlatform(c *gc.C) { 145 c.Skip("broken under ec2 - see https://bugs.launchpad.net/juju-core/+bug/1233278") 146 } 147 148 // localServer represents a fake EC2 server running within 149 // the test process itself. 150 type localServer struct { 151 ec2srv *ec2test.Server 152 s3srv *s3test.Server 153 config *s3test.Config 154 } 155 156 func (srv *localServer) startServer(c *gc.C) { 157 var err error 158 srv.ec2srv, err = ec2test.NewServer() 159 if err != nil { 160 c.Fatalf("cannot start ec2 test server: %v", err) 161 } 162 srv.s3srv, err = s3test.NewServer(srv.config) 163 if err != nil { 164 c.Fatalf("cannot start s3 test server: %v", err) 165 } 166 aws.Regions["test"] = aws.Region{ 167 Name: "test", 168 EC2Endpoint: srv.ec2srv.URL(), 169 S3Endpoint: srv.s3srv.URL(), 170 S3LocationConstraint: true, 171 } 172 s3inst := s3.New(aws.Auth{}, aws.Regions["test"]) 173 storage := ec2.BucketStorage(s3inst.Bucket("juju-dist")) 174 envtesting.UploadFakeTools(c, storage) 175 srv.addSpice(c) 176 } 177 178 // addSpice adds some "spice" to the local server 179 // by adding state that may cause tests to fail. 180 func (srv *localServer) addSpice(c *gc.C) { 181 states := []amzec2.InstanceState{ 182 ec2test.ShuttingDown, 183 ec2test.Terminated, 184 ec2test.Stopped, 185 } 186 for _, state := range states { 187 srv.ec2srv.NewInstances(1, "m1.small", "ami-a7f539ce", state, nil) 188 } 189 } 190 191 func (srv *localServer) stopServer(c *gc.C) { 192 srv.ec2srv.Quit() 193 srv.s3srv.Quit() 194 // Clear out the region because the server address is 195 // no longer valid. 196 delete(aws.Regions, "test") 197 } 198 199 // localServerSuite contains tests that run against a fake EC2 server 200 // running within the test process itself. These tests can test things that 201 // would be unreasonably slow or expensive to test on a live Amazon server. 202 // It starts a new local ec2test server for each test. The server is 203 // accessed by using the "test" region, which is changed to point to the 204 // network address of the local server. 205 type localServerSuite struct { 206 jujutest.Tests 207 srv localServer 208 restoreEC2Patching func() 209 } 210 211 func (t *localServerSuite) SetUpSuite(c *gc.C) { 212 t.TestConfig = localConfigAttrs 213 t.restoreEC2Patching = patchEC2ForTesting() 214 t.Tests.SetUpSuite(c) 215 } 216 217 func (t *localServerSuite) TearDownSuite(c *gc.C) { 218 t.Tests.TearDownSuite(c) 219 t.restoreEC2Patching() 220 } 221 222 func (t *localServerSuite) SetUpTest(c *gc.C) { 223 t.srv.startServer(c) 224 t.Tests.SetUpTest(c) 225 } 226 227 func (t *localServerSuite) TearDownTest(c *gc.C) { 228 t.Tests.TearDownTest(c) 229 t.srv.stopServer(c) 230 } 231 232 func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { 233 env := t.Prepare(c) 234 envtesting.UploadFakeTools(c, env.Storage()) 235 err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) 236 c.Assert(err, gc.IsNil) 237 238 // check that the state holds the id of the bootstrap machine. 239 bootstrapState, err := bootstrap.LoadState(env.Storage()) 240 c.Assert(err, gc.IsNil) 241 c.Assert(bootstrapState.StateInstances, gc.HasLen, 1) 242 243 expectedHardware := instance.MustParseHardware("arch=amd64 cpu-cores=1 cpu-power=100 mem=1740M root-disk=8192M") 244 insts, err := env.AllInstances() 245 c.Assert(err, gc.IsNil) 246 c.Assert(insts, gc.HasLen, 1) 247 c.Check(insts[0].Id(), gc.Equals, bootstrapState.StateInstances[0]) 248 c.Check(expectedHardware, gc.DeepEquals, bootstrapState.Characteristics[0]) 249 250 // check that the user data is configured to start zookeeper 251 // and the machine and provisioning agents. 252 // check that the user data is configured to only configure 253 // authorized SSH keys and set the log output; everything 254 // else happens after the machine is brought up. 255 inst := t.srv.ec2srv.Instance(string(insts[0].Id())) 256 c.Assert(inst, gc.NotNil) 257 bootstrapDNS, err := insts[0].DNSName() 258 c.Assert(err, gc.IsNil) 259 c.Assert(bootstrapDNS, gc.Not(gc.Equals), "") 260 userData, err := utils.Gunzip(inst.UserData) 261 c.Assert(err, gc.IsNil) 262 c.Logf("first instance: UserData: %q", userData) 263 var userDataMap map[interface{}]interface{} 264 err = goyaml.Unmarshal(userData, &userDataMap) 265 c.Assert(err, gc.IsNil) 266 c.Assert(userDataMap, jc.DeepEquals, map[interface{}]interface{}{ 267 "output": map[interface{}]interface{}{ 268 "all": "| tee -a /var/log/cloud-init-output.log", 269 }, 270 "ssh_authorized_keys": splitAuthKeys(env.Config().AuthorizedKeys()), 271 "runcmd": []interface{}{ 272 "set -xe", 273 "install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'", 274 "printf '%s\\n' 'user-admin:bootstrap' > '/var/lib/juju/nonce.txt'", 275 }, 276 }) 277 278 // check that a new instance will be started with a machine agent 279 inst1, hc := testing.AssertStartInstance(c, env, "1") 280 c.Check(*hc.Arch, gc.Equals, "amd64") 281 c.Check(*hc.Mem, gc.Equals, uint64(1740)) 282 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 283 c.Assert(*hc.CpuPower, gc.Equals, uint64(100)) 284 inst = t.srv.ec2srv.Instance(string(inst1.Id())) 285 c.Assert(inst, gc.NotNil) 286 userData, err = utils.Gunzip(inst.UserData) 287 c.Assert(err, gc.IsNil) 288 c.Logf("second instance: UserData: %q", userData) 289 userDataMap = nil 290 err = goyaml.Unmarshal(userData, &userDataMap) 291 c.Assert(err, gc.IsNil) 292 CheckPackage(c, userDataMap, "git", true) 293 CheckPackage(c, userDataMap, "mongodb-server", false) 294 CheckScripts(c, userDataMap, "jujud bootstrap-state", false) 295 CheckScripts(c, userDataMap, "/var/lib/juju/agents/machine-1/agent.conf", true) 296 // TODO check for provisioning agent 297 298 err = env.Destroy() 299 c.Assert(err, gc.IsNil) 300 301 _, err = bootstrap.LoadState(env.Storage()) 302 c.Assert(err, gc.NotNil) 303 } 304 305 // splitAuthKeys splits the given authorized keys 306 // into the form expected to be found in the 307 // user data. 308 func splitAuthKeys(keys string) []interface{} { 309 slines := strings.FieldsFunc(keys, func(r rune) bool { 310 return r == '\n' 311 }) 312 var lines []interface{} 313 for _, line := range slines { 314 lines = append(lines, ssh.EnsureJujuComment(strings.TrimSpace(line))) 315 } 316 return lines 317 } 318 319 func (t *localServerSuite) TestInstanceStatus(c *gc.C) { 320 env := t.Prepare(c) 321 envtesting.UploadFakeTools(c, env.Storage()) 322 err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) 323 c.Assert(err, gc.IsNil) 324 t.srv.ec2srv.SetInitialInstanceState(ec2test.Terminated) 325 inst, _ := testing.AssertStartInstance(c, env, "1") 326 c.Assert(err, gc.IsNil) 327 c.Assert(inst.Status(), gc.Equals, "terminated") 328 } 329 330 func (t *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { 331 env := t.Prepare(c) 332 envtesting.UploadFakeTools(c, env.Storage()) 333 err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) 334 c.Assert(err, gc.IsNil) 335 _, hc := testing.AssertStartInstance(c, env, "1") 336 c.Check(*hc.Arch, gc.Equals, "amd64") 337 c.Check(*hc.Mem, gc.Equals, uint64(1740)) 338 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 339 c.Assert(*hc.CpuPower, gc.Equals, uint64(100)) 340 } 341 342 func (t *localServerSuite) TestAddresses(c *gc.C) { 343 env := t.Prepare(c) 344 envtesting.UploadFakeTools(c, env.Storage()) 345 err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) 346 c.Assert(err, gc.IsNil) 347 inst, _ := testing.AssertStartInstance(c, env, "1") 348 c.Assert(err, gc.IsNil) 349 addrs, err := inst.Addresses() 350 c.Assert(err, gc.IsNil) 351 // Expected values use Address type but really contain a regexp for 352 // the value rather than a valid ip or hostname. 353 expected := []instance.Address{{ 354 Value: "*.testing.invalid", 355 Type: instance.HostName, 356 NetworkScope: instance.NetworkPublic, 357 }, { 358 Value: "*.internal.invalid", 359 Type: instance.HostName, 360 NetworkScope: instance.NetworkCloudLocal, 361 }, { 362 Value: "8.0.0.*", 363 Type: instance.Ipv4Address, 364 NetworkScope: instance.NetworkPublic, 365 }, { 366 Value: "127.0.0.*", 367 Type: instance.Ipv4Address, 368 NetworkScope: instance.NetworkCloudLocal, 369 }} 370 c.Assert(addrs, gc.HasLen, len(expected)) 371 for i, addr := range addrs { 372 c.Check(addr.Value, gc.Matches, expected[i].Value) 373 c.Check(addr.Type, gc.Equals, expected[i].Type) 374 c.Check(addr.NetworkScope, gc.Equals, expected[i].NetworkScope) 375 } 376 } 377 378 func (t *localServerSuite) TestValidateImageMetadata(c *gc.C) { 379 env := t.Prepare(c) 380 params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("test") 381 c.Assert(err, gc.IsNil) 382 params.Series = "precise" 383 params.Endpoint = "https://ec2.endpoint.com" 384 params.Sources, err = imagemetadata.GetMetadataSources(env) 385 c.Assert(err, gc.IsNil) 386 image_ids, _, err := imagemetadata.ValidateImageMetadata(params) 387 c.Assert(err, gc.IsNil) 388 sort.Strings(image_ids) 389 c.Assert(image_ids, gc.DeepEquals, []string{"ami-00000033", "ami-00000034", "ami-00000035"}) 390 } 391 392 func (t *localServerSuite) TestGetToolsMetadataSources(c *gc.C) { 393 env := t.Prepare(c) 394 sources, err := tools.GetMetadataSources(env) 395 c.Assert(err, gc.IsNil) 396 c.Assert(len(sources), gc.Equals, 1) 397 url, err := sources[0].URL("") 398 // The control bucket URL contains the bucket name. 399 c.Assert(strings.Contains(url, ec2.ControlBucketName(env)+"/tools"), jc.IsTrue) 400 } 401 402 // localNonUSEastSuite is similar to localServerSuite but the S3 mock server 403 // behaves as if it is not in the us-east region. 404 type localNonUSEastSuite struct { 405 testbase.LoggingSuite 406 restoreEC2Patching func() 407 srv localServer 408 env environs.Environ 409 } 410 411 func (t *localNonUSEastSuite) SetUpSuite(c *gc.C) { 412 t.LoggingSuite.SetUpSuite(c) 413 t.restoreEC2Patching = patchEC2ForTesting() 414 } 415 416 func (t *localNonUSEastSuite) TearDownSuite(c *gc.C) { 417 t.restoreEC2Patching() 418 t.LoggingSuite.TearDownSuite(c) 419 } 420 421 func (t *localNonUSEastSuite) SetUpTest(c *gc.C) { 422 t.LoggingSuite.SetUpTest(c) 423 t.srv.config = &s3test.Config{ 424 Send409Conflict: true, 425 } 426 t.srv.startServer(c) 427 428 cfg, err := config.New(config.NoDefaults, localConfigAttrs) 429 c.Assert(err, gc.IsNil) 430 env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) 431 c.Assert(err, gc.IsNil) 432 t.env = env 433 } 434 435 func (t *localNonUSEastSuite) TearDownTest(c *gc.C) { 436 t.srv.stopServer(c) 437 t.LoggingSuite.TearDownTest(c) 438 } 439 440 func patchEC2ForTesting() func() { 441 ec2.UseTestImageData(ec2.TestImagesData) 442 ec2.UseTestInstanceTypeData(ec2.TestInstanceTypeCosts) 443 ec2.UseTestRegionData(ec2.TestRegions) 444 restoreTimeouts := envtesting.PatchAttemptStrategies(ec2.ShortAttempt, ec2.StorageAttempt) 445 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 446 return func() { 447 restoreFinishBootstrap() 448 restoreTimeouts() 449 ec2.UseTestImageData(nil) 450 ec2.UseTestInstanceTypeData(nil) 451 ec2.UseTestRegionData(nil) 452 } 453 } 454 455 // If match is true, CheckScripts checks that at least one script started 456 // by the cloudinit data matches the given regexp pattern, otherwise it 457 // checks that no script matches. It's exported so it can be used by tests 458 // defined in ec2_test. 459 func CheckScripts(c *gc.C, userDataMap map[interface{}]interface{}, pattern string, match bool) { 460 scripts0 := userDataMap["runcmd"] 461 if scripts0 == nil { 462 c.Errorf("cloudinit has no entry for runcmd") 463 return 464 } 465 scripts := scripts0.([]interface{}) 466 re := regexp.MustCompile(pattern) 467 found := false 468 for _, s0 := range scripts { 469 s := s0.(string) 470 if re.MatchString(s) { 471 found = true 472 } 473 } 474 switch { 475 case match && !found: 476 c.Errorf("script %q not found in %q", pattern, scripts) 477 case !match && found: 478 c.Errorf("script %q found but not expected in %q", pattern, scripts) 479 } 480 } 481 482 // CheckPackage checks that the cloudinit will or won't install the given 483 // package, depending on the value of match. It's exported so it can be 484 // used by tests defined outside the ec2 package. 485 func CheckPackage(c *gc.C, userDataMap map[interface{}]interface{}, pkg string, match bool) { 486 pkgs0 := userDataMap["packages"] 487 if pkgs0 == nil { 488 if match { 489 c.Errorf("cloudinit has no entry for packages") 490 } 491 return 492 } 493 494 pkgs := pkgs0.([]interface{}) 495 found := false 496 for _, p0 := range pkgs { 497 p := p0.(string) 498 reMatch, _ := regexp.MatchString(pkg, p) 499 if reMatch { 500 found = true 501 } 502 } 503 504 switch { 505 case match && !found: 506 c.Errorf("package %q not found in %v", pkg, pkgs) 507 case !match && found: 508 c.Errorf("%q found but not expected in %v", pkg, pkgs) 509 } 510 }