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