Add period for rate limiter middleware
This commit is contained in:
parent
377c219fd9
commit
6f4aefffe7
12 changed files with 197 additions and 21 deletions
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
|
@ -30,6 +31,15 @@ func TestNewRateLimiter(t *testing.T) {
|
|||
},
|
||||
expectedMaxDelay: 2500 * time.Microsecond,
|
||||
},
|
||||
{
|
||||
desc: "maxDelay computation, low rate regime",
|
||||
config: dynamic.RateLimit{
|
||||
Average: 2,
|
||||
Period: types.Duration(10 * time.Second),
|
||||
Burst: 10,
|
||||
},
|
||||
expectedMaxDelay: 500 * time.Millisecond,
|
||||
},
|
||||
{
|
||||
desc: "default SourceMatcher is remote address ip strategy",
|
||||
config: dynamic.RateLimit{
|
||||
|
@ -127,6 +137,46 @@ func TestRateLimit(t *testing.T) {
|
|||
incomingLoad: 200,
|
||||
burst: 300,
|
||||
},
|
||||
{
|
||||
desc: "lower than 1/s",
|
||||
config: dynamic.RateLimit{
|
||||
Average: 5,
|
||||
Period: types.Duration(10 * time.Second),
|
||||
},
|
||||
loadDuration: 2 * time.Second,
|
||||
incomingLoad: 100,
|
||||
burst: 0,
|
||||
},
|
||||
{
|
||||
desc: "lower than 1/s, longer",
|
||||
config: dynamic.RateLimit{
|
||||
Average: 5,
|
||||
Period: types.Duration(10 * time.Second),
|
||||
},
|
||||
loadDuration: time.Minute,
|
||||
incomingLoad: 100,
|
||||
burst: 0,
|
||||
},
|
||||
{
|
||||
desc: "lower than 1/s, longer, harsher",
|
||||
config: dynamic.RateLimit{
|
||||
Average: 1,
|
||||
Period: types.Duration(time.Minute),
|
||||
},
|
||||
loadDuration: time.Minute,
|
||||
incomingLoad: 100,
|
||||
burst: 0,
|
||||
},
|
||||
{
|
||||
desc: "period below 1 second",
|
||||
config: dynamic.RateLimit{
|
||||
Average: 50,
|
||||
Period: types.Duration(500 * time.Millisecond),
|
||||
},
|
||||
loadDuration: 2 * time.Second,
|
||||
incomingLoad: 300,
|
||||
burst: 0,
|
||||
},
|
||||
// TODO Try to disambiguate when it fails if it is because of too high a load.
|
||||
// {
|
||||
// desc: "Zero average ==> no rate limiting",
|
||||
|
@ -142,6 +192,9 @@ func TestRateLimit(t *testing.T) {
|
|||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
if test.loadDuration >= time.Minute && testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
reqCount := 0
|
||||
|
@ -152,10 +205,10 @@ func TestRateLimit(t *testing.T) {
|
|||
h, err := New(context.Background(), next, test.config, "rate-limiter")
|
||||
require.NoError(t, err)
|
||||
|
||||
period := time.Duration(1e9 / test.incomingLoad)
|
||||
loadPeriod := time.Duration(1e9 / test.incomingLoad)
|
||||
start := time.Now()
|
||||
end := start.Add(test.loadDuration)
|
||||
ticker := time.NewTicker(period)
|
||||
ticker := time.NewTicker(loadPeriod)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
if time.Now().After(end) {
|
||||
|
@ -179,6 +232,15 @@ func TestRateLimit(t *testing.T) {
|
|||
stop := time.Now()
|
||||
elapsed := stop.Sub(start)
|
||||
|
||||
burst := test.config.Burst
|
||||
if burst < 1 {
|
||||
// actual default value
|
||||
burst = 1
|
||||
}
|
||||
period := time.Duration(test.config.Period)
|
||||
if period == 0 {
|
||||
period = time.Second
|
||||
}
|
||||
if test.config.Average == 0 {
|
||||
if reqCount < 75*test.incomingLoad/100 {
|
||||
t.Fatalf("we (arbitrarily) expect at least 75%% of the requests to go through with no rate limiting, and yet only %d/%d went through", reqCount, test.incomingLoad)
|
||||
|
@ -192,7 +254,8 @@ func TestRateLimit(t *testing.T) {
|
|||
// Note that even when there is no bursty traffic,
|
||||
// we take into account the configured burst,
|
||||
// because it also helps absorbing non-bursty traffic.
|
||||
wantCount := int(test.config.Average*int64(test.loadDuration/time.Second) + test.config.Burst)
|
||||
rate := float64(test.config.Average) / float64(period)
|
||||
wantCount := int(int64(rate*float64(test.loadDuration)) + burst)
|
||||
// Allow for a 2% leeway
|
||||
maxCount := wantCount * 102 / 100
|
||||
// With very high CPU loads,
|
||||
|
@ -201,10 +264,10 @@ func TestRateLimit(t *testing.T) {
|
|||
// Feel free to adjust wrt to the load on e.g. the CI.
|
||||
minCount := wantCount * 95 / 100
|
||||
if reqCount < minCount {
|
||||
t.Fatalf("rate was slower than expected: %d requests in %v", reqCount, elapsed)
|
||||
t.Fatalf("rate was slower than expected: %d requests (wanted > %d) in %v", reqCount, minCount, elapsed)
|
||||
}
|
||||
if reqCount > maxCount {
|
||||
t.Fatalf("rate was faster than expected: %d requests in %v", reqCount, elapsed)
|
||||
t.Fatalf("rate was faster than expected: %d requests (wanted < %d) in %v", reqCount, maxCount, elapsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue