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 }