github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	stdcontext "context"
     8  	"fmt"
     9  	"sort"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/aws/aws-sdk-go-v2/aws"
    14  	awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2"
    15  	"github.com/aws/aws-sdk-go-v2/service/ec2/types"
    16  	"github.com/aws/smithy-go"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/names/v5"
    19  	jc "github.com/juju/testing/checkers"
    20  	gc "gopkg.in/check.v1"
    21  
    22  	"github.com/juju/juju/cloud"
    23  	"github.com/juju/juju/core/constraints"
    24  	"github.com/juju/juju/core/instance"
    25  	"github.com/juju/juju/environs"
    26  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    27  	"github.com/juju/juju/environs/config"
    28  	"github.com/juju/juju/environs/context"
    29  	"github.com/juju/juju/environs/tags"
    30  	"github.com/juju/juju/provider/common"
    31  	"github.com/juju/juju/provider/ec2"
    32  	ec2test "github.com/juju/juju/provider/ec2/internal/testing"
    33  	"github.com/juju/juju/storage"
    34  	"github.com/juju/juju/testing"
    35  )
    36  
    37  type ebsSuite struct {
    38  	testing.BaseSuite
    39  	srv         localServer
    40  	modelConfig *config.Config
    41  
    42  	cloudCallCtx context.ProviderCallContext
    43  }
    44  
    45  var _ = gc.Suite(&ebsSuite{})
    46  
    47  func (s *ebsSuite) SetUpTest(c *gc.C) {
    48  	s.BaseSuite.SetUpTest(c)
    49  	s.PatchValue(&ec2.DestroyVolumeAttempt.Delay, time.Duration(0))
    50  
    51  	modelConfig, err := config.New(config.NoDefaults, testing.FakeConfig().Merge(
    52  		testing.Attrs{"type": "ec2"},
    53  	))
    54  	c.Assert(err, jc.ErrorIsNil)
    55  	s.modelConfig = modelConfig
    56  
    57  	s.srv.startServer(c)
    58  	s.AddCleanup(func(c *gc.C) { s.srv.stopServer(c) })
    59  
    60  	restoreEC2Patching := patchEC2ForTesting(c, s.srv.region)
    61  	s.AddCleanup(func(c *gc.C) { restoreEC2Patching() })
    62  
    63  	s.cloudCallCtx = context.NewEmptyCloudCallContext()
    64  }
    65  
    66  func (s *ebsSuite) ebsProvider(c *gc.C) storage.Provider {
    67  	provider, err := environs.Provider("ec2")
    68  	c.Assert(err, jc.ErrorIsNil)
    69  
    70  	credential := cloud.NewCredential(
    71  		cloud.AccessKeyAuthType,
    72  		map[string]string{
    73  			"access-key": "x",
    74  			"secret-key": "x",
    75  		},
    76  	)
    77  	clientFunc := func(ctx stdcontext.Context, spec environscloudspec.CloudSpec, options ...ec2.ClientOption) (ec2.Client, error) {
    78  		c.Assert(spec.Region, gc.Equals, "test")
    79  		return s.srv.ec2srv, nil
    80  	}
    81  
    82  	ctx := stdcontext.WithValue(s.cloudCallCtx, ec2.AWSClientContextKey, clientFunc)
    83  	env, err := environs.Open(ctx, provider, environs.OpenParams{
    84  		Cloud: environscloudspec.CloudSpec{
    85  			Type:       "ec2",
    86  			Name:       "ec2test",
    87  			Region:     *s.srv.region.RegionName,
    88  			Endpoint:   *s.srv.region.Endpoint,
    89  			Credential: &credential,
    90  		},
    91  		Config: s.modelConfig,
    92  	})
    93  	c.Assert(err, jc.ErrorIsNil)
    94  
    95  	p, err := env.StorageProvider(ec2.EBS_ProviderType)
    96  	c.Assert(err, jc.ErrorIsNil)
    97  	return p
    98  }
    99  
   100  func (s *ebsSuite) TestValidateConfigUnknownConfig(c *gc.C) {
   101  	p := s.ebsProvider(c)
   102  	cfg, err := storage.NewConfig("foo", ec2.EBS_ProviderType, map[string]interface{}{
   103  		"unknown": "config",
   104  	})
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	err = p.ValidateConfig(cfg)
   107  	c.Assert(err, jc.ErrorIsNil) // unknown attrs ignored
   108  }
   109  
   110  func (s *ebsSuite) TestSupports(c *gc.C) {
   111  	p := s.ebsProvider(c)
   112  	c.Assert(p.Supports(storage.StorageKindBlock), jc.IsTrue)
   113  	c.Assert(p.Supports(storage.StorageKindFilesystem), jc.IsFalse)
   114  }
   115  
   116  func (s *ebsSuite) volumeSource(c *gc.C, cfg *storage.Config) storage.VolumeSource {
   117  	p := s.ebsProvider(c)
   118  	vs, err := p.VolumeSource(cfg)
   119  	c.Assert(err, jc.ErrorIsNil)
   120  	return vs
   121  }
   122  
   123  func (s *ebsSuite) createVolumesParams(c *gc.C, instanceId string) []storage.VolumeParams {
   124  	if instanceId == "" {
   125  		inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil)
   126  		c.Assert(err, jc.ErrorIsNil)
   127  		instanceId = inst[0]
   128  	}
   129  	volume0 := names.NewVolumeTag("0")
   130  	volume1 := names.NewVolumeTag("1")
   131  	volume2 := names.NewVolumeTag("2")
   132  	volume3 := names.NewVolumeTag("3")
   133  	volume4 := names.NewVolumeTag("4")
   134  	volume5 := names.NewVolumeTag("5")
   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.modelConfig.UUID(),
   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  		Tag:      volume3,
   177  		Size:     40 * 1000,
   178  		Provider: ec2.EBS_ProviderType,
   179  		ResourceTags: map[string]string{
   180  			"volume-type": "st1",
   181  		},
   182  		Attachment: &storage.VolumeAttachmentParams{
   183  			AttachmentParams: storage.AttachmentParams{
   184  				InstanceId: instance.Id(instanceId),
   185  			},
   186  		},
   187  	}, {
   188  		Tag:      volume4,
   189  		Size:     50 * 1024,
   190  		Provider: ec2.EBS_ProviderType,
   191  		ResourceTags: map[string]string{
   192  			"volume-type": "sc1",
   193  		},
   194  		Attachment: &storage.VolumeAttachmentParams{
   195  			AttachmentParams: storage.AttachmentParams{
   196  				InstanceId: instance.Id(instanceId),
   197  			},
   198  		},
   199  	}, {
   200  		Tag:      volume5,
   201  		Size:     60 * 1024,
   202  		Provider: ec2.EBS_ProviderType,
   203  		ResourceTags: map[string]string{
   204  			"volume-type": "gp3",
   205  			"encrypted":   "true",
   206  			"kms-key-id":  "123456789",
   207  			"throughput":  "500M",
   208  		},
   209  		Attachment: &storage.VolumeAttachmentParams{
   210  			AttachmentParams: storage.AttachmentParams{
   211  				InstanceId: instance.Id(instanceId),
   212  			},
   213  		},
   214  	}}
   215  	return params
   216  }
   217  
   218  func (s *ebsSuite) createVolumes(c *gc.C, vs storage.VolumeSource, instanceId string) ([]storage.CreateVolumesResult, error) {
   219  	return vs.CreateVolumes(s.cloudCallCtx, s.createVolumesParams(c, instanceId))
   220  }
   221  
   222  func (s *ebsSuite) assertCreateVolumes(c *gc.C, vs storage.VolumeSource, instanceId string) {
   223  	results, err := s.createVolumes(c, vs, instanceId)
   224  	c.Assert(err, jc.ErrorIsNil)
   225  	c.Assert(results, gc.HasLen, 6)
   226  	c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{
   227  		names.NewVolumeTag("0"),
   228  		storage.VolumeInfo{
   229  			Size:       10240,
   230  			VolumeId:   "vol-0",
   231  			Persistent: true,
   232  		},
   233  	})
   234  	c.Assert(results[1].Volume, jc.DeepEquals, &storage.Volume{
   235  		names.NewVolumeTag("1"),
   236  		storage.VolumeInfo{
   237  			Size:       20480,
   238  			VolumeId:   "vol-1",
   239  			Persistent: true,
   240  		},
   241  	})
   242  	c.Assert(results[2].Volume, jc.DeepEquals, &storage.Volume{
   243  		names.NewVolumeTag("2"),
   244  		storage.VolumeInfo{
   245  			Size:       30720,
   246  			VolumeId:   "vol-2",
   247  			Persistent: true,
   248  		},
   249  	})
   250  	c.Assert(results[3].Volume, jc.DeepEquals, &storage.Volume{
   251  		names.NewVolumeTag("3"),
   252  		storage.VolumeInfo{
   253  			Size:       40960,
   254  			VolumeId:   "vol-3",
   255  			Persistent: true,
   256  		},
   257  	})
   258  	c.Assert(results[4].Volume, jc.DeepEquals, &storage.Volume{
   259  		names.NewVolumeTag("4"),
   260  		storage.VolumeInfo{
   261  			Size:       51200,
   262  			VolumeId:   "vol-4",
   263  			Persistent: true,
   264  		},
   265  	})
   266  	c.Assert(results[5].Volume, jc.DeepEquals, &storage.Volume{
   267  		names.NewVolumeTag("5"),
   268  		storage.VolumeInfo{
   269  			Size:       61440,
   270  			VolumeId:   "vol-5",
   271  			Persistent: true,
   272  		},
   273  	})
   274  	ec2Client := ec2.StorageEC2(vs)
   275  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil)
   276  	c.Assert(err, jc.ErrorIsNil)
   277  	c.Assert(ec2Vols.Volumes, gc.HasLen, 6)
   278  	sortBySize(ec2Vols.Volumes)
   279  	c.Assert(aws.ToInt32(ec2Vols.Volumes[0].Size), gc.Equals, int32(10))
   280  	c.Assert(aws.ToInt32(ec2Vols.Volumes[1].Size), gc.Equals, int32(20))
   281  	c.Assert(aws.ToInt32(ec2Vols.Volumes[2].Size), gc.Equals, int32(30))
   282  	c.Assert(aws.ToInt32(ec2Vols.Volumes[3].Size), gc.Equals, int32(40))
   283  	c.Assert(aws.ToInt32(ec2Vols.Volumes[4].Size), gc.Equals, int32(50))
   284  	c.Assert(aws.ToInt32(ec2Vols.Volumes[5].Size), gc.Equals, int32(60))
   285  }
   286  
   287  type volumeSorter struct {
   288  	vols []types.Volume
   289  	less func(i, j types.Volume) bool
   290  }
   291  
   292  func sortBySize(vols []types.Volume) {
   293  	sort.Sort(volumeSorter{vols, func(i, j types.Volume) bool {
   294  		return aws.ToInt32(i.Size) < aws.ToInt32(j.Size)
   295  	}})
   296  }
   297  
   298  func (s volumeSorter) Len() int {
   299  	return len(s.vols)
   300  }
   301  
   302  func (s volumeSorter) Swap(i, j int) {
   303  	s.vols[i], s.vols[j] = s.vols[j], s.vols[i]
   304  }
   305  
   306  func (s volumeSorter) Less(i, j int) bool {
   307  	return s.less(s.vols[i], s.vols[j])
   308  }
   309  
   310  func (s *ebsSuite) TestCreateVolumes(c *gc.C) {
   311  	vs := s.volumeSource(c, nil)
   312  	s.assertCreateVolumes(c, vs, "")
   313  }
   314  
   315  func (s *ebsSuite) TestVolumeTags(c *gc.C) {
   316  	vs := s.volumeSource(c, nil)
   317  	results, err := s.createVolumes(c, vs, "")
   318  	c.Assert(err, jc.ErrorIsNil)
   319  	c.Assert(results, gc.HasLen, 6)
   320  	c.Assert(results[0].Error, jc.ErrorIsNil)
   321  	c.Assert(results[0].Volume, jc.DeepEquals, &storage.Volume{
   322  		names.NewVolumeTag("0"),
   323  		storage.VolumeInfo{
   324  			Size:       10240,
   325  			VolumeId:   "vol-0",
   326  			Persistent: true,
   327  		},
   328  	})
   329  	c.Assert(results[1].Error, jc.ErrorIsNil)
   330  	c.Assert(results[1].Volume, jc.DeepEquals, &storage.Volume{
   331  		names.NewVolumeTag("1"),
   332  		storage.VolumeInfo{
   333  			Size:       20480,
   334  			VolumeId:   "vol-1",
   335  			Persistent: true,
   336  		},
   337  	})
   338  	c.Assert(results[2].Error, jc.ErrorIsNil)
   339  	c.Assert(results[2].Volume, jc.DeepEquals, &storage.Volume{
   340  		names.NewVolumeTag("2"),
   341  		storage.VolumeInfo{
   342  			Size:       30720,
   343  			VolumeId:   "vol-2",
   344  			Persistent: true,
   345  		},
   346  	})
   347  	c.Assert(results[3].Error, jc.ErrorIsNil)
   348  	c.Assert(results[3].Volume, jc.DeepEquals, &storage.Volume{
   349  		names.NewVolumeTag("3"),
   350  		storage.VolumeInfo{
   351  			Size:       40960,
   352  			VolumeId:   "vol-3",
   353  			Persistent: true,
   354  		},
   355  	})
   356  	c.Assert(results[4].Error, jc.ErrorIsNil)
   357  	c.Assert(results[4].Volume, jc.DeepEquals, &storage.Volume{
   358  		names.NewVolumeTag("4"),
   359  		storage.VolumeInfo{
   360  			Size:       51200,
   361  			VolumeId:   "vol-4",
   362  			Persistent: true,
   363  		},
   364  	})
   365  	c.Assert(results[5].Error, jc.ErrorIsNil)
   366  	c.Assert(results[5].Volume, jc.DeepEquals, &storage.Volume{
   367  		names.NewVolumeTag("5"),
   368  		storage.VolumeInfo{
   369  			Size:       61440,
   370  			VolumeId:   "vol-5",
   371  			Persistent: true,
   372  		},
   373  	})
   374  	ec2Client := ec2.StorageEC2(vs)
   375  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil)
   376  	c.Assert(err, jc.ErrorIsNil)
   377  	c.Assert(ec2Vols.Volumes, gc.HasLen, 6)
   378  	sortBySize(ec2Vols.Volumes)
   379  	compareTags(c, ec2Vols.Volumes[0].Tags, []tagInfo{
   380  		{"juju-model-uuid", "deadbeef-0bad-400d-8000-4b1d0d06f00d"},
   381  		{"Name", "juju-testmodel-volume-0"},
   382  	})
   383  	compareTags(c, ec2Vols.Volumes[1].Tags, []tagInfo{
   384  		{"juju-model-uuid", "something-else"},
   385  		{"Name", "juju-testmodel-volume-1"},
   386  	})
   387  	compareTags(c, ec2Vols.Volumes[2].Tags, []tagInfo{
   388  		{"Name", "juju-testmodel-volume-2"},
   389  		{"abc", "123"},
   390  	})
   391  	compareTags(c, ec2Vols.Volumes[3].Tags, []tagInfo{
   392  		{"Name", "juju-testmodel-volume-3"},
   393  		{"volume-type", "st1"},
   394  	})
   395  	compareTags(c, ec2Vols.Volumes[4].Tags, []tagInfo{
   396  		{"Name", "juju-testmodel-volume-4"},
   397  		{"volume-type", "sc1"},
   398  	})
   399  	compareTags(c, ec2Vols.Volumes[5].Tags, []tagInfo{
   400  		{"Name", "juju-testmodel-volume-5"},
   401  		{"volume-type", "gp3"},
   402  		{"encrypted", "true"},
   403  		{"kms-key-id", "123456789"},
   404  		{"throughput", "500M"},
   405  	})
   406  }
   407  
   408  func (s *ebsSuite) TestVolumeTypeAliases(c *gc.C) {
   409  	inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil)
   410  	c.Assert(err, jc.ErrorIsNil)
   411  	instanceIdRunning := inst[0]
   412  	vs := s.volumeSource(c, nil)
   413  	ec2Client := ec2.StorageEC2(vs)
   414  	aliases := [][2]string{
   415  		{"magnetic", "standard"},
   416  		{"cold-storage", "sc1"},
   417  		{"optimized-hdd", "st1"},
   418  		{"ssd", "gp2"},
   419  		{"provisioned-iops", "io1"},
   420  	}
   421  	for i, alias := range aliases {
   422  		params := []storage.VolumeParams{{
   423  			Tag:      names.NewVolumeTag("0"),
   424  			Size:     500 * 1024,
   425  			Provider: ec2.EBS_ProviderType,
   426  			Attributes: map[string]interface{}{
   427  				"volume-type": alias[0],
   428  			},
   429  			Attachment: &storage.VolumeAttachmentParams{
   430  				AttachmentParams: storage.AttachmentParams{
   431  					InstanceId: instance.Id(instanceIdRunning),
   432  				},
   433  			},
   434  		}}
   435  		if alias[1] == "io1" {
   436  			params[0].Attributes["iops"] = 30
   437  		}
   438  		results, err := vs.CreateVolumes(s.cloudCallCtx, params)
   439  		c.Assert(err, jc.ErrorIsNil)
   440  		c.Assert(results, gc.HasLen, 1)
   441  		c.Assert(results[0].Error, jc.ErrorIsNil)
   442  		c.Assert(results[0].Volume.VolumeId, gc.Equals, fmt.Sprintf("vol-%d", i))
   443  	}
   444  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil)
   445  	c.Assert(err, jc.ErrorIsNil)
   446  	c.Assert(ec2Vols.Volumes, gc.HasLen, len(aliases))
   447  	sort.Sort(volumeSorter{ec2Vols.Volumes, func(i, j types.Volume) bool {
   448  		return aws.ToString(i.VolumeId) < aws.ToString(j.VolumeId)
   449  	}})
   450  	for i, alias := range aliases {
   451  		c.Assert(string(ec2Vols.Volumes[i].VolumeType), gc.Equals, alias[1])
   452  	}
   453  }
   454  
   455  func (s *ebsSuite) TestDestroyVolumesNotFoundReturnsNil(c *gc.C) {
   456  	vs := s.volumeSource(c, nil)
   457  	results, err := vs.DestroyVolumes(s.cloudCallCtx, []string{"vol-42"})
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	c.Assert(results, gc.HasLen, 1)
   460  	c.Assert(results[0], jc.ErrorIsNil)
   461  }
   462  
   463  func (s *ebsSuite) TestDestroyVolumesCredentialError(c *gc.C) {
   464  	vs := s.volumeSource(c, nil)
   465  	s.setupAttachVolumesTest(c, vs, ec2test.Running)
   466  
   467  	s.srv.ec2srv.SetAPIError("DeleteVolume", &smithy.GenericAPIError{Code: "Blocked"})
   468  
   469  	in := []string{"vol-0"}
   470  	results, err := vs.DestroyVolumes(s.cloudCallCtx, in)
   471  	c.Assert(err, jc.ErrorIsNil)
   472  	c.Assert(results, gc.HasLen, len(in))
   473  	for i, result := range results {
   474  		c.Logf("checking volume deletion %d", i)
   475  		c.Assert(errors.Is(result, common.ErrorCredentialNotValid), jc.IsTrue)
   476  	}
   477  }
   478  
   479  func (s *ebsSuite) TestDestroyVolumes(c *gc.C) {
   480  	vs := s.volumeSource(c, nil)
   481  	s.setupAttachVolumesTest(c, vs, ec2test.Running)
   482  	errs, err := vs.DestroyVolumes(s.cloudCallCtx, []string{"vol-0"})
   483  	c.Assert(err, jc.ErrorIsNil)
   484  	c.Assert(errs, jc.DeepEquals, []error{nil})
   485  
   486  	ec2Client := ec2.StorageEC2(vs)
   487  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil)
   488  	c.Assert(err, jc.ErrorIsNil)
   489  	c.Assert(ec2Vols.Volumes, gc.HasLen, 5)
   490  	sortBySize(ec2Vols.Volumes)
   491  	c.Assert(aws.ToInt32(ec2Vols.Volumes[0].Size), gc.Equals, int32(20))
   492  	c.Assert(aws.ToInt32(ec2Vols.Volumes[1].Size), gc.Equals, int32(30))
   493  	c.Assert(aws.ToInt32(ec2Vols.Volumes[2].Size), gc.Equals, int32(40))
   494  	c.Assert(aws.ToInt32(ec2Vols.Volumes[3].Size), gc.Equals, int32(50))
   495  }
   496  
   497  func (s *ebsSuite) TestDestroyVolumesStillAttached(c *gc.C) {
   498  	vs := s.volumeSource(c, nil)
   499  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   500  	_, err := vs.AttachVolumes(s.cloudCallCtx, params)
   501  	c.Assert(err, jc.ErrorIsNil)
   502  	errs, err := vs.DestroyVolumes(s.cloudCallCtx, []string{"vol-0"})
   503  	c.Assert(err, jc.ErrorIsNil)
   504  	c.Assert(errs, jc.DeepEquals, []error{nil})
   505  
   506  	ec2Client := ec2.StorageEC2(vs)
   507  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil)
   508  	c.Assert(err, jc.ErrorIsNil)
   509  	c.Assert(ec2Vols.Volumes, gc.HasLen, 5)
   510  	sortBySize(ec2Vols.Volumes)
   511  	c.Assert(aws.ToInt32(ec2Vols.Volumes[0].Size), gc.Equals, int32(20))
   512  	c.Assert(aws.ToInt32(ec2Vols.Volumes[2].Size), gc.Equals, int32(40))
   513  	c.Assert(aws.ToInt32(ec2Vols.Volumes[3].Size), gc.Equals, int32(50))
   514  }
   515  
   516  func (s *ebsSuite) TestReleaseVolumes(c *gc.C) {
   517  	vs := s.volumeSource(c, nil)
   518  	s.setupAttachVolumesTest(c, vs, ec2test.Running)
   519  	errs, err := vs.ReleaseVolumes(s.cloudCallCtx, []string{"vol-0"})
   520  	c.Assert(err, jc.ErrorIsNil)
   521  	c.Assert(errs, jc.DeepEquals, []error{nil})
   522  
   523  	ec2Client := ec2.StorageEC2(vs)
   524  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, &awsec2.DescribeVolumesInput{
   525  		VolumeIds: []string{"vol-0"},
   526  	})
   527  	c.Assert(err, jc.ErrorIsNil)
   528  	c.Assert(ec2Vols.Volumes, gc.HasLen, 1)
   529  	compareTags(c, ec2Vols.Volumes[0].Tags, []tagInfo{
   530  		{"juju-controller-uuid", ""},
   531  		{"juju-model-uuid", ""},
   532  		{"Name", "juju-testmodel-volume-0"},
   533  	})
   534  }
   535  
   536  func (s *ebsSuite) TestReleaseVolumesCredentialError(c *gc.C) {
   537  	vs := s.volumeSource(c, nil)
   538  	s.setupAttachVolumesTest(c, vs, ec2test.Running)
   539  
   540  	s.srv.ec2srv.SetAPIError("DescribeVolumes", &smithy.GenericAPIError{Code: "Blocked"})
   541  	in := []string{"vol-0"}
   542  	results, err := vs.ReleaseVolumes(s.cloudCallCtx, in)
   543  	c.Assert(err, jc.ErrorIsNil)
   544  	c.Assert(results, gc.HasLen, len(in))
   545  	for i, result := range results {
   546  		c.Logf("checking volume release %d", i)
   547  		c.Assert(errors.Is(result, common.ErrorCredentialNotValid), jc.IsTrue)
   548  	}
   549  }
   550  
   551  func (s *ebsSuite) TestReleaseVolumesStillAttached(c *gc.C) {
   552  	vs := s.volumeSource(c, nil)
   553  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   554  	_, err := vs.AttachVolumes(s.cloudCallCtx, params)
   555  	c.Assert(err, jc.ErrorIsNil)
   556  	errs, err := vs.ReleaseVolumes(s.cloudCallCtx, []string{"vol-0"})
   557  	c.Assert(err, jc.ErrorIsNil)
   558  	c.Assert(errs, gc.HasLen, 1)
   559  	c.Assert(errs[0], gc.ErrorMatches, `cannot release volume "vol-0": attachments still active`)
   560  
   561  	ec2Client := ec2.StorageEC2(vs)
   562  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, &awsec2.DescribeVolumesInput{
   563  		VolumeIds: []string{"vol-0"},
   564  	})
   565  	c.Assert(err, jc.ErrorIsNil)
   566  	c.Assert(ec2Vols.Volumes, gc.HasLen, 1)
   567  	compareTags(c, ec2Vols.Volumes[0].Tags, []tagInfo{
   568  		{"juju-model-uuid", "deadbeef-0bad-400d-8000-4b1d0d06f00d"},
   569  		{"Name", "juju-testmodel-volume-0"},
   570  	})
   571  }
   572  
   573  func (s *ebsSuite) TestAttachVolumesCredentialError(c *gc.C) {
   574  	vs := s.volumeSource(c, nil)
   575  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   576  
   577  	s.srv.ec2srv.SetAPIError("AttachVolume", &smithy.GenericAPIError{Code: "Blocked"})
   578  
   579  	results, err := vs.AttachVolumes(s.cloudCallCtx, params)
   580  	c.Assert(err, jc.ErrorIsNil)
   581  	c.Assert(results, gc.HasLen, 1)
   582  	c.Assert(errors.Is(results[0].Error, common.ErrorCredentialNotValid), jc.IsTrue)
   583  }
   584  
   585  func (s *ebsSuite) TestReleaseVolumesNotFound(c *gc.C) {
   586  	vs := s.volumeSource(c, nil)
   587  	errs, err := vs.ReleaseVolumes(s.cloudCallCtx, []string{"vol-42"})
   588  	c.Assert(err, jc.ErrorIsNil)
   589  	c.Assert(errs, gc.HasLen, 1)
   590  	c.Assert(errs[0], gc.ErrorMatches, `cannot release volume "vol-42": vol-42 not found`)
   591  }
   592  
   593  func (s *ebsSuite) TestDescribeVolumes(c *gc.C) {
   594  	vs := s.volumeSource(c, nil)
   595  	s.assertCreateVolumes(c, vs, "")
   596  
   597  	vols, err := vs.DescribeVolumes(s.cloudCallCtx, []string{"vol-0", "vol-1"})
   598  	c.Assert(err, jc.ErrorIsNil)
   599  	c.Assert(vols, jc.DeepEquals, []storage.DescribeVolumesResult{{
   600  		VolumeInfo: &storage.VolumeInfo{
   601  			Size:       10240,
   602  			VolumeId:   "vol-0",
   603  			Persistent: true,
   604  		},
   605  	}, {
   606  		VolumeInfo: &storage.VolumeInfo{
   607  			Size:       20480,
   608  			VolumeId:   "vol-1",
   609  			Persistent: true,
   610  		},
   611  	}})
   612  }
   613  
   614  func (s *ebsSuite) TestDescribeVolumesNotFound(c *gc.C) {
   615  	vs := s.volumeSource(c, nil)
   616  	vols, err := vs.DescribeVolumes(s.cloudCallCtx, []string{"vol-42"})
   617  	c.Assert(err, jc.ErrorIsNil)
   618  	c.Assert(vols, gc.HasLen, 1)
   619  	c.Assert(vols[0].Error, gc.ErrorMatches, "vol-42 not found")
   620  }
   621  
   622  func (s *ebsSuite) TestDescribeVolumesCredentialError(c *gc.C) {
   623  	vs := s.volumeSource(c, nil)
   624  
   625  	s.srv.ec2srv.SetAPIError("DescribeVolumes", &smithy.GenericAPIError{Code: "Blocked"})
   626  
   627  	results, err := vs.DescribeVolumes(s.cloudCallCtx, []string{"vol-42"})
   628  	c.Assert(errors.Is(err, common.ErrorCredentialNotValid), jc.IsTrue)
   629  	c.Assert(results, gc.IsNil)
   630  }
   631  
   632  func (s *ebsSuite) TestListVolumes(c *gc.C) {
   633  	vs := s.volumeSource(c, nil)
   634  	s.assertCreateVolumes(c, vs, "")
   635  
   636  	// Only one volume created by assertCreateVolumes has
   637  	// the model-uuid tag with the expected value.
   638  	volIds, err := vs.ListVolumes(s.cloudCallCtx)
   639  	c.Assert(err, jc.ErrorIsNil)
   640  	c.Assert(volIds, jc.SameContents, []string{"vol-0"})
   641  }
   642  
   643  func (s *ebsSuite) TestListVolumesCredentialError(c *gc.C) {
   644  	vs := s.volumeSource(c, nil)
   645  
   646  	s.srv.ec2srv.SetAPIError("DescribeVolumes", &smithy.GenericAPIError{Code: "Blocked"})
   647  
   648  	results, err := vs.ListVolumes(s.cloudCallCtx)
   649  	c.Assert(errors.Is(err, common.ErrorCredentialNotValid), jc.IsTrue)
   650  	c.Assert(results, gc.IsNil)
   651  }
   652  
   653  func (s *ebsSuite) TestListVolumesIgnoresRootDisks(c *gc.C) {
   654  	s.srv.ec2srv.SetCreateRootDisks(true)
   655  	s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil)
   656  
   657  	// Tag the root disk with the model UUID.
   658  	_, err := s.srv.ec2srv.CreateTags(s.cloudCallCtx, &awsec2.CreateTagsInput{
   659  		Resources: []string{"vol-0"},
   660  		Tags: []types.Tag{
   661  			{Key: aws.String(tags.JujuModel), Value: aws.String(s.modelConfig.UUID())},
   662  		},
   663  	})
   664  	c.Assert(err, jc.ErrorIsNil)
   665  
   666  	vs := s.volumeSource(c, nil)
   667  	volIds, err := vs.ListVolumes(s.cloudCallCtx)
   668  	c.Assert(err, jc.ErrorIsNil)
   669  	c.Assert(volIds, gc.HasLen, 0)
   670  }
   671  
   672  func (s *ebsSuite) TestCreateVolumesErrors(c *gc.C) {
   673  	vs := s.volumeSource(c, nil)
   674  	volume0 := names.NewVolumeTag("0")
   675  
   676  	inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil)
   677  	c.Assert(err, jc.ErrorIsNil)
   678  	instanceIdPending := inst[0]
   679  	inst, err = s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Running, nil)
   680  	instanceIdRunning := inst[0]
   681  	attachmentParams := storage.VolumeAttachmentParams{
   682  		AttachmentParams: storage.AttachmentParams{
   683  			InstanceId: instance.Id(instanceIdRunning),
   684  		},
   685  	}
   686  
   687  	for _, test := range []struct {
   688  		params storage.VolumeParams
   689  		err    string
   690  	}{{
   691  		params: storage.VolumeParams{
   692  			Size:     1024,
   693  			Provider: ec2.EBS_ProviderType,
   694  			Attachment: &storage.VolumeAttachmentParams{
   695  				AttachmentParams: storage.AttachmentParams{
   696  					InstanceId: "woat",
   697  				},
   698  			},
   699  		},
   700  		err: `querying instance details: api error InvalidInstanceID.NotFound: instance "woat" not found`,
   701  	}, {
   702  		params: storage.VolumeParams{
   703  			Size:     1024,
   704  			Provider: ec2.EBS_ProviderType,
   705  			Attachment: &storage.VolumeAttachmentParams{
   706  				AttachmentParams: storage.AttachmentParams{
   707  					InstanceId: instance.Id(instanceIdPending),
   708  				},
   709  			},
   710  		},
   711  		err: "cannot attach to non-running instance i-3",
   712  	}, {
   713  		params: storage.VolumeParams{
   714  			Size:       100000000,
   715  			Provider:   ec2.EBS_ProviderType,
   716  			Attributes: map[string]interface{}{},
   717  			Attachment: &attachmentParams,
   718  		},
   719  		err: "volume size 97657 GiB exceeds the maximum of 16384 GiB",
   720  	}, {
   721  		params: storage.VolumeParams{
   722  			Size:     100000000,
   723  			Provider: ec2.EBS_ProviderType,
   724  			Attributes: map[string]interface{}{
   725  				"volume-type": "gp2",
   726  			},
   727  			Attachment: &attachmentParams,
   728  		},
   729  		err: "volume size 97657 GiB exceeds the maximum of 16384 GiB",
   730  	}, {
   731  		params: storage.VolumeParams{
   732  			Size:     100000000,
   733  			Provider: ec2.EBS_ProviderType,
   734  			Attributes: map[string]interface{}{
   735  				"volume-type": "io1",
   736  				"iops":        "30",
   737  			},
   738  			Attachment: &attachmentParams,
   739  		},
   740  		err: "volume size 97657 GiB exceeds the maximum of 16384 GiB",
   741  	}, {
   742  		params: storage.VolumeParams{
   743  			Tag:      volume0,
   744  			Size:     1000,
   745  			Provider: ec2.EBS_ProviderType,
   746  			Attributes: map[string]interface{}{
   747  				"volume-type": "io1",
   748  				"iops":        "30",
   749  			},
   750  			Attachment: &attachmentParams,
   751  		},
   752  		err: "volume size is 1 GiB, must be at least 4 GiB",
   753  	}, {
   754  		params: storage.VolumeParams{
   755  			Tag:      volume0,
   756  			Size:     10000,
   757  			Provider: ec2.EBS_ProviderType,
   758  			Attributes: map[string]interface{}{
   759  				"volume-type": "io1",
   760  				"iops":        "1234",
   761  			},
   762  			Attachment: &attachmentParams,
   763  		},
   764  		err: "specified IOPS ratio is 1234/GiB, maximum is 30/GiB",
   765  	}, {
   766  		params: storage.VolumeParams{
   767  			Tag:      volume0,
   768  			Size:     10000,
   769  			Provider: ec2.EBS_ProviderType,
   770  			Attributes: map[string]interface{}{
   771  				"volume-type": "standard",
   772  				"iops":        "30",
   773  			},
   774  			Attachment: &attachmentParams,
   775  		},
   776  		err: `IOPS specified, but volume type is "standard"`,
   777  	}, {
   778  		params: storage.VolumeParams{
   779  			Tag:      volume0,
   780  			Size:     10000,
   781  			Provider: ec2.EBS_ProviderType,
   782  			Attributes: map[string]interface{}{
   783  				"volume-type": "what",
   784  			},
   785  			Attachment: &attachmentParams,
   786  		},
   787  		err: "validating EBS storage config: volume-type: unexpected value \"what\"",
   788  	}, {
   789  		params: storage.VolumeParams{
   790  			Tag:      volume0,
   791  			Size:     400 * 1024,
   792  			Provider: ec2.EBS_ProviderType,
   793  			Attributes: map[string]interface{}{
   794  				"volume-type": "st1",
   795  			},
   796  			Attachment: &attachmentParams,
   797  		},
   798  		err: "volume size is 400 GiB, must be at least 500 GiB",
   799  	}, {
   800  		params: storage.VolumeParams{
   801  			Tag:      volume0,
   802  			Size:     17 * 1024 * 1024,
   803  			Provider: ec2.EBS_ProviderType,
   804  			Attributes: map[string]interface{}{
   805  				"volume-type": "st1",
   806  			},
   807  			Attachment: &attachmentParams,
   808  		},
   809  		err: "volume size 17408 GiB exceeds the maximum of 16384 GiB",
   810  	}, {
   811  		params: storage.VolumeParams{
   812  			Tag:      volume0,
   813  			Size:     10000,
   814  			Provider: ec2.EBS_ProviderType,
   815  			Attributes: map[string]interface{}{
   816  				"volume-type": "st1",
   817  				"iops":        "30",
   818  			},
   819  			Attachment: &attachmentParams,
   820  		},
   821  		err: `IOPS specified, but volume type is "st1"`,
   822  	}, {
   823  		params: storage.VolumeParams{
   824  			Tag:      volume0,
   825  			Size:     300 * 1024,
   826  			Provider: ec2.EBS_ProviderType,
   827  			Attributes: map[string]interface{}{
   828  				"volume-type": "sc1",
   829  			},
   830  			Attachment: &attachmentParams,
   831  		},
   832  		err: "volume size is 300 GiB, must be at least 500 GiB",
   833  	}, {
   834  		params: storage.VolumeParams{
   835  			Tag:      volume0,
   836  			Size:     18 * 1024 * 1024,
   837  			Provider: ec2.EBS_ProviderType,
   838  			Attributes: map[string]interface{}{
   839  				"volume-type": "sc1",
   840  			},
   841  			Attachment: &attachmentParams,
   842  		},
   843  		err: "volume size 18432 GiB exceeds the maximum of 16384 GiB",
   844  	}, {
   845  		params: storage.VolumeParams{
   846  			Tag:      volume0,
   847  			Size:     10000,
   848  			Provider: ec2.EBS_ProviderType,
   849  			Attributes: map[string]interface{}{
   850  				"volume-type": "sc1",
   851  				"iops":        "30",
   852  			},
   853  			Attachment: &attachmentParams,
   854  		},
   855  		err: `IOPS specified, but volume type is "sc1"`,
   856  	}, {
   857  		params: storage.VolumeParams{
   858  			Tag:      volume0,
   859  			Size:     10000,
   860  			Provider: ec2.EBS_ProviderType,
   861  			Attributes: map[string]interface{}{
   862  				"volume-type": "gp2",
   863  				"throughput":  "30",
   864  			},
   865  			Attachment: &attachmentParams,
   866  		},
   867  		err: `"throughput" cannot be specified when volume type is "gp2"`,
   868  	}} {
   869  		results, err := vs.CreateVolumes(s.cloudCallCtx, []storage.VolumeParams{test.params})
   870  		c.Assert(err, jc.ErrorIsNil)
   871  		c.Assert(results, gc.HasLen, 1)
   872  		c.Check(results[0].Error, gc.ErrorMatches, test.err)
   873  	}
   874  }
   875  
   876  func (s *ebsSuite) TestCreateVolumesCredentialError(c *gc.C) {
   877  	vs := s.volumeSource(c, nil)
   878  	params := s.createVolumesParams(c, "")
   879  
   880  	s.srv.ec2srv.SetAPIError("CreateVolume", &smithy.GenericAPIError{Code: "Blocked"})
   881  
   882  	results, err := vs.CreateVolumes(s.cloudCallCtx, params)
   883  	c.Assert(err, jc.ErrorIsNil)
   884  	for i, result := range results {
   885  		c.Logf("checking volume creation %d", i)
   886  		c.Assert(result.Volume, gc.IsNil)
   887  		c.Assert(result.VolumeAttachment, gc.IsNil)
   888  		c.Assert(errors.Is(result.Error, common.ErrorCredentialNotValid), jc.IsTrue)
   889  	}
   890  }
   891  
   892  var imageId = "ami-ccf405a5" // Ubuntu Maverick, i386, EBS store
   893  
   894  func (s *ebsSuite) setupAttachVolumesTest(
   895  	c *gc.C, vs storage.VolumeSource, state types.InstanceState,
   896  ) []storage.VolumeAttachmentParams {
   897  
   898  	inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, state, nil)
   899  	c.Assert(err, jc.ErrorIsNil)
   900  	instanceId := inst[0]
   901  	s.assertCreateVolumes(c, vs, instanceId)
   902  
   903  	return []storage.VolumeAttachmentParams{{
   904  		Volume:   names.NewVolumeTag("0"),
   905  		VolumeId: "vol-0",
   906  		AttachmentParams: storage.AttachmentParams{
   907  			Machine:    names.NewMachineTag("1"),
   908  			InstanceId: instance.Id(instanceId),
   909  		},
   910  	}}
   911  }
   912  
   913  func (s *ebsSuite) TestAttachVolumesNotRunning(c *gc.C) {
   914  	vs := s.volumeSource(c, nil)
   915  	inst, err := s.srv.ec2srv.NewInstances(1, "m1.medium", imageId, ec2test.Pending, nil)
   916  	c.Assert(err, jc.ErrorIsNil)
   917  	results, err := s.createVolumes(c, vs, inst[0])
   918  	c.Assert(err, jc.ErrorIsNil)
   919  	c.Assert(results, gc.Not(gc.HasLen), 0)
   920  	for _, result := range results {
   921  		c.Check(errors.Cause(result.Error), gc.ErrorMatches, "cannot attach to non-running instance i-3")
   922  	}
   923  }
   924  
   925  func (s *ebsSuite) TestAttachVolumes(c *gc.C) {
   926  	vs := s.volumeSource(c, nil)
   927  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   928  	result, err := vs.AttachVolumes(s.cloudCallCtx, params)
   929  	c.Assert(err, jc.ErrorIsNil)
   930  	c.Assert(result, gc.HasLen, 1)
   931  	c.Assert(result[0].Error, jc.ErrorIsNil)
   932  	c.Assert(result[0].VolumeAttachment, jc.DeepEquals, &storage.VolumeAttachment{
   933  		names.NewVolumeTag("0"),
   934  		names.NewMachineTag("1"),
   935  		storage.VolumeAttachmentInfo{
   936  			DeviceName: "xvdf",
   937  			DeviceLink: "/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0",
   938  			ReadOnly:   false,
   939  		},
   940  	})
   941  
   942  	ec2Client := ec2.StorageEC2(vs)
   943  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil)
   944  	c.Assert(err, jc.ErrorIsNil)
   945  	c.Assert(ec2Vols.Volumes, gc.HasLen, 6)
   946  	sortBySize(ec2Vols.Volumes)
   947  	c.Assert(ec2Vols.Volumes[0].Attachments, jc.DeepEquals, []types.VolumeAttachment{{
   948  		VolumeId:   aws.String("vol-0"),
   949  		InstanceId: aws.String("i-3"),
   950  		Device:     aws.String("/dev/sdf"),
   951  		State:      "attached",
   952  	}})
   953  
   954  	// Test idempotency.
   955  	result, err = vs.AttachVolumes(s.cloudCallCtx, params)
   956  	c.Assert(err, jc.ErrorIsNil)
   957  	c.Assert(result, gc.HasLen, 1)
   958  	c.Assert(result[0].Error, jc.ErrorIsNil)
   959  	c.Assert(result[0].VolumeAttachment, jc.DeepEquals, &storage.VolumeAttachment{
   960  		names.NewVolumeTag("0"),
   961  		names.NewMachineTag("1"),
   962  		storage.VolumeAttachmentInfo{
   963  			DeviceName: "xvdf",
   964  			DeviceLink: "/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0",
   965  			ReadOnly:   false,
   966  		},
   967  	})
   968  }
   969  
   970  func (s *ebsSuite) TestAttachVolumesCreating(c *gc.C) {
   971  	vs := s.volumeSource(c, nil)
   972  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   973  	var calls int
   974  	s.srv.ec2srv.SetAPIModifiers("DescribeVolumes", func(out interface{}) {
   975  		out.(*awsec2.DescribeVolumesOutput).Volumes[0].State = "creating"
   976  		calls++
   977  	}, func(out interface{}) {
   978  		out.(*awsec2.DescribeVolumesOutput).Volumes[0].State = "available"
   979  		calls++
   980  	})
   981  	result, err := vs.AttachVolumes(s.cloudCallCtx, params)
   982  	c.Assert(err, jc.ErrorIsNil)
   983  	c.Assert(result, gc.HasLen, 1)
   984  	c.Assert(result[0].Error, jc.ErrorIsNil)
   985  	c.Assert(calls, gc.Equals, 2)
   986  }
   987  
   988  func (s *ebsSuite) TestAttachVolumesDetaching(c *gc.C) {
   989  	vs := s.volumeSource(c, nil)
   990  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
   991  	s.srv.ec2srv.SetAPIModifiers("DescribeVolumes", func(out interface{}) {
   992  		vols := out.(*awsec2.DescribeVolumesOutput).Volumes
   993  		vols[0].State = "in-use"
   994  		vols[0].Attachments = append(vols[0].Attachments, types.VolumeAttachment{
   995  			InstanceId: aws.String("something else"),
   996  		})
   997  	})
   998  	result, err := vs.AttachVolumes(s.cloudCallCtx, params)
   999  	c.Assert(err, jc.ErrorIsNil)
  1000  	c.Assert(result, gc.HasLen, 1)
  1001  	c.Assert(result[0].Error, gc.ErrorMatches, "volume vol-0 is attached to something else")
  1002  }
  1003  
  1004  func (s *ebsSuite) TestDetachVolumes(c *gc.C) {
  1005  	vs := s.volumeSource(c, nil)
  1006  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
  1007  	_, err := vs.AttachVolumes(s.cloudCallCtx, params)
  1008  	c.Assert(err, jc.ErrorIsNil)
  1009  	errs, err := vs.DetachVolumes(s.cloudCallCtx, params)
  1010  	c.Assert(err, jc.ErrorIsNil)
  1011  	c.Assert(errs, jc.DeepEquals, []error{nil})
  1012  
  1013  	ec2Client := ec2.StorageEC2(vs)
  1014  	ec2Vols, err := ec2Client.DescribeVolumes(s.cloudCallCtx, nil)
  1015  	c.Assert(err, jc.ErrorIsNil)
  1016  	c.Assert(ec2Vols.Volumes, gc.HasLen, 6)
  1017  	sortBySize(ec2Vols.Volumes)
  1018  	c.Assert(ec2Vols.Volumes[0].Attachments, gc.HasLen, 0)
  1019  
  1020  	// Test idempotent
  1021  	errs, err = vs.DetachVolumes(s.cloudCallCtx, params)
  1022  	c.Assert(err, jc.ErrorIsNil)
  1023  	c.Assert(errs, jc.DeepEquals, []error{nil})
  1024  }
  1025  
  1026  func (s *ebsSuite) TestDetachVolumesIncorrectState(c *gc.C) {
  1027  	s.testDetachVolumesDetachedState(c, "IncorrectState")
  1028  }
  1029  
  1030  func (s *ebsSuite) TestDetachVolumesAttachmentNotFound(c *gc.C) {
  1031  	s.testDetachVolumesDetachedState(c, "InvalidAttachment.NotFound")
  1032  }
  1033  
  1034  func (s *ebsSuite) testDetachVolumesDetachedState(c *gc.C, errorCode string) {
  1035  	vs := s.volumeSource(c, nil)
  1036  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
  1037  	_, err := vs.AttachVolumes(s.cloudCallCtx, params)
  1038  	c.Assert(err, jc.ErrorIsNil)
  1039  
  1040  	s.srv.ec2srv.SetAPIError("DetachVolume", &smithy.GenericAPIError{Code: errorCode})
  1041  
  1042  	errs, err := vs.DetachVolumes(s.cloudCallCtx, params)
  1043  	c.Assert(err, jc.ErrorIsNil)
  1044  	c.Assert(errs, jc.DeepEquals, []error{nil})
  1045  }
  1046  
  1047  func (s *ebsSuite) TestImportVolume(c *gc.C) {
  1048  	vs := s.volumeSource(c, nil)
  1049  	c.Assert(vs, gc.Implements, new(storage.VolumeImporter))
  1050  
  1051  	resp, err := s.srv.ec2srv.CreateVolume(s.cloudCallCtx, &awsec2.CreateVolumeInput{
  1052  		Size:             aws.Int32(1),
  1053  		VolumeType:       "gp2",
  1054  		AvailabilityZone: aws.String("us-east-1a"),
  1055  	})
  1056  	c.Assert(err, jc.ErrorIsNil)
  1057  
  1058  	volID := aws.ToString(resp.VolumeId)
  1059  	volInfo, err := vs.(storage.VolumeImporter).ImportVolume(s.cloudCallCtx, volID, map[string]string{
  1060  		"foo": "bar",
  1061  	})
  1062  	c.Assert(err, jc.ErrorIsNil)
  1063  	c.Assert(volInfo, jc.DeepEquals, storage.VolumeInfo{
  1064  		VolumeId:   volID,
  1065  		Size:       1024,
  1066  		Persistent: true,
  1067  	})
  1068  
  1069  	volumes, err := s.srv.ec2srv.DescribeVolumes(s.cloudCallCtx, &awsec2.DescribeVolumesInput{
  1070  		VolumeIds: []string{volID},
  1071  	})
  1072  	c.Assert(err, jc.ErrorIsNil)
  1073  	c.Assert(volumes.Volumes, gc.HasLen, 1)
  1074  	compareTags(c, volumes.Volumes[0].Tags, []tagInfo{
  1075  		{"foo", "bar"},
  1076  	})
  1077  }
  1078  
  1079  func (s *ebsSuite) TestImportVolumeCredentialError(c *gc.C) {
  1080  	vs := s.volumeSource(c, nil)
  1081  	c.Assert(vs, gc.Implements, new(storage.VolumeImporter))
  1082  	resp, err := s.srv.ec2srv.CreateVolume(s.cloudCallCtx, &awsec2.CreateVolumeInput{
  1083  		Size:             aws.Int32(1),
  1084  		VolumeType:       "gp2",
  1085  		AvailabilityZone: aws.String("us-east-1a"),
  1086  	})
  1087  	c.Assert(err, jc.ErrorIsNil)
  1088  
  1089  	s.srv.ec2srv.SetAPIError("CreateTags", &smithy.GenericAPIError{Code: "Blocked"})
  1090  
  1091  	_, err = vs.(storage.VolumeImporter).ImportVolume(s.cloudCallCtx, aws.ToString(resp.VolumeId), map[string]string{
  1092  		"foo": "bar",
  1093  	})
  1094  	c.Assert(errors.Is(err, common.ErrorCredentialNotValid), jc.IsTrue)
  1095  }
  1096  
  1097  func (s *ebsSuite) TestImportVolumeInUse(c *gc.C) {
  1098  	vs := s.volumeSource(c, nil)
  1099  	c.Assert(vs, gc.Implements, new(storage.VolumeImporter))
  1100  
  1101  	params := s.setupAttachVolumesTest(c, vs, ec2test.Running)
  1102  	_, err := vs.AttachVolumes(s.cloudCallCtx, params)
  1103  	c.Assert(err, jc.ErrorIsNil)
  1104  
  1105  	volId := params[0].VolumeId
  1106  	_, err = vs.(storage.VolumeImporter).ImportVolume(s.cloudCallCtx, volId, map[string]string{})
  1107  	c.Assert(err, gc.ErrorMatches, `cannot import volume with status "in-use"`)
  1108  }
  1109  
  1110  type blockDeviceMappingSuite struct {
  1111  	testing.BaseSuite
  1112  }
  1113  
  1114  var _ = gc.Suite(&blockDeviceMappingSuite{})
  1115  
  1116  func (*blockDeviceMappingSuite) TestBlockDeviceNamer(c *gc.C) {
  1117  	var nextName func() (string, string, error)
  1118  	expect := func(expectRequest, expectActual string) {
  1119  		request, actual, err := nextName()
  1120  		c.Assert(err, jc.ErrorIsNil)
  1121  		c.Assert(request, gc.Equals, expectRequest)
  1122  		c.Assert(actual, gc.Equals, expectActual)
  1123  	}
  1124  	expectN := func(expectRequest, expectActual string) {
  1125  		for i := 1; i <= 6; i++ {
  1126  			request, actual, err := nextName()
  1127  			c.Assert(err, jc.ErrorIsNil)
  1128  			c.Assert(request, gc.Equals, expectRequest+strconv.Itoa(i))
  1129  			c.Assert(actual, gc.Equals, expectActual+strconv.Itoa(i))
  1130  		}
  1131  	}
  1132  	expectErr := func(expectErr string) {
  1133  		_, _, err := nextName()
  1134  		c.Assert(err, gc.ErrorMatches, expectErr)
  1135  	}
  1136  
  1137  	// First without numbers.
  1138  	nextName = ec2.BlockDeviceNamer(false)
  1139  	expect("/dev/sdf", "xvdf")
  1140  	expect("/dev/sdg", "xvdg")
  1141  	expect("/dev/sdh", "xvdh")
  1142  	expect("/dev/sdi", "xvdi")
  1143  	expect("/dev/sdj", "xvdj")
  1144  	expect("/dev/sdk", "xvdk")
  1145  	expect("/dev/sdl", "xvdl")
  1146  	expect("/dev/sdm", "xvdm")
  1147  	expect("/dev/sdn", "xvdn")
  1148  	expect("/dev/sdo", "xvdo")
  1149  	expect("/dev/sdp", "xvdp")
  1150  	expect("/dev/sdq", "xvdq")
  1151  	expect("/dev/sdr", "xvdr")
  1152  	expect("/dev/sds", "xvds")
  1153  	expect("/dev/sdt", "xvdt")
  1154  	expect("/dev/sdu", "xvdu")
  1155  	expect("/dev/sdv", "xvdv")
  1156  	expect("/dev/sdw", "xvdw")
  1157  	expect("/dev/sdx", "xvdx")
  1158  	expect("/dev/sdy", "xvdy")
  1159  	expect("/dev/sdz", "xvdz")
  1160  	expectErr("too many EBS volumes to attach")
  1161  
  1162  	// Now with numbers.
  1163  	nextName = ec2.BlockDeviceNamer(true)
  1164  	expect("/dev/sdf1", "xvdf1")
  1165  	expect("/dev/sdf2", "xvdf2")
  1166  	expect("/dev/sdf3", "xvdf3")
  1167  	expect("/dev/sdf4", "xvdf4")
  1168  	expect("/dev/sdf5", "xvdf5")
  1169  	expect("/dev/sdf6", "xvdf6")
  1170  	expectN("/dev/sdg", "xvdg")
  1171  	expectN("/dev/sdh", "xvdh")
  1172  	expectN("/dev/sdi", "xvdi")
  1173  	expectN("/dev/sdj", "xvdj")
  1174  	expectN("/dev/sdk", "xvdk")
  1175  	expectN("/dev/sdl", "xvdl")
  1176  	expectN("/dev/sdm", "xvdm")
  1177  	expectN("/dev/sdn", "xvdn")
  1178  	expectN("/dev/sdo", "xvdo")
  1179  	expectN("/dev/sdp", "xvdp")
  1180  	expectN("/dev/sdq", "xvdq")
  1181  	expectN("/dev/sdr", "xvdr")
  1182  	expectN("/dev/sds", "xvds")
  1183  	expectN("/dev/sdt", "xvdt")
  1184  	expectN("/dev/sdu", "xvdu")
  1185  	expectN("/dev/sdv", "xvdv")
  1186  	expectN("/dev/sdw", "xvdw")
  1187  	expectN("/dev/sdx", "xvdx")
  1188  	expectN("/dev/sdy", "xvdy")
  1189  	expectN("/dev/sdz", "xvdz")
  1190  	expectErr("too many EBS volumes to attach")
  1191  }
  1192  
  1193  func (*blockDeviceMappingSuite) TestGetBlockDeviceMappings(c *gc.C) {
  1194  	mapping, err := ec2.GetBlockDeviceMappings(constraints.Value{}, "jammy", false, nil)
  1195  	c.Assert(err, jc.ErrorIsNil)
  1196  	c.Assert(mapping, gc.DeepEquals, []types.BlockDeviceMapping{{
  1197  		Ebs:        &types.EbsBlockDevice{VolumeSize: aws.Int32(8)},
  1198  		DeviceName: aws.String("/dev/sda1"),
  1199  	}, {
  1200  		VirtualName: aws.String("ephemeral0"),
  1201  		DeviceName:  aws.String("/dev/sdb"),
  1202  	}, {
  1203  		VirtualName: aws.String("ephemeral1"),
  1204  		DeviceName:  aws.String("/dev/sdc"),
  1205  	}, {
  1206  		VirtualName: aws.String("ephemeral2"),
  1207  		DeviceName:  aws.String("/dev/sdd"),
  1208  	}, {
  1209  		VirtualName: aws.String("ephemeral3"),
  1210  		DeviceName:  aws.String("/dev/sde"),
  1211  	}})
  1212  }
  1213  
  1214  func (*blockDeviceMappingSuite) TestGetBlockDeviceMappingsController(c *gc.C) {
  1215  	mapping, err := ec2.GetBlockDeviceMappings(constraints.Value{}, "jammy", true, nil)
  1216  	c.Assert(err, jc.ErrorIsNil)
  1217  	c.Assert(mapping, gc.DeepEquals, []types.BlockDeviceMapping{{
  1218  		Ebs:        &types.EbsBlockDevice{VolumeSize: aws.Int32(32)},
  1219  		DeviceName: aws.String("/dev/sda1"),
  1220  	}, {
  1221  		VirtualName: aws.String("ephemeral0"),
  1222  		DeviceName:  aws.String("/dev/sdb"),
  1223  	}, {
  1224  		VirtualName: aws.String("ephemeral1"),
  1225  		DeviceName:  aws.String("/dev/sdc"),
  1226  	}, {
  1227  		VirtualName: aws.String("ephemeral2"),
  1228  		DeviceName:  aws.String("/dev/sdd"),
  1229  	}, {
  1230  		VirtualName: aws.String("ephemeral3"),
  1231  		DeviceName:  aws.String("/dev/sde"),
  1232  	}})
  1233  }
  1234  
  1235  type tagInfo struct {
  1236  	key   string
  1237  	value string
  1238  }
  1239  
  1240  func compareTags(c *gc.C, obtained []types.Tag, expected []tagInfo) {
  1241  	got := make([]tagInfo, len(obtained))
  1242  	for i, t := range obtained {
  1243  		got[i] = tagInfo{
  1244  			key:   aws.ToString(t.Key),
  1245  			value: aws.ToString(t.Value),
  1246  		}
  1247  	}
  1248  	c.Assert(got, jc.SameContents, expected)
  1249  }