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