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