Skip to content

Commit 2d3fb7b

Browse files
authored
Merge pull request #31 from statuspal/me/spl-679-ensure-terraform-provider-complies-with-api-rate-limits
feat: add http request throttling
2 parents f2b9059 + 1ffa0c7 commit 2d3fb7b

File tree

4 files changed

+64
-0
lines changed

4 files changed

+64
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/hashicorp/terraform-plugin-go v0.25.0
1212
github.com/hashicorp/terraform-plugin-log v0.9.0
1313
github.com/hashicorp/terraform-plugin-testing v1.11.0
14+
golang.org/x/time v0.8.0
1415
)
1516

1617
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
255255
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
256256
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
257257
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
258+
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
259+
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
258260
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
259261
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
260262
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

internal/client/client.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"net/http"
77
"os"
88
"time"
9+
10+
"golang.org/x/time/rate"
911
)
1012

1113
// Client struct.
@@ -15,6 +17,15 @@ type Client struct {
1517
ApiKey string
1618
}
1719

20+
// RateLimit defines a limit of requests per second.
21+
const RateLimit = 10
22+
23+
// BurstLimit defines a value of request that can be bursted.
24+
const BurstLimit = 10
25+
26+
// RateLimiter defines the rate limit with a burst for the requests.
27+
var RateLimiter = rate.NewLimiter(BurstLimit, BurstLimit)
28+
1829
// NewClient function.
1930
func NewClient(api_key *string, region *string, test_url *string) (*Client, error) {
2031
env := os.Getenv("TF_ENV")
@@ -47,6 +58,12 @@ func NewClient(api_key *string, region *string, test_url *string) (*Client, erro
4758
}
4859

4960
func (c *Client) doRequest(req *http.Request) (*[]byte, error) {
61+
if !RateLimiter.Allow() {
62+
if err := RateLimiter.Wait(req.Context()); err != nil {
63+
return nil, fmt.Errorf("failed to wait the time required by rate limiter: %w", err)
64+
}
65+
}
66+
5067
req.Header.Set("Accept", "application/json")
5168
req.Header.Set("Content-Type", "application/json")
5269
req.Header.Set("Authorization", c.ApiKey)

internal/client/client_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package statuspal
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
"time"
9+
)
10+
11+
func TestClient_doRequest_rate_limit(t *testing.T) {
12+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13+
w.WriteHeader(http.StatusOK)
14+
}))
15+
defer server.Close()
16+
17+
client := &Client{
18+
HostURL: server.URL,
19+
HTTPClient: server.Client(),
20+
ApiKey: "test",
21+
}
22+
23+
start := time.Now()
24+
var requestsCount uint
25+
26+
// NOTE: Executes the request 100 times to have enough register to validate the request per second.
27+
for i := 0; i < 100; i++ {
28+
req, _ := http.NewRequest(http.MethodGet, client.HostURL, nil)
29+
if _, err := client.doRequest(req.WithContext(context.Background())); err != nil {
30+
t.Errorf("Request error at iteration %d: %v", i, err)
31+
}
32+
33+
requestsCount += 1
34+
}
35+
36+
requestsTime := time.Since(start).Round(time.Duration(RateLimiter.Limit()) * time.Second)
37+
38+
requestPerSecond := requestsCount / uint(requestsTime.Seconds())
39+
if requestPerSecond != uint(RateLimiter.Burst()) {
40+
t.Fatal("Requests rate doesn't meet the request rate required")
41+
}
42+
43+
t.Logf("All requests executed within the rate limit of %d per second", requestPerSecond)
44+
}

0 commit comments

Comments
 (0)