Skip to content

Commit

Permalink
Merge pull request #34070 from ddericco/f-aws_target_group-unhealthy_…
Browse files Browse the repository at this point in the history
…connection_termination

Add support for disabling connection termination for unhealthy targets
  • Loading branch information
ewbankkit authored Oct 24, 2023
2 parents e659dc8 + 5fbb592 commit 7c7ee15
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .changelog/34070.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_lb_target_group: Add `target_health_state` configuration block
```

```release-note:enhancement
resource/aws_lb_target_group: Remove default value (`false`) for `connection_termination` argument and mark as Computed, to support new default behavior for UDP/TCP_UDP target groups
```
72 changes: 71 additions & 1 deletion internal/service/elbv2/target_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func ResourceTargetGroup() *schema.Resource {
"connection_termination": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Computed: true,
},
"deregistration_delay": {
Type: nullable.TypeNullableInt,
Expand Down Expand Up @@ -316,6 +316,19 @@ func ResourceTargetGroup() *schema.Resource {
},
},
},
"target_health_state": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enable_unhealthy_connection_termination": {
Type: schema.TypeBool,
Required: true,
},
},
},
},
"target_type": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -537,6 +550,22 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta
}
}

// Only supported for TCP & TLS protocols
if v, ok := d.Get("protocol").(string); ok {
if v == elbv2.ProtocolEnumTcp || v == elbv2.ProtocolEnumTls {
if v, ok := d.GetOk("target_health_state"); ok && len(v.([]interface{})) > 0 {
targetHealthStateBlock := v.([]interface{})
targetHealthState := targetHealthStateBlock[0].(map[string]interface{})
attrs = append(attrs,
&elbv2.TargetGroupAttribute{
Key: aws.String("target_health_state.unhealthy.connection_termination.enabled"),
Value: aws.String(strconv.FormatBool(targetHealthState["enable_unhealthy_connection_termination"].(bool))),
},
)
}
}
}

if v, ok := d.GetOk("stickiness"); ok && len(v.([]interface{})) > 0 {
stickinessBlocks := v.([]interface{})
stickiness := stickinessBlocks[0].(map[string]interface{})
Expand Down Expand Up @@ -789,6 +818,18 @@ func resourceTargetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta
})
}

if d.HasChange("target_health_state") {
targetHealthStateBlock := d.Get("target_health_state").([]interface{})
if len(targetHealthStateBlock) == 1 {
targetHealthState := targetHealthStateBlock[0].(map[string]interface{})
attrs = append(attrs,
&elbv2.TargetGroupAttribute{
Key: aws.String("target_health_state.unhealthy.connection_termination.enabled"),
Value: aws.String(strconv.FormatBool(targetHealthState["enable_unhealthy_connection_termination"].(bool))),
})
}
}

if d.HasChange("target_failover") {
failoverBlock := d.Get("target_failover").([]interface{})
if len(failoverBlock) == 1 {
Expand Down Expand Up @@ -1102,6 +1143,14 @@ func flattenTargetGroupResource(ctx context.Context, d *schema.ResourceData, met
return fmt.Errorf("setting stickiness: %w", err)
}

targetHealthStateAttr, err := flattenTargetHealthState(attrResp.Attributes)
if err != nil {
return fmt.Errorf("flattening target health state: %w", err)
}
if err := d.Set("target_health_state", targetHealthStateAttr); err != nil {
return fmt.Errorf("setting target health state: %w", err)
}

// Set target failover attributes for GWLB
targetFailoverAttr := flattenTargetGroupFailover(attrResp.Attributes)
if err != nil {
Expand All @@ -1115,6 +1164,27 @@ func flattenTargetGroupResource(ctx context.Context, d *schema.ResourceData, met
return nil
}

func flattenTargetHealthState(attributes []*elbv2.TargetGroupAttribute) ([]interface{}, error) {
if len(attributes) == 0 {
return []interface{}{}, nil
}

m := make(map[string]interface{})

for _, attr := range attributes {
switch aws.StringValue(attr.Key) {
case "target_health_state.unhealthy.connection_termination.enabled":
enabled, err := strconv.ParseBool(aws.StringValue(attr.Value))
if err != nil {
return nil, fmt.Errorf("converting target_health_state.unhealthy.connection_termination to bool: %s", aws.StringValue(attr.Value))
}
m["enable_unhealthy_connection_termination"] = enabled
}
}

return []interface{}{m}, nil
}

func flattenTargetGroupFailover(attributes []*elbv2.TargetGroupAttribute) []interface{} {
if len(attributes) == 0 {
return []interface{}{}
Expand Down
79 changes: 79 additions & 0 deletions internal/service/elbv2/target_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2342,6 +2342,62 @@ func TestAccELBV2TargetGroup_ALBAlias_updateStickinessEnabled(t *testing.T) {
})
}

func TestAccELBV2TargetGroup_targetHealthStateUnhealthyConnectionTermination(t *testing.T) {
ctx := acctest.Context(t)
var targetGroup elbv2.TargetGroup
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_lb_target_group.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckTargetGroupDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TCP", false),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "protocol", "TCP"),
resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "false"),
),
},
{
Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TCP", true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "protocol", "TCP"),
resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "true"),
),
},
{
Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TLS", false),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "protocol", "TLS"),
resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "false"),
),
},
{
Config: testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, "TLS", true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckTargetGroupExists(ctx, resourceName, &targetGroup),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "protocol", "TLS"),
resource.TestCheckResourceAttr(resourceName, "target_health_state.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_health_state.0.enable_unhealthy_connection_termination", "true"),
),
},
},
})
}

func testAccCheckTargetGroupDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx)
Expand Down Expand Up @@ -3118,6 +3174,29 @@ resource "aws_vpc" "test" {
`, protocol, stickyType, enabled, rName)
}

