github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/ec2/ebs.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ec2
     5  
     6  import (
     7  	"regexp"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/juju/collections/set"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/schema"
    15  	"github.com/juju/utils"
    16  	"gopkg.in/amz.v3/ec2"
    17  	"gopkg.in/juju/names.v2"
    18  
    19  	"github.com/juju/juju/core/constraints"
    20  	"github.com/juju/juju/core/instance"
    21  	"github.com/juju/juju/environs/context"
    22  	"github.com/juju/juju/environs/tags"
    23  	"github.com/juju/juju/provider/common"
    24  	"github.com/juju/juju/storage"
    25  )
    26  
    27  const (
    28  	EBS_ProviderType = storage.ProviderType("ebs")
    29  
    30  	// Config attributes
    31  
    32  	// The volume type (default standard):
    33  	//   "gp2" for General Purpose (SSD) volumes
    34  	//   "io1" for Provisioned IOPS (SSD) volumes,
    35  	//   "standard" for Magnetic volumes.
    36  	EBS_VolumeType = "volume-type"
    37  
    38  	// The number of I/O operations per second (IOPS) per GiB
    39  	// to provision for the volume. Only valid for Provisioned
    40  	// IOPS (SSD) volumes.
    41  	EBS_IOPS = "iops"
    42  
    43  	// Specifies whether the volume should be encrypted.
    44  	EBS_Encrypted = "encrypted"
    45  
    46  	// Volume Aliases
    47  	volumeAliasMagnetic        = "magnetic"         // standard
    48  	volumeAliasOptimizedHDD    = "optimized-hdd"    // sc1
    49  	volumeAliasColdStorage     = "cold-storage"     // sc1
    50  	volumeAliasSSD             = "ssd"              // gp2
    51  	volumeAliasProvisionedIops = "provisioned-iops" // io1
    52  
    53  	// Volume types
    54  	volumeTypeStandard = "standard"
    55  	volumeTypeGP2      = "gp2"
    56  	volumeTypeIO1      = "io1"
    57  	volumeTypeST1      = "st1"
    58  	volumeTypeSC1      = "sc1"
    59  
    60  	rootDiskDeviceName = "/dev/sda1"
    61  
    62  	nvmeDeviceLinkPrefix = "/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_"
    63  
    64  	// defaultControllerDiskSizeMiB is the default size for the
    65  	// root disk of controller machines, if no root-disk constraint
    66  	// is specified.
    67  	defaultControllerDiskSizeMiB = 32 * 1024
    68  )
    69  
    70  // AWS error codes
    71  const (
    72  	deviceInUse        = "InvalidDevice.InUse"
    73  	attachmentNotFound = "InvalidAttachment.NotFound"
    74  	volumeNotFound     = "InvalidVolume.NotFound"
    75  	incorrectState     = "IncorrectState"
    76  )
    77  
    78  const (
    79  	volumeStatusAvailable = "available"
    80  	volumeStatusInUse     = "in-use"
    81  	volumeStatusCreating  = "creating"
    82  
    83  	attachmentStatusAttaching = "attaching"
    84  	attachmentStatusAttached  = "attached"
    85  
    86  	instanceStateShuttingDown = "shutting-down"
    87  	instanceStateTerminated   = "terminated"
    88  )
    89  
    90  // Limits for volume parameters. See:
    91  //   http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html
    92  const (
    93  	// minMagneticVolumeSizeGiB is the minimum size for magnetic volumes in GiB.
    94  	minMagneticVolumeSizeGiB = 1
    95  
    96  	// maxMagneticVolumeSizeGiB is the maximum size for magnetic volumes in GiB.
    97  	maxMagneticVolumeSizeGiB = 1024
    98  
    99  	// minSSDVolumeSizeGiB is the minimum size for SSD volumes in GiB.
   100  	minSSDVolumeSizeGiB = 1
   101  
   102  	// maxSSDVolumeSizeGiB is the maximum size for SSD volumes in GiB.
   103  	maxSSDVolumeSizeGiB = 16 * 1024
   104  
   105  	// minProvisionedIopsVolumeSizeGiB is the minimum size of provisioned IOPS
   106  	// volumes in GiB.
   107  	minProvisionedIopsVolumeSizeGiB = 4
   108  
   109  	// maxProvisionedIopsVolumeSizeGiB is the maximum size of provisioned IOPS
   110  	// volumes in GiB.
   111  	maxProvisionedIopsVolumeSizeGiB = 16 * 1024
   112  
   113  	// maxProvisionedIopsSizeRatio is the maximum allowed ratio of IOPS to
   114  	// size (in GiB), for provisioend IOPS volumes.
   115  	maxProvisionedIopsSizeRatio = 30
   116  
   117  	// maxProvisionedIops is the maximum allowed IOPS in total for provisioned IOPS
   118  	// volumes. We take the minimum of volumeSize*maxProvisionedIopsSizeRatio and
   119  	// maxProvisionedIops.
   120  	maxProvisionedIops = 20000
   121  
   122  	// minSt1VolumeSizeGiB is the minimum volume size for st1 volume instances.
   123  	minSt1VolumeSizeGiB = 500
   124  
   125  	// maxSt1VolumeSizeGiB is the maximum volume size for st1 volume instances.
   126  	maxSt1VolumeSizeGiB = 16 * 1024
   127  
   128  	// minSc1VolumeSizeGiB is the minimum volume size for sc1 volume instances.
   129  	minSc1VolumeSizeGiB = 500
   130  
   131  	// maxSc1VolumeSizeGiB is the maximum volume size for sc1 volume instances.
   132  	maxSc1VolumeSizeGiB = 16 * 1024
   133  )
   134  
   135  const (
   136  	// devicePrefix is the prefix for device names specified when creating volumes.
   137  	devicePrefix = "/dev/sd"
   138  
   139  	// renamedDevicePrefix is the prefix for device names after they have
   140  	// been renamed. This should replace "devicePrefix" in the device name
   141  	// when recording the block device info in state.
   142  	renamedDevicePrefix = "xvd"
   143  )
   144  
   145  var deviceInUseRegexp = regexp.MustCompile(".*Attachment point .* is already in use")
   146  
   147  // StorageProviderTypes implements storage.ProviderRegistry.
   148  func (env *environ) StorageProviderTypes() ([]storage.ProviderType, error) {
   149  	return []storage.ProviderType{EBS_ProviderType}, nil
   150  }
   151  
   152  // StorageProvider implements storage.ProviderRegistry.
   153  func (env *environ) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
   154  	if t == EBS_ProviderType {
   155  		return &ebsProvider{env}, nil
   156  	}
   157  	return nil, errors.NotFoundf("storage provider %q", t)
   158  }
   159  
   160  // ebsProvider creates volume sources which use AWS EBS volumes.
   161  type ebsProvider struct {
   162  	env *environ
   163  }
   164  
   165  var _ storage.Provider = (*ebsProvider)(nil)
   166  
   167  var ebsConfigFields = schema.Fields{
   168  	EBS_VolumeType: schema.OneOf(
   169  		schema.Const(volumeAliasMagnetic),
   170  		schema.Const(volumeAliasOptimizedHDD),
   171  		schema.Const(volumeAliasColdStorage),
   172  		schema.Const(volumeAliasSSD),
   173  		schema.Const(volumeAliasProvisionedIops),
   174  		schema.Const(volumeTypeStandard),
   175  		schema.Const(volumeTypeGP2),
   176  		schema.Const(volumeTypeIO1),
   177  		schema.Const(volumeTypeST1),
   178  		schema.Const(volumeTypeSC1),
   179  	),
   180  	EBS_IOPS:      schema.ForceInt(),
   181  	EBS_Encrypted: schema.Bool(),
   182  }
   183  
   184  var ebsConfigChecker = schema.FieldMap(
   185  	ebsConfigFields,
   186  	schema.Defaults{
   187  		EBS_VolumeType: volumeAliasSSD,
   188  		EBS_IOPS:       schema.Omit,
   189  		EBS_Encrypted:  false,
   190  	},
   191  )
   192  
   193  type ebsConfig struct {
   194  	volumeType string
   195  	iops       int
   196  	encrypted  bool
   197  }
   198  
   199  func newEbsConfig(attrs map[string]interface{}) (*ebsConfig, error) {
   200  	out, err := ebsConfigChecker.Coerce(attrs, nil)
   201  	if err != nil {
   202  		return nil, errors.Annotate(err, "validating EBS storage config")
   203  	}
   204  	coerced := out.(map[string]interface{})
   205  	iops, _ := coerced[EBS_IOPS].(int)
   206  	volumeType := coerced[EBS_VolumeType].(string)
   207  	ebsConfig := &ebsConfig{
   208  		volumeType: volumeType,
   209  		iops:       iops,
   210  		encrypted:  coerced[EBS_Encrypted].(bool),
   211  	}
   212  	switch ebsConfig.volumeType {
   213  	case volumeAliasMagnetic:
   214  		ebsConfig.volumeType = volumeTypeStandard
   215  	case volumeAliasColdStorage:
   216  		ebsConfig.volumeType = volumeTypeSC1
   217  	case volumeAliasOptimizedHDD:
   218  		ebsConfig.volumeType = volumeTypeST1
   219  	case volumeAliasSSD:
   220  		ebsConfig.volumeType = volumeTypeGP2
   221  	case volumeAliasProvisionedIops:
   222  		ebsConfig.volumeType = volumeTypeIO1
   223  	}
   224  	if ebsConfig.iops > 0 && ebsConfig.volumeType != volumeTypeIO1 {
   225  		return nil, errors.Errorf("IOPS specified, but volume type is %q", volumeType)
   226  	} else if ebsConfig.iops == 0 && ebsConfig.volumeType == volumeTypeIO1 {
   227  		return nil, errors.Errorf("volume type is %q, IOPS unspecified or zero", volumeTypeIO1)
   228  	}
   229  	return ebsConfig, nil
   230  }
   231  
   232  // ValidateConfig is defined on the Provider interface.
   233  func (e *ebsProvider) ValidateConfig(cfg *storage.Config) error {
   234  	_, err := newEbsConfig(cfg.Attrs())
   235  	return errors.Trace(err)
   236  }
   237  
   238  // Supports is defined on the Provider interface.
   239  func (e *ebsProvider) Supports(k storage.StorageKind) bool {
   240  	return k == storage.StorageKindBlock
   241  }
   242  
   243  // Scope is defined on the Provider interface.
   244  func (e *ebsProvider) Scope() storage.Scope {
   245  	return storage.ScopeEnviron
   246  }
   247  
   248  // Dynamic is defined on the Provider interface.
   249  func (e *ebsProvider) Dynamic() bool {
   250  	return true
   251  }
   252  
   253  // Releasable is defined on the Provider interface.
   254  func (*ebsProvider) Releasable() bool {
   255  	return true
   256  }
   257  
   258  // DefaultPools is defined on the Provider interface.
   259  func (e *ebsProvider) DefaultPools() []*storage.Config {
   260  	ssdPool, _ := storage.NewConfig("ebs-ssd", EBS_ProviderType, map[string]interface{}{
   261  		EBS_VolumeType: volumeAliasSSD,
   262  	})
   263  	return []*storage.Config{ssdPool}
   264  }
   265  
   266  // VolumeSource is defined on the Provider interface.
   267  func (e *ebsProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) {
   268  	environConfig := e.env.Config()
   269  	source := &ebsVolumeSource{
   270  		env:       e.env,
   271  		envName:   environConfig.Name(),
   272  		modelUUID: environConfig.UUID(),
   273  	}
   274  	return source, nil
   275  }
   276  
   277  // FilesystemSource is defined on the Provider interface.
   278  func (e *ebsProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
   279  	return nil, errors.NotSupportedf("filesystems")
   280  }
   281  
   282  type ebsVolumeSource struct {
   283  	env       *environ
   284  	envName   string // non-unique, informational only
   285  	modelUUID string
   286  }
   287  
   288  var _ storage.VolumeSource = (*ebsVolumeSource)(nil)
   289  
   290  // parseVolumeOptions uses storage volume parameters to make a struct used to create volumes.
   291  func parseVolumeOptions(size uint64, attrs map[string]interface{}) (_ ec2.CreateVolume, _ error) {
   292  	ebsConfig, err := newEbsConfig(attrs)
   293  	if err != nil {
   294  		return ec2.CreateVolume{}, errors.Trace(err)
   295  	}
   296  	if ebsConfig.iops > maxProvisionedIopsSizeRatio {
   297  		return ec2.CreateVolume{}, errors.Errorf(
   298  			"specified IOPS ratio is %d/GiB, maximum is %d/GiB",
   299  			ebsConfig.iops, maxProvisionedIopsSizeRatio,
   300  		)
   301  	}
   302  
   303  	sizeInGib := mibToGib(size)
   304  	iops := uint64(ebsConfig.iops) * sizeInGib
   305  	if iops > maxProvisionedIops {
   306  		iops = maxProvisionedIops
   307  	}
   308  	vol := ec2.CreateVolume{
   309  		// Juju size is MiB, AWS size is GiB.
   310  		VolumeSize: int(sizeInGib),
   311  		VolumeType: ebsConfig.volumeType,
   312  		Encrypted:  ebsConfig.encrypted,
   313  		IOPS:       int64(iops),
   314  	}
   315  	return vol, nil
   316  }
   317  
   318  // CreateVolumes is specified on the storage.VolumeSource interface.
   319  func (v *ebsVolumeSource) CreateVolumes(ctx context.ProviderCallContext, params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) {
   320  
   321  	// First, validate the params before we use them.
   322  	results := make([]storage.CreateVolumesResult, len(params))
   323  	instanceIds := set.NewStrings()
   324  	for i, p := range params {
   325  		if err := v.ValidateVolumeParams(p); err != nil {
   326  			results[i].Error = err
   327  			continue
   328  		}
   329  		instanceIds.Add(string(p.Attachment.InstanceId))
   330  	}
   331  
   332  	instances := make(instanceCache)
   333  	if instanceIds.Size() > 1 {
   334  		if err := instances.update(v.env.ec2, ctx, instanceIds.Values()...); err != nil {
   335  			err := maybeConvertCredentialError(err, ctx)
   336  			logger.Debugf("querying running instances: %v", err)
   337  			// We ignore the error, because we don't want an invalid
   338  			// InstanceId reference from one VolumeParams to prevent
   339  			// the creation of another volume.
   340  			// Except if it is a credential error...
   341  			if common.IsCredentialNotValid(err) {
   342  				return nil, errors.Trace(err)
   343  			}
   344  		}
   345  	}
   346  
   347  	for i, p := range params {
   348  		if results[i].Error != nil {
   349  			continue
   350  		}
   351  		volume, attachment, err := v.createVolume(ctx, p, instances)
   352  		if err != nil {
   353  			results[i].Error = err
   354  			continue
   355  		}
   356  		results[i].Volume = volume
   357  		results[i].VolumeAttachment = attachment
   358  	}
   359  	return results, nil
   360  }
   361  
   362  func (v *ebsVolumeSource) createVolume(ctx context.ProviderCallContext, p storage.VolumeParams, instances instanceCache) (_ *storage.Volume, _ *storage.VolumeAttachment, err error) {
   363  	var volumeId string
   364  	defer func() {
   365  		if err == nil || volumeId == "" {
   366  			return
   367  		}
   368  		if _, err := v.env.ec2.DeleteVolume(volumeId); err != nil {
   369  			logger.Errorf("error cleaning up volume %v: %v", volumeId, maybeConvertCredentialError(err, ctx))
   370  		}
   371  	}()
   372  
   373  	// TODO(axw) if preference is to use ephemeral, use ephemeral
   374  	// until the instance stores run out. We'll need to know how
   375  	// many there are and how big each one is. We also need to
   376  	// unmap ephemeral0 in cloud-init.
   377  
   378  	// Create.
   379  	instId := string(p.Attachment.InstanceId)
   380  	if err := instances.update(v.env.ec2, ctx, instId); err != nil {
   381  		return nil, nil, errors.Trace(maybeConvertCredentialError(err, ctx))
   382  	}
   383  	inst, err := instances.get(instId)
   384  	if err != nil {
   385  		// Can't create the volume without the instance,
   386  		// because we need to know what its AZ is.
   387  		return nil, nil, errors.Trace(maybeConvertCredentialError(err, ctx))
   388  	}
   389  	vol, _ := parseVolumeOptions(p.Size, p.Attributes)
   390  	vol.AvailZone = inst.AvailZone
   391  	resp, err := v.env.ec2.CreateVolume(vol)
   392  	if err != nil {
   393  		return nil, nil, errors.Trace(maybeConvertCredentialError(err, ctx))
   394  	}
   395  	volumeId = resp.Id
   396  
   397  	// Tag.
   398  	resourceTags := make(map[string]string)
   399  	for k, v := range p.ResourceTags {
   400  		resourceTags[k] = v
   401  	}
   402  	resourceTags[tagName] = resourceName(p.Tag, v.envName)
   403  	if err := tagResources(v.env.ec2, ctx, resourceTags, volumeId); err != nil {
   404  		return nil, nil, errors.Annotate(err, "tagging volume")
   405  	}
   406  
   407  	volume := storage.Volume{
   408  		p.Tag,
   409  		storage.VolumeInfo{
   410  			VolumeId:   volumeId,
   411  			Size:       gibToMib(uint64(resp.Size)),
   412  			Persistent: true,
   413  		},
   414  	}
   415  	return &volume, nil, nil
   416  }
   417  
   418  // ListVolumes is specified on the storage.VolumeSource interface.
   419  func (v *ebsVolumeSource) ListVolumes(ctx context.ProviderCallContext) ([]string, error) {
   420  	filter := ec2.NewFilter()
   421  	filter.Add("tag:"+tags.JujuModel, v.modelUUID)
   422  	return listVolumes(v.env.ec2, ctx, filter, false)
   423  }
   424  
   425  func listVolumes(client *ec2.EC2, ctx context.ProviderCallContext, filter *ec2.Filter, includeRootDisks bool) ([]string, error) {
   426  	resp, err := client.Volumes(nil, filter)
   427  	if err != nil {
   428  		return nil, maybeConvertCredentialError(err, ctx)
   429  	}
   430  	volumeIds := make([]string, 0, len(resp.Volumes))
   431  	for _, vol := range resp.Volumes {
   432  		var isRootDisk bool
   433  		for _, att := range vol.Attachments {
   434  			if att.Device == rootDiskDeviceName {
   435  				isRootDisk = true
   436  				break
   437  			}
   438  		}
   439  		if isRootDisk && !includeRootDisks {
   440  			// We don't want to list root disks in the output.
   441  			// These are managed by the instance provisioning
   442  			// code; they will be created and destroyed with
   443  			// instances.
   444  			continue
   445  		}
   446  		volumeIds = append(volumeIds, vol.Id)
   447  	}
   448  	return volumeIds, nil
   449  }
   450  
   451  // DescribeVolumes is specified on the storage.VolumeSource interface.
   452  func (v *ebsVolumeSource) DescribeVolumes(ctx context.ProviderCallContext, volIds []string) ([]storage.DescribeVolumesResult, error) {
   453  	// TODO(axw) invalid volIds here should not cause the whole
   454  	// operation to fail. If we get an invalid volume ID response,
   455  	// fall back to querying each volume individually. That should
   456  	// be rare.
   457  	resp, err := v.env.ec2.Volumes(volIds, nil)
   458  	if err != nil {
   459  		return nil, maybeConvertCredentialError(err, ctx)
   460  	}
   461  	byId := make(map[string]ec2.Volume)
   462  	for _, vol := range resp.Volumes {
   463  		byId[vol.Id] = vol
   464  	}
   465  	results := make([]storage.DescribeVolumesResult, len(volIds))
   466  	for i, volId := range volIds {
   467  		vol, ok := byId[volId]
   468  		if !ok {
   469  			results[i].Error = errors.NotFoundf("%s", volId)
   470  			continue
   471  		}
   472  		results[i].VolumeInfo = &storage.VolumeInfo{
   473  			Size:       gibToMib(uint64(vol.Size)),
   474  			VolumeId:   vol.Id,
   475  			Persistent: true,
   476  		}
   477  		for _, attachment := range vol.Attachments {
   478  			if attachment.DeleteOnTermination {
   479  				results[i].VolumeInfo.Persistent = false
   480  				break
   481  			}
   482  		}
   483  	}
   484  	return results, nil
   485  }
   486  
   487  // DestroyVolumes is specified on the storage.VolumeSource interface.
   488  func (v *ebsVolumeSource) DestroyVolumes(ctx context.ProviderCallContext, volIds []string) ([]error, error) {
   489  	return foreachVolume(v.env.ec2, ctx, volIds, destroyVolume), nil
   490  }
   491  
   492  // ReleaseVolumes is specified on the storage.VolumeSource interface.
   493  func (v *ebsVolumeSource) ReleaseVolumes(ctx context.ProviderCallContext, volIds []string) ([]error, error) {
   494  	return foreachVolume(v.env.ec2, ctx, volIds, releaseVolume), nil
   495  }
   496  
   497  func foreachVolume(client *ec2.EC2, ctx context.ProviderCallContext, volIds []string, f func(*ec2.EC2, context.ProviderCallContext, string) error) []error {
   498  	var wg sync.WaitGroup
   499  	wg.Add(len(volIds))
   500  	results := make([]error, len(volIds))
   501  	for i, volumeId := range volIds {
   502  		go func(i int, volumeId string) {
   503  			defer wg.Done()
   504  			results[i] = f(client, ctx, volumeId)
   505  		}(i, volumeId)
   506  	}
   507  	wg.Wait()
   508  	return results
   509  }
   510  
   511  var destroyVolumeAttempt = utils.AttemptStrategy{
   512  	Total: 5 * time.Minute,
   513  	Delay: 5 * time.Second,
   514  }
   515  
   516  func destroyVolume(client *ec2.EC2, ctx context.ProviderCallContext, volumeId string) (err error) {
   517  	defer func() {
   518  		if err != nil {
   519  			if ec2ErrCode(err) == volumeNotFound || errors.IsNotFound(err) {
   520  				// Either the volume isn't found, or we queried the
   521  				// instance corresponding to a DeleteOnTermination
   522  				// attachment; in either case, the volume is or will
   523  				// be destroyed.
   524  				logger.Tracef("Ignoring error destroying volume %q: %v", volumeId, err)
   525  				err = nil
   526  			} else {
   527  				err = maybeConvertCredentialError(err, ctx)
   528  			}
   529  		}
   530  	}()
   531  
   532  	logger.Debugf("destroying %q", volumeId)
   533  
   534  	// Volumes must not be in-use when destroying. A volume may
   535  	// still be in-use when the instance it is attached to is
   536  	// in the process of being terminated.
   537  	volume, err := waitVolume(client, ctx, volumeId, destroyVolumeAttempt, func(volume *ec2.Volume) (bool, error) {
   538  		if volume.Status != volumeStatusInUse {
   539  			// Volume is not in use, it should be OK to destroy now.
   540  			return true, nil
   541  		}
   542  		if len(volume.Attachments) == 0 {
   543  			// There are no attachments remaining now; keep querying
   544  			// until volume transitions out of in-use.
   545  			return false, nil
   546  		}
   547  		var deleteOnTermination []string
   548  		var args []storage.VolumeAttachmentParams
   549  		for _, a := range volume.Attachments {
   550  			switch a.Status {
   551  			case attachmentStatusAttaching, attachmentStatusAttached:
   552  				// The volume is attaching or attached to an
   553  				// instance, we need for it to be detached
   554  				// before we can destroy it.
   555  				args = append(args, storage.VolumeAttachmentParams{
   556  					AttachmentParams: storage.AttachmentParams{
   557  						InstanceId: instance.Id(a.InstanceId),
   558  					},
   559  					VolumeId: volumeId,
   560  				})
   561  				if a.DeleteOnTermination {
   562  					// The volume is still attached, and the
   563  					// attachment is "delete on termination";
   564  					// check if the related instance is being
   565  					// terminated, in which case we can stop
   566  					// waiting and skip destroying the volume.
   567  					//
   568  					// Note: we still accrue in "args" above
   569  					// in case the instance is not terminating;
   570  					// in that case we detach and destroy as
   571  					// usual.
   572  					deleteOnTermination = append(
   573  						deleteOnTermination, a.InstanceId,
   574  					)
   575  				}
   576  			}
   577  		}
   578  		if len(deleteOnTermination) > 0 {
   579  			result, err := client.Instances(deleteOnTermination, nil)
   580  			if err != nil {
   581  				return false, errors.Trace(err)
   582  			}
   583  			for _, reservation := range result.Reservations {
   584  				for _, instance := range reservation.Instances {
   585  					switch instance.State.Name {
   586  					case instanceStateShuttingDown, instanceStateTerminated:
   587  						// The instance is or will be terminated,
   588  						// and so the volume will be deleted by
   589  						// virtue of delete-on-termination.
   590  						return true, nil
   591  					}
   592  				}
   593  			}
   594  		}
   595  		if len(args) == 0 {
   596  			return false, nil
   597  		}
   598  		results, err := detachVolumes(client, ctx, args)
   599  		if err != nil {
   600  			return false, errors.Trace(err)
   601  		}
   602  		for _, err := range results {
   603  			if err != nil {
   604  				return false, errors.Trace(err)
   605  			}
   606  		}
   607  		return false, nil
   608  	})
   609  	if err != nil {
   610  		if err == errWaitVolumeTimeout {
   611  			return errors.Errorf("timed out waiting for volume %v to not be in-use", volumeId)
   612  		}
   613  		return errors.Trace(err)
   614  	}
   615  	if volume.Status == volumeStatusInUse {
   616  		// If the volume is in-use, that means it will be
   617  		// handled by delete-on-termination and we have
   618  		// nothing more to do.
   619  		return nil
   620  	}
   621  	_, err = client.DeleteVolume(volumeId)
   622  	return errors.Annotatef(maybeConvertCredentialError(err, ctx), "destroying %q", volumeId)
   623  }
   624  
   625  func releaseVolume(client *ec2.EC2, ctx context.ProviderCallContext, volumeId string) error {
   626  	logger.Debugf("releasing %q", volumeId)
   627  	_, err := waitVolume(client, ctx, volumeId, destroyVolumeAttempt, func(volume *ec2.Volume) (bool, error) {
   628  		if volume.Status == volumeStatusAvailable {
   629  			return true, nil
   630  		}
   631  		for _, a := range volume.Attachments {
   632  			if a.DeleteOnTermination {
   633  				return false, errors.New("delete-on-termination flag is set")
   634  			}
   635  			switch a.Status {
   636  			case attachmentStatusAttaching, attachmentStatusAttached:
   637  				return false, errors.New("attachments still active")
   638  			}
   639  		}
   640  		return false, nil
   641  	})
   642  	if err != nil {
   643  		if err == errWaitVolumeTimeout {
   644  			return errors.Errorf("timed out waiting for volume %v to become available", volumeId)
   645  		}
   646  		return errors.Annotatef(maybeConvertCredentialError(err, ctx), "cannot release volume %q", volumeId)
   647  	}
   648  	// Releasing the volume just means dropping the
   649  	// tags that associate it with the model and
   650  	// controller.
   651  	tags := map[string]string{
   652  		tags.JujuModel:      "",
   653  		tags.JujuController: "",
   654  	}
   655  	return errors.Annotate(tagResources(client, ctx, tags, volumeId), "tagging volume")
   656  }
   657  
   658  // ValidateVolumeParams is specified on the storage.VolumeSource interface.
   659  func (v *ebsVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   660  	vol, err := parseVolumeOptions(params.Size, params.Attributes)
   661  	if err != nil {
   662  		return err
   663  	}
   664  	var minVolumeSize, maxVolumeSize int
   665  	switch vol.VolumeType {
   666  	case volumeTypeStandard:
   667  		minVolumeSize = minMagneticVolumeSizeGiB
   668  		maxVolumeSize = maxMagneticVolumeSizeGiB
   669  	case volumeTypeGP2:
   670  		minVolumeSize = minSSDVolumeSizeGiB
   671  		maxVolumeSize = maxSSDVolumeSizeGiB
   672  	case volumeTypeIO1:
   673  		minVolumeSize = minProvisionedIopsVolumeSizeGiB
   674  		maxVolumeSize = maxProvisionedIopsVolumeSizeGiB
   675  	case volumeTypeST1:
   676  		minVolumeSize = minSt1VolumeSizeGiB
   677  		maxVolumeSize = maxSt1VolumeSizeGiB
   678  	case volumeTypeSC1:
   679  		minVolumeSize = minSc1VolumeSizeGiB
   680  		maxVolumeSize = maxSc1VolumeSizeGiB
   681  	}
   682  	if vol.VolumeSize < minVolumeSize {
   683  		return errors.Errorf(
   684  			"volume size is %d GiB, must be at least %d GiB",
   685  			vol.VolumeSize, minVolumeSize,
   686  		)
   687  	}
   688  	if vol.VolumeSize > maxVolumeSize {
   689  		return errors.Errorf(
   690  			"volume size %d GiB exceeds the maximum of %d GiB",
   691  			vol.VolumeSize, maxVolumeSize,
   692  		)
   693  	}
   694  	return nil
   695  }
   696  
   697  // AttachVolumes is specified on the storage.VolumeSource interface.
   698  func (v *ebsVolumeSource) AttachVolumes(ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   699  	// We need the instance type for each instance we are
   700  	// attaching to so we can determine how to identify the
   701  	// volume attachment
   702  	instIds := set.NewStrings()
   703  	for _, p := range attachParams {
   704  		instIds.Add(string(p.InstanceId))
   705  	}
   706  	instances := make(instanceCache)
   707  	if err := instances.update(v.env.ec2, ctx, instIds.Values()...); err != nil {
   708  		err := maybeConvertCredentialError(err, ctx)
   709  		logger.Debugf("querying running instances: %v", err)
   710  		// We ignore the error, because we don't want an invalid
   711  		// InstanceId reference from one VolumeParams to prevent
   712  		// the creation of another volume.
   713  		// Except if it is a credential error...
   714  		if common.IsCredentialNotValid(err) {
   715  			return nil, errors.Trace(err)
   716  		}
   717  	}
   718  
   719  	results := make([]storage.AttachVolumesResult, len(attachParams))
   720  	for i, params := range attachParams {
   721  		instId := string(params.InstanceId)
   722  
   723  		// By default we should allocate device names without the
   724  		// trailing number. Block devices with a trailing number are
   725  		// not liked by some applications, e.g. Ceph, which want full
   726  		// disks.
   727  		//
   728  		// TODO(axw) introduce a configuration option if and when
   729  		// someone asks for it to enable use of numbers. This option
   730  		// must error if used with an "hvm" instance type.
   731  		const numbers = false
   732  		nextDeviceName := blockDeviceNamer(numbers)
   733  		_, deviceName, err := v.attachOneVolume(ctx, nextDeviceName, params.VolumeId, instId)
   734  		if err != nil {
   735  			results[i].Error = maybeConvertCredentialError(err, ctx)
   736  			continue
   737  		}
   738  
   739  		var attachmentInfo storage.VolumeAttachmentInfo
   740  		// The newer hypervisor attaches EBS volumes
   741  		// as NVMe devices, and the block device names
   742  		// are unpredictable from here.
   743  		//
   744  		// Instead of using device name, we fill in
   745  		// the device link, based on the statically
   746  		// defined model name ("Amazon Elastic Block Store",
   747  		// and serial (vol123456abcdef...); the serial
   748  		// is the same as the volume ID without the "-".
   749  		//
   750  		// NOTE(axw) inst.Hypervisor still says "xen" for
   751  		// affected instance types, which would seem to
   752  		// be a lie. There's no way to tell how a volume will
   753  		// be exposed so we have to assume an nvme link - the
   754  		// subsequent matching code will correctly skip the link
   755  		// and match against device name for non-nvme volumes.
   756  		sn := strings.Replace(params.VolumeId, "-", "", 1)
   757  		attachmentInfo.DeviceLink = nvmeDeviceLinkPrefix + sn
   758  		attachmentInfo.DeviceName = deviceName
   759  
   760  		results[i].VolumeAttachment = &storage.VolumeAttachment{
   761  			params.Volume,
   762  			params.Machine,
   763  			attachmentInfo,
   764  		}
   765  	}
   766  	return results, nil
   767  }
   768  
   769  func (v *ebsVolumeSource) attachOneVolume(
   770  	ctx context.ProviderCallContext,
   771  	nextDeviceName func() (string, string, error),
   772  	volumeId, instId string,
   773  ) (string, string, error) {
   774  	// Wait for the volume to move out of "creating".
   775  	volume, err := v.waitVolumeCreated(ctx, volumeId)
   776  	if err != nil {
   777  		return "", "", errors.Trace(maybeConvertCredentialError(err, ctx))
   778  	}
   779  
   780  	// Possible statuses:
   781  	//    creating | available | in-use | deleting | deleted | error
   782  	switch volume.Status {
   783  	default:
   784  		return "", "", errors.Errorf("cannot attach to volume with status %q", volume.Status)
   785  
   786  	case volumeStatusInUse:
   787  		// Volume is already attached; see if it's attached to the
   788  		// instance requested.
   789  		attachments := volume.Attachments
   790  		if len(attachments) != 1 {
   791  			return "", "", errors.Errorf("volume %v has unexpected attachment count: %v", volumeId, len(attachments))
   792  		}
   793  		if attachments[0].InstanceId != instId {
   794  			return "", "", errors.Errorf("volume %v is attached to %v", volumeId, attachments[0].InstanceId)
   795  		}
   796  		requestDeviceName := attachments[0].Device
   797  		actualDeviceName := renamedDevicePrefix + requestDeviceName[len(devicePrefix):]
   798  		return requestDeviceName, actualDeviceName, nil
   799  
   800  	case volumeStatusAvailable:
   801  		// Attempt to attach below.
   802  		break
   803  	}
   804  
   805  	for {
   806  		requestDeviceName, actualDeviceName, err := nextDeviceName()
   807  		if err != nil {
   808  			// Can't attach any more volumes.
   809  			return "", "", err
   810  		}
   811  		_, err = v.env.ec2.AttachVolume(volumeId, instId, requestDeviceName)
   812  		if ec2Err, ok := err.(*ec2.Error); ok {
   813  			switch ec2Err.Code {
   814  			case invalidParameterValue:
   815  				// InvalidParameterValue is returned by AttachVolume
   816  				// rather than InvalidDevice.InUse as the docs would
   817  				// suggest.
   818  				if !deviceInUseRegexp.MatchString(ec2Err.Message) {
   819  					break
   820  				}
   821  				fallthrough
   822  
   823  			case deviceInUse:
   824  				// deviceInUse means that the requested device name
   825  				// is in use already. Try again with the next name.
   826  				continue
   827  			}
   828  		}
   829  		if err != nil {
   830  			return "", "", errors.Annotate(maybeConvertCredentialError(err, ctx), "attaching volume")
   831  		}
   832  		return requestDeviceName, actualDeviceName, nil
   833  	}
   834  }
   835  
   836  func (v *ebsVolumeSource) waitVolumeCreated(ctx context.ProviderCallContext, volumeId string) (*ec2.Volume, error) {
   837  	var attempt = utils.AttemptStrategy{
   838  		Total: 5 * time.Second,
   839  		Delay: 200 * time.Millisecond,
   840  	}
   841  	var lastStatus string
   842  	volume, err := waitVolume(v.env.ec2, ctx, volumeId, attempt, func(volume *ec2.Volume) (bool, error) {
   843  		lastStatus = volume.Status
   844  		return volume.Status != volumeStatusCreating, nil
   845  	})
   846  	if err == errWaitVolumeTimeout {
   847  		return nil, errors.Errorf(
   848  			"timed out waiting for volume %v to become available (%v)",
   849  			volumeId, lastStatus,
   850  		)
   851  	} else if err != nil {
   852  		return nil, errors.Trace(maybeConvertCredentialError(err, ctx))
   853  	}
   854  	return volume, nil
   855  }
   856  
   857  var errWaitVolumeTimeout = errors.New("timed out")
   858  
   859  func waitVolume(
   860  	client *ec2.EC2,
   861  	ctx context.ProviderCallContext,
   862  	volumeId string,
   863  	attempt utils.AttemptStrategy,
   864  	pred func(v *ec2.Volume) (bool, error),
   865  ) (*ec2.Volume, error) {
   866  	for a := attempt.Start(); a.Next(); {
   867  		volume, err := describeVolume(client, ctx, volumeId)
   868  		if err != nil {
   869  			return nil, errors.Trace(err)
   870  		}
   871  		ok, err := pred(volume)
   872  		if err != nil {
   873  			return nil, errors.Trace(err)
   874  		}
   875  		if ok {
   876  			return volume, nil
   877  		}
   878  	}
   879  	return nil, errWaitVolumeTimeout
   880  }
   881  
   882  func describeVolume(client *ec2.EC2, ctx context.ProviderCallContext, volumeId string) (*ec2.Volume, error) {
   883  	resp, err := client.Volumes([]string{volumeId}, nil)
   884  	if err != nil {
   885  		return nil, errors.Annotate(maybeConvertCredentialError(err, ctx), "querying volume")
   886  	}
   887  	if len(resp.Volumes) == 0 {
   888  		return nil, errors.NotFoundf("%v", volumeId)
   889  	} else if len(resp.Volumes) != 1 {
   890  		return nil, errors.Errorf("expected one volume, got %d", len(resp.Volumes))
   891  	}
   892  	return &resp.Volumes[0], nil
   893  }
   894  
   895  type instanceCache map[string]ec2.Instance
   896  
   897  func (c instanceCache) update(ec2client *ec2.EC2, ctx context.ProviderCallContext, ids ...string) error {
   898  	if len(ids) == 1 {
   899  		if _, ok := c[ids[0]]; ok {
   900  			return nil
   901  		}
   902  	}
   903  	filter := ec2.NewFilter()
   904  	filter.Add("instance-state-name", "running")
   905  	resp, err := ec2client.Instances(ids, filter)
   906  	if err != nil {
   907  		return errors.Annotate(maybeConvertCredentialError(err, ctx), "querying instance details")
   908  	}
   909  	for j := range resp.Reservations {
   910  		r := &resp.Reservations[j]
   911  		for _, inst := range r.Instances {
   912  			c[inst.InstanceId] = inst
   913  		}
   914  	}
   915  	return nil
   916  }
   917  
   918  func (c instanceCache) get(id string) (ec2.Instance, error) {
   919  	inst, ok := c[id]
   920  	if !ok {
   921  		return ec2.Instance{}, errors.Errorf("cannot attach to non-running instance %v", id)
   922  	}
   923  	return inst, nil
   924  }
   925  
   926  // DetachVolumes is specified on the storage.VolumeSource interface.
   927  func (v *ebsVolumeSource) DetachVolumes(ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]error, error) {
   928  	return detachVolumes(v.env.ec2, ctx, attachParams)
   929  }
   930  
   931  func detachVolumes(client *ec2.EC2, ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]error, error) {
   932  	results := make([]error, len(attachParams))
   933  	for i, params := range attachParams {
   934  		_, err := client.DetachVolume(params.VolumeId, string(params.InstanceId), "", false)
   935  		// Process aws specific error information.
   936  		if err != nil {
   937  			if ec2Err, ok := err.(*ec2.Error); ok {
   938  				switch ec2Err.Code {
   939  				case incorrectState:
   940  					// incorrect state means this volume is "available",
   941  					// i.e. is not attached to any machine.
   942  					err = nil
   943  				case attachmentNotFound:
   944  					// attachment not found means this volume is already detached.
   945  					err = nil
   946  				}
   947  			}
   948  		}
   949  		if err != nil {
   950  			results[i] = errors.Annotatef(
   951  				maybeConvertCredentialError(err, ctx), "detaching %s from %s",
   952  				names.ReadableString(params.Volume),
   953  				names.ReadableString(params.Machine),
   954  			)
   955  		}
   956  	}
   957  	return results, nil
   958  }
   959  
   960  // ImportVolume is specified on the storage.VolumeImporter interface.
   961  func (v *ebsVolumeSource) ImportVolume(ctx context.ProviderCallContext, volumeId string, tags map[string]string) (storage.VolumeInfo, error) {
   962  	resp, err := v.env.ec2.Volumes([]string{volumeId}, nil)
   963  	if err != nil {
   964  		// TODO(axw) check for "not found" response, massage error message?
   965  		return storage.VolumeInfo{}, maybeConvertCredentialError(err, ctx)
   966  	}
   967  	if len(resp.Volumes) != 1 {
   968  		return storage.VolumeInfo{}, errors.Errorf("expected 1 volume result, got %d", len(resp.Volumes))
   969  	}
   970  	vol := resp.Volumes[0]
   971  	if vol.Status != volumeStatusAvailable {
   972  		return storage.VolumeInfo{}, errors.Errorf("cannot import volume with status %q", vol.Status)
   973  	}
   974  	if err := tagResources(v.env.ec2, ctx, tags, volumeId); err != nil {
   975  		return storage.VolumeInfo{}, errors.Annotate(err, "tagging volume")
   976  	}
   977  	return storage.VolumeInfo{
   978  		VolumeId:   volumeId,
   979  		Size:       gibToMib(uint64(vol.Size)),
   980  		Persistent: true,
   981  	}, nil
   982  }
   983  
   984  var errTooManyVolumes = errors.New("too many EBS volumes to attach")
   985  
   986  // blockDeviceNamer returns a function that cycles through block device names.
   987  //
   988  // The returned function returns the device name that should be used in
   989  // requests to the EC2 API, and and also the (kernel) device name as it
   990  // will appear on the machine.
   991  //
   992  // See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
   993  func blockDeviceNamer(numbers bool) func() (requestName, actualName string, err error) {
   994  	const (
   995  		// deviceLetterMin is the first letter to use for EBS block device names.
   996  		deviceLetterMin = 'f'
   997  		// deviceLetterMax is the last letter to use for EBS block device names.
   998  		deviceLetterMax = 'p'
   999  		// deviceNumMax is the maximum value for trailing numbers on block device name.
  1000  		deviceNumMax = 6
  1001  	)
  1002  	var n int
  1003  	letterRepeats := 1
  1004  	if numbers {
  1005  		letterRepeats = deviceNumMax
  1006  	}
  1007  	return func() (string, string, error) {
  1008  		letter := deviceLetterMin + (n / letterRepeats)
  1009  		if letter > deviceLetterMax {
  1010  			return "", "", errTooManyVolumes
  1011  		}
  1012  		deviceName := devicePrefix + string(letter)
  1013  		if numbers {
  1014  			deviceName += string('1' + (n % deviceNumMax))
  1015  		}
  1016  		n++
  1017  		realDeviceName := renamedDevicePrefix + deviceName[len(devicePrefix):]
  1018  		return deviceName, realDeviceName, nil
  1019  	}
  1020  }
  1021  
  1022  func minRootDiskSizeMiB(series string) uint64 {
  1023  	return gibToMib(common.MinRootDiskSizeGiB(series))
  1024  }
  1025  
  1026  // getBlockDeviceMappings translates constraints into BlockDeviceMappings.
  1027  //
  1028  // The first entry is always the root disk mapping, followed by instance
  1029  // stores (ephemeral disks).
  1030  func getBlockDeviceMappings(
  1031  	cons constraints.Value,
  1032  	series string,
  1033  	controller bool,
  1034  ) []ec2.BlockDeviceMapping {
  1035  	minRootDiskSizeMiB := minRootDiskSizeMiB(series)
  1036  	rootDiskSizeMiB := minRootDiskSizeMiB
  1037  	if controller {
  1038  		rootDiskSizeMiB = defaultControllerDiskSizeMiB
  1039  	}
  1040  	if cons.RootDisk != nil {
  1041  		if *cons.RootDisk >= minRootDiskSizeMiB {
  1042  			rootDiskSizeMiB = *cons.RootDisk
  1043  		} else {
  1044  			logger.Infof(
  1045  				"Ignoring root-disk constraint of %dM because it is smaller than the minimum size %dM",
  1046  				*cons.RootDisk,
  1047  				minRootDiskSizeMiB,
  1048  			)
  1049  		}
  1050  	}
  1051  	// The first block device is for the root disk.
  1052  	blockDeviceMappings := []ec2.BlockDeviceMapping{{
  1053  		DeviceName: rootDiskDeviceName,
  1054  		VolumeSize: int64(mibToGib(rootDiskSizeMiB)),
  1055  	}}
  1056  
  1057  	// Not all machines have this many instance stores.
  1058  	// Instances will be started with as many of the
  1059  	// instance stores as they can support.
  1060  	blockDeviceMappings = append(blockDeviceMappings, []ec2.BlockDeviceMapping{{
  1061  		VirtualName: "ephemeral0",
  1062  		DeviceName:  "/dev/sdb",
  1063  	}, {
  1064  		VirtualName: "ephemeral1",
  1065  		DeviceName:  "/dev/sdc",
  1066  	}, {
  1067  		VirtualName: "ephemeral2",
  1068  		DeviceName:  "/dev/sdd",
  1069  	}, {
  1070  		VirtualName: "ephemeral3",
  1071  		DeviceName:  "/dev/sde",
  1072  	}}...)
  1073  
  1074  	return blockDeviceMappings
  1075  }
  1076  
  1077  // mibToGib converts mebibytes to gibibytes.
  1078  // AWS expects GiB, we work in MiB; round up
  1079  // to nearest GiB.
  1080  func mibToGib(m uint64) uint64 {
  1081  	return (m + 1023) / 1024
  1082  }
  1083  
  1084  // gibToMib converts gibibytes to mebibytes.
  1085  func gibToMib(g uint64) uint64 {
  1086  	return g * 1024
  1087  }