alertmanager/dispatch/route_test.go

923 lines
19 KiB
Go

// Copyright 2015 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dispatch
import (
"reflect"
"testing"
"time"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
"github.com/prometheus/alertmanager/config"
)
func TestRouteMatch(t *testing.T) {
in := `
receiver: 'notify-def'
routes:
- match:
owner: 'team-A'
receiver: 'notify-A'
routes:
- match:
env: 'testing'
receiver: 'notify-testing'
group_by: [...]
- match:
env: "production"
receiver: 'notify-productionA'
group_wait: 1m
continue: true
- match_re:
env: "produ.*"
job: ".*"
receiver: 'notify-productionB'
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
group_by: ['job']
- match_re:
owner: 'team-(B|C)'
group_by: ['foo', 'bar']
group_wait: 2m
receiver: 'notify-BC'
- match:
group_by: 'role'
group_by: ['role']
routes:
- match:
env: 'testing'
receiver: 'notify-testing'
routes:
- match:
wait: 'long'
group_wait: 2m
`
var ctree config.Route
if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
t.Fatal(err)
}
var (
def = DefaultRouteOpts
tree = NewRoute(&ctree, nil)
)
lset := func(labels ...string) map[model.LabelName]struct{} {
s := map[model.LabelName]struct{}{}
for _, ls := range labels {
s[model.LabelName(ls)] = struct{}{}
}
return s
}
tests := []struct {
input model.LabelSet
result []*RouteOpts
keys []string
}{
{
input: model.LabelSet{
"owner": "team-A",
},
result: []*RouteOpts{
{
Receiver: "notify-A",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{owner=\"team-A\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "unset",
},
result: []*RouteOpts{
{
Receiver: "notify-A",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{owner=\"team-A\"}"},
},
{
input: model.LabelSet{
"owner": "team-C",
},
result: []*RouteOpts{
{
Receiver: "notify-BC",
GroupBy: lset("foo", "bar"),
GroupByAll: false,
GroupWait: 2 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{owner=~\"^(?:team-(B|C))$\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "testing",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset(),
GroupByAll: true,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{owner=\"team-A\"}/{env=\"testing\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "production",
},
result: []*RouteOpts{
{
Receiver: "notify-productionA",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: 1 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
{
Receiver: "notify-productionB",
GroupBy: lset("job"),
GroupByAll: false,
GroupWait: 30 * time.Second,
GroupInterval: 5 * time.Minute,
RepeatInterval: 1 * time.Hour,
},
},
keys: []string{
"{}/{owner=\"team-A\"}/{env=\"production\"}",
"{}/{owner=\"team-A\"}/{env=~\"^(?:produ.*)$\",job=~\"^(?:.*)$\"}",
},
},
{
input: model.LabelSet{
"group_by": "role",
},
result: []*RouteOpts{
{
Receiver: "notify-def",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}"},
},
{
input: model.LabelSet{
"env": "testing",
"group_by": "role",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}"},
},
{
input: model.LabelSet{
"env": "testing",
"group_by": "role",
"wait": "long",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: 2 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}/{wait=\"long\"}"},
},
}
for _, test := range tests {
var matches []*RouteOpts
var keys []string
for _, r := range tree.Match(test.input) {
matches = append(matches, &r.RouteOpts)
keys = append(keys, r.Key())
}
if !reflect.DeepEqual(matches, test.result) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", test.result, matches)
}
if !reflect.DeepEqual(keys, test.keys) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", test.keys, keys)
}
}
}
func TestRouteWalk(t *testing.T) {
in := `
receiver: 'notify-def'
routes:
- match:
owner: 'team-A'
receiver: 'notify-A'
routes:
- match:
env: 'testing'
receiver: 'notify-testing'
group_by: [...]
- match:
env: "production"
receiver: 'notify-productionA'
group_wait: 1m
continue: true
- match_re:
env: "produ.*"
job: ".*"
receiver: 'notify-productionB'
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
group_by: ['job']
- match_re:
owner: 'team-(B|C)'
group_by: ['foo', 'bar']
group_wait: 2m
receiver: 'notify-BC'
- match:
group_by: 'role'
group_by: ['role']
routes:
- match:
env: 'testing'
receiver: 'notify-testing'
routes:
- match:
wait: 'long'
group_wait: 2m
`
var ctree config.Route
if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
t.Fatal(err)
}
tree := NewRoute(&ctree, nil)
expected := []string{
"notify-def",
"notify-A",
"notify-testing",
"notify-productionA",
"notify-productionB",
"notify-BC",
"notify-def",
"notify-testing",
"notify-testing",
}
var got []string
tree.Walk(func(r *Route) {
got = append(got, r.RouteOpts.Receiver)
})
if !reflect.DeepEqual(got, expected) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, got)
}
}
func TestInheritParentGroupByAll(t *testing.T) {
in := `
routes:
- match:
env: 'parent'
group_by: ['...']
routes:
- match:
env: 'child1'
- match:
env: 'child2'
group_by: ['foo']
`
var ctree config.Route
if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
t.Fatal(err)
}
tree := NewRoute(&ctree, nil)
parent := tree.Routes[0]
child1 := parent.Routes[0]
child2 := parent.Routes[1]
require.True(t, parent.RouteOpts.GroupByAll)
require.True(t, child1.RouteOpts.GroupByAll)
require.False(t, child2.RouteOpts.GroupByAll)
}
func TestRouteMatchers(t *testing.T) {
in := `
receiver: 'notify-def'
routes:
- matchers: ['{owner="team-A"}', '{level!="critical"}']
receiver: 'notify-A'
routes:
- matchers: ['{env="testing"}', '{baz!~".*quux"}']
receiver: 'notify-testing'
group_by: [...]
- matchers: ['{env="production"}']
receiver: 'notify-productionA'
group_wait: 1m
continue: true
- matchers: [ env=~"produ.*", job=~".*"]
receiver: 'notify-productionB'
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
group_by: ['job']
- matchers: [owner=~"team-(B|C)"]
group_by: ['foo', 'bar']
group_wait: 2m
receiver: 'notify-BC'
- matchers: [group_by="role"]
group_by: ['role']
routes:
- matchers: ['{env="testing"}']
receiver: 'notify-testing'
routes:
- matchers: [wait="long"]
group_wait: 2m
`
var ctree config.Route
if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
t.Fatal(err)
}
var (
def = DefaultRouteOpts
tree = NewRoute(&ctree, nil)
)
lset := func(labels ...string) map[model.LabelName]struct{} {
s := map[model.LabelName]struct{}{}
for _, ls := range labels {
s[model.LabelName(ls)] = struct{}{}
}
return s
}
tests := []struct {
input model.LabelSet
result []*RouteOpts
keys []string
}{
{
input: model.LabelSet{
"owner": "team-A",
},
result: []*RouteOpts{
{
Receiver: "notify-A",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "unset",
},
result: []*RouteOpts{
{
Receiver: "notify-A",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}"},
},
{
input: model.LabelSet{
"owner": "team-C",
},
result: []*RouteOpts{
{
Receiver: "notify-BC",
GroupBy: lset("foo", "bar"),
GroupByAll: false,
GroupWait: 2 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{owner=~\"team-(B|C)\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "testing",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset(),
GroupByAll: true,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}/{baz!~\".*quux\",env=\"testing\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "production",
},
result: []*RouteOpts{
{
Receiver: "notify-productionA",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: 1 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
{
Receiver: "notify-productionB",
GroupBy: lset("job"),
GroupByAll: false,
GroupWait: 30 * time.Second,
GroupInterval: 5 * time.Minute,
RepeatInterval: 1 * time.Hour,
},
},
keys: []string{
"{}/{level!=\"critical\",owner=\"team-A\"}/{env=\"production\"}",
"{}/{level!=\"critical\",owner=\"team-A\"}/{env=~\"produ.*\",job=~\".*\"}",
},
},
{
input: model.LabelSet{
"group_by": "role",
},
result: []*RouteOpts{
{
Receiver: "notify-def",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}"},
},
{
input: model.LabelSet{
"env": "testing",
"group_by": "role",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}"},
},
{
input: model.LabelSet{
"env": "testing",
"group_by": "role",
"wait": "long",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: 2 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}/{wait=\"long\"}"},
},
}
for _, test := range tests {
var matches []*RouteOpts
var keys []string
for _, r := range tree.Match(test.input) {
matches = append(matches, &r.RouteOpts)
keys = append(keys, r.Key())
}
if !reflect.DeepEqual(matches, test.result) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", test.result, matches)
}
if !reflect.DeepEqual(keys, test.keys) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", test.keys, keys)
}
}
}
func TestRouteMatchersAndMatch(t *testing.T) {
in := `
receiver: 'notify-def'
routes:
- matchers: ['{owner="team-A"}', '{level!="critical"}']
receiver: 'notify-A'
routes:
- matchers: ['{env="testing"}', '{baz!~".*quux"}']
receiver: 'notify-testing'
group_by: [...]
- match:
env: "production"
receiver: 'notify-productionA'
group_wait: 1m
continue: true
- matchers: [ env=~"produ.*", job=~".*"]
receiver: 'notify-productionB'
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
group_by: ['job']
- match_re:
owner: 'team-(B|C)'
group_by: ['foo', 'bar']
group_wait: 2m
receiver: 'notify-BC'
- matchers: [group_by="role"]
group_by: ['role']
routes:
- matchers: ['{env="testing"}']
receiver: 'notify-testing'
routes:
- matchers: [wait="long"]
group_wait: 2m
`
var ctree config.Route
if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
t.Fatal(err)
}
var (
def = DefaultRouteOpts
tree = NewRoute(&ctree, nil)
)
lset := func(labels ...string) map[model.LabelName]struct{} {
s := map[model.LabelName]struct{}{}
for _, ls := range labels {
s[model.LabelName(ls)] = struct{}{}
}
return s
}
tests := []struct {
input model.LabelSet
result []*RouteOpts
keys []string
}{
{
input: model.LabelSet{
"owner": "team-A",
},
result: []*RouteOpts{
{
Receiver: "notify-A",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "unset",
},
result: []*RouteOpts{
{
Receiver: "notify-A",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}"},
},
{
input: model.LabelSet{
"owner": "team-C",
},
result: []*RouteOpts{
{
Receiver: "notify-BC",
GroupBy: lset("foo", "bar"),
GroupByAll: false,
GroupWait: 2 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{owner=~\"^(?:team-(B|C))$\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "testing",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset(),
GroupByAll: true,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}/{baz!~\".*quux\",env=\"testing\"}"},
},
{
input: model.LabelSet{
"owner": "team-A",
"env": "production",
},
result: []*RouteOpts{
{
Receiver: "notify-productionA",
GroupBy: def.GroupBy,
GroupByAll: false,
GroupWait: 1 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
{
Receiver: "notify-productionB",
GroupBy: lset("job"),
GroupByAll: false,
GroupWait: 30 * time.Second,
GroupInterval: 5 * time.Minute,
RepeatInterval: 1 * time.Hour,
},
},
keys: []string{
"{}/{level!=\"critical\",owner=\"team-A\"}/{env=\"production\"}",
"{}/{level!=\"critical\",owner=\"team-A\"}/{env=~\"produ.*\",job=~\".*\"}",
},
},
{
input: model.LabelSet{
"group_by": "role",
},
result: []*RouteOpts{
{
Receiver: "notify-def",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}"},
},
{
input: model.LabelSet{
"env": "testing",
"group_by": "role",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: def.GroupWait,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}"},
},
{
input: model.LabelSet{
"env": "testing",
"group_by": "role",
"wait": "long",
},
result: []*RouteOpts{
{
Receiver: "notify-testing",
GroupBy: lset("role"),
GroupByAll: false,
GroupWait: 2 * time.Minute,
GroupInterval: def.GroupInterval,
RepeatInterval: def.RepeatInterval,
},
},
keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}/{wait=\"long\"}"},
},
}
for _, test := range tests {
var matches []*RouteOpts
var keys []string
for _, r := range tree.Match(test.input) {
matches = append(matches, &r.RouteOpts)
keys = append(keys, r.Key())
}
if !reflect.DeepEqual(matches, test.result) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", test.result, matches)
}
if !reflect.DeepEqual(keys, test.keys) {
t.Errorf("\nexpected:\n%v\ngot:\n%v", test.keys, keys)
}
}
}
func TestRouteID(t *testing.T) {
in := `
receiver: default
routes:
- continue: true
matchers:
- foo=bar
receiver: test1
routes:
- matchers:
- bar=baz
- continue: true
matchers:
- foo=bar
receiver: test1
routes:
- matchers:
- bar=baz
- continue: true
matchers:
- foo=bar
receiver: test2
routes:
- matchers:
- bar=baz
- continue: true
matchers:
- bar=baz
receiver: test3
routes:
- matchers:
- baz=qux
- matchers:
- qux=corge
- continue: true
matchers:
- qux=~"[a-zA-Z0-9]+"
- continue: true
matchers:
- corge!~"[0-9]+"
`
cr := config.Route{}
require.NoError(t, yaml.Unmarshal([]byte(in), &cr))
r := NewRoute(&cr, nil)
expected := []string{
"{}",
"{}/{foo=\"bar\"}/0",
"{}/{foo=\"bar\"}/0/{bar=\"baz\"}/0",
"{}/{foo=\"bar\"}/1",
"{}/{foo=\"bar\"}/1/{bar=\"baz\"}/0",
"{}/{foo=\"bar\"}/2",
"{}/{foo=\"bar\"}/2/{bar=\"baz\"}/0",
"{}/{bar=\"baz\"}/3",
"{}/{bar=\"baz\"}/3/{baz=\"qux\"}/0",
"{}/{bar=\"baz\"}/3/{qux=\"corge\"}/1",
"{}/{qux=~\"[a-zA-Z0-9]+\"}/4",
"{}/{corge!~\"[0-9]+\"}/5",
}
var actual []string
r.Walk(func(r *Route) {
actual = append(actual, r.ID())
})
require.ElementsMatch(t, actual, expected)
}