github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/provider/ec2/local_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  	"fmt"
     8  	"regexp"
     9  	"sort"
    10  	"strings"
    11  
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	"launchpad.net/goamz/aws"
    15  	amzec2 "launchpad.net/goamz/ec2"
    16  	"launchpad.net/goamz/ec2/ec2test"
    17  	"launchpad.net/goamz/s3"
    18  	"launchpad.net/goamz/s3/s3test"
    19  	gc "launchpad.net/gocheck"
    20  	"launchpad.net/goyaml"
    21  
    22  	"github.com/juju/juju/constraints"
    23  	"github.com/juju/juju/environs"
    24  	"github.com/juju/juju/environs/bootstrap"
    25  	"github.com/juju/juju/environs/config"
    26  	"github.com/juju/juju/environs/configstore"
    27  	"github.com/juju/juju/environs/imagemetadata"
    28  	"github.com/juju/juju/environs/jujutest"
    29  	"github.com/juju/juju/environs/simplestreams"
    30  	envtesting "github.com/juju/juju/environs/testing"
    31  	"github.com/juju/juju/environs/tools"
    32  	"github.com/juju/juju/instance"
    33  	"github.com/juju/juju/juju/arch"
    34  	"github.com/juju/juju/juju/testing"
    35  	"github.com/juju/juju/network"
    36  	"github.com/juju/juju/provider/common"
    37  	"github.com/juju/juju/provider/ec2"
    38  	coretesting "github.com/juju/juju/testing"
    39  	"github.com/juju/juju/utils/ssh"
    40  	"github.com/juju/juju/version"
    41  )
    42  
    43  type ProviderSuite struct {
    44  	coretesting.BaseSuite
    45  }
    46  
    47  var _ = gc.Suite(&ProviderSuite{})
    48  
    49  func (t *ProviderSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) {
    50  	// Make an env configured with the stream.
    51  	envAttrs := localConfigAttrs
    52  	if stream != "" {
    53  		envAttrs = envAttrs.Merge(coretesting.Attrs{
    54  			"image-stream": stream,
    55  		})
    56  	}
    57  	cfg, err := config.New(config.NoDefaults, envAttrs)
    58  	c.Assert(err, gc.IsNil)
    59  	env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem())
    60  	c.Assert(err, gc.IsNil)
    61  	c.Assert(env, gc.NotNil)
    62  
    63  	sources, err := imagemetadata.GetMetadataSources(env)
    64  	c.Assert(err, gc.IsNil)
    65  	c.Assert(len(sources), gc.Equals, 2)
    66  	var urls = make([]string, len(sources))
    67  	for i, source := range sources {
    68  		url, err := source.URL("")
    69  		c.Assert(err, gc.IsNil)
    70  		urls[i] = url
    71  	}
    72  	// The control bucket URL contains the bucket name.
    73  	c.Check(strings.Contains(urls[0], ec2.ControlBucketName(env)+"/images"), jc.IsTrue)
    74  	c.Assert(urls[1], gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath))
    75  }
    76  
    77  func (t *ProviderSuite) TestGetImageMetadataSources(c *gc.C) {
    78  	t.assertGetImageMetadataSources(c, "", "releases")
    79  	t.assertGetImageMetadataSources(c, "released", "releases")
    80  	t.assertGetImageMetadataSources(c, "daily", "daily")
    81  }
    82  
    83  var localConfigAttrs = coretesting.FakeConfig().Merge(coretesting.Attrs{
    84  	"name":           "sample",
    85  	"type":           "ec2",
    86  	"region":         "test",
    87  	"control-bucket": "test-bucket",
    88  	"access-key":     "x",
    89  	"secret-key":     "x",
    90  	"agent-version":  version.Current.Number.String(),
    91  })
    92  
    93  func registerLocalTests() {
    94  	// N.B. Make sure the region we use here
    95  	// has entries in the images/query txt files.
    96  	aws.Regions["test"] = aws.Region{
    97  		Name: "test",
    98  	}
    99  
   100  	gc.Suite(&localServerSuite{})
   101  	gc.Suite(&localLiveSuite{})
   102  	gc.Suite(&localNonUSEastSuite{})
   103  }
   104  
   105  // localLiveSuite runs tests from LiveTests using a fake
   106  // EC2 server that runs within the test process itself.
   107  type localLiveSuite struct {
   108  	LiveTests
   109  	srv                localServer
   110  	restoreEC2Patching func()
   111  }
   112  
   113  func (t *localLiveSuite) SetUpSuite(c *gc.C) {
   114  	t.TestConfig = localConfigAttrs
   115  	t.restoreEC2Patching = patchEC2ForTesting()
   116  	t.srv.startServer(c)
   117  	t.LiveTests.SetUpSuite(c)
   118  }
   119  
   120  func (t *localLiveSuite) TearDownSuite(c *gc.C) {
   121  	t.LiveTests.TearDownSuite(c)
   122  	t.srv.stopServer(c)
   123  	t.restoreEC2Patching()
   124  }
   125  
   126  // localServer represents a fake EC2 server running within
   127  // the test process itself.
   128  type localServer struct {
   129  	ec2srv *ec2test.Server
   130  	s3srv  *s3test.Server
   131  	config *s3test.Config
   132  }
   133  
   134  func (srv *localServer) startServer(c *gc.C) {
   135  	var err error
   136  	srv.ec2srv, err = ec2test.NewServer()
   137  	if err != nil {
   138  		c.Fatalf("cannot start ec2 test server: %v", err)
   139  	}
   140  	srv.s3srv, err = s3test.NewServer(srv.config)
   141  	if err != nil {
   142  		c.Fatalf("cannot start s3 test server: %v", err)
   143  	}
   144  	aws.Regions["test"] = aws.Region{
   145  		Name:                 "test",
   146  		EC2Endpoint:          srv.ec2srv.URL(),
   147  		S3Endpoint:           srv.s3srv.URL(),
   148  		S3LocationConstraint: true,
   149  	}
   150  	s3inst := s3.New(aws.Auth{}, aws.Regions["test"])
   151  	storage := ec2.BucketStorage(s3inst.Bucket("juju-dist"))
   152  	envtesting.UploadFakeTools(c, storage)
   153  	srv.addSpice(c)
   154  
   155  	zones := make([]amzec2.AvailabilityZoneInfo, 3)
   156  	zones[0].Region = "test"
   157  	zones[0].Name = "test-available"
   158  	zones[0].State = "available"
   159  	zones[1].Region = "test"
   160  	zones[1].Name = "test-impaired"
   161  	zones[1].State = "impaired"
   162  	zones[2].Region = "test"
   163  	zones[2].Name = "test-unavailable"
   164  	zones[2].State = "unavailable"
   165  	srv.ec2srv.SetAvailabilityZones(zones)
   166  }
   167  
   168  // addSpice adds some "spice" to the local server
   169  // by adding state that may cause tests to fail.
   170  func (srv *localServer) addSpice(c *gc.C) {
   171  	states := []amzec2.InstanceState{
   172  		ec2test.ShuttingDown,
   173  		ec2test.Terminated,
   174  		ec2test.Stopped,
   175  	}
   176  	for _, state := range states {
   177  		srv.ec2srv.NewInstances(1, "m1.small", "ami-a7f539ce", state, nil)
   178  	}
   179  }
   180  
   181  func (srv *localServer) stopServer(c *gc.C) {
   182  	srv.ec2srv.Quit()
   183  	srv.s3srv.Quit()
   184  	// Clear out the region because the server address is
   185  	// no longer valid.
   186  	delete(aws.Regions, "test")
   187  }
   188  
   189  // localServerSuite contains tests that run against a fake EC2 server
   190  // running within the test process itself.  These tests can test things that
   191  // would be unreasonably slow or expensive to test on a live Amazon server.
   192  // It starts a new local ec2test server for each test.  The server is
   193  // accessed by using the "test" region, which is changed to point to the
   194  // network address of the local server.
   195  type localServerSuite struct {
   196  	coretesting.BaseSuite
   197  	jujutest.Tests
   198  	srv                localServer
   199  	restoreEC2Patching func()
   200  }
   201  
   202  func (t *localServerSuite) SetUpSuite(c *gc.C) {
   203  	t.TestConfig = localConfigAttrs
   204  	t.restoreEC2Patching = patchEC2ForTesting()
   205  	t.BaseSuite.SetUpSuite(c)
   206  }
   207  
   208  func (t *localServerSuite) TearDownSuite(c *gc.C) {
   209  	t.BaseSuite.TearDownSuite(c)
   210  	t.restoreEC2Patching()
   211  }
   212  
   213  func (t *localServerSuite) SetUpTest(c *gc.C) {
   214  	t.BaseSuite.SetUpTest(c)
   215  	t.srv.startServer(c)
   216  	t.Tests.SetUpTest(c)
   217  	t.PatchValue(&version.Current, version.Binary{
   218  		Number: version.Current.Number,
   219  		Series: coretesting.FakeDefaultSeries,
   220  		Arch:   arch.AMD64,
   221  	})
   222  }
   223  
   224  func (t *localServerSuite) TearDownTest(c *gc.C) {
   225  	t.Tests.TearDownTest(c)
   226  	t.srv.stopServer(c)
   227  	t.BaseSuite.TearDownTest(c)
   228  }
   229  
   230  func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) {
   231  	env := t.Prepare(c)
   232  	envtesting.UploadFakeTools(c, env.Storage())
   233  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   234  	c.Assert(err, gc.IsNil)
   235  
   236  	// check that the state holds the id of the bootstrap machine.
   237  	bootstrapState, err := bootstrap.LoadState(env.Storage())
   238  	c.Assert(err, gc.IsNil)
   239  	c.Assert(bootstrapState.StateInstances, gc.HasLen, 1)
   240  
   241  	insts, err := env.AllInstances()
   242  	c.Assert(err, gc.IsNil)
   243  	c.Assert(insts, gc.HasLen, 1)
   244  	c.Check(insts[0].Id(), gc.Equals, bootstrapState.StateInstances[0])
   245  
   246  	// check that the user data is configured to start zookeeper
   247  	// and the machine and provisioning agents.
   248  	// check that the user data is configured to only configure
   249  	// authorized SSH keys and set the log output; everything
   250  	// else happens after the machine is brought up.
   251  	inst := t.srv.ec2srv.Instance(string(insts[0].Id()))
   252  	c.Assert(inst, gc.NotNil)
   253  	addresses, err := insts[0].Addresses()
   254  	c.Assert(err, gc.IsNil)
   255  	c.Assert(addresses, gc.Not(gc.HasLen), 0)
   256  	userData, err := utils.Gunzip(inst.UserData)
   257  	c.Assert(err, gc.IsNil)
   258  	c.Logf("first instance: UserData: %q", userData)
   259  	var userDataMap map[interface{}]interface{}
   260  	err = goyaml.Unmarshal(userData, &userDataMap)
   261  	c.Assert(err, gc.IsNil)
   262  	c.Assert(userDataMap, jc.DeepEquals, map[interface{}]interface{}{
   263  		"output": map[interface{}]interface{}{
   264  			"all": "| tee -a /var/log/cloud-init-output.log",
   265  		},
   266  		"ssh_authorized_keys": splitAuthKeys(env.Config().AuthorizedKeys()),
   267  		"runcmd": []interface{}{
   268  			"set -xe",
   269  			"install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'",
   270  			"printf '%s\\n' 'user-admin:bootstrap' > '/var/lib/juju/nonce.txt'",
   271  		},
   272  	})
   273  
   274  	// check that a new instance will be started with a machine agent
   275  	inst1, hc := testing.AssertStartInstance(c, env, "1")
   276  	c.Check(*hc.Arch, gc.Equals, "amd64")
   277  	c.Check(*hc.Mem, gc.Equals, uint64(1740))
   278  	c.Check(*hc.CpuCores, gc.Equals, uint64(1))
   279  	c.Assert(*hc.CpuPower, gc.Equals, uint64(100))
   280  	inst = t.srv.ec2srv.Instance(string(inst1.Id()))
   281  	c.Assert(inst, gc.NotNil)
   282  	userData, err = utils.Gunzip(inst.UserData)
   283  	c.Assert(err, gc.IsNil)
   284  	c.Logf("second instance: UserData: %q", userData)
   285  	userDataMap = nil
   286  	err = goyaml.Unmarshal(userData, &userDataMap)
   287  	c.Assert(err, gc.IsNil)
   288  	CheckPackage(c, userDataMap, "git", true)
   289  	CheckPackage(c, userDataMap, "mongodb-server", false)
   290  	CheckScripts(c, userDataMap, "jujud bootstrap-state", false)
   291  	CheckScripts(c, userDataMap, "/var/lib/juju/agents/machine-1/agent.conf", true)
   292  	// TODO check for provisioning agent
   293  
   294  	err = env.Destroy()
   295  	c.Assert(err, gc.IsNil)
   296  
   297  	_, err = bootstrap.LoadState(env.Storage())
   298  	c.Assert(err, gc.NotNil)
   299  }
   300  
   301  // splitAuthKeys splits the given authorized keys
   302  // into the form expected to be found in the
   303  // user data.
   304  func splitAuthKeys(keys string) []interface{} {
   305  	slines := strings.FieldsFunc(keys, func(r rune) bool {
   306  		return r == '\n'
   307  	})
   308  	var lines []interface{}
   309  	for _, line := range slines {
   310  		lines = append(lines, ssh.EnsureJujuComment(strings.TrimSpace(line)))
   311  	}
   312  	return lines
   313  }
   314  
   315  func (t *localServerSuite) TestInstanceStatus(c *gc.C) {
   316  	env := t.Prepare(c)
   317  	envtesting.UploadFakeTools(c, env.Storage())
   318  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   319  	c.Assert(err, gc.IsNil)
   320  	t.srv.ec2srv.SetInitialInstanceState(ec2test.Terminated)
   321  	inst, _ := testing.AssertStartInstance(c, env, "1")
   322  	c.Assert(err, gc.IsNil)
   323  	c.Assert(inst.Status(), gc.Equals, "terminated")
   324  }
   325  
   326  func (t *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) {
   327  	env := t.Prepare(c)
   328  	envtesting.UploadFakeTools(c, env.Storage())
   329  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   330  	c.Assert(err, gc.IsNil)
   331  	_, hc := testing.AssertStartInstance(c, env, "1")
   332  	c.Check(*hc.Arch, gc.Equals, "amd64")
   333  	c.Check(*hc.Mem, gc.Equals, uint64(1740))
   334  	c.Check(*hc.CpuCores, gc.Equals, uint64(1))
   335  	c.Assert(*hc.CpuPower, gc.Equals, uint64(100))
   336  }
   337  
   338  func (t *localServerSuite) TestStartInstanceAvailZone(c *gc.C) {
   339  	inst, err := t.testStartInstanceAvailZone(c, "test-available")
   340  	c.Assert(err, gc.IsNil)
   341  	c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available")
   342  }
   343  
   344  func (t *localServerSuite) TestStartInstanceAvailZoneImpaired(c *gc.C) {
   345  	_, err := t.testStartInstanceAvailZone(c, "test-impaired")
   346  	c.Assert(err, gc.ErrorMatches, `availability zone "test-impaired" is impaired`)
   347  }
   348  
   349  func (t *localServerSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) {
   350  	_, err := t.testStartInstanceAvailZone(c, "test-unknown")
   351  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`)
   352  }
   353  
   354  func (t *localServerSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) {
   355  	env := t.Prepare(c)
   356  	envtesting.UploadFakeTools(c, env.Storage())
   357  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   358  	c.Assert(err, gc.IsNil)
   359  
   360  	params := environs.StartInstanceParams{Placement: "zone=" + zone}
   361  	inst, _, _, err := testing.StartInstanceWithParams(env, "1", params, nil)
   362  	return inst, err
   363  }
   364  
   365  func (t *localServerSuite) TestGetAvailabilityZones(c *gc.C) {
   366  	var resultZones []amzec2.AvailabilityZoneInfo
   367  	var resultErr error
   368  	t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) {
   369  		resp := &amzec2.AvailabilityZonesResp{
   370  			Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...),
   371  		}
   372  		return resp, resultErr
   373  	})
   374  	env := t.Prepare(c).(common.ZonedEnviron)
   375  
   376  	resultErr = fmt.Errorf("failed to get availability zones")
   377  	zones, err := env.AvailabilityZones()
   378  	c.Assert(err, gc.Equals, resultErr)
   379  	c.Assert(zones, gc.IsNil)
   380  
   381  	resultErr = nil
   382  	resultZones = make([]amzec2.AvailabilityZoneInfo, 1)
   383  	resultZones[0].Name = "whatever"
   384  	zones, err = env.AvailabilityZones()
   385  	c.Assert(err, gc.IsNil)
   386  	c.Assert(zones, gc.HasLen, 1)
   387  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
   388  
   389  	// A successful result is cached, currently for the lifetime
   390  	// of the Environ. This will change if/when we have long-lived
   391  	// Environs to cut down repeated IaaS requests.
   392  	resultErr = fmt.Errorf("failed to get availability zones")
   393  	resultZones[0].Name = "andever"
   394  	zones, err = env.AvailabilityZones()
   395  	c.Assert(err, gc.IsNil)
   396  	c.Assert(zones, gc.HasLen, 1)
   397  	c.Assert(zones[0].Name(), gc.Equals, "whatever")
   398  }
   399  
   400  func (t *localServerSuite) TestGetAvailabilityZonesCommon(c *gc.C) {
   401  	var resultZones []amzec2.AvailabilityZoneInfo
   402  	t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) {
   403  		resp := &amzec2.AvailabilityZonesResp{
   404  			Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...),
   405  		}
   406  		return resp, nil
   407  	})
   408  	env := t.Prepare(c).(common.ZonedEnviron)
   409  	resultZones = make([]amzec2.AvailabilityZoneInfo, 2)
   410  	resultZones[0].Name = "az1"
   411  	resultZones[1].Name = "az2"
   412  	resultZones[0].State = "available"
   413  	resultZones[1].State = "impaired"
   414  	zones, err := env.AvailabilityZones()
   415  	c.Assert(err, gc.IsNil)
   416  	c.Assert(zones, gc.HasLen, 2)
   417  	c.Assert(zones[0].Name(), gc.Equals, resultZones[0].Name)
   418  	c.Assert(zones[1].Name(), gc.Equals, resultZones[1].Name)
   419  	c.Assert(zones[0].Available(), jc.IsTrue)
   420  	c.Assert(zones[1].Available(), jc.IsFalse)
   421  }
   422  
   423  type mockBestAvailabilityZoneAllocations struct {
   424  	group  []instance.Id // input param
   425  	result map[string][]instance.Id
   426  	err    error
   427  }
   428  
   429  func (t *mockBestAvailabilityZoneAllocations) BestAvailabilityZoneAllocations(
   430  	e common.ZonedEnviron, group []instance.Id,
   431  ) (map[string][]instance.Id, error) {
   432  	t.group = group
   433  	return t.result, t.err
   434  }
   435  
   436  func (t *localServerSuite) TestStartInstanceDistributionParams(c *gc.C) {
   437  	env := t.Prepare(c)
   438  	envtesting.UploadFakeTools(c, env.Storage())
   439  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   440  	c.Assert(err, gc.IsNil)
   441  
   442  	var mock mockBestAvailabilityZoneAllocations
   443  	t.PatchValue(ec2.BestAvailabilityZoneAllocations, mock.BestAvailabilityZoneAllocations)
   444  
   445  	// no distribution group specified
   446  	testing.AssertStartInstance(c, env, "1")
   447  	c.Assert(mock.group, gc.HasLen, 0)
   448  
   449  	// distribution group specified: ensure it's passed through to BestAvailabilityZone.
   450  	expectedInstances := []instance.Id{"i-0", "i-1"}
   451  	params := environs.StartInstanceParams{
   452  		DistributionGroup: func() ([]instance.Id, error) {
   453  			return expectedInstances, nil
   454  		},
   455  	}
   456  	_, _, _, err = testing.StartInstanceWithParams(env, "1", params, nil)
   457  	c.Assert(err, gc.IsNil)
   458  	c.Assert(mock.group, gc.DeepEquals, expectedInstances)
   459  }
   460  
   461  func (t *localServerSuite) TestStartInstanceDistributionErrors(c *gc.C) {
   462  	env := t.Prepare(c)
   463  	envtesting.UploadFakeTools(c, env.Storage())
   464  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   465  	c.Assert(err, gc.IsNil)
   466  
   467  	mock := mockBestAvailabilityZoneAllocations{
   468  		err: fmt.Errorf("BestAvailabilityZoneAllocations failed"),
   469  	}
   470  	t.PatchValue(ec2.BestAvailabilityZoneAllocations, mock.BestAvailabilityZoneAllocations)
   471  	_, _, _, err = testing.StartInstance(env, "1")
   472  	c.Assert(err, gc.Equals, mock.err)
   473  
   474  	mock.err = nil
   475  	dgErr := fmt.Errorf("DistributionGroup failed")
   476  	params := environs.StartInstanceParams{
   477  		DistributionGroup: func() ([]instance.Id, error) {
   478  			return nil, dgErr
   479  		},
   480  	}
   481  	_, _, _, err = testing.StartInstanceWithParams(env, "1", params, nil)
   482  	c.Assert(err, gc.Equals, dgErr)
   483  }
   484  
   485  func (t *localServerSuite) TestStartInstanceDistribution(c *gc.C) {
   486  	env := t.Prepare(c)
   487  	envtesting.UploadFakeTools(c, env.Storage())
   488  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   489  	c.Assert(err, gc.IsNil)
   490  
   491  	// test-available is the only available AZ, so BestAvailabilityZoneAllocations
   492  	// is guaranteed to return that.
   493  	inst, _ := testing.AssertStartInstance(c, env, "1")
   494  	c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available")
   495  }
   496  
   497  func (t *localServerSuite) TestAddresses(c *gc.C) {
   498  	env := t.Prepare(c)
   499  	envtesting.UploadFakeTools(c, env.Storage())
   500  	err := bootstrap.Bootstrap(coretesting.Context(c), env, environs.BootstrapParams{})
   501  	c.Assert(err, gc.IsNil)
   502  	inst, _ := testing.AssertStartInstance(c, env, "1")
   503  	c.Assert(err, gc.IsNil)
   504  	addrs, err := inst.Addresses()
   505  	c.Assert(err, gc.IsNil)
   506  	// Expected values use Address type but really contain a regexp for
   507  	// the value rather than a valid ip or hostname.
   508  	expected := []network.Address{{
   509  		Value: "*.testing.invalid",
   510  		Type:  network.HostName,
   511  		Scope: network.ScopePublic,
   512  	}, {
   513  		Value: "*.internal.invalid",
   514  		Type:  network.HostName,
   515  		Scope: network.ScopeCloudLocal,
   516  	}, {
   517  		Value: "8.0.0.*",
   518  		Type:  network.IPv4Address,
   519  		Scope: network.ScopePublic,
   520  	}, {
   521  		Value: "127.0.0.*",
   522  		Type:  network.IPv4Address,
   523  		Scope: network.ScopeCloudLocal,
   524  	}}
   525  	c.Assert(addrs, gc.HasLen, len(expected))
   526  	for i, addr := range addrs {
   527  		c.Check(addr.Value, gc.Matches, expected[i].Value)
   528  		c.Check(addr.Type, gc.Equals, expected[i].Type)
   529  		c.Check(addr.Scope, gc.Equals, expected[i].Scope)
   530  	}
   531  }
   532  
   533  func (t *localServerSuite) TestConstraintsValidatorUnsupported(c *gc.C) {
   534  	env := t.Prepare(c)
   535  	validator, err := env.ConstraintsValidator()
   536  	c.Assert(err, gc.IsNil)
   537  	cons := constraints.MustParse("arch=amd64 tags=foo")
   538  	unsupported, err := validator.Validate(cons)
   539  	c.Assert(err, gc.IsNil)
   540  	c.Assert(unsupported, gc.DeepEquals, []string{"tags"})
   541  }
   542  
   543  func (t *localServerSuite) TestConstraintsValidatorVocab(c *gc.C) {
   544  	env := t.Prepare(c)
   545  	validator, err := env.ConstraintsValidator()
   546  	c.Assert(err, gc.IsNil)
   547  	cons := constraints.MustParse("arch=ppc64")
   548  	_, err = validator.Validate(cons)
   549  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64\nvalid values are:.*")
   550  	cons = constraints.MustParse("instance-type=foo")
   551  	_, err = validator.Validate(cons)
   552  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*")
   553  }
   554  
   555  func (t *localServerSuite) TestConstraintsMerge(c *gc.C) {
   556  	env := t.Prepare(c)
   557  	validator, err := env.ConstraintsValidator()
   558  	c.Assert(err, gc.IsNil)
   559  	consA := constraints.MustParse("arch=amd64 mem=1G cpu-power=10 cpu-cores=2 tags=bar")
   560  	consB := constraints.MustParse("arch=i386 instance-type=m1.small")
   561  	cons, err := validator.Merge(consA, consB)
   562  	c.Assert(err, gc.IsNil)
   563  	c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=i386 instance-type=m1.small tags=bar"))
   564  }
   565  
   566  func (t *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) {
   567  	env := t.Prepare(c)
   568  	cons := constraints.MustParse("instance-type=m1.small root-disk=1G")
   569  	placement := ""
   570  	err := env.PrecheckInstance("precise", cons, placement)
   571  	c.Assert(err, gc.IsNil)
   572  }
   573  
   574  func (t *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) {
   575  	env := t.Prepare(c)
   576  	cons := constraints.MustParse("instance-type=m1.invalid")
   577  	placement := ""
   578  	err := env.PrecheckInstance("precise", cons, placement)
   579  	c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "m1.invalid" specified`)
   580  }
   581  
   582  func (t *localServerSuite) TestPrecheckInstanceUnsupportedArch(c *gc.C) {
   583  	env := t.Prepare(c)
   584  	cons := constraints.MustParse("instance-type=cc1.4xlarge arch=i386")
   585  	placement := ""
   586  	err := env.PrecheckInstance("precise", cons, placement)
   587  	c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "cc1.4xlarge" and arch "i386" specified`)
   588  }
   589  
   590  func (t *localServerSuite) TestPrecheckInstanceAvailZone(c *gc.C) {
   591  	env := t.Prepare(c)
   592  	placement := "zone=test-available"
   593  	err := env.PrecheckInstance("precise", constraints.Value{}, placement)
   594  	c.Assert(err, gc.IsNil)
   595  }
   596  
   597  func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnavailable(c *gc.C) {
   598  	env := t.Prepare(c)
   599  	placement := "zone=test-unavailable"
   600  	err := env.PrecheckInstance("precise", constraints.Value{}, placement)
   601  	c.Assert(err, gc.IsNil)
   602  }
   603  
   604  func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) {
   605  	env := t.Prepare(c)
   606  	placement := "zone=test-unknown"
   607  	err := env.PrecheckInstance("precise", constraints.Value{}, placement)
   608  	c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`)
   609  }
   610  
   611  func (t *localServerSuite) TestValidateImageMetadata(c *gc.C) {
   612  	env := t.Prepare(c)
   613  	params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("test")
   614  	c.Assert(err, gc.IsNil)
   615  	params.Series = "precise"
   616  	params.Endpoint = "https://ec2.endpoint.com"
   617  	params.Sources, err = imagemetadata.GetMetadataSources(env)
   618  	c.Assert(err, gc.IsNil)
   619  	image_ids, _, err := imagemetadata.ValidateImageMetadata(params)
   620  	c.Assert(err, gc.IsNil)
   621  	sort.Strings(image_ids)
   622  	c.Assert(image_ids, gc.DeepEquals, []string{"ami-00000033", "ami-00000034", "ami-00000035"})
   623  }
   624  
   625  func (t *localServerSuite) TestGetToolsMetadataSources(c *gc.C) {
   626  	env := t.Prepare(c)
   627  	sources, err := tools.GetMetadataSources(env)
   628  	c.Assert(err, gc.IsNil)
   629  	c.Assert(len(sources), gc.Equals, 1)
   630  	url, err := sources[0].URL("")
   631  	// The control bucket URL contains the bucket name.
   632  	c.Assert(strings.Contains(url, ec2.ControlBucketName(env)+"/tools"), jc.IsTrue)
   633  }
   634  
   635  func (t *localServerSuite) TestSupportedArchitectures(c *gc.C) {
   636  	env := t.Prepare(c)
   637  	a, err := env.SupportedArchitectures()
   638  	c.Assert(err, gc.IsNil)
   639  	c.Assert(a, jc.SameContents, []string{"amd64", "i386"})
   640  }
   641  
   642  func (t *localServerSuite) TestSupportNetworks(c *gc.C) {
   643  	env := t.Prepare(c)
   644  	c.Assert(env.SupportNetworks(), jc.IsFalse)
   645  }
   646  
   647  // localNonUSEastSuite is similar to localServerSuite but the S3 mock server
   648  // behaves as if it is not in the us-east region.
   649  type localNonUSEastSuite struct {
   650  	coretesting.BaseSuite
   651  	restoreEC2Patching func()
   652  	srv                localServer
   653  	env                environs.Environ
   654  }
   655  
   656  func (t *localNonUSEastSuite) SetUpSuite(c *gc.C) {
   657  	t.BaseSuite.SetUpSuite(c)
   658  	t.restoreEC2Patching = patchEC2ForTesting()
   659  }
   660  
   661  func (t *localNonUSEastSuite) TearDownSuite(c *gc.C) {
   662  	t.restoreEC2Patching()
   663  	t.BaseSuite.TearDownSuite(c)
   664  }
   665  
   666  func (t *localNonUSEastSuite) SetUpTest(c *gc.C) {
   667  	t.BaseSuite.SetUpTest(c)
   668  	t.srv.config = &s3test.Config{
   669  		Send409Conflict: true,
   670  	}
   671  	t.srv.startServer(c)
   672  
   673  	cfg, err := config.New(config.NoDefaults, localConfigAttrs)
   674  	c.Assert(err, gc.IsNil)
   675  	env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem())
   676  	c.Assert(err, gc.IsNil)
   677  	t.env = env
   678  }
   679  
   680  func (t *localNonUSEastSuite) TearDownTest(c *gc.C) {
   681  	t.srv.stopServer(c)
   682  	t.BaseSuite.TearDownTest(c)
   683  }
   684  
   685  func patchEC2ForTesting() func() {
   686  	ec2.UseTestImageData(ec2.TestImagesData)
   687  	ec2.UseTestInstanceTypeData(ec2.TestInstanceTypeCosts)
   688  	ec2.UseTestRegionData(ec2.TestRegions)
   689  	restoreTimeouts := envtesting.PatchAttemptStrategies(ec2.ShortAttempt, ec2.StorageAttempt)
   690  	restoreFinishBootstrap := envtesting.DisableFinishBootstrap()
   691  	return func() {
   692  		restoreFinishBootstrap()
   693  		restoreTimeouts()
   694  		ec2.UseTestImageData(nil)
   695  		ec2.UseTestInstanceTypeData(nil)
   696  		ec2.UseTestRegionData(nil)
   697  	}
   698  }
   699  
   700  // If match is true, CheckScripts checks that at least one script started
   701  // by the cloudinit data matches the given regexp pattern, otherwise it
   702  // checks that no script matches.  It's exported so it can be used by tests
   703  // defined in ec2_test.
   704  func CheckScripts(c *gc.C, userDataMap map[interface{}]interface{}, pattern string, match bool) {
   705  	scripts0 := userDataMap["runcmd"]
   706  	if scripts0 == nil {
   707  		c.Errorf("cloudinit has no entry for runcmd")
   708  		return
   709  	}
   710  	scripts := scripts0.([]interface{})
   711  	re := regexp.MustCompile(pattern)
   712  	found := false
   713  	for _, s0 := range scripts {
   714  		s := s0.(string)
   715  		if re.MatchString(s) {
   716  			found = true
   717  		}
   718  	}
   719  	switch {
   720  	case match && !found:
   721  		c.Errorf("script %q not found in %q", pattern, scripts)
   722  	case !match && found:
   723  		c.Errorf("script %q found but not expected in %q", pattern, scripts)
   724  	}
   725  }
   726  
   727  // CheckPackage checks that the cloudinit will or won't install the given
   728  // package, depending on the value of match.  It's exported so it can be
   729  // used by tests defined outside the ec2 package.
   730  func CheckPackage(c *gc.C, userDataMap map[interface{}]interface{}, pkg string, match bool) {
   731  	pkgs0 := userDataMap["packages"]
   732  	if pkgs0 == nil {
   733  		if match {
   734  			c.Errorf("cloudinit has no entry for packages")
   735  		}
   736  		return
   737  	}
   738  
   739  	pkgs := pkgs0.([]interface{})
   740  
   741  	found := false
   742  	for _, p0 := range pkgs {
   743  		p := p0.(string)
   744  		if p == pkg {
   745  			found = true
   746  		}
   747  	}
   748  	switch {
   749  	case match && !found:
   750  		c.Errorf("package %q not found in %v", pkg, pkgs)
   751  	case !match && found:
   752  		c.Errorf("%q found but not expected in %v", pkg, pkgs)
   753  	}
   754  }