X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/579f6139c9c03fe1b08993746c87aa525ff314e9..2376a805daae84c5e899f190485d7d7ac96e3f20:/sdk/go/arvadosclient/arvadosclient.go diff --git a/sdk/go/arvadosclient/arvadosclient.go b/sdk/go/arvadosclient/arvadosclient.go index a5d5a02219..57f81c2c86 100644 --- a/sdk/go/arvadosclient/arvadosclient.go +++ b/sdk/go/arvadosclient/arvadosclient.go @@ -32,6 +32,8 @@ var ErrInvalidArgument = errors.New("Invalid argument") // such failures by always using a new or recently active socket. var MaxIdleConnectionDuration = 30 * time.Second +var RetryDelay = 2 * time.Second + // Indicates an error that was returned by the API server. type APIServerError struct { // Address of server returning error, of the form "host:port". @@ -84,6 +86,12 @@ type ArvadosClient struct { // the client is outside the cluster. External bool + // Base URIs of Keep services, e.g., {"https://host1:8443", + // "https://host2:8443"}. If this is nil, Keep clients will + // use the arvados.v1.keep_services.accessible API to discover + // available services. + KeepServiceURIs []string + // Discovery document DiscoveryDoc Dict @@ -93,9 +101,10 @@ type ArvadosClient struct { Retries int } -// Create a new ArvadosClient, initialized with standard Arvados environment -// variables ARVADOS_API_HOST, ARVADOS_API_TOKEN, and (optionally) -// ARVADOS_API_HOST_INSECURE. +// MakeArvadosClient creates a new ArvadosClient using the standard +// environment variables ARVADOS_API_HOST, ARVADOS_API_TOKEN, +// ARVADOS_API_HOST_INSECURE, ARVADOS_EXTERNAL_CLIENT, and +// ARVADOS_KEEP_SERVICES. func MakeArvadosClient() (ac ArvadosClient, err error) { var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$") insecure := matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")) @@ -111,6 +120,10 @@ func MakeArvadosClient() (ac ArvadosClient, err error) { External: external, Retries: 2} + if s := os.Getenv("ARVADOS_KEEP_SERVICES"); s != "" { + ac.KeepServiceURIs = strings.Split(s, " ") + } + if ac.ApiServer == "" { return ac, MissingArvadosApiHost } @@ -161,14 +174,26 @@ func (c ArvadosClient) CallRaw(method string, resourceType string, uuid string, } } + retryable := false + switch method { + case "GET", "HEAD", "PUT", "OPTIONS", "DELETE": + retryable = true + } + + // Non-retryable methods such as POST are not safe to retry automatically, + // so we minimize such failures by always using a new or recently active socket + if !retryable { + if time.Since(c.lastClosedIdlesAt) > MaxIdleConnectionDuration { + c.lastClosedIdlesAt = time.Now() + c.Client.Transport.(*http.Transport).CloseIdleConnections() + } + } + // Make the request - remainingTries := 1 + c.Retries var req *http.Request var resp *http.Response - var errs []string - var badResp bool - for remainingTries > 0 { + for attempt := 0; attempt <= c.Retries; attempt++ { if method == "GET" || method == "HEAD" { u.RawQuery = vals.Encode() if req, err = http.NewRequest(method, u.String(), nil); err != nil { @@ -187,21 +212,10 @@ func (c ArvadosClient) CallRaw(method string, resourceType string, uuid string, req.Header.Add("X-External-Client", "1") } - // POST and DELETE are not safe to retry automatically, so we minimize - // such failures by always using a new or recently active socket - if method == "POST" || method == "DELETE" { - if time.Since(c.lastClosedIdlesAt) > MaxIdleConnectionDuration { - c.lastClosedIdlesAt = time.Now() - c.Client.Transport.(*http.Transport).CloseIdleConnections() - } - } - resp, err = c.Client.Do(req) if err != nil { - if method == "GET" || method == "HEAD" || method == "PUT" { - errs = append(errs, err.Error()) - badResp = false - remainingTries -= 1 + if retryable { + time.Sleep(RetryDelay) continue } else { return nil, err @@ -214,27 +228,19 @@ func (c ArvadosClient) CallRaw(method string, resourceType string, uuid string, defer resp.Body.Close() - if resp.StatusCode == 408 || - resp.StatusCode == 409 || - resp.StatusCode == 422 || - resp.StatusCode == 423 || - resp.StatusCode == 500 || - resp.StatusCode == 502 || - resp.StatusCode == 503 || - resp.StatusCode == 504 { - badResp = true - remainingTries -= 1 + switch resp.StatusCode { + case 408, 409, 422, 423, 500, 502, 503, 504: + time.Sleep(RetryDelay) continue - } else { + default: return nil, newAPIServerError(c.ApiServer, resp) } } - if badResp { + if resp != nil { return nil, newAPIServerError(c.ApiServer, resp) - } else { - return nil, fmt.Errorf("%v", errs) } + return nil, err } func newAPIServerError(ServerAddress string, resp *http.Response) APIServerError { @@ -278,7 +284,7 @@ func newAPIServerError(ServerAddress string, resp *http.Response) APIServerError // Returns a non-nil error if an error occurs making the API call, the // API responds with a non-successful HTTP status, or an error occurs // parsing the response body. -func (c ArvadosClient) Call(method string, resourceType string, uuid string, action string, parameters Dict, output interface{}) error { +func (c ArvadosClient) Call(method, resourceType, uuid, action string, parameters Dict, output interface{}) error { reader, err := c.CallRaw(method, resourceType, uuid, action, parameters) if reader != nil { defer reader.Close()