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)
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"golang.org/x/exp/constraints"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -44,7 +45,7 @@ type RuleStore interface {
|
||||
}
|
||||
|
||||
type lokiQueryClient interface {
|
||||
RangeQuery(ctx context.Context, query string, start, end, limit int64) (historian.QueryRes, error)
|
||||
RangeQuery(ctx context.Context, query string, start, end, limit int64) (lokiclient.QueryRes, error)
|
||||
MaxQuerySize() int
|
||||
}
|
||||
|
||||
@@ -60,14 +61,15 @@ func NewLokiHistorianStore(cfg setting.UnifiedAlertingStateHistorySettings, db d
|
||||
if !useStore(cfg) {
|
||||
return nil
|
||||
}
|
||||
lokiCfg, err := historian.NewLokiConfig(cfg)
|
||||
lokiCfg, err := lokiclient.NewLokiConfig(cfg.LokiSettings)
|
||||
if err != nil {
|
||||
// this config error is already handled elsewhere
|
||||
return nil
|
||||
}
|
||||
|
||||
metrics := ngmetrics.NewHistorianMetrics(reg, subsystem)
|
||||
return &LokiHistorianStore{
|
||||
client: historian.NewLokiClient(lokiCfg, historian.NewRequester(), ngmetrics.NewHistorianMetrics(reg, subsystem), log, tracer),
|
||||
client: lokiclient.NewLokiClient(lokiCfg, lokiclient.NewRequester(), metrics.BytesWritten, metrics.WriteDuration, log, tracer, historian.LokiClientSpanName),
|
||||
db: db,
|
||||
log: log,
|
||||
ruleStore: ruleStore,
|
||||
@@ -142,7 +144,7 @@ func (r *LokiHistorianStore) Get(ctx context.Context, query annotations.ItemQuer
|
||||
return items, err
|
||||
}
|
||||
|
||||
func (r *LokiHistorianStore) annotationsFromStream(stream historian.Stream, ac accesscontrol.AccessResources) []*annotations.ItemDTO {
|
||||
func (r *LokiHistorianStore) annotationsFromStream(stream lokiclient.Stream, ac accesscontrol.AccessResources) []*annotations.ItemDTO {
|
||||
items := make([]*annotations.ItemDTO, 0, len(stream.Values))
|
||||
for _, sample := range stream.Values {
|
||||
entry := historian.LokiEntry{}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/lokiclient"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/maps"
|
||||
@@ -84,7 +85,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
t.Run("can query history by alert id", func(t *testing.T) {
|
||||
rule := dashboardRules[dashboard1.UID][0]
|
||||
|
||||
fakeLokiClient.rangeQueryRes = []historian.Stream{
|
||||
fakeLokiClient.rangeQueryRes = []lokiclient.Stream{
|
||||
historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
}
|
||||
|
||||
@@ -111,7 +112,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
t.Run("can query history by alert uid", func(t *testing.T) {
|
||||
rule := dashboardRules[dashboard1.UID][0]
|
||||
|
||||
fakeLokiClient.rangeQueryRes = []historian.Stream{
|
||||
fakeLokiClient.rangeQueryRes = []lokiclient.Stream{
|
||||
historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
}
|
||||
|
||||
@@ -186,7 +187,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("can query history by dashboard id", func(t *testing.T) {
|
||||
fakeLokiClient.rangeQueryRes = []historian.Stream{
|
||||
fakeLokiClient.rangeQueryRes = []lokiclient.Stream{
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][0]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][1]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
}
|
||||
@@ -212,7 +213,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should return empty results when type is annotation", func(t *testing.T) {
|
||||
fakeLokiClient.rangeQueryRes = []historian.Stream{
|
||||
fakeLokiClient.rangeQueryRes = []lokiclient.Stream{
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][0]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][1]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
}
|
||||
@@ -236,7 +237,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should return empty results when history is outside time range", func(t *testing.T) {
|
||||
fakeLokiClient.rangeQueryRes = []historian.Stream{
|
||||
fakeLokiClient.rangeQueryRes = []lokiclient.Stream{
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][0]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][1]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
}
|
||||
@@ -262,7 +263,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should return partial results when history is partly outside clamped time range", func(t *testing.T) {
|
||||
fakeLokiClient.rangeQueryRes = []historian.Stream{
|
||||
fakeLokiClient.rangeQueryRes = []lokiclient.Stream{
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][0]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][1]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
}
|
||||
@@ -295,7 +296,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should sort history by time and be able to query by dashboard uid", func(t *testing.T) {
|
||||
fakeLokiClient.rangeQueryRes = []historian.Stream{
|
||||
fakeLokiClient.rangeQueryRes = []lokiclient.Stream{
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][0]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][1]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
}
|
||||
@@ -329,7 +330,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should return nothing if query is for tags only", func(t *testing.T) {
|
||||
fakeLokiClient.rangeQueryRes = []historian.Stream{
|
||||
fakeLokiClient.rangeQueryRes = []lokiclient.Stream{
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][0]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
historian.StatesToStream(ruleMetaFromRule(t, dashboardRules[dashboard1.UID][1]), transitions, map[string]string{}, log.NewNopLogger()),
|
||||
}
|
||||
@@ -360,13 +361,13 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
store := createTestLokiStore(t, sql, fakeLokiClient)
|
||||
|
||||
t.Run("should return empty list when no streams", func(t *testing.T) {
|
||||
items := store.annotationsFromStream(historian.Stream{}, annotation_ac.AccessResources{})
|
||||
items := store.annotationsFromStream(lokiclient.Stream{}, annotation_ac.AccessResources{})
|
||||
require.Empty(t, items)
|
||||
})
|
||||
|
||||
t.Run("should return empty list when no entries", func(t *testing.T) {
|
||||
items := store.annotationsFromStream(historian.Stream{
|
||||
Values: []historian.Sample{},
|
||||
items := store.annotationsFromStream(lokiclient.Stream{
|
||||
Values: []lokiclient.Sample{},
|
||||
}, annotation_ac.AccessResources{})
|
||||
require.Empty(t, items)
|
||||
})
|
||||
@@ -419,7 +420,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
rule = createAlertRule(t, sql, "Test rule", gen)
|
||||
stream2 := historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger())
|
||||
|
||||
stream := historian.Stream{
|
||||
stream := lokiclient.Stream{
|
||||
Values: append(stream1.Values, stream2.Values...),
|
||||
Stream: stream1.Stream,
|
||||
}
|
||||
@@ -450,7 +451,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) {
|
||||
rule.DashboardUID = nil
|
||||
stream2 := historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger())
|
||||
|
||||
stream := historian.Stream{
|
||||
stream := lokiclient.Stream{
|
||||
Values: append(stream1.Values, stream2.Values...),
|
||||
Stream: stream1.Stream,
|
||||
}
|
||||
@@ -811,23 +812,23 @@ func compareAnnotationItem(t *testing.T, expected, actual *annotations.ItemDTO)
|
||||
|
||||
type FakeLokiClient struct {
|
||||
client client.Requester
|
||||
cfg historian.LokiConfig
|
||||
cfg lokiclient.LokiConfig
|
||||
metrics *metrics.Historian
|
||||
log log.Logger
|
||||
rangeQueryRes []historian.Stream
|
||||
rangeQueryRes []lokiclient.Stream
|
||||
}
|
||||
|
||||
func NewFakeLokiClient() *FakeLokiClient {
|
||||
url, _ := url.Parse("http://some.url")
|
||||
req := historian.NewFakeRequester()
|
||||
req := lokiclient.NewFakeRequester()
|
||||
metrics := metrics.NewHistorianMetrics(prometheus.NewRegistry(), "annotations_test")
|
||||
|
||||
return &FakeLokiClient{
|
||||
client: client.NewTimedClient(req, metrics.WriteDuration),
|
||||
cfg: historian.LokiConfig{
|
||||
cfg: lokiclient.LokiConfig{
|
||||
WritePathURL: url,
|
||||
ReadPathURL: url,
|
||||
Encoder: historian.JsonEncoder{},
|
||||
Encoder: lokiclient.JsonEncoder{},
|
||||
MaxQueryLength: 721 * time.Hour,
|
||||
MaxQuerySize: 65536,
|
||||
},
|
||||
@@ -836,15 +837,15 @@ func NewFakeLokiClient() *FakeLokiClient {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FakeLokiClient) RangeQuery(ctx context.Context, query string, from, to, limit int64) (historian.QueryRes, error) {
|
||||
streams := make([]historian.Stream, len(c.rangeQueryRes))
|
||||
func (c *FakeLokiClient) RangeQuery(ctx context.Context, query string, from, to, limit int64) (lokiclient.QueryRes, error) {
|
||||
streams := make([]lokiclient.Stream, len(c.rangeQueryRes))
|
||||
|
||||
// clamp time range using logic from historian
|
||||
from, to = historian.ClampRange(from, to, c.cfg.MaxQueryLength.Nanoseconds())
|
||||
from, to = lokiclient.ClampRange(from, to, c.cfg.MaxQueryLength.Nanoseconds())
|
||||
|
||||
for n, stream := range c.rangeQueryRes {
|
||||
streams[n].Stream = stream.Stream
|
||||
streams[n].Values = []historian.Sample{}
|
||||
streams[n].Values = []lokiclient.Sample{}
|
||||
for _, sample := range stream.Values {
|
||||
if sample.T.UnixNano() < from || sample.T.UnixNano() >= to { // matches Loki behavior
|
||||
continue
|
||||
@@ -853,14 +854,14 @@ func (c *FakeLokiClient) RangeQuery(ctx context.Context, query string, from, to,
|
||||
}
|
||||
}
|
||||
|
||||
res := historian.QueryRes{
|
||||
Data: historian.QueryData{
|
||||
res := lokiclient.QueryRes{
|
||||
Data: lokiclient.QueryData{
|
||||
Result: streams,
|
||||
},
|
||||
}
|
||||
|
||||
// reset expected streams on read
|
||||
c.rangeQueryRes = []historian.Stream{}
|
||||
c.rangeQueryRes = []lokiclient.Stream{}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user