Skip to content

Commit

Permalink
add the paginated version of list alerts api
Browse files Browse the repository at this point in the history
  • Loading branch information
qinxx108 authored and alanprot committed May 2, 2024
1 parent e7caf35 commit a6ed57a
Show file tree
Hide file tree
Showing 21 changed files with 2,657 additions and 87 deletions.
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ type Options struct {
// GroupFunc returns a list of alert groups. The alerts are grouped
// according to the current active configuration. Alerts returned are
// filtered by the arguments provided to the function.
GroupFunc func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string)
GroupFunc func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool, func(string) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string)
// GroupInfoFunc returns a list of alert groups information. The alerts are grouped
// according to the current active configuration. This function will not return the alerts inside each group.
GroupInfoFunc func(func(*dispatch.Route) bool) dispatch.AlertGroupInfos
Expand Down
231 changes: 181 additions & 50 deletions api/v2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package v2

import (
"context"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -42,6 +43,7 @@ import (
"github.com/prometheus/alertmanager/api/v2/restapi/operations"
alert_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alert"
alertgroup_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroup"
alertinfo_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertinfo"
general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general"
receiver_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver"
silence_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence"
Expand Down Expand Up @@ -82,7 +84,7 @@ type API struct {
}

type (
groupsFn func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string)
groupsFn func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool, func(string) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string)
groupInfosFn func(func(*dispatch.Route) bool) dispatch.AlertGroupInfos
getAlertStatusFn func(prometheus_model.Fingerprint) types.AlertStatus
setAlertStatusFn func(prometheus_model.LabelSet)
Expand Down Expand Up @@ -135,6 +137,7 @@ func NewAPI(
}

openAPI.AlertGetAlertsHandler = alert_ops.GetAlertsHandlerFunc(api.getAlertsHandler)
openAPI.AlertinfoGetAlertInfosHandler = alertinfo_ops.GetAlertInfosHandlerFunc(api.getAlertInfosHandler)
openAPI.AlertPostAlertsHandler = alert_ops.PostAlertsHandlerFunc(api.postAlertsHandler)
openAPI.AlertgroupGetAlertGroupsHandler = alertgroup_ops.GetAlertGroupsHandlerFunc(api.getAlertGroupsHandler)
openAPI.AlertgroupinfolistGetAlertGroupInfoListHandler = alertgroupinfolist_ops.GetAlertGroupInfoListHandlerFunc(api.getAlertGroupInfoListHandler)
Expand Down Expand Up @@ -249,10 +252,7 @@ func (api *API) getReceiversHandler(params receiver_ops.GetReceiversParams) midd
func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Responder {
var (
receiverFilter *regexp.Regexp
// Initialize result slice to prevent api returning `null` when there
// are no alerts present
res = open_api_models.GettableAlerts{}
ctx = params.HTTPRequest.Context()
ctx = params.HTTPRequest.Context()

logger = api.requestLogger(params.HTTPRequest)
)
Expand All @@ -275,56 +275,87 @@ func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Re
}
}

alerts := api.alerts.GetPending()
defer alerts.Close()

alertFilter := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
now := time.Now()
alerts, err := api.getAlerts(ctx, receiverFilter, alertFilter)
if err != nil {
level.Error(logger).Log("msg", "Failed to get alerts", "err", err)
return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
}

api.mtx.RLock()
for a := range alerts.Next() {
if err = alerts.Err(); err != nil {
break
}
if err = ctx.Err(); err != nil {
break
}
callbackRes, err := api.apiCallback.V2GetAlertsCallback(alerts)
if err != nil {
level.Error(logger).Log("msg", "Failed to call api callback", "err", err)
return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
}

routes := api.route.Match(a.Labels)
receivers := make([]string, 0, len(routes))
for _, r := range routes {
receivers = append(receivers, r.RouteOpts.Receiver)
}
return alert_ops.NewGetAlertsOK().WithPayload(callbackRes)
}

