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