Skip to content

Commit

Permalink
add unittests for ratelimits.go file
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulait committed Sep 24, 2024
1 parent 6826a47 commit 32797c8
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 18 deletions.
3 changes: 2 additions & 1 deletion clients/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type LinodeClient interface {
LinodePlacementGroupClient
LinodeFirewallClient
LinodeTokenClient

OnAfterResponse(m func(response *resty.Response) error)
}

type AkamClient interface {
Expand Down Expand Up @@ -52,7 +54,6 @@ type LinodeInstanceClient interface {
CreateStackscript(ctx context.Context, opts linodego.StackscriptCreateOptions) (*linodego.Stackscript, error)
ListStackscripts(ctx context.Context, opts *linodego.ListOptions) ([]linodego.Stackscript, error)
GetType(ctx context.Context, typeID string) (*linodego.LinodeType, error)
OnAfterResponse(m func(response *resty.Response) error)
}

// LinodeVPCClient defines the methods that interact with Linode's VPC service.
Expand Down
9 changes: 7 additions & 2 deletions controller/linodemachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ func (r *LinodeMachineReconciler) createInstance(ctx context.Context, logger log
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))
if ctr.IsPOSTLimitReached() {
logger.Info(fmt.Sprintf("Cannot make more requests as max requests have been made. Waiting and retrying 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
}

Expand Down Expand Up @@ -317,6 +317,11 @@ func (r *LinodeMachineReconciler) reconcilePreflightCreate(ctx context.Context,
}
return retryIfTransient(err)
}

if linodeInstance == nil {
return ctrl.Result{RequeueAfter: reconciler.DefaultMachineControllerWaitForPreflightTimeout}, nil

Check warning on line 322 in controller/linodemachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controller/linodemachine_controller.go#L322

Added line #L322 was not covered by tests
}

conditions.MarkTrue(machineScope.LinodeMachine, ConditionPreflightCreated)
// Set the provider ID since the instance is successfully created
machineScope.LinodeMachine.Spec.ProviderID = util.Pointer(fmt.Sprintf("linode://%d", linodeInstance.ID))
Expand Down
12 changes: 0 additions & 12 deletions mock/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions util/ratelimits.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"sync"
"time"

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

"github.com/linode/cluster-api-provider-linode/util/reconciler"
Expand Down Expand Up @@ -63,7 +62,7 @@ func (c *PostRequestCounter) ApiResponseRatelimitCounter(resp *resty.Response) e
}

