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_instance: Support for gp3 volumes #16620

Merged
merged 12 commits into from
Dec 17, 2020
10 changes: 10 additions & 0 deletions aws/data_source_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ func dataSourceAwsInstance() *schema.Resource {
Computed: true,
},

"throughput": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation missing in website/docs/d/instance.html.markdown -- will followup post-merge. 👍

Type: schema.TypeInt,
Computed: true,
},

"volume_size": {
Type: schema.TypeInt,
Computed: true,
Expand Down Expand Up @@ -263,6 +268,11 @@ func dataSourceAwsInstance() *schema.Resource {
Computed: true,
},

"throughput": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation missing in website/docs/d/instance.html.markdown -- will followup post-merge. 👍

Type: schema.TypeInt,
Computed: true,
},

"volume_size": {
Type: schema.TypeInt,
Computed: true,
Expand Down
49 changes: 49 additions & 0 deletions aws/data_source_aws_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,30 @@ func TestAccAWSInstanceDataSource_gp2IopsDevice(t *testing.T) {
})
}

func TestAccAWSInstanceDataSource_gp3ThroughputDevice(t *testing.T) {
resourceName := "aws_instance.test"
datasourceName := "data.aws_instance.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccInstanceDataSourceConfig_gp3ThroughputDevice,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(datasourceName, "ami", resourceName, "ami"),
resource.TestCheckResourceAttrPair(datasourceName, "instance_type", resourceName, "instance_type"),
resource.TestCheckResourceAttrPair(datasourceName, "root_block_device.#", resourceName, "root_block_device.#"),
resource.TestCheckResourceAttrPair(datasourceName, "root_block_device.0.volume_size", resourceName, "root_block_device.0.volume_size"),
resource.TestCheckResourceAttrPair(datasourceName, "root_block_device.0.volume_type", resourceName, "root_block_device.0.volume_type"),
resource.TestCheckResourceAttrPair(datasourceName, "root_block_device.0.device_name", resourceName, "root_block_device.0.device_name"),
resource.TestCheckResourceAttrPair(datasourceName, "root_block_device.0.throughput", resourceName, "root_block_device.0.throughput"),
),
},
},
})
}

