mirror of https://github.com/grafana/grafana.git
Alerting: Update prometheus api to reuse list query logic
This lets the prometheus api respect NoGroup query logic and treat non-grouped rules consistently. Co-authored-by: William Wernert <william.wernert@grafana.com>
This commit is contained in:
parent
ca8324e62a
commit
f65e219b21
|
|
@ -23,7 +23,7 @@ type RuleStore interface {
|
|||
GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) (*ngmodels.AlertRule, error)
|
||||
GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) ([]*ngmodels.AlertRule, error)
|
||||
ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error)
|
||||
ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesByGroupQuery) (ngmodels.RulesGroup, string, error)
|
||||
ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesExtendedQuery) (ngmodels.RulesGroup, string, error)
|
||||
ListDeletedRules(ctx context.Context, orgID int64) ([]*ngmodels.AlertRule, error)
|
||||
|
||||
// InsertAlertRules will insert all alert rules passed into the function
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ type ListAlertRulesStore interface {
|
|||
}
|
||||
|
||||
type ListAlertRulesStoreV2 interface {
|
||||
ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesByGroupQuery) (ngmodels.RulesGroup, string, error)
|
||||
ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesExtendedQuery) (ngmodels.RulesGroup, string, error)
|
||||
}
|
||||
|
||||
func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) response.Response {
|
||||
|
|
@ -487,7 +487,7 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt
|
|||
return ruleResponse
|
||||
}
|
||||
|
||||
byGroupQuery := ngmodels.ListAlertRulesByGroupQuery{
|
||||
byGroupQuery := ngmodels.ListAlertRulesExtendedQuery{
|
||||
ListAlertRulesQuery: ngmodels.ListAlertRulesQuery{
|
||||
OrgID: opts.OrgID,
|
||||
NamespaceUIDs: namespaceUIDs,
|
||||
|
|
@ -496,8 +496,8 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt
|
|||
RuleGroups: ruleGroups,
|
||||
ReceiverName: receiverName,
|
||||
},
|
||||
GroupLimit: maxGroups,
|
||||
GroupContinueToken: nextToken,
|
||||
Limit: maxGroups,
|
||||
ContinueToken: nextToken,
|
||||
}
|
||||
ruleList, continueToken, err := store.ListAlertRulesByGroup(opts.Ctx, &byGroupQuery)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -938,14 +938,6 @@ const (
|
|||
RuleTypeFilterRecording
|
||||
)
|
||||
|
||||
type ListAlertRulesByGroupQuery struct {
|
||||
ListAlertRulesQuery
|
||||
RuleType RuleTypeFilter
|
||||
|
||||
GroupLimit int64 // Number of groups to fetch
|
||||
GroupContinueToken string // Token for per-group pagination
|
||||
}
|
||||
|
||||
type GroupCursor struct {
|
||||
NamespaceUID string `json:"n"`
|
||||
RuleGroup string `json:"g"`
|
||||
|
|
|
|||
|
|
@ -584,80 +584,17 @@ func (st DBstore) CountInFolders(ctx context.Context, orgID int64, folderUIDs []
|
|||
return count, err
|
||||
}
|
||||
|
||||
func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesByGroupQuery) (result ngmodels.RulesGroup, nextToken string, err error) {
|
||||
func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesExtendedQuery) (result ngmodels.RulesGroup, nextToken string, err error) {
|
||||
err = st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
q := sess.Table("alert_rule")
|
||||
|
||||
if query.OrgID >= 0 {
|
||||
q = q.Where("org_id = ?", query.OrgID)
|
||||
q, groupsSet, err := st.buildListAlertRulesQuery(sess, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if query.DashboardUID != "" {
|
||||
q = q.Where("dashboard_uid = ?", query.DashboardUID)
|
||||
if query.PanelID != 0 {
|
||||
q = q.Where("panel_id = ?", query.PanelID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(query.NamespaceUIDs) > 0 {
|
||||
args, in := getINSubQueryArgs(query.NamespaceUIDs)
|
||||
q = q.Where(fmt.Sprintf("namespace_uid IN (%s)", strings.Join(in, ",")), args...)
|
||||
}
|
||||
|
||||
if len(query.RuleUIDs) > 0 {
|
||||
args, in := getINSubQueryArgs(query.RuleUIDs)
|
||||
q = q.Where(fmt.Sprintf("uid IN (%s)", strings.Join(in, ",")), args...)
|
||||
}
|
||||
|
||||
var groupsMap map[string]struct{}
|
||||
if len(query.RuleGroups) > 0 {
|
||||
groupsMap = make(map[string]struct{})
|
||||
args, in := getINSubQueryArgs(query.RuleGroups)
|
||||
q = q.Where(fmt.Sprintf("rule_group IN (%s)", strings.Join(in, ",")), args...)
|
||||
for _, group := range query.RuleGroups {
|
||||
groupsMap[group] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if query.ReceiverName != "" {
|
||||
q, err = st.filterByContentInNotificationSettings(query.ReceiverName, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if query.TimeIntervalName != "" {
|
||||
q, err = st.filterByContentInNotificationSettings(query.TimeIntervalName, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if query.HasPrometheusRuleDefinition != nil {
|
||||
q, err = st.filterWithPrometheusRuleDefinition(*query.HasPrometheusRuleDefinition, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch query.RuleType {
|
||||
case ngmodels.RuleTypeFilterAlerting:
|
||||
q = q.Where("record = ''")
|
||||
case ngmodels.RuleTypeFilterRecording:
|
||||
q = q.Where("record != ''")
|
||||
case ngmodels.RuleTypeFilterAll:
|
||||
// no additional filter
|
||||
default:
|
||||
return fmt.Errorf("unknown rule type filter %q", query.RuleType)
|
||||
}
|
||||
|
||||
// Order by group first, then by rule index within group
|
||||
q = q.Asc("namespace_uid", "rule_group", "rule_group_idx", "id")
|
||||
|
||||
var cursor ngmodels.GroupCursor
|
||||
if query.GroupContinueToken != "" {
|
||||
if query.ContinueToken != "" {
|
||||
// only set the cursor if it's valid, otherwise we'll start from the beginning
|
||||
if cur, err := ngmodels.DecodeGroupCursor(query.GroupContinueToken); err == nil {
|
||||
if cur, err := ngmodels.DecodeGroupCursor(query.ContinueToken); err == nil {
|
||||
cursor = cur
|
||||
}
|
||||
}
|
||||
|
|
@ -701,7 +638,7 @@ func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.Lis
|
|||
}
|
||||
if key != cursor {
|
||||
// Check if we've reached the group limit
|
||||
if query.GroupLimit > 0 && groupsFetched == query.GroupLimit {
|
||||
if query.Limit > 0 && groupsFetched == query.Limit {
|
||||
// Generate next token for the next group
|
||||
nextToken = ngmodels.EncodeGroupCursor(cursor)
|
||||
break
|
||||
|
|
@ -713,7 +650,7 @@ func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.Lis
|
|||
}
|
||||
|
||||
// Apply post-query filters
|
||||
if !shouldIncludeRule(&converted, query, groupsMap) {
|
||||
if !shouldIncludeRule(&converted, query, groupsSet) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -731,7 +668,7 @@ func buildGroupCursorCondition(sess *xorm.Session, c ngmodels.GroupCursor) *xorm
|
|||
Or("(namespace_uid = ? AND rule_group > ?)", c.NamespaceUID, c.RuleGroup)
|
||||
}
|
||||
|
||||
func shouldIncludeRule(rule *ngmodels.AlertRule, query *ngmodels.ListAlertRulesByGroupQuery, groupsMap map[string]struct{}) bool {
|
||||
func shouldIncludeRule(rule *ngmodels.AlertRule, query *ngmodels.ListAlertRulesExtendedQuery, groupsMap map[string]struct{}) bool {
|
||||
if query.ReceiverName != "" {
|
||||
if !slices.ContainsFunc(rule.NotificationSettings, func(settings ngmodels.NotificationSettings) bool {
|
||||
return settings.Receiver == query.ReceiverName
|
||||
|
|
@ -786,6 +723,24 @@ func (st DBstore) ListAlertRulesPaginated(ctx context.Context, query *ngmodels.L
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if query.ContinueToken != "" {
|
||||
cursor, err := decodeCursor(query.ContinueToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid continue token: %w", err)
|
||||
}
|
||||
|
||||
// Build cursor condition that matches the ORDER BY clause
|
||||
q = buildCursorCondition(q, cursor)
|
||||
}
|
||||
|
||||
if query.Limit > 0 {
|
||||
// Ensure we clamp to the max int available on the platform
|
||||
lim := min(query.Limit, math.MaxInt)
|
||||
// Fetch one extra rule to determine if there are more results
|
||||
q = q.Limit(int(lim) + 1)
|
||||
}
|
||||
|
||||
alertRules := make([]*ngmodels.AlertRule, 0)
|
||||
rule := new(alertRule)
|
||||
rows, err := q.Rows(rule)
|
||||
|
|
@ -919,23 +874,6 @@ func (st DBstore) buildListAlertRulesQuery(sess *db.Session, query *ngmodels.Lis
|
|||
}
|
||||
|
||||
q = q.Asc("namespace_uid", "rule_group", "rule_group_idx", "id")
|
||||
|
||||
if query.ContinueToken != "" {
|
||||
cursor, err := decodeCursor(query.ContinueToken)
|
||||
if err != nil {
|
||||
return nil, groupsSet, fmt.Errorf("invalid continue token: %w", err)
|
||||
}
|
||||
|
||||
// Build cursor condition that matches the ORDER BY clause
|
||||
q = buildCursorCondition(q, cursor)
|
||||
}
|
||||
|
||||
if query.Limit > 0 {
|
||||
// Ensure we clamp to the max int available on the platform
|
||||
lim := min(query.Limit, math.MaxInt)
|
||||
// Fetch one extra rule to determine if there are more results
|
||||
q = q.Limit(int(lim) + 1)
|
||||
}
|
||||
return q, groupsSet, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1757,7 +1757,7 @@ func TestIntegration_ListAlertRulesByGroup(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should return all rules when no limit passed", func(t *testing.T) {
|
||||
result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
|
||||
result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesExtendedQuery{
|
||||
ListAlertRulesQuery: models.ListAlertRulesQuery{OrgID: orgID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
|
@ -1768,9 +1768,9 @@ func TestIntegration_ListAlertRulesByGroup(t *testing.T) {
|
|||
t.Run("should return paginated results when group limit is set", func(t *testing.T) {
|
||||
// random number from 1 to totalGroups - 1 (to ensure we always receive less than totalGroups)
|
||||
groupLimit := rand.Int64N(int64(totalGroups)-1) + 1
|
||||
result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
|
||||
result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesExtendedQuery{
|
||||
ListAlertRulesQuery: models.ListAlertRulesQuery{OrgID: orgID},
|
||||
GroupLimit: groupLimit,
|
||||
Limit: groupLimit,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
expectedRuleCount := groupLimit * int64(rulesPerGroup)
|
||||
|
|
@ -1780,9 +1780,9 @@ func TestIntegration_ListAlertRulesByGroup(t *testing.T) {
|
|||
|
||||
t.Run("pagination should all for continuation", func(t *testing.T) {
|
||||
groupLimit := int64(2) // fixed group limit for this test
|
||||
result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
|
||||
result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesExtendedQuery{
|
||||
ListAlertRulesQuery: models.ListAlertRulesQuery{OrgID: orgID},
|
||||
GroupLimit: groupLimit,
|
||||
Limit: groupLimit,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, int(groupLimit*int64(rulesPerGroup)), "should return rules for the first two groups")
|
||||
|
|
@ -1798,9 +1798,9 @@ func TestIntegration_ListAlertRulesByGroup(t *testing.T) {
|
|||
resultRules = append(resultRules, result...)
|
||||
|
||||
// Continue from previous, fetching the rest of the rules
|
||||
result, continueToken, err = store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
|
||||
result, continueToken, err = store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesExtendedQuery{
|
||||
ListAlertRulesQuery: models.ListAlertRulesQuery{OrgID: orgID},
|
||||
GroupContinueToken: continueToken,
|
||||
ContinueToken: continueToken,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
resultRules = append(resultRules, result...)
|
||||
|
|
@ -1863,9 +1863,9 @@ func Benchmark_ListAlertRules(b *testing.B) {
|
|||
for _, groupLimit := range []int{1, 2, 5, 10, 50, 100} {
|
||||
b.Run(fmt.Sprintf("list %d groups paginated", groupLimit), func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_, _, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
|
||||
_, _, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesExtendedQuery{
|
||||
ListAlertRulesQuery: models.ListAlertRulesQuery{OrgID: orgID},
|
||||
GroupLimit: int64(groupLimit),
|
||||
Limit: int64(groupLimit),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ func (f *RuleStore) GetAlertRulesGroupByRuleUID(_ context.Context, q *models.Get
|
|||
return ruleList, nil
|
||||
}
|
||||
|
||||
func (f *RuleStore) ListAlertRulesByGroup(_ context.Context, q *models.ListAlertRulesByGroupQuery) (models.RulesGroup, string, error) {
|
||||
func (f *RuleStore) ListAlertRulesByGroup(_ context.Context, q *models.ListAlertRulesExtendedQuery) (models.RulesGroup, string, error) {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
f.RecordedOps = append(f.RecordedOps, *q)
|
||||
|
|
@ -228,13 +228,13 @@ func (f *RuleStore) ListAlertRulesByGroup(_ context.Context, q *models.ListAlert
|
|||
|
||||
var nextToken string
|
||||
var cursor models.GroupCursor
|
||||
if q.GroupContinueToken != "" {
|
||||
if cur, err := models.DecodeGroupCursor(q.GroupContinueToken); err == nil {
|
||||
if q.ContinueToken != "" {
|
||||
if cur, err := models.DecodeGroupCursor(q.ContinueToken); err == nil {
|
||||
cursor = cur
|
||||
}
|
||||
}
|
||||
|
||||
if q.GroupLimit < 0 {
|
||||
if q.Limit < 0 {
|
||||
return ruleList, "", nil
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ func (f *RuleStore) ListAlertRulesByGroup(_ context.Context, q *models.ListAlert
|
|||
RuleGroup: r.RuleGroup,
|
||||
}
|
||||
if key != cursor {
|
||||
if q.GroupLimit > 0 && groupsFetched == q.GroupLimit {
|
||||
if q.Limit > 0 && groupsFetched == q.Limit {
|
||||
nextToken = models.EncodeGroupCursor(cursor)
|
||||
break
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue