From e406722b5e485bd9674249df6e665342d1c4a11f Mon Sep 17 00:00:00 2001 From: Kyle Gentle <140856249+kgpdt@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:38:13 -0400 Subject: [PATCH] Add timeout support for EC2 Capacity Reservations --- .../service/ec2/ec2_capacity_reservation.go | 45 +++++++++++++++---- internal/service/ec2/service_package.go | 9 +++- internal/service/ec2/wait.go | 13 ++---- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/internal/service/ec2/ec2_capacity_reservation.go b/internal/service/ec2/ec2_capacity_reservation.go index 115632f77437..05b566d87d9b 100644 --- a/internal/service/ec2/ec2_capacity_reservation.go +++ b/internal/service/ec2/ec2_capacity_reservation.go @@ -5,6 +5,7 @@ package ec2 import ( "context" + "fmt" "log" "time" @@ -13,6 +14,7 @@ import ( "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -37,6 +39,12 @@ func ResourceCapacityReservation() *schema.Resource { CustomizeDiff: verify.SetTagsDiff, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -163,15 +171,25 @@ func resourceCapacityReservationCreate(ctx context.Context, d *schema.ResourceDa input.Tenancy = aws.String(v.(string)) } - output, err := conn.CreateCapacityReservationWithContext(ctx, input) + err := retry.RetryContext(ctx, d.Timeout(schema.TimeoutCreate)-(5*time.Second), func() *retry.RetryError { + output, err := conn.CreateCapacityReservationWithContext(ctx, input) + + if err != nil { + if tfawserr.ErrCodeEquals(err, errCodeInsufficientInstanceCapacity) { + return retry.RetryableError(fmt.Errorf("creating EC2 Capacity Reservation: insufficient capacity for instance type %s in availability zone %s", *input.InstanceType, *input.AvailabilityZone)) + } + return retry.NonRetryableError(fmt.Errorf("creating EC2 Capacity Reservation: %s", err)) + } + + d.SetId(aws.StringValue(output.CapacityReservation.CapacityReservationId)) + return nil + }) if err != nil { - return sdkdiag.AppendErrorf(diags, "creating EC2 Capacity Reservation: %s", err) + return sdkdiag.AppendFromErr(diags, err) } - d.SetId(aws.StringValue(output.CapacityReservation.CapacityReservationId)) - - if _, err := WaitCapacityReservationActive(ctx, conn, d.Id()); err != nil { + if _, err := WaitCapacityReservationActive(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 Capacity Reservation (%s) create: %s", d.Id(), err) } @@ -235,13 +253,24 @@ func resourceCapacityReservationUpdate(ctx context.Context, d *schema.ResourceDa input.EndDate = aws.Time(v) } - _, err := conn.ModifyCapacityReservationWithContext(ctx, input) + err := retry.RetryContext(ctx, d.Timeout(schema.TimeoutUpdate)-(5*time.Second), func() *retry.RetryError { + _, err := conn.ModifyCapacityReservationWithContext(ctx, input) + + if err != nil { + if tfawserr.ErrCodeEquals(err, errCodeInsufficientInstanceCapacity) { + return retry.RetryableError(fmt.Errorf("updating EC2 Capacity Reservation: insufficient capacity")) + } + return retry.NonRetryableError(fmt.Errorf("updating EC2 Capacity Reservation: %s", err)) + } + + return nil + }) if err != nil { return sdkdiag.AppendErrorf(diags, "updating EC2 Capacity Reservation (%s): %s", d.Id(), err) } - if _, err := WaitCapacityReservationActive(ctx, conn, d.Id()); err != nil { + if _, err := WaitCapacityReservationActive(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 Capacity Reservation (%s) update: %s", d.Id(), err) } } @@ -266,7 +295,7 @@ func resourceCapacityReservationDelete(ctx context.Context, d *schema.ResourceDa return sdkdiag.AppendErrorf(diags, "deleting EC2 Capacity Reservation (%s): %s", d.Id(), err) } - if _, err := WaitCapacityReservationDeleted(ctx, conn, d.Id()); err != nil { + if _, err := WaitCapacityReservationDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 Capacity Reservation (%s) delete: %s", d.Id(), err) } diff --git a/internal/service/ec2/service_package.go b/internal/service/ec2/service_package.go index 599867c0b317..227d41d73f49 100644 --- a/internal/service/ec2/service_package.go +++ b/internal/service/ec2/service_package.go @@ -42,7 +42,14 @@ func (p *servicePackage) CustomizeConn(ctx context.Context, conn *ec2_sdkv1.EC2) } case "RunInstances": - // `InsufficientInstanceCapacity` error has status code 500 and AWS SDK try retry this error by default. + // `InsufficientInstanceCapacity` error has status code 500 and AWS SDK try retries this error by default. + if tfawserr.ErrCodeEquals(err, errCodeInsufficientInstanceCapacity) { + r.Retryable = aws_sdkv1.Bool(false) + } + + case "CreateCapacityReservation": + // `InsufficientInstanceCapacity` error has status code 500 and AWS SDK retries this error by default. + // Terraform should handle retries so that we can support a timeout. if tfawserr.ErrCodeEquals(err, errCodeInsufficientInstanceCapacity) { r.Retryable = aws_sdkv1.Bool(false) } diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index c74852bbedb8..762657c45d30 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -81,17 +81,12 @@ func WaitAvailabilityZoneGroupNotOptedIn(ctx context.Context, conn *ec2.EC2, nam return nil, err } -const ( - CapacityReservationActiveTimeout = 2 * time.Minute - CapacityReservationDeletedTimeout = 2 * time.Minute -) - -func WaitCapacityReservationActive(ctx context.Context, conn *ec2.EC2, id string) (*ec2.CapacityReservation, error) { +func WaitCapacityReservationActive(ctx context.Context, conn *ec2.EC2, id string, timeout time.Duration) (*ec2.CapacityReservation, error) { stateConf := &retry.StateChangeConf{ Pending: []string{ec2.CapacityReservationStatePending}, Target: []string{ec2.CapacityReservationStateActive}, Refresh: StatusCapacityReservationState(ctx, conn, id), - Timeout: CapacityReservationActiveTimeout, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -103,12 +98,12 @@ func WaitCapacityReservationActive(ctx context.Context, conn *ec2.EC2, id string return nil, err } -func WaitCapacityReservationDeleted(ctx context.Context, conn *ec2.EC2, id string) (*ec2.CapacityReservation, error) { +func WaitCapacityReservationDeleted(ctx context.Context, conn *ec2.EC2, id string, timeout time.Duration) (*ec2.CapacityReservation, error) { stateConf := &retry.StateChangeConf{ Pending: []string{ec2.CapacityReservationStateActive}, Target: []string{}, Refresh: StatusCapacityReservationState(ctx, conn, id), - Timeout: CapacityReservationDeletedTimeout, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx)