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