Merge pull request #7523 from hashicorp/f-aws-beanstalk-env-poll-timing

provider/aws: Beanstalk environments, bump the minimum timeout between API calls
This commit is contained in:
Paul Hinze 2016-07-21 16:43:21 -05:00 committed by GitHub
commit 261043fd1a
3 changed files with 114 additions and 57 deletions

View File

@ -135,6 +135,23 @@ func resourceAwsElasticBeanstalkEnvironment() *schema.Resource {
return return
}, },
}, },
"poll_interval": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
duration, err := time.ParseDuration(value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q cannot be parsed as a duration: %s", k, err))
}
if duration < 10*time.Second || duration > 60*time.Second {
errors = append(errors, fmt.Errorf(
"%q must be between 10s and 180s", k))
}
return
},
},
"autoscaling_groups": &schema.Schema{ "autoscaling_groups": &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
Computed: true, Computed: true,
@ -183,10 +200,6 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
settings := d.Get("setting").(*schema.Set) settings := d.Get("setting").(*schema.Set)
solutionStack := d.Get("solution_stack_name").(string) solutionStack := d.Get("solution_stack_name").(string)
templateName := d.Get("template_name").(string) templateName := d.Get("template_name").(string)
waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
if err != nil {
return err
}
// TODO set tags // TODO set tags
// Note: at time of writing, you cannot view or edit Tags after creation // Note: at time of writing, you cannot view or edit Tags after creation
@ -244,13 +257,25 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
// Assign the application name as the resource ID // Assign the application name as the resource ID
d.SetId(*resp.EnvironmentId) d.SetId(*resp.EnvironmentId)
waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
if err != nil {
return err
}
pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string))
if err != nil {
pollInterval = 0
log.Printf("[WARN] Error parsing poll_interval, using default backoff")
}
stateConf := &resource.StateChangeConf{ stateConf := &resource.StateChangeConf{
Pending: []string{"Launching", "Updating"}, Pending: []string{"Launching", "Updating"},
Target: []string{"Ready"}, Target: []string{"Ready"},
Refresh: environmentStateRefreshFunc(conn, d.Id()), Refresh: environmentStateRefreshFunc(conn, d.Id()),
Timeout: waitForReadyTimeOut, Timeout: waitForReadyTimeOut,
Delay: 10 * time.Second, Delay: 10 * time.Second,
MinTimeout: 3 * time.Second, PollInterval: pollInterval,
MinTimeout: 3 * time.Second,
} }
_, err = stateConf.WaitForState() _, err = stateConf.WaitForState()
@ -272,24 +297,25 @@ func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta i
conn := meta.(*AWSClient).elasticbeanstalkconn conn := meta.(*AWSClient).elasticbeanstalkconn
envId := d.Id() envId := d.Id()
waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
if err != nil { var hasChange bool
return err
}
updateOpts := elasticbeanstalk.UpdateEnvironmentInput{ updateOpts := elasticbeanstalk.UpdateEnvironmentInput{
EnvironmentId: aws.String(envId), EnvironmentId: aws.String(envId),
} }
if d.HasChange("description") { if d.HasChange("description") {
hasChange = true
updateOpts.Description = aws.String(d.Get("description").(string)) updateOpts.Description = aws.String(d.Get("description").(string))
} }
if d.HasChange("solution_stack_name") { if d.HasChange("solution_stack_name") {
hasChange = true
updateOpts.SolutionStackName = aws.String(d.Get("solution_stack_name").(string)) updateOpts.SolutionStackName = aws.String(d.Get("solution_stack_name").(string))
} }
if d.HasChange("setting") { if d.HasChange("setting") {
hasChange = true
o, n := d.GetChange("setting") o, n := d.GetChange("setting")
if o == nil { if o == nil {
o = &schema.Set{F: optionSettingValueHash} o = &schema.Set{F: optionSettingValueHash}
@ -305,36 +331,50 @@ func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta i
} }
if d.HasChange("template_name") { if d.HasChange("template_name") {
hasChange = true
updateOpts.TemplateName = aws.String(d.Get("template_name").(string)) updateOpts.TemplateName = aws.String(d.Get("template_name").(string))
} }
// Get the current time to filter describeBeanstalkEvents messages if hasChange {
t := time.Now() // Get the current time to filter describeBeanstalkEvents messages
log.Printf("[DEBUG] Elastic Beanstalk Environment update opts: %s", updateOpts) t := time.Now()
_, err = conn.UpdateEnvironment(&updateOpts) log.Printf("[DEBUG] Elastic Beanstalk Environment update opts: %s", updateOpts)
if err != nil { _, err := conn.UpdateEnvironment(&updateOpts)
return err if err != nil {
} return err
}
stateConf := &resource.StateChangeConf{ waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
Pending: []string{"Launching", "Updating"}, if err != nil {
Target: []string{"Ready"}, return err
Refresh: environmentStateRefreshFunc(conn, d.Id()), }
Timeout: waitForReadyTimeOut, pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string))
Delay: 10 * time.Second, if err != nil {
MinTimeout: 3 * time.Second, pollInterval = 0
} log.Printf("[WARN] Error parsing poll_interval, using default backoff")
}
_, err = stateConf.WaitForState() stateConf := &resource.StateChangeConf{
if err != nil { Pending: []string{"Launching", "Updating"},
return fmt.Errorf( Target: []string{"Ready"},
"Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s", Refresh: environmentStateRefreshFunc(conn, d.Id()),
d.Id(), err) Timeout: waitForReadyTimeOut,
} Delay: 10 * time.Second,
PollInterval: pollInterval,
MinTimeout: 3 * time.Second,
}
err = describeBeanstalkEvents(conn, d.Id(), t) _, err = stateConf.WaitForState()
if err != nil { if err != nil {
return err return fmt.Errorf(
"Error waiting for Elastic Beanstalk Environment (%s) to become ready: %s",
d.Id(), err)
}
err = describeBeanstalkEvents(conn, d.Id(), t)
if err != nil {
return err
}
} }
return resourceAwsElasticBeanstalkEnvironmentRead(d, meta) return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
@ -547,11 +587,6 @@ func resourceAwsElasticBeanstalkEnvironmentSettingsRead(d *schema.ResourceData,
func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn conn := meta.(*AWSClient).elasticbeanstalkconn
waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
if err != nil {
return err
}
opts := elasticbeanstalk.TerminateEnvironmentInput{ opts := elasticbeanstalk.TerminateEnvironmentInput{
EnvironmentId: aws.String(d.Id()), EnvironmentId: aws.String(d.Id()),
TerminateResources: aws.Bool(true), TerminateResources: aws.Bool(true),
@ -560,19 +595,30 @@ func resourceAwsElasticBeanstalkEnvironmentDelete(d *schema.ResourceData, meta i
// Get the current time to filter describeBeanstalkEvents messages // Get the current time to filter describeBeanstalkEvents messages
t := time.Now() t := time.Now()
log.Printf("[DEBUG] Elastic Beanstalk Environment terminate opts: %s", opts) log.Printf("[DEBUG] Elastic Beanstalk Environment terminate opts: %s", opts)
_, err = conn.TerminateEnvironment(&opts) _, err := conn.TerminateEnvironment(&opts)
if err != nil { if err != nil {
return err return err
} }
waitForReadyTimeOut, err := time.ParseDuration(d.Get("wait_for_ready_timeout").(string))
if err != nil {
return err
}
pollInterval, err := time.ParseDuration(d.Get("poll_interval").(string))
if err != nil {
pollInterval = 0
log.Printf("[WARN] Error parsing poll_interval, using default backoff")
}
stateConf := &resource.StateChangeConf{ stateConf := &resource.StateChangeConf{
Pending: []string{"Terminating"}, Pending: []string{"Terminating"},
Target: []string{"Terminated"}, Target: []string{"Terminated"},
Refresh: environmentStateRefreshFunc(conn, d.Id()), Refresh: environmentStateRefreshFunc(conn, d.Id()),
Timeout: waitForReadyTimeOut, Timeout: waitForReadyTimeOut,
Delay: 10 * time.Second, Delay: 10 * time.Second,
MinTimeout: 3 * time.Second, PollInterval: pollInterval,
MinTimeout: 3 * time.Second,
} }
_, err = stateConf.WaitForState() _, err = stateConf.WaitForState()

View File

@ -26,6 +26,7 @@ type StateChangeConf struct {
Target []string // Target state Target []string // Target state
Timeout time.Duration // The amount of time to wait before timeout Timeout time.Duration // The amount of time to wait before timeout
MinTimeout time.Duration // Smallest time to wait before refreshes MinTimeout time.Duration // Smallest time to wait before refreshes
PollInterval time.Duration // Override MinTimeout/backoff and only poll this often
NotFoundChecks int // Number of times to allow not found NotFoundChecks int // Number of times to allow not found
// This is to work around inconsistent APIs // This is to work around inconsistent APIs
@ -72,14 +73,20 @@ func (conf *StateChangeConf) WaitForState() (interface{}, error) {
time.Sleep(conf.Delay) time.Sleep(conf.Delay)
var err error var err error
var wait time.Duration
for tries := 0; ; tries++ { for tries := 0; ; tries++ {
// Wait between refreshes using an exponential backoff // Wait between refreshes using an exponential backoff
wait := time.Duration(math.Pow(2, float64(tries))) * // If a poll interval has been specified, choose that interval
100 * time.Millisecond if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second {
if wait < conf.MinTimeout { wait = conf.PollInterval
wait = conf.MinTimeout } else {
} else if wait > 10*time.Second { wait = time.Duration(math.Pow(2, float64(tries))) *
wait = 10 * time.Second 100 * time.Millisecond
if wait < conf.MinTimeout {
wait = conf.MinTimeout
} else if wait > 10*time.Second {
wait = 10 * time.Second
}
} }
log.Printf("[TRACE] Waiting %s before next try", wait) log.Printf("[TRACE] Waiting %s before next try", wait)

View File

@ -51,10 +51,14 @@ The following arguments are supported:
off of. Example stacks can be found in the [Amazon API documentation][1] off of. Example stacks can be found in the [Amazon API documentation][1]
* `template_name` (Optional) The name of the Elastic Beanstalk Configuration * `template_name` (Optional) The name of the Elastic Beanstalk Configuration
template to use in deployment template to use in deployment
* `wait_for_ready_timeout` - (Default: "10m") The maximum * `wait_for_ready_timeout` - (Default: `10m`) The maximum
[duration](https://golang.org/pkg/time/#ParseDuration) that Terraform should [duration](https://golang.org/pkg/time/#ParseDuration) that Terraform should
wait for an Elastic Beanstalk Environment to be in a ready state before timing wait for an Elastic Beanstalk Environment to be in a ready state before timing
out. out.
* `poll_interval`  The time between polling the AWS API to
check if changes have been applied. Use this to adjust the rate of API calls
for any `create` or `update` action. Minimum `10s`, maximum `180s`. Omit this to
use the default behavior, which is an exponential backoff
* `tags`  (Optional) A set of tags to apply to the Environment. **Note:** at * `tags`  (Optional) A set of tags to apply to the Environment. **Note:** at
this time the Elastic Beanstalk API does not provide a programatic way of this time the Elastic Beanstalk API does not provide a programatic way of
changing these tags after initial application changing these tags after initial application