From 4e0b0e0f195971e6ffc9e37585bf87a176801da8 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 18 Oct 2017 08:17:53 +0100 Subject: [PATCH 1/2] r/aws_lb: Allow assigning EIP to network LB --- aws/resource_aws_lb.go | 4 +- aws/resource_aws_lb_test.go | 79 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/aws/resource_aws_lb.go b/aws/resource_aws_lb.go index 8442a748aab1..6e5b66ded3c5 100644 --- a/aws/resource_aws_lb.go +++ b/aws/resource_aws_lb.go @@ -223,9 +223,7 @@ func resourceAwsLbCreate(d *schema.ResourceData, meta interface{}) error { } if subnetMap["allocation_id"].(string) != "" { - elbOpts.SubnetMappings[i] = &elbv2.SubnetMapping{ - AllocationId: aws.String(subnetMap["allocation_id"].(string)), - } + elbOpts.SubnetMappings[i].AllocationId = aws.String(subnetMap["allocation_id"].(string)) } } } diff --git a/aws/resource_aws_lb_test.go b/aws/resource_aws_lb_test.go index c568449b1518..56333af09c19 100644 --- a/aws/resource_aws_lb_test.go +++ b/aws/resource_aws_lb_test.go @@ -109,6 +109,33 @@ func TestAccAWSLB_networkLoadbalancer(t *testing.T) { }) } +func TestAccAWSLB_networkLoadbalancerEIP(t *testing.T) { + var conf elbv2.LoadBalancer + lbName := fmt.Sprintf("testaccawslb-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLBDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLBConfig_networkLoadBalancerEIP(lbName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSLBExists("aws_lb.test", &conf), + resource.TestCheckResourceAttr("aws_lb.test", "name", lbName), + resource.TestCheckResourceAttr("aws_lb.test", "internal", "false"), + resource.TestCheckResourceAttr("aws_lb.test", "ip_address_type", "ipv4"), + resource.TestCheckResourceAttrSet("aws_lb.test", "zone_id"), + resource.TestCheckResourceAttrSet("aws_lb.test", "dns_name"), + resource.TestCheckResourceAttrSet("aws_lb.test", "arn"), + resource.TestCheckResourceAttr("aws_lb.test", "load_balancer_type", "network"), + resource.TestCheckResourceAttr("aws_lb.test", "subnet_mapping.#", "2"), + ), + }, + }, + }) +} + func TestAccAWSLBBackwardsCompatibility(t *testing.T) { var conf elbv2.LoadBalancer lbName := fmt.Sprintf("testaccawslb-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) @@ -830,6 +857,58 @@ resource "aws_subnet" "alb_test" { `, lbName) } +func testAccAWSLBConfig_networkLoadBalancerEIP(lbName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" {} + +resource "aws_vpc" "main" { + cidr_block = "10.10.0.0/16" +} + +resource "aws_subnet" "public" { + count = "${length(data.aws_availability_zones.available.names)}" + availability_zone = "${data.aws_availability_zones.available.names[count.index]}" + cidr_block = "10.10.${count.index}.0/24" + vpc_id = "${aws_vpc.main.id}" +} + +resource "aws_internet_gateway" "default" { + vpc_id = "${aws_vpc.main.id}" +} + +resource "aws_route_table" "public" { + vpc_id = "${aws_vpc.main.id}" + route { + cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.default.id}" + } +} + +resource "aws_route_table_association" "a" { + count = "${length(data.aws_availability_zones.available.names)}" + subnet_id = "${aws_subnet.public.*.id[count.index]}" + route_table_id = "${aws_route_table.public.id}" +} + +resource "aws_lb" "test" { + name = "%s" + load_balancer_type = "network" + subnet_mapping { + subnet_id = "${aws_subnet.public.0.id}" + allocation_id = "${aws_eip.lb.0.id}" + } + subnet_mapping { + subnet_id = "${aws_subnet.public.1.id}" + allocation_id = "${aws_eip.lb.1.id}" + } +} + +resource "aws_eip" "lb" { + count = "${length(data.aws_availability_zones.available.names)}" +} +`, lbName) +} + func testAccAWSLBConfigBackwardsCompatibility(lbName string) string { return fmt.Sprintf(`resource "aws_alb" "lb_test" { name = "%s" From f9fbfa22178da6978046f5671b192bd17cbe5053 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 18 Oct 2017 21:53:25 +0300 Subject: [PATCH 2/2] r/aws_lb: Wait until NLB ENIs are gone --- aws/resource_aws_lb.go | 74 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_lb.go b/aws/resource_aws_lb.go index 6e5b66ded3c5..75f56b6b998a 100644 --- a/aws/resource_aws_lb.go +++ b/aws/resource_aws_lb.go @@ -460,11 +460,18 @@ func resourceAwsLbDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error deleting ALB: %s", err) } - err := cleanupLBNetworkInterfaces(meta.(*AWSClient).ec2conn, d.Id()) + conn := meta.(*AWSClient).ec2conn + + err := cleanupLBNetworkInterfaces(conn, d.Id()) if err != nil { log.Printf("[WARN] Failed to cleanup ENIs for ALB %q: %#v", d.Id(), err) } + err = waitForNLBNetworkInterfacesToDetach(conn, d.Id()) + if err != nil { + log.Printf("[WARN] Failed to wait for ENIs to disappear for NLB %q: %#v", d.Id(), err) + } + return nil } @@ -473,15 +480,11 @@ func resourceAwsLbDelete(d *schema.ResourceData, meta interface{}) error { // which then blocks IGW, SG or VPC on deletion // So we make the cleanup "synchronous" here func cleanupLBNetworkInterfaces(conn *ec2.EC2, lbArn string) error { - re := regexp.MustCompile("([^/]+/[^/]+/[^/]+)$") - matches := re.FindStringSubmatch(lbArn) - if len(matches) != 2 { - return fmt.Errorf("Unexpected ARN format: %q", lbArn) + name, err := getLbNameFromArn(lbArn) + if err != nil { + return err } - // e.g. app/example-alb/b26e625cdde161e6 - name := matches[1] - out, err := conn.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ Filters: []*ec2.Filter{ { @@ -498,7 +501,7 @@ func cleanupLBNetworkInterfaces(conn *ec2.EC2, lbArn string) error { return err } - log.Printf("[DEBUG] Found %d ENIs to cleanup for ALB %q", + log.Printf("[DEBUG] Found %d ENIs to cleanup for LB %q", len(out.NetworkInterfaces), name) if len(out.NetworkInterfaces) == 0 { @@ -519,6 +522,59 @@ func cleanupLBNetworkInterfaces(conn *ec2.EC2, lbArn string) error { return nil } +func waitForNLBNetworkInterfacesToDetach(conn *ec2.EC2, lbArn string) error { + name, err := getLbNameFromArn(lbArn) + if err != nil { + return err + } + + // We cannot cleanup these ENIs ourselves as that would result in + // OperationNotPermitted: You are not allowed to manage 'ela-attach' attachments. + // yet presence of these ENIs may prevent us from deleting EIPs associated w/ the NLB + + return resource.Retry(1*time.Minute, func() *resource.RetryError { + out, err := conn.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("attachment.instance-owner-id"), + Values: []*string{aws.String("amazon-aws")}, + }, + { + Name: aws.String("attachment.attachment-id"), + Values: []*string{aws.String("ela-attach-*")}, + }, + { + Name: aws.String("description"), + Values: []*string{aws.String("ELB " + name)}, + }, + }, + }) + if err != nil { + return resource.NonRetryableError(err) + } + + niCount := len(out.NetworkInterfaces) + if niCount > 0 { + log.Printf("[DEBUG] Found %d ENIs to cleanup for NLB %q", niCount, lbArn) + return resource.RetryableError(fmt.Errorf("Waiting for %d ENIs of %q to clean up", niCount, lbArn)) + } + log.Printf("[DEBUG] ENIs gone for NLB %q", lbArn) + + return nil + }) +} + +func getLbNameFromArn(arn string) (string, error) { + re := regexp.MustCompile("([^/]+/[^/]+/[^/]+)$") + matches := re.FindStringSubmatch(arn) + if len(matches) != 2 { + return "", fmt.Errorf("Unexpected ARN format: %q", arn) + } + + // e.g. app/example-alb/b26e625cdde161e6 + return matches[1], nil +} + // flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs // for the ALB based on the AvailabilityZones structure returned by the API. func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string {