mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AlertingNG: Correctly set StartsAt, EndsAt, UpdatedAt after alert reception (#33109)
Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com>
This commit is contained in:
parent
e7fd30be17
commit
0a03d5c29e
@ -1,60 +0,0 @@
|
|||||||
package notifier
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
gokit_log "github.com/go-kit/kit/log"
|
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
||||||
"github.com/prometheus/alertmanager/api/v2/models"
|
|
||||||
"github.com/prometheus/alertmanager/provider"
|
|
||||||
"github.com/prometheus/alertmanager/provider/mem"
|
|
||||||
"github.com/prometheus/alertmanager/types"
|
|
||||||
"github.com/prometheus/common/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AlertProvider struct {
|
|
||||||
provider.Alerts
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAlertProvider returns AlertProvider that provides a method to translate
|
|
||||||
// Grafana alerts to Prometheus Alertmanager alerts before passing it ahead.
|
|
||||||
func NewAlertProvider(m types.Marker) (*AlertProvider, error) {
|
|
||||||
alerts, err := mem.NewAlerts(context.Background(), m, 30*time.Minute, gokit_log.NewNopLogger())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AlertProvider{Alerts: alerts}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ap *AlertProvider) PutPostableAlert(postableAlerts apimodels.PostableAlerts) error {
|
|
||||||
alerts := make([]*types.Alert, 0, len(postableAlerts.PostableAlerts))
|
|
||||||
for _, a := range postableAlerts.PostableAlerts {
|
|
||||||
alerts = append(alerts, alertForDelivery(a))
|
|
||||||
}
|
|
||||||
return ap.Alerts.Put(alerts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func alertForDelivery(a models.PostableAlert) *types.Alert {
|
|
||||||
lbls := model.LabelSet{}
|
|
||||||
annotations := model.LabelSet{}
|
|
||||||
for k, v := range a.Labels {
|
|
||||||
lbls[model.LabelName(k)] = model.LabelValue(v)
|
|
||||||
}
|
|
||||||
for k, v := range a.Annotations {
|
|
||||||
annotations[model.LabelName(k)] = model.LabelValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.Alert{
|
|
||||||
Alert: model.Alert{
|
|
||||||
Labels: lbls,
|
|
||||||
Annotations: annotations,
|
|
||||||
StartsAt: time.Time(a.StartsAt),
|
|
||||||
EndsAt: time.Time(a.EndsAt),
|
|
||||||
GeneratorURL: a.GeneratorURL.String(),
|
|
||||||
},
|
|
||||||
UpdatedAt: time.Time{}, // TODO(codesome) what should this be?
|
|
||||||
Timeout: false, // TODO(codesome).
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package notifier
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
||||||
"github.com/prometheus/alertmanager/api/v2/models"
|
|
||||||
"github.com/prometheus/alertmanager/provider"
|
|
||||||
"github.com/prometheus/alertmanager/types"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/common/model"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAlertProvider(t *testing.T) {
|
|
||||||
marker := types.NewMarker(prometheus.DefaultRegisterer)
|
|
||||||
alertProvider := &mockAlertProvider{}
|
|
||||||
|
|
||||||
ap, err := NewAlertProvider(marker)
|
|
||||||
require.NoError(t, err)
|
|
||||||
ap.Alerts = alertProvider
|
|
||||||
|
|
||||||
startTime := time.Now()
|
|
||||||
endTime := startTime.Add(2 * time.Hour)
|
|
||||||
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{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, ap.PutPostableAlert(postableAlerts))
|
|
||||||
|
|
||||||
// Alerts that should be sent for routing.
|
|
||||||
expProviderAlerts := []*types.Alert{
|
|
||||||
{
|
|
||||||
Alert: model.Alert{
|
|
||||||
Annotations: model.LabelSet{"msg": "Alert1 annotation"},
|
|
||||||
Labels: model.LabelSet{"alertname": "Alert1"},
|
|
||||||
StartsAt: startTime,
|
|
||||||
EndsAt: endTime,
|
|
||||||
GeneratorURL: "http://localhost/url1",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Alert: model.Alert{
|
|
||||||
Annotations: model.LabelSet{"msg": "Alert2 annotation"},
|
|
||||||
Labels: model.LabelSet{"alertname": "Alert2"},
|
|
||||||
EndsAt: endTime,
|
|
||||||
GeneratorURL: "http://localhost/url2",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Alert: model.Alert{
|
|
||||||
Annotations: model.LabelSet{"msg": "Alert3 annotation"},
|
|
||||||
Labels: model.LabelSet{"alertname": "Alert3"},
|
|
||||||
StartsAt: startTime,
|
|
||||||
GeneratorURL: "http://localhost/url3",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Alert: model.Alert{
|
|
||||||
Annotations: model.LabelSet{"msg": "Alert4 annotation"},
|
|
||||||
Labels: model.LabelSet{"alertname": "Alert4"},
|
|
||||||
GeneratorURL: "http://localhost/url4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.Equal(t, expProviderAlerts, alertProvider.alerts)
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockAlertProvider struct {
|
|
||||||
alerts []*types.Alert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *mockAlertProvider) Subscribe() provider.AlertIterator { return nil }
|
|
||||||
func (a *mockAlertProvider) GetPending() provider.AlertIterator { return nil }
|
|
||||||
func (a *mockAlertProvider) Get(model.Fingerprint) (*types.Alert, error) { return nil, nil }
|
|
||||||
|
|
||||||
func (a *mockAlertProvider) Put(alerts ...*types.Alert) error {
|
|
||||||
a.alerts = append(a.alerts, alerts...)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -13,22 +13,26 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
gokit_log "github.com/go-kit/kit/log"
|
gokit_log "github.com/go-kit/kit/log"
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||||
"github.com/prometheus/alertmanager/dispatch"
|
"github.com/prometheus/alertmanager/dispatch"
|
||||||
"github.com/prometheus/alertmanager/inhibit"
|
"github.com/prometheus/alertmanager/inhibit"
|
||||||
"github.com/prometheus/alertmanager/nflog"
|
"github.com/prometheus/alertmanager/nflog"
|
||||||
"github.com/prometheus/alertmanager/nflog/nflogpb"
|
"github.com/prometheus/alertmanager/nflog/nflogpb"
|
||||||
"github.com/prometheus/alertmanager/notify"
|
"github.com/prometheus/alertmanager/notify"
|
||||||
|
"github.com/prometheus/alertmanager/provider"
|
||||||
|
"github.com/prometheus/alertmanager/provider/mem"
|
||||||
"github.com/prometheus/alertmanager/silence"
|
"github.com/prometheus/alertmanager/silence"
|
||||||
"github.com/prometheus/alertmanager/template"
|
"github.com/prometheus/alertmanager/template"
|
||||||
"github.com/prometheus/alertmanager/types"
|
"github.com/prometheus/alertmanager/types"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/securejsondata"
|
"github.com/grafana/grafana/pkg/components/securejsondata"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
"github.com/grafana/grafana/pkg/services/alerting"
|
"github.com/grafana/grafana/pkg/services/alerting"
|
||||||
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels"
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
@ -42,6 +46,11 @@ const (
|
|||||||
workingDir = "alerting"
|
workingDir = "alerting"
|
||||||
// How long should we keep silences and notification entries on-disk after they've served their purpose.
|
// How long should we keep silences and notification entries on-disk after they've served their purpose.
|
||||||
retentionNotificationsAndSilences = 5 * 24 * time.Hour
|
retentionNotificationsAndSilences = 5 * 24 * time.Hour
|
||||||
|
// 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
|
||||||
// To start, the alertmanager needs at least one route defined.
|
// To start, the alertmanager needs at least one route defined.
|
||||||
// TODO: we should move this to Grafana settings and define this as the default.
|
// TODO: we should move this to Grafana settings and define this as the default.
|
||||||
alertmanagerDefaultConfiguration = `
|
alertmanagerDefaultConfiguration = `
|
||||||
@ -76,10 +85,11 @@ type Alertmanager struct {
|
|||||||
// notificationLog keeps tracks of which notifications we've fired already.
|
// notificationLog keeps tracks of which notifications we've fired already.
|
||||||
notificationLog *nflog.Log
|
notificationLog *nflog.Log
|
||||||
// silences keeps the track of which notifications we should not fire due to user configuration.
|
// silences keeps the track of which notifications we should not fire due to user configuration.
|
||||||
|
|
||||||
silencer *silence.Silencer
|
silencer *silence.Silencer
|
||||||
silences *silence.Silences
|
silences *silence.Silences
|
||||||
marker types.Marker
|
marker types.Marker
|
||||||
alerts *AlertProvider
|
alerts provider.Alerts
|
||||||
route *dispatch.Route
|
route *dispatch.Route
|
||||||
dispatcher *dispatch.Dispatcher
|
dispatcher *dispatch.Dispatcher
|
||||||
inhibitor *inhibit.Inhibitor
|
inhibitor *inhibit.Inhibitor
|
||||||
@ -126,7 +136,7 @@ func (am *Alertmanager) Init() (err error) {
|
|||||||
return fmt.Errorf("unable to initialize the silencing component of alerting: %w", err)
|
return fmt.Errorf("unable to initialize the silencing component of alerting: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
am.alerts, err = NewAlertProvider(am.marker)
|
am.alerts, err = mem.NewAlerts(context.Background(), am.marker, memoryAlertsGCInterval, gokit_log.NewNopLogger())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to initialize the alert provider component of alerting: %w", err)
|
return fmt.Errorf("unable to initialize the alert provider component of alerting: %w", err)
|
||||||
}
|
}
|
||||||
@ -368,8 +378,88 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not
|
// PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not
|
||||||
func (am *Alertmanager) PutAlerts(alerts apimodels.PostableAlerts) error {
|
func (am *Alertmanager) PutAlerts(postableAlerts apimodels.PostableAlerts) error {
|
||||||
return am.alerts.PutPostableAlert(alerts)
|
now := time.Now()
|
||||||
|
alerts := make([]*types.Alert, 0, len(postableAlerts.PostableAlerts))
|
||||||
|
var validationErr *AlertValidationError
|
||||||
|
for _, a := range postableAlerts.PostableAlerts {
|
||||||
|
alert := &types.Alert{
|
||||||
|
Alert: model.Alert{
|
||||||
|
Labels: model.LabelSet{},
|
||||||
|
Annotations: model.LabelSet{},
|
||||||
|
StartsAt: time.Time(a.StartsAt),
|
||||||
|
EndsAt: time.Time(a.EndsAt),
|
||||||
|
GeneratorURL: a.GeneratorURL.String(),
|
||||||
|
},
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
for k, v := range a.Labels {
|
||||||
|
if len(v) == 0 { // Skip empty labels.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
alert.Alert.Labels[model.LabelName(k)] = model.LabelValue(v)
|
||||||
|
}
|
||||||
|
for k, v := range a.Annotations {
|
||||||
|
if len(v) == 0 { // Skip empty annotation.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
alert.Alert.Annotations[model.LabelName(k)] = model.LabelValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure StartsAt is set.
|
||||||
|
if alert.StartsAt.IsZero() {
|
||||||
|
if alert.EndsAt.IsZero() {
|
||||||
|
alert.StartsAt = now
|
||||||
|
} else {
|
||||||
|
alert.StartsAt = alert.EndsAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no end time is defined, set a timeout after which an alert
|
||||||
|
// is marked resolved if it is not updated.
|
||||||
|
if alert.EndsAt.IsZero() {
|
||||||
|
alert.Timeout = true
|
||||||
|
alert.EndsAt = now.Add(defaultResolveTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := alert.Validate(); err != nil {
|
||||||
|
if validationErr == nil {
|
||||||
|
validationErr = &AlertValidationError{}
|
||||||
|
}
|
||||||
|
validationErr.Alerts = append(validationErr.Alerts, a)
|
||||||
|
validationErr.Errors = append(validationErr.Errors, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
alerts = append(alerts, alert)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := am.alerts.Put(alerts...); err != nil {
|
||||||
|
// Notification sending alert takes precedence over validation errors.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if validationErr != nil {
|
||||||
|
// Even if validationErr is nil, the require.NoError fails on it.
|
||||||
|
return validationErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertValidationError is the error capturing the validation errors
|
||||||
|
// faced on the alerts.
|
||||||
|
type AlertValidationError struct {
|
||||||
|
Alerts []amv2.PostableAlert
|
||||||
|
Errors []error // Errors[i] refers to Alerts[i].
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e AlertValidationError) Error() string {
|
||||||
|
errMsg := ""
|
||||||
|
if len(e.Errors) != 0 {
|
||||||
|
errMsg := e.Errors[0].Error()
|
||||||
|
for _, e := range e.Errors[1:] {
|
||||||
|
errMsg += ";" + e.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
// createReceiverStage creates a pipeline of stages for a receiver.
|
// createReceiverStage creates a pipeline of stages for a receiver.
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
package notifier
|
package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/prometheus/alertmanager/api/v2/models"
|
||||||
|
"github.com/prometheus/alertmanager/provider"
|
||||||
|
"github.com/prometheus/alertmanager/types"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@ -18,3 +28,265 @@ func TestAlertmanager_ShouldUseDefaultConfigurationWhenNoConfiguration(t *testin
|
|||||||
require.NoError(t, am.SyncAndApplyConfigFromDatabase())
|
require.NoError(t, am.SyncAndApplyConfigFromDatabase())
|
||||||
require.NotNil(t, am.config)
|
require.NotNil(t, am.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPutAlert(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, os.RemoveAll(dir))
|
||||||
|
})
|
||||||
|
|
||||||
|
am := &Alertmanager{
|
||||||
|
Settings: &setting.Cfg{
|
||||||
|
DataPath: dir,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, am.Init())
|
||||||
|
|
||||||
|
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: "Invalid labels",
|
||||||
|
postableAlerts: apimodels.PostableAlerts{
|
||||||
|
PostableAlerts: []models.PostableAlert{
|
||||||
|
{
|
||||||
|
Alert: models.Alert{
|
||||||
|
Labels: models.LabelSet{"alertname$": "Alert1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expError: &AlertValidationError{
|
||||||
|
Alerts: []models.PostableAlert{
|
||||||
|
{
|
||||||
|
Alert: models.Alert{
|
||||||
|
Labels: models.LabelSet{"alertname$": "Alert1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Errors: []error{errors.New("invalid label set: invalid name \"alertname$\"")},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
title: "Invalid annotation",
|
||||||
|
postableAlerts: apimodels.PostableAlerts{
|
||||||
|
PostableAlerts: []models.PostableAlert{
|
||||||
|
{
|
||||||
|
Annotations: models.LabelSet{"msg$": "Alert4 annotation"},
|
||||||
|
Alert: models.Alert{
|
||||||
|
Labels: models.LabelSet{"alertname": "Alert1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expError: &AlertValidationError{
|
||||||
|
Alerts: []models.PostableAlert{
|
||||||
|
{
|
||||||
|
Annotations: models.LabelSet{"msg$": "Alert4 annotation"},
|
||||||
|
Alert: models.Alert{
|
||||||
|
Labels: models.LabelSet{"alertname": "Alert1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Errors: []error{errors.New("invalid annotations: invalid name \"msg$\"")},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
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 {
|
||||||
|
t.Run(c.title, func(t *testing.T) {
|
||||||
|
alertProvider := &mockAlertProvider{}
|
||||||
|
am.alerts = alertProvider
|
||||||
|
|
||||||
|
err := am.PutAlerts(c.postableAlerts)
|
||||||
|
if c.expError != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, c.expError, err)
|
||||||
|
require.Equal(t, 0, len(alertProvider.alerts))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// We take the "now" time from one of the UpdatedAt.
|
||||||
|
now := alertProvider.alerts[0].UpdatedAt
|
||||||
|
require.Equal(t, c.expAlerts(now), alertProvider.alerts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockAlertProvider struct {
|
||||||
|
alerts []*types.Alert
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *mockAlertProvider) Subscribe() provider.AlertIterator { return nil }
|
||||||
|
func (a *mockAlertProvider) GetPending() provider.AlertIterator { return nil }
|
||||||
|
func (a *mockAlertProvider) Get(model.Fingerprint) (*types.Alert, error) { return nil, nil }
|
||||||
|
|
||||||
|
func (a *mockAlertProvider) Put(alerts ...*types.Alert) error {
|
||||||
|
a.alerts = append(a.alerts, alerts...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user