From 0016fc83d5424ea40c92102d3629f3e9490fd24a Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Thu, 25 Mar 2021 17:38:30 -0400 Subject: [PATCH] resource/aws_subnet: Handle read-after-create eventual consistency (#18392) * resource/aws_subnet: Handle read-after-create eventual consistency Reference: https://github.com/hashicorp/terraform-provider-aws/issues/12829 Reference: https://github.com/hashicorp/terraform-provider-aws/issues/16796 Output from acceptance testing in AWS Commercial: ``` --- PASS: TestAccAWSSubnet_availabilityZoneId (53.74s) --- PASS: TestAccAWSSubnet_basic (52.50s) --- PASS: TestAccAWSSubnet_defaultAndIgnoreTags (80.26s) --- PASS: TestAccAWSSubnet_defaultTags_providerAndResource_duplicateTag (6.45s) --- PASS: TestAccAWSSubnet_defaultTags_providerAndResource_nonOverlappingTag (84.84s) --- PASS: TestAccAWSSubnet_defaultTags_providerAndResource_overlappingTag (86.61s) --- PASS: TestAccAWSSubnet_defaultTags_providerOnly (87.99s) --- PASS: TestAccAWSSubnet_defaultTags_updateToProviderOnly (69.48s) --- PASS: TestAccAWSSubnet_defaultTags_updateToResourceOnly (72.12s) --- PASS: TestAccAWSSubnet_disappears (41.08s) --- PASS: TestAccAWSSubnet_enableIpv6 (109.36s) --- PASS: TestAccAWSSubnet_ignoreTags (81.13s) --- PASS: TestAccAWSSubnet_ipv6 (118.69s) --- PASS: TestAccAWSSubnet_MapPublicIpOnLaunch (118.55s) --- PASS: TestAccAWSSubnet_tags (106.25s) --- SKIP: TestAccAWSSubnet_CustomerOwnedIpv4Pool (1.44s) --- SKIP: TestAccAWSSubnet_MapCustomerOwnedIpOnLaunch (1.40s) --- SKIP: TestAccAWSSubnet_outpost (1.70s) ``` Output from acceptance testing in AWS GovCloud (US): ``` --- PASS: TestAccAWSSubnet_availabilityZoneId (55.41s) --- PASS: TestAccAWSSubnet_basic (55.46s) --- PASS: TestAccAWSSubnet_defaultAndIgnoreTags (85.95s) --- PASS: TestAccAWSSubnet_defaultTags_providerAndResource_duplicateTag (6.64s) --- PASS: TestAccAWSSubnet_defaultTags_providerAndResource_nonOverlappingTag (82.23s) --- PASS: TestAccAWSSubnet_defaultTags_providerAndResource_overlappingTag (84.95s) --- PASS: TestAccAWSSubnet_defaultTags_providerOnly (86.26s) --- PASS: TestAccAWSSubnet_defaultTags_updateToProviderOnly (69.05s) --- PASS: TestAccAWSSubnet_defaultTags_updateToResourceOnly (73.74s) --- PASS: TestAccAWSSubnet_disappears (42.78s) --- PASS: TestAccAWSSubnet_enableIpv6 (117.40s) --- PASS: TestAccAWSSubnet_ignoreTags (83.74s) --- PASS: TestAccAWSSubnet_ipv6 (126.25s) --- PASS: TestAccAWSSubnet_MapPublicIpOnLaunch (124.27s) --- PASS: TestAccAWSSubnet_tags (115.31s) --- SKIP: TestAccAWSSubnet_CustomerOwnedIpv4Pool (6.95s) --- SKIP: TestAccAWSSubnet_MapCustomerOwnedIpOnLaunch (2.60s) --- SKIP: TestAccAWSSubnet_outpost (2.14s) ``` * Update CHANGELOG for #18392 --- .changelog/18392.txt | 3 ++ aws/internal/service/ec2/waiter/waiter.go | 1 + aws/resource_aws_subnet.go | 55 ++++++++++++++++++----- 3 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 .changelog/18392.txt diff --git a/.changelog/18392.txt b/.changelog/18392.txt new file mode 100644 index 000000000000..34bcc004200e --- /dev/null +++ b/.changelog/18392.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_subnet: Handle EC2 eventual consistency errors on creation +``` diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index 7c3a06ebf89b..07bf6682c73a 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -270,6 +270,7 @@ func SecurityGroupCreated(conn *ec2.EC2, id string, timeout time.Duration) (*ec2 } const ( + SubnetPropagationTimeout = 2 * time.Minute SubnetAttributePropagationTimeout = 5 * time.Minute ) diff --git a/aws/resource_aws_subnet.go b/aws/resource_aws_subnet.go index 46e98235d3c4..c36de8b53b0c 100644 --- a/aws/resource_aws_subnet.go +++ b/aws/resource_aws_subnet.go @@ -7,10 +7,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsSubnet() *schema.Resource { @@ -232,23 +235,53 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsInput{ - SubnetIds: []*string{aws.String(d.Id())}, - }) + var subnet *ec2.Subnet - if err != nil { - if isAWSErr(err, "InvalidSubnetID.NotFound", "") { - log.Printf("[WARN] Subnet (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil + err := resource.Retry(waiter.SubnetPropagationTimeout, func() *resource.RetryError { + var err error + + subnet, err = finder.SubnetByID(conn, d.Id()) + + if d.IsNewResource() && tfawserr.ErrCodeEquals(err, "InvalidSubnetID.NotFound") { + return resource.RetryableError(err) } - return err + + if err != nil { + return resource.NonRetryableError(err) + } + + if d.IsNewResource() && subnet == nil { + return resource.RetryableError(&resource.NotFoundError{ + LastError: fmt.Errorf("EC2 Subnet (%s) not found", d.Id()), + }) + } + + return nil + }) + + if tfresource.TimedOut(err) { + subnet, err = finder.SubnetByID(conn, d.Id()) } - if resp == nil { + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, "InvalidSubnetID.NotFound") { + log.Printf("[WARN] EC2 Subnet (%s) not found, removing from state", d.Id()) + d.SetId("") return nil } - subnet := resp.Subnets[0] + if err != nil { + return fmt.Errorf("error reading EC2 Subnet (%s): %w", d.Id(), err) + } + + if subnet == nil { + if d.IsNewResource() { + return fmt.Errorf("error reading EC2 Subnet (%s): not found after creation", d.Id()) + } + + log.Printf("[WARN] EC2 Subnet (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } d.Set("vpc_id", subnet.VpcId) d.Set("availability_zone", subnet.AvailabilityZone)