Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for disabling connection termination for unhealthy targets #34070

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading