From a864ab3ee7d675620a517d8880850e3afd8a0a88 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Sat, 14 Sep 2024 19:01:13 +0200 Subject: [PATCH 1/5] Support exclude_alerts parameter in Prometheus list rules endpoint --- .../mimir/references/http-api/index.md | 4 +- pkg/ruler/api.go | 39 +++- pkg/ruler/api_test.go | 71 ++++++++ pkg/ruler/ruler.go | 32 ++-- pkg/ruler/ruler.pb.go | 167 +++++++++++------- pkg/ruler/ruler.proto | 1 + pkg/ruler/ruler_test.go | 94 +++++++++- 7 files changed, 323 insertions(+), 85 deletions(-) diff --git a/docs/sources/mimir/references/http-api/index.md b/docs/sources/mimir/references/http-api/index.md index b7c9dd2411a..6791b60b1a2 100644 --- a/docs/sources/mimir/references/http-api/index.md +++ b/docs/sources/mimir/references/http-api/index.md @@ -815,7 +815,7 @@ List all tenant rules. This endpoint is not part of ruler-API and is always avai ### List Prometheus rules ``` -GET /api/v1/rules?type={alert|record}&file={}&rule_group={}&rule_name={} +GET /api/v1/rules?type={alert|record}&file={}&rule_group={}&rule_name={}&exclude_alerts={} ``` Prometheus-compatible rules endpoint to list alerting and recording rules that are currently loaded. @@ -824,6 +824,8 @@ The `type` parameter is optional. If set, only the specified type of rule is ret The `file`, `rule_group` and `rule_name` parameters are optional, and can accept multiple values. If set, the response content is filtered accordingly. +The `exclude_alerts` parameter is optional. If set, only rules are returned, active alerts are excluded. + For more information, refer to Prometheus [rules](https://prometheus.io/docs/prometheus/latest/querying/api/#rules). Requires [authentication](#authentication). diff --git a/pkg/ruler/api.go b/pkg/ruler/api.go index fa4a1677ff9..a271b64f55a 100644 --- a/pkg/ruler/api.go +++ b/pkg/ruler/api.go @@ -166,11 +166,18 @@ func (a *API) PrometheusRules(w http.ResponseWriter, req *http.Request) { return } + excludeAlerts, err := parseExcludeAlerts(req) + if err != nil { + respondInvalidRequest(logger, w, "invalid exclude_alerts parameter") + return + } + rulesReq := RulesRequest{ - Filter: AnyRule, - RuleName: req.URL.Query()["rule_name"], - RuleGroup: req.URL.Query()["rule_group"], - File: req.URL.Query()["file"], + Filter: AnyRule, + RuleName: req.URL.Query()["rule_name"], + RuleGroup: req.URL.Query()["rule_group"], + File: req.URL.Query()["file"], + ExcludeAlerts: excludeAlerts, } ruleTypeFilter := strings.ToLower(req.URL.Query().Get("type")) @@ -209,9 +216,12 @@ func (a *API) PrometheusRules(w http.ResponseWriter, req *http.Request) { for i, rl := range g.ActiveRules { if g.ActiveRules[i].Rule.Alert != "" { - alerts := make([]*Alert, 0, len(rl.Alerts)) - for _, a := range rl.Alerts { - alerts = append(alerts, alertStateDescToPrometheusAlert(a)) + var alerts []*Alert + if !excludeAlerts { + alerts = make([]*Alert, 0, len(rl.Alerts)) + for _, a := range rl.Alerts { + alerts = append(alerts, alertStateDescToPrometheusAlert(a)) + } } grp.Rules[i] = alertingRule{ State: rl.GetState(), @@ -265,6 +275,21 @@ func (a *API) PrometheusRules(w http.ResponseWriter, req *http.Request) { } } +func parseExcludeAlerts(req *http.Request) (bool, error) { + excludeAlerts := req.URL.Query().Get("exclude_alerts") + if excludeAlerts == "" { + return false, nil + } + + value, err := strconv.ParseBool(excludeAlerts) + if err != nil { + return false, fmt.Errorf("unable to pase exclude_alerts value %w", err) + } + + return value, nil + +} + func (a *API) PrometheusAlerts(w http.ResponseWriter, req *http.Request) { logger, ctx := spanlogger.NewWithLogger(req.Context(), a.logger, "API.PrometheusAlerts") defer logger.Finish() diff --git a/pkg/ruler/api_test.go b/pkg/ruler/api_test.go index 10c63cbcf55..ebfca7cdb96 100644 --- a/pkg/ruler/api_test.go +++ b/pkg/ruler/api_test.go @@ -542,6 +542,77 @@ func TestRuler_PrometheusRules(t *testing.T) { }, }, }, + "API request with exclude_alerts=true returns alerting rules without alerts": { + configuredRules: rulespb.RuleGroupList{ + &rulespb.RuleGroupDesc{ + Name: "group1", + Namespace: "namespace1", + User: userID, + Rules: []*rulespb.RuleDesc{createAlertingRule("UP_ALERT", "up < 1")}, + Interval: interval, + }, + }, + expectedConfigured: 1, + queryParams: "?exclude_alerts=true", + limits: validation.MockDefaultOverrides(), + expectedRules: []*RuleGroup{ + { + Name: "group1", + File: "namespace1", + Rules: []rule{ + &alertingRule{ + Name: "UP_ALERT", + Query: "up < 1", + State: "inactive", + Health: "unknown", + Type: "alerting", + Alerts: nil, + }, + }, + Interval: 60, + }, + }, + }, + "API request with exclude_alerts=false returns alerting rules including alerts": { + configuredRules: rulespb.RuleGroupList{ + &rulespb.RuleGroupDesc{ + Name: "group1", + Namespace: "namespace1", + User: userID, + Rules: []*rulespb.RuleDesc{createAlertingRule("UP_ALERT", "up < 1")}, + Interval: interval, + }, + }, + expectedConfigured: 1, + queryParams: "?exclude_alerts=false", + limits: validation.MockDefaultOverrides(), + expectedRules: []*RuleGroup{ + { + Name: "group1", + File: "namespace1", + Rules: []rule{ + &alertingRule{ + Name: "UP_ALERT", + Query: "up < 1", + State: "inactive", + Health: "unknown", + Type: "alerting", + Alerts: []*Alert{}, + }, + }, + Interval: 60, + }, + }, + }, + "Invalid exlude_alerts param": { + configuredRules: rulespb.RuleGroupList{}, + expectedConfigured: 0, + queryParams: "?exclude_alerts=foo", + limits: validation.MockDefaultOverrides(), + expectedStatusCode: http.StatusBadRequest, + expectedErrorType: v1.ErrBadData, + expectedRules: []*RuleGroup{}, + }, "Invalid type param": { configuredRules: rulespb.RuleGroupList{}, expectedConfigured: 0, diff --git a/pkg/ruler/ruler.go b/pkg/ruler/ruler.go index c527fcf67e2..1c42d3fcab3 100644 --- a/pkg/ruler/ruler.go +++ b/pkg/ruler/ruler.go @@ -1111,20 +1111,24 @@ func (r *Ruler) getLocalRules(ctx context.Context, userID string, req RulesReque continue } - alerts := []*AlertStateDesc{} - for _, a := range rule.ActiveAlerts() { - alerts = append(alerts, &AlertStateDesc{ - State: a.State.String(), - Labels: mimirpb.FromLabelsToLabelAdapters(a.Labels), - Annotations: mimirpb.FromLabelsToLabelAdapters(a.Annotations), - Value: a.Value, - ActiveAt: a.ActiveAt, - FiredAt: a.FiredAt, - ResolvedAt: a.ResolvedAt, - LastSentAt: a.LastSentAt, - ValidUntil: a.ValidUntil, - KeepFiringSince: a.KeepFiringSince, - }) + var alerts []*AlertStateDesc + if !req.ExcludeAlerts { + activeAlerts := rule.ActiveAlerts() + alerts = make([]*AlertStateDesc, 0, len(activeAlerts)) + for _, a := range activeAlerts { + alerts = append(alerts, &AlertStateDesc{ + State: a.State.String(), + Labels: mimirpb.FromLabelsToLabelAdapters(a.Labels), + Annotations: mimirpb.FromLabelsToLabelAdapters(a.Annotations), + Value: a.Value, + ActiveAt: a.ActiveAt, + FiredAt: a.FiredAt, + ResolvedAt: a.ResolvedAt, + LastSentAt: a.LastSentAt, + ValidUntil: a.ValidUntil, + KeepFiringSince: a.KeepFiringSince, + }) + } } ruleDesc = &RuleStateDesc{ Rule: &rulespb.RuleDesc{ diff --git a/pkg/ruler/ruler.pb.go b/pkg/ruler/ruler.pb.go index a2bd34ada74..59d3f8df60b 100644 --- a/pkg/ruler/ruler.pb.go +++ b/pkg/ruler/ruler.pb.go @@ -64,10 +64,11 @@ func (RulesRequest_RuleType) EnumDescriptor() ([]byte, []int) { } type RulesRequest struct { - Filter RulesRequest_RuleType `protobuf:"varint,1,opt,name=filter,proto3,enum=ruler.RulesRequest_RuleType" json:"filter,omitempty"` - RuleName []string `protobuf:"bytes,2,rep,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` - RuleGroup []string `protobuf:"bytes,3,rep,name=rule_group,json=ruleGroup,proto3" json:"rule_group,omitempty"` - File []string `protobuf:"bytes,4,rep,name=file,proto3" json:"file,omitempty"` + Filter RulesRequest_RuleType `protobuf:"varint,1,opt,name=filter,proto3,enum=ruler.RulesRequest_RuleType" json:"filter,omitempty"` + RuleName []string `protobuf:"bytes,2,rep,name=rule_name,json=ruleName,proto3" json:"rule_name,omitempty"` + RuleGroup []string `protobuf:"bytes,3,rep,name=rule_group,json=ruleGroup,proto3" json:"rule_group,omitempty"` + File []string `protobuf:"bytes,4,rep,name=file,proto3" json:"file,omitempty"` + ExcludeAlerts bool `protobuf:"varint,5,opt,name=exclude_alerts,json=excludeAlerts,proto3" json:"exclude_alerts,omitempty"` } func (m *RulesRequest) Reset() { *m = RulesRequest{} } @@ -130,6 +131,13 @@ func (m *RulesRequest) GetFile() []string { return nil } +func (m *RulesRequest) GetExcludeAlerts() bool { + if m != nil { + return m.ExcludeAlerts + } + return false +} + type RulesResponse struct { Groups []*GroupStateDesc `protobuf:"bytes,1,rep,name=groups,proto3" json:"groups,omitempty"` } @@ -529,62 +537,63 @@ func init() { func init() { proto.RegisterFile("ruler.proto", fileDescriptor_9ecbec0a4cfddea6) } var fileDescriptor_9ecbec0a4cfddea6 = []byte{ - // 871 bytes of a gzipped FileDescriptorProto + // 891 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xcf, 0x6f, 0x1b, 0x45, - 0x14, 0xde, 0x71, 0xe2, 0x1f, 0xfb, 0x9c, 0xa6, 0xc9, 0x24, 0xc0, 0xd6, 0x94, 0x4d, 0x64, 0x2e, - 0x11, 0x52, 0xd6, 0x60, 0x22, 0x10, 0x12, 0x02, 0x1c, 0xb5, 0x45, 0x48, 0x08, 0x55, 0xeb, 0xc2, - 0xd5, 0x1a, 0xdb, 0xe3, 0xcd, 0xa8, 0xeb, 0xdd, 0x65, 0x66, 0xd6, 0x22, 0x27, 0xf8, 0x13, 0x7a, - 0xe4, 0xcc, 0x89, 0xbf, 0x83, 0x53, 0x8f, 0x11, 0xa7, 0x0a, 0xa1, 0x42, 0x9c, 0x0b, 0xc7, 0xfe, - 0x09, 0x68, 0xde, 0xec, 0xd6, 0x76, 0x1b, 0x10, 0x16, 0x70, 0x89, 0xe7, 0xcd, 0xfb, 0xbe, 0xef, - 0xcd, 0xfb, 0xe6, 0xed, 0x04, 0x9a, 0x32, 0x8f, 0xb9, 0x0c, 0x32, 0x99, 0xea, 0x94, 0x56, 0x31, - 0x68, 0x1d, 0x47, 0x42, 0x9f, 0xe5, 0xc3, 0x60, 0x94, 0x4e, 0x3b, 0x51, 0x1a, 0xa5, 0x1d, 0xcc, - 0x0e, 0xf3, 0x09, 0x46, 0x18, 0xe0, 0xca, 0xb2, 0x5a, 0x7e, 0x94, 0xa6, 0x51, 0xcc, 0x17, 0xa8, - 0x71, 0x2e, 0x99, 0x16, 0x69, 0x52, 0xe4, 0x0f, 0x5e, 0xcc, 0x6b, 0x31, 0xe5, 0x4a, 0xb3, 0x69, - 0x56, 0x00, 0xde, 0x5e, 0xae, 0x27, 0xd9, 0x84, 0x25, 0xac, 0x33, 0x15, 0x53, 0x21, 0x3b, 0xd9, - 0xc3, 0xc8, 0xae, 0xb2, 0xa1, 0xfd, 0x2d, 0x18, 0xef, 0xfd, 0x2d, 0x03, 0xbb, 0xc0, 0xbf, 0x2a, - 0x1b, 0xda, 0x5f, 0xcb, 0x6b, 0xff, 0x4c, 0x60, 0x2b, 0x34, 0x71, 0xc8, 0xbf, 0xce, 0xb9, 0xd2, - 0xf4, 0x04, 0x6a, 0x13, 0x11, 0x6b, 0x2e, 0x3d, 0x72, 0x48, 0x8e, 0xb6, 0xbb, 0xb7, 0x03, 0xeb, - 0xc7, 0x32, 0x08, 0x83, 0x07, 0xe7, 0x19, 0x0f, 0x0b, 0x2c, 0x7d, 0x1d, 0x5c, 0x03, 0x1b, 0x24, - 0x6c, 0xca, 0xbd, 0xca, 0xe1, 0xc6, 0x91, 0x1b, 0x36, 0xcc, 0xc6, 0x17, 0x6c, 0xca, 0xe9, 0x1b, - 0x00, 0x98, 0x8c, 0x64, 0x9a, 0x67, 0xde, 0x06, 0x66, 0x11, 0xfe, 0xa9, 0xd9, 0xa0, 0x14, 0x36, - 0x27, 0x22, 0xe6, 0xde, 0x26, 0x26, 0x70, 0xdd, 0xfe, 0x10, 0x1a, 0x65, 0x0d, 0xda, 0x84, 0x7a, - 0x2f, 0x39, 0x37, 0xe1, 0x8e, 0x43, 0x77, 0x60, 0xab, 0x17, 0x73, 0xa9, 0x45, 0x12, 0xe1, 0x0e, - 0xa1, 0xbb, 0x70, 0x23, 0xe4, 0xa3, 0x54, 0x8e, 0xcb, 0xad, 0x4a, 0xfb, 0x23, 0xb8, 0x51, 0x1c, - 0x57, 0x65, 0x69, 0xa2, 0x38, 0x3d, 0x86, 0x1a, 0x16, 0x57, 0x1e, 0x39, 0xdc, 0x38, 0x6a, 0x76, - 0x5f, 0x29, 0x9a, 0xc2, 0x03, 0xf4, 0x35, 0xd3, 0xfc, 0x0e, 0x57, 0xa3, 0xb0, 0x00, 0xb5, 0x8f, - 0x61, 0xa7, 0x7f, 0x9e, 0x8c, 0x56, 0x7c, 0xb9, 0x05, 0x8d, 0x5c, 0x71, 0x39, 0x10, 0x63, 0x2b, - 0xe2, 0x86, 0x75, 0x13, 0x7f, 0x36, 0x56, 0xed, 0x3d, 0xd8, 0x5d, 0x82, 0xdb, 0x92, 0xed, 0x1f, - 0x2a, 0xb0, 0xbd, 0x2a, 0x4f, 0xdf, 0x82, 0xaa, 0xb5, 0xc0, 0x38, 0xdb, 0xec, 0xee, 0x07, 0xf6, - 0x22, 0xc2, 0xd2, 0x09, 0x3c, 0x83, 0x85, 0xd0, 0xf7, 0x61, 0x8b, 0x8d, 0xb4, 0x98, 0xf1, 0x01, - 0x82, 0xd0, 0xd3, 0x92, 0x62, 0x2f, 0x63, 0x71, 0xec, 0xa6, 0x45, 0x62, 0x7d, 0xfa, 0x15, 0xec, - 0xf1, 0x19, 0x8b, 0x73, 0x9c, 0xb7, 0x07, 0xe5, 0x5c, 0x79, 0x1b, 0x58, 0xb2, 0x15, 0xd8, 0xc9, - 0x0b, 0xca, 0xc9, 0x0b, 0x9e, 0x23, 0x4e, 0x1b, 0x8f, 0x9f, 0x1e, 0x38, 0x8f, 0x7e, 0x3b, 0x20, - 0xe1, 0x75, 0x02, 0xb4, 0x0f, 0x74, 0xb1, 0x7d, 0xa7, 0x98, 0x67, 0x6f, 0x13, 0x65, 0x6f, 0xbd, - 0x24, 0x5b, 0x02, 0xac, 0xea, 0xf7, 0x46, 0xf5, 0x1a, 0x7a, 0xfb, 0xd7, 0x8a, 0xbd, 0xa9, 0x85, - 0x47, 0x6f, 0xc2, 0xa6, 0x69, 0xb1, 0xb0, 0xe8, 0xe6, 0x92, 0x45, 0xd8, 0x2a, 0x26, 0xe9, 0x3e, - 0x54, 0x95, 0x61, 0x78, 0x95, 0x43, 0x72, 0xe4, 0x86, 0x36, 0xa0, 0xaf, 0x42, 0xed, 0x8c, 0xb3, - 0x58, 0x9f, 0x61, 0xb3, 0x6e, 0x58, 0x44, 0xf4, 0x36, 0xb8, 0x31, 0x53, 0xfa, 0xae, 0x94, 0xa9, - 0xc4, 0x03, 0xbb, 0xe1, 0x62, 0xc3, 0x8c, 0x06, 0x33, 0x03, 0xa5, 0xbc, 0xea, 0xca, 0x68, 0xe0, - 0x94, 0x2d, 0x8d, 0x86, 0x05, 0xfd, 0x95, 0xbd, 0xb5, 0xff, 0xc7, 0xde, 0xfa, 0xbf, 0xb3, 0xf7, - 0xa7, 0x2a, 0x6c, 0xaf, 0xf6, 0xb1, 0xb0, 0x8e, 0x2c, 0x5b, 0x37, 0x81, 0x5a, 0xcc, 0x86, 0x3c, - 0x2e, 0xe7, 0x6c, 0x2f, 0x18, 0xa5, 0x52, 0xf3, 0x6f, 0xb2, 0x61, 0xf0, 0xb9, 0xd9, 0xbf, 0xcf, - 0x84, 0x3c, 0xfd, 0xc0, 0xd4, 0xfa, 0xe5, 0xe9, 0xc1, 0x3b, 0xff, 0xe4, 0x71, 0xb2, 0xbc, 0xde, - 0x98, 0x65, 0x9a, 0xcb, 0xb0, 0x50, 0xa7, 0x19, 0x34, 0x59, 0x92, 0xa4, 0x1a, 0x8f, 0xa7, 0xf0, - 0x29, 0xf8, 0xef, 0x8b, 0x2d, 0x97, 0x30, 0xfd, 0x1a, 0x5f, 0x38, 0x5e, 0x3c, 0x09, 0x6d, 0x40, - 0x7b, 0xe0, 0x16, 0x5f, 0x17, 0xd3, 0x5e, 0x75, 0x8d, 0xbb, 0x6b, 0x58, 0x5a, 0x4f, 0xd3, 0x8f, - 0xa1, 0x31, 0x11, 0x92, 0x8f, 0x8d, 0xc2, 0x3a, 0xb7, 0x5f, 0x47, 0x56, 0x4f, 0xd3, 0xbb, 0xd0, - 0x94, 0x5c, 0xa5, 0xf1, 0xcc, 0x6a, 0xd4, 0xd7, 0xd0, 0x80, 0x92, 0xd8, 0xd3, 0xf4, 0x1e, 0x6c, - 0x99, 0x61, 0x1e, 0x28, 0x9e, 0x68, 0xa3, 0xd3, 0x58, 0x47, 0xc7, 0x30, 0xfb, 0x3c, 0xd1, 0xf6, - 0x38, 0x33, 0x16, 0x8b, 0xf1, 0x20, 0x4f, 0xb4, 0x88, 0x3d, 0x77, 0x1d, 0x19, 0x24, 0x7e, 0x69, - 0x78, 0xf4, 0x3e, 0xec, 0x3e, 0xe4, 0x3c, 0x1b, 0x4c, 0x84, 0x14, 0x49, 0x34, 0x50, 0x22, 0x19, - 0x71, 0x0f, 0xd6, 0x10, 0xbb, 0x69, 0xe8, 0xf7, 0x90, 0xdd, 0x37, 0xe4, 0xee, 0xb7, 0x50, 0x35, - 0x9f, 0xbf, 0xa4, 0x27, 0x76, 0xa1, 0xe8, 0xde, 0x35, 0xff, 0x92, 0x5a, 0xfb, 0xab, 0x9b, 0xc5, - 0x2b, 0xec, 0xd0, 0x4f, 0xc0, 0x7d, 0xfe, 0x38, 0xd3, 0xd7, 0x0a, 0xd0, 0x8b, 0xaf, 0x7b, 0xcb, - 0x7b, 0x39, 0x51, 0x2a, 0x9c, 0x9e, 0x5c, 0x5c, 0xfa, 0xce, 0x93, 0x4b, 0xdf, 0x79, 0x76, 0xe9, - 0x93, 0xef, 0xe6, 0x3e, 0xf9, 0x71, 0xee, 0x93, 0xc7, 0x73, 0x9f, 0x5c, 0xcc, 0x7d, 0xf2, 0xfb, - 0xdc, 0x27, 0x7f, 0xcc, 0x7d, 0xe7, 0xd9, 0xdc, 0x27, 0x8f, 0xae, 0x7c, 0xe7, 0xe2, 0xca, 0x77, - 0x9e, 0x5c, 0xf9, 0xce, 0xb0, 0x86, 0x5d, 0xbe, 0xfb, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3c, - 0xcd, 0xed, 0x61, 0x4f, 0x08, 0x00, 0x00, + 0x14, 0xde, 0x71, 0xe2, 0x1f, 0xfb, 0x9c, 0xa4, 0xc9, 0x24, 0xc0, 0xd6, 0x94, 0x8d, 0x65, 0x84, + 0x64, 0x21, 0x65, 0x03, 0x21, 0x02, 0x21, 0x21, 0xc0, 0x51, 0x5b, 0x84, 0x84, 0x50, 0xb5, 0x2e, + 0x5c, 0xad, 0xb1, 0x3d, 0xde, 0x8c, 0xba, 0xde, 0x5d, 0x66, 0x66, 0xa3, 0xe6, 0x04, 0x7f, 0x42, + 0x8f, 0x9c, 0x39, 0xf1, 0x77, 0x70, 0xea, 0x31, 0xc7, 0x0a, 0xa1, 0x42, 0x9c, 0x0b, 0xc7, 0x5e, + 0xb9, 0xa1, 0x79, 0xb3, 0x1b, 0x3b, 0x6d, 0x40, 0xb5, 0xa0, 0x97, 0x78, 0xde, 0x7b, 0xdf, 0xf7, + 0xcd, 0xfb, 0xb5, 0x13, 0x68, 0xca, 0x3c, 0xe6, 0x32, 0xc8, 0x64, 0xaa, 0x53, 0x5a, 0x45, 0xa3, + 0xb5, 0x17, 0x09, 0x7d, 0x9c, 0x0f, 0x83, 0x51, 0x3a, 0xdd, 0x8f, 0xd2, 0x28, 0xdd, 0xc7, 0xe8, + 0x30, 0x9f, 0xa0, 0x85, 0x06, 0x9e, 0x2c, 0xab, 0xe5, 0x47, 0x69, 0x1a, 0xc5, 0x7c, 0x8e, 0x1a, + 0xe7, 0x92, 0x69, 0x91, 0x26, 0x45, 0x7c, 0xf7, 0xf9, 0xb8, 0x16, 0x53, 0xae, 0x34, 0x9b, 0x66, + 0x05, 0xe0, 0xbd, 0xc5, 0xfb, 0x24, 0x9b, 0xb0, 0x84, 0xed, 0x4f, 0xc5, 0x54, 0xc8, 0xfd, 0xec, + 0x41, 0x64, 0x4f, 0xd9, 0xd0, 0xfe, 0x16, 0x8c, 0x0f, 0xff, 0x95, 0x81, 0x55, 0xe0, 0x5f, 0x95, + 0x0d, 0xed, 0xaf, 0xe5, 0x75, 0xfe, 0x22, 0xb0, 0x16, 0x1a, 0x3b, 0xe4, 0xdf, 0xe5, 0x5c, 0x69, + 0x7a, 0x08, 0xb5, 0x89, 0x88, 0x35, 0x97, 0x1e, 0x69, 0x93, 0xee, 0xc6, 0xc1, 0xad, 0xc0, 0xf6, + 0x63, 0x11, 0x84, 0xc6, 0xfd, 0xd3, 0x8c, 0x87, 0x05, 0x96, 0xbe, 0x09, 0xae, 0x81, 0x0d, 0x12, + 0x36, 0xe5, 0x5e, 0xa5, 0xbd, 0xd2, 0x75, 0xc3, 0x86, 0x71, 0x7c, 0xcd, 0xa6, 0x9c, 0xbe, 0x05, + 0x80, 0xc1, 0x48, 0xa6, 0x79, 0xe6, 0xad, 0x60, 0x14, 0xe1, 0x5f, 0x18, 0x07, 0xa5, 0xb0, 0x3a, + 0x11, 0x31, 0xf7, 0x56, 0x31, 0x80, 0x67, 0xfa, 0x0e, 0x6c, 0xf0, 0x87, 0xa3, 0x38, 0x1f, 0xf3, + 0x01, 0x8b, 0xb9, 0xd4, 0xca, 0xab, 0xb6, 0x49, 0xb7, 0x11, 0xae, 0x17, 0xde, 0x1e, 0x3a, 0x3b, + 0x9f, 0x40, 0xa3, 0x4c, 0x85, 0x36, 0xa1, 0xde, 0x4b, 0x4e, 0x8d, 0xb9, 0xe9, 0xd0, 0x4d, 0x58, + 0x43, 0x88, 0x48, 0x22, 0xf4, 0x10, 0xba, 0x05, 0xeb, 0x21, 0x1f, 0xa5, 0x72, 0x5c, 0xba, 0x2a, + 0x9d, 0x4f, 0x61, 0xbd, 0xa8, 0x4a, 0x65, 0x69, 0xa2, 0x38, 0xdd, 0x83, 0x1a, 0xe6, 0xa8, 0x3c, + 0xd2, 0x5e, 0xe9, 0x36, 0x0f, 0x5e, 0x2b, 0x6a, 0xc7, 0x3c, 0xfb, 0x9a, 0x69, 0x7e, 0x9b, 0xab, + 0x51, 0x58, 0x80, 0x3a, 0x7b, 0xb0, 0xd9, 0x3f, 0x4d, 0x46, 0x57, 0xda, 0x77, 0x13, 0x1a, 0xb9, + 0xe2, 0x72, 0x20, 0xc6, 0x56, 0xc4, 0x0d, 0xeb, 0xc6, 0xfe, 0x72, 0xac, 0x3a, 0xdb, 0xb0, 0xb5, + 0x00, 0xb7, 0x57, 0x76, 0x7e, 0xaa, 0xc0, 0xc6, 0x55, 0x79, 0xfa, 0x2e, 0x54, 0x6d, 0xa7, 0xcc, + 0x00, 0x9a, 0x07, 0x3b, 0x81, 0x9d, 0x57, 0x58, 0x36, 0x0c, 0x73, 0xb0, 0x10, 0xfa, 0x11, 0xac, + 0xb1, 0x91, 0x16, 0x27, 0x7c, 0x80, 0x20, 0x6c, 0x7d, 0x49, 0xb1, 0x33, 0x9b, 0xa7, 0xdd, 0xb4, + 0x48, 0xbc, 0x9f, 0x7e, 0x0b, 0xdb, 0xfc, 0x84, 0xc5, 0x39, 0xae, 0xe5, 0xfd, 0x72, 0xfd, 0xbc, + 0x15, 0xbc, 0xb2, 0x15, 0xd8, 0x05, 0x0d, 0xca, 0x05, 0x0d, 0x2e, 0x11, 0x47, 0x8d, 0xc7, 0x4f, + 0x77, 0x9d, 0x47, 0xbf, 0xef, 0x92, 0xf0, 0x3a, 0x01, 0xda, 0x07, 0x3a, 0x77, 0xdf, 0x2e, 0xd6, + 0xde, 0x5b, 0x45, 0xd9, 0x9b, 0x2f, 0xc8, 0x96, 0x00, 0xab, 0xfa, 0xa3, 0x51, 0xbd, 0x86, 0xde, + 0xf9, 0xad, 0x62, 0x27, 0x35, 0xef, 0xd1, 0xdb, 0xb0, 0x6a, 0x4a, 0x2c, 0x5a, 0x74, 0x63, 0xa1, + 0x45, 0x58, 0x2a, 0x06, 0xe9, 0x0e, 0x54, 0x95, 0x61, 0x78, 0x95, 0x36, 0xe9, 0xba, 0xa1, 0x35, + 0xe8, 0xeb, 0x50, 0x3b, 0xe6, 0x2c, 0xd6, 0xc7, 0x58, 0xac, 0x1b, 0x16, 0x16, 0xbd, 0x05, 0x6e, + 0xcc, 0x94, 0xbe, 0x23, 0x65, 0x2a, 0x31, 0x61, 0x37, 0x9c, 0x3b, 0xcc, 0x6a, 0x5c, 0x2e, 0xe2, + 0xe2, 0x6a, 0xe0, 0x96, 0x2d, 0xac, 0x86, 0x05, 0xfd, 0x53, 0x7b, 0x6b, 0xaf, 0xa6, 0xbd, 0xf5, + 0xff, 0xd6, 0xde, 0x5f, 0xaa, 0xb0, 0x71, 0xb5, 0x8e, 0x79, 0xeb, 0xc8, 0x62, 0xeb, 0x26, 0x50, + 0x8b, 0xd9, 0x90, 0xc7, 0xe5, 0x9e, 0x6d, 0x07, 0xa3, 0x54, 0x6a, 0xfe, 0x30, 0x1b, 0x06, 0x5f, + 0x19, 0xff, 0x3d, 0x26, 0xe4, 0xd1, 0xc7, 0xe6, 0xae, 0x5f, 0x9f, 0xee, 0xbe, 0xff, 0x32, 0x6f, + 0x98, 0xe5, 0xf5, 0xc6, 0x2c, 0xd3, 0x5c, 0x86, 0x85, 0x3a, 0xcd, 0xa0, 0xc9, 0x92, 0x24, 0xd5, + 0x98, 0x9e, 0xc2, 0x17, 0xe3, 0xff, 0xbf, 0x6c, 0xf1, 0x0a, 0x53, 0xaf, 0xe9, 0x0b, 0xc7, 0xc1, + 0x93, 0xd0, 0x1a, 0xb4, 0x07, 0x6e, 0xf1, 0x75, 0x31, 0x8d, 0x0f, 0xd0, 0xcb, 0xce, 0xae, 0x61, + 0x69, 0x3d, 0x4d, 0x3f, 0x83, 0xc6, 0x44, 0x48, 0x3e, 0x36, 0x0a, 0xcb, 0x4c, 0xbf, 0x8e, 0xac, + 0x9e, 0xa6, 0x77, 0xa0, 0x29, 0xb9, 0x4a, 0xe3, 0x13, 0xab, 0x51, 0x5f, 0x42, 0x03, 0x4a, 0x62, + 0x4f, 0xd3, 0xbb, 0xb0, 0x66, 0x96, 0x79, 0xa0, 0x78, 0xa2, 0x8d, 0x4e, 0x63, 0x19, 0x1d, 0xc3, + 0xec, 0xf3, 0x44, 0xdb, 0x74, 0x4e, 0x58, 0x2c, 0xc6, 0x83, 0x3c, 0xd1, 0x22, 0xf6, 0xdc, 0x65, + 0x64, 0x90, 0xf8, 0x8d, 0xe1, 0xd1, 0x7b, 0xb0, 0xf5, 0x80, 0xf3, 0x6c, 0x30, 0x11, 0x52, 0x24, + 0xd1, 0x40, 0x89, 0x64, 0xc4, 0x3d, 0x58, 0x42, 0xec, 0x86, 0xa1, 0xdf, 0x45, 0x76, 0xdf, 0x90, + 0x0f, 0xbe, 0x87, 0xaa, 0xf9, 0xfc, 0x25, 0x3d, 0xb4, 0x07, 0x45, 0xb7, 0xaf, 0xf9, 0xcf, 0xd5, + 0xda, 0xb9, 0xea, 0x2c, 0x5e, 0x61, 0x87, 0x7e, 0x0e, 0xee, 0xe5, 0xe3, 0x4c, 0xdf, 0x28, 0x40, + 0xcf, 0xbf, 0xee, 0x2d, 0xef, 0xc5, 0x40, 0xa9, 0x70, 0x74, 0x78, 0x76, 0xee, 0x3b, 0x4f, 0xce, + 0x7d, 0xe7, 0xd9, 0xb9, 0x4f, 0x7e, 0x98, 0xf9, 0xe4, 0xe7, 0x99, 0x4f, 0x1e, 0xcf, 0x7c, 0x72, + 0x36, 0xf3, 0xc9, 0x1f, 0x33, 0x9f, 0xfc, 0x39, 0xf3, 0x9d, 0x67, 0x33, 0x9f, 0x3c, 0xba, 0xf0, + 0x9d, 0xb3, 0x0b, 0xdf, 0x79, 0x72, 0xe1, 0x3b, 0xc3, 0x1a, 0x56, 0xf9, 0xc1, 0xdf, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x1e, 0x1c, 0xe1, 0x9c, 0x76, 0x08, 0x00, 0x00, } func (x RulesRequest_RuleType) String() string { @@ -640,6 +649,9 @@ func (this *RulesRequest) Equal(that interface{}) bool { return false } } + if this.ExcludeAlerts != that1.ExcludeAlerts { + return false + } return true } func (this *RulesResponse) Equal(that interface{}) bool { @@ -871,12 +883,13 @@ func (this *RulesRequest) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 8) + s := make([]string, 0, 9) s = append(s, "&ruler.RulesRequest{") s = append(s, "Filter: "+fmt.Sprintf("%#v", this.Filter)+",\n") s = append(s, "RuleName: "+fmt.Sprintf("%#v", this.RuleName)+",\n") s = append(s, "RuleGroup: "+fmt.Sprintf("%#v", this.RuleGroup)+",\n") s = append(s, "File: "+fmt.Sprintf("%#v", this.File)+",\n") + s = append(s, "ExcludeAlerts: "+fmt.Sprintf("%#v", this.ExcludeAlerts)+",\n") s = append(s, "}") return strings.Join(s, "") } @@ -1116,6 +1129,16 @@ func (m *RulesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ExcludeAlerts { + i-- + if m.ExcludeAlerts { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } if len(m.File) > 0 { for iNdEx := len(m.File) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.File[iNdEx]) @@ -1544,6 +1567,9 @@ func (m *RulesRequest) Size() (n int) { n += 1 + l + sovRuler(uint64(l)) } } + if m.ExcludeAlerts { + n += 2 + } return n } @@ -1699,6 +1725,7 @@ func (this *RulesRequest) String() string { `RuleName:` + fmt.Sprintf("%v", this.RuleName) + `,`, `RuleGroup:` + fmt.Sprintf("%v", this.RuleGroup) + `,`, `File:` + fmt.Sprintf("%v", this.File) + `,`, + `ExcludeAlerts:` + fmt.Sprintf("%v", this.ExcludeAlerts) + `,`, `}`, }, "") return s @@ -1947,6 +1974,26 @@ func (m *RulesRequest) Unmarshal(dAtA []byte) error { } m.File = append(m.File, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExcludeAlerts", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRuler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ExcludeAlerts = bool(v != 0) default: iNdEx = preIndex skippy, err := skipRuler(dAtA[iNdEx:]) diff --git a/pkg/ruler/ruler.proto b/pkg/ruler/ruler.proto index 15855e3d84c..5c75445b2ff 100644 --- a/pkg/ruler/ruler.proto +++ b/pkg/ruler/ruler.proto @@ -37,6 +37,7 @@ message RulesRequest { repeated string rule_name = 2; repeated string rule_group = 3; repeated string file = 4; + bool exclude_alerts = 5; } message RulesResponse { diff --git a/pkg/ruler/ruler_test.go b/pkg/ruler/ruler_test.go index 973c0203480..1fdc1e5be5b 100644 --- a/pkg/ruler/ruler_test.go +++ b/pkg/ruler/ruler_test.go @@ -35,6 +35,7 @@ import ( "github.com/prometheus/prometheus/model/rulefmt" "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/rules" promRules "github.com/prometheus/prometheus/rules" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/util/testutil" @@ -136,6 +137,7 @@ type prepareOptions struct { rulerAddrMap map[string]*Ruler rulerAddrAutoMap bool start bool + managerQueryFunc rules.QueryFunc } func applyPrepareOptions(t *testing.T, instanceID string, opts ...prepareOption) prepareOptions { @@ -197,6 +199,13 @@ func withPrometheusRegisterer(reg prometheus.Registerer) prepareOption { } } +// withManagerQueryFunc is a prepareOption that configures the query function to pass to the ruler manager. +func withManagerQueryFunc(queryFunc rules.QueryFunc) prepareOption { + return func(opts *prepareOptions) { + opts.managerQueryFunc = queryFunc + } +} + func prepareRuler(t *testing.T, cfg Config, storage rulestore.RuleStore, opts ...prepareOption) *Ruler { options := applyPrepareOptions(t, cfg.Ring.Common.InstanceID, opts...) manager := prepareRulerManager(t, cfg, opts...) @@ -227,15 +236,21 @@ func prepareRulerManager(t *testing.T, cfg Config, opts ...prepareOption) *Defau noopQueryable := storage.QueryableFunc(func(int64, int64) (storage.Querier, error) { return storage.NoopQuerier(), nil }) - noopQueryFunc := func(context.Context, string, time.Time) (promql.Vector, error) { - return nil, nil + + var queryFunc rules.QueryFunc + if options.managerQueryFunc != nil { + queryFunc = options.managerQueryFunc + } else { + queryFunc = func(context.Context, string, time.Time) (promql.Vector, error) { + return nil, nil + } } // Mock the pusher pusher := newPusherMock() pusher.MockPush(&mimirpb.WriteResponse{}, nil) - managerFactory := DefaultTenantManagerFactory(cfg, pusher, noopQueryable, noopQueryFunc, &NoopMultiTenantConcurrencyController{}, options.limits, options.registerer) + managerFactory := DefaultTenantManagerFactory(cfg, pusher, noopQueryable, queryFunc, &NoopMultiTenantConcurrencyController{}, options.limits, options.registerer) manager, err := NewDefaultMultiTenantManager(cfg, managerFactory, prometheus.NewRegistry(), options.logger, nil) require.NoError(t, err) @@ -342,6 +357,79 @@ func TestRuler_Rules(t *testing.T) { } } +func TestRuler_ExcludeAlerts(t *testing.T) { + alertingMockRules := map[string]rulespb.RuleGroupList{ + "user1": { + &rulespb.RuleGroupDesc{ + Name: "group1", + Namespace: "namespace1", + User: "user1", + SourceTenants: []string{"tenant-1"}, + Rules: []*rulespb.RuleDesc{createAlertingRule("testAlert", "up")}, + Interval: time.Duration(0 * time.Second), + }, + }, + } + + testCases := map[string]struct { + mockRules map[string]rulespb.RuleGroupList + userID string + excludeAlerts bool + expectedAlertsCount int + }{ + "rules - user1 - exclude_alerts=true does not return alerts": { + userID: "user1", + mockRules: alertingMockRules, + excludeAlerts: true, + expectedAlertsCount: 0, + }, + "rules - user1 - exclude_alerts=false returns alerts": { + userID: "user1", + mockRules: alertingMockRules, + excludeAlerts: false, + expectedAlertsCount: 1, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + cfg := defaultRulerConfig(t) + cfg.EvaluationInterval = time.Second + cfg.TenantFederation.Enabled = true + + // Mock the query function to return a constant vector + constantQueryFunc := func(context.Context, string, time.Time) (promql.Vector, error) { + return promql.Vector{ + {T: 12345, F: 1.0}, + }, nil + } + + r := prepareRuler(t, cfg, newMockRuleStore(tc.mockRules), withStart(), withManagerQueryFunc(constantQueryFunc)) + + // Rules will be synchronized asynchronously, so we wait until the expected number of rule groups + // has been synched. + ctx := user.InjectOrgID(context.Background(), tc.userID) + test.Poll(t, 5*time.Second, len(mockRules[tc.userID]), func() interface{} { + rls, _ := r.Rules(ctx, &RulesRequest{ExcludeAlerts: tc.excludeAlerts}) + return len(rls.Groups) + }) + + // Rules will be evaluated after some time + require.EventuallyWithT(t, func(c *assert.CollectT) { + rls, err := r.Rules(ctx, &RulesRequest{ExcludeAlerts: tc.excludeAlerts}) + assert.NoError(c, err) + assert.Len(c, rls.Groups, len(mockRules[tc.userID])) + + for _, ruleGroup := range rls.Groups { + for _, activeRule := range ruleGroup.ActiveRules { + assert.Len(c, activeRule.Alerts, tc.expectedAlertsCount) + } + } + }, time.Second*5, 1*time.Second) + }) + } +} + func compareRuleGroupDescToStateDesc(t *testing.T, expected *rulespb.RuleGroupDesc, got *GroupStateDesc) { t.Helper() From ac2ce93b74fb7a55905aa48f593c1f3343519792 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Sun, 15 Sep 2024 11:02:23 +0200 Subject: [PATCH 2/5] Changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4560403436..80c7362131a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ * [ENHANCEMENT] OTLP: If the flag `-distributor.otel-created-timestamp-zero-ingestion-enabled` is true, OTel start timestamps are converted to Prometheus zero samples to mark series start. #9131 * [ENHANCEMENT] Querier: attach logs emitted during query consistency check to trace span for query. #9213 * [ENHANCEMENT] Query-scheduler: Experimental `-query-scheduler.prioritize-query-components` flag enables the querier-worker queue priority algorithm to take precedence over tenant rotation when dequeuing requests. #9220 +* [ENHANCEMENT] Support `exclude_alerts` parameter in `/api/v1/rules` endpoint. #9300 * [BUGFIX] Ruler: add support for draining any outstanding alert notifications before shutting down. This can be enabled with the `-ruler.drain-notification-queue-on-shutdown=true` CLI flag. #8346 * [BUGFIX] Query-frontend: fix `-querier.max-query-lookback` enforcement when `-compactor.blocks-retention-period` is not set, and viceversa. #8388 * [BUGFIX] Ingester: fix sporadic `not found` error causing an internal server error if label names are queried with matchers during head compaction. #8391 From a1b50591050124f8c4a066f70dcc464050f04643 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Sun, 15 Sep 2024 11:03:52 +0200 Subject: [PATCH 3/5] Update the doc --- docs/sources/mimir/references/http-api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/mimir/references/http-api/index.md b/docs/sources/mimir/references/http-api/index.md index 6791b60b1a2..74f2f292da1 100644 --- a/docs/sources/mimir/references/http-api/index.md +++ b/docs/sources/mimir/references/http-api/index.md @@ -815,7 +815,7 @@ List all tenant rules. This endpoint is not part of ruler-API and is always avai ### List Prometheus rules ``` -GET /api/v1/rules?type={alert|record}&file={}&rule_group={}&rule_name={}&exclude_alerts={} +GET /api/v1/rules?type={alert|record}&file={}&rule_group={}&rule_name={}&exclude_alerts={true|false} ``` Prometheus-compatible rules endpoint to list alerting and recording rules that are currently loaded. From 9d699901c5d6b8ed9fde916a10d695a2eafd8f40 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 17 Sep 2024 15:05:11 +0200 Subject: [PATCH 4/5] Fix typos --- pkg/ruler/api.go | 2 +- pkg/ruler/api_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/ruler/api.go b/pkg/ruler/api.go index a271b64f55a..05f02a91f97 100644 --- a/pkg/ruler/api.go +++ b/pkg/ruler/api.go @@ -283,7 +283,7 @@ func parseExcludeAlerts(req *http.Request) (bool, error) { value, err := strconv.ParseBool(excludeAlerts) if err != nil { - return false, fmt.Errorf("unable to pase exclude_alerts value %w", err) + return false, fmt.Errorf("unable to parse exclude_alerts value %w", err) } return value, nil diff --git a/pkg/ruler/api_test.go b/pkg/ruler/api_test.go index ebfca7cdb96..c1e75340121 100644 --- a/pkg/ruler/api_test.go +++ b/pkg/ruler/api_test.go @@ -604,7 +604,7 @@ func TestRuler_PrometheusRules(t *testing.T) { }, }, }, - "Invalid exlude_alerts param": { + "Invalid exclude_alerts param": { configuredRules: rulespb.RuleGroupList{}, expectedConfigured: 0, queryParams: "?exclude_alerts=foo", From 29efdfff6d771b35e9da3b438e99a2d910818094 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 17 Sep 2024 17:50:10 +0200 Subject: [PATCH 5/5] Update docs/sources/mimir/references/http-api/index.md Co-authored-by: Taylor C <41653732+tacole02@users.noreply.github.com> --- docs/sources/mimir/references/http-api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/mimir/references/http-api/index.md b/docs/sources/mimir/references/http-api/index.md index 74f2f292da1..265fd01c130 100644 --- a/docs/sources/mimir/references/http-api/index.md +++ b/docs/sources/mimir/references/http-api/index.md @@ -824,7 +824,7 @@ The `type` parameter is optional. If set, only the specified type of rule is ret The `file`, `rule_group` and `rule_name` parameters are optional, and can accept multiple values. If set, the response content is filtered accordingly. -The `exclude_alerts` parameter is optional. If set, only rules are returned, active alerts are excluded. +The `exclude_alerts` parameter is optional. If set, it only returns rules and excludes active alerts. For more information, refer to Prometheus [rules](https://prometheus.io/docs/prometheus/latest/querying/api/#rules).