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