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