Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aws_ebs_volume: gp3 volume scalable throughput #16517

Merged
merged 7 commits into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 37 additions & 24 deletions aws/resource_aws_ebs_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

Expand Down Expand Up @@ -61,15 +62,17 @@ func resourceAwsEbsVolume() *schema.Resource {
ForceNew: true,
},
"size": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Type: schema.TypeInt,
Optional: true,
Computed: true,
ExactlyOneOf: []string{"size", "snapshot_id"},
},
"snapshot_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ExactlyOneOf: []string{"size", "snapshot_id"},
},
"outpost_arn": {
Type: schema.TypeString,
Expand All @@ -83,6 +86,12 @@ func resourceAwsEbsVolume() *schema.Resource {
Computed: true,
},
"tags": tagsSchema(),
"throughput": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ValidateFunc: validation.IntBetween(125, 1000),
},
},
}
}
Expand Down Expand Up @@ -112,6 +121,9 @@ func resourceAwsEbsVolumeCreate(d *schema.ResourceData, meta interface{}) error
if value, ok := d.GetOk("outpost_arn"); ok {
request.OutpostArn = aws.String(value.(string))
}
if value, ok := d.GetOk("throughput"); ok {
request.Throughput = aws.Int64(int64(value.(int)))
}

// IOPs are only valid, and required for, storage type io1 and io2. The current minimum
// is 100. Hard validation in place to return an error if IOPs are provided
Expand Down Expand Up @@ -168,27 +180,27 @@ func resourceAwsEbsVolumeCreate(d *schema.ResourceData, meta interface{}) error
func resourceAWSEbsVolumeUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

