Skip to content

Commit

Permalink
Add timeout support for EC2 Capacity Reservations
Browse files Browse the repository at this point in the history
  • Loading branch information
kgpdt authored and Kyle Gentle committed Apr 3, 2024
1 parent eb930bb commit e406722
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 18 deletions.
45 changes: 37 additions & 8 deletions internal/service/ec2/ec2_capacity_reservation.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package ec2

import (
"context"
"fmt"
"log"
"time"

Expand All @@ -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"
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}
}
Expand All @@ -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)
}

Expand Down
9 changes: 8 additions & 1 deletion internal/service/ec2/service_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
13 changes: 4 additions & 9 deletions internal/service/ec2/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit e406722

Please sign in to comment.