From dac7f7dab12e0f7317d6bb8e25cb737960eb6e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20W=C3=B6hrl?= Date: Fri, 30 Aug 2024 21:10:43 +0200 Subject: [PATCH 1/2] feat: allow defining a rate limit to call the newrelic api --- go.mod | 1 + go.sum | 2 ++ newrelic/config.go | 24 +++++++++++++++++++++++- newrelic/provider.go | 6 ++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d792b94de..8513f6394 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,7 @@ require ( golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect google.golang.org/grpc v1.57.1 // indirect diff --git a/go.sum b/go.sum index bc8fd481f..05986eacf 100644 --- a/go.sum +++ b/go.sum @@ -481,6 +481,8 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/newrelic/config.go b/newrelic/config.go index 01eb9a3a9..8a2321d94 100644 --- a/newrelic/config.go +++ b/newrelic/config.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" "github.com/mitchellh/go-homedir" + "golang.org/x/time/rate" insights "github.com/newrelic/go-insights/client" nr "github.com/newrelic/newrelic-client-go/v2/newrelic" @@ -36,8 +37,27 @@ type Config struct { SyntheticsAPIURL string userAgent string serviceName string + MaxRequestsPerSecond int } +type ThrottledRoundTripper struct{ + original http.RoundTripper + ratelimiter *rate.Limiter +} + +func NewThrottledRoundTripper(rt http.RoundTripper, limiter *rate.Limiter) *ThrottledRoundTripper { + return &ThrottledRoundTripper{ + original: rt, + ratelimiter: limiter, + } +} + +func (t *ThrottledRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + t.ratelimiter.Wait(req.Context()) + return t.original.RoundTrip(req) +} + + // Client returns a new client for accessing New Relic func (c *Config) Client() (*nr.NewRelic, error) { options := []nr.ConfigOption{} @@ -75,7 +95,9 @@ func (c *Config) Client() (*nr.NewRelic, error) { t = logging.NewTransport("newrelic", t) } - options = append(options, nr.ConfigHTTPTransport(t)) + rateLimiter := rate.NewLimiter(rate.Limit(c.MaxRequestsPerSecond), c.MaxRequestsPerSecond) + throttledTransport := NewThrottledRoundTripper(t, rateLimiter) + options = append(options, nr.ConfigHTTPTransport(throttledTransport)) if c.APIURL != "" { options = append(options, nr.ConfigBaseURL(c.APIURL)) diff --git a/newrelic/provider.go b/newrelic/provider.go index dfeecd2f6..c9bd06903 100755 --- a/newrelic/provider.go +++ b/newrelic/provider.go @@ -117,6 +117,11 @@ func Provider() *schema.Provider { Optional: true, DefaultFunc: schema.EnvDefaultFunc("NEW_RELIC_API_CACERT", ""), }, + "max_requests_per_second": { + Type: schema.TypeInt, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("NEW_RELIC_MAX_REQUESTS_PER_SECOND", 600), + }, }, DataSourcesMap: map[string]*schema.Resource{ @@ -245,6 +250,7 @@ func providerConfigure(data *schema.ResourceData, terraformVersion string) (inte InsecureSkipVerify: data.Get("insecure_skip_verify").(bool), CACertFile: data.Get("cacert_file").(string), serviceName: userAgentServiceName, + MaxRequestsPerSecond: data.Get("max_requests_per_second").(int), } log.Println("[INFO] Initializing newrelic-client-go") From 70ac50a81619a3bf7b7656f00b5b2648f0a69b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20W=C3=B6hrl?= Date: Fri, 30 Aug 2024 22:56:08 +0200 Subject: [PATCH 2/2] feat: add max concurrency control --- newrelic/config.go | 60 +++++++++++++++++++++++++------------------- newrelic/provider.go | 34 ++++++++++++++----------- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/newrelic/config.go b/newrelic/config.go index 8a2321d94..87ac6ae99 100644 --- a/newrelic/config.go +++ b/newrelic/config.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" "github.com/mitchellh/go-homedir" + "golang.org/x/sync/semaphore" "golang.org/x/time/rate" insights "github.com/newrelic/go-insights/client" @@ -21,43 +22,51 @@ import ( // Config contains New Relic provider settings type Config struct { - AdminAPIKey string - PersonalAPIKey string - Region string - APIURL string - CACertFile string - InfrastructureAPIURL string - InsecureSkipVerify bool - InsightsAccountID string - InsightsInsertKey string - InsightsInsertURL string - InsightsQueryKey string - InsightsQueryURL string - NerdGraphAPIURL string - SyntheticsAPIURL string - userAgent string - serviceName string - MaxRequestsPerSecond int + AdminAPIKey string + PersonalAPIKey string + Region string + APIURL string + CACertFile string + InfrastructureAPIURL string + InsecureSkipVerify bool + InsightsAccountID string + InsightsInsertKey string + InsightsInsertURL string + InsightsQueryKey string + InsightsQueryURL string + NerdGraphAPIURL string + SyntheticsAPIURL string + userAgent string + serviceName string + MaxRequestsPerSecond int + MaxConcurrentRequests int } -type ThrottledRoundTripper struct{ - original http.RoundTripper +type ThrottledRoundTripper struct { + original http.RoundTripper ratelimiter *rate.Limiter + concurrency *semaphore.Weighted } -func NewThrottledRoundTripper(rt http.RoundTripper, limiter *rate.Limiter) *ThrottledRoundTripper { +func NewThrottledRoundTripper(rt http.RoundTripper, maxRequestsPerSecond int, maxConcurrentRequests int) *ThrottledRoundTripper { return &ThrottledRoundTripper{ - original: rt, - ratelimiter: limiter, + original: rt, + ratelimiter: rate.NewLimiter(rate.Limit(maxRequestsPerSecond), maxRequestsPerSecond), + concurrency: semaphore.NewWeighted(int64(maxConcurrentRequests)), } } func (t *ThrottledRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - t.ratelimiter.Wait(req.Context()) + ctx := req.Context() + + if err := t.concurrency.Acquire(ctx, 1); err != nil { + return nil, err + } + defer t.concurrency.Release(1) + t.ratelimiter.Wait(ctx) return t.original.RoundTrip(req) } - // Client returns a new client for accessing New Relic func (c *Config) Client() (*nr.NewRelic, error) { options := []nr.ConfigOption{} @@ -95,8 +104,7 @@ func (c *Config) Client() (*nr.NewRelic, error) { t = logging.NewTransport("newrelic", t) } - rateLimiter := rate.NewLimiter(rate.Limit(c.MaxRequestsPerSecond), c.MaxRequestsPerSecond) - throttledTransport := NewThrottledRoundTripper(t, rateLimiter) + throttledTransport := NewThrottledRoundTripper(t, c.MaxRequestsPerSecond, c.MaxConcurrentRequests) options = append(options, nr.ConfigHTTPTransport(throttledTransport)) if c.APIURL != "" { diff --git a/newrelic/provider.go b/newrelic/provider.go index c9bd06903..8ad9073bb 100755 --- a/newrelic/provider.go +++ b/newrelic/provider.go @@ -118,10 +118,15 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("NEW_RELIC_API_CACERT", ""), }, "max_requests_per_second": { - Type: schema.TypeInt, - Optional: true, + Type: schema.TypeInt, + Optional: true, DefaultFunc: schema.EnvDefaultFunc("NEW_RELIC_MAX_REQUESTS_PER_SECOND", 600), }, + "max_concurrent_requests": { + Type: schema.TypeInt, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("NEW_RELIC_MAX_CONCURRENT_REQUESTS", 25), + }, }, DataSourcesMap: map[string]*schema.Resource{ @@ -239,18 +244,19 @@ func providerConfigure(data *schema.ResourceData, terraformVersion string) (inte log.Printf("[INFO] UserAgent: %s", userAgent) cfg := Config{ - AdminAPIKey: adminAPIKey, - PersonalAPIKey: personalAPIKey, - Region: data.Get("region").(string), - APIURL: data.Get("api_url").(string), - SyntheticsAPIURL: data.Get("synthetics_api_url").(string), - NerdGraphAPIURL: data.Get("nerdgraph_api_url").(string), - InfrastructureAPIURL: getInfraAPIURL(data), - userAgent: userAgent, - InsecureSkipVerify: data.Get("insecure_skip_verify").(bool), - CACertFile: data.Get("cacert_file").(string), - serviceName: userAgentServiceName, - MaxRequestsPerSecond: data.Get("max_requests_per_second").(int), + AdminAPIKey: adminAPIKey, + PersonalAPIKey: personalAPIKey, + Region: data.Get("region").(string), + APIURL: data.Get("api_url").(string), + SyntheticsAPIURL: data.Get("synthetics_api_url").(string), + NerdGraphAPIURL: data.Get("nerdgraph_api_url").(string), + InfrastructureAPIURL: getInfraAPIURL(data), + userAgent: userAgent, + InsecureSkipVerify: data.Get("insecure_skip_verify").(bool), + CACertFile: data.Get("cacert_file").(string), + serviceName: userAgentServiceName, + MaxRequestsPerSecond: data.Get("max_requests_per_second").(int), + MaxConcurrentRequests: data.Get("max_concurrent_requests").(int), } log.Println("[INFO] Initializing newrelic-client-go")