Skip to content

Commit

Permalink
Merge pull request #15295 from madpipeline/patch-1
Browse files Browse the repository at this point in the history
Add stickiness support for NLB target_groups
  • Loading branch information
YakDriver authored Oct 9, 2020
2 parents 1c9472b + 6e6c684 commit 66d28e8
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 51 deletions.
57 changes: 21 additions & 36 deletions aws/resource_aws_lb_target_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,22 @@ func resourceAwsLbTargetGroup() *schema.Resource {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"lb_cookie",
"lb_cookie", // Only for ALBs
"source_ip", // Only for NLBs
}, false),
},
"cookie_duration": {
Type: schema.TypeInt,
Optional: true,
Default: 86400,
ValidateFunc: validation.IntBetween(0, 604800),
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
switch d.Get("protocol").(string) {
case elbv2.ProtocolEnumTcp, elbv2.ProtocolEnumUdp, elbv2.ProtocolEnumTcpUdp, elbv2.ProtocolEnumTls:
return true
}
return false
},
},
},
},
Expand Down Expand Up @@ -434,11 +442,7 @@ func resourceAwsLbTargetGroupUpdate(d *schema.ResourceData, meta interface{}) er
})
}

// In CustomizeDiff we allow LB stickiness to be declared for TCP target
// groups, so long as it's not enabled. This allows for better support for
// modules, but also means we need to completely skip sending the data to the
// API if it's defined on a TCP target group.
if d.HasChange("stickiness") && d.Get("protocol") != elbv2.ProtocolEnumTcp {
if d.HasChange("stickiness") {
stickinessBlocks := d.Get("stickiness").([]interface{})
if len(stickinessBlocks) == 1 {
stickiness := stickinessBlocks[0].(map[string]interface{})
Expand All @@ -451,11 +455,16 @@ func resourceAwsLbTargetGroupUpdate(d *schema.ResourceData, meta interface{}) er
&elbv2.TargetGroupAttribute{
Key: aws.String("stickiness.type"),
Value: aws.String(stickiness["type"].(string)),
},
&elbv2.TargetGroupAttribute{
Key: aws.String("stickiness.lb_cookie.duration_seconds"),
Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))),
})

switch d.Get("protocol").(string) {
case elbv2.ProtocolEnumHttp, elbv2.ProtocolEnumHttps:
attrs = append(attrs,
&elbv2.TargetGroupAttribute{
Key: aws.String("stickiness.lb_cookie.duration_seconds"),
Value: aws.String(fmt.Sprintf("%d", stickiness["cookie_duration"].(int))),
})
}
} else if len(stickinessBlocks) == 0 {
attrs = append(attrs, &elbv2.TargetGroupAttribute{
Key: aws.String("stickiness.enabled"),
Expand Down Expand Up @@ -654,23 +663,8 @@ func flattenAwsLbTargetGroupResource(d *schema.ResourceData, meta interface{}, t
}
}

// We only read in the stickiness attributes if the target group is not
// TCP-based. This ensures we don't end up causing a spurious diff if someone
// has defined the stickiness block on a TCP target group (albeit with
// false), for which this update would clobber the state coming from config
// for.
//
// This is a workaround to support module design where the module needs to
// support HTTP and TCP target groups.
switch {
case aws.StringValue(targetGroup.Protocol) != elbv2.ProtocolEnumTcp:
if err = flattenAwsLbTargetGroupStickiness(d, attrResp.Attributes); err != nil {
return err
}
case aws.StringValue(targetGroup.Protocol) == elbv2.ProtocolEnumTcp && len(d.Get("stickiness").([]interface{})) < 1:
if err = d.Set("stickiness", []interface{}{}); err != nil {
return fmt.Errorf("error setting stickiness: %s", err)
}
if err = flattenAwsLbTargetGroupStickiness(d, attrResp.Attributes); err != nil {
return err
}

tags, err := keyvaluetags.Elbv2ListTags(elbconn, d.Id())
Expand Down Expand Up @@ -725,15 +719,6 @@ func flattenAwsLbTargetGroupStickiness(d *schema.ResourceData, attributes []*elb

func resourceAwsLbTargetGroupCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error {
protocol := diff.Get("protocol").(string)
if protocol == elbv2.ProtocolEnumTcp {
// TCP load balancers do not support stickiness
if stickinessBlocks := diff.Get("stickiness").([]interface{}); len(stickinessBlocks) == 1 {
stickiness := stickinessBlocks[0].(map[string]interface{})
if val := stickiness["enabled"].(bool); val {
return fmt.Errorf("Network Load Balancers do not support Stickiness")
}
}
}

// Network Load Balancers have many special qwirks to them.
// See http://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_CreateTargetGroup.html
Expand Down
204 changes: 193 additions & 11 deletions aws/resource_aws_lb_target_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ protocol = "TCP"
})
}

func TestAccAWSLBTargetGroup_stickinessWithTCPDisabled(t *testing.T) {
func TestAccAWSLBTargetGroup_stickinessDefaultNLB(t *testing.T) {
var conf elbv2.TargetGroup
resourceName := "aws_lb_target_group.test"

Expand All @@ -975,11 +975,135 @@ func TestAccAWSLBTargetGroup_stickinessWithTCPDisabled(t *testing.T) {
CheckDestroy: testAccCheckAWSLBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLBTargetGroupConfig_stickinessWithTCP(false),
Config: testAccAWSLBTargetGroupConfig_stickinessDefault("TCP"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"),
),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessDefault("UDP"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"),
),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessDefault("TCP_UDP"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"),
),
},
},
})
}

