Skip to content

Commit 14ac82e

Browse files
qinxx108yeya24
authored andcommitted
add the paginated version of list alerts api
1 parent b3868a3 commit 14ac82e

21 files changed

+2657
-87
lines changed

api/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ type Options struct {
7979
// GroupFunc returns a list of alert groups. The alerts are grouped
8080
// according to the current active configuration. Alerts returned are
8181
// filtered by the arguments provided to the function.
82-
GroupFunc func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string)
82+
GroupFunc func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool, func(string) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string)
8383
// GroupInfoFunc returns a list of alert groups information. The alerts are grouped
8484
// according to the current active configuration. This function will not return the alerts inside each group.
8585
GroupInfoFunc func(func(*dispatch.Route) bool) dispatch.AlertGroupInfos

api/v2/api.go

Lines changed: 181 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package v2
1515

1616
import (
17+
"context"
1718
"errors"
1819
"fmt"
1920
"log/slog"
@@ -40,6 +41,7 @@ import (
4041
"github.com/prometheus/alertmanager/api/v2/restapi/operations"
4142
alert_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alert"
4243
alertgroup_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroup"
44+
alertinfo_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertinfo"
4345
general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general"
4446
receiver_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver"
4547
silence_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence"
@@ -82,7 +84,7 @@ type API struct {
8284
}
8385

8486
type (
85-
groupsFn func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string)
87+
groupsFn func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool, func(string) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string)
8688
groupMutedFunc func(routeID, groupKey string) ([]string, bool)
8789
groupInfosFn func(func(*dispatch.Route) bool) dispatch.AlertGroupInfos
8890
getAlertStatusFn func(prometheus_model.Fingerprint) types.AlertStatus
@@ -138,6 +140,7 @@ func NewAPI(
138140
}
139141

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

260260
logger = api.requestLogger(params.HTTPRequest)
261261
)
@@ -278,56 +278,87 @@ func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Re
278278
}
279279
}
280280

281-
alerts := api.alerts.GetPending()
282-
defer alerts.Close()
283-
284281
alertFilter := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
285-
now := time.Now()
282+
alerts, err := api.getAlerts(ctx, receiverFilter, alertFilter)
283+
if err != nil {
284+
logger.Error("Failed to get alerts", "err", err)
285+
return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
286+
}
286287

287-
api.mtx.RLock()
288-
for a := range alerts.Next() {
289-
if err = alerts.Err(); err != nil {
290-
break
291-
}
292-
if err = ctx.Err(); err != nil {
293-
break
294-
}
288+
callbackRes, err := api.apiCallback.V2GetAlertsCallback(alerts)
289+
if err != nil {
290+
logger.Error("Failed to call api callback", "err", err)
291+
return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
292+
}
295293

296-
routes := api.route.Match(a.Labels)
297-
receivers := make([]string, 0, len(routes))
298-
for _, r := range routes {
299-
receivers = append(receivers, r.RouteOpts.Receiver)
300-
}
294+
return alert_ops.NewGetAlertsOK().WithPayload(callbackRes)
295+
}
301296