if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
continue
}
func (api *API) getAlertInfosHandler(params alertinfo_ops.GetAlertInfosParams) middleware.Responder {
var (
alerts open_api_models.GettableAlerts
receiverFilter *regexp.Regexp
ctx = params.HTTPRequest.Context()

if !alertFilter(a, now) {
continue
logger = api.requestLogger(params.HTTPRequest)
)

matchers, err := parseFilter(params.Filter)
if err != nil {
level.Debug(logger).Log("msg", "Failed to parse matchers", "err", err)
return alertinfo_ops.NewGetAlertInfosBadRequest().WithPayload(err.Error())
}

if params.Receiver != nil {
receiverFilter, err = regexp.Compile("^(?:" + *params.Receiver + ")$")
if err != nil {
level.Debug(logger).Log("msg", "Failed to compile receiver regex", "err", err)
return alertinfo_ops.
NewGetAlertInfosBadRequest().
WithPayload(
fmt.Sprintf("failed to parse receiver param: %v", err.Error()),
)
}
}

alert := AlertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers)
if err = validateMaxResult(params.MaxResults); err != nil {
level.Error(logger).Log("msg", "Failed to parse MaxResults parameter", "err", err)
return alertinfo_ops.
NewGetAlertInfosBadRequest().
WithPayload(
fmt.Sprintf("failed to parse MaxResults param: %v", *params.MaxResults),
)
}

res = append(res, alert)
if err = validateAlertInfoNextToken(params.NextToken); err != nil {
level.Error(logger).Log("msg", "Failed to parse NextToken parameter", "err", err)
return alertinfo_ops.
NewGetAlertInfosBadRequest().
WithPayload(
fmt.Sprintf("failed to parse NextToken param: %v", *params.NextToken),
)
}
api.mtx.RUnlock()

alertFilter := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
groupIdsFilter := api.groupIDFilter(params.GroupID)
if len(params.GroupID) > 0 {
alerts, err = api.getAlertsFromAlertGroup(ctx, receiverFilter, alertFilter, groupIdsFilter)
} else {
alerts, err = api.getAlerts(ctx, receiverFilter, alertFilter)
}
if err != nil {
level.Error(logger).Log("msg", "Failed to get alerts", "err", err)
return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
return alertinfo_ops.NewGetAlertInfosInternalServerError().WithPayload(err.Error())
}
sort.Slice(res, func(i, j int) bool {
return *res[i].Fingerprint < *res[j].Fingerprint
})

callbackRes, err := api.apiCallback.V2GetAlertsCallback(res)
if err != nil {
level.Error(logger).Log("msg", "Failed to call api callback", "err", err)
return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
returnAlertInfos, nextItem := AlertInfosTruncate(alerts, params.MaxResults, params.NextToken)

response := &open_api_models.GettableAlertInfos{
Alerts: returnAlertInfos,
NextToken: nextItem,
}

return alert_ops.NewGetAlertsOK().WithPayload(callbackRes)
return alertinfo_ops.NewGetAlertInfosOK().WithPayload(response)
}

func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware.Responder {
Expand Down Expand Up @@ -411,18 +442,10 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams
}
}

rf := func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool {
return func(r *dispatch.Route) bool {
receiver := r.RouteOpts.Receiver
if receiverFilter != nil && !receiverFilter.MatchString(receiver) {
return false
}
return true
}
}(receiverFilter)

rf := api.routeFilter(receiverFilter)
af := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
alertGroups, allReceivers := api.alertGroups(rf, af)
gf := api.groupIDFilter([]string{})
alertGroups, allReceivers := api.alertGroups(rf, af, gf)

res := make(open_api_models.AlertGroups, 0, len(alertGroups))

Expand Down Expand Up @@ -524,6 +547,32 @@ func (api *API) getAlertGroupInfoListHandler(params alertgroupinfolist_ops.GetAl
return alertgroupinfolist_ops.NewGetAlertGroupInfoListOK().WithPayload(response)
}

