Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: Support suspending auto scaling group processes #5107

Closed
wants to merge 8 commits into from
73 changes: 65 additions & 8 deletions builtin/providers/aws/resource_aws_autoscaling_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ func resourceAwsAutoscalingGroup() *schema.Resource {
Set: schema.HashString,
},

"suspended_processes": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},

"vpc_zone_identifier": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Expand Down Expand Up @@ -232,10 +239,15 @@ func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{})
return fmt.Errorf("Error creating Autoscaling Group: %s", err)
}

if err := updateSuspendedProcesses(d, meta); err != nil {
return err
}

d.SetId(d.Get("name").(string))

log.Printf("[INFO] AutoScaling Group ID: %s", d.Id())

if err := waitForASGCapacity(d, meta, capacitySatifiedCreate); err != nil {
if err := waitForASGCapacity(d, meta); err != nil {
return err
}

Expand Down Expand Up @@ -273,6 +285,7 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e
d.Set("max_size", g.MaxSize)
d.Set("placement_group", g.PlacementGroup)
d.Set("name", g.AutoScalingGroupName)
d.Set("suspended_processes", flattenSuspendedProcesses(g.SuspendedProcesses))
d.Set("tag", g.Tags)
d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ","))

Expand All @@ -298,7 +311,6 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e

func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).autoscalingconn
shouldWaitForCapacity := false

opts := autoscaling.UpdateAutoScalingGroupInput{
AutoScalingGroupName: aws.String(d.Id()),
Expand All @@ -310,7 +322,6 @@ func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{})

if d.HasChange("desired_capacity") {
opts.DesiredCapacity = aws.Int64(int64(d.Get("desired_capacity").(int)))
shouldWaitForCapacity = true
}

if d.HasChange("launch_configuration") {
Expand All @@ -319,7 +330,6 @@ func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{})

if d.HasChange("min_size") {
opts.MinSize = aws.Int64(int64(d.Get("min_size").(int)))
shouldWaitForCapacity = true
}

if d.HasChange("max_size") {
Expand Down Expand Up @@ -360,12 +370,16 @@ func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{})
}
}

if err := updateSuspendedProcesses(d, meta); err != nil {
return err
}

if err := setAutoscalingTags(conn, d); err != nil {
return err
} else {
d.SetPartial("tag")
}

d.SetPartial("tag")

log.Printf("[DEBUG] AutoScaling Group update configuration: %#v", opts)
_, err := conn.UpdateAutoScalingGroup(&opts)
if err != nil {
Expand Down Expand Up @@ -409,8 +423,8 @@ func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{})
}
}

if shouldWaitForCapacity {
waitForASGCapacity(d, meta, capacitySatifiedUpdate)
if err := waitForASGCapacity(d, meta); err != nil {
return err
}

if d.HasChange("enabled_metrics") {
Expand Down Expand Up @@ -643,3 +657,46 @@ func expandVpcZoneIdentifiers(list []interface{}) *string {
}
return aws.String(strings.Join(strs, ","))
}

func flattenSuspendedProcesses(procs []*autoscaling.SuspendedProcess) []string {
names := make([]string, len(procs))
for i, p := range procs {
names[i] = *p.ProcessName
}
return names
}

func updateSuspendedProcesses(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).autoscalingconn

if d.HasChange("suspended_processes") {
old, new := d.GetChange("suspended_processes")
oldSet := old.(*schema.Set)
newSet := new.(*schema.Set)

added := newSet.Difference(oldSet).List()
if len(added) > 0 {
_, err := conn.SuspendProcesses(&autoscaling.ScalingProcessQuery{
AutoScalingGroupName: aws.String(d.Get("name").(string)),
ScalingProcesses: expandStringList(added),
})
if err != nil {
return fmt.Errorf("Failure suspending scaling processes: %s", err)
}
}

removed := oldSet.Difference(newSet).List()
if len(removed) > 0 {
_, err := conn.ResumeProcesses(&autoscaling.ScalingProcessQuery{
AutoScalingGroupName: aws.String(d.Get("name").(string)),
ScalingProcesses: expandStringList(removed),
})
if err != nil {
return fmt.Errorf("Failure resuming scaling processes: %s", err)
}
}
d.SetPartial("suspended_processes")
}

return nil
}
70 changes: 70 additions & 0 deletions builtin/providers/aws/resource_aws_autoscaling_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,41 @@ func TestAccAWSAutoScalingGroup_terminationPolicies(t *testing.T) {
})
}

func TestAccAWSAutoScalingGroup_suspendedProcceses(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAutoScalingGroupConfig_suspendedProcessesNone,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "suspended_processes.#", "0"),
),
},
resource.TestStep{
Config: testAccAWSAutoScalingGroupConfig_suspendedProcessesUpdate,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "suspended_processes.#", "2"),
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "suspended_processes.997436260", "HealthCheck"),
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "suspended_processes.2282213524", "ScheduledActions"),
),
},
resource.TestStep{
Config: testAccAWSAutoScalingGroupConfig_suspendedProcessesNone,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"aws_autoscaling_group.bar", "suspended_processes.#", "0"),
),
},
},
})
}

