2020-05-10 16:58:17 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-09-13 12:48:18 +00:00
|
|
|
"fmt"
|
2022-03-29 13:09:07 +00:00
|
|
|
"reflect"
|
2020-05-10 16:58:17 +00:00
|
|
|
"sort"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2020-06-01 10:46:37 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
|
2020-05-10 16:58:17 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
2022-12-06 14:36:46 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/remotewrite"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
2022-02-11 14:17:00 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
2020-05-10 16:58:17 +00:00
|
|
|
)
|
|
|
|
|
2020-09-02 22:00:55 +00:00
|
|
|
func init() {
|
|
|
|
// Disable rand sleep on group start during tests in order to speed up test execution.
|
|
|
|
// Rand sleep is needed only in prod code.
|
|
|
|
skipRandSleepOnGroupStart = true
|
|
|
|
}
|
|
|
|
|
2020-05-10 16:58:17 +00:00
|
|
|
func TestUpdateWith(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
2020-06-15 19:15:47 +00:00
|
|
|
currentRules []config.Rule
|
|
|
|
newRules []config.Rule
|
2020-05-10 16:58:17 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
"new rule",
|
2020-06-15 19:15:47 +00:00
|
|
|
nil,
|
|
|
|
[]config.Rule{{Alert: "bar"}},
|
2020-05-10 16:58:17 +00:00
|
|
|
},
|
|
|
|
{
|
2020-06-01 10:46:37 +00:00
|
|
|
"update alerting rule",
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{{
|
|
|
|
Alert: "foo",
|
|
|
|
Expr: "up > 0",
|
2022-02-11 14:17:00 +00:00
|
|
|
For: promutils.NewDuration(time.Second),
|
2020-05-10 16:58:17 +00:00
|
|
|
Labels: map[string]string{
|
|
|
|
"bar": "baz",
|
|
|
|
},
|
|
|
|
Annotations: map[string]string{
|
|
|
|
"summary": "{{ $value|humanize }}",
|
|
|
|
"description": "{{$labels}}",
|
|
|
|
},
|
2023-07-27 13:13:13 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Alert: "bar",
|
|
|
|
Expr: "up > 0",
|
|
|
|
For: promutils.NewDuration(time.Second),
|
|
|
|
Labels: map[string]string{
|
|
|
|
"bar": "baz",
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
[]config.Rule{
|
|
|
|
{
|
|
|
|
Alert: "foo",
|
|
|
|
Expr: "up > 10",
|
|
|
|
For: promutils.NewDuration(time.Second),
|
|
|
|
Labels: map[string]string{
|
|
|
|
"baz": "bar",
|
|
|
|
},
|
|
|
|
Annotations: map[string]string{
|
|
|
|
"summary": "none",
|
|
|
|
},
|
2020-05-10 16:58:17 +00:00
|
|
|
},
|
2023-07-27 13:13:13 +00:00
|
|
|
{
|
|
|
|
Alert: "bar",
|
|
|
|
Expr: "up > 0",
|
|
|
|
For: promutils.NewDuration(2 * time.Second),
|
|
|
|
KeepFiringFor: promutils.NewDuration(time.Minute),
|
|
|
|
Labels: map[string]string{
|
|
|
|
"bar": "baz",
|
|
|
|
},
|
|
|
|
}},
|
2020-05-10 16:58:17 +00:00
|
|
|
},
|
2020-06-01 10:46:37 +00:00
|
|
|
{
|
|
|
|
"update recording rule",
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{{
|
|
|
|
Record: "foo",
|
|
|
|
Expr: "max(up)",
|
2020-06-01 10:46:37 +00:00
|
|
|
Labels: map[string]string{
|
|
|
|
"bar": "baz",
|
|
|
|
},
|
|
|
|
}},
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{{
|
|
|
|
Record: "foo",
|
|
|
|
Expr: "min(up)",
|
2020-06-01 10:46:37 +00:00
|
|
|
Labels: map[string]string{
|
|
|
|
"baz": "bar",
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
},
|
2020-05-10 16:58:17 +00:00
|
|
|
{
|
|
|
|
"empty rule",
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{{Alert: "foo"}, {Record: "bar"}},
|
|
|
|
nil,
|
2020-05-10 16:58:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"multiple rules",
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{
|
|
|
|
{Alert: "bar"},
|
|
|
|
{Alert: "baz"},
|
|
|
|
{Alert: "foo"},
|
2020-06-01 10:46:37 +00:00
|
|
|
},
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{
|
|
|
|
{Alert: "baz"},
|
|
|
|
{Record: "foo"},
|
2020-06-01 10:46:37 +00:00
|
|
|
},
|
2020-05-15 06:55:22 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"replace rule",
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{{Alert: "foo1"}},
|
|
|
|
[]config.Rule{{Alert: "foo2"}},
|
2020-05-15 06:55:22 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"replace multiple rules",
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{
|
|
|
|
{Alert: "foo1"},
|
|
|
|
{Record: "foo2"},
|
|
|
|
{Alert: "foo3"},
|
2020-06-01 10:46:37 +00:00
|
|
|
},
|
2020-06-15 19:15:47 +00:00
|
|
|
[]config.Rule{
|
|
|
|
{Alert: "foo3"},
|
|
|
|
{Alert: "foo4"},
|
|
|
|
{Record: "foo5"},
|
2020-06-01 10:46:37 +00:00
|
|
|
},
|
2020-05-10 16:58:17 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
2020-06-15 19:15:47 +00:00
|
|
|
g := &Group{Name: "test"}
|
2021-04-28 20:41:15 +00:00
|
|
|
qb := &fakeQuerier{}
|
2020-06-15 19:15:47 +00:00
|
|
|
for _, r := range tc.currentRules {
|
|
|
|
r.ID = config.HashRule(r)
|
2021-04-28 20:41:15 +00:00
|
|
|
g.Rules = append(g.Rules, g.newRule(qb, r))
|
2020-06-15 19:15:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ng := &Group{Name: "test"}
|
|
|
|
for _, r := range tc.newRules {
|
|
|
|
r.ID = config.HashRule(r)
|
2021-04-28 20:41:15 +00:00
|
|
|
ng.Rules = append(ng.Rules, ng.newRule(qb, r))
|
2020-06-15 19:15:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err := g.updateWith(ng)
|
2020-06-01 10:46:37 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2020-05-10 16:58:17 +00:00
|
|
|
|
|
|
|
if len(g.Rules) != len(tc.newRules) {
|
|
|
|
t.Fatalf("expected to have %d rules; got: %d",
|
|
|
|
len(g.Rules), len(tc.newRules))
|
|
|
|
}
|
2020-05-15 06:55:22 +00:00
|
|
|
sort.Slice(g.Rules, func(i, j int) bool {
|
2020-06-01 10:46:37 +00:00
|
|
|
return g.Rules[i].ID() < g.Rules[j].ID()
|
2020-05-15 06:55:22 +00:00
|
|
|
})
|
2020-06-15 19:15:47 +00:00
|
|
|
sort.Slice(ng.Rules, func(i, j int) bool {
|
|
|
|
return ng.Rules[i].ID() < ng.Rules[j].ID()
|
|
|
|
})
|
2020-05-10 16:58:17 +00:00
|
|
|
for i, r := range g.Rules {
|
2020-06-15 19:15:47 +00:00
|
|
|
got, want := r, ng.Rules[i]
|
2020-06-01 10:46:37 +00:00
|
|
|
if got.ID() != want.ID() {
|
|
|
|
t.Fatalf("expected to have rule %q; got %q", want, got)
|
2020-05-10 16:58:17 +00:00
|
|
|
}
|
2020-06-01 10:46:37 +00:00
|
|
|
if err := compareRules(t, got, want); err != nil {
|
2022-07-18 09:02:51 +00:00
|
|
|
t.Fatalf("comparison error: %s", err)
|
2020-05-10 16:58:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGroupStart(t *testing.T) {
|
|
|
|
// TODO: make parsing from string instead of file
|
2022-07-22 11:50:41 +00:00
|
|
|
groups, err := config.Parse([]string{"config/testdata/rules/rules1-good.rules"}, notifier.ValidateTemplates, true)
|
2020-05-10 16:58:17 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to parse rules: %s", err)
|
|
|
|
}
|
2022-03-16 15:26:33 +00:00
|
|
|
|
2020-05-10 16:58:17 +00:00
|
|
|
fs := &fakeQuerier{}
|
2021-04-28 20:41:15 +00:00
|
|
|
fn := &fakeNotifier{}
|
|
|
|
|
2022-03-16 15:26:33 +00:00
|
|
|
const evalInterval = time.Millisecond
|
2021-04-28 20:41:15 +00:00
|
|
|
g := newGroup(groups[0], fs, evalInterval, map[string]string{"cluster": "east-1"})
|
|
|
|
g.Concurrency = 2
|
2020-05-10 16:58:17 +00:00
|
|
|
|
|
|
|
const inst1, inst2, job = "foo", "bar", "baz"
|
|
|
|
m1 := metricWithLabels(t, "instance", inst1, "job", job)
|
|
|
|
m2 := metricWithLabels(t, "instance", inst2, "job", job)
|
|
|
|
|
2020-06-01 10:46:37 +00:00
|
|
|
r := g.Rules[0].(*AlertingRule)
|
vmalert: fix labels and annotations processing for alerts (#2403)
To improve compatibility with Prometheus alerting the order of
templates processing has changed.
Before, vmalert did all labels processing beforehand. It meant
all extra labels (such as `alertname`, `alertgroup` or rule labels)
were available in templating. All collisions were resolved in favour
of extra labels.
In Prometheus, only labels from the received metric are available in
templating, so no collisions are possible.
This change makes vmalert's behaviour similar to Prometheus.
For example, consider alerting rule which is triggered by time series
with `alertname` label. In vmalert, this label would be overriden
by alerting rule's name everywhere: for alert labels, for annotations, etc.
In Prometheus, it would be overriden for alert's labels only, but in annotations
the original label value would be available.
See more details here https://github.com/prometheus/compliance/issues/80
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2022-04-06 18:24:45 +00:00
|
|
|
alert1, err := r.newAlert(m1, nil, time.Now(), nil)
|
2020-05-10 16:58:17 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("faield to create alert: %s", err)
|
|
|
|
}
|
|
|
|
alert1.State = notifier.StateFiring
|
2020-11-09 22:27:32 +00:00
|
|
|
// add external label
|
|
|
|
alert1.Labels["cluster"] = "east-1"
|
|
|
|
// add rule labels - see config/testdata/rules1-good.rules
|
|
|
|
alert1.Labels["label"] = "bar"
|
|
|
|
alert1.Labels["host"] = inst1
|
2021-10-22 09:30:38 +00:00
|
|
|
// add service labels
|
|
|
|
alert1.Labels[alertNameLabel] = alert1.Name
|
|
|
|
alert1.Labels[alertGroupNameLabel] = g.Name
|
vmalert: fix labels and annotations processing for alerts (#2403)
To improve compatibility with Prometheus alerting the order of
templates processing has changed.
Before, vmalert did all labels processing beforehand. It meant
all extra labels (such as `alertname`, `alertgroup` or rule labels)
were available in templating. All collisions were resolved in favour
of extra labels.
In Prometheus, only labels from the received metric are available in
templating, so no collisions are possible.
This change makes vmalert's behaviour similar to Prometheus.
For example, consider alerting rule which is triggered by time series
with `alertname` label. In vmalert, this label would be overriden
by alerting rule's name everywhere: for alert labels, for annotations, etc.
In Prometheus, it would be overriden for alert's labels only, but in annotations
the original label value would be available.
See more details here https://github.com/prometheus/compliance/issues/80
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2022-04-06 18:24:45 +00:00
|
|
|
alert1.ID = hash(alert1.Labels)
|
2020-05-10 16:58:17 +00:00
|
|
|
|
vmalert: fix labels and annotations processing for alerts (#2403)
To improve compatibility with Prometheus alerting the order of
templates processing has changed.
Before, vmalert did all labels processing beforehand. It meant
all extra labels (such as `alertname`, `alertgroup` or rule labels)
were available in templating. All collisions were resolved in favour
of extra labels.
In Prometheus, only labels from the received metric are available in
templating, so no collisions are possible.
This change makes vmalert's behaviour similar to Prometheus.
For example, consider alerting rule which is triggered by time series
with `alertname` label. In vmalert, this label would be overriden
by alerting rule's name everywhere: for alert labels, for annotations, etc.
In Prometheus, it would be overriden for alert's labels only, but in annotations
the original label value would be available.
See more details here https://github.com/prometheus/compliance/issues/80
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2022-04-06 18:24:45 +00:00
|
|
|
alert2, err := r.newAlert(m2, nil, time.Now(), nil)
|
2020-05-10 16:58:17 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("faield to create alert: %s", err)
|
|
|
|
}
|
|
|
|
alert2.State = notifier.StateFiring
|
2020-11-09 22:27:32 +00:00
|
|
|
// add external label
|
|
|
|
alert2.Labels["cluster"] = "east-1"
|
|
|
|
// add rule labels - see config/testdata/rules1-good.rules
|
|
|
|
alert2.Labels["label"] = "bar"
|
|
|
|
alert2.Labels["host"] = inst2
|
2021-10-22 09:30:38 +00:00
|
|
|
// add service labels
|
|
|
|
alert2.Labels[alertNameLabel] = alert2.Name
|
|
|
|
alert2.Labels[alertGroupNameLabel] = g.Name
|
vmalert: fix labels and annotations processing for alerts (#2403)
To improve compatibility with Prometheus alerting the order of
templates processing has changed.
Before, vmalert did all labels processing beforehand. It meant
all extra labels (such as `alertname`, `alertgroup` or rule labels)
were available in templating. All collisions were resolved in favour
of extra labels.
In Prometheus, only labels from the received metric are available in
templating, so no collisions are possible.
This change makes vmalert's behaviour similar to Prometheus.
For example, consider alerting rule which is triggered by time series
with `alertname` label. In vmalert, this label would be overriden
by alerting rule's name everywhere: for alert labels, for annotations, etc.
In Prometheus, it would be overriden for alert's labels only, but in annotations
the original label value would be available.
See more details here https://github.com/prometheus/compliance/issues/80
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2022-04-06 18:24:45 +00:00
|
|
|
alert2.ID = hash(alert2.Labels)
|
2020-05-10 16:58:17 +00:00
|
|
|
|
|
|
|
finished := make(chan struct{})
|
|
|
|
fs.add(m1)
|
|
|
|
fs.add(m2)
|
|
|
|
go func() {
|
2023-02-04 03:46:13 +00:00
|
|
|
g.start(context.Background(), func() []notifier.Notifier { return []notifier.Notifier{fn} }, nil, fs)
|
2020-05-10 16:58:17 +00:00
|
|
|
close(finished)
|
|
|
|
}()
|
|
|
|
|
|
|
|
// wait for multiple evals
|
|
|
|
time.Sleep(20 * evalInterval)
|
|
|
|
|
|
|
|
gotAlerts := fn.getAlerts()
|
|
|
|
expectedAlerts := []notifier.Alert{*alert1, *alert2}
|
|
|
|
compareAlerts(t, expectedAlerts, gotAlerts)
|
|
|
|
|
2022-03-16 15:26:33 +00:00
|
|
|
gotAlertsNum := fn.getCounter()
|
|
|
|
if gotAlertsNum < len(expectedAlerts)*2 {
|
|
|
|
t.Fatalf("expected to receive at least %d alerts; got %d instead",
|
|
|
|
len(expectedAlerts)*2, gotAlertsNum)
|
|
|
|
}
|
|
|
|
|
2020-05-10 16:58:17 +00:00
|
|
|
// reset previous data
|
|
|
|
fs.reset()
|
|
|
|
// and set only one datapoint for response
|
|
|
|
fs.add(m1)
|
|
|
|
|
|
|
|
// wait for multiple evals
|
|
|
|
time.Sleep(20 * evalInterval)
|
|
|
|
|
|
|
|
gotAlerts = fn.getAlerts()
|
2022-03-29 13:09:07 +00:00
|
|
|
alert2.State = notifier.StateInactive
|
|
|
|
expectedAlerts = []notifier.Alert{*alert1, *alert2}
|
2020-05-10 16:58:17 +00:00
|
|
|
compareAlerts(t, expectedAlerts, gotAlerts)
|
|
|
|
|
|
|
|
g.close()
|
|
|
|
<-finished
|
|
|
|
}
|
2021-09-13 12:48:18 +00:00
|
|
|
|
|
|
|
func TestResolveDuration(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
groupInterval time.Duration
|
|
|
|
maxDuration time.Duration
|
2022-03-16 15:26:33 +00:00
|
|
|
resendDelay time.Duration
|
2021-09-13 12:48:18 +00:00
|
|
|
expected time.Duration
|
|
|
|
}{
|
2022-03-16 15:26:33 +00:00
|
|
|
{time.Minute, 0, 0, 4 * time.Minute},
|
|
|
|
{time.Minute, 0, 2 * time.Minute, 8 * time.Minute},
|
|
|
|
{time.Minute, 4 * time.Minute, 4 * time.Minute, 4 * time.Minute},
|
|
|
|
{2 * time.Minute, time.Minute, 2 * time.Minute, time.Minute},
|
|
|
|
{time.Minute, 2 * time.Minute, 1 * time.Minute, 2 * time.Minute},
|
|
|
|
{2 * time.Minute, 0, 1 * time.Minute, 8 * time.Minute},
|
|
|
|
{0, 0, 0, 0},
|
2021-09-13 12:48:18 +00:00
|
|
|
}
|
2022-03-16 15:26:33 +00:00
|
|
|
|
2021-09-13 12:48:18 +00:00
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(fmt.Sprintf("%v-%v-%v", tc.groupInterval, tc.expected, tc.maxDuration), func(t *testing.T) {
|
2022-03-29 13:09:07 +00:00
|
|
|
got := getResolveDuration(tc.groupInterval, tc.resendDelay, tc.maxDuration)
|
2021-09-13 12:48:18 +00:00
|
|
|
if got != tc.expected {
|
|
|
|
t.Errorf("expected to have %v; got %v", tc.expected, got)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-03-29 13:09:07 +00:00
|
|
|
|
|
|
|
func TestGetStaleSeries(t *testing.T) {
|
|
|
|
ts := time.Now()
|
|
|
|
e := &executor{
|
|
|
|
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
|
|
|
}
|
|
|
|
f := func(rule Rule, labels, expLabels [][]prompbmarshal.Label) {
|
|
|
|
t.Helper()
|
|
|
|
var tss []prompbmarshal.TimeSeries
|
|
|
|
for _, l := range labels {
|
|
|
|
tss = append(tss, newTimeSeriesPB([]float64{1}, []int64{ts.Unix()}, l))
|
|
|
|
}
|
|
|
|
staleS := e.getStaleSeries(rule, tss, ts)
|
|
|
|
if staleS == nil && expLabels == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(staleS) != len(expLabels) {
|
|
|
|
t.Fatalf("expected to get %d stale series, got %d",
|
|
|
|
len(expLabels), len(staleS))
|
|
|
|
}
|
|
|
|
for i, exp := range expLabels {
|
|
|
|
got := staleS[i]
|
|
|
|
if !reflect.DeepEqual(exp, got.Labels) {
|
|
|
|
t.Fatalf("expected to get labels: \n%v;\ngot instead: \n%v",
|
|
|
|
exp, got.Labels)
|
|
|
|
}
|
|
|
|
if len(got.Samples) != 1 {
|
|
|
|
t.Fatalf("expected to have 1 sample; got %d", len(got.Samples))
|
|
|
|
}
|
|
|
|
if !decimal.IsStaleNaN(got.Samples[0].Value) {
|
|
|
|
t.Fatalf("expected sample value to be %v; got %v", decimal.StaleNaN, got.Samples[0].Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// warn: keep in mind, that executor holds the state, so sequence of f calls matters
|
|
|
|
|
|
|
|
// single series
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")},
|
|
|
|
nil)
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")},
|
|
|
|
nil)
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
nil,
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")})
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
nil,
|
|
|
|
nil)
|
|
|
|
|
|
|
|
// multiple series
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
[][]prompbmarshal.Label{
|
|
|
|
toPromLabels(t, "__name__", "job:foo", "job", "foo"),
|
|
|
|
toPromLabels(t, "__name__", "job:foo", "job", "bar"),
|
|
|
|
},
|
|
|
|
nil)
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")},
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")})
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")},
|
|
|
|
nil)
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
nil,
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")})
|
|
|
|
|
|
|
|
// multiple rules and series
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
[][]prompbmarshal.Label{
|
|
|
|
toPromLabels(t, "__name__", "job:foo", "job", "foo"),
|
|
|
|
toPromLabels(t, "__name__", "job:foo", "job", "bar"),
|
|
|
|
},
|
|
|
|
nil)
|
|
|
|
f(&AlertingRule{RuleID: 2},
|
|
|
|
[][]prompbmarshal.Label{
|
|
|
|
toPromLabels(t, "__name__", "job:foo", "job", "foo"),
|
|
|
|
toPromLabels(t, "__name__", "job:foo", "job", "bar"),
|
|
|
|
},
|
|
|
|
nil)
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")},
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")})
|
|
|
|
f(&AlertingRule{RuleID: 1},
|
|
|
|
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")},
|
|
|
|
nil)
|
|
|
|
}
|
2022-05-13 08:04:49 +00:00
|
|
|
|
|
|
|
func TestPurgeStaleSeries(t *testing.T) {
|
|
|
|
ts := time.Now()
|
|
|
|
labels := toPromLabels(t, "__name__", "job:foo", "job", "foo")
|
|
|
|
tss := []prompbmarshal.TimeSeries{newTimeSeriesPB([]float64{1}, []int64{ts.Unix()}, labels)}
|
|
|
|
|
|
|
|
f := func(curRules, newRules, expStaleRules []Rule) {
|
|
|
|
t.Helper()
|
|
|
|
e := &executor{
|
|
|
|
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
|
|
|
}
|
|
|
|
// seed executor with series for
|
|
|
|
// current rules
|
|
|
|
for _, rule := range curRules {
|
|
|
|
e.getStaleSeries(rule, tss, ts)
|
|
|
|
}
|
|
|
|
|
|
|
|
e.purgeStaleSeries(newRules)
|
|
|
|
|
|
|
|
if len(e.previouslySentSeriesToRW) != len(expStaleRules) {
|
|
|
|
t.Fatalf("expected to get %d stale series, got %d",
|
|
|
|
len(expStaleRules), len(e.previouslySentSeriesToRW))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, exp := range expStaleRules {
|
|
|
|
if _, ok := e.previouslySentSeriesToRW[exp.ID()]; !ok {
|
|
|
|
t.Fatalf("expected to have rule %d; got nil instead", exp.ID())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f(nil, nil, nil)
|
|
|
|
f(
|
|
|
|
nil,
|
|
|
|
[]Rule{&AlertingRule{RuleID: 1}},
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
f(
|
|
|
|
[]Rule{&AlertingRule{RuleID: 1}},
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
f(
|
|
|
|
[]Rule{&AlertingRule{RuleID: 1}},
|
|
|
|
[]Rule{&AlertingRule{RuleID: 2}},
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
f(
|
|
|
|
[]Rule{&AlertingRule{RuleID: 1}, &AlertingRule{RuleID: 2}},
|
|
|
|
[]Rule{&AlertingRule{RuleID: 2}},
|
|
|
|
[]Rule{&AlertingRule{RuleID: 2}},
|
|
|
|
)
|
|
|
|
f(
|
|
|
|
[]Rule{&AlertingRule{RuleID: 1}, &AlertingRule{RuleID: 2}},
|
|
|
|
[]Rule{&AlertingRule{RuleID: 1}, &AlertingRule{RuleID: 2}},
|
|
|
|
[]Rule{&AlertingRule{RuleID: 1}, &AlertingRule{RuleID: 2}},
|
|
|
|
)
|
|
|
|
}
|
2022-06-18 07:11:37 +00:00
|
|
|
|
|
|
|
func TestFaultyNotifier(t *testing.T) {
|
|
|
|
fq := &fakeQuerier{}
|
|
|
|
fq.add(metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "bar"))
|
|
|
|
|
|
|
|
r := newTestAlertingRule("instant", 0)
|
|
|
|
r.q = fq
|
|
|
|
|
|
|
|
fn := &fakeNotifier{}
|
|
|
|
e := &executor{
|
|
|
|
notifiers: func() []notifier.Notifier {
|
|
|
|
return []notifier.Notifier{
|
|
|
|
&faultyNotifier{},
|
|
|
|
fn,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
delay := 5 * time.Second
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), delay)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
_ = e.exec(ctx, r, time.Now(), 0, 10)
|
|
|
|
}()
|
|
|
|
|
|
|
|
tn := time.Now()
|
|
|
|
deadline := tn.Add(delay / 2)
|
|
|
|
for {
|
|
|
|
if fn.getCounter() > 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if tn.After(deadline) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
tn = time.Now()
|
|
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
}
|
|
|
|
t.Fatalf("alive notifier didn't receive notification by %v", deadline)
|
|
|
|
}
|
2022-12-06 14:36:46 +00:00
|
|
|
|
|
|
|
func TestFaultyRW(t *testing.T) {
|
|
|
|
fq := &fakeQuerier{}
|
|
|
|
fq.add(metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "bar"))
|
|
|
|
|
|
|
|
r := &RecordingRule{
|
|
|
|
Name: "test",
|
2022-12-29 11:36:44 +00:00
|
|
|
state: newRuleState(10),
|
2022-12-06 14:36:46 +00:00
|
|
|
q: fq,
|
|
|
|
}
|
|
|
|
|
|
|
|
e := &executor{
|
|
|
|
rw: &remotewrite.Client{},
|
|
|
|
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
|
|
|
}
|
|
|
|
|
|
|
|
err := e.exec(context.Background(), r, time.Now(), 0, 10)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("expected to get an error from faulty RW client, got nil instead")
|
|
|
|
}
|
|
|
|
}
|
2023-03-01 14:48:20 +00:00
|
|
|
|
|
|
|
func TestCloseWithEvalInterruption(t *testing.T) {
|
|
|
|
groups, err := config.Parse([]string{"config/testdata/rules/rules1-good.rules"}, notifier.ValidateTemplates, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to parse rules: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
const delay = time.Second * 2
|
|
|
|
fq := &fakeQuerierWithDelay{delay: delay}
|
|
|
|
|
|
|
|
const evalInterval = time.Millisecond
|
|
|
|
g := newGroup(groups[0], fq, evalInterval, nil)
|
|
|
|
|
|
|
|
go g.start(context.Background(), nil, nil, nil)
|
|
|
|
|
|
|
|
time.Sleep(evalInterval * 20)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
g.close()
|
|
|
|
}()
|
|
|
|
|
|
|
|
deadline := time.Tick(delay / 2)
|
|
|
|
select {
|
|
|
|
case <-deadline:
|
|
|
|
t.Fatalf("deadline for close exceeded")
|
|
|
|
case <-g.finishedCh:
|
|
|
|
}
|
|
|
|
}
|