Files
grafana/pkg/services/ngalert/notifier/historian_test.go
T
Vadim Stepanov bccc980b90 Alerting: Notifiication history (#107644)
* Add unified_alerting.notification_history to ini files

* Parse notification history settings

* Move Loki client to a separate package

* Loki client: add params for metrics and traces

* add NotificationHistorian

* rm writeDuration

* remove RangeQuery stuff

* wip

* wip

* wip

* wip

* pass notification historian in tests

* unify loki settings

* unify loki settings

* add test

* update grafana/alerting

* make update-workspace

* add feature toggle

* fix configureNotificationHistorian

* Revert "add feature toggle"

This reverts commit de7af8f7

* add feature toggle

* more tests

* RuleUID

* fix metrics test

* met.Info.Set(0)
2025-07-17 14:26:26 +01:00

127 lines
5.5 KiB
Go

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
}