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