provider/aws: aws_autoscaling_group ALB target group support (#10243)

This update adds ALB support to the
wait_for_elb_capacity/min_elb_capacity options. Target groups are
handled in nearly the same way as Classic ELBs, so the change should be
transparent. This supports both ALB target groups and classic ELBs being
attached to the ASG at the same time.
This commit is contained in:
Chris Marchesi 2016-11-21 01:50:58 -08:00 committed by Paul Stack
parent fa892adf72
commit 67d162a126
3 changed files with 229 additions and 5 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/elbv2"
)
func resourceAwsAutoscalingGroup() *schema.Resource {
@ -800,11 +801,13 @@ func updateASGMetricsCollection(d *schema.ResourceData, conn *autoscaling.AutoSc
return nil
}
// Returns a mapping of the instance states of all the ELBs attached to the
// getELBInstanceStates returns a mapping of the instance states of all the ELBs attached to the
// provided ASG.
//
// Note that this is the instance state function for ELB Classic.
//
// Nested like: lbName -> instanceId -> instanceState
func getLBInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map[string]string, error) {
func getELBInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map[string]string, error) {
lbInstanceStates := make(map[string]map[string]string)
elbconn := meta.(*AWSClient).elbconn
@ -826,6 +829,35 @@ func getLBInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map
return lbInstanceStates, nil
}
// getTargetGroupInstanceStates returns a mapping of the instance states of
// all the ALB target groups attached to the provided ASG.
//
// Note that this is the instance state function for Application Load
// Balancing (aka ELBv2).
//
// Nested like: targetGroupARN -> instanceId -> instanceState
func getTargetGroupInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map[string]string, error) {
targetInstanceStates := make(map[string]map[string]string)
elbv2conn := meta.(*AWSClient).elbv2conn
for _, targetGroupARN := range g.TargetGroupARNs {
targetInstanceStates[*targetGroupARN] = make(map[string]string)
opts := &elbv2.DescribeTargetHealthInput{TargetGroupArn: targetGroupARN}
r, err := elbv2conn.DescribeTargetHealth(opts)
if err != nil {
return nil, err
}
for _, desc := range r.TargetHealthDescriptions {
if desc.Target == nil || desc.Target.Id == nil || desc.TargetHealth == nil || desc.TargetHealth.State == nil {
continue
}
targetInstanceStates[*targetGroupARN][*desc.Target.Id] = *desc.TargetHealth.State
}
}
return targetInstanceStates, nil
}
func expandVpcZoneIdentifiers(list []interface{}) *string {
strs := make([]string, len(list))
for _, s := range list {

View File

@ -1,6 +1,7 @@
package aws
import (
"errors"
"fmt"
"reflect"
"regexp"
@ -430,6 +431,27 @@ func TestAccAWSAutoScalingGroup_initialLifecycleHook(t *testing.T) {
})
}
func TestAccAWSAutoScalingGroup_ALB_TargetGroups_ELBCapacity(t *testing.T) {
var group autoscaling.Group
var tg elbv2.TargetGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAutoScalingGroupConfig_ALB_TargetGroup_ELBCapacity,
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
testAccCheckAWSALBTargetGroupExists("aws_alb_target_group.test", &tg),
testAccCheckAWSALBTargetGroupHealthy(&tg),
),
},
},
})
}
func testAccCheckAWSAutoScalingGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
@ -648,6 +670,30 @@ func testAccCheckAWSAutoScalingGroupAttributesVPCZoneIdentifer(group *autoscalin
}
}
// testAccCheckAWSALBTargetGroupHealthy checks an *elbv2.TargetGroup to make
// sure that all instances in it are healthy.
func testAccCheckAWSALBTargetGroupHealthy(res *elbv2.TargetGroup) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).elbv2conn
resp, err := conn.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{
TargetGroupArn: res.TargetGroupArn,
})
if err != nil {
return err
}
for _, target := range resp.TargetHealthDescriptions {
if target.TargetHealth == nil || target.TargetHealth.State == nil || *target.TargetHealth.State != "healthy" {
return errors.New("Not all instances in target group are healthy yet, but should be")
}
}
return nil
}
}
const testAccAWSAutoScalingGroupConfig_autoGeneratedName = `
resource "aws_launch_configuration" "foobar" {
image_id = "ami-21f78e11"
@ -1302,3 +1348,142 @@ resource "aws_autoscaling_group" "bar" {
}
`, name)
}
const testAccAWSAutoScalingGroupConfig_ALB_TargetGroup_ELBCapacity = `
provider "aws" {
region = "us-west-2"
}
resource "aws_vpc" "default" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = "true"
enable_dns_support = "true"
tags {
Name = "testAccAWSAutoScalingGroupConfig_ALB_TargetGroup_ELBCapacity"
}
}
resource "aws_alb" "test_lb" {
subnets = ["${aws_subnet.main.id}", "${aws_subnet.alt.id}"]
tags {
Name = "testAccAWSAutoScalingGroupConfig_ALB_TargetGroup_ELBCapacity"
}
}
resource "aws_alb_listener" "test_listener" {
load_balancer_arn = "${aws_alb.test_lb.arn}"
port = "80"
default_action {
target_group_arn = "${aws_alb_target_group.test.arn}"
type = "forward"
}
}
resource "aws_alb_target_group" "test" {
name = "tf-example-alb-tg"
port = 80
protocol = "HTTP"
vpc_id = "${aws_vpc.default.id}"
health_check {
path = "/"
healthy_threshold = "2"
timeout = "2"
interval = "5"
}
}
resource "aws_subnet" "main" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
tags {
Name = "testAccAWSAutoScalingGroupConfig_ALB_TargetGroup_ELBCapacity"
}
}
resource "aws_subnet" "alt" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "10.0.2.0/24"
availability_zone = "us-west-2b"
tags {
Name = "testAccAWSAutoScalingGroupConfig_ALB_TargetGroup_ELBCapacity"
}
}
resource "aws_internet_gateway" "internet_gateway" {
vpc_id = "${aws_vpc.default.id}"
}
resource "aws_route_table" "route_table" {
vpc_id = "${aws_vpc.default.id}"
}
resource "aws_route_table_association" "route_table_association_main" {
subnet_id = "${aws_subnet.main.id}"
route_table_id = "${aws_route_table.route_table.id}"
}
resource "aws_route_table_association" "route_table_association_alt" {
subnet_id = "${aws_subnet.alt.id}"
route_table_id = "${aws_route_table.route_table.id}"
}
resource "aws_route" "public_default_route" {
route_table_id = "${aws_route_table.route_table.id}"
destination_cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.internet_gateway.id}"
}
data "aws_ami" "test_ami" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_launch_configuration" "foobar" {
image_id = "${data.aws_ami.test_ami.id}"
instance_type = "t2.micro"
associate_public_ip_address = "true"
user_data = <<EOS
#!/bin/bash
yum -y install httpd
echo "hello world" > /var/www/html/index.html
chkconfig httpd on
service httpd start
EOS
}
resource "aws_autoscaling_group" "bar" {
vpc_zone_identifier = [
"${aws_subnet.main.id}",
"${aws_subnet.alt.id}",
]
target_group_arns = ["${aws_alb_target_group.test.arn}"]
max_size = 2
min_size = 2
health_check_grace_period = 300
health_check_type = "ELB"
desired_capacity = 2
wait_for_elb_capacity = 2
force_delete = true
termination_policies = ["OldestInstance"]
launch_configuration = "${aws_launch_configuration.foobar.name}"
}
`

