open-consul/agent/grpc-middleware/rate_test.go
John Murret 700c693b33
adding config for request_limits (#15531)
* server: add placeholder glue for rate limit handler

This commit adds a no-op implementation of the rate-limit handler and
adds it to the `consul.Server` struct and setup code.

This allows us to start working on the net/rpc and gRPC interceptors and
config logic.

* Add handler errors

* Set the global read and write limits

* fixing multilimiter moving packages

* Fix typo

* Simplify globalLimit usage

* add multilimiter and tests

* exporting LimitedEntity

* Apply suggestions from code review

Co-authored-by: John Murret <john.murret@hashicorp.com>

* add config update and rename config params

* add doc string and split config

* Apply suggestions from code review

Co-authored-by: Dan Upton <daniel@floppy.co>

* use timer to avoid go routine leak and change the interface

* add comments to tests

* fix failing test

* add prefix with config edge, refactor tests

* Apply suggestions from code review

Co-authored-by: Dan Upton <daniel@floppy.co>

* refactor to apply configs for limiters under a prefix

* add fuzz tests and fix bugs found. Refactor reconcile loop to have a simpler logic

* make KeyType an exported type

* split the config and limiter trees to fix race conditions in config update

* rename variables

* fix race in test and remove dead code

* fix reconcile loop to not create a timer on each loop

* add extra benchmark tests and fix tests

* fix benchmark test to pass value to func

* server: add placeholder glue for rate limit handler

This commit adds a no-op implementation of the rate-limit handler and
adds it to the `consul.Server` struct and setup code.

This allows us to start working on the net/rpc and gRPC interceptors and
config logic.

* Set the global read and write limits

* fixing multilimiter moving packages

* add server configuration for global rate limiting.

* remove agent test

* remove added stuff from handler

* remove added stuff from multilimiter

* removing unnecessary TODOs

* Removing TODO comment from handler

* adding in defaulting to infinite

* add disabled status in there

* adding in documentation for disabled mode.

* make disabled the default.

* Add mock and agent test

* addig documentation and missing mock file.

* Fixing test TestLoad_IntegrationWithFlags

* updating docs based on PR feedback.

* Updating Request Limits mode to use int based on PR feedback.

* Adding RequestLimits struct so we have a nested struct in ReloadableConfig.

* fixing linting references

* Update agent/consul/rate/handler.go

Co-authored-by: Dan Upton <daniel@floppy.co>

* Update agent/consul/config.go

Co-authored-by: Dan Upton <daniel@floppy.co>

* removing the ignore of the request limits in JSON.  addingbuilder logic to convert any read rate or write rate less than 0 to rate.Inf

* added conversion function to convert request limits object to handler config.

* Updating docs to reflect gRPC and RPC are rate limit and as a result, HTTP requests are as well.

* Updating values for TestLoad_FullConfig() so that they were different and discernable.

* Updating TestRuntimeConfig_Sanitize

* Fixing TestLoad_IntegrationWithFlags test

* putting nil check in place

* fixing rebase

* removing change for missing error checks.  will put in another PR

* Rebasing after default multilimiter config change

* resolving rebase issues

* updating reference for incomingRPCLimiter to use interface

* updating interface

* Updating interfaces

* Fixing mock reference

Co-authored-by: Daniel Upton <daniel@floppy.co>
Co-authored-by: Dhia Ayachi <dhia@hashicorp.com>
2022-12-13 13:09:55 -07:00

112 lines
2.9 KiB
Go

package middleware
import (
"context"
"errors"
"net"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/consul/agent/consul/rate"
)
func TestServerRateLimiterMiddleware_Integration(t *testing.T) {
limiter := rate.NewMockRequestLimitsHandler(t)
server := grpc.NewServer(
grpc.InTapHandle(ServerRateLimiterMiddleware(limiter, NewPanicHandler(hclog.NewNullLogger()))),
)
server.RegisterService(&healthpb.Health_ServiceDesc, health.NewServer())
lis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() {
if err := lis.Close(); err != nil {
t.Logf("failed to close listener: %v", err)
}
})
go server.Serve(lis)
t.Cleanup(server.Stop)
conn, err := grpc.Dial(
lis.Addr().String(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
require.NoError(t, err)
t.Cleanup(func() {
if err := conn.Close(); err != nil {
t.Logf("failed to close client connection: %v", err)
}
})
client := healthpb.NewHealthClient(conn)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
t.Run("ErrRetryElsewhere = ResourceExhausted", func(t *testing.T) {
limiter.On("Allow", mock.Anything).
Run(func(args mock.Arguments) {
op := args.Get(0).(rate.Operation)
require.Equal(t, "/grpc.health.v1.Health/Check", op.Name)
addr := op.SourceAddr.(*net.TCPAddr)
require.True(t, addr.IP.IsLoopback())
}).
Return(rate.ErrRetryElsewhere).
Once()
_, err = client.Check(ctx, &healthpb.HealthCheckRequest{})
require.Error(t, err)
require.Equal(t, codes.ResourceExhausted.String(), status.Code(err).String())
})
t.Run("ErrRetryLater = Unavailable", func(t *testing.T) {
limiter.On("Allow", mock.Anything).
Return(rate.ErrRetryLater).
Once()
_, err = client.Check(ctx, &healthpb.HealthCheckRequest{})
require.Error(t, err)
require.Equal(t, codes.Unavailable.String(), status.Code(err).String())
})
t.Run("unexpected error", func(t *testing.T) {
limiter.On("Allow", mock.Anything).
Return(errors.New("uh oh")).
Once()
_, err = client.Check(ctx, &healthpb.HealthCheckRequest{})
require.Error(t, err)
require.Equal(t, codes.Internal.String(), status.Code(err).String())
})
t.Run("operation allowed", func(t *testing.T) {
limiter.On("Allow", mock.Anything).
Return(nil).
Once()
_, err = client.Check(ctx, &healthpb.HealthCheckRequest{})
require.NoError(t, err)
})
t.Run("Allow panics", func(t *testing.T) {
limiter.On("Allow", mock.Anything).
Panic("uh oh").
Once()
_, err = client.Check(ctx, &healthpb.HealthCheckRequest{})
require.Error(t, err)
require.Equal(t, codes.Internal.String(), status.Code(err).String())
})
}