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