mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Alerting: Use Alertmanager types extracted into grafana/alerting We're in the process of exporting all Alertmanager types into grafana/alerting so that they can be imported in the Mimir Alertmanager, without a neeed to import Grafana directly. This change introduces type aliasing for all Alertmanager types based on their 1:1 copy that now live in grafana/alerting. Signed-off-by: gotjosh <josue.abreu@gmail.com> --------- Signed-off-by: gotjosh <josue.abreu@gmail.com>
482 lines
12 KiB
Go
482 lines
12 KiB
Go
package definitions
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/prometheus/alertmanager/config"
|
|
"github.com/prometheus/alertmanager/timeinterval"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestValidateRoutes(t *testing.T) {
|
|
zero := model.Duration(0)
|
|
|
|
type testCase struct {
|
|
desc string
|
|
route Route
|
|
expMsg string
|
|
}
|
|
|
|
t.Run("valid route", func(t *testing.T) {
|
|
cases := []testCase{
|
|
{
|
|
desc: "empty",
|
|
route: Route{},
|
|
},
|
|
{
|
|
desc: "simple",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
},
|
|
},
|
|
{
|
|
desc: "nested",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
Routes: []*Route{
|
|
{
|
|
Receiver: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
err := c.route.ValidateChild()
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("invalid route", func(t *testing.T) {
|
|
cases := []testCase{
|
|
{
|
|
desc: "zero group interval",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
GroupInterval: &zero,
|
|
},
|
|
expMsg: "group_interval cannot be zero",
|
|
},
|
|
{
|
|
desc: "zero repeat interval",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
RepeatInterval: &zero,
|
|
},
|
|
expMsg: "repeat_interval cannot be zero",
|
|
},
|
|
{
|
|
desc: "duplicated label",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{
|
|
"abc",
|
|
"abc",
|
|
},
|
|
},
|
|
expMsg: "duplicated label",
|
|
},
|
|
{
|
|
desc: "wildcard and non-wildcard label simultaneously",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{
|
|
"...",
|
|
"abc",
|
|
},
|
|
},
|
|
expMsg: "cannot have wildcard",
|
|
},
|
|
{
|
|
desc: "valid with nested invalid",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
Routes: []*Route{
|
|
{
|
|
GroupByStr: []string{
|
|
"abc",
|
|
"abc",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expMsg: "duplicated label",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
err := c.route.ValidateChild()
|
|
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), c.expMsg)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("route validator normalizes group_by", func(t *testing.T) {
|
|
t.Run("when grouping normally", func(t *testing.T) {
|
|
route := Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"abc", "def"},
|
|
}
|
|
|
|
_ = route.ValidateChild()
|
|
|
|
require.False(t, route.GroupByAll)
|
|
require.Equal(t, []model.LabelName{"abc", "def"}, route.GroupBy)
|
|
})
|
|
|
|
t.Run("when grouping by wildcard, nil", func(t *testing.T) {
|
|
route := Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
}
|
|
|
|
_ = route.ValidateChild()
|
|
|
|
require.True(t, route.GroupByAll)
|
|
require.Nil(t, route.GroupBy)
|
|
})
|
|
|
|
t.Run("idempotently", func(t *testing.T) {
|
|
route := Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"abc", "def"},
|
|
}
|
|
|
|
err := route.ValidateChild()
|
|
require.NoError(t, err)
|
|
err = route.ValidateChild()
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, route.GroupByAll)
|
|
require.Equal(t, []model.LabelName{"abc", "def"}, route.GroupBy)
|
|
})
|
|
})
|
|
|
|
t.Run("valid root route", func(t *testing.T) {
|
|
cases := []testCase{
|
|
{
|
|
desc: "simple",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
err := c.route.Validate()
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("invalid root route", func(t *testing.T) {
|
|
cases := []testCase{
|
|
{
|
|
desc: "no receiver",
|
|
route: Route{
|
|
GroupByStr: []string{"..."},
|
|
},
|
|
expMsg: "must specify a default receiver",
|
|
},
|
|
{
|
|
desc: "exact matchers present",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
Match: map[string]string{
|
|
"abc": "def",
|
|
},
|
|
},
|
|
expMsg: "must not have any matchers",
|
|
},
|
|
{
|
|
desc: "regex matchers present",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
Match: map[string]string{
|
|
"abc": "def",
|
|
},
|
|
},
|
|
expMsg: "must not have any matchers",
|
|
},
|
|
{
|
|
desc: "mute time intervals present",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"..."},
|
|
MuteTimeIntervals: []string{"10"},
|
|
},
|
|
expMsg: "must not have any mute time intervals",
|
|
},
|
|
{
|
|
desc: "validation error that is not specific to root",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
GroupByStr: []string{"abc", "abc"},
|
|
},
|
|
expMsg: "duplicated label",
|
|
},
|
|
{
|
|
desc: "nested validation error that is not specific to root",
|
|
route: Route{
|
|
Receiver: "foo",
|
|
Routes: []*Route{
|
|
{
|
|
GroupByStr: []string{"abc", "abc"},
|
|
},
|
|
},
|
|
},
|
|
expMsg: "duplicated label",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
err := c.route.Validate()
|
|
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), c.expMsg)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestValidateMuteTimeInterval(t *testing.T) {
|
|
type testCase struct {
|
|
desc string
|
|
mti MuteTimeInterval
|
|
expMsg string
|
|
}
|
|
|
|
t.Run("valid interval", func(t *testing.T) {
|
|
cases := []testCase{
|
|
{
|
|
desc: "nil intervals",
|
|
mti: MuteTimeInterval{
|
|
MuteTimeInterval: config.MuteTimeInterval{
|
|
Name: "interval",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "empty intervals",
|
|
mti: MuteTimeInterval{
|
|
MuteTimeInterval: config.MuteTimeInterval{
|
|
Name: "interval",
|
|
TimeIntervals: []timeinterval.TimeInterval{},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "blank interval",
|
|
mti: MuteTimeInterval{
|
|
MuteTimeInterval: config.MuteTimeInterval{
|
|
Name: "interval",
|
|
TimeIntervals: []timeinterval.TimeInterval{
|
|
{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "simple",
|
|
mti: MuteTimeInterval{
|
|
MuteTimeInterval: config.MuteTimeInterval{
|
|
Name: "interval",
|
|
TimeIntervals: []timeinterval.TimeInterval{
|
|
{
|
|
Weekdays: []timeinterval.WeekdayRange{
|
|
{
|
|
InclusiveRange: timeinterval.InclusiveRange{
|
|
Begin: 1,
|
|
End: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
err := c.mti.Validate()
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("invalid interval", func(t *testing.T) {
|
|
cases := []testCase{
|
|
{
|
|
desc: "empty",
|
|
mti: MuteTimeInterval{},
|
|
expMsg: "missing name",
|
|
},
|
|
{
|
|
desc: "empty",
|
|
mti: MuteTimeInterval{
|
|
MuteTimeInterval: config.MuteTimeInterval{
|
|
Name: "interval",
|
|
TimeIntervals: []timeinterval.TimeInterval{
|
|
{
|
|
Weekdays: []timeinterval.WeekdayRange{
|
|
{
|
|
InclusiveRange: timeinterval.InclusiveRange{
|
|
Begin: -1,
|
|
End: 7,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expMsg: "unable to convert -1 into weekday",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
err := c.mti.Validate()
|
|
|
|
require.ErrorContains(t, err, c.expMsg)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestValidateNotificationTemplates(t *testing.T) {
|
|
tc := []struct {
|
|
name string
|
|
template NotificationTemplate
|
|
expContent string
|
|
expError error
|
|
}{
|
|
{
|
|
name: "Same template name as definition",
|
|
template: NotificationTemplate{
|
|
Name: "Same name as definition",
|
|
Template: `{{ define "Same name as definition" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
|
Provenance: "test",
|
|
},
|
|
expContent: `{{ define "Same name as definition" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
|
expError: nil,
|
|
},
|
|
{
|
|
name: "Different template name than definition",
|
|
template: NotificationTemplate{
|
|
Name: "Different name than definition",
|
|
Template: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
|
Provenance: "test",
|
|
},
|
|
expContent: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
|
expError: nil,
|
|
},
|
|
{
|
|
name: "Fix template - missing both {{ define }} and {{ end }}",
|
|
template: NotificationTemplate{
|
|
Name: "Alert Instance Template",
|
|
Template: `Firing: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}`,
|
|
Provenance: "test",
|
|
},
|
|
expContent: "{{ define \"Alert Instance Template\" }}\n Firing: {{ .Labels.alertname }}\\nSilence: {{ .SilenceURL }}\n{{ end }}",
|
|
expError: nil,
|
|
},
|
|
{
|
|
name: "Multiple definitions",
|
|
template: NotificationTemplate{
|
|
Name: "Alert Instance Template",
|
|
Template: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}{{ define "Alert Instance Template2" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
|
Provenance: "test",
|
|
},
|
|
expContent: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}{{ define "Alert Instance Template2" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
|
expError: nil,
|
|
},
|
|
{
|
|
name: "Malformed template - missing {{ define }}",
|
|
template: NotificationTemplate{
|
|
Name: "Alert Instance Template",
|
|
Template: `\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
|
Provenance: "test",
|
|
},
|
|
expError: errors.New("invalid template: template: Alert Instance Template:3: unexpected {{end}}"),
|
|
},
|
|
{
|
|
name: "Malformed template - missing {{ end }}",
|
|
template: NotificationTemplate{
|
|
Name: "Alert Instance Template",
|
|
Template: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n`,
|
|
Provenance: "test",
|
|
},
|
|
expError: errors.New("invalid template: template: Alert Instance Template:1: unexpected EOF"),
|
|
},
|
|
{
|
|
name: "Malformed template - multiple definitions duplicate name",
|
|
template: NotificationTemplate{
|
|
Name: "Alert Instance Template",
|
|
Template: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}\n{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
|
Provenance: "test",
|
|
},
|
|
expError: errors.New("invalid template: template: Different name than definition:1: template: multiple definition of template \"Alert Instance Template\""),
|
|
},
|
|
{
|
|
// This is fine as long as the template name is different from the definition, it just ignores the extra text.
|
|
name: "Extra text outside definition block - different template name and definition",
|
|
template: NotificationTemplate{
|
|
Name: "Different name than definition",
|
|
Template: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
|
|
Provenance: "test",
|
|
},
|
|
expContent: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
|
|
expError: nil,
|
|
},
|
|
{
|
|
// This is NOT fine as the template name is the same as the definition.
|
|
// GO template parser will treat it as if it's wrapped in {{ define "Alert Instance Template" }}, thus creating a duplicate definition.
|
|
name: "Extra text outside definition block - same template name and definition",
|
|
template: NotificationTemplate{
|
|
Name: "Alert Instance Template",
|
|
Template: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
|
|
Provenance: "test",
|
|
},
|
|
expError: errors.New("invalid template: template: Alert Instance Template:1: template: multiple definition of template \"Alert Instance Template\""),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tc {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.template.Validate()
|
|
if tt.expError == nil {
|
|
require.NoError(t, err)
|
|
} else {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.Equal(t, tt.expContent, tt.template.Template)
|
|
})
|
|
}
|
|
}
|