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