Skip to content

Commit

Permalink
move rate-limits related functions to separate file and simplify logic
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulait committed Sep 23, 2024
1 parent c256d9c commit c16e2c6
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 96 deletions.
10 changes: 10 additions & 0 deletions cloud/scope/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package scope

import (
"context"
"crypto/md5"

Check failure on line 5 in cloud/scope/common.go

View workflow job for this annotation

GitHub Actions / go-analyze

G501: Blocklisted import crypto/md5: weak cryptographic primitive (gosec)
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"net/http"
Expand All @@ -16,6 +18,7 @@ import (
"github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/session"
"github.com/linode/linodego"
corev1 "k8s.io/api/core/v1"
hashutil "k8s.io/kubernetes/pkg/util/hash"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

Expand Down Expand Up @@ -187,3 +190,10 @@ func toFinalizer(obj client.Object) string {
}
return fmt.Sprintf("%s.%s/%s.%s", kind, group, namespace, name)
}

// GetMD5Hash returns md5 hash of input string
func GetMD5Hash(key string) string {
hasher := md5.New()

Check failure on line 196 in cloud/scope/common.go

View workflow job for this annotation

GitHub Actions / go-analyze

G401: Use of weak cryptographic primitive (gosec)
hashutil.DeepHashObject(hasher, key)
return hex.EncodeToString(hasher.Sum(nil)[0:])
}
2 changes: 2 additions & 0 deletions cloud/scope/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type MachineScope struct {
PatchHelper *patch.Helper
Cluster *clusterv1.Cluster
Machine *clusterv1.Machine
TokenHash string
LinodeClient LinodeClient
LinodeCluster *infrav1alpha2.LinodeCluster
LinodeMachine *infrav1alpha2.LinodeMachine
Expand Down Expand Up @@ -71,6 +72,7 @@ func NewMachineScope(ctx context.Context, linodeClientConfig ClientConfig, param
PatchHelper: helper,
Cluster: params.Cluster,
Machine: params.Machine,
TokenHash: GetMD5Hash(linodeClientConfig.Token),
LinodeClient: linodeClient,
LinodeCluster: params.LinodeCluster,
LinodeMachine: params.LinodeMachine,
Expand Down
7 changes: 3 additions & 4 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,9 @@ spec:
# More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources:
limits:
cpu: 500m
memory: 512Mi
memory: 2Gi
requests:
cpu: 10m
memory: 128Mi
cpu: 1000m
memory: 512Mi
serviceAccountName: controller-manager
terminationGracePeriodSeconds: 10
95 changes: 20 additions & 75 deletions controller/linodemachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,10 @@ import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"

"github.com/go-logr/logr"
"github.com/go-resty/resty/v2"
"github.com/linode/linodego"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -86,16 +83,6 @@ var requeueInstanceStatuses = map[linodego.InstanceStatus]bool{
linodego.InstanceResizing: true,
}

type PostRequestCounter struct {
reqRemaining int
refreshTime int
}

var (
mu sync.RWMutex
tokenMap = make(map[string]*PostRequestCounter, 0)
)

// LinodeMachineReconciler reconciles a LinodeMachine object
type LinodeMachineReconciler struct {
client.Client
Expand Down Expand Up @@ -291,6 +278,23 @@ func (r *LinodeMachineReconciler) reconcileCreate(
return ctrl.Result{}, nil
}

// createInstance provisions linode instance after checking if the request will be within the rate-limits
// Note: it takes a lock before checking for the rate limits and releases it after making request to linode API or when returning from function
func (r *LinodeMachineReconciler) createInstance(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope, createOpts *linodego.InstanceCreateOptions) (*linodego.Instance, error, bool) {
ctr := util.GetPostReqCounter(machineScope.TokenHash)
ctr.Mu.Lock()
defer ctr.Mu.Unlock()

if ctr.IsPOSTLimitReached(logger) {
logger.Info(fmt.Sprintf("reached linode API rate limit, retry after %v seconds", reconciler.SecondaryLinodeTooManyPOSTRequestsErrorRetryDelay))
return nil, nil, true

Check warning on line 290 in controller/linodemachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controller/linodemachine_controller.go#L289-L290

Added lines #L289 - L290 were not covered by tests
}

machineScope.LinodeClient.OnAfterResponse(ctr.ApiResponseRatelimitCounter)
linodeInstance, err := machineScope.LinodeClient.CreateInstance(ctx, *createOpts)
return linodeInstance, err, false
}

func (r *LinodeMachineReconciler) reconcilePreflightCreate(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope) (ctrl.Result, error) {
// get the bootstrap data for the Linode instance and set it for create config
createOpts, err := newCreateConfig(ctx, machineScope, logger)
Expand All @@ -299,16 +303,11 @@ func (r *LinodeMachineReconciler) reconcilePreflightCreate(ctx context.Context,
return retryIfTransient(err)
}

mu.Lock()
if isPOSTLimitReached(r.LinodeClientConfig.Token, logger) {
mu.Unlock()
return ctrl.Result{RequeueAfter: 15 * time.Second}, nil
linodeInstance, err, skipAndRetry := r.createInstance(ctx, logger, machineScope, createOpts)
if skipAndRetry {
return ctrl.Result{RequeueAfter: reconciler.SecondaryLinodeTooManyPOSTRequestsErrorRetryDelay}, nil

Check warning on line 308 in controller/linodemachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controller/linodemachine_controller.go#L308

Added line #L308 was not covered by tests
}

machineScope.LinodeClient.OnAfterResponse(r.apiResponseRatelimitCounter)
linodeInstance, err := machineScope.LinodeClient.CreateInstance(ctx, *createOpts)
mu.Unlock()

if err != nil {
logger.Error(err, "Failed to create Linode machine instance")
if reconciler.RecordDecayingCondition(machineScope.LinodeMachine,
Expand Down Expand Up @@ -522,57 +521,3 @@ func (r *LinodeMachineReconciler) SetupWithManager(mgr ctrl.Manager, options crc
func (r *LinodeMachineReconciler) TracedClient() client.Client {
return wrappedruntimeclient.NewRuntimeClientWithTracing(r.Client, wrappedruntimeclient.DefaultDecorator())
}

func (r *LinodeMachineReconciler) apiResponseRatelimitCounter(resp *resty.Response) error {
if resp.Request.Method != "POST" || !strings.HasSuffix(resp.Request.URL, "/linode/instances") {
return nil
}

postReqCtr, exists := tokenMap[r.LinodeClientConfig.Token]
if !exists {
postReqCtr = &PostRequestCounter{
reqRemaining: reconciler.DefaultPOSTRequestLimit,
refreshTime: 0,
}
tokenMap[r.LinodeClientConfig.Token] = postReqCtr
}

var err error
postReqCtr.reqRemaining, err = strconv.Atoi(resp.Header().Get("X-Ratelimit-Remaining"))
if err != nil {
return err
}

postReqCtr.refreshTime, err = strconv.Atoi(resp.Header().Get("X-Ratelimit-Reset"))
if err != nil {
return err
}
return nil
}

func isPOSTLimitReached(token string, logger logr.Logger) bool {
postReqCtr, exists := tokenMap[token]
if !exists {
postReqCtr = &PostRequestCounter{
reqRemaining: reconciler.DefaultPOSTRequestLimit,
refreshTime: 0,
}
tokenMap[token] = postReqCtr
}

logger.Info(fmt.Sprintf("Requests Remaining: %v, Refresh Time: %v, currentTime: %v", postReqCtr.reqRemaining, postReqCtr.refreshTime, time.Now().Unix()))
if postReqCtr.reqRemaining == reconciler.SecondaryPOSTRequestLimit || postReqCtr.reqRemaining == 0 {
actualRefreshTime := postReqCtr.refreshTime
if postReqCtr.reqRemaining == reconciler.SecondaryPOSTRequestLimit {
actualRefreshTime = postReqCtr.refreshTime - int(reconciler.SecondaryLinodeTooManyPOSTRequestsErrorRetryDelay.Seconds())
}
if time.Now().Unix() <= int64(actualRefreshTime) {
logger.Info("Cannot make more requests as max requests have been made. Waiting and retrying ...")
return true
} else if postReqCtr.reqRemaining == 0 {
// reset limits, set max allowed POST requests to default max
postReqCtr.reqRemaining = reconciler.DefaultPOSTRequestLimit
}
}
return false
}
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
k8s.io/api v0.30.3
k8s.io/apimachinery v0.30.3
k8s.io/client-go v0.30.3
k8s.io/utils v0.0.0-20240102154912-e7106e64919e
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/cluster-api v1.8.0
sigs.k8s.io/controller-runtime v0.18.5
)
Expand All @@ -45,9 +45,9 @@ require (
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/go-resty/resty/v2 v2.13.1 // indirect
github.com/go-resty/resty/v2 v2.13.1
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gobuffalo/flect v1.0.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
Expand Down Expand Up @@ -111,8 +111,9 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.30.3 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/kubernetes v1.31.1
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
Expand Down
29 changes: 16 additions & 13 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ github.com/akamai/AkamaiOPEN-edgegrid-golang/v8 v8.4.0 h1:zZJimNqkV3o7qZqBnprKyH
github.com/akamai/AkamaiOPEN-edgegrid-golang/v8 v8.4.0/go.mod h1:2xRRnHx8dnw0i8IZPYOI0I7xbr1gnAN1uIYo7acMIbg=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
Expand Down Expand Up @@ -64,8 +64,9 @@ github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
Expand All @@ -82,8 +83,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto=
github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand Down Expand Up @@ -215,8 +216,8 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.55.0 h1:1oZYcP3wuazG3O1563m8cs
go.opentelemetry.io/contrib/bridges/prometheus v0.55.0/go.mod h1:sU48aWFqiqBXo2RBtq7KarczkW8uK6RdIU54y4VzpZs=
go.opentelemetry.io/contrib/exporters/autoexport v0.55.0 h1:8kNP8SX9id5TY2feLB+79aFxE0kqzh3KvjF1nAfGxVM=
go.opentelemetry.io/contrib/exporters/autoexport v0.55.0/go.mod h1:WhcvzeuTOr58aYsJ7S4ubY1xMs0WXAPaqTQnxr8bRHk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.6.0 h1:WYsDPt0fM4KZaMhLvY+x6TVXd85P/KNl3Ez3t+0+kGs=
Expand Down Expand Up @@ -402,14 +403,16 @@ k8s.io/cluster-bootstrap v0.30.3 h1:MgxyxMkpaC6mu0BKWJ8985XCOnKU+eH3Iy+biwtDXRk=
k8s.io/cluster-bootstrap v0.30.3/go.mod h1:h8BoLDfdD7XEEIXy7Bx9FcMzxHwz29jsYYi34bM5DKU=
k8s.io/component-base v0.30.3 h1:Ci0UqKWf4oiwy8hr1+E3dsnliKnkMLZMVbWzeorlk7s=
k8s.io/component-base v0.30.3/go.mod h1:C1SshT3rGPCuNtBs14RmVD2xW0EhRSeLvBh7AGk1quA=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ=
k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.0 h1:Tc9rS7JJoZ9sl3OpL4842oIk6lH7gWBb0JOmJ0ute7M=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.0/go.mod h1:1ewhL9l1gkPcU/IU/6rFYfikf+7Y5imWv7ARVbBOzNs=
k8s.io/kubernetes v1.31.1 h1:1fcYJe8SAhtannpChbmnzHLwAV9Je99PrGaFtBvCxms=
k8s.io/kubernetes v1.31.1/go.mod h1:/YGPL//Fb9mdv5vukvAQ7Xon+Bqwry52bmjTdORAw+Q=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/cluster-api v1.8.0 h1:xdF9svGCbezxOn9Y6QmlVnNaZ0n9QkRJpNuKJkeorUw=
sigs.k8s.io/cluster-api v1.8.0/go.mod h1:iSUcU8rHBNRa6wZJvU6klHKI3EVQC0aMcgjeSofBwKw=
sigs.k8s.io/controller-runtime v0.18.5 h1:nTHio/W+Q4aBlQMgbnC5hZb4IjIidyrizMai9P6n4Rk=
Expand Down
99 changes: 99 additions & 0 deletions util/ratelimits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
Copyright 2023 Akamai Technologies, Inc.
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 util

import (
"strconv"
"strings"
"sync"
"time"

"github.com/go-logr/logr"
"github.com/go-resty/resty/v2"

"github.com/linode/cluster-api-provider-linode/util/reconciler"
)

// PostRequestCounter keeps track of rate limits for POST to /linode/instances
type PostRequestCounter struct {
Mu sync.RWMutex
ReqRemaining int
RefreshTime int
}

var (
// mu is global lock to coordinate access to shared resource postRequestCounters
mu sync.RWMutex
// postRequestCounters stores token hash and pointer to its equivalent PostRequestCounter
postRequestCounters = make(map[string]*PostRequestCounter, 0)
)

// ApiResponseRatelimitCounter updates ReqRemaining and RefreshTime when a POST call is made to /linode/instances
func (c *PostRequestCounter) ApiResponseRatelimitCounter(resp *resty.Response) error {
if resp.Request.Method != "POST" || !strings.HasSuffix(resp.Request.URL, "/linode/instances") {

Check failure on line 47 in util/ratelimits.go

View workflow job for this annotation

GitHub Actions / go-analyze

"POST" can be replaced by http.MethodPost (usestdlibvars)
return nil

Check warning on line 48 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L46-L48

Added lines #L46 - L48 were not covered by tests
}

var err error
c.ReqRemaining, err = strconv.Atoi(resp.Header().Get("X-Ratelimit-Remaining"))
if err != nil {
return err

Check warning on line 54 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L51-L54

Added lines #L51 - L54 were not covered by tests
}

c.RefreshTime, err = strconv.Atoi(resp.Header().Get("X-Ratelimit-Reset"))
if err != nil {
return err

Check warning on line 59 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L57-L59

Added lines #L57 - L59 were not covered by tests
}
return nil

Check warning on line 61 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L61

Added line #L61 was not covered by tests
}

// IsPOSTLimitReached checks whether POST limits have been reached.
func (c *PostRequestCounter) IsPOSTLimitReached(logger logr.Logger) bool {

Check warning on line 65 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L65

Added line #L65 was not covered by tests
// TODO: Once linode API adjusts rate-limits, remove secondary rate limit and simplify accordingly
if c.ReqRemaining == reconciler.SecondaryPOSTRequestLimit || c.ReqRemaining == 0 {
actualRefreshTime := c.RefreshTime

Check warning on line 68 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L67-L68

Added lines #L67 - L68 were not covered by tests
// adjust refresh time based on secondary limit
if c.ReqRemaining == reconciler.SecondaryPOSTRequestLimit {
actualRefreshTime = c.RefreshTime - int(reconciler.SecondaryLinodeTooManyPOSTRequestsErrorRetryDelay.Seconds())

Check warning on line 71 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L70-L71

Added lines #L70 - L71 were not covered by tests
}
// check if refresh time has passed
if time.Now().Unix() <= int64(actualRefreshTime) {
logger.Info("Cannot make more requests as max requests have been made. Waiting and retrying ...")
return true
} else if c.ReqRemaining == 0 {

Check warning on line 77 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L74-L77

Added lines #L74 - L77 were not covered by tests
// reset limits, set max allowed POST requests to default max
c.ReqRemaining = reconciler.DefaultPOSTRequestLimit

Check warning on line 79 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L79

Added line #L79 was not covered by tests
}
}
return false

Check warning on line 82 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L82

Added line #L82 was not covered by tests
}

// GetPostReqCounter returns pointer to PostRequestCounter for a given token hash
func GetPostReqCounter(tokenHash string) *PostRequestCounter {
mu.Lock()
defer mu.Unlock()

Check warning on line 88 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L86-L88

Added lines #L86 - L88 were not covered by tests

ctr, exists := postRequestCounters[tokenHash]
if !exists {
ctr = &PostRequestCounter{
ReqRemaining: reconciler.DefaultPOSTRequestLimit,
RefreshTime: 0,

Check warning on line 94 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L90-L94

Added lines #L90 - L94 were not covered by tests
}
postRequestCounters[tokenHash] = ctr

Check warning on line 96 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L96

Added line #L96 was not covered by tests
}
return ctr

Check warning on line 98 in util/ratelimits.go

View check run for this annotation

Codecov / codecov/patch

util/ratelimits.go#L98

Added line #L98 was not covered by tests
}

0 comments on commit c16e2c6

Please sign in to comment.