provider/aws: wait for ASG capacity on creation

On ASG creation, waits for up to 10m for desired_capacity or min_size
healthy nodes to show up in the group before continuing.

With CBD and proper HealthCheck tuning, this allows us guarantee safe
ASG replacement.
This commit is contained in:
Paul Hinze 2015-05-06 18:34:20 -05:00
parent e7ca6cbe9e
commit 063454e9b8
3 changed files with 80 additions and 5 deletions

View File

@ -174,6 +174,10 @@ func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{})
d.SetId(d.Get("name").(string)) d.SetId(d.Get("name").(string))
log.Printf("[INFO] AutoScaling Group ID: %s", d.Id()) log.Printf("[INFO] AutoScaling Group ID: %s", d.Id())
if err := waitForASGCapacity(d, meta); err != nil {
return err
}
return resourceAwsAutoscalingGroupRead(d, meta) return resourceAwsAutoscalingGroupRead(d, meta)
} }
@ -359,3 +363,45 @@ func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{})
return fmt.Errorf("group still has %d instances", len(g.Instances)) return fmt.Errorf("group still has %d instances", len(g.Instances))
}) })
} }
var waitForASGCapacityTimeout = 10 * time.Minute
// Waits for a minimum number of healthy instances to show up as healthy in the
// ASG before continuing. Waits up to `waitForASGCapacityTimeout` for
// "desired_capacity", or "min_size" if desired capacity is not specified.
func waitForASGCapacity(d *schema.ResourceData, meta interface{}) error {
waitFor := d.Get("min_size").(int)
if v := d.Get("desired_capacity").(int); v > 0 {
waitFor = v
}
log.Printf("[DEBUG] Waiting for group to have %d healthy instances", waitFor)
return resource.Retry(waitForASGCapacityTimeout, func() error {
g, err := getAwsAutoscalingGroup(d, meta)
if err != nil {
return resource.RetryError{Err: err}
}
if g == nil {
return nil
}
healthy := 0
for _, i := range g.Instances {
if i.HealthStatus == nil {
continue
}
if strings.EqualFold(*i.HealthStatus, "Healthy") {
healthy++
}
}
log.Printf(
"[DEBUG] %q has %d/%d healthy instances", d.Id(), healthy, waitFor)
if healthy >= waitFor {
return nil
}
return fmt.Errorf("Waiting for healthy instances: %d/%d", healthy, waitFor)
})
}

View File

@ -3,6 +3,7 @@ package aws
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/awslabs/aws-sdk-go/aws" "github.com/awslabs/aws-sdk-go/aws"
@ -24,6 +25,7 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
Config: testAccAWSAutoScalingGroupConfig, Config: testAccAWSAutoScalingGroupConfig,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group),
testAccCheckAWSAutoScalingGroupHealthyCapacity(&group, 2),
testAccCheckAWSAutoScalingGroupAttributes(&group), testAccCheckAWSAutoScalingGroupAttributes(&group),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "availability_zones.2487133097", "us-west-2a"), "aws_autoscaling_group.bar", "availability_zones.2487133097", "us-west-2a"),
@ -116,6 +118,7 @@ func TestAccAWSAutoScalingGroup_WithLoadBalancer(t *testing.T) {
}, },
}) })
} }
func testAccCheckAWSAutoScalingGroupDestroy(s *terraform.State) error { func testAccCheckAWSAutoScalingGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).autoscalingconn conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
@ -261,6 +264,25 @@ func testLaunchConfigurationName(n string, lc *autoscaling.LaunchConfiguration)
} }
} }
func testAccCheckAWSAutoScalingGroupHealthyCapacity(
g *autoscaling.AutoScalingGroup, exp int) resource.TestCheckFunc {
return func(s *terraform.State) error {
healthy := 0
for _, i := range g.Instances {
if i.HealthStatus == nil {
continue
}
if strings.EqualFold(*i.HealthStatus, "Healthy") {
healthy++
}
}
if healthy < exp {
return fmt.Errorf("Expected at least %d healthy, got %d.", exp, healthy)
}
return nil
}
}
const testAccAWSAutoScalingGroupConfig = ` const testAccAWSAutoScalingGroupConfig = `
resource "aws_launch_configuration" "foobar" { resource "aws_launch_configuration" "foobar" {
name = "foobarautoscaling-terraform-test" name = "foobarautoscaling-terraform-test"

View File

@ -43,12 +43,19 @@ The following arguments are supported:
* `name` - (Required) The name of the auto scale group. * `name` - (Required) The name of the auto scale group.
* `max_size` - (Required) The maximum size of the auto scale group. * `max_size` - (Required) The maximum size of the auto scale group.
* `min_size` - (Required) The minimum size of the auto scale group. * `min_size` - (Required) The minimum size of the auto scale group. Terraform
waits after ASG creation for this number of healthy instances to show up in
the ASG before continuing. Currently, it will wait for a maxiumum of 10m, if
ASG creation is taking more than a few minutes, it's worth investigating for
scaling actvity errors caused by problems with the selected Launch
Configuration.
* `availability_zones` - (Required) A list of AZs to launch resources in. * `availability_zones` - (Required) A list of AZs to launch resources in.
* `launch_configuration` - (Required) The ID of the launch configuration to use. * `launch_configuration` - (Required) The ID of the launch configuration to use.
* `health_check_grace_period` - (Optional) Time after instance comes into service before checking health. * `health_check_grace_period` - (Optional) Time after instance comes into service before checking health.
* `health_check_type` - (Optional) "EC2" or "ELB". Controls how health checking is done. * `health_check_type` - (Optional) "EC2" or "ELB". Controls how health checking is done.
* `desired_capacity` - (Optional) The number of Amazon EC2 instances that should be running in the group. * `desired_capacity` - (Optional) The number of Amazon EC2 instances that
should be running in the group. (If this is specified, Terraform will wait for
this number of healthy instances after ASG creation instead of `min_size`.)
* `force_delete` - (Optional) Allows deleting the autoscaling group without waiting * `force_delete` - (Optional) Allows deleting the autoscaling group without waiting
for all instances in the pool to terminate. for all instances in the pool to terminate.
* `load_balancers` (Optional) A list of load balancer names to add to the autoscaling * `load_balancers` (Optional) A list of load balancer names to add to the autoscaling