Skip to content

Commit 64268bd

Browse files
committed
query rejection - address comments
Signed-off-by: Erlan Zholdubai uulu <[email protected]>
1 parent 6bfecc2 commit 64268bd

File tree

7 files changed

+216
-96
lines changed

7 files changed

+216
-96
lines changed

docs/configuration/config-file-reference.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3287,7 +3287,9 @@ query_rejection:
32873287
# CLI flag: -frontend.query-rejection.enabled
32883288
[enabled: <boolean> | default = false]
32893289

3290-
# List of query attributes for rejection.
3290+
# List of query attributes that queries will be matched against. Query will be
3291+
# matched against each of those query attributes separately and if query
3292+
# matches any of them then it will be rejected.
32913293
[query_attributes: <list of QueryAttribute> | default = []]
32923294

32933295
# Duration to delay the evaluation of rules to ensure the underlying metrics
@@ -5350,14 +5352,17 @@ limits:
53505352
# priority level. Value between 0 and 1 will be used as a percentage.
53515353
[reserved_queriers: <float> | default = 0]
53525354
5353-
# List of query attributes to assign the priority.
5355+
# List of query attributes that queries will be matched against. Query will be
5356+
# matched against each of those query attributes separately and if query matches
5357+
# any of them then it will be assigned to this priority.
53545358
[query_attributes: <list of QueryAttribute> | default = []]
53555359
```
53565360

53575361
### `QueryAttribute`
53585362

53595363
```yaml
5360-
# Regex that the query string should match. If not set, it won't be checked.
5364+
# Regex that the query string(or any of the matchers in metadata query) should
5365+
# match. If not set, it won't be checked.
53615366
[regex: <string> | default = ""]
53625367
53635368
# Overall data select time window (including range selectors, modifiers and
@@ -5374,7 +5379,8 @@ time_window:
53745379
# checked.
53755380
[end: <int> | default = 0]
53765381
5377-
# Limit that query time range should be within. If not set, it won't be checked.
5382+
# Queries with time range that is within this limits will match. If not set, it
5383+
# won't be checked.
53785384
time_range_limit:
53795385
# Query time range should be above or equal to this value to match. If set to
53805386
# 0, it won't be checked.
@@ -5395,13 +5401,18 @@ query_step_limit:
53955401
# won't be checked.
53965402
[max: <int> | default = 0]
53975403
5398-
# User agent for the query. If not set, it won't be checked.
5404+
# Regex that User-Agent header of the request should match. If not set, it won't
5405+
# be checked.
53995406
[user_agent: <string> | default = ""]
54005407
5401-
# Dashboard UID for the query. If not set, it won't be checked.
5408+
# Grafana includes X-Dashboard-Uid header in query requests. If this field is
5409+
# provided then X-Dashboard-Uid header of request should match this value. If
5410+
# not set, it won't be checked.
54025411
[dashboard_uid: <string> | default = ""]
54035412
5404-
# Panel Id for the query. If not set, it won't be checked.
5413+
# Grafana includes X-Panel-Id header in query requests. If this field is
5414+
# provided then X-Panel-Id header of request should match this value. If not
5415+
# set, it won't be checked.
54055416
[panel_id: <string> | default = ""]
54065417
```
54075418

docs/configuration/v1-guarantees.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,4 @@ Currently experimental features are:
113113
- `-ruler.ring.tokens-file-path` (path) CLI flag
114114
- Native Histograms
115115
- Ingestion can be enabled by setting `-blocks-storage.tsdb.enable-native-histograms=true` on Ingester.
116+
- Query-frontend: query rejection (`-frontend.query-rejection.enabled`)

pkg/querier/tripperware/query_attribute_matcher.go

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ func rejectQueryOrSetPriority(r *http.Request, now time.Time, lookbackDelta time
2121
if limits == nil || !(limits.QueryPriority(userStr).Enabled || limits.QueryRejection(userStr).Enabled) {
2222
return nil
2323
}
24+
op := getOperation(r)
2425

25-
isExpressionQuery := strings.HasSuffix(r.URL.Path, "/query") || strings.HasSuffix(r.URL.Path, "/query_range")
26-
isMetadataQuery := strings.HasSuffix(r.URL.Path, "/series") || strings.HasSuffix(r.URL.Path, "/labels") || strings.HasSuffix(r.URL.Path, "/values")
27-
28-
if isExpressionQuery {
26+
if op == "query" || op == "query_range" {
2927
query := r.FormValue("query")
3028
expr, err := parser.ParseExpr(query)
3129
if err != nil {
@@ -36,7 +34,7 @@ func rejectQueryOrSetPriority(r *http.Request, now time.Time, lookbackDelta time
3634
if queryReject := limits.QueryRejection(userStr); queryReject.Enabled && query != "" {
3735
for _, attribute := range queryReject.QueryAttributes {
3836
if matchAttributeForExpressionQuery(attribute, r, query, expr, now, minTime, maxTime) {
39-
rejectedQueriesPerTenant.WithLabelValues(userStr).Inc()
37+
rejectedQueriesPerTenant.WithLabelValues(op, userStr).Inc()
4038
return httpgrpc.Errorf(http.StatusUnprocessableEntity, queryRejectErrorMessage)
4139
}
4240
}
@@ -59,11 +57,10 @@ func rejectQueryOrSetPriority(r *http.Request, now time.Time, lookbackDelta time
5957
}
6058
}
6159

62-
if queryReject := limits.QueryRejection(userStr); queryReject.Enabled && isMetadataQuery {
60+
if queryReject := limits.QueryRejection(userStr); queryReject.Enabled && (op == "series" || op == "labels" || op == "label_values") {
6361
for _, attribute := range queryReject.QueryAttributes {
64-
6562
if matchAttributeForMetadataQuery(attribute, r, now) {
66-
rejectedQueriesPerTenant.WithLabelValues(userStr).Inc()
63+
rejectedQueriesPerTenant.WithLabelValues(op, userStr).Inc()
6764
return httpgrpc.Errorf(http.StatusUnprocessableEntity, queryRejectErrorMessage)
6865
}
6966
}
@@ -72,6 +69,23 @@ func rejectQueryOrSetPriority(r *http.Request, now time.Time, lookbackDelta time
7269
return nil
7370
}
7471

72+
func getOperation(r *http.Request) string {
73+
switch {
74+
case strings.HasSuffix(r.URL.Path, "/query"):
75+
return "query"
76+
case strings.HasSuffix(r.URL.Path, "/query_range"):
77+
return "query_range"
78+
case strings.HasSuffix(r.URL.Path, "/series"):
79+
return "series"
80+
case strings.HasSuffix(r.URL.Path, "/labels"):
81+
return "labels"
82+
case strings.HasSuffix(r.URL.Path, "/values"):
83+
return "label_values"
84+
default:
85+
return "other"
86+
}
87+
}
88+
7589
func matchAttributeForExpressionQuery(attribute validation.QueryAttribute, r *http.Request, query string, expr parser.Expr, now time.Time, minTime, maxTime int64) bool {
7690
if attribute.Regex != "" && attribute.Regex != ".*" && attribute.Regex != ".+" {
7791
if attribute.CompiledRegex != nil && !attribute.CompiledRegex.MatchString(query) {
@@ -83,16 +97,18 @@ func matchAttributeForExpressionQuery(attribute validation.QueryAttribute, r *ht
8397
return false
8498
}
8599

86-
if !isWithinTimeRangeAttribute(attribute.TimeRangeLimit, maxTime-minTime) {
100+
if !isWithinTimeRangeAttribute(attribute.TimeRangeLimit, minTime, maxTime) {
87101
return false
88102
}
89103

90104
if !isWithinQueryStepLimit(attribute.QueryStepLimit, r, expr) {
91105
return false
92106
}
93107

94-
if attribute.UserAgent != "" && attribute.UserAgent != r.Header.Get("User-Agent") {
95-
return false
108+
if attribute.UserAgentRegex != "" && attribute.UserAgentRegex != ".*" && attribute.CompiledUserAgentRegex != nil {
109+
if !attribute.CompiledUserAgentRegex.MatchString(r.Header.Get("User-Agent")) {
110+
return false
111+
}
96112
}
97113

98114
if attribute.DashboardUID != "" && attribute.DashboardUID != r.Header.Get("X-Dashboard-Uid") {
@@ -107,13 +123,13 @@ func matchAttributeForExpressionQuery(attribute validation.QueryAttribute, r *ht
107123
}
108124

109125
func matchAttributeForMetadataQuery(attribute validation.QueryAttribute, r *http.Request, now time.Time) bool {
110-
if attribute.Regex != "" && attribute.Regex != ".*" && attribute.Regex != ".+" && attribute.CompiledRegex != nil {
126+
if attribute.Regex != "" && attribute.Regex != ".*" && attribute.CompiledRegex != nil {
111127
for _, matcher := range r.Form["match[]"] {
112128
if attribute.CompiledRegex.MatchString(matcher) {
113-
continue
129+
break
114130
}
115-
return false
116131
}
132+
return false
117133
}
118134

119135
startTime, _ := util.ParseTime(r.FormValue("start"))
@@ -123,20 +139,14 @@ func matchAttributeForMetadataQuery(attribute validation.QueryAttribute, r *http
123139
return false
124140
}
125141

126-
if startTime != 0 && endTime != 0 && !isWithinTimeRangeAttribute(attribute.TimeRangeLimit, endTime-startTime) {
127-
return false
128-
}
129-
130-
if attribute.UserAgent != "" && attribute.UserAgent != r.Header.Get("User-Agent") {
142+
if !isWithinTimeRangeAttribute(attribute.TimeRangeLimit, startTime, endTime) {
131143
return false
132144
}
133145

134-
if attribute.DashboardUID != "" && attribute.DashboardUID != r.Header.Get("X-Dashboard-Uid") {
135-
return false
136-
}
137-
138-
if attribute.PanelID != "" && attribute.PanelID != r.Header.Get("X-Panel-Id") {
139-
return false
146+
if attribute.UserAgentRegex != "" && attribute.UserAgentRegex != ".*" && attribute.CompiledUserAgentRegex != nil {
147+
if !attribute.CompiledUserAgentRegex.MatchString(r.Header.Get("User-Agent")) {
148+
return false
149+
}
140150
}
141151

142152
return true
@@ -164,11 +174,17 @@ func isWithinTimeAttributes(timeWindow validation.TimeWindow, now time.Time, sta
164174
return true
165175
}
166176

167-
func isWithinTimeRangeAttribute(limit validation.TimeRangeLimit, timeRangeInMillis int64) bool {
177+
func isWithinTimeRangeAttribute(limit validation.TimeRangeLimit, startTime, endTime int64) bool {
168178
if limit.Min == 0 && limit.Max == 0 {
169179
return true
170180
}
171181

182+
if startTime == 0 || endTime == 0 {
183+
return false
184+
}
185+
186+
timeRangeInMillis := endTime - startTime
187+
172188
if limit.Min != 0 && time.Duration(limit.Min).Milliseconds() > timeRangeInMillis {
173189
return false
174190
}

0 commit comments

Comments
 (0)