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/uuid v1.6.0 // @grafana/backend-platform
github.com/google/wire v0.5.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/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/cuetsy v0.1.11 // @grafana/grafana-as-code
github.com/grafana/grafana-aws-sdk v0.23.1 // @grafana/aws-datasources 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 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/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 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/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-20240222104113-abfafef9a7d2 h1:fmUMdtP7ditGgJFdXCwVxDrKnondHNNe0TkhN5YaIAI=
github.com/grafana/alerting v0.0.0-20240213130827-92f64f0f2a12/go.mod h1:brTFeACal/cSZAR8XO/4LPKs7rzNfS86okl6QjSP1eY= 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 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ= 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 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs=
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= 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.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 h1:ohgMktFAasLTzAhKhcIzk81O60E29Za6ly02GhEqGIU=
github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs= 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= 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 return c.Receivers
} }
func (c *GettableApiAlertingConfig) GetTimeIntervals() []config.TimeInterval { return c.TimeIntervals }
func (c *GettableApiAlertingConfig) GetMuteTimeIntervals() []config.MuteTimeInterval { func (c *GettableApiAlertingConfig) GetMuteTimeIntervals() []config.MuteTimeInterval {
return c.MuteTimeIntervals return c.MuteTimeIntervals
} }
@ -803,6 +805,7 @@ type Config struct {
Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"` Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
Route *Route `yaml:"route,omitempty" json:"route,omitempty"` Route *Route `yaml:"route,omitempty" json:"route,omitempty"`
InhibitRules []config.InhibitRule `yaml:"inhibit_rules,omitempty" json:"inhibit_rules,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"` MuteTimeIntervals []config.MuteTimeInterval `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty"`
Templates []string `yaml:"templates" json:"templates"` Templates []string `yaml:"templates" json:"templates"`
} }
@ -936,6 +939,15 @@ func (c *Config) UnmarshalJSON(b []byte) error {
} }
tiNames := make(map[string]struct{}) 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 { for _, mt := range c.MuteTimeIntervals {
if mt.Name == "" { if mt.Name == "" {
return fmt.Errorf("missing name in mute time interval") return fmt.Errorf("missing name in mute time interval")
@ -976,6 +988,8 @@ func (c *PostableApiAlertingConfig) GetReceivers() []*PostableApiReceiver {
return c.Receivers return c.Receivers
} }
func (c *PostableApiAlertingConfig) GetTimeIntervals() []config.TimeInterval { return c.TimeIntervals }
func (c *PostableApiAlertingConfig) GetMuteTimeIntervals() []config.MuteTimeInterval { func (c *PostableApiAlertingConfig) GetMuteTimeIntervals() []config.MuteTimeInterval {
return c.MuteTimeIntervals return c.MuteTimeIntervals
} }

View File

@ -485,7 +485,50 @@ func Test_ConfigUnmashaling(t *testing.T) {
err error 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"), err: errors.New("missing name in mute time interval"),
input: ` 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"), err: errors.New("mute time interval \"test1\" is not unique"),
input: ` 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", desc: "mute time intervals on root route should error",
err: errors.New("root route must not have any mute time intervals"), 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 return a.alertmanagerConfig.InhibitRules
} }
func (a AlertingConfiguration) TimeIntervals() []alertingNotify.TimeInterval {
return a.alertmanagerConfig.TimeIntervals
}
func (a AlertingConfiguration) MuteTimeIntervals() []alertingNotify.MuteTimeInterval { func (a AlertingConfiguration) MuteTimeIntervals() []alertingNotify.MuteTimeInterval {
return a.alertmanagerConfig.MuteTimeIntervals return a.alertmanagerConfig.MuteTimeIntervals
} }

View File

@ -21,12 +21,13 @@ type NotificationSettingsValidator interface {
// staticValidator is a NotificationSettingsValidator that uses static pre-fetched values for available receivers and mute timings. // staticValidator is a NotificationSettingsValidator that uses static pre-fetched values for available receivers and mute timings.
type staticValidator struct { type staticValidator struct {
availableReceivers map[string]struct{} availableReceivers map[string]struct{}
availableMuteTimings map[string]struct{} availableTimeIntervals map[string]struct{}
} }
// apiAlertingConfig contains the methods required to validate NotificationSettings and create autogen routes. // apiAlertingConfig contains the methods required to validate NotificationSettings and create autogen routes.
type apiAlertingConfig[R receiver] interface { type apiAlertingConfig[R receiver] interface {
GetReceivers() []R GetReceivers() []R
GetTimeIntervals() []config.TimeInterval
GetMuteTimeIntervals() []config.MuteTimeInterval GetMuteTimeIntervals() []config.MuteTimeInterval
GetRoute() *definitions.Route GetRoute() *definitions.Route
} }
@ -42,14 +43,17 @@ func NewNotificationSettingsValidator[R receiver](am apiAlertingConfig[R]) Notif
availableReceivers[receiver.GetName()] = struct{}{} 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() { for _, interval := range am.GetMuteTimeIntervals() {
availableMuteTimings[interval.Name] = struct{}{} availableTimeIntervals[interval.Name] = struct{}{}
} }
return staticValidator{ return staticValidator{
availableReceivers: availableReceivers, availableReceivers: availableReceivers,
availableMuteTimings: availableMuteTimings, 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)) errs = append(errs, fmt.Errorf("receiver '%s' does not exist", settings.Receiver))
} }
for _, interval := range settings.MuteTimeIntervals { 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)) 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/config"
"github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/alertmanager/timeinterval"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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 { for _, tc := range cases {