Alerting: Refactor annotation historian to isolate dashboard service dependency (#71689)

* Refactor annotation historian to isolate dashboard service dependency

* Export PanelKey

* Don't export parsePanelKey

* Remove commented out code
This commit is contained in:
Alexander Weaver 2023-07-18 08:18:55 -05:00 committed by GitHub
parent f7bffb4c1c
commit 18b910e654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 94 additions and 57 deletions

View File

@ -420,7 +420,8 @@ func configureHistorianBackend(ctx context.Context, cfg setting.UnifiedAlertingS
return historian.NewMultipleBackend(primary, secondaries...), nil return historian.NewMultipleBackend(primary, secondaries...), nil
} }
if backend == historian.BackendTypeAnnotations { if backend == historian.BackendTypeAnnotations {
return historian.NewAnnotationBackend(ar, ds, rs, met), nil store := historian.NewAnnotationStore(ar, ds, met)
return historian.NewAnnotationBackend(store, rs, met), nil
} }
if backend == historian.BackendTypeLoki { if backend == historian.BackendTypeLoki {
lcfg, err := historian.NewLokiConfig(cfg) lcfg, err := historian.NewLokiConfig(cfg)

View File

@ -16,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/metrics"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
@ -26,12 +25,11 @@ import (
// AnnotationBackend is an implementation of state.Historian that uses Grafana Annotations as the backing datastore. // AnnotationBackend is an implementation of state.Historian that uses Grafana Annotations as the backing datastore.
type AnnotationBackend struct { type AnnotationBackend struct {
annotations AnnotationStore store AnnotationStore
dashboards *dashboardResolver rules RuleStore
rules RuleStore clock clock.Clock
clock clock.Clock metrics *metrics.Historian
metrics *metrics.Historian log log.Logger
log log.Logger
} }
type RuleStore interface { type RuleStore interface {
@ -40,18 +38,17 @@ type RuleStore interface {
type AnnotationStore interface { type AnnotationStore interface {
Find(ctx context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) Find(ctx context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error)
SaveMany(ctx context.Context, items []annotations.Item) error Save(ctx context.Context, panel *PanelKey, annotations []annotations.Item, orgID int64, logger log.Logger) error
} }
func NewAnnotationBackend(annotations AnnotationStore, dashboards dashboards.DashboardService, rules RuleStore, metrics *metrics.Historian) *AnnotationBackend { func NewAnnotationBackend(annotations AnnotationStore, rules RuleStore, metrics *metrics.Historian) *AnnotationBackend {
logger := log.New("ngalert.state.historian", "backend", "annotations") logger := log.New("ngalert.state.historian", "backend", "annotations")
return &AnnotationBackend{ return &AnnotationBackend{
annotations: annotations, store: annotations,
dashboards: newDashboardResolver(dashboards, defaultDashboardCacheExpiry), rules: rules,
rules: rules, clock: clock.New(),
clock: clock.New(), metrics: metrics,
metrics: metrics, log: logger,
log: logger,
} }
} }
@ -83,7 +80,7 @@ func (h *AnnotationBackend) Record(ctx context.Context, rule history_model.RuleM
defer close(errCh) defer close(errCh)
logger := h.log.FromContext(ctx) logger := h.log.FromContext(ctx)
errCh <- h.recordAnnotations(ctx, panel, annotations, rule.OrgID, logger) errCh <- h.store.Save(ctx, panel, annotations, rule.OrgID, logger)
}(writeCtx) }(writeCtx)
return errCh return errCh
} }
@ -118,7 +115,7 @@ func (h *AnnotationBackend) Query(ctx context.Context, query ngmodels.HistoryQue
To: query.To.Unix(), To: query.To.Unix(),
SignedInUser: query.SignedInUser, SignedInUser: query.SignedInUser,
} }
items, err := h.annotations.Find(ctx, &q) items, err := h.store.Find(ctx, &q)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query annotations for state history: %w", err) return nil, fmt.Errorf("failed to query annotations for state history: %w", err)
} }
@ -198,34 +195,6 @@ func buildAnnotations(rule history_model.RuleMeta, states []state.StateTransitio
return items return items
} }
func (h *AnnotationBackend) recordAnnotations(ctx context.Context, panel *panelKey, annotations []annotations.Item, orgID int64, logger log.Logger) error {
if panel != nil {
dashID, err := h.dashboards.getID(ctx, panel.orgID, panel.dashUID)
if err != nil {
logger.Error("Error getting dashboard for alert annotation", "dashboardUID", panel.dashUID, "error", err)
dashID = 0
}
for i := range annotations {
annotations[i].DashboardID = dashID
annotations[i].PanelID = panel.panelID
}
}
org := fmt.Sprint(orgID)
h.metrics.WritesTotal.WithLabelValues(org, "annotations").Inc()
h.metrics.TransitionsTotal.WithLabelValues(org).Add(float64(len(annotations)))
if err := h.annotations.SaveMany(ctx, annotations); err != nil {
logger.Error("Error saving alert annotation batch", "error", err)
h.metrics.WritesFailed.WithLabelValues(org, "annotations").Inc()
h.metrics.TransitionsFailed.WithLabelValues(org).Add(float64(len(annotations)))
return fmt.Errorf("error saving alert annotation batch: %w", err)
}
logger.Debug("Done saving alert annotation batch")
return nil
}
func buildAnnotationTextAndData(rule history_model.RuleMeta, currentState *state.State) (string, *simplejson.Json) { func buildAnnotationTextAndData(rule history_model.RuleMeta, currentState *state.State) (string, *simplejson.Json) {
jsonData := simplejson.New() jsonData := simplejson.New()
var value string var value string

View File

@ -0,0 +1,62 @@
package historian
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
)
type AnnotationService interface {
Find(ctx context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error)
SaveMany(ctx context.Context, items []annotations.Item) error
}
type AnnotationServiceStore struct {
svc AnnotationService
dashboards *dashboardResolver
metrics *metrics.Historian
}
func NewAnnotationStore(svc AnnotationService, dashboards dashboards.DashboardService, metrics *metrics.Historian) *AnnotationServiceStore {
return &AnnotationServiceStore{
svc: svc,
dashboards: newDashboardResolver(dashboards, defaultDashboardCacheExpiry),
metrics: metrics,
}
}
func (s *AnnotationServiceStore) Save(ctx context.Context, panel *PanelKey, annotations []annotations.Item, orgID int64, logger log.Logger) error {
if panel != nil {
dashID, err := s.dashboards.getID(ctx, panel.orgID, panel.dashUID)
if err != nil {
logger.Error("Error getting dashboard for alert annotation", "dashboardUID", panel.dashUID, "error", err)
dashID = 0
}
for i := range annotations {
annotations[i].DashboardID = dashID
annotations[i].PanelID = panel.panelID
}
}
org := fmt.Sprint(orgID)
s.metrics.WritesTotal.WithLabelValues(org, "annotations").Inc()
s.metrics.TransitionsTotal.WithLabelValues(org).Add(float64(len(annotations)))
if err := s.svc.SaveMany(ctx, annotations); err != nil {
logger.Error("Error saving alert annotation batch", "error", err)
s.metrics.WritesFailed.WithLabelValues(org, "annotations").Inc()
s.metrics.TransitionsFailed.WithLabelValues(org).Add(float64(len(annotations)))
return fmt.Errorf("error saving alert annotation batch: %w", err)
}
logger.Debug("Done saving alert annotation batch")
return nil
}
func (s *AnnotationServiceStore) Find(ctx context.Context, query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) {
return s.svc.Find(ctx, query)
}

View File

@ -31,7 +31,7 @@ func TestAnnotationHistorian(t *testing.T) {
t.Run("alert annotations are queryable", func(t *testing.T) { t.Run("alert annotations are queryable", func(t *testing.T) {
anns := createTestAnnotationBackendSut(t) anns := createTestAnnotationBackendSut(t)
items := []annotations.Item{createAnnotation()} items := []annotations.Item{createAnnotation()}
require.NoError(t, anns.recordAnnotations(context.Background(), nil, items, 1, log.NewNopLogger())) require.NoError(t, anns.store.Save(context.Background(), nil, items, 1, log.NewNopLogger()))
q := models.HistoryQuery{ q := models.HistoryQuery{
RuleUID: "my-rule", RuleUID: "my-rule",
@ -113,7 +113,8 @@ func createTestAnnotationBackendSutWithMetrics(t *testing.T, met *metrics.Histor
} }
dbs := &dashboards.FakeDashboardService{} dbs := &dashboards.FakeDashboardService{}
dbs.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) dbs.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
return NewAnnotationBackend(fakeAnnoRepo, dbs, rules, met) store := NewAnnotationStore(fakeAnnoRepo, dbs, met)
return NewAnnotationBackend(store, rules, met)
} }
func createFailingAnnotationSut(t *testing.T, met *metrics.Historian) *AnnotationBackend { func createFailingAnnotationSut(t *testing.T, met *metrics.Historian) *AnnotationBackend {
@ -124,7 +125,8 @@ func createFailingAnnotationSut(t *testing.T, met *metrics.Historian) *Annotatio
} }
dbs := &dashboards.FakeDashboardService{} dbs := &dashboards.FakeDashboardService{}
dbs.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) dbs.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil)
return NewAnnotationBackend(fakeAnnoRepo, dbs, rules, met) store := NewAnnotationStore(fakeAnnoRepo, dbs, met)
return NewAnnotationBackend(store, rules, met)
} }
func createAnnotation() annotations.Item { func createAnnotation() annotations.Item {

View File

@ -50,17 +50,17 @@ func labelFingerprint(labels data.Labels) string {
return fmt.Sprintf("%016x", sig) return fmt.Sprintf("%016x", sig)
} }
// panelKey uniquely identifies a panel. // PanelKey uniquely identifies a panel.
type panelKey struct { type PanelKey struct {
orgID int64 orgID int64
dashUID string dashUID string
panelID int64 panelID int64
} }
// panelKey attempts to get the key of the panel attached to the given rule. Returns nil if the rule is not attached to a panel. // PanelKey attempts to get the key of the panel attached to the given rule. Returns nil if the rule is not attached to a panel.
func parsePanelKey(rule history_model.RuleMeta, logger log.Logger) *panelKey { func parsePanelKey(rule history_model.RuleMeta, logger log.Logger) *PanelKey {
if rule.DashboardUID != "" { if rule.DashboardUID != "" {
return &panelKey{ return &PanelKey{
orgID: rule.OrgID, orgID: rule.OrgID,
dashUID: rule.DashboardUID, dashUID: rule.DashboardUID,
panelID: rule.PanelID, panelID: rule.PanelID,

View File

@ -20,7 +20,8 @@ func BenchmarkProcessEvalResults(b *testing.B) {
as := annotations.FakeAnnotationsRepo{} as := annotations.FakeAnnotationsRepo{}
as.On("SaveMany", mock.Anything, mock.Anything).Return(nil) as.On("SaveMany", mock.Anything, mock.Anything).Return(nil)
metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry()) metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry())
hist := historian.NewAnnotationBackend(&as, nil, nil, metrics) store := historian.NewAnnotationStore(&as, nil, metrics)
hist := historian.NewAnnotationBackend(store, nil, metrics)
cfg := state.ManagerCfg{ cfg := state.ManagerCfg{
Historian: hist, Historian: hist,
MaxStateSaveConcurrency: 1, MaxStateSaveConcurrency: 1,

View File

@ -226,7 +226,8 @@ func TestDashboardAnnotations(t *testing.T) {
fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo() fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo()
metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry()) metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry())
hist := historian.NewAnnotationBackend(fakeAnnoRepo, &dashboards.FakeDashboardService{}, nil, metrics) store := historian.NewAnnotationStore(fakeAnnoRepo, &dashboards.FakeDashboardService{}, metrics)
hist := historian.NewAnnotationBackend(store, nil, metrics)
cfg := state.ManagerCfg{ cfg := state.ManagerCfg{
Metrics: testMetrics.GetStateMetrics(), Metrics: testMetrics.GetStateMetrics(),
ExternalURL: nil, ExternalURL: nil,
@ -2256,7 +2257,8 @@ func TestProcessEvalResults(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo() fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo()
metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry()) metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry())
hist := historian.NewAnnotationBackend(fakeAnnoRepo, &dashboards.FakeDashboardService{}, nil, metrics) store := historian.NewAnnotationStore(fakeAnnoRepo, &dashboards.FakeDashboardService{}, metrics)
hist := historian.NewAnnotationBackend(store, nil, metrics)
cfg := state.ManagerCfg{ cfg := state.ManagerCfg{
Metrics: testMetrics.GetStateMetrics(), Metrics: testMetrics.GetStateMetrics(),
ExternalURL: nil, ExternalURL: nil,