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