Skip to content

Commit 1c0ceb2

Browse files
Adding support to filter rules
Signed-off-by: Anand Rajagopal <[email protected]>
1 parent 548ea7d commit 1c0ceb2

File tree

5 files changed

+56
-36
lines changed

5 files changed

+56
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* [FEATURE] Querier: Log query stats when querying store gateway. #5376
1818
* [FEATURE] Querier/StoreGateway: Allow the tenant shard sizes to be a percent of total instances. #5393
1919
* [FEATURE] Added the flag `-alertmanager.api-concurrency` to configure alert manager api concurrency limit. #5412
20+
* [FEATURE] Ruler: Support for filtering rules in the API. #5417
2021
* [ENHANCEMENT] Distributor/Ingester: Add span on push path #5319
2122
* [ENHANCEMENT] Support object storage backends for runtime configuration file. #5292
2223
* [ENHANCEMENT] Query Frontend: Reject subquery with too small step size. #5323
@@ -31,7 +32,6 @@
3132
* [ENHANCEMENT] Store Gateway: Add new metrics `cortex_bucket_store_sent_chunk_size_bytes`, `cortex_bucket_store_postings_size_bytes` and `cortex_bucket_store_empty_postings_total`. #5397
3233
* [ENHANCEMENT] Add jitter to lifecycler heartbeat. #5404
3334
* [ENHANCEMENT] Store Gateway: Add config `estimated_max_series_size_bytes` and `estimated_max_chunk_size_bytes` to address data overfetch. #5401
34-
* [ENHANCEMENT] Ruler: Support for filtering rules. #5417
3535
* [BUGFIX] Ruler: Validate if rule group can be safely converted back to rule group yaml from protobuf message #5265
3636
* [BUGFIX] Querier: Convert gRPC `ResourceExhausted` status code from store gateway to 422 limit error. #5286
3737
* [BUGFIX] Alertmanager: Route web-ui requests to the alertmanager distributor when sharding is enabled. #5293

docs/contributing/how-integration-tests-work.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ This will locally build the `quay.io/cortexproject/cortex:latest` image used by
2020
Once the Docker image is built, you can run integration tests:
2121

2222
```
23-
go test -v -tags=integration,requires_docker,integration_alertmanager,integration_memberlist,integration_querier,integration_ruler,integration_query_fuzz ./integration/...
23+
go test -v -tags=requires_docker ./integration/...
2424
```
2525

26-
If you want to run a single test you can use a filter. For example, to only run `TestRulerMetricsWhenIngesterFails`:
26+
If you want to run a single test you can use a filter. For example, to only run `TestChunksStorageAllIndexBackends`:
2727

2828
```
29-
go test -v -tags=integration,requires_docker,integration_ruler ./integration/ -run "^TestRulerMetricsWhenIngesterFails$" -count=1
29+
go test -v -tags=requires_docker ./integration -run "^TestChunksStorageAllIndexBackends$"
3030
```
3131

