From ded0d4a9694a2f01cccdf077c74cc6923fc3cf19 Mon Sep 17 00:00:00 2001 From: Owen Diehl Date: Wed, 24 Feb 2021 11:15:39 -0500 Subject: [PATCH] ability to split routes between AM & Grafana --- pkg/api/alertmanager.go | 12 ++- pkg/validation/alertconfig.go | 38 ++++++++- pkg/validation/alertconfig_test.go | 127 +++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 7 deletions(-) diff --git a/pkg/api/alertmanager.go b/pkg/api/alertmanager.go index 581c59614b7..5177aeea43c 100644 --- a/pkg/api/alertmanager.go +++ b/pkg/api/alertmanager.go @@ -183,8 +183,8 @@ type GrafanaReceiver models.CreateAlertNotificationCommand type ReceiverType int const ( - GrafanaManagedReceiver ReceiverType = iota - AMReceiver + GrafanaReceiverType ReceiverType = iota + AlertmanagerReceiverType ) type ApiReceiver struct { @@ -227,11 +227,17 @@ func (r *ApiReceiver) UnmarshalJSON(b []byte) error { } } - return nil } +func (r *ApiReceiver) Type() ReceiverType { + if len(r.GrafanaReceivers.GrafanaManagedReceivers) > 0 { + return GrafanaReceiverType + } + return AlertmanagerReceiverType +} + type GrafanaReceivers struct { GrafanaManagedReceivers []*GrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"` } diff --git a/pkg/validation/alertconfig.go b/pkg/validation/alertconfig.go index 35f1fd5d3d5..1b73a722f40 100644 --- a/pkg/validation/alertconfig.go +++ b/pkg/validation/alertconfig.go @@ -1,6 +1,8 @@ package validation import ( + "fmt" + "github.com/grafana/alerting-api/pkg/api" "github.com/prometheus/alertmanager/config" ) @@ -20,10 +22,38 @@ type GrafanaAlertingConfig struct { // } // Routing trees must either contain only Grafana Managed receiver types or AlertManager receiver types -// func SplitRoutes(routes []*config.Route, receivers map[string]*api.ApiReceiver) (gRoutes, amRoutes []*config.Route, err error) { -// for _, route := range routes { -// } -// } +func SplitRoutes(routes []*config.Route, receivers map[string]api.ReceiverType) (gRoutes, amRoutes []*config.Route, err error) { + for _, route := range routes { + var grafanaCt, amCt int + for _, receiver := range allReceivers(route) { + t, ok := receivers[receiver] + if !ok { + return nil, nil, fmt.Errorf("unexpected receiver (%s) is undefined", receiver) + } + + if t == api.GrafanaReceiverType { + grafanaCt++ + } else { + amCt++ + } + + } + + // TODO(owen-d): We may want to eventually support this, but short-circuiting for now + // to reduce routing tree complexity. + if grafanaCt > 0 && amCt > 0 { + return nil, nil, fmt.Errorf("cannot mix Grafana Managed receivers with Alertmanager receivers in the same (non-root) routing tree") + } + + if grafanaCt > 0 { + gRoutes = append(gRoutes, route) + } else { + amRoutes = append(amRoutes, route) + } + } + + return gRoutes, amRoutes, nil +} func allReceivers(route *config.Route) (res []string) { res = append(res, route.Receiver) diff --git a/pkg/validation/alertconfig_test.go b/pkg/validation/alertconfig_test.go index 48f4f624314..29f2d099e3c 100644 --- a/pkg/validation/alertconfig_test.go +++ b/pkg/validation/alertconfig_test.go @@ -3,6 +3,7 @@ package validation import ( "testing" + "github.com/grafana/alerting-api/pkg/api" "github.com/prometheus/alertmanager/config" "github.com/stretchr/testify/require" ) @@ -27,3 +28,129 @@ func Test_allReceivers(t *testing.T) { require.Equal(t, []string{"foo", "bar", "bazz", "buzz"}, allReceivers(input)) } + +func Test_Splitroutes(t *testing.T) { + receivers := map[string]api.ReceiverType{ + "am": api.AlertmanagerReceiverType, + "graf": api.GrafanaReceiverType, + } + + for _, tc := range []struct { + desc string + input []*config.Route + receivers map[string]api.ReceiverType + err bool + gRoutes, amRoutes []*config.Route + }{ + { + desc: "splits simple", + input: []*config.Route{ + { + Receiver: "am", + }, + { + Receiver: "graf", + }, + }, + err: false, + gRoutes: []*config.Route{ + { + Receiver: "graf", + }, + }, + amRoutes: []*config.Route{ + { + Receiver: "am", + }, + }, + }, + { + desc: "splits nested", + input: []*config.Route{ + { + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "am", + }, + }, + }, + { + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + }, + err: false, + gRoutes: []*config.Route{ + { + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + }, + amRoutes: []*config.Route{ + { + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "am", + }, + }, + }, + }, + }, + { + desc: "errors undefined receiver", + input: []*config.Route{ + { + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "unmentioned", + }, + }, + }, + }, + err: true, + }, + { + desc: "errors mixed", + input: []*config.Route{ + { + Receiver: "am", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + { + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + }, + err: true, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + gRoutes, amRoutes, err := SplitRoutes(tc.input, receivers) + if tc.err { + require.Error(t, err) + } else { + require.Equal(t, tc.gRoutes, gRoutes) + require.Equal(t, tc.amRoutes, amRoutes) + } + }) + } +}