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, &params)
   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  }