tools/mc2bq/pkg/gapiutil/gapiutil.go (64 lines of code) (raw):
// Copyright 2023 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gapiutil
import (
"errors"
"net/http"
"time"
"github.com/GoogleCloudPlatform/migrationcenter-utils/tools/mc2bq/pkg/backoff"
"google.golang.org/api/googleapi"
)
// DefaultBackoff is the recommended backoff as documented in https://cloud.google.com/apis/design/errors#retrying_errors
var DefaultBackoff = backoff.Backoff{
Duration: 1 * time.Second,
Factor: 1.2,
Jitter: 0.5,
Cap: 10 * time.Second,
}
func IsErrorWithCode(err error, code int) bool {
if err == nil {
return false
}
var gapiError *googleapi.Error
if !errors.As(err, &gapiError) {
// it's not a GAPI error
return false
}
return gapiError.Code == code
}
func IgnoreErrorWithCode(err error, code int) error {
if IsErrorWithCode(err, code) {
return nil
}
return err
}
// IsTransientError checks if a result of a GAPI call is a transient error.
func IsTransientError(err error) bool {
if err == nil {
return false
}
var gapiError *googleapi.Error
if !errors.As(err, &gapiError) {
return false
}
// See https://cloud.google.com/storage/docs/xml-api/reference-status
switch gapiError.Code {
case http.StatusTooManyRequests,
http.StatusRequestTimeout,
http.StatusInternalServerError,
http.StatusServiceUnavailable,
http.StatusGatewayTimeout:
return true
}
return false
}
// RetryGAPI calls fn with opts, it retries on transient network errors
// but not on GAPI errors
func RetryGAPI[T any](
backoff backoff.Backoff,
fn func(opts ...googleapi.CallOption) (T, error),
opts ...googleapi.CallOption,
) (T, error) {
var r T
var err error
for {
r, err = fn(opts...)
if IsTransientError(err) {
time.Sleep(backoff.Step())
continue
}
return r, err
}
}