From 0be920e61c778f410011f2fbb210279f5f8758d1 Mon Sep 17 00:00:00 2001 From: gotjosh Date: Mon, 23 Jan 2023 10:30:10 +0000 Subject: [PATCH] Alerting: Remove unused code after importing from grafana/alerting (#61869) * Alerting: Remove unused code after importing from grafana/alerting --- pkg/services/ngalert/notifier/alertmanager.go | 190 +-------- .../ngalert/notifier/alertmanager_test.go | 372 +----------------- pkg/services/ngalert/notifier/receivers.go | 5 +- 3 files changed, 8 insertions(+), 559 deletions(-) diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go index 543f792dd24..05b83bfc284 100644 --- a/pkg/services/ngalert/notifier/alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager.go @@ -1,4 +1,3 @@ -//nolint:golint,unused package notifier import ( @@ -8,29 +7,12 @@ import ( "encoding/json" "errors" "fmt" - "net/url" "path/filepath" "strconv" - "sync" "time" - "unicode/utf8" - - amv2 "github.com/prometheus/alertmanager/api/v2/models" - "github.com/prometheus/alertmanager/config" - "github.com/prometheus/alertmanager/dispatch" - "github.com/prometheus/alertmanager/inhibit" - "github.com/prometheus/alertmanager/nflog" - "github.com/prometheus/alertmanager/nflog/nflogpb" - "github.com/prometheus/alertmanager/notify" - "github.com/prometheus/alertmanager/provider/mem" - "github.com/prometheus/alertmanager/silence" - "github.com/prometheus/alertmanager/timeinterval" - "github.com/prometheus/alertmanager/types" - "github.com/prometheus/common/model" "github.com/grafana/alerting/alerting" "github.com/grafana/alerting/alerting/notifier/channels" - "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -40,6 +22,8 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/setting" + + amv2 "github.com/prometheus/alertmanager/api/v2/models" ) const ( @@ -47,13 +31,8 @@ const ( silencesFilename = "silences" workingDir = "alerting" - // maintenanceNotificationAndSilences how often should we flush and gargabe collect notifications + // maintenanceNotificationAndSilences how often should we flush and garbage collect notifications notificationLogMaintenanceInterval = 15 * time.Minute - // defaultResolveTimeout is the default timeout used for resolving an alert - // if the end time is not specified. - defaultResolveTimeout = 5 * time.Minute - // memoryAlertsGCInterval is the interval at which we'll remove resolved alerts from memory. - memoryAlertsGCInterval = 30 * time.Minute ) // How long should we keep silences and notification entries on-disk after they've served their purpose. @@ -72,42 +51,10 @@ type Alertmanager struct { Settings *setting.Cfg Store AlertingStore fileStore *FileStore - Metrics *metrics.Alertmanager NotificationService notifications.Service - notificationLog *nflog.Log - marker types.Marker - alerts *mem.Alerts - route *dispatch.Route - peer alerting.ClusterPeer - peerTimeout time.Duration - - dispatcher *dispatch.Dispatcher - inhibitor *inhibit.Inhibitor - // wg is for dispatcher, inhibitor, silences and notifications - // Across configuration changes dispatcher and inhibitor are completely replaced, however, silences, notification log and alerts remain the same. - // stopc is used to let silences and notifications know we are done. - stopc chan struct{} - wg sync.WaitGroup - - silencer *silence.Silencer - silences *silence.Silences - - receivers []*alerting.Receiver - - // muteTimes is a map where the key is the name of the mute_time_interval - // and the value represents all configured time_interval(s) - muteTimes map[string][]timeinterval.TimeInterval - - stageMetrics *notify.Metrics - dispatcherMetrics *dispatch.DispatcherMetrics - - reloadConfigMtx sync.RWMutex - config *apimodels.PostableUserConfig - configHash [16]byte - orgID int64 - decryptFn channels.GetDecryptedValueFn + orgID int64 } // maintenanceOptions represent the options for components that need maintenance on a frequency within the Alertmanager. @@ -203,16 +150,12 @@ func (am *Alertmanager) Ready() bool { return am.Base.Ready() } -func (am *Alertmanager) ready() bool { - return am.config != nil -} - func (am *Alertmanager) StopAndWait() { am.Base.StopAndWait() } // SaveAndApplyDefaultConfig saves the default configuration the database and applies the configuration to the Alertmanager. -// It rollbacks the save if we fail to apply the configuration. +// It rolls back the save if we fail to apply the configuration. func (am *Alertmanager) SaveAndApplyDefaultConfig(ctx context.Context) error { var outerErr error am.Base.WithLock(func() { @@ -294,40 +237,6 @@ func (am *Alertmanager) ApplyConfig(dbCfg *ngmodels.AlertConfiguration) error { return outerErr } -func (am *Alertmanager) getTemplate() (*alerting.Template, error) { - am.reloadConfigMtx.RLock() - defer am.reloadConfigMtx.RUnlock() - if !am.ready() { - return nil, errors.New("alertmanager is not initialized") - } - paths := make([]string, 0, len(am.config.TemplateFiles)) - for name := range am.config.TemplateFiles { - paths = append(paths, filepath.Join(am.WorkingDirPath(), name)) - } - return am.templateFromPaths(paths...) -} - -func (am *Alertmanager) templateFromPaths(paths ...string) (*alerting.Template, error) { - tmpl, err := alerting.FromGlobs(paths) - if err != nil { - return nil, err - } - externalURL, err := url.Parse(am.Settings.AppURL) - if err != nil { - return nil, err - } - tmpl.ExternalURL = externalURL - return tmpl, nil -} - -func (am *Alertmanager) buildMuteTimesMap(muteTimeIntervals []config.MuteTimeInterval) map[string][]timeinterval.TimeInterval { - muteTimes := make(map[string][]timeinterval.TimeInterval, len(muteTimeIntervals)) - for _, ti := range muteTimeIntervals { - muteTimes[ti.Name] = ti.TimeIntervals - } - return muteTimes -} - // applyConfig applies a new configuration by re-initializing all components using the configuration provided. // It is not safe to call concurrently. func (am *Alertmanager) applyConfig(cfg *apimodels.PostableUserConfig, rawConfig []byte) (err error) { @@ -479,51 +388,6 @@ func (am *Alertmanager) PutAlerts(postableAlerts apimodels.PostableAlerts) error return am.Base.PutAlerts(alerts) } -// validateAlert is a.Validate() while additionally allowing -// space for label and annotation names. -func validateAlert(a *types.Alert) error { - if a.StartsAt.IsZero() { - return fmt.Errorf("start time missing") - } - if !a.EndsAt.IsZero() && a.EndsAt.Before(a.StartsAt) { - return fmt.Errorf("start time must be before end time") - } - if err := validateLabelSet(a.Labels); err != nil { - return fmt.Errorf("invalid label set: %s", err) - } - if len(a.Labels) == 0 { - return fmt.Errorf("at least one label pair required") - } - if err := validateLabelSet(a.Annotations); err != nil { - return fmt.Errorf("invalid annotations: %s", err) - } - return nil -} - -// validateLabelSet is ls.Validate() while additionally allowing -// space for label names. -func validateLabelSet(ls model.LabelSet) error { - for ln, lv := range ls { - if !isValidLabelName(ln) { - return fmt.Errorf("invalid name %q", ln) - } - if !lv.IsValid() { - return fmt.Errorf("invalid value %q", lv) - } - } - return nil -} - -// isValidLabelName is ln.IsValid() without restrictions other than it can not be empty. -// The regex for Prometheus data model is ^[a-zA-Z_][a-zA-Z0-9_]*$. -func isValidLabelName(ln model.LabelName) bool { - if len(ln) == 0 { - return false - } - - return utf8.ValidString(string(ln)) -} - // AlertValidationError is the error capturing the validation errors // faced on the alerts. type AlertValidationError struct { @@ -542,50 +406,6 @@ func (e AlertValidationError) Error() string { return errMsg } -// createReceiverStage creates a pipeline of stages for a receiver. -func (am *Alertmanager) createReceiverStage(name string, integrations []*notify.Integration, wait func() time.Duration, notificationLog notify.NotificationLog) notify.Stage { - var fs notify.FanoutStage - for _, integration := range integrations { - recv := &nflogpb.Receiver{ - GroupName: name, - Integration: integration.Name(), - Idx: uint32(integration.Index()), - } - var s notify.MultiStage - s = append(s, notify.NewWaitStage(wait)) - s = append(s, notify.NewDedupStage(integration, notificationLog, recv)) - s = append(s, notify.NewRetryStage(integration, name, am.stageMetrics)) - s = append(s, notify.NewSetNotifiesStage(notificationLog, recv)) - - fs = append(fs, s) - } - return fs -} - -// getActiveReceiversMap returns all receivers that are in use by a route. -func (am *Alertmanager) getActiveReceiversMap(r *dispatch.Route) map[string]struct{} { - receiversMap := make(map[string]struct{}) - visitFunc := func(r *dispatch.Route) { - receiversMap[r.RouteOpts.Receiver] = struct{}{} - } - r.Walk(visitFunc) - - return receiversMap -} - -func (am *Alertmanager) waitFunc() time.Duration { - return time.Duration(am.peer.Position()) * am.peerTimeout -} - -func (am *Alertmanager) timeoutFunc(d time.Duration) time.Duration { - // time.Duration d relates to the receiver's group_interval. Even with a group interval of 1s, - // we need to make sure (non-position-0) peers in the cluster wait before flushing the notifications. - if d < notify.MinTimeout { - d = notify.MinTimeout - } - return d + am.waitFunc() -} - type nilLimits struct{} func (n nilLimits) MaxNumberOfAggregationGroups() int { return 0 } diff --git a/pkg/services/ngalert/notifier/alertmanager_test.go b/pkg/services/ngalert/notifier/alertmanager_test.go index 5ccef2bbf1a..889de070c0c 100644 --- a/pkg/services/ngalert/notifier/alertmanager_test.go +++ b/pkg/services/ngalert/notifier/alertmanager_test.go @@ -2,25 +2,17 @@ package notifier import ( "context" - "errors" - "sort" "testing" "time" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/secrets/database" - "github.com/go-openapi/strfmt" - "github.com/prometheus/alertmanager/api/v2/models" - "github.com/prometheus/alertmanager/provider/mem" - "github.com/prometheus/alertmanager/types" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/model" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" - apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/store" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" @@ -53,367 +45,7 @@ func setupAMTest(t *testing.T) *Alertmanager { return am } -func TestPutAlert(t *testing.T) { - t.SkipNow() +func TestAlertmanager_newAlertmanager(t *testing.T) { am := setupAMTest(t) - - startTime := time.Now() - endTime := startTime.Add(2 * time.Hour) - - cases := []struct { - title string - postableAlerts apimodels.PostableAlerts - expAlerts func(now time.Time) []*types.Alert - expError *AlertValidationError - }{ - { - title: "Valid alerts with different start/end set", - postableAlerts: apimodels.PostableAlerts{ - PostableAlerts: []models.PostableAlert{ - { // Start and end set. - Annotations: models.LabelSet{"msg": "Alert1 annotation"}, - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": "Alert1"}, - GeneratorURL: "http://localhost/url1", - }, - StartsAt: strfmt.DateTime(startTime), - EndsAt: strfmt.DateTime(endTime), - }, { // Only end is set. - Annotations: models.LabelSet{"msg": "Alert2 annotation"}, - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": "Alert2"}, - GeneratorURL: "http://localhost/url2", - }, - StartsAt: strfmt.DateTime{}, - EndsAt: strfmt.DateTime(endTime), - }, { // Only start is set. - Annotations: models.LabelSet{"msg": "Alert3 annotation"}, - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": "Alert3"}, - GeneratorURL: "http://localhost/url3", - }, - StartsAt: strfmt.DateTime(startTime), - EndsAt: strfmt.DateTime{}, - }, { // Both start and end are not set. - Annotations: models.LabelSet{"msg": "Alert4 annotation"}, - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": "Alert4"}, - GeneratorURL: "http://localhost/url4", - }, - StartsAt: strfmt.DateTime{}, - EndsAt: strfmt.DateTime{}, - }, - }, - }, - expAlerts: func(now time.Time) []*types.Alert { - return []*types.Alert{ - { - Alert: model.Alert{ - Annotations: model.LabelSet{"msg": "Alert1 annotation"}, - Labels: model.LabelSet{"alertname": "Alert1"}, - StartsAt: startTime, - EndsAt: endTime, - GeneratorURL: "http://localhost/url1", - }, - UpdatedAt: now, - }, { - Alert: model.Alert{ - Annotations: model.LabelSet{"msg": "Alert2 annotation"}, - Labels: model.LabelSet{"alertname": "Alert2"}, - StartsAt: endTime, - EndsAt: endTime, - GeneratorURL: "http://localhost/url2", - }, - UpdatedAt: now, - }, { - Alert: model.Alert{ - Annotations: model.LabelSet{"msg": "Alert3 annotation"}, - Labels: model.LabelSet{"alertname": "Alert3"}, - StartsAt: startTime, - EndsAt: now.Add(defaultResolveTimeout), - GeneratorURL: "http://localhost/url3", - }, - UpdatedAt: now, - Timeout: true, - }, { - Alert: model.Alert{ - Annotations: model.LabelSet{"msg": "Alert4 annotation"}, - Labels: model.LabelSet{"alertname": "Alert4"}, - StartsAt: now, - EndsAt: now.Add(defaultResolveTimeout), - GeneratorURL: "http://localhost/url4", - }, - UpdatedAt: now, - Timeout: true, - }, - } - }, - }, { - title: "Removing empty labels and annotations", - postableAlerts: apimodels.PostableAlerts{ - PostableAlerts: []models.PostableAlert{ - { - Annotations: models.LabelSet{"msg": "Alert4 annotation", "empty": ""}, - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": "Alert4", "emptylabel": ""}, - GeneratorURL: "http://localhost/url1", - }, - StartsAt: strfmt.DateTime{}, - EndsAt: strfmt.DateTime{}, - }, - }, - }, - expAlerts: func(now time.Time) []*types.Alert { - return []*types.Alert{ - { - Alert: model.Alert{ - Annotations: model.LabelSet{"msg": "Alert4 annotation"}, - Labels: model.LabelSet{"alertname": "Alert4"}, - StartsAt: now, - EndsAt: now.Add(defaultResolveTimeout), - GeneratorURL: "http://localhost/url1", - }, - UpdatedAt: now, - Timeout: true, - }, - } - }, - }, { - title: "Allow spaces in label and annotation name", - postableAlerts: apimodels.PostableAlerts{ - PostableAlerts: []models.PostableAlert{ - { - Annotations: models.LabelSet{"Dashboard URL": "http://localhost:3000"}, - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": "Alert4", "Spaced Label": "works"}, - GeneratorURL: "http://localhost/url1", - }, - StartsAt: strfmt.DateTime{}, - EndsAt: strfmt.DateTime{}, - }, - }, - }, - expAlerts: func(now time.Time) []*types.Alert { - return []*types.Alert{ - { - Alert: model.Alert{ - Annotations: model.LabelSet{"Dashboard URL": "http://localhost:3000"}, - Labels: model.LabelSet{"alertname": "Alert4", "Spaced Label": "works"}, - StartsAt: now, - EndsAt: now.Add(defaultResolveTimeout), - GeneratorURL: "http://localhost/url1", - }, - UpdatedAt: now, - Timeout: true, - }, - } - }, - }, { - title: "Special characters in labels", - postableAlerts: apimodels.PostableAlerts{ - PostableAlerts: []models.PostableAlert{ - { - Alert: models.Alert{ - Labels: models.LabelSet{"alertname$": "Alert1", "az3-- __...++!!!£@@312312": "1"}, - }, - }, - }, - }, - expAlerts: func(now time.Time) []*types.Alert { - return []*types.Alert{ - { - Alert: model.Alert{ - Labels: model.LabelSet{"alertname$": "Alert1", "az3-- __...++!!!£@@312312": "1"}, - Annotations: model.LabelSet{}, - StartsAt: now, - EndsAt: now.Add(defaultResolveTimeout), - GeneratorURL: "", - }, - UpdatedAt: now, - Timeout: true, - }, - } - }, - }, { - title: "Special characters in annotations", - postableAlerts: apimodels.PostableAlerts{ - PostableAlerts: []models.PostableAlert{ - { - Annotations: models.LabelSet{"az3-- __...++!!!£@@312312": "Alert4 annotation"}, - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": "Alert4"}, - }, - }, - }, - }, - expAlerts: func(now time.Time) []*types.Alert { - return []*types.Alert{ - { - Alert: model.Alert{ - Labels: model.LabelSet{"alertname": "Alert4"}, - Annotations: model.LabelSet{"az3-- __...++!!!£@@312312": "Alert4 annotation"}, - StartsAt: now, - EndsAt: now.Add(defaultResolveTimeout), - GeneratorURL: "", - }, - UpdatedAt: now, - Timeout: true, - }, - } - }, - }, { - title: "No labels after removing empty", - postableAlerts: apimodels.PostableAlerts{ - PostableAlerts: []models.PostableAlert{ - { - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": ""}, - }, - }, - }, - }, - expError: &AlertValidationError{ - Alerts: []models.PostableAlert{ - { - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": ""}, - }, - }, - }, - Errors: []error{errors.New("at least one label pair required")}, - }, - }, { - title: "Start should be before end", - postableAlerts: apimodels.PostableAlerts{ - PostableAlerts: []models.PostableAlert{ - { - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": ""}, - }, - StartsAt: strfmt.DateTime(endTime), - EndsAt: strfmt.DateTime(startTime), - }, - }, - }, - expError: &AlertValidationError{ - Alerts: []models.PostableAlert{ - { - Alert: models.Alert{ - Labels: models.LabelSet{"alertname": ""}, - }, - StartsAt: strfmt.DateTime(endTime), - EndsAt: strfmt.DateTime(startTime), - }, - }, - Errors: []error{errors.New("start time must be before end time")}, - }, - }, - } - - for _, c := range cases { - var err error - t.Run(c.title, func(t *testing.T) { - r := prometheus.NewRegistry() - am.marker = types.NewMarker(r) - am.alerts, err = mem.NewAlerts(context.Background(), am.marker, 15*time.Minute, nil, am.logger, r) - require.NoError(t, err) - - alerts := []*types.Alert{} - err := am.PutAlerts(c.postableAlerts) - if c.expError != nil { - require.Error(t, err) - require.Equal(t, c.expError, err) - require.Equal(t, 0, len(alerts)) - return - } - require.NoError(t, err) - - iter := am.alerts.GetPending() - defer iter.Close() - for a := range iter.Next() { - alerts = append(alerts, a) - } - - // We take the "now" time from one of the UpdatedAt. - now := alerts[0].UpdatedAt - expAlerts := c.expAlerts(now) - - sort.Sort(types.AlertSlice(expAlerts)) - sort.Sort(types.AlertSlice(alerts)) - - require.Equal(t, expAlerts, alerts) - }) - } -} - -// Tests cleanup of expired Silences. We rely on prometheus/alertmanager for -// our alert silencing functionality, so we rely on its tests. However, we -// implement a custom maintenance function for silences, because we snapshot -// our data differently, so we test that functionality. -func TestSilenceCleanup(t *testing.T) { - require := require.New(t) - - oldRetention := retentionNotificationsAndSilences - retentionNotificationsAndSilences = 30 * time.Millisecond - oldMaintenance := silenceMaintenanceInterval - silenceMaintenanceInterval = 15 * time.Millisecond - t.Cleanup( - func() { - retentionNotificationsAndSilences = oldRetention - silenceMaintenanceInterval = oldMaintenance - }) - - am := setupAMTest(t) - now := time.Now() - dt := func(t time.Time) strfmt.DateTime { return strfmt.DateTime(t) } - - makeSilence := func(comment string, createdBy string, - startsAt, endsAt strfmt.DateTime, matchers models.Matchers) *apimodels.PostableSilence { - return &apimodels.PostableSilence{ - ID: "", - Silence: models.Silence{ - Comment: &comment, - CreatedBy: &createdBy, - StartsAt: &startsAt, - EndsAt: &endsAt, - Matchers: matchers, - }, - } - } - - tru := true - testString := "testName" - matchers := models.Matchers{&models.Matcher{Name: &testString, IsEqual: &tru, IsRegex: &tru, Value: &testString}} - // Create silences - one in the future, one currently active, one expired but - // retained, one expired and not retained. - silences := []*apimodels.PostableSilence{ - // Active in future - makeSilence("", "tests", dt(now.Add(5*time.Hour)), dt(now.Add(6*time.Hour)), matchers), - // Active now - makeSilence("", "tests", dt(now.Add(-5*time.Hour)), dt(now.Add(6*time.Hour)), matchers), - // Expiring soon. - makeSilence("", "tests", dt(now.Add(-5*time.Hour)), dt(now.Add(2*time.Second)), matchers), - // Expiring *very* soon - makeSilence("", "tests", dt(now.Add(-5*time.Hour)), dt(now.Add(1*time.Second)), matchers), - } - - for _, s := range silences { - _, err := am.CreateSilence(s) - require.NoError(err) - } - - // Let enough time pass for the maintenance window to run. - require.Eventually(func() bool { - // So, what silences do we have now? - found, err := am.ListSilences(nil) - require.NoError(err) - return len(found) == 3 - }, 3*time.Second, 100*time.Millisecond) - - // Wait again for another silence to expire. - require.Eventually(func() bool { - found, err := am.ListSilences(nil) - require.NoError(err) - return len(found) == 2 - }, 6*time.Second, 100*time.Millisecond) + require.False(t, am.Ready()) } diff --git a/pkg/services/ngalert/notifier/receivers.go b/pkg/services/ngalert/notifier/receivers.go index 2897f9799b9..02478c589bd 100644 --- a/pkg/services/ngalert/notifier/receivers.go +++ b/pkg/services/ngalert/notifier/receivers.go @@ -126,10 +126,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei }, err } -func (am *Alertmanager) GetReceivers(ctx context.Context) []apimodels.Receiver { - am.reloadConfigMtx.RLock() - defer am.reloadConfigMtx.RUnlock() - +func (am *Alertmanager) GetReceivers(_ context.Context) []apimodels.Receiver { apiReceivers := make([]apimodels.Receiver, 0, len(am.Base.GetReceivers())) for _, rcv := range am.Base.GetReceivers() { // Build integrations slice for each receiver.