package aws import ( "fmt" "math/rand" "reflect" "regexp" "sort" "testing" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) func TestAccAWSELB_basic(t *testing.T) { var conf elb.LoadBalancerDescription resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), testAccCheckAWSELBAttributes(&conf), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.#", "3"), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.2487133097", "us-west-2a"), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.221770259", "us-west-2b"), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.2050015877", "us-west-2c"), resource.TestCheckResourceAttr( "aws_elb.bar", "subnets.#", "3"), // NOTE: Subnet IDs are different across AWS accounts and cannot be checked. resource.TestCheckResourceAttr( "aws_elb.bar", "listener.206423021.instance_port", "8000"), resource.TestCheckResourceAttr( "aws_elb.bar", "listener.206423021.instance_protocol", "http"), resource.TestCheckResourceAttr( "aws_elb.bar", "listener.206423021.lb_port", "80"), resource.TestCheckResourceAttr( "aws_elb.bar", "listener.206423021.lb_protocol", "http"), resource.TestCheckResourceAttr( "aws_elb.bar", "cross_zone_load_balancing", "true"), ), }, }, }) } func TestAccAWSELB_fullCharacterRange(t *testing.T) { var conf elb.LoadBalancerDescription lbName := fmt.Sprintf("Tf-%d", rand.New(rand.NewSource(time.Now().UnixNano())).Int()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: fmt.Sprintf(testAccAWSELBFullRangeOfCharacters, lbName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.foo", &conf), resource.TestCheckResourceAttr( "aws_elb.foo", "name", lbName), ), }, }, }) } func TestAccAWSELB_AccessLogs(t *testing.T) { var conf elb.LoadBalancerDescription resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBAccessLogs, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.foo", &conf), ), }, resource.TestStep{ Config: testAccAWSELBAccessLogsOn, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.foo", &conf), resource.TestCheckResourceAttr( "aws_elb.foo", "access_logs.#", "1"), resource.TestCheckResourceAttr( "aws_elb.foo", "access_logs.1713209538.bucket", "terraform-access-logs-bucket"), resource.TestCheckResourceAttr( "aws_elb.foo", "access_logs.1713209538.interval", "5"), ), }, resource.TestStep{ Config: testAccAWSELBAccessLogs, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.foo", &conf), resource.TestCheckResourceAttr( "aws_elb.foo", "access_logs.#", "0"), ), }, }, }) } func TestAccAWSELB_generatedName(t *testing.T) { var conf elb.LoadBalancerDescription generatedNameRegexp := regexp.MustCompile("^tf-lb-") resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBGeneratedName, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.foo", &conf), resource.TestMatchResourceAttr( "aws_elb.foo", "name", generatedNameRegexp), ), }, }, }) } func TestAccAWSELB_availabilityZones(t *testing.T) { var conf elb.LoadBalancerDescription resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.#", "3"), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.2487133097", "us-west-2a"), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.221770259", "us-west-2b"), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.2050015877", "us-west-2c"), ), }, resource.TestStep{ Config: testAccAWSELBConfig_AvailabilityZonesUpdate, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.#", "2"), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.2487133097", "us-west-2a"), resource.TestCheckResourceAttr( "aws_elb.bar", "availability_zones.221770259", "us-west-2b"), ), }, }, }) } func TestAccAWSELB_tags(t *testing.T) { var conf elb.LoadBalancerDescription var td elb.TagDescription resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), testAccCheckAWSELBAttributes(&conf), testAccLoadTags(&conf, &td), testAccCheckELBTags(&td.Tags, "bar", "baz"), ), }, resource.TestStep{ Config: testAccAWSELBConfig_TagUpdate, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), testAccCheckAWSELBAttributes(&conf), testAccLoadTags(&conf, &td), testAccCheckELBTags(&td.Tags, "foo", "bar"), testAccCheckELBTags(&td.Tags, "new", "type"), ), }, }, }) } func TestAccAWSELB_iam_server_cert(t *testing.T) { var conf elb.LoadBalancerDescription // var td elb.TagDescription testCheck := func(*terraform.State) error { if len(conf.ListenerDescriptions) != 1 { return fmt.Errorf( "TestAccAWSELB_iam_server_cert expected 1 listener, got %d", len(conf.ListenerDescriptions)) } return nil } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccELBIAMServerCertConfig( fmt.Sprintf("tf-acctest-%s", acctest.RandString(10))), Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), testCheck, ), }, }, }) } func testAccLoadTags(conf *elb.LoadBalancerDescription, td *elb.TagDescription) resource.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).elbconn describe, err := conn.DescribeTags(&elb.DescribeTagsInput{ LoadBalancerNames: []*string{conf.LoadBalancerName}, }) if err != nil { return err } if len(describe.TagDescriptions) > 0 { *td = *describe.TagDescriptions[0] } return nil } } func TestAccAWSELB_InstanceAttaching(t *testing.T) { var conf elb.LoadBalancerDescription testCheckInstanceAttached := func(count int) resource.TestCheckFunc { return func(*terraform.State) error { if len(conf.Instances) != count { return fmt.Errorf("instance count does not match") } return nil } } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), testAccCheckAWSELBAttributes(&conf), ), }, resource.TestStep{ Config: testAccAWSELBConfigNewInstance, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), testCheckInstanceAttached(1), ), }, }, }) } func TestAccAWSELBUpdate_Listener(t *testing.T) { var conf elb.LoadBalancerDescription resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), testAccCheckAWSELBAttributes(&conf), resource.TestCheckResourceAttr( "aws_elb.bar", "listener.206423021.instance_port", "8000"), ), }, resource.TestStep{ Config: testAccAWSELBConfigListener_update, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), resource.TestCheckResourceAttr( "aws_elb.bar", "listener.3931999347.instance_port", "8080"), ), }, }, }) } func TestAccAWSELB_HealthCheck(t *testing.T) { var conf elb.LoadBalancerDescription resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfigHealthCheck, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), testAccCheckAWSELBAttributesHealthCheck(&conf), resource.TestCheckResourceAttr( "aws_elb.bar", "health_check.0.healthy_threshold", "5"), resource.TestCheckResourceAttr( "aws_elb.bar", "health_check.0.unhealthy_threshold", "5"), resource.TestCheckResourceAttr( "aws_elb.bar", "health_check.0.target", "HTTP:8000/"), resource.TestCheckResourceAttr( "aws_elb.bar", "health_check.0.timeout", "30"), resource.TestCheckResourceAttr( "aws_elb.bar", "health_check.0.interval", "60"), ), }, }, }) } func TestAccAWSELBUpdate_HealthCheck(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfigHealthCheck, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_elb.bar", "health_check.0.healthy_threshold", "5"), ), }, resource.TestStep{ Config: testAccAWSELBConfigHealthCheck_update, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_elb.bar", "health_check.0.healthy_threshold", "10"), ), }, }, }) } func TestAccAWSELB_Timeout(t *testing.T) { var conf elb.LoadBalancerDescription resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfigIdleTimeout, Check: resource.ComposeTestCheckFunc( testAccCheckAWSELBExists("aws_elb.bar", &conf), resource.TestCheckResourceAttr( "aws_elb.bar", "idle_timeout", "200", ), ), }, }, }) } func TestAccAWSELBUpdate_Timeout(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfigIdleTimeout, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_elb.bar", "idle_timeout", "200", ), ), }, resource.TestStep{ Config: testAccAWSELBConfigIdleTimeout_update, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_elb.bar", "idle_timeout", "400", ), ), }, }, }) } func TestAccAWSELB_ConnectionDraining(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfigConnectionDraining, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_elb.bar", "connection_draining", "true", ), resource.TestCheckResourceAttr( "aws_elb.bar", "connection_draining_timeout", "400", ), ), }, }, }) } func TestAccAWSELBUpdate_ConnectionDraining(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfigConnectionDraining, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_elb.bar", "connection_draining", "true", ), resource.TestCheckResourceAttr( "aws_elb.bar", "connection_draining_timeout", "400", ), ), }, resource.TestStep{ Config: testAccAWSELBConfigConnectionDraining_update_timeout, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_elb.bar", "connection_draining", "true", ), resource.TestCheckResourceAttr( "aws_elb.bar", "connection_draining_timeout", "600", ), ), }, resource.TestStep{ Config: testAccAWSELBConfigConnectionDraining_update_disable, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_elb.bar", "connection_draining", "false", ), ), }, }, }) } func TestAccAWSELB_SecurityGroups(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSELBDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAWSELBConfig, Check: resource.ComposeTestCheckFunc( // ELBs get a default security group resource.TestCheckResourceAttr( "aws_elb.bar", "security_groups.#", "1", ), ), }, resource.TestStep{ Config: testAccAWSELBConfigSecurityGroups, Check: resource.ComposeTestCheckFunc( // Count should still be one as we swap in a custom security group resource.TestCheckResourceAttr( "aws_elb.bar", "security_groups.#", "1", ), ), }, }, }) } // Unit test for listeners hash func TestResourceAwsElbListenerHash(t *testing.T) { cases := map[string]struct { Left map[string]interface{} Right map[string]interface{} Match bool }{ "protocols are case insensitive": { map[string]interface{}{ "instance_port": 80, "instance_protocol": "TCP", "lb_port": 80, "lb_protocol": "TCP", }, map[string]interface{}{ "instance_port": 80, "instance_protocol": "Tcp", "lb_port": 80, "lb_protocol": "tcP", }, true, }, } for tn, tc := range cases { leftHash := resourceAwsElbListenerHash(tc.Left) rightHash := resourceAwsElbListenerHash(tc.Right) if leftHash == rightHash != tc.Match { t.Fatalf("%s: expected match: %t, but did not get it", tn, tc.Match) } } } func TestResourceAWSELB_validateElbNameCannotBeginWithHyphen(t *testing.T) { var elbName = "-Testing123" _, errors := validateElbName(elbName, "SampleKey") if len(errors) != 1 { t.Fatalf("Expected the ELB Name to trigger a validation error") } } func TestResourceAWSELB_validateElbNameCannotBeLongerThen32Characters(t *testing.T) { var elbName = "Testing123dddddddddddddddddddvvvv" _, errors := validateElbName(elbName, "SampleKey") if len(errors) != 1 { t.Fatalf("Expected the ELB Name to trigger a validation error") } } func TestResourceAWSELB_validateElbNameCannotHaveSpecialCharacters(t *testing.T) { var elbName = "Testing123%%" _, errors := validateElbName(elbName, "SampleKey") if len(errors) != 1 { t.Fatalf("Expected the ELB Name to trigger a validation error") } } func TestResourceAWSELB_validateElbNameCannotEndWithHyphen(t *testing.T) { var elbName = "Testing123-" _, errors := validateElbName(elbName, "SampleKey") if len(errors) != 1 { t.Fatalf("Expected the ELB Name to trigger a validation error") } } func testAccCheckAWSELBDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).elbconn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_elb" { continue } describe, err := conn.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{ LoadBalancerNames: []*string{aws.String(rs.Primary.ID)}, }) if err == nil { if len(describe.LoadBalancerDescriptions) != 0 && *describe.LoadBalancerDescriptions[0].LoadBalancerName == rs.Primary.ID { return fmt.Errorf("ELB still exists") } } // Verify the error providerErr, ok := err.(awserr.Error) if !ok { return err } if providerErr.Code() != "LoadBalancerNotFound" { return fmt.Errorf("Unexpected error: %s", err) } } return nil } func testAccCheckAWSELBAttributes(conf *elb.LoadBalancerDescription) resource.TestCheckFunc { return func(s *terraform.State) error { zones := []string{"us-west-2a", "us-west-2b", "us-west-2c"} azs := make([]string, 0, len(conf.AvailabilityZones)) for _, x := range conf.AvailabilityZones { azs = append(azs, *x) } sort.StringSlice(azs).Sort() if !reflect.DeepEqual(azs, zones) { return fmt.Errorf("bad availability_zones") } l := elb.Listener{ InstancePort: aws.Int64(int64(8000)), InstanceProtocol: aws.String("HTTP"), LoadBalancerPort: aws.Int64(int64(80)), Protocol: aws.String("HTTP"), } if !reflect.DeepEqual(conf.ListenerDescriptions[0].Listener, &l) { return fmt.Errorf( "Got:\n\n%#v\n\nExpected:\n\n%#v\n", conf.ListenerDescriptions[0].Listener, l) } if *conf.DNSName == "" { return fmt.Errorf("empty dns_name") } return nil } } func testAccCheckAWSELBAttributesHealthCheck(conf *elb.LoadBalancerDescription) resource.TestCheckFunc { return func(s *terraform.State) error { zones := []string{"us-west-2a", "us-west-2b", "us-west-2c"} azs := make([]string, 0, len(conf.AvailabilityZones)) for _, x := range conf.AvailabilityZones { azs = append(azs, *x) } sort.StringSlice(azs).Sort() if !reflect.DeepEqual(azs, zones) { return fmt.Errorf("bad availability_zones") } check := &elb.HealthCheck{ Timeout: aws.Int64(int64(30)), UnhealthyThreshold: aws.Int64(int64(5)), HealthyThreshold: aws.Int64(int64(5)), Interval: aws.Int64(int64(60)), Target: aws.String("HTTP:8000/"), } if !reflect.DeepEqual(conf.HealthCheck, check) { return fmt.Errorf( "Got:\n\n%#v\n\nExpected:\n\n%#v\n", conf.HealthCheck, check) } if *conf.DNSName == "" { return fmt.Errorf("empty dns_name") } return nil } } func testAccCheckAWSELBExists(n string, res *elb.LoadBalancerDescription) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { return fmt.Errorf("No ELB ID is set") } conn := testAccProvider.Meta().(*AWSClient).elbconn describe, err := conn.DescribeLoadBalancers(&elb.DescribeLoadBalancersInput{ LoadBalancerNames: []*string{aws.String(rs.Primary.ID)}, }) if err != nil { return err } if len(describe.LoadBalancerDescriptions) != 1 || *describe.LoadBalancerDescriptions[0].LoadBalancerName != rs.Primary.ID { return fmt.Errorf("ELB not found") } *res = *describe.LoadBalancerDescriptions[0] // Confirm source_security_group_id for ELBs in a VPC // See https://github.com/hashicorp/terraform/pull/3780 if res.VPCId != nil { sgid := rs.Primary.Attributes["source_security_group_id"] if sgid == "" { return fmt.Errorf("Expected to find source_security_group_id for ELB, but was empty") } } return nil } } const testAccAWSELBConfig = ` resource "aws_elb" "bar" { availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] listener { instance_port = 8000 instance_protocol = "http" lb_port = 80 // Protocol should be case insensitive lb_protocol = "HttP" } tags { bar = "baz" } cross_zone_load_balancing = true } ` const testAccAWSELBFullRangeOfCharacters = ` resource "aws_elb" "foo" { name = "%s" availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] listener { instance_port = 8000 instance_protocol = "http" lb_port = 80 lb_protocol = "http" } } ` const testAccAWSELBAccessLogs = ` resource "aws_elb" "foo" { availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] listener { instance_port = 8000 instance_protocol = "http" lb_port = 80 lb_protocol = "http" } } ` const testAccAWSELBAccessLogsOn = ` # an S3 bucket configured for Access logs # The 797873946194 is the AWS ID for us-west-2, so this test # must be ran in us-west-2 resource "aws_s3_bucket" "acceslogs_bucket" { bucket = "terraform-access-logs-bucket" acl = "private" force_destroy = true policy = <