func TestAccAWSAutoScalingGroup_tags(t *testing.T) {
var group autoscaling.Group

Expand Down Expand Up @@ -539,6 +574,41 @@ resource "aws_autoscaling_group" "bar" {
}
`

const testAccAWSAutoScalingGroupConfig_suspendedProcessesNone = `
resource "aws_launch_configuration" "foobar" {
image_id = "ami-21f78e11"
instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
availability_zones = ["us-west-2a"]
desired_capacity = 0
max_size = 0
min_size = 0
launch_configuration = "${aws_launch_configuration.foobar.name}"
}
`

const testAccAWSAutoScalingGroupConfig_suspendedProcessesUpdate = `
resource "aws_launch_configuration" "foobar" {
image_id = "ami-21f78e11"
instance_type = "t1.micro"
}

resource "aws_autoscaling_group" "bar" {
availability_zones = ["us-west-2a"]
desired_capacity = 0
max_size = 0
min_size = 0
launch_configuration = "${aws_launch_configuration.foobar.name}"

suspended_processes = [
"HealthCheck",
"ScheduledActions",
]
}
`

const testAccAWSAutoScalingGroupConfig = `
resource "aws_launch_configuration" "foobar" {
image_id = "ami-21f78e11"
Expand Down
74 changes: 42 additions & 32 deletions builtin/providers/aws/resource_aws_autoscaling_group_waiting.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ import (
// the capacitySatisfiedFunc returns true.
//
// See "Waiting for Capacity" in docs for more discussion of the feature.
func waitForASGCapacity(
d *schema.ResourceData,
meta interface{},
satisfiedFunc capacitySatisfiedFunc) error {
func waitForASGCapacity(d *schema.ResourceData, meta interface{}) error {
wait, err := time.ParseDuration(d.Get("wait_for_capacity_timeout").(string))
if err != nil {
return err
Expand Down Expand Up @@ -77,7 +74,7 @@ func waitForASGCapacity(
}
}

satisfied, reason := satisfiedFunc(d, haveASG, haveELB)
satisfied, reason := checkCapacitySatisfied(d, haveASG, haveELB)

log.Printf("[DEBUG] %q Capacity: %d ASG, %d ELB, satisfied: %t, reason: %q",
d.Id(), haveASG, haveELB, satisfied, reason)
Expand All @@ -91,42 +88,55 @@ func waitForASGCapacity(
})
}

type capacitySatisfiedFunc func(*schema.ResourceData, int, int) (bool, string)
// checkCapacitySatisfied determines if required ASG and ELB targets are met.
func checkCapacitySatisfied(d *schema.ResourceData, haveASG, haveELB int) (bool, string) {
isLaunchSuspended := false
isTerminateSuspended := false
if procs, ok := d.GetOk("suspended_processes"); ok {
for _, p := range procs.(*schema.Set).List() {
switch p.(string) {
case "Launch":
isLaunchSuspended = true
case "Terminate":
isTerminateSuspended = true
}
}
}

// capacitySatifiedCreate treats all targets as minimums
func capacitySatifiedCreate(d *schema.ResourceData, haveASG, haveELB int) (bool, string) {
minASG := d.Get("min_size").(int)
if wantASG := d.Get("desired_capacity").(int); wantASG > 0 {
minASG = wantASG
if desiredASG, ok := d.GetOk("desired_capacity"); ok && desiredASG.(int) > haveASG && !isLaunchSuspended {
return false, fmt.Sprintf(
"Need exactly %d healthy instances in ASG, have %d", desiredASG.(int), haveASG)
}
if haveASG < minASG {

if desiredASG, ok := d.GetOk("desired_capacity"); ok && desiredASG.(int) < haveASG && !isTerminateSuspended {
return false, fmt.Sprintf(
"Need at least %d healthy instances in ASG, have %d", minASG, haveASG)
"Need exactly %d healthy instances in ASG, have %d", desiredASG.(int), haveASG)
}
minELB := d.Get("min_elb_capacity").(int)
if wantELB := d.Get("wait_for_elb_capacity").(int); wantELB > 0 {
minELB = wantELB

if minASG, ok := d.GetOk("min_size"); ok && minASG.(int) > haveASG && !isLaunchSuspended {
return false, fmt.Sprintf(
"Need at least %d healthy instances in ASG, have %d", minASG.(int), haveASG)
}
if haveELB < minELB {

if maxASG, ok := d.GetOk("max_size"); ok && maxASG.(int) < haveASG && !isTerminateSuspended {
return false, fmt.Sprintf(
"Need at least %d healthy instances in ELB, have %d", minELB, haveELB)
"Need at most %d healthy instances in ASG, have %d", maxASG.(int), haveASG)
}
return true, ""
}

// capacitySatifiedUpdate only cares about specific targets
func capacitySatifiedUpdate(d *schema.ResourceData, haveASG, haveELB int) (bool, string) {
if wantASG := d.Get("desired_capacity").(int); wantASG > 0 {
if haveASG != wantASG {
return false, fmt.Sprintf(
"Need exactly %d healthy instances in ASG, have %d", wantASG, haveASG)
}
if desiredELB, ok := d.GetOk("wait_for_elb_capacity"); ok && desiredELB.(int) > haveELB && !isLaunchSuspended {
return false, fmt.Sprintf(
"Need exactly %d healthy instances in ELB, have %d", desiredELB.(int), haveELB)
}
if wantELB := d.Get("wait_for_elb_capacity").(int); wantELB > 0 {
if haveELB != wantELB {
return false, fmt.Sprintf(
"Need exactly %d healthy instances in ELB, have %d", wantELB, haveELB)
}

if desiredELB, ok := d.GetOk("wait_for_elb_capacity"); ok && desiredELB.(int) < haveELB && !isTerminateSuspended {
return false, fmt.Sprintf(
"Need exactly %d healthy instances in ELB, have %d", desiredELB.(int), haveELB)
}

if minELB, ok := d.GetOk("min_elb_capacity"); ok && minELB.(int) > haveELB && !isLaunchSuspended {
return false, fmt.Sprintf(
"Need at least %d healthy instances in ELB, have %d", minELB.(int), haveELB)
}

return true, ""
}
Loading