View File

@ -42,7 +42,8 @@ func waitForASGCapacity(
d.SetId("")
return nil
}
lbis, err := getLBInstanceStates(g, meta)
elbis, err := getELBInstanceStates(g, meta)
albis, err := getTargetGroupInstanceStates(g, meta)
if err != nil {
return resource.NonRetryableError(err)
}
@ -66,12 +67,18 @@ func waitForASGCapacity(
haveASG++
inAllLbs := true
for _, states := range lbis {
for _, states := range elbis {
state, ok := states[*i.InstanceId]
if !ok || !strings.EqualFold(state, "InService") {
inAllLbs = false
}
}
for _, states := range albis {
state, ok := states[*i.InstanceId]
if !ok || !strings.EqualFold(state, "healthy") {
inAllLbs = false
}
}
if inAllLbs {
haveELB++
}
@ -79,7 +86,7 @@ func waitForASGCapacity(
satisfied, reason := satisfiedFunc(d, haveASG, haveELB)
log.Printf("[DEBUG] %q Capacity: %d ASG, %d ELB, satisfied: %t, reason: %q",
log.Printf("[DEBUG] %q Capacity: %d ASG, %d ELB/ALB, satisfied: %t, reason: %q",
d.Id(), haveASG, haveELB, satisfied, reason)
if satisfied {