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