3232
### Supported environment variables
@@ -46,4 +46,4 @@ Integration tests have `requires_docker` tag (`// +build requires_docker` line f
4646

4747
## Isolation
4848

49-
Each integration test runs in isolation. For each integration test, we do create a Docker network, start Cortex and its dependencies containers, push/query series to/from Cortex and run assertions on it. Once the test has done, both the Docker network and containers are terminated and deleted.
49+
Each integration test runs in isolation. For each integration test, we do create a Docker network, start Cortex and its dependencies containers, push/query series to/from Cortex and run assertions on it. Once the test has done, both the Docker network and containers are terminated and deleted.

pkg/ruler/api.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,6 @@ type recordingRule struct {
100100
EvaluationTime float64 `json:"evaluationTime"`
101101
}
102102

103-
const (
104-
AlertingRuleFilter string = "alert"
105-
RecordingRuleFilter string = "record"
106-
)
107-
108103
func respondError(logger log.Logger, w http.ResponseWriter, msg string) {
109104
b, err := json.Marshal(&response{
110105
Status: "error",
@@ -125,10 +120,10 @@ func respondError(logger log.Logger, w http.ResponseWriter, msg string) {
125120
}
126121
}
127122

128-
func respondClientError(logger log.Logger, w http.ResponseWriter, msg string) {
123+
func respondBadRequest(logger log.Logger, w http.ResponseWriter, msg string) {
129124
b, err := json.Marshal(&response{
130125
Status: "error",
131-
ErrorType: v1.ErrServer,
126+
ErrorType: v1.ErrBadData,
132127
Error: msg,
133128
Data: nil,
134129
})
@@ -173,13 +168,13 @@ func (a *API) PrometheusRules(w http.ResponseWriter, req *http.Request) {
173168

174169
if err := req.ParseForm(); err != nil {
175170
level.Error(logger).Log("msg", "error parsing form/query params", "err", err)
176-
respondClientError(logger, w, err.Error())
171+
respondBadRequest(logger, w, "error parsing form/query params")
177172
return
178173
}
179174

180175
typ := strings.ToLower(req.URL.Query().Get("type"))
181-
if typ != "" && typ != AlertingRuleFilter && typ != RecordingRuleFilter {
182-
respondClientError(logger, w, fmt.Sprintf("not supported value %q", typ))
176+
if typ != "" && typ != alertingRuleFilter && typ != recordingRuleFilter {
177+
respondBadRequest(logger, w, fmt.Sprintf("unsupported rule type %q", typ))
183178
return
184179
}
185180

@@ -284,7 +279,7 @@ func (a *API) PrometheusAlerts(w http.ResponseWriter, req *http.Request) {
284279

285280
w.Header().Set("Content-Type", "application/json")
286281
rulesRequest := RulesRequest{
287-
Type: AlertingRuleFilter,
282+
Type: alertingRuleFilter,
288283
}
289284
rgs, err := a.ruler.GetRules(req.Context(), rulesRequest)
290285

pkg/ruler/ruler.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ const (
6666

6767
// errors
6868
errListAllUser = "unable to list the ruler users"
69+
70+
alertingRuleFilter string = "alert"
71+
recordingRuleFilter string = "record"
6972
)
7073

7174
// Config is the configuration for the recording rules server.
@@ -677,12 +680,17 @@ func (r *Ruler) getLocalRules(userID string, rulesRequest RulesRequest) ([]*Grou
677680
fileSet := sliceToSet(rulesRequest.Files)
678681
ruleType := rulesRequest.Type
679682

680-
returnAlerts := ruleType == "" || ruleType == "alert"
681-
returnRecording := ruleType == "" || ruleType == "record"
683+
returnAlerts := ruleType == "" || ruleType == alertingRuleFilter
684+
returnRecording := ruleType == "" || ruleType == recordingRuleFilter
682685

683686
for _, group := range groups {
687+
// The mapped filename is url path escaped encoded to make handling `/` characters easier
688+
decodedNamespace, err := url.PathUnescape(strings.TrimPrefix(group.File(), prefix))
689+
if err != nil {
690+
return nil, errors.Wrap(err, "unable to decode rule filename")
691+
}
684692
if len(fileSet) > 0 {
685-
if _, OK := fileSet[group.File()]; !OK {
693+
if _, OK := fileSet[decodedNamespace]; !OK {
686694
continue
687695
}
688696
}
@@ -694,12 +702,6 @@ func (r *Ruler) getLocalRules(userID string, rulesRequest RulesRequest) ([]*Grou
694702
}
695703
interval := group.Interval()
696704

697-
// The mapped filename is url path escaped encoded to make handling `/` characters easier
698-
decodedNamespace, err := url.PathUnescape(strings.TrimPrefix(group.File(), prefix))
699-
if err != nil {
700-
return nil, errors.Wrap(err, "unable to decode rule filename")
701-
}
702-
703705
groupDesc := &GroupStateDesc{
704706
Group: &rulespb.RuleGroupDesc{
705707
Name: group.Name(),

pkg/ruler/ruler_test.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -380,11 +380,17 @@ func TestGetRules(t *testing.T) {
380380
},
381381
"ruler2-user2-rule-group2": []*rulespb.RuleDesc{
382382
{
383-
Record: "rtest_user1_1",
383+
Record: "rtest_user2_1",
384384
Expr: "sum(rate(node_cpu_seconds_total[3h:10m]))",
385385
},
386386
{
387-
Alert: "atest_user1_1",
387+
Alert: "atest_user2_1",
388+
Expr: "sum(rate(node_cpu_seconds_total[3h:10m]))",
389+
},
390+
},
391+
"ruler2-user3-rule-group1": []*rulespb.RuleDesc{
392+
{
393+
Alert: "atest_user3_1",
388394
Expr: "sum(rate(node_cpu_seconds_total[3h:10m]))",
389395
},
390396
},
@@ -438,6 +444,9 @@ func TestGetRules(t *testing.T) {
438444
&rulespb.RuleGroupDesc{User: "user2", Namespace: "namespace", Name: "first", Interval: 10 * time.Second, Rules: ruleMap["ruler2-user2-rule-group1"]},
439445
&rulespb.RuleGroupDesc{User: "user2", Namespace: "namespace", Name: "second", Interval: 10 * time.Second, Rules: ruleMap["ruler2-user2-rule-group2"]},
440446
},
447+
"user3": {
448+
&rulespb.RuleGroupDesc{User: "user3", Namespace: "latency-test", Name: "first", Interval: 10 * time.Second, Rules: ruleMap["ruler2-user3-rule-group1"]},
449+
},
441450
},
442451
"ruler3": map[string]rulespb.RuleGroupList{
443452
"user3": {
@@ -451,33 +460,33 @@ func TestGetRules(t *testing.T) {
451460
}
452461

453462
testCases := map[string]testCase{
454-
"No Sharding": {
463+
"No Sharding with Rule Type Filter": {
455464
sharding: false,
456465
rulesRequest: RulesRequest{
457-
Type: AlertingRuleFilter,
466+
Type: alertingRuleFilter,
458467
},
459468
expectedCount: map[string]int{
460469
"user1": 2,
461470
"user2": 4,
462-
"user3": 1,
471+
"user3": 2,
463472
},
464473
},
465-
"Default Sharding": {
474+
"Default Sharding with No Filter": {
466475
sharding: true,
467476
shardingStrategy: util.ShardingStrategyDefault,
468-
rulesRequest: RulesRequest{},
477+
//rulesRequest: RulesRequest{},
469478
expectedCount: map[string]int{
470479
"user1": 5,
471480
"user2": 9,
472-
"user3": 2,
481+
"user3": 3,
473482
},
474483
},
475-
"Shuffle Sharding and ShardSize = 2": {
484+
"Shuffle Sharding and ShardSize = 2 with Rule Type Filter": {
476485
sharding: true,
477486
shuffleShardSize: 2,
478487
shardingStrategy: util.ShardingStrategyShuffle,
479488
rulesRequest: RulesRequest{
480-
Type: RecordingRuleFilter,
489+
Type: recordingRuleFilter,
481490
},
482491
expectedCount: map[string]int{
483492
"user1": 3,
@@ -504,14 +513,28 @@ func TestGetRules(t *testing.T) {
504513
shardingStrategy: util.ShardingStrategyShuffle,
505514
rulesRequest: RulesRequest{
506515
RuleGroupNames: []string{"second", "third"},
507-
Type: RecordingRuleFilter,
516+
Type: recordingRuleFilter,
508517
},
509518
expectedCount: map[string]int{
510519
"user1": 2,
511520
"user2": 2,
512521
"user3": 1,
513522
},
514523
},
524+
"Shuffle Sharding and ShardSize = 2 with Rule Type and Namespace Filters": {
525+
sharding: true,
526+
shuffleShardSize: 2,
527+
shardingStrategy: util.ShardingStrategyShuffle,
528+
rulesRequest: RulesRequest{
529+
Type: alertingRuleFilter,
530+
Files: []string{"latency-test"},
531+
},
532+
expectedCount: map[string]int{
533+
"user1": 0,
534+
"user2": 0,
535+
"user3": 1,
536+
},
537+
},
515538
}
516539

517540
for name, tc := range testCases {

0 commit comments

Comments
 (0)