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