func TestAccAWSInstanceDataSource_blockDevices(t *testing.T) {
resourceName := "aws_instance.test"
datasourceName := "data.aws_instance.test"
Expand Down Expand Up @@ -567,6 +591,24 @@ data "aws_instance" "test" {
}
`

// GP3ThroughputDevice
var testAccInstanceDataSourceConfig_gp3ThroughputDevice = testAccLatestAmazonLinuxHvmEbsAmiConfig() + `
resource "aws_instance" "test" {
ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id
instance_type = "t3.medium"

root_block_device {
volume_type = "gp3"
volume_size = 10
throughput = 300
}
}

data "aws_instance" "test" {
instance_id = aws_instance.test.id
}
`

// Block Device
var testAccInstanceDataSourceConfig_blockDevices = testAccLatestAmazonLinuxHvmEbsAmiConfig() + `
resource "aws_instance" "test" {
Expand Down Expand Up @@ -601,6 +643,13 @@ resource "aws_instance" "test" {
device_name = "/dev/sde"
virtual_name = "ephemeral0"
}

ebs_block_device {
device_name = "/dev/sdf"
volume_size = 10
volume_type = "gp3"
throughput = 300
}
}

data "aws_instance" "test" {
Expand Down
65 changes: 58 additions & 7 deletions aws/resource_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,14 @@ func resourceAwsInstance() *schema.Resource {
ForceNew: true,
},

"throughput": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
DiffSuppressFunc: throughputDiffSuppressFunc,
},

"volume_size": {
Type: schema.TypeInt,
Optional: true,
Expand Down Expand Up @@ -496,6 +504,13 @@ func resourceAwsInstance() *schema.Resource {
DiffSuppressFunc: iopsDiffSuppressFunc,
},

"throughput": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
DiffSuppressFunc: throughputDiffSuppressFunc,
},

"volume_size": {
Type: schema.TypeInt,
Optional: true,
Expand Down Expand Up @@ -585,11 +600,19 @@ func resourceAwsInstance() *schema.Resource {
}

func iopsDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool {
// Suppress diff if volume_type is not io1 or io2 and iops is unset or configured as 0
// Suppress diff if volume_type is not io1, io2, or gp3 and iops is unset or configured as 0
i := strings.LastIndexByte(k, '.')
vt := k[:i+1] + "volume_type"
v := d.Get(vt).(string)
return (strings.ToLower(v) != ec2.VolumeTypeIo1 || strings.ToLower(v) != ec2.VolumeTypeIo2) && new == "0"
return (strings.ToLower(v) != ec2.VolumeTypeIo1 && strings.ToLower(v) != ec2.VolumeTypeIo2 && strings.ToLower(v) != ec2.VolumeTypeGp3) && new == "0"
}

func throughputDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool {
// Suppress diff if volume_type is not gp3 and throughput is unset or configured as 0
i := strings.LastIndexByte(k, '.')
vt := k[:i+1] + "volume_type"
v := d.Get(vt).(string)
return strings.ToLower(v) != ec2.VolumeTypeGp3 && new == "0"
}

func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -1399,7 +1422,7 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
if v, ok := d.Get("root_block_device.0.iops").(int); ok && v != 0 {
// Enforce IOPs usage with a valid volume type
// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/12667
if t, ok := d.Get("root_block_device.0.volume_type").(string); ok && t != ec2.VolumeTypeIo1 && t != ec2.VolumeTypeIo2 {
if t, ok := d.Get("root_block_device.0.volume_type").(string); ok && t != ec2.VolumeTypeIo1 && t != ec2.VolumeTypeIo2 && t != ec2.VolumeTypeGp3 {
if t == "" {
// Volume defaults to gp2
t = ec2.VolumeTypeGp2
Expand All @@ -1410,6 +1433,16 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
input.Iops = aws.Int64(int64(v))
}
}
if d.HasChange("root_block_device.0.throughput") {
if v, ok := d.Get("root_block_device.0.throughput").(int); ok && v != 0 {
// Enforce throughput usage with a valid volume type
if t, ok := d.Get("root_block_device.0.volume_type").(string); ok && t != ec2.VolumeTypeGp3 {
return fmt.Errorf("error updating instance: throughput attribute not supported for type %s", t)
}
modifyVolume = true
input.Throughput = aws.Int64(int64(v))
}
}
if modifyVolume {
_, err := conn.ModifyVolume(&input)
if err != nil {
Expand Down Expand Up @@ -1743,6 +1776,9 @@ func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[st
if vol.KmsKeyId != nil {
bd["kms_key_id"] = aws.StringValue(vol.KmsKeyId)
}
if vol.Throughput != nil {
bd["throughput"] = aws.Int64Value(vol.Throughput)
}
if instanceBd.DeviceName != nil {
bd["device_name"] = aws.StringValue(instanceBd.DeviceName)
}
Expand Down Expand Up @@ -1931,9 +1967,9 @@ func readBlockDeviceMappingsFromConfig(d *schema.ResourceData, conn *ec2.EC2) ([
if v, ok := bd["volume_type"].(string); ok && v != "" {
ebs.VolumeType = aws.String(v)
if iops, ok := bd["iops"].(int); ok && iops > 0 {
if ec2.VolumeTypeIo1 == strings.ToLower(v) || ec2.VolumeTypeIo2 == strings.ToLower(v) {
if ec2.VolumeTypeIo1 == strings.ToLower(v) || ec2.VolumeTypeIo2 == strings.ToLower(v) || ec2.VolumeTypeGp3 == strings.ToLower(v) {
// Condition: This parameter is required for requests to create io1 or io2
// volumes; it is not used in requests to create gp2, st1, sc1, or
// volumes and optional for gp3; it is not used in requests to create gp2, st1, sc1, or
// standard volumes.
// See: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html
ebs.Iops = aws.Int64(int64(iops))
Expand All @@ -1942,6 +1978,13 @@ func readBlockDeviceMappingsFromConfig(d *schema.ResourceData, conn *ec2.EC2) ([
// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/12667
return nil, fmt.Errorf("error creating resource: iops attribute not supported for ebs_block_device with volume_type %s", v)
}
} else if throughput, ok := bd["throughput"].(int); ok && throughput > 0 {
// `throughput` is only valid for gp3
if ec2.VolumeTypeGp3 == strings.ToLower(v) {
ebs.Throughput = aws.Int64(int64(throughput))
} else {
return nil, fmt.Errorf("error creating resource: throughput attribute not supported for ebs_block_device with volume_type %s", v)
}
}
}

Expand Down Expand Up @@ -1997,8 +2040,8 @@ func readBlockDeviceMappingsFromConfig(d *schema.ResourceData, conn *ec2.EC2) ([
if v, ok := bd["volume_type"].(string); ok && v != "" {
ebs.VolumeType = aws.String(v)
if iops, ok := bd["iops"].(int); ok && iops > 0 {
if ec2.VolumeTypeIo1 == strings.ToLower(v) || ec2.VolumeTypeIo2 == strings.ToLower(v) {
// Only set the iops attribute if the volume type is io1 or io2. Setting otherwise
if ec2.VolumeTypeIo1 == strings.ToLower(v) || ec2.VolumeTypeIo2 == strings.ToLower(v) || ec2.VolumeTypeGp3 == strings.ToLower(v) {
// Only set the iops attribute if the volume type is io1, io2, or gp3. Setting otherwise
// can trigger a refresh/plan loop based on the computed value that is given
// from AWS, and prevent us from specifying 0 as a valid iops.
// See https://github.com/hashicorp/terraform/pull/4146
Expand All @@ -2009,6 +2052,14 @@ func readBlockDeviceMappingsFromConfig(d *schema.ResourceData, conn *ec2.EC2) ([
// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/12667
return nil, fmt.Errorf("error creating resource: iops attribute not supported for root_block_device with volume_type %s", v)
}
} else if throughput, ok := bd["throughput"].(int); ok && throughput > 0 {
// throughput is only valid for gp3
if ec2.VolumeTypeGp3 == strings.ToLower(v) {
ebs.Throughput = aws.Int64(int64(throughput))
} else {
// Enforce throughput usage with a valid volume type
return nil, fmt.Errorf("error creating resource: throughput attribute not supported for root_block_device with volume_type %s", v)
}
}
}

Expand Down
Loading