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  }