mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-07-05 17:52:38 +00:00
Add working retries for SQL operations where the operation has the error (#3298)
<!-- This change is generated by MagicModules. --> /cc @rileykarson
This commit is contained in:
parent
8be6190ab7
commit
ef974d2781
|
@ -22,7 +22,9 @@ type Waiter interface {
|
||||||
SetOp(interface{}) error
|
SetOp(interface{}) error
|
||||||
|
|
||||||
// QueryOp sends a request to the server to get the current status of the
|
// QueryOp sends a request to the server to get the current status of the
|
||||||
// operation.
|
// operation. It's expected that QueryOp will return exactly one of an
|
||||||
|
// operation or an error as non-nil, and that requests will be retried by
|
||||||
|
// specific implementations of the method.
|
||||||
QueryOp() (interface{}, error)
|
QueryOp() (interface{}, error)
|
||||||
|
|
||||||
// OpName is the name of the operation and is used to log its status.
|
// OpName is the name of the operation and is used to log its status.
|
||||||
|
@ -90,35 +92,22 @@ func OperationDone(w Waiter) bool {
|
||||||
|
|
||||||
func CommonRefreshFunc(w Waiter) resource.StateRefreshFunc {
|
func CommonRefreshFunc(w Waiter) resource.StateRefreshFunc {
|
||||||
return func() (interface{}, string, error) {
|
return func() (interface{}, string, error) {
|
||||||
// First, read the operation from the server.
|
|
||||||
op, err := w.QueryOp()
|
op, err := w.QueryOp()
|
||||||
|
|
||||||
// If we got a non-retryable error, return it.
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isRetryableError(err) {
|
// Importantly, this error is in the GET to the operation, and isn't an error
|
||||||
return nil, "", fmt.Errorf("Not retriable error: %s", err)
|
// with the resource CRUD request itself.
|
||||||
|
return nil, "", fmt.Errorf("error while retrieving operation: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Saw error polling for op, but dismissed as retriable: %s", err)
|
|
||||||
if op == nil {
|
|
||||||
return nil, "", fmt.Errorf("Cannot continue, Operation is nil. %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] working with op %#v", op)
|
|
||||||
|
|
||||||
// Try to set the operation (so we can check it's Error/State),
|
|
||||||
// and fail if we can't.
|
|
||||||
if err = w.SetOp(op); err != nil {
|
if err = w.SetOp(op); err != nil {
|
||||||
return nil, "", fmt.Errorf("Cannot continue %s", err)
|
return nil, "", fmt.Errorf("Cannot continue, unable to use operation: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail if the operation object contains an error.
|
|
||||||
if err = w.Error(); err != nil {
|
if err = w.Error(); err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
log.Printf("[DEBUG] Got %v while polling for operation %s's status", w.State(), w.OpName())
|
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Got %v while polling for operation %s's status", w.State(), w.OpName())
|
||||||
return op, w.State(), nil
|
return op, w.State(), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,18 @@ func (w *SqlAdminOperationWaiter) QueryOp() (interface{}, error) {
|
||||||
return nil, fmt.Errorf("Cannot query operation, service is nil.")
|
return nil, fmt.Errorf("Cannot query operation, service is nil.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return w.Service.Operations.Get(w.Project, w.Op.Name).Do()
|
var op interface{}
|
||||||
|
var err error
|
||||||
|
err = retryTimeDuration(
|
||||||
|
func() error {
|
||||||
|
op, err = w.Service.Operations.Get(w.Project, w.Op.Name).Do()
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
|
||||||
|
DefaultRequestTimeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
return op, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SqlAdminOperationWaiter) OpName() string {
|
func (w *SqlAdminOperationWaiter) OpName() string {
|
||||||
|
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var DefaultRequestTimeout = 5 * time.Minute
|
||||||
|
|
||||||
func isEmptyValue(v reflect.Value) bool {
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
@ -33,7 +35,7 @@ func isEmptyValue(v reflect.Value) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendRequest(config *Config, method, rawurl string, body map[string]interface{}) (map[string]interface{}, error) {
|
func sendRequest(config *Config, method, rawurl string, body map[string]interface{}) (map[string]interface{}, error) {
|
||||||
return sendRequestWithTimeout(config, method, rawurl, body, 5*time.Minute)
|
return sendRequestWithTimeout(config, method, rawurl, body, DefaultRequestTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendRequestWithTimeout(config *Config, method, rawurl string, body map[string]interface{}, timeout time.Duration) (map[string]interface{}, error) {
|
func sendRequestWithTimeout(config *Config, method, rawurl string, body map[string]interface{}, timeout time.Duration) (map[string]interface{}, error) {
|
||||||
|
|
|
@ -351,11 +351,13 @@ func retryTimeDuration(retryFunc func() error, duration time.Duration) error {
|
||||||
|
|
||||||
func isRetryableError(err error) bool {
|
func isRetryableError(err error) bool {
|
||||||
if gerr, ok := err.(*googleapi.Error); ok && (gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503) {
|
if gerr, ok := err.(*googleapi.Error); ok && (gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503) {
|
||||||
|
log.Printf("[DEBUG] Dismissed an error as retryable based on error code: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// These operations are always hitting googleapis.com - they should rarely
|
// These operations are always hitting googleapis.com - they should rarely
|
||||||
// time out, and if they do, that timeout is retryable.
|
// time out, and if they do, that timeout is retryable.
|
||||||
if urlerr, ok := err.(*url.Error); ok && urlerr.Timeout() {
|
if urlerr, ok := err.(*url.Error); ok && urlerr.Timeout() {
|
||||||
|
log.Printf("[DEBUG] Dismissed an error as retryable based on googleapis.com target: %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 409 && strings.Contains(gerr.Body, "operationInProgress") {
|
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 409 && strings.Contains(gerr.Body, "operationInProgress") {
|
||||||
|
@ -363,6 +365,7 @@ func isRetryableError(err error) bool {
|
||||||
// The only way right now to determine it is a SQL 409 due to concurrent calls is to
|
// The only way right now to determine it is a SQL 409 due to concurrent calls is to
|
||||||
// look at the contents of the error message.
|
// look at the contents of the error message.
|
||||||
// See https://github.com/terraform-providers/terraform-provider-google/issues/3279
|
// See https://github.com/terraform-providers/terraform-provider-google/issues/3279
|
||||||
|
log.Printf("[DEBUG] Dismissed an error as retryable based on error code 409 and error reason 'operationInProgress': %s", err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user