func testAccTargetGroupConfig_targetHealthStateConnectionTermination(rName, protocol string, enabled bool) string {
return fmt.Sprintf(`
resource "aws_lb_target_group" "test" {
name = %[1]q
port = 25
protocol = %[2]q
vpc_id = aws_vpc.test.id
target_health_state {
enable_unhealthy_connection_termination = %[3]t
}
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
tags = {
Name = %[1]q
}
}
`, rName, protocol, enabled)
}

func testAccTargetGroupConfig_typeTCP(rName string) string {
return fmt.Sprintf(`
resource "aws_lb_target_group" "test" {
Expand Down
22 changes: 22 additions & 0 deletions website/docs/r/lb_target_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ resource "aws_lb_target_group" "alb-example" {
}
```

### Target group with unhealthy connection termination disabled

```terraform
resource "aws_lb_target_group" "tcp-example" {
name = "tf-example-lb-nlb-tg"
port = 25
protocol = "TCP"
vpc_id = aws_vpc.main.id
target_health_state {
enable_unhealthy_connection_termination = false
}
}
```

## Argument Reference

This resource supports the following arguments:
Expand All @@ -87,6 +102,7 @@ This resource supports the following arguments:
* `stickiness` - (Optional, Maximum of 1) Stickiness configuration block. Detailed below.
* `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `target_failover` - (Optional) Target failover block. Only applicable for Gateway Load Balancer target groups. See [target_failover](#target_failover) for more information.
* `target_health_state` - (Optional) Target health state block. Only applicable for Network Load Balancer target groups when `protocol` is `TCP` or `TLS`. See [target_health_state](#target_health_state) for more information.
* `target_type` - (May be required, Forces new resource) Type of target that you must specify when registering targets with this target group. See [doc](https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html) for supported values. The default is `instance`.

Note that you can't specify targets for a target group using both instance IDs and IP addresses.
Expand Down Expand Up @@ -129,6 +145,12 @@ This resource supports the following arguments:
* `on_deregistration` - (Optional) Indicates how the GWLB handles existing flows when a target is deregistered. Possible values are `rebalance` and `no_rebalance`. Must match the attribute value set for `on_unhealthy`. Default: `no_rebalance`.
* `on_unhealthy` - Indicates how the GWLB handles existing flows when a target is unhealthy. Possible values are `rebalance` and `no_rebalance`. Must match the attribute value set for `on_deregistration`. Default: `no_rebalance`.

### target_health_state

~> **NOTE:** This block is only valid for a Network Load Balancer (NLB) target group when `protocol` is `TCP` or `TLS`.

* `enable_unhealthy_connection_termination` - (Optional) Indicates whether the load balancer terminates connections to unhealthy targets. Possible values are `true` or `false`. Default: `true`.

## Attribute Reference

This resource exports the following attributes in addition to the arguments above:
Expand Down

0 comments on commit 7c7ee15

Please sign in to comment.