// IsPOSTLimitReached checks whether POST limits have been reached.
func (c *PostRequestCounter) IsPOSTLimitReached(logger logr.Logger) bool {
func (c *PostRequestCounter) IsPOSTLimitReached() bool {
// 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
Expand All @@ -73,7 +72,6 @@ func (c *PostRequestCounter) IsPOSTLimitReached(logger logr.Logger) bool {
}
// 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 {
// reset limits, set max allowed POST requests to default max
Expand Down
213 changes: 213 additions & 0 deletions util/ratelimits_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
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 (
"net/http"
"reflect"
"testing"

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

func TestGetPostReqCounter(t *testing.T) {
t.Parallel()
tests := []struct {
name string
tokenHash string
want *PostRequestCounter
}{
{
name: "provide hash which exists in map",
tokenHash: "abcdef",
want: &PostRequestCounter{
ReqRemaining: 5,
RefreshTime: 3,
},
},
{
name: "provide hash which doesn't exist",
tokenHash: "uvwxyz",
want: &PostRequestCounter{
ReqRemaining: reconciler.DefaultPOSTRequestLimit,
RefreshTime: 0,
},
},
}
for _, tt := range tests {

Check failure on line 52 in util/ratelimits_test.go

View workflow job for this annotation

GitHub Actions / go-analyze

Range statement for test TestGetPostReqCounter missing the call to method parallel in test Run (paralleltest)
postRequestCounters["abcdef"] = &PostRequestCounter{
ReqRemaining: reconciler.SecondaryPOSTRequestLimit,
RefreshTime: 3,
}
t.Run(tt.name, func(t *testing.T) {
if got := GetPostReqCounter(tt.tokenHash); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetPostReqCounter() = %v, want %v", got, tt.want)
}
})
}
}

func TestPostRequestCounter_IsPOSTLimitReached(t *testing.T) {
t.Parallel()
tests := []struct {
name string
fields *PostRequestCounter
want bool
}{
{
name: "not reached rate limits",
fields: &PostRequestCounter{
ReqRemaining: 7,
RefreshTime: 9999999,
},
want: false,
},
{
name: "reached rate limits",
fields: &PostRequestCounter{
ReqRemaining: reconciler.SecondaryPOSTRequestLimit,
RefreshTime: 9999999999,
},
want: true,
},
{
name: "reached rate limits",
fields: &PostRequestCounter{
ReqRemaining: 0,
RefreshTime: 9999999999,
},
want: true,
},
{
name: "refresh time smaller than current time",
fields: &PostRequestCounter{
ReqRemaining: reconciler.SecondaryPOSTRequestLimit,
RefreshTime: 1000000000,
},
want: false,
},
}
for _, tt := range tests {

Check failure on line 105 in util/ratelimits_test.go

View workflow job for this annotation

GitHub Actions / go-analyze

Range statement for test TestPostRequestCounter_IsPOSTLimitReached missing the call to method parallel in test Run (paralleltest)
t.Run(tt.name, func(t *testing.T) {
c := &PostRequestCounter{
ReqRemaining: tt.fields.ReqRemaining,
RefreshTime: tt.fields.RefreshTime,
}
if got := c.IsPOSTLimitReached(); got != tt.want {
t.Errorf("PostRequestCounter.IsPOSTLimitReached() = %v, want %v", got, tt.want)
}
})
}
}

func TestPostRequestCounter_ApiResponseRatelimitCounter(t *testing.T) {
t.Parallel()
tests := []struct {
name string
fields *PostRequestCounter
args *resty.Response
wantErr bool
}{
{
name: "not a POST call",
fields: &PostRequestCounter{
ReqRemaining: 6,
RefreshTime: 1000000000,
},
args: &resty.Response{
Request: &resty.Request{
Method: http.MethodGet,
},
},
wantErr: false,
},
{
name: "endpoint different than /linode/instances",
fields: &PostRequestCounter{
ReqRemaining: 6,
RefreshTime: 1000000000,
},
args: &resty.Response{
Request: &resty.Request{
Method: http.MethodPost,
URL: "/v4/vpc/ips",
},
},
wantErr: false,
},
{
name: "no headers in response",
fields: &PostRequestCounter{
ReqRemaining: 6,
RefreshTime: 1000000000,
},
args: &resty.Response{
Request: &resty.Request{
Method: http.MethodPost,
URL: "/v4/linode/instances",
},
},
wantErr: true,
},
{
name: "missing one value in response header",
fields: &PostRequestCounter{
ReqRemaining: 6,
RefreshTime: 1000000000,
},
args: &resty.Response{
Request: &resty.Request{
Method: http.MethodPost,
URL: "/v4/linode/instances",
},
RawResponse: &http.Response{
Header: http.Header{"X-Ratelimit-Remaining": []string{"10"}},
},
},
wantErr: true,
},
{
name: "correct headers in response",
fields: &PostRequestCounter{
ReqRemaining: 6,
RefreshTime: 1000000000,
},
args: &resty.Response{
Request: &resty.Request{
Method: http.MethodPost,
URL: "/v4/linode/instances",
},
RawResponse: &http.Response{
Header: http.Header{"X-Ratelimit-Remaining": []string{"10"}, "X-Ratelimit-Reset": []string{"10"}},
},
},
wantErr: false,
},
}
for _, tt := range tests {

Check failure on line 202 in util/ratelimits_test.go

View workflow job for this annotation

GitHub Actions / go-analyze

Range statement for test TestPostRequestCounter_ApiResponseRatelimitCounter missing the call to method parallel in test Run (paralleltest)
t.Run(tt.name, func(t *testing.T) {
c := &PostRequestCounter{
ReqRemaining: tt.fields.ReqRemaining,
RefreshTime: tt.fields.RefreshTime,
}
if err := c.ApiResponseRatelimitCounter(tt.args); (err != nil) != tt.wantErr {
t.Errorf("PostRequestCounter.ApiResponseRatelimitCounter() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

0 comments on commit 32797c8

Please sign in to comment.