github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/provider/ec2/live_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 "crypto/rand" 8 "fmt" 9 "io" 10 "strings" 11 12 jc "github.com/juju/testing/checkers" 13 amzec2 "launchpad.net/goamz/ec2" 14 gc "launchpad.net/gocheck" 15 16 "github.com/juju/juju/constraints" 17 "github.com/juju/juju/environs" 18 "github.com/juju/juju/environs/config" 19 "github.com/juju/juju/environs/jujutest" 20 "github.com/juju/juju/environs/storage" 21 envtesting "github.com/juju/juju/environs/testing" 22 "github.com/juju/juju/instance" 23 "github.com/juju/juju/juju/arch" 24 "github.com/juju/juju/juju/testing" 25 jujutesting "github.com/juju/juju/juju/testing" 26 "github.com/juju/juju/provider/ec2" 27 coretesting "github.com/juju/juju/testing" 28 "github.com/juju/juju/version" 29 ) 30 31 // uniqueName is generated afresh for every test run, so that 32 // we are not polluted by previous test state. 33 var uniqueName = randomName() 34 35 func randomName() string { 36 buf := make([]byte, 8) 37 _, err := io.ReadFull(rand.Reader, buf) 38 if err != nil { 39 panic(fmt.Sprintf("error from crypto rand: %v", err)) 40 } 41 return fmt.Sprintf("%x", buf) 42 } 43 44 func registerAmazonTests() { 45 // The following attributes hold the environment configuration 46 // for running the amazon EC2 integration tests. 47 // 48 // This is missing keys for security reasons; set the following 49 // environment variables to make the Amazon testing work: 50 // access-key: $AWS_ACCESS_KEY_ID 51 // secret-key: $AWS_SECRET_ACCESS_KEY 52 attrs := coretesting.FakeConfig().Merge(map[string]interface{}{ 53 "name": "sample-" + uniqueName, 54 "type": "ec2", 55 "control-bucket": "juju-test-" + uniqueName, 56 "admin-secret": "for real", 57 "firewall-mode": config.FwInstance, 58 "agent-version": version.Current.Number.String(), 59 }) 60 gc.Suite(&LiveTests{ 61 LiveTests: jujutest.LiveTests{ 62 TestConfig: attrs, 63 Attempt: *ec2.ShortAttempt, 64 CanOpenState: true, 65 HasProvisioner: true, 66 }, 67 }) 68 } 69 70 // LiveTests contains tests that can be run against the Amazon servers. 71 // Each test runs using the same ec2 connection. 72 type LiveTests struct { 73 coretesting.BaseSuite 74 jujutest.LiveTests 75 } 76 77 func (t *LiveTests) SetUpSuite(c *gc.C) { 78 t.BaseSuite.SetUpSuite(c) 79 t.LiveTests.SetUpSuite(c) 80 // TODO: Share code from jujutest.LiveTests for creating environment 81 e, err := environs.NewFromAttrs(t.TestConfig) 82 c.Assert(err, gc.IsNil) 83 84 // Put some fake tools in place so that tests that are simply 85 // starting instances without any need to check if those instances 86 // are running will find them in the public bucket. 87 envtesting.UploadFakeTools(c, e.Storage()) 88 } 89 90 func (t *LiveTests) TearDownSuite(c *gc.C) { 91 if t.Env == nil { 92 // This can happen if SetUpSuite fails. 93 return 94 } 95 t.LiveTests.TearDownSuite(c) 96 t.BaseSuite.TearDownSuite(c) 97 } 98 99 func (t *LiveTests) SetUpTest(c *gc.C) { 100 t.BaseSuite.SetUpTest(c) 101 t.LiveTests.SetUpTest(c) 102 t.PatchValue(&version.Current, version.Binary{ 103 Number: version.Current.Number, 104 Series: coretesting.FakeDefaultSeries, 105 Arch: arch.AMD64, 106 }) 107 108 } 109 110 func (t *LiveTests) TearDownTest(c *gc.C) { 111 t.LiveTests.TearDownTest(c) 112 t.BaseSuite.TearDownTest(c) 113 } 114 115 // TODO(niemeyer): Looks like many of those tests should be moved to jujutest.LiveTests. 116 117 func (t *LiveTests) TestInstanceAttributes(c *gc.C) { 118 inst, hc := testing.AssertStartInstance(c, t.Env, "30") 119 defer t.Env.StopInstances(inst.Id()) 120 // Sanity check for hardware characteristics. 121 c.Assert(hc.Arch, gc.NotNil) 122 c.Assert(hc.Mem, gc.NotNil) 123 c.Assert(hc.RootDisk, gc.NotNil) 124 c.Assert(hc.CpuCores, gc.NotNil) 125 c.Assert(hc.CpuPower, gc.NotNil) 126 addresses, err := jujutesting.WaitInstanceAddresses(t.Env, inst.Id()) 127 // TODO(niemeyer): This assert sometimes fails with "no instances found" 128 c.Assert(err, gc.IsNil) 129 c.Assert(addresses, gc.Not(gc.HasLen), 0) 130 131 insts, err := t.Env.Instances([]instance.Id{inst.Id()}) 132 c.Assert(err, gc.IsNil) 133 c.Assert(len(insts), gc.Equals, 1) 134 135 ec2inst := ec2.InstanceEC2(insts[0]) 136 c.Assert(ec2inst.DNSName, gc.Equals, addresses[0].Value) 137 c.Assert(ec2inst.InstanceType, gc.Equals, "m1.small") 138 } 139 140 func (t *LiveTests) TestStartInstanceConstraints(c *gc.C) { 141 cons := constraints.MustParse("mem=2G") 142 inst, hc := testing.AssertStartInstanceWithConstraints(c, t.Env, "30", cons) 143 defer t.Env.StopInstances(inst.Id()) 144 ec2inst := ec2.InstanceEC2(inst) 145 c.Assert(ec2inst.InstanceType, gc.Equals, "m1.medium") 146 c.Assert(*hc.Arch, gc.Equals, "amd64") 147 c.Assert(*hc.Mem, gc.Equals, uint64(3840)) 148 c.Assert(*hc.RootDisk, gc.Equals, uint64(8192)) 149 c.Assert(*hc.CpuCores, gc.Equals, uint64(1)) 150 c.Assert(*hc.CpuPower, gc.Equals, uint64(200)) 151 } 152 153 func (t *LiveTests) TestInstanceGroups(c *gc.C) { 154 t.PrepareOnce(c) 155 ec2conn := ec2.EnvironEC2(t.Env) 156 157 groups := amzec2.SecurityGroupNames( 158 ec2.JujuGroupName(t.Env), 159 ec2.MachineGroupName(t.Env, "98"), 160 ec2.MachineGroupName(t.Env, "99"), 161 ) 162 info := make([]amzec2.SecurityGroupInfo, len(groups)) 163 164 // Create a group with the same name as the juju group 165 // but with different permissions, to check that it's deleted 166 // and recreated correctly. 167 oldJujuGroup := createGroup(c, ec2conn, groups[0].Name, "old juju group") 168 169 // Add two permissions: one is required and should be left alone; 170 // the other is not and should be deleted. 171 // N.B. this is unfortunately sensitive to the actual set of permissions used. 172 _, err := ec2conn.AuthorizeSecurityGroup(oldJujuGroup, 173 []amzec2.IPPerm{ 174 { 175 Protocol: "tcp", 176 FromPort: 22, 177 ToPort: 22, 178 SourceIPs: []string{"0.0.0.0/0"}, 179 }, 180 { 181 Protocol: "udp", 182 FromPort: 4321, 183 ToPort: 4322, 184 SourceIPs: []string{"3.4.5.6/32"}, 185 }, 186 }) 187 c.Assert(err, gc.IsNil) 188 189 inst0, _ := testing.AssertStartInstance(c, t.Env, "98") 190 defer t.Env.StopInstances(inst0.Id()) 191 192 // Create a same-named group for the second instance 193 // before starting it, to check that it's reused correctly. 194 oldMachineGroup := createGroup(c, ec2conn, groups[2].Name, "old machine group") 195 196 inst1, _ := testing.AssertStartInstance(c, t.Env, "99") 197 defer t.Env.StopInstances(inst1.Id()) 198 199 groupsResp, err := ec2conn.SecurityGroups(groups, nil) 200 c.Assert(err, gc.IsNil) 201 c.Assert(groupsResp.Groups, gc.HasLen, len(groups)) 202 203 // For each group, check that it exists and record its id. 204 for i, group := range groups { 205 found := false 206 for _, g := range groupsResp.Groups { 207 if g.Name == group.Name { 208 groups[i].Id = g.Id 209 info[i] = g 210 found = true 211 break 212 } 213 } 214 if !found { 215 c.Fatalf("group %q not found", group.Name) 216 } 217 } 218 219 // The old juju group should have been reused. 220 c.Check(groups[0].Id, gc.Equals, oldJujuGroup.Id) 221 222 // Check that it authorizes the correct ports and there 223 // are no extra permissions (in particular we are checking 224 // that the unneeded permission that we added earlier 225 // has been deleted). 226 perms := info[0].IPPerms 227 c.Assert(perms, gc.HasLen, 6) 228 checkPortAllowed(c, perms, 22) // SSH 229 checkPortAllowed(c, perms, coretesting.FakeConfig()["state-port"].(int)) 230 checkPortAllowed(c, perms, coretesting.FakeConfig()["api-port"].(int)) 231 checkSecurityGroupAllowed(c, perms, groups[0]) 232 233 // The old machine group should have been reused also. 234 c.Check(groups[2].Id, gc.Equals, oldMachineGroup.Id) 235 236 // Check that each instance is part of the correct groups. 237 resp, err := ec2conn.Instances([]string{string(inst0.Id()), string(inst1.Id())}, nil) 238 c.Assert(err, gc.IsNil) 239 c.Assert(resp.Reservations, gc.HasLen, 2) 240 for _, r := range resp.Reservations { 241 c.Assert(r.Instances, gc.HasLen, 1) 242 // each instance must be part of the general juju group. 243 inst := r.Instances[0] 244 msg := gc.Commentf("instance %#v", inst) 245 c.Assert(hasSecurityGroup(inst, groups[0]), gc.Equals, true, msg) 246 switch instance.Id(inst.InstanceId) { 247 case inst0.Id(): 248 c.Assert(hasSecurityGroup(inst, groups[1]), gc.Equals, true, msg) 249 c.Assert(hasSecurityGroup(inst, groups[2]), gc.Equals, false, msg) 250 case inst1.Id(): 251 c.Assert(hasSecurityGroup(inst, groups[2]), gc.Equals, true, msg) 252 c.Assert(hasSecurityGroup(inst, groups[1]), gc.Equals, false, msg) 253 default: 254 c.Errorf("unknown instance found: %v", inst) 255 } 256 } 257 258 // Check that listing those instances finds them using the groups 259 instIds := []instance.Id{inst0.Id(), inst1.Id()} 260 idsFromInsts := func(insts []instance.Instance) (ids []instance.Id) { 261 for _, inst := range insts { 262 ids = append(ids, inst.Id()) 263 } 264 return ids 265 } 266 insts, err := t.Env.Instances(instIds) 267 c.Assert(err, gc.IsNil) 268 c.Assert(instIds, jc.SameContents, idsFromInsts(insts)) 269 allInsts, err := t.Env.AllInstances() 270 c.Assert(err, gc.IsNil) 271 c.Assert(instIds, jc.SameContents, idsFromInsts(allInsts)) 272 } 273 274 func (t *LiveTests) TestDestroy(c *gc.C) { 275 s := t.Env.Storage() 276 err := s.Put("foo", strings.NewReader("foo"), 3) 277 c.Assert(err, gc.IsNil) 278 err = s.Put("bar", strings.NewReader("bar"), 3) 279 c.Assert(err, gc.IsNil) 280 281 // Check that the bucket exists, so we can be sure 282 // we have checked correctly that it's been destroyed. 283 names, err := storage.List(s, "") 284 c.Assert(err, gc.IsNil) 285 c.Assert(len(names) >= 2, gc.Equals, true) 286 287 t.Destroy(c) 288 for a := ec2.ShortAttempt.Start(); a.Next(); { 289 names, err = storage.List(s, "") 290 if len(names) == 0 { 291 break 292 } 293 } 294 c.Assert(names, gc.HasLen, 0) 295 } 296 297 func checkPortAllowed(c *gc.C, perms []amzec2.IPPerm, port int) { 298 for _, perm := range perms { 299 if perm.FromPort == port { 300 c.Check(perm.Protocol, gc.Equals, "tcp") 301 c.Check(perm.ToPort, gc.Equals, port) 302 c.Check(perm.SourceIPs, gc.DeepEquals, []string{"0.0.0.0/0"}) 303 c.Check(perm.SourceGroups, gc.HasLen, 0) 304 return 305 } 306 } 307 c.Errorf("ip port permission not found for %d in %#v", port, perms) 308 } 309 310 func checkSecurityGroupAllowed(c *gc.C, perms []amzec2.IPPerm, g amzec2.SecurityGroup) { 311 protos := map[string]struct { 312 fromPort int 313 toPort int 314 }{ 315 "tcp": {0, 65535}, 316 "udp": {0, 65535}, 317 "icmp": {-1, -1}, 318 } 319 for _, perm := range perms { 320 if len(perm.SourceGroups) > 0 { 321 c.Check(perm.SourceGroups, gc.HasLen, 1) 322 c.Check(perm.SourceGroups[0].Id, gc.Equals, g.Id) 323 ports, ok := protos[perm.Protocol] 324 if !ok { 325 c.Errorf("unexpected protocol in security group: %q", perm.Protocol) 326 continue 327 } 328 delete(protos, perm.Protocol) 329 c.Check(perm.FromPort, gc.Equals, ports.fromPort) 330 c.Check(perm.ToPort, gc.Equals, ports.toPort) 331 } 332 } 333 if len(protos) > 0 { 334 c.Errorf("%d security group permission not found for %#v in %#v", len(protos), g, perms) 335 } 336 } 337 338 func (t *LiveTests) TestStopInstances(c *gc.C) { 339 // It would be nice if this test was in jujutest, but 340 // there's no way for jujutest to fabricate a valid-looking 341 // instance id. 342 inst0, _ := testing.AssertStartInstance(c, t.Env, "40") 343 inst1 := ec2.FabricateInstance(inst0, "i-aaaaaaaa") 344 inst2, _ := testing.AssertStartInstance(c, t.Env, "41") 345 346 err := t.Env.StopInstances(inst0.Id(), inst1.Id(), inst2.Id()) 347 c.Check(err, gc.IsNil) 348 349 var insts []instance.Instance 350 351 // We need the retry logic here because we are waiting 352 // for Instances to return an error, and it will not retry 353 // if it succeeds. 354 gone := false 355 for a := ec2.ShortAttempt.Start(); a.Next(); { 356 insts, err = t.Env.Instances([]instance.Id{inst0.Id(), inst2.Id()}) 357 if err == environs.ErrPartialInstances { 358 // instances not gone yet. 359 continue 360 } 361 if err == environs.ErrNoInstances { 362 gone = true 363 break 364 } 365 c.Fatalf("error getting instances: %v", err) 366 } 367 if !gone { 368 c.Errorf("after termination, instances remaining: %v", insts) 369 } 370 } 371 372 func (t *LiveTests) TestPutBucketOnlyOnce(c *gc.C) { 373 s3inst := ec2.EnvironS3(t.Env) 374 b := s3inst.Bucket("test-once-" + uniqueName) 375 s := ec2.BucketStorage(b) 376 377 // Check that we don't do a PutBucket every time by 378 // getting it to create the bucket, destroying the bucket behind 379 // the scenes, and trying to put another object, 380 // which should fail because it doesn't try to do 381 // the PutBucket again. 382 383 err := s.Put("test-object", strings.NewReader("test"), 4) 384 c.Assert(err, gc.IsNil) 385 386 err = s.Remove("test-object") 387 c.Assert(err, gc.IsNil) 388 389 err = ec2.DeleteBucket(s) 390 c.Assert(err, gc.IsNil) 391 392 err = s.Put("test-object", strings.NewReader("test"), 4) 393 c.Assert(err, gc.ErrorMatches, ".*The specified bucket does not exist") 394 } 395 396 // createGroup creates a new EC2 group and returns it. If it already exists, 397 // it revokes all its permissions and returns the existing group. 398 func createGroup(c *gc.C, ec2conn *amzec2.EC2, name, descr string) amzec2.SecurityGroup { 399 resp, err := ec2conn.CreateSecurityGroup(name, descr) 400 if err == nil { 401 return resp.SecurityGroup 402 } 403 if err.(*amzec2.Error).Code != "InvalidGroup.Duplicate" { 404 c.Fatalf("cannot make group %q: %v", name, err) 405 } 406 407 // Found duplicate group, so revoke its permissions and return it. 408 gresp, err := ec2conn.SecurityGroups(amzec2.SecurityGroupNames(name), nil) 409 c.Assert(err, gc.IsNil) 410 411 gi := gresp.Groups[0] 412 if len(gi.IPPerms) > 0 { 413 _, err = ec2conn.RevokeSecurityGroup(gi.SecurityGroup, gi.IPPerms) 414 c.Assert(err, gc.IsNil) 415 } 416 return gi.SecurityGroup 417 } 418 419 func hasSecurityGroup(inst amzec2.Instance, group amzec2.SecurityGroup) bool { 420 for _, instGroup := range inst.SecurityGroups { 421 if instGroup.Id == group.Id { 422 return true 423 } 424 } 425 return false 426 }