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 }