github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/provider/ec2/ebs_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ec2_test
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils/arch"
    15  	"github.com/juju/utils/clock"
    16  	"github.com/juju/utils/series"
    17  	awsec2 "gopkg.in/amz.v3/ec2"
    18  	"gopkg.in/amz.v3/ec2/ec2test"
    19  	gc "gopkg.in/check.v1"
    20  	"gopkg.in/juju/names.v2"
    21  
    22  	"github.com/juju/juju/cloud"
    23  	"github.com/juju/juju/constraints"
    24  	"github.com/juju/juju/environs/imagemetadata"
    25  	imagetesting "github.com/juju/juju/environs/imagemetadata/testing"
    26  	"github.com/juju/juju/environs/jujutest"
    27  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    28  	"github.com/juju/juju/environs/tags"
    29  	"github.com/juju/juju/instance"
    30  	"github.com/juju/juju/juju/keys"
    31  	"github.com/juju/juju/provider/ec2"
    32  	"github.com/juju/juju/storage"
    33  	"github.com/juju/juju/testing"
    34  	jujuversion "github.com/juju/juju/version"
    35  )
    36  
    37  type ebsSuite struct {
    38  	testing.BaseSuite
    39  	// TODO(axw) the EBS tests should not be embedding jujutest.Tests.
    40  	jujutest.Tests
    41  	srv    localServer
    42  	client *awsec2.EC2
    43  
    44  	instanceId string
    45  }
    46  
    47  var _ = gc.Suite(&ebsSuite{})
    48  
    49  func (s *ebsSuite) SetUpSuite(c *gc.C) {
    50  	s.BaseSuite.SetUpSuite(c)
    51  	s.Tests.SetUpSuite(c)
    52  	s.Credential = cloud.NewCredential(
    53  		cloud.AccessKeyAuthType,
    54  		map[string]string{
    55  			"access-key": "x",
    56  			"secret-key": "x",
    57  		},
    58  	)
    59  
    60  	// Upload arches that ec2 supports; add to this
    61  	// as ec2 coverage expands.
    62  	s.UploadArches = []string{arch.AMD64, arch.I386}
    63  	s.TestConfig = localConfigAttrs.Merge(testing.Attrs{
    64  		"access-key": "x",
    65  		"secret-key": "x",
    66  		"region":     "test",
    67  	})
    68  	s.BaseSuite.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey)
    69  	s.BaseSuite.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
    70  	imagetesting.PatchOfficialDataSources(&s.BaseSuite.CleanupSuite, "test:")
    71  	s.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc)
    72  }
    73  
    74  func (s *ebsSuite) TearDownSuite(c *gc.C) {
    75  	s.Tests.TearDownSuite(c)
    76  	s.BaseSuite.TearDownSuite(c)
    77  }
    78  
    79  func (s *ebsSuite) SetUpTest(c *gc.C) {
    80  	s.BaseSuite.SetUpTest(c)
    81  	s.BaseSuite.PatchValue(&jujuversion.Current, testing.FakeVersionNumber)
    82  	s.BaseSuite.PatchValue(&arch.HostArch, func() string { return arch.AMD64 })
    83  	s.BaseSuite.PatchValue(&series.HostSeries, func() string { return series.LatestLts() })
    84  	s.srv.startServer(c)
    85  	s.Tests.SetUpTest(c)
    86  	s.PatchValue(&ec2.DestroyVolumeAttempt.Delay, time.Duration(0))
    87  
    88  	region := s.srv.region()
    89  	s.CloudRegion = region.Name
    90  	s.CloudEndpoint = region.EC2Endpoint
    91  	s.client = s.srv.client()
    92  	restoreEC2Patching := patchEC2ForTesting(c, region)
    93  	s.AddCleanup(func(c *gc.C) { restoreEC2Patching() })
    94  }
    95  
    96  func (s *ebsSuite) TearDownTest(c *gc.C) {
    97  	s.Tests.TearDownTest(c)
    98  	s.srv.stopServer(c)
    99  	s.BaseSuite.TearDownTest(c)
   100  }
   101  
   102  func (s *ebsSuite) ebsProvider(c *gc.C) storage.Provider {
   103  	env := s.Prepare(c)
   104  	p, err := env.StorageProvider(ec2.EBS_ProviderType)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	return p
   107  }
   108  
   109  func (s *ebsSuite) TestValidateConfigUnknownConfig(c *gc.C) {
   110  	p := s.ebsProvider(c)
   111  	cfg, err := storage.NewConfig("foo", ec2.EBS_ProviderType, map[string]interface{}{
   112  		"unknown": "config",
   113  	})
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	err = p.ValidateConfig(cfg)
   116  	c.Assert(err, jc.ErrorIsNil) // unknown attrs ignored
   117  }
   118  
   119  func (s *ebsSuite) TestSupports(c *gc.C) {
   120  	p := s.ebsProvider(c)
   121  	c.Assert(p.Supports(storage.StorageKindBlock), jc.IsTrue)
   122  	c.Assert(p.Supports(storage.StorageKindFilesystem), jc.IsFalse)
   123  }
   124  
   125  func (s *ebsSuite) volumeSource(c *gc.C, cfg *storage.Config) storage.VolumeSource {
   126  	p := s.ebsProvider(c)
   127  	vs, err := p.VolumeSource(cfg)
   128  	c.Assert(err, jc.ErrorIsNil)
   129  	return vs
   130  }
   131  
   132  func (s *ebsSuite) createVolumes(vs storage.VolumeSource, instanceId string) ([]storage.CreateVolumesResult, error) {
   133  	if instanceId == "" {
   134  		instanceId = s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil)[0]
   135  	}
   136  	volume0 := names.NewVolumeTag("0")
   137  	volume1 := names.NewVolumeTag("1")
   138  	volume2 := names.NewVolumeTag("2")
   139  	params := []storage.VolumeParams{{
   140  		Tag:      volume0,
   141  		Size:     10 * 1000,
   142  		Provider: ec2.EBS_ProviderType,
   143  		Attributes: map[string]interface{}{
   144  			"volume-type": "io1",
   145  			"iops":        30,
   146  		},
   147  		Attachment: &storage.VolumeAttachmentParams{
   148  			AttachmentParams: storage.AttachmentParams{
   149  				InstanceId: instance.Id(instanceId),
   150  			},
   151  		},
   152  		ResourceTags: map[string]string{
   153  			tags.JujuModel: s.TestConfig["uuid"].(string),
   154  		},
   155  	}, {
   156  		Tag:      volume1,
   157  		Size:     20 * 1000,
   158  		Provider: ec2.EBS_ProviderType,
   159  		Attachment: &storage.VolumeAttachmentParams{
   160  			AttachmentParams: storage.AttachmentParams{
   161  				InstanceId: instance.Id(instanceId),
   162  			},
   163  		},
   164  		ResourceTags: map[string]string{
   165  			tags.JujuModel: "something-else",
   166  		},
   167  	}, {
   168  		Tag:      volume2,
   169  		Size:     30 * 1000,
   170  		Provider: ec2.EBS_ProviderType,
   171  		ResourceTags: map[string]string{
   172  			"abc": "123",
   173  		},
   174  		Attachment: &storage.VolumeAttachmentParams{
   175  			AttachmentParams: storage.AttachmentParams{
   176  				InstanceId: instance.Id(instanceId),
   177  			},
   178  		},
   179  	}}
   180  	return vs.CreateVolumes(params)
   181  }
   182  
   183  func (s *ebsSuite) assertCreateVolumes(c *gc.C, vs storage.VolumeSource, instanceId string) {
   184  	results, err := s.createVolumes(vs, instanceId)
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	c.Assert(results, gc.HasLen, 3)
   187  	c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{
   188  		names.NewVolumeTag("0"),
   189  		storage.VolumeInfo{
   190  			Size:       10240,
   191  			VolumeId:   "vol-0",
   192  			Persistent: true,
   193  		},
   194  	})
   195  	c.Assert(results[1].Volume, jc.DeepEquals, &storage.Volume{
   196  		names.NewVolumeTag("1"),
   197  		storage.VolumeInfo{
   198  			Size:       20480,
   199  			VolumeId:   "vol-1",
   200  			Persistent: true,
   201  		},
   202  	})
   203  	c.Assert(results[2].Volume, jc.DeepEquals, &storage.Volume{
   204  		names.NewVolumeTag("2"),
   205  		storage.VolumeInfo{
   206  			Size:       30720,
   207  			VolumeId:   "vol-2",
   208  			Persistent: true,
   209  		},
   210  	})
   211  	ec2Client := ec2.StorageEC2(vs)
   212  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	c.Assert(ec2Vols.Volumes, gc.HasLen, 3)
   215  	sortBySize(ec2Vols.Volumes)
   216  	c.Assert(ec2Vols.Volumes[0].Size, gc.Equals, 10)
   217  	c.Assert(ec2Vols.Volumes[1].Size, gc.Equals, 20)
   218  	c.Assert(ec2Vols.Volumes[2].Size, gc.Equals, 30)
   219  }
   220  
   221  var deleteSecurityGroupForTestFunc = func(inst ec2.SecurityGroupCleaner, group awsec2.SecurityGroup, _ clock.Clock) error {
   222  	// With an exponential retry for deleting security groups,
   223  	// we never return from local live tests.
   224  	// No need to re-try in tests anyway - just call delete.
   225  	_, err := inst.DeleteSecurityGroup(group)
   226  	return err
   227  }
   228  
   229  type volumeSorter struct {
   230  	vols []awsec2.Volume
   231  	less func(i, j awsec2.Volume) bool
   232  }
   233  
   234  func sortBySize(vols []awsec2.Volume) {
   235  	sort.Sort(volumeSorter{vols, func(i, j awsec2.Volume) bool {
   236  		return i.Size < j.Size
   237  	}})
   238  }
   239  
   240  func (s volumeSorter) Len() int {
   241  	return len(s.vols)
   242  }
   243  
   244  func (s volumeSorter) Swap(i, j int) {
   245  	s.vols[i], s.vols[j] = s.vols[j], s.vols[i]
   246  }
   247  
   248  func (s volumeSorter) Less(i, j int) bool {
   249  	return s.less(s.vols[i], s.vols[j])
   250  }
   251  
   252  func (s *ebsSuite) TestCreateVolumes(c *gc.C) {
   253  	vs := s.volumeSource(c, nil)
   254  	s.assertCreateVolumes(c, vs, "")
   255  }
   256  
   257  func (s *ebsSuite) TestVolumeTags(c *gc.C) {
   258  	vs := s.volumeSource(c, nil)
   259  	results, err := s.createVolumes(vs, "")
   260  	c.Assert(err, jc.ErrorIsNil)
   261  	c.Assert(results, gc.HasLen, 3)
   262  	c.Assert(results[0].Error, jc.ErrorIsNil)
   263  	c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{
   264  		names.NewVolumeTag("0"),
   265  		storage.VolumeInfo{
   266  			Size:       10240,
   267  			VolumeId:   "vol-0",
   268  			Persistent: true,
   269  		},
   270  	})
   271  	c.Assert(results[1].Error, jc.ErrorIsNil)
   272  	c.Assert(results[1].Volume, jc.DeepEquals, &storage.Volume{
   273  		names.NewVolumeTag("1"),
   274  		storage.VolumeInfo{
   275  			Size:       20480,
   276  			VolumeId:   "vol-1",
   277  			Persistent: true,
   278  		},
   279  	})
   280  	c.Assert(results[2].Error, jc.ErrorIsNil)
   281  	c.Assert(results[2].Volume, jc.DeepEquals, &storage.Volume{
   282  		names.NewVolumeTag("2"),
   283  		storage.VolumeInfo{
   284  			Size:       30720,
   285  			VolumeId:   "vol-2",
   286  			Persistent: true,
   287  		},
   288  	})
   289  	ec2Client := ec2.StorageEC2(vs)
   290  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	c.Assert(ec2Vols.Volumes, gc.HasLen, 3)
   293  	sortBySize(ec2Vols.Volumes)
   294  	c.Assert(ec2Vols.Volumes[0].Tags, jc.SameContents, []awsec2.Tag{
   295  		{"juju-model-uuid", "deadbeef-0bad-400d-8000-4b1d0d06f00d"},
   296  		{"Name", "juju-sample-volume-0"},
   297  	})
   298  	c.Assert(ec2Vols.Volumes[1].Tags, jc.SameContents, []awsec2.Tag{
   299  		{"juju-model-uuid", "something-else"},
   300  		{"Name", "juju-sample-volume-1"},
   301  	})
   302  	c.Assert(ec2Vols.Volumes[2].Tags, jc.SameContents, []awsec2.Tag{
   303  		{"Name", "juju-sample-volume-2"},
   304  		{"abc", "123"},
   305  	})
   306  }
   307  
   308  func (s *ebsSuite) TestVolumeTypeAliases(c *gc.C) {
   309  	instanceIdRunning := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil)[0]
   310  	vs := s.volumeSource(c, nil)
   311  	ec2Client := ec2.StorageEC2(vs)
   312  	aliases := [][2]string{
   313  		{"magnetic", "standard"},
   314  		{"ssd", "gp2"},
   315  		{"provisioned-iops", "io1"},
   316  	}
   317  	for i, alias := range aliases {
   318  		params := []storage.VolumeParams{{
   319  			Tag:      names.NewVolumeTag("0"),
   320  			Size:     10 * 1000,
   321  			Provider: ec2.EBS_ProviderType,
   322  			Attributes: map[string]interface{}{
   323  				"volume-type": alias[0],
   324  			},
   325  			Attachment: &storage.VolumeAttachmentParams{
   326  				AttachmentParams: storage.AttachmentParams{
   327  					InstanceId: instance.Id(instanceIdRunning),
   328  				},
   329  			},
   330  		}}
   331  		if alias[1] == "io1" {
   332  			params[0].Attributes["iops"] = 30
   333  		}
   334  		results, err := vs.CreateVolumes(params)
   335  		c.Assert(err, jc.ErrorIsNil)
   336  		c.Assert(results, gc.HasLen, 1)
   337  		c.Assert(results[0].Error, jc.ErrorIsNil)
   338  		c.Assert(results[0].Volume.VolumeId, gc.Equals, fmt.Sprintf("vol-%d", i))
   339  	}
   340  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   341  	c.Assert(err, jc.ErrorIsNil)
   342  	c.Assert(ec2Vols.Volumes, gc.HasLen, len(aliases))
   343  	sort.Sort(volumeSorter{ec2Vols.Volumes, func(i, j awsec2.Volume) bool {
   344  		return i.Id < j.Id
   345  	}})
   346  	for i, alias := range aliases {
   347  		c.Assert(ec2Vols.Volumes[i].VolumeType, gc.Equals, alias[1])
   348  	}
   349  }
   350  
   351  func (s *ebsSuite) TestDestroyVolumesNotFoundReturnsNil(c *gc.C) {
   352  	vs := s.volumeSource(c, nil)
   353  	results, err := vs.DestroyVolumes([]string{"vol-42"})
   354  	c.Assert(err, jc.ErrorIsNil)
   355  	c.Assert(results, gc.HasLen, 1)
   356  	c.Assert(results[0], jc.ErrorIsNil)
   357  }
   358  
   359  func (s *ebsSuite) TestDestroyVolumes(c *gc.C) {
   360  	vs := s.volumeSource(c, nil)
   361  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   362  	errs, err := vs.DetachVolumes(params)
   363  	c.Assert(err, jc.ErrorIsNil)
   364  	c.Assert(errs, jc.DeepEquals, []error{nil})
   365  	errs, err = vs.DestroyVolumes([]string{"vol-0"})
   366  	c.Assert(err, jc.ErrorIsNil)
   367  	c.Assert(errs, jc.DeepEquals, []error{nil})
   368  
   369  	ec2Client := ec2.StorageEC2(vs)
   370  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   371  	c.Assert(err, jc.ErrorIsNil)
   372  	c.Assert(ec2Vols.Volumes, gc.HasLen, 2)
   373  	sortBySize(ec2Vols.Volumes)
   374  	c.Assert(ec2Vols.Volumes[0].Size, gc.Equals, 20)
   375  }
   376  
   377  func (s *ebsSuite) TestDestroyVolumesStillAttached(c *gc.C) {
   378  	vs := s.volumeSource(c, nil)
   379  	s.setupAttachVolumesTest(c, vs, ec2test.Running)
   380  	errs, err := vs.DestroyVolumes([]string{"vol-0"})
   381  	c.Assert(err, jc.ErrorIsNil)
   382  	c.Assert(errs, jc.DeepEquals, []error{nil})
   383  
   384  	ec2Client := ec2.StorageEC2(vs)
   385  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   386  	c.Assert(err, jc.ErrorIsNil)
   387  	c.Assert(ec2Vols.Volumes, gc.HasLen, 2)
   388  	sortBySize(ec2Vols.Volumes)
   389  	c.Assert(ec2Vols.Volumes[0].Size, gc.Equals, 20)
   390  }
   391  
   392  func (s *ebsSuite) TestDescribeVolumes(c *gc.C) {
   393  	vs := s.volumeSource(c, nil)
   394  	s.assertCreateVolumes(c, vs, "")
   395  
   396  	vols, err := vs.DescribeVolumes([]string{"vol-0", "vol-1"})
   397  	c.Assert(err, jc.ErrorIsNil)
   398  	c.Assert(vols, jc.DeepEquals, []storage.DescribeVolumesResult{{
   399  		VolumeInfo: &storage.VolumeInfo{
   400  			Size:       10240,
   401  			VolumeId:   "vol-0",
   402  			Persistent: true,
   403  		},
   404  	}, {
   405  		VolumeInfo: &storage.VolumeInfo{
   406  			Size:       20480,
   407  			VolumeId:   "vol-1",
   408  			Persistent: true,
   409  		},
   410  	}})
   411  }
   412  
   413  func (s *ebsSuite) TestDescribeVolumesNotFound(c *gc.C) {
   414  	vs := s.volumeSource(c, nil)
   415  	vols, err := vs.DescribeVolumes([]string{"vol-42"})
   416  	c.Assert(err, jc.ErrorIsNil)
   417  	c.Assert(vols, gc.HasLen, 1)
   418  	c.Assert(vols[0].Error, gc.ErrorMatches, "vol-42 not found")
   419  }
   420  
   421  func (s *ebsSuite) TestListVolumes(c *gc.C) {
   422  	vs := s.volumeSource(c, nil)
   423  	s.assertCreateVolumes(c, vs, "")
   424  
   425  	// Only one volume created by assertCreateVolumes has
   426  	// the model-uuid tag with the expected value.
   427  	volIds, err := vs.ListVolumes()
   428  	c.Assert(err, jc.ErrorIsNil)
   429  	c.Assert(volIds, jc.SameContents, []string{"vol-0"})
   430  }
   431  
   432  func (s *ebsSuite) TestListVolumesIgnoresRootDisks(c *gc.C) {
   433  	s.srv.ec2srv.SetCreateRootDisks(true)
   434  	s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil)
   435  
   436  	// Tag the root disk with the model UUID.
   437  	_, err := s.client.CreateTags([]string{"vol-0"}, []awsec2.Tag{
   438  		{tags.JujuModel, s.TestConfig["uuid"].(string)},
   439  	})
   440  	c.Assert(err, jc.ErrorIsNil)
   441  
   442  	vs := s.volumeSource(c, nil)
   443  	volIds, err := vs.ListVolumes()
   444  	c.Assert(err, jc.ErrorIsNil)
   445  	c.Assert(volIds, gc.HasLen, 0)
   446  }
   447  
   448  func (s *ebsSuite) TestCreateVolumesErrors(c *gc.C) {
   449  	vs := s.volumeSource(c, nil)
   450  	volume0 := names.NewVolumeTag("0")
   451  
   452  	instanceIdPending := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil)[0]
   453  	instanceIdRunning := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil)[0]
   454  	attachmentParams := storage.VolumeAttachmentParams{
   455  		AttachmentParams: storage.AttachmentParams{
   456  			InstanceId: instance.Id(instanceIdRunning),
   457  		},
   458  	}
   459  
   460  	for _, test := range []struct {
   461  		params storage.VolumeParams
   462  		err    string
   463  	}{{
   464  		params: storage.VolumeParams{
   465  			Size:     1024,
   466  			Provider: ec2.EBS_ProviderType,
   467  			Attachment: &storage.VolumeAttachmentParams{
   468  				AttachmentParams: storage.AttachmentParams{
   469  					InstanceId: "woat",
   470  				},
   471  			},
   472  		},
   473  		err: `querying instance details: instance "woat" not found \(InvalidInstanceID.NotFound\)`,
   474  	}, {
   475  		params: storage.VolumeParams{
   476  			Size:     1024,
   477  			Provider: ec2.EBS_ProviderType,
   478  			Attachment: &storage.VolumeAttachmentParams{
   479  				AttachmentParams: storage.AttachmentParams{
   480  					InstanceId: instance.Id(instanceIdPending),
   481  				},
   482  			},
   483  		},
   484  		err: "cannot attach to non-running instance i-3",
   485  	}, {
   486  		params: storage.VolumeParams{
   487  			Size:       100000000,
   488  			Provider:   ec2.EBS_ProviderType,
   489  			Attributes: map[string]interface{}{},
   490  			Attachment: &attachmentParams,
   491  		},
   492  		err: "volume size 97657 GiB exceeds the maximum of 1024 GiB",
   493  	}, {
   494  		params: storage.VolumeParams{
   495  			Size:     100000000,
   496  			Provider: ec2.EBS_ProviderType,
   497  			Attributes: map[string]interface{}{
   498  				"volume-type": "gp2",
   499  			},
   500  			Attachment: &attachmentParams,
   501  		},
   502  		err: "volume size 97657 GiB exceeds the maximum of 16384 GiB",
   503  	}, {
   504  		params: storage.VolumeParams{
   505  			Size:     100000000,
   506  			Provider: ec2.EBS_ProviderType,
   507  			Attributes: map[string]interface{}{
   508  				"volume-type": "io1",
   509  				"iops":        "30",
   510  			},
   511  			Attachment: &attachmentParams,
   512  		},
   513  		err: "volume size 97657 GiB exceeds the maximum of 16384 GiB",
   514  	}, {
   515  		params: storage.VolumeParams{
   516  			Tag:      volume0,
   517  			Size:     1000,
   518  			Provider: ec2.EBS_ProviderType,
   519  			Attributes: map[string]interface{}{
   520  				"volume-type": "io1",
   521  				"iops":        "30",
   522  			},
   523  			Attachment: &attachmentParams,
   524  		},
   525  		err: "volume size is 1 GiB, must be at least 4 GiB",
   526  	}, {
   527  		params: storage.VolumeParams{
   528  			Tag:      volume0,
   529  			Size:     10000,
   530  			Provider: ec2.EBS_ProviderType,
   531  			Attributes: map[string]interface{}{
   532  				"volume-type": "io1",
   533  				"iops":        "1234",
   534  			},
   535  			Attachment: &attachmentParams,
   536  		},
   537  		err: "specified IOPS ratio is 1234/GiB, maximum is 30/GiB",
   538  	}, {
   539  		params: storage.VolumeParams{
   540  			Tag:      volume0,
   541  			Size:     10000,
   542  			Provider: ec2.EBS_ProviderType,
   543  			Attributes: map[string]interface{}{
   544  				"volume-type": "standard",
   545  				"iops":        "30",
   546  			},
   547  			Attachment: &attachmentParams,
   548  		},
   549  		err: `IOPS specified, but volume type is "standard"`,
   550  	}, {
   551  		params: storage.VolumeParams{
   552  			Tag:      volume0,
   553  			Size:     10000,
   554  			Provider: ec2.EBS_ProviderType,
   555  			Attributes: map[string]interface{}{
   556  				"volume-type": "what",
   557  			},
   558  			Attachment: &attachmentParams,
   559  		},
   560  		err: "validating EBS storage config: volume-type: unexpected value \"what\"",
   561  	}} {
   562  		results, err := vs.CreateVolumes([]storage.VolumeParams{test.params})
   563  		c.Assert(err, jc.ErrorIsNil)
   564  		c.Assert(results, gc.HasLen, 1)
   565  		c.Check(results[0].Error, gc.ErrorMatches, test.err)
   566  	}
   567  }
   568  
   569  var imageId = "ami-ccf405a5" // Ubuntu Maverick, i386, EBS store
   570  
   571  func (s *ebsSuite) setupAttachVolumesTest(
   572  	c *gc.C, vs storage.VolumeSource, state awsec2.InstanceState,
   573  ) []storage.VolumeAttachmentParams {
   574  
   575  	instanceId := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, state, nil)[0]
   576  	s.assertCreateVolumes(c, vs, instanceId)
   577  
   578  	return []storage.VolumeAttachmentParams{{
   579  		Volume:   names.NewVolumeTag("0"),
   580  		VolumeId: "vol-0",
   581  		AttachmentParams: storage.AttachmentParams{
   582  			Machine:    names.NewMachineTag("1"),
   583  			InstanceId: instance.Id(instanceId),
   584  		},
   585  	}}
   586  }
   587  
   588  func (s *ebsSuite) TestAttachVolumesNotRunning(c *gc.C) {
   589  	vs := s.volumeSource(c, nil)
   590  	instanceId := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil)[0]
   591  	results, err := s.createVolumes(vs, instanceId)
   592  	c.Assert(err, jc.ErrorIsNil)
   593  	c.Assert(results, gc.Not(gc.HasLen), 0)
   594  	for _, result := range results {
   595  		c.Check(errors.Cause(result.Error), gc.ErrorMatches, "cannot attach to non-running instance i-3")
   596  	}
   597  }
   598  
   599  func (s *ebsSuite) TestAttachVolumes(c *gc.C) {
   600  	vs := s.volumeSource(c, nil)
   601  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   602  	result, err := vs.AttachVolumes(params)
   603  	c.Assert(err, jc.ErrorIsNil)
   604  	c.Assert(result, gc.HasLen, 1)
   605  	c.Assert(result[0].Error, jc.ErrorIsNil)
   606  	c.Assert(result[0].VolumeAttachment, jc.DeepEquals, &storage.VolumeAttachment{
   607  		names.NewVolumeTag("0"),
   608  		names.NewMachineTag("1"),
   609  		storage.VolumeAttachmentInfo{
   610  			DeviceName: "xvdf",
   611  			ReadOnly:   false,
   612  		},
   613  	})
   614  
   615  	ec2Client := ec2.StorageEC2(vs)
   616  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   617  	c.Assert(err, jc.ErrorIsNil)
   618  	c.Assert(ec2Vols.Volumes, gc.HasLen, 3)
   619  	sortBySize(ec2Vols.Volumes)
   620  	c.Assert(ec2Vols.Volumes[0].Attachments, jc.DeepEquals, []awsec2.VolumeAttachment{{
   621  		VolumeId:   "vol-0",
   622  		InstanceId: "i-3",
   623  		Device:     "/dev/sdf",
   624  		Status:     "attached",
   625  	}})
   626  
   627  	// Test idempotency.
   628  	result, err = vs.AttachVolumes(params)
   629  	c.Assert(err, jc.ErrorIsNil)
   630  	c.Assert(result, gc.HasLen, 1)
   631  	c.Assert(result[0].Error, jc.ErrorIsNil)
   632  	c.Assert(result[0].VolumeAttachment, jc.DeepEquals, &storage.VolumeAttachment{
   633  		names.NewVolumeTag("0"),
   634  		names.NewMachineTag("1"),
   635  		storage.VolumeAttachmentInfo{
   636  			DeviceName: "xvdf",
   637  			ReadOnly:   false,
   638  		},
   639  	})
   640  }
   641  
   642  // TODO(axw) add tests for attempting to attach while
   643  // a volume is still in the "creating" state.
   644  
   645  func (s *ebsSuite) TestDetachVolumes(c *gc.C) {
   646  	vs := s.volumeSource(c, nil)
   647  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   648  	_, err := vs.AttachVolumes(params)
   649  	c.Assert(err, jc.ErrorIsNil)
   650  	errs, err := vs.DetachVolumes(params)
   651  	c.Assert(err, jc.ErrorIsNil)
   652  	c.Assert(errs, jc.DeepEquals, []error{nil})
   653  
   654  	ec2Client := ec2.StorageEC2(vs)
   655  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   656  	c.Assert(err, jc.ErrorIsNil)
   657  	c.Assert(ec2Vols.Volumes, gc.HasLen, 3)
   658  	sortBySize(ec2Vols.Volumes)
   659  	c.Assert(ec2Vols.Volumes[0].Attachments, gc.HasLen, 0)
   660  
   661  	// Test idempotent
   662  	errs, err = vs.DetachVolumes(params)
   663  	c.Assert(err, jc.ErrorIsNil)
   664  	c.Assert(errs, jc.DeepEquals, []error{nil})
   665  }
   666  
   667  type blockDeviceMappingSuite struct {
   668  	testing.BaseSuite
   669  }
   670  
   671  var _ = gc.Suite(&blockDeviceMappingSuite{})
   672  
   673  func (*blockDeviceMappingSuite) TestBlockDeviceNamer(c *gc.C) {
   674  	var nextName func() (string, string, error)
   675  	expect := func(expectRequest, expectActual string) {
   676  		request, actual, err := nextName()
   677  		c.Assert(err, jc.ErrorIsNil)
   678  		c.Assert(request, gc.Equals, expectRequest)
   679  		c.Assert(actual, gc.Equals, expectActual)
   680  	}
   681  	expectN := func(expectRequest, expectActual string) {
   682  		for i := 1; i <= 6; i++ {
   683  			request, actual, err := nextName()
   684  			c.Assert(err, jc.ErrorIsNil)
   685  			c.Assert(request, gc.Equals, expectRequest+strconv.Itoa(i))
   686  			c.Assert(actual, gc.Equals, expectActual+strconv.Itoa(i))
   687  		}
   688  	}
   689  	expectErr := func(expectErr string) {
   690  		_, _, err := nextName()
   691  		c.Assert(err, gc.ErrorMatches, expectErr)
   692  	}
   693  
   694  	// First without numbers.
   695  	nextName = ec2.BlockDeviceNamer(false)
   696  	expect("/dev/sdf", "xvdf")
   697  	expect("/dev/sdg", "xvdg")
   698  	expect("/dev/sdh", "xvdh")
   699  	expect("/dev/sdi", "xvdi")
   700  	expect("/dev/sdj", "xvdj")
   701  	expect("/dev/sdk", "xvdk")
   702  	expect("/dev/sdl", "xvdl")
   703  	expect("/dev/sdm", "xvdm")
   704  	expect("/dev/sdn", "xvdn")
   705  	expect("/dev/sdo", "xvdo")
   706  	expect("/dev/sdp", "xvdp")
   707  	expectErr("too many EBS volumes to attach")
   708  
   709  	// Now with numbers.
   710  	nextName = ec2.BlockDeviceNamer(true)
   711  	expect("/dev/sdf1", "xvdf1")
   712  	expect("/dev/sdf2", "xvdf2")
   713  	expect("/dev/sdf3", "xvdf3")
   714  	expect("/dev/sdf4", "xvdf4")
   715  	expect("/dev/sdf5", "xvdf5")
   716  	expect("/dev/sdf6", "xvdf6")
   717  	expectN("/dev/sdg", "xvdg")
   718  	expectN("/dev/sdh", "xvdh")
   719  	expectN("/dev/sdi", "xvdi")
   720  	expectN("/dev/sdj", "xvdj")
   721  	expectN("/dev/sdk", "xvdk")
   722  	expectN("/dev/sdl", "xvdl")
   723  	expectN("/dev/sdm", "xvdm")
   724  	expectN("/dev/sdn", "xvdn")
   725  	expectN("/dev/sdo", "xvdo")
   726  	expectN("/dev/sdp", "xvdp")
   727  	expectErr("too many EBS volumes to attach")
   728  }
   729  
   730  func (*blockDeviceMappingSuite) TestGetBlockDeviceMappings(c *gc.C) {
   731  	mapping := ec2.GetBlockDeviceMappings(constraints.Value{}, "trusty", false)
   732  	c.Assert(mapping, gc.DeepEquals, []awsec2.BlockDeviceMapping{{
   733  		VolumeSize: 8,
   734  		DeviceName: "/dev/sda1",
   735  	}, {
   736  		VirtualName: "ephemeral0",
   737  		DeviceName:  "/dev/sdb",
   738  	}, {
   739  		VirtualName: "ephemeral1",
   740  		DeviceName:  "/dev/sdc",
   741  	}, {
   742  		VirtualName: "ephemeral2",
   743  		DeviceName:  "/dev/sdd",
   744  	}, {
   745  		VirtualName: "ephemeral3",
   746  		DeviceName:  "/dev/sde",
   747  	}})
   748  }
   749  
   750  func (*blockDeviceMappingSuite) TestGetBlockDeviceMappingsController(c *gc.C) {
   751  	mapping := ec2.GetBlockDeviceMappings(constraints.Value{}, "trusty", true)
   752  	c.Assert(mapping, gc.DeepEquals, []awsec2.BlockDeviceMapping{{
   753  		VolumeSize: 32,
   754  		DeviceName: "/dev/sda1",
   755  	}, {
   756  		VirtualName: "ephemeral0",
   757  		DeviceName:  "/dev/sdb",
   758  	}, {
   759  		VirtualName: "ephemeral1",
   760  		DeviceName:  "/dev/sdc",
   761  	}, {
   762  		VirtualName: "ephemeral2",
   763  		DeviceName:  "/dev/sdd",
   764  	}, {
   765  		VirtualName: "ephemeral3",
   766  		DeviceName:  "/dev/sde",
   767  	}})
   768  }