From a4d008454618582cb7a8689e9eadaa4936a2c736 Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Mon, 29 Apr 2024 14:35:09 +0200 Subject: [PATCH 1/5] Add TickerRateLimiter Apparently this is a good enough method "up to tens of operations per second" per https://go.dev/wiki/RateLimiting. --- pkg/cloud/ratelimit.go | 37 +++++++++++++++++++++++++++++++++++++ pkg/cloud/ratelimit_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/pkg/cloud/ratelimit.go b/pkg/cloud/ratelimit.go index 3af4796e..35aea380 100644 --- a/pkg/cloud/ratelimit.go +++ b/pkg/cloud/ratelimit.go @@ -112,3 +112,40 @@ func (m *MinimumRateLimiter) Accept(ctx context.Context, key *RateLimitKey) erro func (m *MinimumRateLimiter) Observe(ctx context.Context, err error, key *RateLimitKey) { m.RateLimiter.Observe(ctx, err, key) } + +// TickerRateLimiter uses time.Ticker to spread Accepts over time. +// +// Concurrent calls to Accept will block on the same channel. It is not +// guaranteed what caller will be unblocked first. +type TickerRateLimiter struct { + ticker *time.Ticker +} + +// NewTickerRateLimiter creates a new TickerRateLimiter which will space Accept +// calls at least interval/limit time apart. +// +// For example, limit=4 interval=time.Minute will unblock a single Accept call +// every 15 seconds. +func NewTickerRateLimiter(limit int, interval time.Duration) *TickerRateLimiter { + return &TickerRateLimiter{ + ticker: time.NewTicker(interval / time.Duration(limit)), + } +} + +// Accept will block until a time, specified when creating TickerRateLimiter, +// passes since the last call to Accept. +func (t *TickerRateLimiter) Accept(ctx context.Context, rlk *RateLimitKey) error { + select { + case <-t.ticker.C: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// Observe does nothing. +func (*TickerRateLimiter) Observe(context.Context, error, *RateLimitKey) { +} + +// Make sure that TickerRateLimiter implements RateLimiter. +var _ RateLimiter = new(TickerRateLimiter) diff --git a/pkg/cloud/ratelimit_test.go b/pkg/cloud/ratelimit_test.go index ce110419..21198e3c 100644 --- a/pkg/cloud/ratelimit_test.go +++ b/pkg/cloud/ratelimit_test.go @@ -82,3 +82,33 @@ func TestMinimumRateLimiter(t *testing.T) { t.Errorf("`called` = true, want false") } } + +func TestTickerRateLimiter(t *testing.T) { + t.Parallel() + + trl := NewTickerRateLimiter(100, time.Second) + start := time.Now() + for i := 0; i < 50; i++ { + err := trl.Accept(context.Background(), nil) + if err != nil { + t.Errorf("TickerRateLimiter.Accept = %v, want nil", err) + } + } + elapsed := time.Since(start) + if elapsed > time.Second { + t.Errorf("TickerRateLimiter.Accept took too long: %v, want <1s", elapsed) + } + if elapsed < 500*time.Millisecond { + t.Errorf("TickerRateLimiter.Accept took too short: %v, want >500ms", elapsed) + } + + // Use context that has been cancelled and expect a context error returned. + ctxCancelled, cancelled := context.WithCancel(context.Background()) + cancelled() + // Verify context is cancelled by now. + <-ctxCancelled.Done() + err := trl.Accept(ctxCancelled, nil) + if err != ctxCancelled.Err() { + t.Errorf("TickerRateLimiter.Accept() = %v, want %v", err, ctxCancelled.Err()) + } +} From 890775f2e6c2a845053232b89ffb33ca59466502 Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Tue, 30 Apr 2024 09:51:36 +0200 Subject: [PATCH 2/5] Add CompositeRateLimiter --- pkg/cloud/ratelimit.go | 108 +++++++++++++++++++++++++++++++++ pkg/cloud/ratelimit_test.go | 116 ++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) diff --git a/pkg/cloud/ratelimit.go b/pkg/cloud/ratelimit.go index 35aea380..14ed943d 100644 --- a/pkg/cloud/ratelimit.go +++ b/pkg/cloud/ratelimit.go @@ -149,3 +149,111 @@ func (*TickerRateLimiter) Observe(context.Context, error, *RateLimitKey) { // Make sure that TickerRateLimiter implements RateLimiter. var _ RateLimiter = new(TickerRateLimiter) + +// CompositeRateLimiter combines rate limiters based on RateLimitKey. +type CompositeRateLimiter struct { + // map[project id]map[resource name]map[operation name]RateLimiter + rateLimiters map[string]map[string]map[string]RateLimiter + // defaultRL is used when no matching RateLimiter was found. + defaultRL RateLimiter +} + +// NewCompositeRateLimiter creates a new CompositeRateLimiter that will use +// provided default rate limiter if no better match is found. +// +// # Example +// +// defaultRL := /* default rate limiter */ +// bsGetListRL := /* backend service rate limiter for get and list operation in project-1 */ +// projectRL := /* rate limiter for project-1 */ +// bsOtherProjectRL := /* rate limiter for backend service in other projects */ +// +// rl := NewCompositeRateLimiter(defaultRL) +// rl.Register("project-1", "", "", projectRL) +// rl.Register("project-1", "BackendServices", "Get", bsGetListRL) +// rl.Register("project-1", "BackendServices", "List", bsGetListRL) +// rl.Register("", "BackendServices", "", bsOtherProjectRL) +// +// This rate limiter is not nesting. Only one rate limiter is used for any +// particular combination of: project, resource, operation. For the case above, +// rate limiter registered at ("project-1", "", "") won't be applied to +// operation ("project-1", "BackendServices", "Get"), because a more specific +// rate limiter was registered. +func NewCompositeRateLimiter(defaultRL RateLimiter) *CompositeRateLimiter { + m := map[string]map[string]map[string]RateLimiter{ + "": { + "": { + "": defaultRL, + }, + }, + } + return &CompositeRateLimiter{ + rateLimiters: m, + defaultRL: defaultRL, + } +} + +// ensureExists creates sub-maps as needed. +func (c *CompositeRateLimiter) ensureExists(project, service string) { + if _, ok := c.rateLimiters[project]; !ok { + c.rateLimiters[project] = map[string]map[string]RateLimiter{} + } + if _, ok := c.rateLimiters[project][service]; !ok { + c.rateLimiters[project][service] = map[string]RateLimiter{} + } +} + +// fillMissing finds all combinations where resource and/or operation name +// could be omitted and sets it to defaultRL. +func (c *CompositeRateLimiter) fillMissing() { + for _, subProject := range c.rateLimiters { + if subProject[""] == nil { + subProject[""] = map[string]RateLimiter{} + } + for _, subService := range subProject { + if subService[""] == nil { + subService[""] = c.defaultRL + } + } + } +} + +// Register adds provided rl to the composite rate limiter. Any/all of project, +// service, operation can be omitted by providing an empty string. In this +// case, the provided rate limiter will be used only when there is no other +// rate limiter matching a particular project, resource, or operaiton. +// +// It replaces previous rate limiter provided for the same project, service, +// operation combination. Once a rate limiter is added, it can't be removed. +// +// Same rate limiter can be used for multiple Register calls. +func (c *CompositeRateLimiter) Register(project, service, operation string, rl RateLimiter) { + c.ensureExists(project, service) + c.rateLimiters[project][service][operation] = rl + c.fillMissing() +} + +// Accept either calls underlying rate limiter matching rlk or a default rate +// limiter when none is found. +func (c *CompositeRateLimiter) Accept(ctx context.Context, rlk *RateLimitKey) error { + if rlk == nil { + return c.defaultRL.Accept(ctx, rlk) + } + project := rlk.ProjectID + if _, ok := c.rateLimiters[project]; !ok { + project = "" + } + service := rlk.Service + if _, ok := c.rateLimiters[project][service]; !ok { + service = "" + } + operation := rlk.Operation + if _, ok := c.rateLimiters[project][service][operation]; !ok { + operation = "" + } + return c.rateLimiters[project][service][operation].Accept(ctx, rlk) +} + +// Observe does nothing. +func (*CompositeRateLimiter) Observe(context.Context, error, *RateLimitKey) { +} diff --git a/pkg/cloud/ratelimit_test.go b/pkg/cloud/ratelimit_test.go index 21198e3c..2bdf402a 100644 --- a/pkg/cloud/ratelimit_test.go +++ b/pkg/cloud/ratelimit_test.go @@ -112,3 +112,119 @@ func TestTickerRateLimiter(t *testing.T) { t.Errorf("TickerRateLimiter.Accept() = %v, want %v", err, ctxCancelled.Err()) } } + +func TestCompositeRateLimiter(t *testing.T) { + t.Parallel() + + var calledA bool + fa := &FakeAcceptor{accept: func() { calledA = true }} + arl := &AcceptRateLimiter{fa} + rl := NewCompositeRateLimiter(arl) + + // Call default. + err := rl.Accept(context.Background(), nil) + if err != nil { + t.Errorf("CompositeRateLimiter.Accept = %v, want nil", err) + } + if !calledA { + t.Errorf("`calledA` = false, want true") + } + + calledA = false + calledB := false + fb := &FakeAcceptor{accept: func() { calledB = true }} + brl := &AcceptRateLimiter{fb} + rl.Register("projectB", "", "", brl) + + // Call registered rate limiter. + err = rl.Accept(context.Background(), &CallContextKey{ProjectID: "projectB"}) + if err != nil { + t.Errorf("CompositeRateLimiter.Accept = %v, want nil", err) + } + if !calledB { + t.Errorf("`calledB` = false, want true") + } + if calledA { + t.Errorf("`calledA` = true, want false") + } + + calledB = false + // Call default rate limiter when registered is not found + err = rl.Accept(context.Background(), &CallContextKey{ProjectID: "project-does-not-exist"}) + if err != nil { + t.Errorf("CompositeRateLimiter.Accept = %v, want nil", err) + } + if !calledA { + t.Errorf("`calledA` = false, want true") + } + if calledB { + t.Errorf("`calledB` = true, want false") + } + + calledA = false + calledC := false + fc := &FakeAcceptor{accept: func() { calledC = true }} + crl := &AcceptRateLimiter{fc} + rl.Register("", "networks", "", crl) + + // Call rate limiter for network service when no project was specified + err = rl.Accept(context.Background(), &CallContextKey{ProjectID: "project-does-not-exist", Service: "networks"}) + if err != nil { + t.Errorf("CompositeRateLimiter.Accept = %v, want nil", err) + } + if !calledC { + t.Errorf("`calledC` = false, want true") + } + if calledA { + t.Errorf("`calledA` = true, want false") + } + if calledB { + t.Errorf("`calledB` = true, want false") + } +} + +type CountingRateLimiter int + +func (crl *CountingRateLimiter) Accept(_ context.Context, key *CallContextKey) error { + *crl++ + return nil +} + +func (*CountingRateLimiter) Observe(context.Context, error, *RateLimitKey) {} + +func TestCompositeRateLimiter_Table(t *testing.T) { + t.Parallel() + + def := new(CountingRateLimiter) + rl := NewCompositeRateLimiter(def) + projectBnets := new(CountingRateLimiter) + rl.Register("projectB", "networks", "", projectBnets) + defNetGets := new(CountingRateLimiter) + rl.Register("", "networks", "get", defNetGets) + + for _, project := range []string{"", "projectB", "project-does-not-exist"} { + for _, service := range []string{"", "networks", "service-does-not-exist"} { + for _, operation := range []string{"", "get", "operation-does-not-exist"} { + key := &CallContextKey{ + ProjectID: project, + Service: service, + Operation: operation, + } + err := rl.Accept(context.Background(), key) + if err != nil { + t.Errorf("CompositeRateLimiter.Accept = %v, want nil", err) + } + } + } + } + + if *def != 22 { + t.Errorf("def served %d calls, want = 22", *def) + } + if *projectBnets != 3 { + t.Errorf("projectBnets served %d calls, want = 3", *projectBnets) + } + if *defNetGets != 2 { + t.Errorf("def served %d calls, want = 2", *defNetGets) + } +} From af30761b78923d76f842d95a4ef19bd2f8e5770b Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Tue, 30 Apr 2024 11:05:04 +0200 Subject: [PATCH 3/5] Use CompositeRateLimiter in e2e tests --- e2e/testmain_test.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/e2e/testmain_test.go b/e2e/testmain_test.go index 06979272..d248544b 100644 --- a/e2e/testmain_test.go +++ b/e2e/testmain_test.go @@ -98,8 +98,26 @@ func TestMain(m *testing.M) { } } client := oauth2.NewClient(ctx, ts) + mrl := &cloud.MinimumRateLimiter{RateLimiter: &cloud.NopRateLimiter{}, Minimum: 50 * time.Millisecond} - svc, err := cloud.NewService(ctx, client, &cloud.SingleProjectRouter{ID: testFlags.project}, mrl) + crl := cloud.NewCompositeRateLimiter(mrl) + + // The default limit is 1500 per minute. Leave 200 buffer. + computeRL := cloud.NewTickerRateLimiter(1300, time.Minute) + crl.Register("", "HealthChecks", "", computeRL) + crl.Register("", "BackendServices", "", computeRL) + crl.Register("", "NetworkEndpointGroups", "", computeRL) + + // The default limit is 1200 per minute. Leave 200 buffer. + networkServicesRL := cloud.NewTickerRateLimiter(1000, time.Minute) + crl.Register("", "TcpRoutes", "", networkServicesRL) + crl.Register("", "Meshes", "", networkServicesRL) + + // To ensure minimum time between operations, wrap the network services rate limiter. + orl := &cloud.MinimumRateLimiter{RateLimiter: networkServicesRL, Minimum: 100 * time.Millisecond} + crl.Register("", "Operations", "", orl) + + svc, err := cloud.NewService(ctx, client, &cloud.SingleProjectRouter{ID: testFlags.project}, crl) if err != nil { log.Fatal(err) } From affd31df78d9c6a6b6abf9a8ffb612ffdc61fad9 Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Mon, 6 May 2024 09:42:07 +0200 Subject: [PATCH 4/5] Fix channel leak when using time.After() It affects Go versions before 1.23. --- pkg/cloud/ratelimit.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/cloud/ratelimit.go b/pkg/cloud/ratelimit.go index 14ed943d..10e791aa 100644 --- a/pkg/cloud/ratelimit.go +++ b/pkg/cloud/ratelimit.go @@ -100,10 +100,12 @@ type MinimumRateLimiter struct { // Accept blocks on the minimum duration and context. Once the minimum duration is met, // the func is blocked on the underlying ratelimiter. func (m *MinimumRateLimiter) Accept(ctx context.Context, key *RateLimitKey) error { + t := time.NewTimer(m.Minimum) select { - case <-time.After(m.Minimum): + case <-t.C: return m.RateLimiter.Accept(ctx, key) case <-ctx.Done(): + t.Stop() return ctx.Err() } } From 0f65d14e9ebba58f8620a5d3e65a4e38d2525884 Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Tue, 7 May 2024 10:22:37 +0200 Subject: [PATCH 5/5] Remove project argument from CompositeRateLimiter --- e2e/testmain_test.go | 12 +++--- pkg/cloud/ratelimit.go | 82 +++++++++++++++---------------------- pkg/cloud/ratelimit_test.go | 30 +++++++------- 3 files changed, 54 insertions(+), 70 deletions(-) diff --git a/e2e/testmain_test.go b/e2e/testmain_test.go index d248544b..ea28074d 100644 --- a/e2e/testmain_test.go +++ b/e2e/testmain_test.go @@ -104,18 +104,18 @@ func TestMain(m *testing.M) { // The default limit is 1500 per minute. Leave 200 buffer. computeRL := cloud.NewTickerRateLimiter(1300, time.Minute) - crl.Register("", "HealthChecks", "", computeRL) - crl.Register("", "BackendServices", "", computeRL) - crl.Register("", "NetworkEndpointGroups", "", computeRL) + crl.Register("HealthChecks", "", computeRL) + crl.Register("BackendServices", "", computeRL) + crl.Register("NetworkEndpointGroups", "", computeRL) // The default limit is 1200 per minute. Leave 200 buffer. networkServicesRL := cloud.NewTickerRateLimiter(1000, time.Minute) - crl.Register("", "TcpRoutes", "", networkServicesRL) - crl.Register("", "Meshes", "", networkServicesRL) + crl.Register("TcpRoutes", "", networkServicesRL) + crl.Register("Meshes", "", networkServicesRL) // To ensure minimum time between operations, wrap the network services rate limiter. orl := &cloud.MinimumRateLimiter{RateLimiter: networkServicesRL, Minimum: 100 * time.Millisecond} - crl.Register("", "Operations", "", orl) + crl.Register("Operations", "", orl) svc, err := cloud.NewService(ctx, client, &cloud.SingleProjectRouter{ID: testFlags.project}, crl) if err != nil { diff --git a/pkg/cloud/ratelimit.go b/pkg/cloud/ratelimit.go index 10e791aa..631827ee 100644 --- a/pkg/cloud/ratelimit.go +++ b/pkg/cloud/ratelimit.go @@ -154,39 +154,35 @@ var _ RateLimiter = new(TickerRateLimiter) // CompositeRateLimiter combines rate limiters based on RateLimitKey. type CompositeRateLimiter struct { - // map[project id]map[resource name]map[operation name]RateLimiter - rateLimiters map[string]map[string]map[string]RateLimiter + // map[resource name]map[operation name]RateLimiter + rateLimiters map[string]map[string]RateLimiter // defaultRL is used when no matching RateLimiter was found. defaultRL RateLimiter } // NewCompositeRateLimiter creates a new CompositeRateLimiter that will use -// provided default rate limiter if no better match is found. +// provided default rate limiter if no better match is found. It is intended to +// be used for a single project. // // # Example // -// defaultRL := /* default rate limiter */ -// bsGetListRL := /* backend service rate limiter for get and list operation in project-1 */ -// projectRL := /* rate limiter for project-1 */ -// bsOtherProjectRL := /* rate limiter for backend service in other projects */ +// bsDefaultRL := /* backend service default rate limiter */ +// bsGetListRL := /* backend service rate limiter for get and list operations */ // // rl := NewCompositeRateLimiter(defaultRL) -// rl.Register("project-1", "", "", projectRL) -// rl.Register("project-1", "BackendServices", "Get", bsGetListRL) -// rl.Register("project-1", "BackendServices", "List", bsGetListRL) -// rl.Register("", "BackendServices", "", bsOtherProjectRL) +// rl.Register("BackendServices", "", bsDefaultRL) +// rl.Register("BackendServices", "Get", bsGetListRL) +// rl.Register("BackendServices", "List", bsGetListRL) // // This rate limiter is not nesting. Only one rate limiter is used for any -// particular combination of: project, resource, operation. For the case above, -// rate limiter registered at ("project-1", "", "") won't be applied to -// operation ("project-1", "BackendServices", "Get"), because a more specific -// rate limiter was registered. +// particular combination of: resource, operation. For the case above, rate +// limiter registered at ("BackendServices", "") won't be applied to operation +// ("BackendServices", "Get"), because a more specific rate limiter was +// registered. func NewCompositeRateLimiter(defaultRL RateLimiter) *CompositeRateLimiter { - m := map[string]map[string]map[string]RateLimiter{ + m := map[string]map[string]RateLimiter{ "": { - "": { - "": defaultRL, - }, + "": defaultRL, }, } return &CompositeRateLimiter{ @@ -196,42 +192,34 @@ func NewCompositeRateLimiter(defaultRL RateLimiter) *CompositeRateLimiter { } // ensureExists creates sub-maps as needed. -func (c *CompositeRateLimiter) ensureExists(project, service string) { - if _, ok := c.rateLimiters[project]; !ok { - c.rateLimiters[project] = map[string]map[string]RateLimiter{} - } - if _, ok := c.rateLimiters[project][service]; !ok { - c.rateLimiters[project][service] = map[string]RateLimiter{} +func (c *CompositeRateLimiter) ensureExists(service string) { + if _, ok := c.rateLimiters[service]; !ok { + c.rateLimiters[service] = map[string]RateLimiter{} } } // fillMissing finds all combinations where resource and/or operation name // could be omitted and sets it to defaultRL. func (c *CompositeRateLimiter) fillMissing() { - for _, subProject := range c.rateLimiters { - if subProject[""] == nil { - subProject[""] = map[string]RateLimiter{} - } - for _, subService := range subProject { - if subService[""] == nil { - subService[""] = c.defaultRL - } + for _, subService := range c.rateLimiters { + if subService[""] == nil { + subService[""] = c.defaultRL } } } -// Register adds provided rl to the composite rate limiter. Any/all of project, -// service, operation can be omitted by providing an empty string. In this -// case, the provided rate limiter will be used only when there is no other -// rate limiter matching a particular project, resource, or operaiton. +// Register adds provided rl to the composite rate limiter. Service, operation +// can be omitted by providing an empty string. In this case, the provided rate +// limiter will be used only when there is no other rate limiter matching a +// particular resource, or operation. // -// It replaces previous rate limiter provided for the same project, service, -// operation combination. Once a rate limiter is added, it can't be removed. +// It replaces previous rate limiter provided for the same service, operation +// combination. Once a rate limiter is added, it can't be removed. // // Same rate limiter can be used for multiple Register calls. -func (c *CompositeRateLimiter) Register(project, service, operation string, rl RateLimiter) { - c.ensureExists(project, service) - c.rateLimiters[project][service][operation] = rl +func (c *CompositeRateLimiter) Register(service, operation string, rl RateLimiter) { + c.ensureExists(service) + c.rateLimiters[service][operation] = rl c.fillMissing() } @@ -241,19 +229,15 @@ func (c *CompositeRateLimiter) Accept(ctx context.Context, rlk *RateLimitKey) er if rlk == nil { return c.defaultRL.Accept(ctx, rlk) } - project := rlk.ProjectID - if _, ok := c.rateLimiters[project]; !ok { - project = "" - } service := rlk.Service - if _, ok := c.rateLimiters[project][service]; !ok { + if _, ok := c.rateLimiters[service]; !ok { service = "" } operation := rlk.Operation - if _, ok := c.rateLimiters[project][service][operation]; !ok { + if _, ok := c.rateLimiters[service][operation]; !ok { operation = "" } - return c.rateLimiters[project][service][operation].Accept(ctx, rlk) + return c.rateLimiters[service][operation].Accept(ctx, rlk) } // Observe does nothing. diff --git a/pkg/cloud/ratelimit_test.go b/pkg/cloud/ratelimit_test.go index 2bdf402a..dcf22189 100644 --- a/pkg/cloud/ratelimit_test.go +++ b/pkg/cloud/ratelimit_test.go @@ -134,10 +134,10 @@ func TestCompositeRateLimiter(t *testing.T) { calledB := false fb := &FakeAcceptor{accept: func() { calledB = true }} brl := &AcceptRateLimiter{fb} - rl.Register("projectB", "", "", brl) + rl.Register("Meshes", "", brl) // Call registered rate limiter. - err = rl.Accept(context.Background(), &CallContextKey{ProjectID: "projectB"}) + err = rl.Accept(context.Background(), &CallContextKey{Service: "Meshes"}) if err != nil { t.Errorf("CompositeRateLimiter.Accept = %v, want nil", err) } @@ -150,7 +150,7 @@ func TestCompositeRateLimiter(t *testing.T) { calledB = false // Call default rate limiter when registered is not found - err = rl.Accept(context.Background(), &CallContextKey{ProjectID: "project-does-not-exist"}) + err = rl.Accept(context.Background(), &CallContextKey{Service: "service-does-not-exist"}) if err != nil { t.Errorf("CompositeRateLimiter.Accept = %v, want nil", err) } @@ -165,10 +165,10 @@ func TestCompositeRateLimiter(t *testing.T) { calledC := false fc := &FakeAcceptor{accept: func() { calledC = true }} crl := &AcceptRateLimiter{fc} - rl.Register("", "networks", "", crl) + rl.Register("", "Get", crl) // Call rate limiter for network service when no project was specified - err = rl.Accept(context.Background(), &CallContextKey{ProjectID: "project-does-not-exist", Service: "networks"}) + err = rl.Accept(context.Background(), &CallContextKey{ProjectID: "project-does-not-exist", Service: "Networks", Operation: "Get"}) if err != nil { t.Errorf("CompositeRateLimiter.Accept = %v, want nil", err) } @@ -197,10 +197,10 @@ func TestCompositeRateLimiter_Table(t *testing.T) { def := new(CountingRateLimiter) rl := NewCompositeRateLimiter(def) - projectBnets := new(CountingRateLimiter) - rl.Register("projectB", "networks", "", projectBnets) - defNetGets := new(CountingRateLimiter) - rl.Register("", "networks", "get", defNetGets) + defNetRL := new(CountingRateLimiter) + rl.Register("networks", "", defNetRL) + getNetRL := new(CountingRateLimiter) + rl.Register("networks", "get", getNetRL) for _, project := range []string{"", "projectB", "project-does-not-exist"} { for _, service := range []string{"", "networks", "service-does-not-exist"} { @@ -218,13 +218,13 @@ func TestCompositeRateLimiter_Table(t *testing.T) { } } - if *def != 22 { - t.Errorf("def served %d calls, want = 22", *def) + if *def != 18 { + t.Errorf("def served %d calls, want = 18", *def) } - if *projectBnets != 3 { - t.Errorf("projectBnets served %d calls, want = 3", *projectBnets) + if *defNetRL != 6 { + t.Errorf("defNetRL served %d calls, want = 6", *defNetRL) } - if *defNetGets != 2 { - t.Errorf("def served %d calls, want = 2", *defNetGets) + if *getNetRL != 3 { + t.Errorf("getNetRL served %d calls, want = 3", *getNetRL) } }