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

Retry instance metadata on fingerprint mismatch. #3372

Merged
merged 1 commit into from
Apr 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 3 additions & 16 deletions google/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,24 @@ package google
import (
"errors"
"fmt"
"strings"

computeBeta "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/compute/v1"
)

const FINGERPRINT_RETRIES = 10

var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}
const METADATA_FINGERPRINT_RETRIES = 10

// Since the google compute API uses optimistic locking, there is a chance
// we need to resubmit our updated metadata. To do this, you need to provide
// an update function that attempts to submit your metadata
func MetadataRetryWrapper(update func() error) error {
attempt := 0
for attempt < FINGERPRINT_RETRIES {
for attempt < METADATA_FINGERPRINT_RETRIES {
err := update()
if err == nil {
return nil
}

// Check to see if the error matches any of our fingerprint-related failure messages
var fingerprintError bool
for _, msg := range FINGERPRINT_FAIL_ERRORS {
if strings.Contains(err.Error(), msg) {
fingerprintError = true
break
}
}

if !fingerprintError {
if !isFingerprintError(err) {
// Something else went wrong, don't retry
return err
}
Expand Down
34 changes: 27 additions & 7 deletions google/resource_compute_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -938,14 +938,34 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
return err
}

op, err := config.clientCompute.Instances.SetMetadata(project, zone, d.Id(), metadataV1).Do()
if err != nil {
return fmt.Errorf("Error updating metadata: %s", err)
}
// We're retrying for an error 412 where the metadata fingerprint is out of date
err = retry(
func() error {
// retrieve up-to-date metadata from the API in case several updates hit simultaneously. instances
// sometimes but not always share metadata fingerprints.
instance, err := config.clientComputeBeta.Instances.Get(project, zone, d.Id()).Do()
if err != nil {
return fmt.Errorf("Error retrieving metadata: %s", err)
}

opErr := computeOperationWaitTime(config.clientCompute, op, project, "metadata to update", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
if opErr != nil {
return opErr
metadataV1.Fingerprint = instance.Metadata.Fingerprint

op, err := config.clientCompute.Instances.SetMetadata(project, zone, d.Id(), metadataV1).Do()
if err != nil {
return fmt.Errorf("Error updating metadata: %s", err)
}

opErr := computeOperationWaitTime(config.clientCompute, op, project, "metadata to update", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
if opErr != nil {
return opErr
}

return nil
},
)

if err != nil {
return err
}

d.SetPartial("metadata")
Expand Down
19 changes: 19 additions & 0 deletions google/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ func isFailedPreconditionError(err error) bool {
return false
}

var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}

// We've encountered a few common fingerprint-related strings; if this is one of
// them, we're confident this is an error due to fingerprints.
func isFingerprintError(err error) bool {
for _, msg := range FINGERPRINT_FAIL_ERRORS {
if strings.Contains(err.Error(), msg) {
return true
}
}

return false
}

func isConflictError(err error) bool {
if e, ok := err.(*googleapi.Error); ok && e.Code == 409 {
return true
Expand Down Expand Up @@ -369,6 +383,11 @@ func isRetryableError(err error) bool {
return true
}

if gerr, ok := err.(*googleapi.Error); ok && (gerr.Code == 412) && isFingerprintError(err) {
log.Printf("[DEBUG] Dismissed an error as retryable as a fingerprint mismatch: %s", err)
return true
}

return false
}

Expand Down