302-
if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
303-
continue
304-
}
297+
func (api *API) getAlertInfosHandler(params alertinfo_ops.GetAlertInfosParams) middleware.Responder {
298+
var (
299+
alerts open_api_models.GettableAlerts
300+
receiverFilter *regexp.Regexp
301+
ctx = params.HTTPRequest.Context()
305302

306-
if !alertFilter(a, now) {
307-
continue
303+
logger = api.requestLogger(params.HTTPRequest)
304+
)
305+
306+
matchers, err := parseFilter(params.Filter)
307+
if err != nil {
308+
logger.Debug("Failed to parse matchers", "err", err)
309+
return alertinfo_ops.NewGetAlertInfosBadRequest().WithPayload(err.Error())
310+
}
311+
312+
if params.Receiver != nil {
313+
receiverFilter, err = regexp.Compile("^(?:" + *params.Receiver + ")$")
314+
if err != nil {
315+
logger.Debug("Failed to compile receiver regex", "err", err)
316+
return alertinfo_ops.
317+
NewGetAlertInfosBadRequest().
318+
WithPayload(
319+
fmt.Sprintf("failed to parse receiver param: %v", err.Error()),
320+
)
308321
}
322+
}
309323

310-
alert := AlertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers, nil)
324+
if err = validateMaxResult(params.MaxResults); err != nil {
325+
logger.Error("Failed to parse MaxResults parameter", "err", err)
326+
return alertinfo_ops.
327+
NewGetAlertInfosBadRequest().
328+
WithPayload(
329+
fmt.Sprintf("failed to parse MaxResults param: %v", *params.MaxResults),
330+
)
331+
}
311332

312-
res = append(res, alert)
333+
if err = validateAlertInfoNextToken(params.NextToken); err != nil {
334+
logger.Error("Failed to parse NextToken parameter", "err", err)
335+
return alertinfo_ops.
336+
NewGetAlertInfosBadRequest().
337+
WithPayload(
338+
fmt.Sprintf("failed to parse NextToken param: %v", *params.NextToken),
339+
)
313340
}
314-
api.mtx.RUnlock()
315341

342+
alertFilter := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
343+
groupIdsFilter := api.groupIDFilter(params.GroupID)
344+
if len(params.GroupID) > 0 {
345+
alerts, err = api.getAlertsFromAlertGroup(ctx, receiverFilter, alertFilter, groupIdsFilter)
346+
} else {
347+
alerts, err = api.getAlerts(ctx, receiverFilter, alertFilter)
348+
}
316349
if err != nil {
317350
logger.Error("Failed to get alerts", "err", err)
318-
return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
351+
return alertinfo_ops.NewGetAlertInfosInternalServerError().WithPayload(err.Error())
319352
}
320-
sort.Slice(res, func(i, j int) bool {
321-
return *res[i].Fingerprint < *res[j].Fingerprint
322-
})
323353

324-
callbackRes, err := api.apiCallback.V2GetAlertsCallback(res)
325-
if err != nil {
326-
logger.Error("Failed to call api callback", "err", err)
327-
return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error())
354+
returnAlertInfos, nextItem := AlertInfosTruncate(alerts, params.MaxResults, params.NextToken)
355+
356+
response := &open_api_models.GettableAlertInfos{
357+
Alerts: returnAlertInfos,
358+
NextToken: nextItem,
328359
}
329360

330-
return alert_ops.NewGetAlertsOK().WithPayload(callbackRes)
361+
return alertinfo_ops.NewGetAlertInfosOK().WithPayload(response)
331362
}
332363

333364
func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware.Responder {
@@ -414,18 +445,10 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams
414445
}
415446
}
416447

417-
rf := func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool {
418-
return func(r *dispatch.Route) bool {
419-
receiver := r.RouteOpts.Receiver
420-
if receiverFilter != nil && !receiverFilter.MatchString(receiver) {
421-
return false
422-
}
423-
return true
424-
}
425-
}(receiverFilter)
426-
448+
rf := api.routeFilter(receiverFilter)
427449
af := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
428-
alertGroups, allReceivers := api.alertGroups(rf, af)
450+
gf := api.groupIDFilter([]string{})
451+
alertGroups, allReceivers := api.alertGroups(rf, af, gf)
429452

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

@@ -532,6 +555,32 @@ func (api *API) getAlertGroupInfoListHandler(params alertgroupinfolist_ops.GetAl
532555
return alertgroupinfolist_ops.NewGetAlertGroupInfoListOK().WithPayload(response)
533556
}
534557

