Skip to content

Commit

Permalink
UPSTREAM: resource/aws_vpc: Handle read-after-create eventual consist…
Browse files Browse the repository at this point in the history
…ency (hashicorp#18391)

* resource/aws_vpc: Handle read-after-create eventual consistency

Reference: hashicorp#16796

Output from acceptance testing in AWS Commercial:

```
--- PASS: TestAccAWSVpc_AssignGeneratedIpv6CidrBlock (82.88s)
--- PASS: TestAccAWSVpc_basic (41.94s)
--- PASS: TestAccAWSVpc_bothDnsOptionsSet (50.15s)
--- PASS: TestAccAWSVpc_classiclinkDnsSupportOptionSet (42.24s)
--- PASS: TestAccAWSVpc_classiclinkOptionSet (42.26s)
--- PASS: TestAccAWSVpc_coreMismatchedDiffs (29.27s)
--- PASS: TestAccAWSVpc_defaultAndIgnoreTags (62.73s)
--- PASS: TestAccAWSVpc_defaultTags_providerAndResource_duplicateTag (4.83s)
--- PASS: TestAccAWSVpc_defaultTags_providerAndResource_nonOverlappingTag (70.21s)
--- PASS: TestAccAWSVpc_defaultTags_providerAndResource_overlappingTag (69.97s)
--- PASS: TestAccAWSVpc_defaultTags_providerOnly (65.84s)
--- PASS: TestAccAWSVpc_defaultTags_updateToProviderOnly (55.23s)
--- PASS: TestAccAWSVpc_defaultTags_updateToResourceOnly (50.23s)
--- PASS: TestAccAWSVpc_DisabledDnsSupport (49.90s)
--- PASS: TestAccAWSVpc_disappears (22.42s)
--- PASS: TestAccAWSVpc_ignoreTags (64.60s)
--- PASS: TestAccAWSVpc_tags (82.89s)
--- PASS: TestAccAWSVpc_Tenancy (84.83s)
--- PASS: TestAccAWSVpc_update (67.73s)
```

Output from acceptance testing in AWS GovCloud (US):

```
--- PASS: TestAccAWSVpc_AssignGeneratedIpv6CidrBlock (85.77s)
--- PASS: TestAccAWSVpc_basic (36.99s)
--- PASS: TestAccAWSVpc_bothDnsOptionsSet (48.65s)
--- PASS: TestAccAWSVpc_coreMismatchedDiffs (32.11s)
--- PASS: TestAccAWSVpc_defaultAndIgnoreTags (63.25s)
--- PASS: TestAccAWSVpc_defaultTags_providerAndResource_duplicateTag (4.70s)
--- PASS: TestAccAWSVpc_defaultTags_providerAndResource_nonOverlappingTag (59.36s)
--- PASS: TestAccAWSVpc_defaultTags_providerAndResource_overlappingTag (59.09s)
--- PASS: TestAccAWSVpc_defaultTags_providerOnly (60.21s)
--- PASS: TestAccAWSVpc_defaultTags_updateToProviderOnly (46.79s)
--- PASS: TestAccAWSVpc_defaultTags_updateToResourceOnly (48.55s)
--- PASS: TestAccAWSVpc_DisabledDnsSupport (47.71s)
--- PASS: TestAccAWSVpc_disappears (23.64s)
--- PASS: TestAccAWSVpc_ignoreTags (62.21s)
--- PASS: TestAccAWSVpc_tags (82.23s)
--- PASS: TestAccAWSVpc_Tenancy (83.42s)
--- PASS: TestAccAWSVpc_update (66.22s)
--- SKIP: TestAccAWSVpc_classiclinkDnsSupportOptionSet (15.15s)
--- SKIP: TestAccAWSVpc_classiclinkOptionSet (15.15s)
```

* Update CHANGELOG for hashicorp#18391
  • Loading branch information
bflad authored and staebler committed Dec 15, 2021
1 parent 045a9cf commit fd40b24
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .changelog/18391.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_vpc: Handle EC2 eventual consistency errors on creation
```
31 changes: 31 additions & 0 deletions aws/internal/service/ec2/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,34 @@ func ClientVpnRouteByID(conn *ec2.EC2, routeID string) (*ec2.DescribeClientVpnRo

return ClientVpnRoute(conn, endpointID, targetSubnetID, destinationCidr)
}

// VpcByID looks up a Vpc by ID. When not found, returns nil and potentially an API error.
func VpcByID(conn *ec2.EC2, id string) (*ec2.Vpc, error) {
input := &ec2.DescribeVpcsInput{
VpcIds: aws.StringSlice([]string{id}),
}

output, err := conn.DescribeVpcs(input)

if err != nil {
return nil, err
}

if output == nil {
return nil, nil
}

for _, vpc := range output.Vpcs {
if vpc == nil {
continue
}

if aws.StringValue(vpc.VpcId) != id {
continue
}

return vpc, nil
}

return nil, nil
}
2 changes: 2 additions & 0 deletions aws/internal/service/ec2/waiter/waiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const (

// General timeout for EC2 resource creations to propagate
PropagationTimeout = 2 * time.Minute

VpcPropagationTimeout = 2 * time.Minute
)

// LocalGatewayRouteTableVpcAssociationAssociated waits for a LocalGatewayRouteTableVpcAssociation to return Associated
Expand Down
57 changes: 49 additions & 8 deletions aws/resource_aws_vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ import (
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"

"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 resourceAwsVpc() *schema.Resource {
//lintignore:R011
// lintignore:R011
return &schema.Resource{
Create: resourceAwsVpcCreate,
Read: resourceAwsVpcRead,
Expand Down Expand Up @@ -234,18 +239,54 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

// Refresh the VPC state
vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Id())()
var vpc *ec2.Vpc

err := resource.Retry(waiter.VpcPropagationTimeout, func() *resource.RetryError {
var err error

vpc, err = finder.VpcByID(conn, d.Id())

if d.IsNewResource() && tfawserr.ErrCodeEquals(err, "InvalidVpcID.NotFound") {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}

if d.IsNewResource() && vpc == nil {
return resource.RetryableError(&resource.NotFoundError{
LastError: fmt.Errorf("EC2 VPC (%s) not found", d.Id()),
})
}

return nil
})

if tfresource.TimedOut(err) {
vpc, err = finder.VpcByID(conn, d.Id())
}

if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, "InvalidVpcID.NotFound") {
log.Printf("[WARN] EC2 VPC (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return err
return fmt.Errorf("error reading EC2 VPC (%s): %w", d.Id(), err)
}
if vpcRaw == nil {

if vpc == nil {
if d.IsNewResource() {
return fmt.Errorf("error reading EC2 VPC (%s): not found after creation", d.Id())
}

log.Printf("[WARN] EC2 VPC (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

// VPC stuff
vpc := vpcRaw.(*ec2.Vpc)
vpcid := d.Id()
d.Set("cidr_block", vpc.CidrBlock)
d.Set("dhcp_options_id", vpc.DhcpOptionsId)
Expand Down Expand Up @@ -273,7 +314,7 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error {
d.Set("ipv6_cidr_block", "")

for _, a := range vpc.Ipv6CidrBlockAssociationSet {
if aws.StringValue(a.Ipv6CidrBlockState.State) == ec2.VpcCidrBlockStateCodeAssociated { //we can only ever have 1 IPv6 block associated at once
if aws.StringValue(a.Ipv6CidrBlockState.State) == ec2.VpcCidrBlockStateCodeAssociated { // we can only ever have 1 IPv6 block associated at once
d.Set("assign_generated_ipv6_cidr_block", true)
d.Set("ipv6_association_id", a.AssociationId)
d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)
Expand Down

0 comments on commit fd40b24

Please sign in to comment.