Alerting: Basic support for time_intervals (#83216)

This commit adds basic support for time_intervals, as mute_time_intervals
is deprecated in Alertmanager and scheduled to be removed before 1.0.
It does not add support for time_intervals in API or file provisioning,
nor does it support exporting time intervals. This will be added in
later commits to keep the changes as simple as possible.
This commit is contained in:
George Robinson 2024-02-22 15:58:56 +00:00 committed by GitHub
parent 13cb09b178
commit 1ed1242358
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 257 additions and 14 deletions

2
go.mod
View File

@ -59,7 +59,7 @@ require (
github.com/google/uuid v1.6.0 // @grafana/backend-platform
github.com/google/wire v0.5.0 // @grafana/backend-platform
github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20240213130827-92f64f0f2a12 // @grafana/alerting-squad-backend
github.com/grafana/alerting v0.0.0-20240222104113-abfafef9a7d2 // @grafana/alerting-squad-backend
github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
github.com/grafana/grafana-aws-sdk v0.23.1 // @grafana/aws-datasources
github.com/grafana/grafana-azure-sdk-go v1.12.0 // @grafana/partner-datasources

6
go.sum
View File

@ -2505,8 +2505,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
github.com/grafana/alerting v0.0.0-20240213130827-92f64f0f2a12 h1:QepaY7wUP3U1hFiU1Lnv+tymnovzK21KQ/evMDpYsEw=
github.com/grafana/alerting v0.0.0-20240213130827-92f64f0f2a12/go.mod h1:brTFeACal/cSZAR8XO/4LPKs7rzNfS86okl6QjSP1eY=
github.com/grafana/alerting v0.0.0-20240222104113-abfafef9a7d2 h1:fmUMdtP7ditGgJFdXCwVxDrKnondHNNe0TkhN5YaIAI=
github.com/grafana/alerting v0.0.0-20240222104113-abfafef9a7d2/go.mod h1:brTFeACal/cSZAR8XO/4LPKs7rzNfS86okl6QjSP1eY=
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=
@ -2530,8 +2530,6 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs=
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ=
github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
github.com/grafana/grafana-plugin-sdk-go v0.211.0 h1:hYtieOoYvsv/BcFbtspml4OzfuYrv1d14nESdf13qxQ=
github.com/grafana/grafana-plugin-sdk-go v0.211.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs=
github.com/grafana/grafana-plugin-sdk-go v0.212.0 h1:ohgMktFAasLTzAhKhcIzk81O60E29Za6ly02GhEqGIU=
github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs=
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482 h1:1YNoeIhii4UIIQpCPU+EXidnqf449d0C3ZntAEt4KSo=

View File

@ -739,6 +739,8 @@ func (c *GettableApiAlertingConfig) GetReceivers() []*GettableApiReceiver {
return c.Receivers
}
func (c *GettableApiAlertingConfig) GetTimeIntervals() []config.TimeInterval { return c.TimeIntervals }
func (c *GettableApiAlertingConfig) GetMuteTimeIntervals() []config.MuteTimeInterval {
return c.MuteTimeIntervals
}
@ -803,6 +805,7 @@ type Config struct {
Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
Route *Route `yaml:"route,omitempty" json:"route,omitempty"`
InhibitRules []config.InhibitRule `yaml:"inhibit_rules,omitempty" json:"inhibit_rules,omitempty"`
TimeIntervals []config.TimeInterval `yaml:"time_intervals,omitempty" json:"time_intervals,omitempty"`
MuteTimeIntervals []config.MuteTimeInterval `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty"`
Templates []string `yaml:"templates" json:"templates"`
}
@ -936,6 +939,15 @@ func (c *Config) UnmarshalJSON(b []byte) error {
}
tiNames := make(map[string]struct{})
for _, ti := range c.TimeIntervals {
if ti.Name == "" {
return fmt.Errorf("missing name in time interval")
}
if _, ok := tiNames[ti.Name]; ok {
return fmt.Errorf("time interval %q is not unique", ti.Name)
}
tiNames[ti.Name] = struct{}{}
}
for _, mt := range c.MuteTimeIntervals {
if mt.Name == "" {
return fmt.Errorf("missing name in mute time interval")
@ -976,6 +988,8 @@ func (c *PostableApiAlertingConfig) GetReceivers() []*PostableApiReceiver {
return c.Receivers
}
func (c *PostableApiAlertingConfig) GetTimeIntervals() []config.TimeInterval { return c.TimeIntervals }
func (c *PostableApiAlertingConfig) GetMuteTimeIntervals() []config.MuteTimeInterval {
return c.MuteTimeIntervals
}

View File

@ -485,7 +485,50 @@ func Test_ConfigUnmashaling(t *testing.T) {
err error
}{
{
desc: "empty mute time name should error",
desc: "missing time interval name should error",
err: errors.New("missing name in time interval"),
input: `
{
"route": {
"receiver": "grafana-default-email"
},
"time_intervals": [
{
"name": "",
"time_intervals": [
{
"times": [
{
"start_time": "00:00",
"end_time": "12:00"
}
]
}
]
}
],
"templates": null,
"receivers": [
{
"name": "grafana-default-email",
"grafana_managed_receiver_configs": [
{
"uid": "uxwfZvtnz",
"name": "email receiver",
"type": "email",
"disableResolveMessage": false,
"settings": {
"addresses": "<example@email.com>"
},
"secureFields": {}
}
]
}
]
}
`,
}, {
desc: "missing mute time interval name should error",
err: errors.New("missing name in mute time interval"),
input: `
{
@ -529,7 +572,64 @@ func Test_ConfigUnmashaling(t *testing.T) {
`,
},
{
desc: "not unique mute time names should error",
desc: "duplicate time interval names should error",
err: errors.New("time interval \"test1\" is not unique"),
input: `
{
"route": {
"receiver": "grafana-default-email"
},
"time_intervals": [
{
"name": "test1",
"time_intervals": [
{
"times": [
{
"start_time": "00:00",
"end_time": "12:00"
}
]
}
]
},
{
"name": "test1",
"time_intervals": [
{
"times": [
{
"start_time": "00:00",
"end_time": "12:00"
}
]
}
]
}
],
"templates": null,
"receivers": [
{
"name": "grafana-default-email",
"grafana_managed_receiver_configs": [
{
"uid": "uxwfZvtnz",
"name": "email receiver",
"type": "email",
"disableResolveMessage": false,
"settings": {
"addresses": "<example@email.com>"
},
"secureFields": {}
}
]
}
]
}
`,
},
{
desc: "duplicate mute time interval names should error",
err: errors.New("mute time interval \"test1\" is not unique"),
input: `
{
@ -585,6 +685,65 @@ func Test_ConfigUnmashaling(t *testing.T) {
}
`,
},
{
desc: "duplicate time and mute time interval names should error",
err: errors.New("mute time interval \"test1\" is not unique"),
input: `
{
"route": {
"receiver": "grafana-default-email"
},
"mute_time_intervals": [
{
"name": "test1",
"time_intervals": [
{
"times": [
{
"start_time": "00:00",
"end_time": "12:00"
}
]
}
]
}
],
"time_intervals": [
{
"name": "test1",
"time_intervals": [
{
"times": [
{
"start_time": "00:00",
"end_time": "12:00"
}
]
}
]
}
],
"templates": null,
"receivers": [
{
"name": "grafana-default-email",
"grafana_managed_receiver_configs": [
{
"uid": "uxwfZvtnz",
"name": "email receiver",
"type": "email",
"disableResolveMessage": false,
"settings": {
"addresses": "<example@email.com>"
},
"secureFields": {}
}
]
}
]
}
`,
},
{
desc: "mute time intervals on root route should error",
err: errors.New("root route must not have any mute time intervals"),

View File

@ -111,6 +111,10 @@ func (a AlertingConfiguration) InhibitRules() []alertingNotify.InhibitRule {
return a.alertmanagerConfig.InhibitRules
}
func (a AlertingConfiguration) TimeIntervals() []alertingNotify.TimeInterval {
return a.alertmanagerConfig.TimeIntervals
}
func (a AlertingConfiguration) MuteTimeIntervals() []alertingNotify.MuteTimeInterval {
return a.alertmanagerConfig.MuteTimeIntervals
}

View File

@ -20,13 +20,14 @@ type NotificationSettingsValidator interface {
// staticValidator is a NotificationSettingsValidator that uses static pre-fetched values for available receivers and mute timings.
type staticValidator struct {
availableReceivers map[string]struct{}
availableMuteTimings map[string]struct{}
availableReceivers map[string]struct{}
availableTimeIntervals map[string]struct{}
}
// apiAlertingConfig contains the methods required to validate NotificationSettings and create autogen routes.
type apiAlertingConfig[R receiver] interface {
GetReceivers() []R
GetTimeIntervals() []config.TimeInterval
GetMuteTimeIntervals() []config.MuteTimeInterval
GetRoute() *definitions.Route
}
@ -42,14 +43,17 @@ func NewNotificationSettingsValidator[R receiver](am apiAlertingConfig[R]) Notif
availableReceivers[receiver.GetName()] = struct{}{}
}
availableMuteTimings := make(map[string]struct{})
availableTimeIntervals := make(map[string]struct{})
for _, interval := range am.GetTimeIntervals() {
availableTimeIntervals[interval.Name] = struct{}{}
}
for _, interval := range am.GetMuteTimeIntervals() {
availableMuteTimings[interval.Name] = struct{}{}
availableTimeIntervals[interval.Name] = struct{}{}
}
return staticValidator{
availableReceivers: availableReceivers,
availableMuteTimings: availableMuteTimings,
availableReceivers: availableReceivers,
availableTimeIntervals: availableTimeIntervals,
}
}
@ -63,7 +67,7 @@ func (n staticValidator) Validate(settings models.NotificationSettings) error {
errs = append(errs, fmt.Errorf("receiver '%s' does not exist", settings.Receiver))
}
for _, interval := range settings.MuteTimeIntervals {
if _, ok := n.availableMuteTimings[interval]; !ok {
if _, ok := n.availableTimeIntervals[interval]; !ok {
errs = append(errs, fmt.Errorf("mute time interval '%s' does not exist", interval))
}
}

View File

@ -12,6 +12,7 @@ import (
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/alertmanager/timeinterval"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -192,6 +193,69 @@ func TestIntegrationAlertmanagerConfiguration(t *testing.T) {
}},
},
},
}, {
name: "configuration with time intervals",
cfg: apimodels.PostableUserConfig{
AlertmanagerConfig: apimodels.PostableApiAlertingConfig{
Config: apimodels.Config{
Route: &apimodels.Route{
Receiver: "test",
Routes: []*apimodels.Route{{
MuteTimeIntervals: []string{"weekends"},
}},
},
TimeIntervals: []config.TimeInterval{{
Name: "weekends",
TimeIntervals: []timeinterval.TimeInterval{{
Weekdays: []timeinterval.WeekdayRange{{
InclusiveRange: timeinterval.InclusiveRange{
Begin: 1,
End: 5,
},
}},
}},
}},
},
Receivers: []*apimodels.PostableApiReceiver{{
Receiver: config.Receiver{
Name: "test",
},
}},
},
},
}, {
// TODO: Mute time intervals is deprecated in Alertmanager and scheduled to be
// removed before version 1.0. Remove this test when support for mute time
// intervals is removed.
name: "configuration with mute time intervals",
cfg: apimodels.PostableUserConfig{
AlertmanagerConfig: apimodels.PostableApiAlertingConfig{
Config: apimodels.Config{
Route: &apimodels.Route{
Receiver: "test",
Routes: []*apimodels.Route{{
MuteTimeIntervals: []string{"weekends"},
}},
},
MuteTimeIntervals: []config.MuteTimeInterval{{
Name: "weekends",
TimeIntervals: []timeinterval.TimeInterval{{
Weekdays: []timeinterval.WeekdayRange{{
InclusiveRange: timeinterval.InclusiveRange{
Begin: 1,
End: 5,
},
}},
}},
}},
},
Receivers: []*apimodels.PostableApiReceiver{{
Receiver: config.Receiver{
Name: "test",
},
}},
},
},
}}
for _, tc := range cases {