bccc980b90
* 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)
127 lines
5.5 KiB
Go
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
|
|
}
|