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