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