func TestAccAWSLBTargetGroup_stickinessDefaultALB(t *testing.T) {
var conf elbv2.TargetGroup
resourceName := "aws_lb_target_group.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: resourceName,
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSLBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLBTargetGroupConfig_stickinessDefault("HTTP"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "lb_cookie"),
),
},
},
})
}

func TestAccAWSLBTargetGroup_stickinessValidNLB(t *testing.T) {
var conf elbv2.TargetGroup
resourceName := "aws_lb_target_group.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: resourceName,
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSLBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP", "source_ip", false),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"),
),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP", "source_ip", true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"),
),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("UDP", "source_ip", true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"),
),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP_UDP", "source_ip", true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "source_ip"),
),
},
},
})
}

func TestAccAWSLBTargetGroup_stickinessValidALB(t *testing.T) {
var conf elbv2.TargetGroup
resourceName := "aws_lb_target_group.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: resourceName,
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSLBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("HTTP", "lb_cookie", true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "lb_cookie"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "86400"),
),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("HTTPS", "lb_cookie", true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSLBTargetGroupExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "stickiness.#", "1"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.type", "lb_cookie"),
resource.TestCheckResourceAttr(resourceName, "stickiness.0.cookie_duration", "86400"),
),
Expand All @@ -988,16 +1112,55 @@ func TestAccAWSLBTargetGroup_stickinessWithTCPDisabled(t *testing.T) {
})
}

func TestAccAWSLBTargetGroup_stickinessWithTCPEnabledShouldError(t *testing.T) {
func TestAccAWSLBTargetGroup_stickinessInvalidNLB(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSLBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP", "lb_cookie", true),
ExpectError: regexp.MustCompile("Stickiness type 'lb_cookie' is not supported for target groups with"),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("UDP", "lb_cookie", true),
ExpectError: regexp.MustCompile("Stickiness type 'lb_cookie' is not supported for target groups with"),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP_UDP", "lb_cookie", true),
ExpectError: regexp.MustCompile("Stickiness type 'lb_cookie' is not supported for target groups with"),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP_UDP", "lb_cookie", false),
PlanOnly: true,
ExpectNonEmptyPlan: true,
},
},
})
}

