github.com/meteor/terraform@v0.6.15-0.20210412225145-79ec4bc057c6/builtin/providers/aws/resource_aws_autoscaling_group_waiting.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/resource"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  )
    12  
    13  // waitForASGCapacityTimeout gathers the current numbers of healthy instances
    14  // in the ASG and its attached ELBs and yields these numbers to a
    15  // capacitySatifiedFunction. Loops for up to wait_for_capacity_timeout until
    16  // the capacitySatisfiedFunc returns true.
    17  //
    18  // See "Waiting for Capacity" in docs for more discussion of the feature.
    19  func waitForASGCapacity(
    20  	d *schema.ResourceData,
    21  	meta interface{},
    22  	satisfiedFunc capacitySatisfiedFunc) error {
    23  	wait, err := time.ParseDuration(d.Get("wait_for_capacity_timeout").(string))
    24  	if err != nil {
    25  		return err
    26  	}
    27  
    28  	if wait == 0 {
    29  		log.Printf("[DEBUG] Capacity timeout set to 0, skipping capacity waiting.")
    30  		return nil
    31  	}
    32  
    33  	log.Printf("[DEBUG] Waiting on %s for capacity...", d.Id())
    34  
    35  	return resource.Retry(wait, func() *resource.RetryError {
    36  		g, err := getAwsAutoscalingGroup(d.Id(), meta.(*AWSClient).autoscalingconn)
    37  		if err != nil {
    38  			return resource.NonRetryableError(err)
    39  		}
    40  		if g == nil {
    41  			log.Printf("[INFO] Autoscaling Group %q not found", d.Id())
    42  			d.SetId("")
    43  			return nil
    44  		}
    45  		elbis, err := getELBInstanceStates(g, meta)
    46  		albis, err := getTargetGroupInstanceStates(g, meta)
    47  		if err != nil {
    48  			return resource.NonRetryableError(err)
    49  		}
    50  
    51  		haveASG := 0
    52  		haveELB := 0
    53  
    54  		for _, i := range g.Instances {
    55  			if i.HealthStatus == nil || i.InstanceId == nil || i.LifecycleState == nil {
    56  				continue
    57  			}
    58  
    59  			if !strings.EqualFold(*i.HealthStatus, "Healthy") {
    60  				continue
    61  			}
    62  
    63  			if !strings.EqualFold(*i.LifecycleState, "InService") {
    64  				continue
    65  			}
    66  
    67  			haveASG++
    68  
    69  			inAllLbs := true
    70  			for _, states := range elbis {
    71  				state, ok := states[*i.InstanceId]
    72  				if !ok || !strings.EqualFold(state, "InService") {
    73  					inAllLbs = false
    74  				}
    75  			}
    76  			for _, states := range albis {
    77  				state, ok := states[*i.InstanceId]
    78  				if !ok || !strings.EqualFold(state, "healthy") {
    79  					inAllLbs = false
    80  				}
    81  			}
    82  			if inAllLbs {
    83  				haveELB++
    84  			}
    85  		}
    86  
    87  		satisfied, reason := satisfiedFunc(d, haveASG, haveELB)
    88  
    89  		log.Printf("[DEBUG] %q Capacity: %d ASG, %d ELB/ALB, satisfied: %t, reason: %q",
    90  			d.Id(), haveASG, haveELB, satisfied, reason)
    91  
    92  		if satisfied {
    93  			return nil
    94  		}
    95  
    96  		return resource.RetryableError(
    97  			fmt.Errorf("%q: Waiting up to %s: %s", d.Id(), wait, reason))
    98  	})
    99  }
   100  
   101  type capacitySatisfiedFunc func(*schema.ResourceData, int, int) (bool, string)
   102  
   103  // capacitySatisfiedCreate treats all targets as minimums
   104  func capacitySatisfiedCreate(d *schema.ResourceData, haveASG, haveELB int) (bool, string) {
   105  	minASG := d.Get("min_size").(int)
   106  	if wantASG := d.Get("desired_capacity").(int); wantASG > 0 {
   107  		minASG = wantASG
   108  	}
   109  	if haveASG < minASG {
   110  		return false, fmt.Sprintf(
   111  			"Need at least %d healthy instances in ASG, have %d", minASG, haveASG)
   112  	}
   113  	minELB := d.Get("min_elb_capacity").(int)
   114  	if wantELB := d.Get("wait_for_elb_capacity").(int); wantELB > 0 {
   115  		minELB = wantELB
   116  	}
   117  	if haveELB < minELB {
   118  		return false, fmt.Sprintf(
   119  			"Need at least %d healthy instances in ELB, have %d", minELB, haveELB)
   120  	}
   121  	return true, ""
   122  }
   123  
   124  // capacitySatisfiedUpdate only cares about specific targets
   125  func capacitySatisfiedUpdate(d *schema.ResourceData, haveASG, haveELB int) (bool, string) {
   126  	if wantASG := d.Get("desired_capacity").(int); wantASG > 0 {
   127  		if haveASG != wantASG {
   128  			return false, fmt.Sprintf(
   129  				"Need exactly %d healthy instances in ASG, have %d", wantASG, haveASG)
   130  		}
   131  	}
   132  	if wantELB := d.Get("wait_for_elb_capacity").(int); wantELB > 0 {
   133  		if haveELB != wantELB {
   134  			return false, fmt.Sprintf(
   135  				"Need exactly %d healthy instances in ELB, have %d", wantELB, haveELB)
   136  		}
   137  	}
   138  	return true, ""
   139  }
   140  
   141  // waitForASGScaleDown gathers the current numbers of instances in the ASG and
   142  // loops for up to wait_for_capacity_timeout.
   143  //
   144  // Unlike waitForASGCapacity, it doesn't care about ELBs, and it also doesn't
   145  // care about health. Specifically, it considers a terminating instance to still
   146  // exist, which is what you want when you're scaling down: just starting a
   147  // termination hook shouldn't be enough to count as scaled down.
   148  func waitForASGScaleDown(
   149  	d *schema.ResourceData,
   150  	meta interface{},
   151  	wantASG int) error {
   152  	wait, err := time.ParseDuration(d.Get("wait_for_capacity_timeout").(string))
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	if wait == 0 {
   158  		log.Printf("[DEBUG] Capacity timeout set to 0, skipping capacity waiting.")
   159  		return nil
   160  	}
   161  
   162  	log.Printf("[DEBUG] Waiting on %s to scale down...", d.Id())
   163  
   164  	return resource.Retry(wait, func() *resource.RetryError {
   165  		g, err := getAwsAutoscalingGroup(d.Id(), meta.(*AWSClient).autoscalingconn)
   166  		if err != nil {
   167  			return resource.NonRetryableError(err)
   168  		}
   169  		if g == nil {
   170  			log.Printf("[INFO] Autoscaling Group %q not found", d.Id())
   171  			d.SetId("")
   172  			return nil
   173  		}
   174  
   175  		haveASG := len(g.Instances)
   176  
   177  		if haveASG == wantASG {
   178  			log.Printf("[DEBUG] %q Capacity %d achieved", d.Id(), wantASG)
   179  			return nil
   180  		}
   181  
   182  		log.Printf("[DEBUG] %q Capacity: %d desired, %d got, waiting", d.Id(), wantASG, haveASG)
   183  
   184  		return resource.RetryableError(fmt.Errorf("%q: Waiting up to %s", d.Id(), wait))
   185  	})
   186  }