func (api *API) groupIDFilter(groupIDsFilter []string) func(groupId string) bool {
return func(groupId string) bool {
if len(groupIDsFilter) <= 0 {
return true
}
for _, groupIDFilter := range groupIDsFilter {
if groupIDFilter == groupId {
return true
}
}
return false
}
}

func (api *API) routeFilter(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool {
return func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool {
return func(r *dispatch.Route) bool {
receiver := r.RouteOpts.Receiver
if receiverFilter != nil && !receiverFilter.MatchString(receiver) {
return false
}
return true
}
}(receiverFilter)
}

func (api *API) alertFilter(matchers []*labels.Matcher, silenced, inhibited, active bool) func(a *types.Alert, now time.Time) bool {
return func(a *types.Alert, now time.Time) bool {
if !a.EndsAt.IsZero() && a.EndsAt.Before(now) {
Expand Down Expand Up @@ -815,6 +864,78 @@ func getSwaggerSpec() (*loads.Document, *analysis.Spec, error) {
return swaggerSpec, swaggerSpecAnalysisCache, nil
}

func (api *API) getAlertsFromAlertGroup(ctx context.Context, receiverFilter *regexp.Regexp, alertFilter func(a *types.Alert, now time.Time) bool, groupIdsFilter func(groupId string) bool) (open_api_models.GettableAlerts, error) {
res := open_api_models.GettableAlerts{}
routeFilter := api.routeFilter(receiverFilter)
alertGroups, allReceivers := api.alertGroups(routeFilter, alertFilter, groupIdsFilter)
for _, alertGroup := range alertGroups {
for _, alert := range alertGroup.Alerts {
if err := ctx.Err(); err != nil {
break
}
fp := alert.Fingerprint()
receivers := allReceivers[fp]
status := api.getAlertStatus(fp)
apiAlert := AlertToOpenAPIAlert(alert, status, receivers)
res = append(res, apiAlert)
}
}

sort.Slice(res, func(i, j int) bool {
return *res[i].Fingerprint < *res[j].Fingerprint
})

return res, nil
}

func (api *API) getAlerts(ctx context.Context, receiverFilter *regexp.Regexp, alertFilter func(a *types.Alert, now time.Time) bool) (open_api_models.GettableAlerts, error) {
var err error
res := open_api_models.GettableAlerts{}

alerts := api.alerts.GetPending()
defer alerts.Close()

now := time.Now()

api.mtx.RLock()
for a := range alerts.Next() {
if err = alerts.Err(); err != nil {
break
}
if err = ctx.Err(); err != nil {
break
}

routes := api.route.Match(a.Labels)
receivers := make([]string, 0, len(routes))
for _, r := range routes {
receivers = append(receivers, r.RouteOpts.Receiver)
}

if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
continue
}

if !alertFilter(a, now) {
continue
}

alert := AlertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers)

res = append(res, alert)
}
api.mtx.RUnlock()

if err != nil {
return res, err
}
sort.Slice(res, func(i, j int) bool {
return *res[i].Fingerprint < *res[j].Fingerprint
})

return res, nil
}

func validateMaxResult(maxItem *int64) error {
if maxItem != nil {
if *maxItem < 0 {
Expand All @@ -824,6 +945,16 @@ func validateMaxResult(maxItem *int64) error {
return nil
}

func validateAlertInfoNextToken(nextToken *string) error {
if nextToken != nil {
_, err := prometheus_model.ParseFingerprint(*nextToken)
if err != nil {
return err
}
}
return nil
}

func validateNextToken(nextToken *string) error {
if nextToken != nil {
match, _ := regexp.MatchString("^[a-fA-F0-9]{40}$", *nextToken)
Expand Down
Loading

0 comments on commit a6ed57a

Please sign in to comment.