diff --git a/util/ratelimits.go b/util/ratelimits.go index 5d88ced00..a7c4dd281 100644 --- a/util/ratelimits.go +++ b/util/ratelimits.go @@ -23,6 +23,7 @@ import ( "sync" "time" + "github.com/go-logr/logr" "github.com/go-resty/resty/v2" ) @@ -46,15 +47,40 @@ func (c *PostRequestCounter) ApiResponseRatelimitCounter(resp *resty.Response) e return nil } - var err error - c.ReqRemaining, err = strconv.Atoi(resp.Header().Get("X-Ratelimit-Remaining")) - if err != nil { - return err + var ( + err error + logger = logr.FromContextOrDiscard(resp.Request.Context()) + now = time.Now().Add(15 * time.Second).Unix() + epochTime int64 + // Linode creation limits ref: https://techdocs.akamai.com/linode-api/reference/rate-limits#specific-operation-rate-limits + // Creating Linodes has a dedicated rate limit of 20 requests per 15 seconds. + instancesPostDefaultLimit = 20 + reqRemaining int + rateLimitRemainingHeader = resp.Header().Get("X-Ratelimit-Remaining") + rateLimitResetHeader = resp.Header().Get("X-Ratelimit-Reset") + ) + + if rateLimitRemainingHeader == "" { + logger.Info("missing X-Ratelimit-Remaining header, using default value") + reqRemaining = instancesPostDefaultLimit - 1 + } else { + reqRemaining, err = strconv.Atoi(rateLimitRemainingHeader) + if err != nil { + logger.Error(err, "Failed to parse X-Ratelimit-Remaining header, using default value") + reqRemaining = instancesPostDefaultLimit - 1 + } } + c.ReqRemaining = reqRemaining - epochTime, err := strconv.ParseInt(resp.Header().Get("X-Ratelimit-Reset"), 10, 64) - if err != nil { - return err + if rateLimitResetHeader == "" { + logger.Info("missing X-Ratelimit-Reset header, using default value", "X-Ratelimit-Reset", now) + epochTime = now + } else { + epochTime, err = strconv.ParseInt(rateLimitResetHeader, 10, 64) + if err != nil { + logger.Error(err, "Failed to parse X-Ratelimit-Reset header, using default value") + epochTime = now + } } c.RefreshTime = time.Unix(epochTime, 0) return nil diff --git a/util/ratelimits_test.go b/util/ratelimits_test.go index e648e27b6..b4892d74a 100644 --- a/util/ratelimits_test.go +++ b/util/ratelimits_test.go @@ -149,7 +149,7 @@ func TestPostRequestCounter_ApiResponseRatelimitCounter(t *testing.T) { wantErr: false, }, { - name: "no headers in response", + name: "no headers in response, expect default values", fields: &PostRequestCounter{ ReqRemaining: 4, RefreshTime: now, @@ -159,8 +159,11 @@ func TestPostRequestCounter_ApiResponseRatelimitCounter(t *testing.T) { Method: http.MethodPost, URL: "/v4/linode/instances", }, + RawResponse: &http.Response{ + Header: http.Header{"X-Ratelimit-Remaining": []string{"19"}, "X-Ratelimit-Reset": []string{strconv.Itoa(int(time.Now().Add(15 * time.Second).Unix()))}}, + }, }, - wantErr: true, + wantErr: false, }, { name: "missing one value in response header", @@ -174,10 +177,10 @@ func TestPostRequestCounter_ApiResponseRatelimitCounter(t *testing.T) { URL: "/v4/linode/instances", }, RawResponse: &http.Response{ - Header: http.Header{"X-Ratelimit-Remaining": []string{"5"}}, + Header: http.Header{"X-Ratelimit-Remaining": []string{"5"}, "X-Ratelimit-Reset": []string{strconv.Itoa(int(time.Now().Add(15 * time.Second).Unix()))}}, }, }, - wantErr: true, + wantErr: false, }, { name: "correct headers in response",