Alerting: Consistently return Prometheus-style responses from rules APIs. (#86600)

* Alerting: Consistently return Prometheus-style responses from rules APIs.

This commit is part refactor and part fix. The /rules API occasionally returns
error responses which are inconsistent with other error responses. This fixes
that, and adds a function to map from Prometheus error type and HTTP code.

* Fix integration tests

* Linter happiness

* Make linter more happy

* Fix up one more place returning non-Prometheus responses
This commit is contained in:
Steve Simpson
2024-04-19 21:03:20 +02:00
committed by GitHub
parent 9ed616a7b3
commit 6ea97e41fb
4 changed files with 116 additions and 34 deletions
+49 -31
View File
@@ -95,7 +95,7 @@ func (srv PrometheusSrv) RouteGetAlertStatuses(c *contextmodel.ReqContext) respo
})
}
return response.JSON(http.StatusOK, alertResponse)
return response.JSON(alertResponse.HTTPStatusCode(), alertResponse)
}
func formatValues(alertState *state.State) string {
@@ -175,31 +175,6 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
// As we are using req.Form directly, this triggers a call to ParseForm() if needed.
c.Query("")
dashboardUID := c.Query("dashboard_uid")
panelID, err := getPanelIDFromRequest(c.Req)
if err != nil {
return ErrResp(http.StatusBadRequest, err, "invalid panel_id")
}
if dashboardUID == "" && panelID != 0 {
return ErrResp(http.StatusBadRequest, errors.New("panel_id must be set with dashboard_uid"), "")
}
limitGroups := getInt64WithDefault(c.Req.Form, "limit", -1)
limitRulesPerGroup := getInt64WithDefault(c.Req.Form, "limit_rules", -1)
limitAlertsPerRule := getInt64WithDefault(c.Req.Form, "limit_alerts", -1)
matchers, err := getMatchersFromRequest(c.Req)
if err != nil {
return ErrResp(http.StatusBadRequest, err, "")
}
withStates, err := getStatesFromRequest(c.Req)
if err != nil {
return ErrResp(http.StatusBadRequest, err, "")
}
withStatesFast := make(map[eval.State]struct{})
for _, state := range withStates {
withStatesFast[state] = struct{}{}
}
ruleResponse := apimodels.RuleResponse{
DiscoveryBase: apimodels.DiscoveryBase{
Status: "success",
@@ -209,6 +184,43 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
},
}
dashboardUID := c.Query("dashboard_uid")
panelID, err := getPanelIDFromRequest(c.Req)
if err != nil {
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = fmt.Sprintf("invalid panel_id: %s", err.Error())
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
if dashboardUID == "" && panelID != 0 {
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = "panel_id must be set with dashboard_uid"
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
limitGroups := getInt64WithDefault(c.Req.Form, "limit", -1)
limitRulesPerGroup := getInt64WithDefault(c.Req.Form, "limit_rules", -1)
limitAlertsPerRule := getInt64WithDefault(c.Req.Form, "limit_alerts", -1)
matchers, err := getMatchersFromRequest(c.Req)
if err != nil {
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = err.Error()
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
withStates, err := getStatesFromRequest(c.Req)
if err != nil {
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = err.Error()
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
withStatesFast := make(map[eval.State]struct{})
for _, state := range withStates {
withStatesFast[state] = struct{}{}
}
var labelOptions []ngmodels.LabelOption
if !getBoolWithDefault(c.Req.Form, queryIncludeInternalLabels, false) {
labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels())
@@ -216,12 +228,15 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
namespaceMap, err := srv.store.GetUserVisibleNamespaces(c.Req.Context(), c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to get namespaces visible to the user")
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = fmt.Sprintf("failed to get namespaces visible to the user: %s", err.Error())
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
if len(namespaceMap) == 0 {
srv.log.Debug("User does not have access to any namespaces")
return response.JSON(http.StatusOK, ruleResponse)
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
namespaceUIDs := make([]string, len(namespaceMap))
@@ -240,7 +255,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = fmt.Sprintf("failure getting rules: %s", err.Error())
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer
return response.JSON(http.StatusInternalServerError, ruleResponse)
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
// Group rules together by Namespace and Rule Group. Rules are also grouped by Org ID,
@@ -267,7 +282,10 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
}
ok, err := srv.authz.HasAccessToRuleGroup(c.Req.Context(), c.SignedInUser, rules)
if err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "cannot authorize access to rule group", err)
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = fmt.Sprintf("cannot authorize access to rule group: %s", err.Error())
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
if !ok {
continue
@@ -317,7 +335,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
ruleResponse.Data.RuleGroups = ruleResponse.Data.RuleGroups[0:limitGroups]
}
return response.JSON(http.StatusOK, ruleResponse)
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
// This is the same as matchers.Matches but avoids the need to create a LabelSet