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.
pull/83140/head^2
George Robinson 1 year ago committed by GitHub
parent 13cb09b178
commit 1ed1242358
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      go.mod
  2. 6
      go.sum
  3. 14
      pkg/services/ngalert/api/tooling/definitions/alertmanager.go
  4. 163
      pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go
  5. 4
      pkg/services/ngalert/notifier/config.go
  6. 18
      pkg/services/ngalert/notifier/validation.go
  7. 64
      pkg/tests/api/alerting/api_alertmanager_configuration_test.go

@ -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

@ -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=

@ -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
}

@ -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"),

@ -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
}

@ -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))
}
}

@ -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 {

Loading…
Cancel
Save