Skip to content

Commit

Permalink
ruler: add filtering support for prometheus rules api (grafana#11817)
Browse files Browse the repository at this point in the history
**What this PR does / why we need it**:
Adds `rule_name`, `rule_group`, `file` and `type` query parameters for
filtering the response of `/prometheus/api/v1/rules` endpoint.
Replicates mimir's functionality:
grafana/mimir#5291

- all of them are optional.
- `type` paremeter accepts either `alert` or `record`
- `rule_name`, `rule_group`, `file` can accept multiple values and they
filter the response accordingly.




There is a minor change in behavior: `/prometheus/api/v1/rules` endpoint
will no longer return empty rule groups which is inline with both
[prometheus](https://github.com/prometheus/prometheus/pull/12270/files#diff-315f251cdd7e93fcec1e7e9505744da1d1828f30d2b61d1f4ce963fa26bf1909R1403)
and
[mimir](https://github.com/grafana/mimir/pull/5291/files#diff-e5424c21c0e827bd1c9d3f669ed605897696bdc27993bc8bfd7113eba787b49dR1120).
This is not a breaking change since rule groups with [no rules fail
validation](https://github.com/grafana/loki/blob/27fbd62505f4412e3cb9180b1a5a66518bba9752/pkg/ruler/base/manager.go#L295)
and cannot be created.

**Which issue(s) this PR fixes**:
Fixes grafana#9295

**Special notes for your reviewer**:

**Checklist**
- [x] Reviewed the
[`CONTRIBUTING.md`](https://github.com/grafana/loki/blob/main/CONTRIBUTING.md)
guide (**required**)
- [x] Documentation added
- [x] Tests updated
- [x] `CHANGELOG.md` updated
- [ ] If the change is worth mentioning in the release notes, add
`add-to-release-notes` label
- [ ] Changes that require user attention or interaction to upgrade are
documented in `docs/sources/setup/upgrade/_index.md`
- [ ] For Helm chart changes bump the Helm chart version in
`production/helm/loki/Chart.yaml` and update
`production/helm/loki/CHANGELOG.md` and
`production/helm/loki/README.md`. [Example
PR](grafana@d10549e)
- [ ] If the change is deprecating or removing a configuration option,
update the `deprecated-config.yaml` and `deleted-config.yaml` files
respectively in the `tools/deprecated-config-checker` directory.
[Example
PR](grafana@0d4416a)
  • Loading branch information
ashwanthgoli authored and rhnasc committed Apr 12, 2024
1 parent 1d02cdf commit ba66e1a
Show file tree
Hide file tree
Showing 8 changed files with 1,126 additions and 246 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
* [11679](https://github.com/grafana/loki/pull/11679) **dannykopping** Cache: extending #11535 to align custom ingester query split with cache keys for correct caching of results.
* [11143](https://github.com/grafana/loki/pull/11143) **sandeepsukhani** otel: Add support for per tenant configuration for mapping otlp data to loki format
* [11499](https://github.com/grafana/loki/pull/11284) **jmichalek132** Config: Adds `frontend.log-query-request-headers` to enable logging of request headers in query logs.
* [11817](https://github.com/grafana/loki/pull/11817) **ashwanthgoli** Ruler: Add support for filtering results of `/prometheus/api/v1/rules` endpoint by rule_name, rule_group, file and type.

##### Fixes
* [11074](https://github.com/grafana/loki/pull/11074) **hainenber** Fix panic in lambda-promtail due to mishandling of empty DROP_LABELS env var.
Expand Down
6 changes: 5 additions & 1 deletion docs/sources/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1178,11 +1178,15 @@ Deletes all the rule groups in a namespace (including the namespace itself). Thi
### List rules

```
GET /prometheus/api/v1/rules
GET /prometheus/api/v1/rules?type={alert|record}&file={}&rule_group={}&rule_name={}
```

Prometheus-compatible rules endpoint to list alerting and recording rules that are currently loaded.

The `type` parameter is optional. If set, only the specified type of rule is returned.

The `file`, `rule_group` and `rule_name` parameters are optional, and can accept multiple values. If set, the response content is filtered accordingly.

For more information, refer to the [Prometheus rules](https://prometheus.io/docs/prometheus/latest/querying/api/#rules) documentation.

### List alerts
Expand Down
68 changes: 48 additions & 20 deletions pkg/ruler/base/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package base

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
Expand Down Expand Up @@ -101,10 +102,10 @@ type recordingRule struct {
EvaluationTime float64 `json:"evaluationTime"`
}

func respondError(logger log.Logger, w http.ResponseWriter, msg string) {
func respondError(logger log.Logger, w http.ResponseWriter, status int, errorType v1.ErrorType, msg string) {
b, err := json.Marshal(&response{
Status: "error",
ErrorType: v1.ErrServer,
ErrorType: errorType,
Error: msg,
Data: nil,
})
Expand All @@ -115,12 +116,20 @@ func respondError(logger log.Logger, w http.ResponseWriter, msg string) {
return
}

w.WriteHeader(http.StatusInternalServerError)
w.WriteHeader(status)
if n, err := w.Write(b); err != nil {
level.Error(logger).Log("msg", "error writing response", "bytesWritten", n, "err", err)
}
}

func respondInvalidRequest(logger log.Logger, w http.ResponseWriter, msg string) {
respondError(logger, w, http.StatusBadRequest, v1.ErrBadData, msg)
}

func respondServerError(logger log.Logger, w http.ResponseWriter, msg string) {
respondError(logger, w, http.StatusInternalServerError, v1.ErrServer, msg)
}

// API is used to handle HTTP requests for the ruler service
type API struct {
ruler *Ruler
Expand All @@ -143,15 +152,34 @@ func (a *API) PrometheusRules(w http.ResponseWriter, req *http.Request) {
userID, err := tenant.TenantID(req.Context())
if err != nil || userID == "" {
level.Error(logger).Log("msg", "error extracting org id from context", "err", err)
respondError(logger, w, "no valid org id found")
respondServerError(logger, w, "no valid org id found")
return
}

w.Header().Set("Content-Type", "application/json")
rgs, err := a.ruler.GetRules(req.Context())
var rulesReq = RulesRequest{
Filter: AnyRule,
RuleName: req.URL.Query()["rule_name"],
RuleGroup: req.URL.Query()["rule_group"],
File: req.URL.Query()["file"],
}

ruleTypeFilter := strings.ToLower(req.URL.Query().Get("type"))
if ruleTypeFilter != "" {
switch ruleTypeFilter {
case "alert":
rulesReq.Filter = AlertingRule
case "record":
rulesReq.Filter = RecordingRule
default:
respondInvalidRequest(logger, w, fmt.Sprintf("not supported value %q", ruleTypeFilter))
return
}
}

rgs, err := a.ruler.GetRules(req.Context(), &rulesReq)

if err != nil {
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand Down Expand Up @@ -221,7 +249,7 @@ func (a *API) PrometheusRules(w http.ResponseWriter, req *http.Request) {
})
if err != nil {
level.Error(logger).Log("msg", "error marshaling json response", "err", err)
respondError(logger, w, "unable to marshal the requested data")
respondServerError(logger, w, "unable to marshal the requested data")
return
}
w.Header().Set("Content-Type", "application/json")
Expand All @@ -236,15 +264,15 @@ func (a *API) PrometheusAlerts(w http.ResponseWriter, req *http.Request) {
userID, err := tenant.TenantID(req.Context())
if err != nil || userID == "" {
level.Error(logger).Log("msg", "error extracting org id from context", "err", err)
respondError(logger, w, "no valid org id found")
respondServerError(logger, w, "no valid org id found")
return
}

w.Header().Set("Content-Type", "application/json")
rgs, err := a.ruler.GetRules(req.Context())
rgs, err := a.ruler.GetRules(req.Context(), &RulesRequest{Filter: AlertingRule})

if err != nil {
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand Down Expand Up @@ -272,7 +300,7 @@ func (a *API) PrometheusAlerts(w http.ResponseWriter, req *http.Request) {
})
if err != nil {
level.Error(logger).Log("msg", "error marshaling json response", "err", err)
respondError(logger, w, "unable to marshal the requested data")
respondServerError(logger, w, "unable to marshal the requested data")
return
}
w.Header().Set("Content-Type", "application/json")
Expand Down Expand Up @@ -314,7 +342,7 @@ func respondAccepted(w http.ResponseWriter, logger log.Logger) {
})
if err != nil {
level.Error(logger).Log("msg", "error marshaling json response", "err", err)
respondError(logger, w, "unable to marshal the requested data")
respondServerError(logger, w, "unable to marshal the requested data")
return
}
w.Header().Set("Content-Type", "application/json")
Expand Down Expand Up @@ -466,7 +494,7 @@ func (a *API) ListRules(w http.ResponseWriter, req *http.Request) {

pr, err := parseRequest(req, false, false)
if err != nil {
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand Down Expand Up @@ -504,7 +532,7 @@ func (a *API) GetRuleGroup(w http.ResponseWriter, req *http.Request) {
logger := util_log.WithContext(req.Context(), a.logger)
pr, err := parseRequest(req, true, true)
if err != nil {
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand All @@ -526,7 +554,7 @@ func (a *API) CreateRuleGroup(w http.ResponseWriter, req *http.Request) {
logger := util_log.WithContext(req.Context(), a.logger)
pr, err := parseRequest(req, true, false)
if err != nil {
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand Down Expand Up @@ -600,7 +628,7 @@ func (a *API) DeleteNamespace(w http.ResponseWriter, req *http.Request) {

pr, err := parseRequest(req, true, false)
if err != nil {
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand All @@ -610,7 +638,7 @@ func (a *API) DeleteNamespace(w http.ResponseWriter, req *http.Request) {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand All @@ -622,7 +650,7 @@ func (a *API) DeleteRuleGroup(w http.ResponseWriter, req *http.Request) {

pr, err := parseRequest(req, true, true)
if err != nil {
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand All @@ -632,7 +660,7 @@ func (a *API) DeleteRuleGroup(w http.ResponseWriter, req *http.Request) {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
respondError(logger, w, err.Error())
respondServerError(logger, w, err.Error())
return
}

Expand Down

0 comments on commit ba66e1a

Please sign in to comment.