diff --git a/cluster-autoscaler/cloudprovider/ovhcloud/sdk/error.go b/cluster-autoscaler/cloudprovider/ovhcloud/sdk/error.go index 65e77760175a..c39045cb7e3f 100644 --- a/cluster-autoscaler/cloudprovider/ovhcloud/sdk/error.go +++ b/cluster-autoscaler/cloudprovider/ovhcloud/sdk/error.go @@ -17,9 +17,14 @@ limitations under the License. package sdk import ( + "errors" "fmt" + "net/http" + "strings" ) +const canadianTenantSyncErrorMessage = "Internal Server Error" + // APIError represents an error that can occurred while calling the API. type APIError struct { // Error message. @@ -48,3 +53,13 @@ type ( func (e Error) Error() string { return fmt.Sprintf("%d: %s", e.StatusCode, e.Message) } + +// IsPossiblyCanadianTenantSyncError returns whether the given error and URL could be due to the tenant being canadian and too recent. +// This is a temporary fix until the issue is correctly handled +func IsPossiblyCanadianTenantSyncError(err error, url string) bool { + var apiError *APIError + return (strings.HasPrefix(url, OvhEU) || strings.HasPrefix(url, "https://api.ovh.com/1.0")) && + errors.As(err, &apiError) && + apiError.Code == http.StatusInternalServerError && + apiError.Message == canadianTenantSyncErrorMessage +} diff --git a/cluster-autoscaler/cloudprovider/ovhcloud/sdk/ovh.go b/cluster-autoscaler/cloudprovider/ovhcloud/sdk/ovh.go index 7be99711cbbe..e31438654171 100644 --- a/cluster-autoscaler/cloudprovider/ovhcloud/sdk/ovh.go +++ b/cluster-autoscaler/cloudprovider/ovhcloud/sdk/ovh.go @@ -258,7 +258,6 @@ func (c *Client) DeleteUnAuthWithContext(ctx context.Context, url string, result // timeDelta returns the time delta between the host and the remote API func (c *Client) getTimeDelta() (time.Duration, error) { - if !c.timeDeltaDone { // Ensure only one thread is updating c.timeDeltaMutex.Lock() @@ -434,12 +433,35 @@ func (c *Client) CallAPIWithContext(ctx context.Context, method, path string, re if err != nil { return err } + req = req.WithContext(ctx) response, err := c.Do(req) if err != nil { return err } - return c.UnmarshalResponse(response, result) + + err = c.UnmarshalResponse(response, result) + if err != nil { + // An error 500 on api.ovh.com could be due to the tenant being canadian and too recent, so let's retry on ca.api.ovh. + // This is a temporary fix until the issue is correctly handled + if IsPossiblyCanadianTenantSyncError(err, req.URL.String()) { + // Create a canadian API client with the same token + client, err2 := NewClient(OvhCA, "none", "none", "none") + if err2 != nil { + return fmt.Errorf("failed to create canadian ovh API client for fallback: %w", err2) + } + client.openStackToken = c.openStackToken + + // Execute the same call on ca.api.ovh.com and ignore the potential error, we will return the original one + err2 = client.CallAPIWithContext(ctx, method, path, reqBody, result, queryParams, headers, needAuth) + if err2 == nil { + // OK on the canadian API, our job is done + return nil + } + } + } + + return err } // UnmarshalResponse checks the response and unmarshals it into the response