requestUpdate := false
params := &ec2.ModifyVolumeInput{
VolumeId: aws.String(d.Id()),
}
if d.HasChangesExcept("tags") {
params := &ec2.ModifyVolumeInput{
VolumeId: aws.String(d.Id()),
}

if d.HasChange("size") {
requestUpdate = true
params.Size = aws.Int64(int64(d.Get("size").(int)))
}
if d.HasChange("size") {
params.Size = aws.Int64(int64(d.Get("size").(int)))
}

if d.HasChange("type") {
requestUpdate = true
params.VolumeType = aws.String(d.Get("type").(string))
}
if d.HasChange("type") {
params.VolumeType = aws.String(d.Get("type").(string))
}

if d.HasChange("iops") {
requestUpdate = true
params.Iops = aws.Int64(int64(d.Get("iops").(int)))
}
if d.HasChange("iops") {
params.Iops = aws.Int64(int64(d.Get("iops").(int)))
}

if d.HasChange("throughput") {
params.Throughput = aws.Int64(int64(d.Get("throughput").(int)))
}

if requestUpdate {
result, err := conn.ModifyVolume(params)
if err != nil {
return err
Expand Down Expand Up @@ -286,6 +298,7 @@ func resourceAwsEbsVolumeRead(d *schema.ResourceData, meta interface{}) error {
d.Set("snapshot_id", aws.StringValue(volume.SnapshotId))
d.Set("outpost_arn", aws.StringValue(volume.OutpostArn))
d.Set("multi_attach_enabled", volume.MultiAttachEnabled)
d.Set("throughput", aws.Int64Value(volume.Throughput))
ewbankkit marked this conversation as resolved.
Show resolved Hide resolved

if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(volume.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %s", err)
Expand Down
141 changes: 141 additions & 0 deletions aws/resource_aws_ebs_volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func TestAccAWSEBSVolume_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "type", "gp2"),
resource.TestCheckResourceAttr(resourceName, "outpost_arn", ""),
resource.TestCheckResourceAttr(resourceName, "multi_attach_enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
ewbankkit marked this conversation as resolved.
Show resolved Hide resolved
),
},
{
Expand All @@ -115,6 +116,7 @@ func TestAccAWSEBSVolume_updateAttachedEbsVolume(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "size", "10"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
{
Expand All @@ -127,6 +129,7 @@ func TestAccAWSEBSVolume_updateAttachedEbsVolume(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "size", "20"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
},
Expand All @@ -148,6 +151,7 @@ func TestAccAWSEBSVolume_updateSize(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "size", "1"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
{
Expand All @@ -160,6 +164,7 @@ func TestAccAWSEBSVolume_updateSize(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "size", "10"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
},
Expand All @@ -181,6 +186,7 @@ func TestAccAWSEBSVolume_updateType(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "type", "gp2"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
{
Expand All @@ -193,6 +199,7 @@ func TestAccAWSEBSVolume_updateType(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "type", "sc1"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
},
Expand All @@ -214,6 +221,7 @@ func TestAccAWSEBSVolume_updateIops_Io1(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "iops", "100"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
{
Expand All @@ -226,6 +234,7 @@ func TestAccAWSEBSVolume_updateIops_Io1(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "iops", "200"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
},
Expand All @@ -247,6 +256,7 @@ func TestAccAWSEBSVolume_updateIops_Io2(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "iops", "100"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
{
Expand All @@ -259,6 +269,7 @@ func TestAccAWSEBSVolume_updateIops_Io2(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "iops", "200"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
},
Expand All @@ -284,6 +295,7 @@ func TestAccAWSEBSVolume_kmsKey(t *testing.T) {
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "encrypted", "true"),
resource.TestCheckResourceAttrPair(resourceName, "kms_key_id", kmsKeyResourceName, "arn"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
{
Expand All @@ -308,6 +320,7 @@ func TestAccAWSEBSVolume_NoIops(t *testing.T) {
Config: testAccAwsEbsVolumeConfigWithNoIops,
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
{
Expand Down Expand Up @@ -378,6 +391,7 @@ func TestAccAWSEBSVolume_multiAttach(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "multi_attach_enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "throughput", "0"),
),
},
{
Expand Down Expand Up @@ -416,6 +430,100 @@ func TestAccAWSEBSVolume_outpost(t *testing.T) {
})
}

func TestAccAWSEBSVolume_gp3_basic(t *testing.T) {
var v ec2.Volume
resourceName := "aws_ebs_volume.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: resourceName,
Providers: testAccProviders,
CheckDestroy: testAccCheckVolumeDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsEbsVolumeConfigSizeType(rName, 10, "gp3"),
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
testAccMatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`volume/vol-.+`)),
resource.TestCheckResourceAttr(resourceName, "encrypted", "false"),
testCheckResourceAttrGreaterThanValue(resourceName, "iops", "0"),
resource.TestCheckResourceAttr(resourceName, "kms_key_id", ""),
resource.TestCheckResourceAttr(resourceName, "multi_attach_enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "outpost_arn", ""),
resource.TestCheckResourceAttr(resourceName, "size", "10"),
resource.TestCheckResourceAttr(resourceName, "snapshot_id", ""),
resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
testCheckResourceAttrGreaterThanValue(resourceName, "throughput", "0"),
resource.TestCheckResourceAttr(resourceName, "type", "gp3"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAWSEBSVolume_gp3_throughput(t *testing.T) {
var v ec2.Volume
resourceName := "aws_ebs_volume.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: resourceName,
Providers: testAccProviders,
CheckDestroy: testAccCheckVolumeDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsEbsVolumeConfigGp3Throughput(rName, 400),
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
testAccMatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`volume/vol-.+`)),
resource.TestCheckResourceAttr(resourceName, "encrypted", "false"),
testCheckResourceAttrGreaterThanValue(resourceName, "iops", "0"),
resource.TestCheckResourceAttr(resourceName, "kms_key_id", ""),
resource.TestCheckResourceAttr(resourceName, "multi_attach_enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "outpost_arn", ""),
resource.TestCheckResourceAttr(resourceName, "size", "10"),
resource.TestCheckResourceAttr(resourceName, "snapshot_id", ""),
resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
resource.TestCheckResourceAttr(resourceName, "throughput", "400"),
resource.TestCheckResourceAttr(resourceName, "type", "gp3"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccAwsEbsVolumeConfigGp3Throughput(rName, 600),
Check: resource.ComposeTestCheckFunc(
testAccCheckVolumeExists(resourceName, &v),
testAccMatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`volume/vol-.+`)),
resource.TestCheckResourceAttr(resourceName, "encrypted", "false"),
testCheckResourceAttrGreaterThanValue(resourceName, "iops", "0"),
resource.TestCheckResourceAttr(resourceName, "kms_key_id", ""),
resource.TestCheckResourceAttr(resourceName, "multi_attach_enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "outpost_arn", ""),
resource.TestCheckResourceAttr(resourceName, "size", "10"),
resource.TestCheckResourceAttr(resourceName, "snapshot_id", ""),
resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
resource.TestCheckResourceAttr(resourceName, "throughput", "600"),
ewbankkit marked this conversation as resolved.
Show resolved Hide resolved
resource.TestCheckResourceAttr(resourceName, "type", "gp3"),
),
},
},
})
}

func TestAccAWSEBSVolume_disappears(t *testing.T) {
var v ec2.Volume
resourceName := "aws_ebs_volume.test"
Expand Down Expand Up @@ -913,3 +1021,36 @@ resource "aws_ebs_volume" "test" {
}
`, rName)
}

func testAccAwsEbsVolumeConfigSizeType(rName string, size int, volumeType string) string {
return composeConfig(
testAccAvailableAZsNoOptInConfig(),
fmt.Sprintf(`
resource "aws_ebs_volume" "test" {
availability_zone = data.aws_availability_zones.available.names[0]
type = %[3]q
size = %[2]d

tags = {
Name = %[1]q
}
}
`, rName, size, volumeType))
}

func testAccAwsEbsVolumeConfigGp3Throughput(rName string, throughput int) string {
return composeConfig(
testAccAvailableAZsNoOptInConfig(),
fmt.Sprintf(`
resource "aws_ebs_volume" "test" {
availability_zone = data.aws_availability_zones.available.names[0]
type = "gp3"
size = 10
throughput = %[2]d

tags = {
Name = %[1]q
}
}
`, rName, throughput))
}
3 changes: 2 additions & 1 deletion website/docs/r/ebs_volume.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ The following arguments are supported:
* `size` - (Optional) The size of the drive in GiBs.
* `snapshot_id` (Optional) A snapshot to base the EBS volume off of.
* `outpost_arn` - (Optional) The Amazon Resource Name (ARN) of the Outpost.
* `type` - (Optional) The type of EBS volume. Can be "standard", "gp2", "io1", "io2", "sc1" or "st1" (Default: "gp2").
* `type` - (Optional) The type of EBS volume. Can be `standard`, `gp2`, `gp3`, `io1`, `io2`, `sc1` or `st1` (Default: `gp2`).
* `kms_key_id` - (Optional) The ARN for the KMS encryption key. When specifying `kms_key_id`, `encrypted` needs to be set to true.
* `tags` - (Optional) A map of tags to assign to the resource.
* `throughput` - (Optional) The throughput that the volume supports, in MiB/s. Only valid for `type` of `gp3`.

~> **NOTE**: When changing the `size`, `iops` or `type` of an instance, there are [considerations](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/considerations.html) to be aware of that Amazon have written about this.

Expand Down