700c693b33
* 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>
112 lines
2.9 KiB
Go
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())
|
|
})
|
|
}
|