Skip to content

Commit 207d3ee

Browse files
authored
limits: allow block user by negative rate limit (#423)
* limits: allow block user by negative rate limit * add proxy test cases
1 parent e34dec1 commit 207d3ee

File tree

5 files changed

+38
-7
lines changed

5 files changed

+38
-7
lines changed

config/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ max_concurrent_queries: <int> | optional | default = 0
262262
max_execution_time: <duration> | optional | default = 120s
263263

264264
# Maximum number of requests per minute for user.
265-
# By default there are no per-minute limits
265+
# By default there are no per-minute limits.
266+
# A negative value would effectively block the user.
266267
requests_per_minute: <int> | optional | default = 0
267268

268269
# The burst of request packet size token bucket for user

config/config.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,8 @@ type User struct {
733733

734734
// Maximum number of requests per minute for user
735735
// if omitted or zero - no limits would be applied
736-
ReqPerMin uint32 `yaml:"requests_per_minute,omitempty"`
736+
// if negative - the user is effectively blocked
737+
ReqPerMin int32 `yaml:"requests_per_minute,omitempty"`
737738

738739
// The burst of request packet size token bucket for user
739740
// if omitted or zero - no limits would be applied
@@ -1086,7 +1087,8 @@ type ClusterUser struct {
10861087

10871088
// Maximum number of requests per minute for user
10881089
// if omitted or zero - no limits would be applied
1089-
ReqPerMin uint32 `yaml:"requests_per_minute,omitempty"`
1090+
// if negative - the user is effectively blocked
1091+
ReqPerMin int32 `yaml:"requests_per_minute,omitempty"`
10901092

10911093
// The burst of request packet size token bucket for user
10921094
// if omitted or zero - no limits would be applied

config/examples/combined.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ users:
3333
max_queue_time: 25s
3434
cache: "shortterm"
3535

36+
- name: "blocked"
37+
password: "****"
38+
to_cluster: "stats-raw"
39+
to_user: "blocked"
40+
requests_per_minute: -1
41+
3642
clusters:
3743
- name: "stats-aggregate"
3844
nodes: [

proxy_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,17 @@ func TestReverseProxy_ServeHTTP1(t *testing.T) {
415415
return makeRequest(p)
416416
},
417417
},
418+
{
419+
cfg: goodCfg,
420+
name: "max requests per minute negative for cluster user",
421+
expResponse: "rate limit for cluster user \"web\" is exceeded: requests_per_minute limit: -1",
422+
expStatusCode: http.StatusTooManyRequests,
423+
f: func(p *reverseProxy) *http.Response {
424+
p.clusters["cluster"].users["web"].reqPerMin = -1
425+
runHeavyRequestInGoroutine(p, 1, true)
426+
return makeRequest(p)
427+
},
428+
},
418429
{
419430
cfg: goodCfg,
420431
name: "max time for cluster user",
@@ -458,6 +469,17 @@ func TestReverseProxy_ServeHTTP1(t *testing.T) {
458469
return makeRequest(p)
459470
},
460471
},
472+
{
473+
cfg: goodCfg,
474+
name: "max requests per minute negative for user",
475+
expResponse: fmt.Sprintf("rate limit for user %q is exceeded: requests_per_minute limit: -1", defaultUsername),
476+
expStatusCode: http.StatusTooManyRequests,
477+
f: func(p *reverseProxy) *http.Response {
478+
p.users[defaultUsername].reqPerMin = -1
479+
runHeavyRequestInGoroutine(p, 1, true)
480+
return makeRequest(p)
481+
},
482+
},
461483
{
462484
cfg: goodCfg,
463485
name: "queuing queries for user",

scope.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,11 @@ func (s *scope) checkTokenFreeRateLimiters() error {
255255
// is decremented on error below after per-minute zeroing
256256
// in rateLimiter.run.
257257
// These races become innocent with the given check.
258-
if s.user.reqPerMin > 0 && int32(uRPM) > 0 && uRPM > s.user.reqPerMin {
258+
if (s.user.reqPerMin > 0 && int32(uRPM) > 0 && uRPM > uint32(s.user.reqPerMin)) || s.user.reqPerMin < 0 {
259259
err = fmt.Errorf("rate limit for user %q is exceeded: requests_per_minute limit: %d",
260260
s.user.name, s.user.reqPerMin)
261261
}
262-
if s.clusterUser.reqPerMin > 0 && int32(cRPM) > 0 && cRPM > s.clusterUser.reqPerMin {
262+
if (s.clusterUser.reqPerMin > 0 && int32(cRPM) > 0 && cRPM > uint32(s.clusterUser.reqPerMin)) || s.clusterUser.reqPerMin < 0 {
263263
err = fmt.Errorf("rate limit for cluster user %q is exceeded: requests_per_minute limit: %d",
264264
s.clusterUser.name, s.clusterUser.reqPerMin)
265265
}
@@ -525,7 +525,7 @@ type user struct {
525525

526526
maxExecutionTime time.Duration
527527

528-
reqPerMin uint32
528+
reqPerMin int32
529529
rateLimiter rateLimiter
530530

531531
reqPacketSizeTokenLimiter *rate.Limiter
@@ -635,7 +635,7 @@ type clusterUser struct {
635635

636636
maxExecutionTime time.Duration
637637

638-
reqPerMin uint32
638+
reqPerMin int32
639639
rateLimiter rateLimiter
640640

641641
queueCh chan struct{}

0 commit comments

Comments
 (0)