fix the homegrown waiters

fix image import; issue was with wait options not being evaluated
This commit is contained in:
Megan Marsh 2018-06-04 15:34:20 -07:00
parent f49a2d8aed
commit bfbe318727
2 changed files with 69 additions and 58 deletions

View File

@ -146,15 +146,9 @@ func WaitForVolumeToBeAttached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeV
{
State: request.SuccessWaiterState,
Matcher: request.PathAllWaiterMatch,
Argument: "Volumes[].State",
Argument: "Volumes[].Attachments[].State",
Expected: "attached",
},
{
State: request.FailureWaiterState,
Matcher: request.PathAnyWaiterMatch,
Argument: "Volumes[].State",
Expected: "deleted",
},
},
Logger: c.Config.Logger,
NewRequest: func(opts []request.Option) (*request.Request, error) {
@ -181,8 +175,8 @@ func WaitForVolumeToBeDetached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeV
{
State: request.SuccessWaiterState,
Matcher: request.PathAllWaiterMatch,
Argument: "Volumes[].State",
Expected: "detached",
Argument: "length(Volumes[].Attachments[]) == `0`",
Expected: true,
},
},
Logger: c.Config.Logger,
@ -204,20 +198,15 @@ func WaitForVolumeToBeDetached(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeV
func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeImportImageTasksInput, opts ...request.WaiterOption) error {
w := request.Waiter{
Name: "DescribeImages",
MaxAttempts: 40,
MaxAttempts: 300,
Delay: request.ConstantWaiterDelay(5 * time.Second),
Acceptors: []request.WaiterAcceptor{
{
State: request.SuccessWaiterState,
Matcher: request.PathAllWaiterMatch,
Argument: "ImportImageTasks[].State",
Argument: "ImportImageTasks[].Status",
Expected: "completed",
},
{
State: request.RetryWaiterState,
Matcher: request.ErrorWaiterMatch,
Expected: "InvalidConversionTaskId",
},
},
Logger: c.Config.Logger,
NewRequest: func(opts []request.Option) (*request.Request, error) {
@ -239,57 +228,75 @@ func WaitForImageToBeImported(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeIm
// AWS_POLL_DELAY_SECONDS to generate waiter options that can be passed into any
// request.Waiter function. These options will control how many times the waiter
// will retry the request, as well as how long to wait between the retries.
// DEFAULTING BEHAVIOR:
// if AWS_POLL_DELAY_SECONDS is set but the others are not, Packer will set this
// poll delay and use the waiter-specific default
// if AWS_TIMEOUT_SECONDS is set but AWS_MAX_ATTEMPTS is not, Packer will use
// AWS_TIMEOUT_SECONDS and _either_ AWS_POLL_DELAY_SECONDS _or_ 2 if the user has not set AWS_POLL_DELAY_SECONDS, to determine a max number of attempts to make.
// if AWS_TIMEOUT_SECONDS, _and_ AWS_MAX_ATTEMPTS are both set,
// AWS_TIMEOUT_SECONDS will be ignored.
// if AWS_MAX_ATTEMPTS is set but AWS_POLL_DELAY_SECONDS is not, then we will
// use waiter-specific defaults.
func getWaiterOptions() []request.WaiterOption {
// use env vars to read in the wait delay and the max amount of time to wait
delay := SleepSeconds()
timeoutSeconds := TimeoutSeconds()
// AWS sdk uses max attempts instead of a timeout; convert timeout into
// max attempts
maxAttempts := timeoutSeconds / delay
delaySeconds := request.ConstantWaiterDelay(time.Duration(delay) * time.Second)
waitOpts := make([]request.WaiterOption, 0)
// If user has set poll delay seconds, overwrite it. If user has NOT,
// default to a poll delay of 2 seconds
delayOverridden, delay := getEnvOverrides(2, "AWS_POLL_DELAY_SECONDS")
if delayOverridden {
delaySeconds := request.ConstantWaiterDelay(time.Duration(delay) * time.Second)
waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
}
return []request.WaiterOption{
request.WithWaiterDelay(delaySeconds),
request.WithWaiterMaxAttempts(maxAttempts)}
// If user has set max attempts, overwrite it. If user hasn't set max
// attempts, default to whatever the waiter has set as a default.
maxAttemptsOverridden, maxAttempts := getEnvOverrides(0, "AWS_MAX_ATTEMPTS")
if maxAttemptsOverridden {
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(maxAttempts))
}
timeoutOverridden, timeoutSeconds := getEnvOverrides(300, "AWS_TIMEOUT_SECONDS")
if maxAttemptsOverridden {
log.Printf("WARNING: AWS_MAX_ATTEMPTS and AWS_TIMEOUT_SECONDS are" +
" both set. Packer will be using AWS_MAX_ATTEMPTS and discarding " +
"AWS_TIMEOUT_SECONDS. If you have not set AWS_POLL_DELAY_SECONDS, " +
"Packer will default to a 2 second poll delay.")
} else if timeoutOverridden {
log.Printf("DEPRECATION WARNING: env var AWS_TIMEOUT_SECONDS is " +
"deprecated in favor of AWS_MAX_ATTEMPTS. If you have not " +
"explicitly set AWS_POLL_DELAY_SECONDS, we are defaulting to a " +
"poll delay of 2 seconds, regardless of the AWS waiter's default.")
maxAttempts := timeoutSeconds / delay
// override the delay so we can get the timeout right
if !delayOverridden {
delaySeconds := request.ConstantWaiterDelay(time.Duration(delay) * time.Second)
waitOpts = append(waitOpts, request.WithWaiterDelay(delaySeconds))
}
waitOpts = append(waitOpts, request.WithWaiterMaxAttempts(maxAttempts))
}
return waitOpts
}
// Returns 300 seconds (5 minutes) by default
// Some AWS operations, like copying an AMI to a distant region, take a very long time
// Allow user to override with AWS_TIMEOUT_SECONDS environment variable
func TimeoutSeconds() (seconds int) {
seconds = 300
override := os.Getenv("AWS_TIMEOUT_SECONDS")
func getEnvOverrides(defaultValue int, envVarName string) (bool, int) {
// "AWS_POLL_DELAY_SECONDS"
retVal := defaultValue
overridden := false
override := os.Getenv(envVarName)
if override != "" {
n, err := strconv.Atoi(override)
if err != nil {
log.Printf("Invalid timeout seconds '%s', using default", override)
log.Printf("Invalid %s '%s', using default", envVarName, override)
} else {
seconds = n
overridden = true
retVal = n
}
}
log.Printf("Allowing %ds to complete (change with AWS_TIMEOUT_SECONDS)", seconds)
return seconds
}
// Returns 2 seconds by default
// AWS async operations sometimes takes long times, if there are multiple parallel builds,
// polling at 2 second frequency will exceed the request limit. Allow 2 seconds to be
// overwritten with AWS_POLL_DELAY_SECONDS
func SleepSeconds() (seconds int) {
seconds = 2
override := os.Getenv("AWS_POLL_DELAY_SECONDS")
if override != "" {
n, err := strconv.Atoi(override)
if err != nil {
log.Printf("Invalid sleep seconds '%s', using default", override)
} else {
seconds = n
}
}
log.Printf("Using %ds as polling delay (change with AWS_POLL_DELAY_SECONDS)", seconds)
return seconds
log.Printf("Using %ds for %s", retVal, envVarName)
return overridden, retVal
}

View File

@ -187,6 +187,9 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
// Wait for import process to complete, this takes a while
ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *import_start.ImportTaskId))
err = awscommon.WaitUntilImageImported(aws.BackgroundContext(), ec2conn, *import_start.ImportTaskId)
if err != nil {
return nil, false, fmt.Errorf("Import task %s failed with error: %s", *import_start.ImportTaskId, err)
}
// Retrieve what the outcome was for the import task
import_result, err := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{
@ -200,6 +203,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
}
// Check it was actually completed
log.Printf("MEGAN result was %s", *import_result.ImportImageTasks[0].Status)
if *import_result.ImportImageTasks[0].Status != "completed" {
// The most useful error message is from the job itself
return nil, false, fmt.Errorf("Import task %s failed: %s", *import_start.ImportTaskId, *import_result.ImportImageTasks[0].StatusMessage)