558+
func (api *API) groupIDFilter(groupIDsFilter []string) func(groupId string) bool {
559+
return func(groupId string) bool {
560+
if len(groupIDsFilter) <= 0 {
561+
return true
562+
}
563+
for _, groupIDFilter := range groupIDsFilter {
564+
if groupIDFilter == groupId {
565+
return true
566+
}
567+
}
568+
return false
569+
}
570+
}
571+
572+
func (api *API) routeFilter(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool {
573+
return func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool {
574+
return func(r *dispatch.Route) bool {
575+
receiver := r.RouteOpts.Receiver
576+
if receiverFilter != nil && !receiverFilter.MatchString(receiver) {
577+
return false
578+
}
579+
return true
580+
}
581+
}(receiverFilter)
582+
}
583+
535584
func (api *API) alertFilter(matchers []*labels.Matcher, silenced, inhibited, active bool) func(a *types.Alert, now time.Time) bool {
536585
return func(a *types.Alert, now time.Time) bool {
537586
if !a.EndsAt.IsZero() && a.EndsAt.Before(now) {
@@ -822,6 +871,78 @@ func getSwaggerSpec() (*loads.Document, *analysis.Spec, error) {
822871
return swaggerSpec, swaggerSpecAnalysisCache, nil
823872
}
824873

874+
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) {
875+
res := open_api_models.GettableAlerts{}
876+
routeFilter := api.routeFilter(receiverFilter)
877+
alertGroups, allReceivers := api.alertGroups(routeFilter, alertFilter, groupIdsFilter)
878+
for _, alertGroup := range alertGroups {
879+
for _, alert := range alertGroup.Alerts {
880+
if err := ctx.Err(); err != nil {
881+
break
882+
}
883+
fp := alert.Fingerprint()
884+
receivers := allReceivers[fp]
885+
status := api.getAlertStatus(fp)
886+
apiAlert := AlertToOpenAPIAlert(alert, status, receivers)
887+
res = append(res, apiAlert)
888+
}
889+
}
890+
891+
sort.Slice(res, func(i, j int) bool {
892+
return *res[i].Fingerprint < *res[j].Fingerprint
893+
})
894+
895+
return res, nil
896+
}
897+
898+
func (api *API) getAlerts(ctx context.Context, receiverFilter *regexp.Regexp, alertFilter func(a *types.Alert, now time.Time) bool) (open_api_models.GettableAlerts, error) {
899+
var err error
900+
res := open_api_models.GettableAlerts{}
901+
902+
alerts := api.alerts.GetPending()
903+
defer alerts.Close()
904+
905+
now := time.Now()
906+
907+
api.mtx.RLock()
908+
for a := range alerts.Next() {
909+
if err = alerts.Err(); err != nil {
910+
break
911+
}
912+
if err = ctx.Err(); err != nil {
913+
break
914+
}
915+
916+
routes := api.route.Match(a.Labels)
917+
receivers := make([]string, 0, len(routes))
918+
for _, r := range routes {
919+
receivers = append(receivers, r.RouteOpts.Receiver)
920+
}
921+
922+
if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
923+
continue
924+
}
925+
926+
if !alertFilter(a, now) {
927+
continue
928+
}
929+
930+
alert := AlertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers, nil)
931+
932+
res = append(res, alert)
933+
}
934+
api.mtx.RUnlock()
935+
936+
if err != nil {
937+
return res, err
938+
}
939+
sort.Slice(res, func(i, j int) bool {
940+
return *res[i].Fingerprint < *res[j].Fingerprint
941+
})
942+
943+
return res, nil
944+
}
945+
825946
func validateMaxResult(maxItem *int64) error {
826947
if maxItem != nil {
827948
if *maxItem < 0 {
@@ -831,6 +952,16 @@ func validateMaxResult(maxItem *int64) error {
831952
return nil
832953
}
833954

955+
func validateAlertInfoNextToken(nextToken *string) error {
956+
if nextToken != nil {
957+
_, err := prometheus_model.ParseFingerprint(*nextToken)
958+
if err != nil {
959+
return err
960+
}
961+
}
962+
return nil
963+
}
964+
834965
func validateNextToken(nextToken *string) error {
835966
if nextToken != nil {
836967
match, _ := regexp.MatchString("^[a-fA-F0-9]{40}$", *nextToken)

0 commit comments

Comments
 (0)