func TestAccAWSLBTargetGroup_stickinessInvalidALB(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSLBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSLBTargetGroupConfig_stickinessWithTCP(true),
PlanOnly: true,
ExpectError: regexp.MustCompile("Network Load Balancers do not support Stickiness"),
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("HTTP", "source_ip", true),
ExpectError: regexp.MustCompile("Stickiness type 'source_ip' is not supported for target groups with"),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("HTTPS", "source_ip", true),
ExpectError: regexp.MustCompile("Stickiness type 'source_ip' is not supported for target groups with"),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TLS", "lb_cookie", true),
ExpectError: regexp.MustCompile("Stickiness type 'lb_cookie' is not supported for target groups with"),
},
{
Config: testAccAWSLBTargetGroupConfig_stickinessValidity("TCP_UDP", "lb_cookie", false),
PlanOnly: true,
ExpectNonEmptyPlan: true,
},
},
})
Expand Down Expand Up @@ -1923,16 +2086,35 @@ resource "aws_vpc" "test" {
}
`

func testAccAWSLBTargetGroupConfig_stickinessWithTCP(enabled bool) string {
func testAccAWSLBTargetGroupConfig_stickinessDefault(protocol string) string {
return fmt.Sprintf(`
resource "aws_lb_target_group" "test" {
name_prefix = "tf-"
port = 25
protocol = %q
vpc_id = aws_vpc.test.id
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "testAccAWSLBTargetGroupConfig_stickinessDefault"
}
}
`, protocol)
}

func testAccAWSLBTargetGroupConfig_stickinessValidity(protocol, stickyType string, enabled bool) string {
return fmt.Sprintf(`
resource "aws_lb_target_group" "test" {
name_prefix = "tf-"
port = 25
protocol = "TCP"
protocol = %q
vpc_id = aws_vpc.test.id
stickiness {
type = "lb_cookie"
type = %q
enabled = %t
}
}
Expand All @@ -1941,8 +2123,8 @@ resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "testAccAWSLBTargetGroupConfig_namePrefix"
Name = "testAccAWSLBTargetGroupConfig_stickinessValidity"
}
}
`, enabled)
`, protocol, stickyType, enabled)
}
8 changes: 4 additions & 4 deletions website/docs/r/lb_target_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ The following arguments are supported:
* `load_balancing_algorithm_type` - (Optional) Determines how the load balancer selects targets when routing requests. Only applicable for Application Load Balancer Target Groups. The value is `round_robin` or `least_outstanding_requests`. The default is `round_robin`.
* `lambda_multi_value_headers_enabled` - (Optional) Boolean whether the request and response headers exchanged between the load balancer and the Lambda function include arrays of values or strings. Only applies when `target_type` is `lambda`.
* `proxy_protocol_v2` - (Optional) Boolean to enable / disable support for proxy protocol v2 on Network Load Balancers. See [doc](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#proxy-protocol) for more information.
* `stickiness` - (Optional) A Stickiness block. Stickiness blocks are documented below. `stickiness` is only valid if used with Load Balancers of type `Application`
* `health_check` - (Optional) A Health Check block. Health Check blocks are documented below.
* `stickiness` - (Optional, Maximum of 1) A Stickiness block. Stickiness blocks are documented below.
* `health_check` - (Optional, Maximum of 1) A Health Check block. Health Check blocks are documented below.
* `target_type` - (Optional, Forces new resource) The type of target that you must specify when registering targets with this target group.
The possible values are `instance` (targets are specified by instance ID) or `ip` (targets are specified by IP address) or `lambda` (targets are specified by lambda arn).
The default is `instance`. Note that you can't specify targets for a target group using both instance IDs and IP addresses.
Expand All @@ -81,8 +81,8 @@ You can't specify publicly routable IP addresses.

Stickiness Blocks (`stickiness`) support the following:

* `type` - (Required) The type of sticky sessions. The only current possible value is `lb_cookie`.
* `cookie_duration` - (Optional) The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds).
* `type` - (Required) The type of sticky sessions. The only current possible values are `lb_cookie` for ALBs and `source_ip` for NLBs.
* `cookie_duration` - (Optional) Only used when the type is `lb_cookie`. The time period, in seconds, during which requests from a client should be routed to the same target. After this time period expires, the load balancer-generated cookie is considered stale. The range is 1 second to 1 week (604800 seconds). The default value is 1 day (86400 seconds).
* `enabled` - (Optional) Boolean to enable / disable `stickiness`. Default is `true`

~> **NOTE:** To help facilitate the authoring of modules that support target groups of any protocol, you can define `stickiness` regardless of the protocol chosen. However, for `TCP` target groups, `enabled` must be `false`.
Expand Down

0 comments on commit 66d28e8

Please sign in to comment.