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