2020-06-01 12:46:37 +02:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
2020-06-15 21:15:47 +02:00
|
|
|
"time"
|
2020-06-01 12:46:37 +02:00
|
|
|
|
2021-02-01 14:02:44 +01:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
2020-12-14 19:11:45 +01:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
2021-07-12 11:34:10 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
|
|
|
"gopkg.in/yaml.v2"
|
2020-06-01 12:46:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
u, _ := url.Parse("https://victoriametrics.com/path")
|
|
|
|
notifier.InitTemplateFunc(u)
|
|
|
|
os.Exit(m.Run())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseGood(t *testing.T) {
|
2020-06-06 22:27:09 +02:00
|
|
|
if _, err := Parse([]string{"testdata/*good.rules", "testdata/dir/*good.*"}, true, true); err != nil {
|
2020-06-01 12:46:37 +02:00
|
|
|
t.Errorf("error parsing files %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseBad(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
path []string
|
|
|
|
expErr string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
[]string{"testdata/rules0-bad.rules"},
|
|
|
|
"unexpected token",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
[]string{"testdata/dir/rules0-bad.rules"},
|
|
|
|
"error parsing annotation",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
[]string{"testdata/dir/rules1-bad.rules"},
|
|
|
|
"duplicate in file",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
[]string{"testdata/dir/rules2-bad.rules"},
|
2020-12-14 19:11:45 +01:00
|
|
|
"function \"unknown\" not defined",
|
2020-06-01 12:46:37 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
[]string{"testdata/dir/rules3-bad.rules"},
|
|
|
|
"either `record` or `alert` must be set",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
[]string{"testdata/dir/rules4-bad.rules"},
|
|
|
|
"either `record` or `alert` must be set",
|
|
|
|
},
|
2021-02-01 14:02:44 +01:00
|
|
|
{
|
|
|
|
[]string{"testdata/rules1-bad.rules"},
|
|
|
|
"bad graphite expr",
|
|
|
|
},
|
2020-06-01 12:46:37 +02:00
|
|
|
}
|
|
|
|
for _, tc := range testCases {
|
2020-06-06 22:27:09 +02:00
|
|
|
_, err := Parse(tc.path, true, true)
|
2020-06-01 12:46:37 +02:00
|
|
|
if err == nil {
|
|
|
|
t.Errorf("expected to get error")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), tc.expErr) {
|
|
|
|
t.Errorf("expected err to contain %q; got %q instead", tc.expErr, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRule_Validate(t *testing.T) {
|
|
|
|
if err := (&Rule{}).Validate(); err == nil {
|
2020-07-02 17:05:36 +02:00
|
|
|
t.Errorf("expected empty name error")
|
2020-06-01 12:46:37 +02:00
|
|
|
}
|
|
|
|
if err := (&Rule{Alert: "alert"}).Validate(); err == nil {
|
2020-07-02 17:05:36 +02:00
|
|
|
t.Errorf("expected empty expr error")
|
2020-06-01 12:46:37 +02:00
|
|
|
}
|
|
|
|
if err := (&Rule{Alert: "alert", Expr: "test>0"}).Validate(); err != nil {
|
2020-07-02 17:05:36 +02:00
|
|
|
t.Errorf("expected valid rule; got %s", err)
|
2020-06-06 22:27:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGroup_Validate(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
group *Group
|
|
|
|
rules []Rule
|
|
|
|
validateAnnotations bool
|
|
|
|
validateExpressions bool
|
|
|
|
expErr string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
group: &Group{},
|
|
|
|
expErr: "group name must be set",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{
|
|
|
|
Record: "record",
|
|
|
|
Expr: "up | 0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{
|
|
|
|
Record: "record",
|
|
|
|
Expr: "up | 0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "invalid expression",
|
|
|
|
validateExpressions: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{
|
|
|
|
Alert: "alert",
|
|
|
|
Expr: "up == 1",
|
|
|
|
Labels: map[string]string{
|
|
|
|
"summary": "{{ value|query }}",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{
|
|
|
|
Alert: "alert",
|
|
|
|
Expr: "up == 1",
|
|
|
|
Labels: map[string]string{
|
2020-12-14 19:11:45 +01:00
|
|
|
"summary": `
|
|
|
|
{{ with printf "node_memory_MemTotal{job='node',instance='%s'}" "localhost" | query }}
|
|
|
|
{{ . | first | value | humanize1024 }}B
|
|
|
|
{{ end }}`,
|
2020-06-06 22:27:09 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
validateAnnotations: true,
|
|
|
|
},
|
2020-06-15 21:15:47 +02:00
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{
|
|
|
|
Alert: "alert",
|
|
|
|
Expr: "up == 1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Alert: "alert",
|
|
|
|
Expr: "up == 1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "duplicate",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"summary": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"summary": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "duplicate",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{Record: "record", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"summary": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
{Record: "record", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"summary": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "duplicate",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"summary": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"description": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test",
|
|
|
|
Rules: []Rule{
|
|
|
|
{Record: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"summary": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"summary": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "",
|
|
|
|
},
|
2021-02-01 14:02:44 +01:00
|
|
|
{
|
|
|
|
group: &Group{Name: "test thanos",
|
|
|
|
Type: datasource.NewRawType("thanos"),
|
|
|
|
Rules: []Rule{
|
|
|
|
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"description": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
validateExpressions: true,
|
|
|
|
expErr: "unknown datasource type",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test graphite",
|
|
|
|
Type: datasource.NewGraphiteType(),
|
|
|
|
Rules: []Rule{
|
|
|
|
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"description": "some-description",
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
validateExpressions: true,
|
|
|
|
expErr: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{Name: "test prometheus",
|
|
|
|
Type: datasource.NewPrometheusType(),
|
|
|
|
Rules: []Rule{
|
|
|
|
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"description": "{{ value|query }}",
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
validateExpressions: true,
|
|
|
|
expErr: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{
|
|
|
|
Name: "test graphite inherit",
|
|
|
|
Type: datasource.NewGraphiteType(),
|
|
|
|
Rules: []Rule{
|
|
|
|
{
|
|
|
|
Expr: "sumSeries(time('foo.bar',10))",
|
2021-07-12 11:34:10 +02:00
|
|
|
For: utils.NewPromDuration(10 * time.Millisecond),
|
2021-02-01 14:02:44 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Expr: "sum(up == 0 ) by (host)",
|
|
|
|
Type: datasource.NewPrometheusType(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
group: &Group{
|
|
|
|
Name: "test graphite prometheus bad expr",
|
|
|
|
Type: datasource.NewGraphiteType(),
|
|
|
|
Rules: []Rule{
|
|
|
|
{
|
|
|
|
Expr: "sum(up == 0 ) by (host)",
|
2021-07-12 11:34:10 +02:00
|
|
|
For: utils.NewPromDuration(10 * time.Millisecond),
|
2021-02-01 14:02:44 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Expr: "sumSeries(time('foo.bar',10))",
|
|
|
|
Type: datasource.NewPrometheusType(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expErr: "invalid rule",
|
|
|
|
},
|
2020-06-06 22:27:09 +02:00
|
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
|
|
err := tc.group.Validate(tc.validateAnnotations, tc.validateExpressions)
|
|
|
|
if err == nil {
|
|
|
|
if tc.expErr != "" {
|
|
|
|
t.Errorf("expected to get err %q; got nil insted", tc.expErr)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), tc.expErr) {
|
|
|
|
t.Errorf("expected err to contain %q; got %q instead", tc.expErr, err)
|
|
|
|
}
|
2020-06-01 12:46:37 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-15 21:15:47 +02:00
|
|
|
|
|
|
|
func TestHashRule(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
|
|
a, b Rule
|
|
|
|
equal bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
Rule{Record: "record", Expr: "up == 1"},
|
|
|
|
Rule{Record: "record", Expr: "up == 1"},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1"},
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1"},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"baz": "foo",
|
|
|
|
}},
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"baz": "foo",
|
|
|
|
}},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"baz": "foo",
|
|
|
|
}},
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"baz": "foo",
|
|
|
|
"foo": "bar",
|
|
|
|
}},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Alert: "record", Expr: "up == 1"},
|
|
|
|
Rule{Alert: "record", Expr: "up == 1"},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
2021-07-12 11:34:10 +02:00
|
|
|
Rule{Alert: "alert", Expr: "up == 1", For: utils.NewPromDuration(time.Minute)},
|
2020-06-15 21:15:47 +02:00
|
|
|
Rule{Alert: "alert", Expr: "up == 1"},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Alert: "record", Expr: "up == 1"},
|
|
|
|
Rule{Record: "record", Expr: "up == 1"},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Record: "record", Expr: "up == 1"},
|
|
|
|
Rule{Record: "record", Expr: "up == 2"},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"baz": "foo",
|
|
|
|
}},
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"baz": "foo",
|
|
|
|
"foo": "baz",
|
|
|
|
}},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"baz": "foo",
|
|
|
|
}},
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"baz": "foo",
|
|
|
|
}},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"baz": "foo",
|
|
|
|
}},
|
|
|
|
Rule{Alert: "alert", Expr: "up == 1"},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
|
|
aID, bID := HashRule(tc.a), HashRule(tc.b)
|
|
|
|
if tc.equal != (aID == bID) {
|
|
|
|
t.Fatalf("missmatch for rule %d", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-11 21:14:30 +02:00
|
|
|
|
|
|
|
func TestGroupChecksum(t *testing.T) {
|
2020-11-29 08:48:42 +01:00
|
|
|
f := func(t *testing.T, data, newData string) {
|
|
|
|
t.Helper()
|
|
|
|
var g Group
|
|
|
|
if err := yaml.Unmarshal([]byte(data), &g); err != nil {
|
|
|
|
t.Fatalf("failed to unmarshal: %s", err)
|
|
|
|
}
|
|
|
|
if g.Checksum == "" {
|
|
|
|
t.Fatalf("expected to get non-empty checksum")
|
|
|
|
}
|
|
|
|
|
|
|
|
var ng Group
|
|
|
|
if err := yaml.Unmarshal([]byte(newData), &ng); err != nil {
|
|
|
|
t.Fatalf("failed to unmarshal: %s", err)
|
|
|
|
}
|
|
|
|
if g.Checksum == ng.Checksum {
|
|
|
|
t.Fatalf("expected to get different checksums")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t.Run("Ok", func(t *testing.T) {
|
|
|
|
f(t, `
|
2020-09-11 21:14:30 +02:00
|
|
|
name: TestGroup
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertAlwaysFiring
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
- record: handler:requests:rate5m
|
|
|
|
expr: sum(rate(prometheus_http_requests_total[5m])) by (handler)
|
2020-11-29 08:48:42 +01:00
|
|
|
`, `
|
2020-09-11 21:14:30 +02:00
|
|
|
name: TestGroup
|
|
|
|
rules:
|
|
|
|
- record: handler:requests:rate5m
|
|
|
|
expr: sum(rate(prometheus_http_requests_total[5m])) by (handler)
|
|
|
|
- alert: ExampleAlertAlwaysFiring
|
|
|
|
expr: sum by(job) (up == 1)
|
2020-11-29 08:48:42 +01:00
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2021-09-23 16:55:59 +02:00
|
|
|
t.Run("`for` change", func(t *testing.T) {
|
2020-11-29 08:48:42 +01:00
|
|
|
f(t, `
|
|
|
|
name: TestGroup
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertWithFor
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
for: 5m
|
|
|
|
`, `
|
|
|
|
name: TestGroup
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertWithFor
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
`)
|
|
|
|
})
|
2021-09-23 16:55:59 +02:00
|
|
|
t.Run("`interval` change", func(t *testing.T) {
|
|
|
|
f(t, `
|
|
|
|
name: TestGroup
|
|
|
|
interval: 2s
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertWithFor
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
`, `
|
|
|
|
name: TestGroup
|
|
|
|
interval: 4s
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertWithFor
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
t.Run("`concurrency` change", func(t *testing.T) {
|
|
|
|
f(t, `
|
|
|
|
name: TestGroup
|
|
|
|
concurrency: 2
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertWithFor
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
`, `
|
|
|
|
name: TestGroup
|
|
|
|
concurrency: 16
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertWithFor
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
`)
|
|
|
|
})
|
2020-09-11 21:14:30 +02:00
|
|
|
}
|