The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/ngalert/notifier/historian_test.go

126 lines
5.5 KiB

package notifier
import (
"bytes"
"context"
"errors"
"io"
"net/url"
"testing"
"time"
alertingModels "github.com/grafana/alerting/models"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/ngalert/client"
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
var testNow = time.Date(2025, time.July, 15, 16, 55, 0, 0, time.UTC)
var testAlerts = []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "Alert1", alertingModels.RuleUIDLabel: "testRuleUID"},
Annotations: model.LabelSet{"foo": "bar", "__private__": "baz"},
StartsAt: testNow,
EndsAt: testNow,
GeneratorURL: "http://localhost/test",
},
},
}
func TestRecord(t *testing.T) {
t.Run("write notification history to Loki", func(t *testing.T) {
testCases := []struct {
name string
retry bool
notificationErr error
expected string
}{
{
"successful notification",
false,
nil,
"{\"streams\":[{\"stream\":{\"externalLabelKey\":\"externalLabelValue\",\"from\":\"notify-history\"},\"values\":[[\"1752598500000000000\",\"{\\\"schemaVersion\\\":1,\\\"receiver\\\":\\\"testReceiverName\\\",\\\"status\\\":\\\"resolved\\\",\\\"groupLabels\\\":{\\\"foo\\\":\\\"bar\\\"},\\\"alerts\\\":[{\\\"status\\\":\\\"resolved\\\",\\\"labels\\\":{\\\"alertname\\\":\\\"Alert1\\\"},\\\"annotations\\\":{\\\"foo\\\":\\\"bar\\\"},\\\"startsAt\\\":\\\"2025-07-15T16:55:00Z\\\",\\\"endsAt\\\":\\\"2025-07-15T16:55:00Z\\\",\\\"ruleUID\\\":\\\"testRuleUID\\\"}],\\\"retry\\\":false,\\\"duration\\\":1000}\"]]}]}",
},
{
"failed notification",
true,
errors.New("test notification error"),
"{\"streams\":[{\"stream\":{\"externalLabelKey\":\"externalLabelValue\",\"from\":\"notify-history\"},\"values\":[[\"1752598500000000000\",\"{\\\"schemaVersion\\\":1,\\\"receiver\\\":\\\"testReceiverName\\\",\\\"status\\\":\\\"resolved\\\",\\\"groupLabels\\\":{\\\"foo\\\":\\\"bar\\\"},\\\"alerts\\\":[{\\\"status\\\":\\\"resolved\\\",\\\"labels\\\":{\\\"alertname\\\":\\\"Alert1\\\"},\\\"annotations\\\":{\\\"foo\\\":\\\"bar\\\"},\\\"startsAt\\\":\\\"2025-07-15T16:55:00Z\\\",\\\"endsAt\\\":\\\"2025-07-15T16:55:00Z\\\",\\\"ruleUID\\\":\\\"testRuleUID\\\"}],\\\"retry\\\":true,\\\"error\\\":\\\"test notification error\\\",\\\"duration\\\":1000}\"]]}]}",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := lokiclient.NewFakeRequester()
met := metrics.NewNotificationHistorianMetrics(prometheus.NewRegistry())
h := createTestNotificationHistorian(req, met)
err := <-h.Record(recordCtx(), testAlerts, tc.retry, tc.notificationErr, time.Second)
require.NoError(t, err)
reqBody, err := io.ReadAll(req.LastRequest.Body)
require.NoError(t, err)
require.Equal(t, tc.expected, string(reqBody))
})
}
})
t.Run("emits expected write metrics", func(t *testing.T) {
reg := prometheus.NewRegistry()
met := metrics.NewNotificationHistorianMetrics(reg)
goodHistorian := createTestNotificationHistorian(lokiclient.NewFakeRequester(), met)
badHistorian := createTestNotificationHistorian(lokiclient.NewFakeRequester().WithResponse(lokiclient.BadResponse()), met)
<-goodHistorian.Record(recordCtx(), testAlerts, false, nil, time.Second)
<-badHistorian.Record(recordCtx(), testAlerts, false, nil, time.Second)
exp := bytes.NewBufferString(`
# HELP grafana_alerting_notification_history_writes_failed_total The total number of failed writes of notification history batches.
# TYPE grafana_alerting_notification_history_writes_failed_total counter
grafana_alerting_notification_history_writes_failed_total 1
# HELP grafana_alerting_notification_history_writes_total The total number of notification history batches that were attempted to be written.
# TYPE grafana_alerting_notification_history_writes_total counter
grafana_alerting_notification_history_writes_total 2
`)
err := testutil.GatherAndCompare(reg, exp,
"grafana_alerting_notification_history_writes_total",
"grafana_alerting_notification_history_writes_failed_total",
)
require.NoError(t, err)
})
t.Run("returns error when context is missing required fields", func(t *testing.T) {
req := lokiclient.NewFakeRequester()
met := metrics.NewNotificationHistorianMetrics(prometheus.NewRegistry())
h := createTestNotificationHistorian(req, met)
err := <-h.Record(context.Background(), testAlerts, false, nil, time.Second)
require.Error(t, err)
})
}
func createTestNotificationHistorian(req client.Requester, met *metrics.NotificationHistorian) *NotificationHistorian {
writePathURL, _ := url.Parse("http://some.url")
cfg := lokiclient.LokiConfig{
WritePathURL: writePathURL,
ExternalLabels: map[string]string{"externalLabelKey": "externalLabelValue"},
Encoder: lokiclient.JsonEncoder{},
}
tracer := tracing.InitializeTracerForTest()
return NewNotificationHistorian(log.NewNopLogger(), cfg, req, met, tracer)
}
func recordCtx() context.Context {
ctx := notify.WithReceiverName(context.Background(), "testReceiverName")
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"foo": "bar"})
ctx = notify.WithNow(ctx, testNow)
return ctx
}