github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	awsec2 "gopkg.in/amz.v3/ec2"
    17  	"gopkg.in/amz.v3/ec2/ec2test"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/constraints"
    21  	"github.com/juju/juju/environs/config"
    22  	"github.com/juju/juju/environs/jujutest"
    23  	"github.com/juju/juju/environs/tags"
    24  	"github.com/juju/juju/instance"
    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":        30,
   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].Error, jc.ErrorIsNil)
   228  	c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{
   229  		names.NewVolumeTag("0"),
   230  		storage.VolumeInfo{
   231  			Size:       10240,
   232  			VolumeId:   "vol-0",
   233  			Persistent: true,
   234  		},
   235  	})
   236  	c.Assert(results[1].Error, jc.ErrorIsNil)
   237  	c.Assert(results[1].Volume, jc.DeepEquals, &storage.Volume{
   238  		names.NewVolumeTag("1"),
   239  		storage.VolumeInfo{
   240  			Size:       20480,
   241  			VolumeId:   "vol-1",
   242  			Persistent: true,
   243  		},
   244  	})
   245  	c.Assert(results[2].Error, jc.ErrorIsNil)
   246  	c.Assert(results[2].Volume, jc.DeepEquals, &storage.Volume{
   247  		names.NewVolumeTag("2"),
   248  		storage.VolumeInfo{
   249  			Size:       30720,
   250  			VolumeId:   "vol-2",
   251  			Persistent: true,
   252  		},
   253  	})
   254  	ec2Client := ec2.StorageEC2(vs)
   255  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   256  	c.Assert(err, jc.ErrorIsNil)
   257  	c.Assert(ec2Vols.Volumes, gc.HasLen, 3)
   258  	sortBySize(ec2Vols.Volumes)
   259  	c.Assert(ec2Vols.Volumes[0].Tags, jc.SameContents, []awsec2.Tag{
   260  		{"juju-env-uuid", "deadbeef-0bad-400d-8000-4b1d0d06f00d"},
   261  		{"Name", "juju-sample-volume-0"},
   262  	})
   263  	c.Assert(ec2Vols.Volumes[1].Tags, jc.SameContents, []awsec2.Tag{
   264  		{"juju-env-uuid", "something-else"},
   265  		{"Name", "juju-sample-volume-1"},
   266  	})
   267  	c.Assert(ec2Vols.Volumes[2].Tags, jc.SameContents, []awsec2.Tag{
   268  		{"Name", "juju-sample-volume-2"},
   269  		{"abc", "123"},
   270  	})
   271  }
   272  
   273  func (s *ebsVolumeSuite) TestVolumeTypeAliases(c *gc.C) {
   274  	instanceIdRunning := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil)[0]
   275  	vs := s.volumeSource(c, nil)
   276  	ec2Client := ec2.StorageEC2(vs)
   277  	aliases := [][2]string{
   278  		{"magnetic", "standard"},
   279  		{"ssd", "gp2"},
   280  		{"provisioned-iops", "io1"},
   281  	}
   282  	for i, alias := range aliases {
   283  		params := []storage.VolumeParams{{
   284  			Tag:      names.NewVolumeTag("0"),
   285  			Size:     10 * 1000,
   286  			Provider: ec2.EBS_ProviderType,
   287  			Attributes: map[string]interface{}{
   288  				"volume-type": alias[0],
   289  			},
   290  			Attachment: &storage.VolumeAttachmentParams{
   291  				AttachmentParams: storage.AttachmentParams{
   292  					InstanceId: instance.Id(instanceIdRunning),
   293  				},
   294  			},
   295  		}}
   296  		if alias[1] == "io1" {
   297  			params[0].Attributes["iops"] = 30
   298  		}
   299  		results, err := vs.CreateVolumes(params)
   300  		c.Assert(err, jc.ErrorIsNil)
   301  		c.Assert(results, gc.HasLen, 1)
   302  		c.Assert(results[0].Error, jc.ErrorIsNil)
   303  		c.Assert(results[0].Volume.VolumeId, gc.Equals, fmt.Sprintf("vol-%d", i))
   304  	}
   305  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   306  	c.Assert(err, jc.ErrorIsNil)
   307  	c.Assert(ec2Vols.Volumes, gc.HasLen, len(aliases))
   308  	sort.Sort(volumeSorter{ec2Vols.Volumes, func(i, j awsec2.Volume) bool {
   309  		return i.Id < j.Id
   310  	}})
   311  	for i, alias := range aliases {
   312  		c.Assert(ec2Vols.Volumes[i].VolumeType, gc.Equals, alias[1])
   313  	}
   314  }
   315  
   316  func (s *ebsVolumeSuite) TestDestroyVolumes(c *gc.C) {
   317  	vs := s.volumeSource(c, nil)
   318  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   319  	errs, err := vs.DetachVolumes(params)
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	c.Assert(errs, jc.DeepEquals, []error{nil})
   322  	errs, err = vs.DestroyVolumes([]string{"vol-0"})
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	c.Assert(errs, jc.DeepEquals, []error{nil})
   325  
   326  	ec2Client := ec2.StorageEC2(vs)
   327  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	c.Assert(ec2Vols.Volumes, gc.HasLen, 2)
   330  	sortBySize(ec2Vols.Volumes)
   331  	c.Assert(ec2Vols.Volumes[0].Size, gc.Equals, 20)
   332  }
   333  
   334  func (s *ebsVolumeSuite) TestDestroyVolumesStillAttached(c *gc.C) {
   335  	vs := s.volumeSource(c, nil)
   336  	s.setupAttachVolumesTest(c, vs, ec2test.Running)
   337  	errs, err := vs.DestroyVolumes([]string{"vol-0"})
   338  	c.Assert(err, jc.ErrorIsNil)
   339  	c.Assert(errs, jc.DeepEquals, []error{nil})
   340  
   341  	ec2Client := ec2.StorageEC2(vs)
   342  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   343  	c.Assert(err, jc.ErrorIsNil)
   344  	c.Assert(ec2Vols.Volumes, gc.HasLen, 2)
   345  	sortBySize(ec2Vols.Volumes)
   346  	c.Assert(ec2Vols.Volumes[0].Size, gc.Equals, 20)
   347  }
   348  
   349  func (s *ebsVolumeSuite) TestDescribeVolumes(c *gc.C) {
   350  	vs := s.volumeSource(c, nil)
   351  	s.assertCreateVolumes(c, vs, "")
   352  
   353  	vols, err := vs.DescribeVolumes([]string{"vol-0", "vol-1"})
   354  	c.Assert(err, jc.ErrorIsNil)
   355  	c.Assert(vols, jc.DeepEquals, []storage.DescribeVolumesResult{{
   356  		VolumeInfo: &storage.VolumeInfo{
   357  			Size:       10240,
   358  			VolumeId:   "vol-0",
   359  			Persistent: true,
   360  		},
   361  	}, {
   362  		VolumeInfo: &storage.VolumeInfo{
   363  			Size:       20480,
   364  			VolumeId:   "vol-1",
   365  			Persistent: true,
   366  		},
   367  	}})
   368  }
   369  
   370  func (s *ebsVolumeSuite) TestDescribeVolumesNotFound(c *gc.C) {
   371  	vs := s.volumeSource(c, nil)
   372  	vols, err := vs.DescribeVolumes([]string{"vol-42"})
   373  	c.Assert(err, jc.ErrorIsNil)
   374  	c.Assert(vols, gc.HasLen, 1)
   375  	c.Assert(vols[0].Error, gc.ErrorMatches, "vol-42 not found")
   376  }
   377  
   378  func (s *ebsVolumeSuite) TestListVolumes(c *gc.C) {
   379  	vs := s.volumeSource(c, nil)
   380  	s.assertCreateVolumes(c, vs, "")
   381  
   382  	// Only one volume created by assertCreateVolumes has
   383  	// the env-uuid tag with the expected value.
   384  	volIds, err := vs.ListVolumes()
   385  	c.Assert(err, jc.ErrorIsNil)
   386  	c.Assert(volIds, jc.SameContents, []string{"vol-0"})
   387  }
   388  
   389  func (s *ebsVolumeSuite) TestCreateVolumesErrors(c *gc.C) {
   390  	vs := s.volumeSource(c, nil)
   391  	volume0 := names.NewVolumeTag("0")
   392  
   393  	instanceIdPending := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil)[0]
   394  	instanceIdRunning := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil)[0]
   395  	attachmentParams := storage.VolumeAttachmentParams{
   396  		AttachmentParams: storage.AttachmentParams{
   397  			InstanceId: instance.Id(instanceIdRunning),
   398  		},
   399  	}
   400  
   401  	for _, test := range []struct {
   402  		params storage.VolumeParams
   403  		err    string
   404  	}{{
   405  		params: storage.VolumeParams{
   406  			Size:     1024,
   407  			Provider: ec2.EBS_ProviderType,
   408  			Attachment: &storage.VolumeAttachmentParams{
   409  				AttachmentParams: storage.AttachmentParams{
   410  					InstanceId: "woat",
   411  				},
   412  			},
   413  		},
   414  		err: `querying instance details: instance "woat" not found \(InvalidInstanceID.NotFound\)`,
   415  	}, {
   416  		params: storage.VolumeParams{
   417  			Size:     1024,
   418  			Provider: ec2.EBS_ProviderType,
   419  			Attachment: &storage.VolumeAttachmentParams{
   420  				AttachmentParams: storage.AttachmentParams{
   421  					InstanceId: instance.Id(instanceIdPending),
   422  				},
   423  			},
   424  		},
   425  		err: "cannot attach to non-running instance i-3",
   426  	}, {
   427  		params: storage.VolumeParams{
   428  			Size:       100000000,
   429  			Provider:   ec2.EBS_ProviderType,
   430  			Attributes: map[string]interface{}{},
   431  			Attachment: &attachmentParams,
   432  		},
   433  		err: "volume size 97657 GiB exceeds the maximum of 1024 GiB",
   434  	}, {
   435  		params: storage.VolumeParams{
   436  			Size:     100000000,
   437  			Provider: ec2.EBS_ProviderType,
   438  			Attributes: map[string]interface{}{
   439  				"volume-type": "gp2",
   440  			},
   441  			Attachment: &attachmentParams,
   442  		},
   443  		err: "volume size 97657 GiB exceeds the maximum of 16384 GiB",
   444  	}, {
   445  		params: storage.VolumeParams{
   446  			Size:     100000000,
   447  			Provider: ec2.EBS_ProviderType,
   448  			Attributes: map[string]interface{}{
   449  				"volume-type": "io1",
   450  				"iops":        "30",
   451  			},
   452  			Attachment: &attachmentParams,
   453  		},
   454  		err: "volume size 97657 GiB exceeds the maximum of 16384 GiB",
   455  	}, {
   456  		params: storage.VolumeParams{
   457  			Tag:      volume0,
   458  			Size:     1000,
   459  			Provider: ec2.EBS_ProviderType,
   460  			Attributes: map[string]interface{}{
   461  				"volume-type": "io1",
   462  				"iops":        "30",
   463  			},
   464  			Attachment: &attachmentParams,
   465  		},
   466  		err: "volume size is 1 GiB, must be at least 4 GiB",
   467  	}, {
   468  		params: storage.VolumeParams{
   469  			Tag:      volume0,
   470  			Size:     10000,
   471  			Provider: ec2.EBS_ProviderType,
   472  			Attributes: map[string]interface{}{
   473  				"volume-type": "io1",
   474  				"iops":        "1234",
   475  			},
   476  			Attachment: &attachmentParams,
   477  		},
   478  		err: "specified IOPS ratio is 1234/GiB, maximum is 30/GiB",
   479  	}, {
   480  		params: storage.VolumeParams{
   481  			Tag:      volume0,
   482  			Size:     10000,
   483  			Provider: ec2.EBS_ProviderType,
   484  			Attributes: map[string]interface{}{
   485  				"volume-type": "standard",
   486  				"iops":        "30",
   487  			},
   488  			Attachment: &attachmentParams,
   489  		},
   490  		err: `IOPS specified, but volume type is "standard"`,
   491  	}, {
   492  		params: storage.VolumeParams{
   493  			Tag:      volume0,
   494  			Size:     10000,
   495  			Provider: ec2.EBS_ProviderType,
   496  			Attributes: map[string]interface{}{
   497  				"volume-type": "what",
   498  			},
   499  			Attachment: &attachmentParams,
   500  		},
   501  		err: "validating EBS storage config: volume-type: unexpected value \"what\"",
   502  	}} {
   503  		results, err := vs.CreateVolumes([]storage.VolumeParams{test.params})
   504  		c.Assert(err, jc.ErrorIsNil)
   505  		c.Assert(results, gc.HasLen, 1)
   506  		c.Check(results[0].Error, gc.ErrorMatches, test.err)
   507  	}
   508  }
   509  
   510  var imageId = "ami-ccf405a5" // Ubuntu Maverick, i386, EBS store
   511  
   512  func (s *ebsVolumeSuite) setupAttachVolumesTest(
   513  	c *gc.C, vs storage.VolumeSource, state awsec2.InstanceState,
   514  ) []storage.VolumeAttachmentParams {
   515  
   516  	instanceId := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, state, nil)[0]
   517  	s.assertCreateVolumes(c, vs, instanceId)
   518  
   519  	return []storage.VolumeAttachmentParams{{
   520  		Volume:   names.NewVolumeTag("0"),
   521  		VolumeId: "vol-0",
   522  		AttachmentParams: storage.AttachmentParams{
   523  			Machine:    names.NewMachineTag("1"),
   524  			InstanceId: instance.Id(instanceId),
   525  		},
   526  	}}
   527  }
   528  
   529  func (s *ebsVolumeSuite) TestAttachVolumesNotRunning(c *gc.C) {
   530  	vs := s.volumeSource(c, nil)
   531  	instanceId := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil)[0]
   532  	results, err := s.createVolumes(vs, instanceId)
   533  	c.Assert(err, jc.ErrorIsNil)
   534  	c.Assert(results, gc.Not(gc.HasLen), 0)
   535  	for _, result := range results {
   536  		c.Check(errors.Cause(result.Error), gc.ErrorMatches, "cannot attach to non-running instance i-3")
   537  	}
   538  }
   539  
   540  func (s *ebsVolumeSuite) TestAttachVolumes(c *gc.C) {
   541  	vs := s.volumeSource(c, nil)
   542  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   543  	result, err := vs.AttachVolumes(params)
   544  	c.Assert(err, jc.ErrorIsNil)
   545  	c.Assert(result, gc.HasLen, 1)
   546  	c.Assert(result[0].Error, jc.ErrorIsNil)
   547  	c.Assert(result[0].VolumeAttachment, jc.DeepEquals, &storage.VolumeAttachment{
   548  		names.NewVolumeTag("0"),
   549  		names.NewMachineTag("1"),
   550  		storage.VolumeAttachmentInfo{
   551  			DeviceName: "xvdf",
   552  			ReadOnly:   false,
   553  		},
   554  	})
   555  
   556  	ec2Client := ec2.StorageEC2(vs)
   557  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   558  	c.Assert(err, jc.ErrorIsNil)
   559  	c.Assert(ec2Vols.Volumes, gc.HasLen, 3)
   560  	sortBySize(ec2Vols.Volumes)
   561  	c.Assert(ec2Vols.Volumes[0].Attachments, jc.DeepEquals, []awsec2.VolumeAttachment{{
   562  		VolumeId:   "vol-0",
   563  		InstanceId: "i-3",
   564  		Device:     "/dev/sdf",
   565  		Status:     "attached",
   566  	}})
   567  
   568  	// Test idempotency.
   569  	result, err = vs.AttachVolumes(params)
   570  	c.Assert(err, jc.ErrorIsNil)
   571  	c.Assert(result, gc.HasLen, 1)
   572  	c.Assert(result[0].Error, jc.ErrorIsNil)
   573  	c.Assert(result[0].VolumeAttachment, jc.DeepEquals, &storage.VolumeAttachment{
   574  		names.NewVolumeTag("0"),
   575  		names.NewMachineTag("1"),
   576  		storage.VolumeAttachmentInfo{
   577  			DeviceName: "xvdf",
   578  			ReadOnly:   false,
   579  		},
   580  	})
   581  }
   582  
   583  // TODO(axw) add tests for attempting to attach while
   584  // a volume is still in the "creating" state.
   585  
   586  func (s *ebsVolumeSuite) TestDetachVolumes(c *gc.C) {
   587  	vs := s.volumeSource(c, nil)
   588  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   589  	_, err := vs.AttachVolumes(params)
   590  	c.Assert(err, jc.ErrorIsNil)
   591  	errs, err := vs.DetachVolumes(params)
   592  	c.Assert(err, jc.ErrorIsNil)
   593  	c.Assert(errs, jc.DeepEquals, []error{nil})
   594  
   595  	ec2Client := ec2.StorageEC2(vs)
   596  	ec2Vols, err := ec2Client.Volumes(nil, nil)
   597  	c.Assert(err, jc.ErrorIsNil)
   598  	c.Assert(ec2Vols.Volumes, gc.HasLen, 3)
   599  	sortBySize(ec2Vols.Volumes)
   600  	c.Assert(ec2Vols.Volumes[0].Attachments, gc.HasLen, 0)
   601  
   602  	// Test idempotent
   603  	errs, err = vs.DetachVolumes(params)
   604  	c.Assert(err, jc.ErrorIsNil)
   605  	c.Assert(errs, jc.DeepEquals, []error{nil})
   606  }
   607  
   608  type blockDeviceMappingSuite struct {
   609  	testing.BaseSuite
   610  }
   611  
   612  var _ = gc.Suite(&blockDeviceMappingSuite{})
   613  
   614  func (*blockDeviceMappingSuite) TestBlockDeviceNamer(c *gc.C) {
   615  	var nextName func() (string, string, error)
   616  	expect := func(expectRequest, expectActual string) {
   617  		request, actual, err := nextName()
   618  		c.Assert(err, jc.ErrorIsNil)
   619  		c.Assert(request, gc.Equals, expectRequest)
   620  		c.Assert(actual, gc.Equals, expectActual)
   621  	}
   622  	expectN := func(expectRequest, expectActual string) {
   623  		for i := 1; i <= 6; i++ {
   624  			request, actual, err := nextName()
   625  			c.Assert(err, jc.ErrorIsNil)
   626  			c.Assert(request, gc.Equals, expectRequest+strconv.Itoa(i))
   627  			c.Assert(actual, gc.Equals, expectActual+strconv.Itoa(i))
   628  		}
   629  	}
   630  	expectErr := func(expectErr string) {
   631  		_, _, err := nextName()
   632  		c.Assert(err, gc.ErrorMatches, expectErr)
   633  	}
   634  
   635  	// First without numbers.
   636  	nextName = ec2.BlockDeviceNamer(false)
   637  	expect("/dev/sdf", "xvdf")
   638  	expect("/dev/sdg", "xvdg")
   639  	expect("/dev/sdh", "xvdh")
   640  	expect("/dev/sdi", "xvdi")
   641  	expect("/dev/sdj", "xvdj")
   642  	expect("/dev/sdk", "xvdk")
   643  	expect("/dev/sdl", "xvdl")
   644  	expect("/dev/sdm", "xvdm")
   645  	expect("/dev/sdn", "xvdn")
   646  	expect("/dev/sdo", "xvdo")
   647  	expect("/dev/sdp", "xvdp")
   648  	expectErr("too many EBS volumes to attach")
   649  
   650  	// Now with numbers.
   651  	nextName = ec2.BlockDeviceNamer(true)
   652  	expect("/dev/sdf1", "xvdf1")
   653  	expect("/dev/sdf2", "xvdf2")
   654  	expect("/dev/sdf3", "xvdf3")
   655  	expect("/dev/sdf4", "xvdf4")
   656  	expect("/dev/sdf5", "xvdf5")
   657  	expect("/dev/sdf6", "xvdf6")
   658  	expectN("/dev/sdg", "xvdg")
   659  	expectN("/dev/sdh", "xvdh")
   660  	expectN("/dev/sdi", "xvdi")
   661  	expectN("/dev/sdj", "xvdj")
   662  	expectN("/dev/sdk", "xvdk")
   663  	expectN("/dev/sdl", "xvdl")
   664  	expectN("/dev/sdm", "xvdm")
   665  	expectN("/dev/sdn", "xvdn")
   666  	expectN("/dev/sdo", "xvdo")
   667  	expectN("/dev/sdp", "xvdp")
   668  	expectErr("too many EBS volumes to attach")
   669  }
   670  
   671  func (*blockDeviceMappingSuite) TestGetBlockDeviceMappings(c *gc.C) {
   672  	mapping, err := ec2.GetBlockDeviceMappings(constraints.Value{})
   673  	c.Assert(err, jc.ErrorIsNil)
   674  	c.Assert(mapping, gc.DeepEquals, []awsec2.BlockDeviceMapping{{
   675  		VolumeSize: 8,
   676  		DeviceName: "/dev/sda1",
   677  	}, {
   678  		VirtualName: "ephemeral0",
   679  		DeviceName:  "/dev/sdb",
   680  	}, {
   681  		VirtualName: "ephemeral1",
   682  		DeviceName:  "/dev/sdc",
   683  	}, {
   684  		VirtualName: "ephemeral2",
   685  		DeviceName:  "/dev/sdd",
   686  	}, {
   687  		VirtualName: "ephemeral3",
   688  		DeviceName:  "/dev/sde",
   689  	}})
   690  }