package cloudmigrationimpl import ( "context" "encoding/json" "fmt" "net/http" "testing" "time" "github.com/grafana/alerting/definition" "github.com/prometheus/alertmanager/pkg/labels" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) // Read-only. var alertRulesPermissions = map[string][]string{ accesscontrol.ActionAlertingRuleRead: {"*"}, accesscontrol.ActionAlertingRuleCreate: {"*"}, accesscontrol.ActionAlertingRuleUpdate: {"*"}, dashboards.ActionFoldersRead: {"*"}, datasources.ActionQuery: {"*"}, } func TestGetAlertMuteTimings(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("it returns the mute timings", func(t *testing.T) { t.Parallel() s := setUpServiceTest(t, false).(*Service) s.features = featuremgmt.WithFeatures(featuremgmt.FlagOnPremToCloudMigrations) user := &user.SignedInUser{OrgID: 1} createdMuteTiming := createMuteTiming(t, ctx, s, user) muteTimeIntervals, err := s.getAlertMuteTimings(ctx, user) require.NoError(t, err) require.NotNil(t, muteTimeIntervals) require.Len(t, muteTimeIntervals, 1) require.Equal(t, createdMuteTiming.Name, muteTimeIntervals[0].Name) }) } func TestGetNotificationTemplates(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("it returns the notification templates", func(t *testing.T) { t.Parallel() s := setUpServiceTest(t, false).(*Service) user := &user.SignedInUser{OrgID: 1} createdTemplate := createNotificationTemplate(t, ctx, s, user) notificationTemplates, err := s.getNotificationTemplates(ctx, user) require.NoError(t, err) require.NotNil(t, notificationTemplates) require.Len(t, notificationTemplates, 1) require.Equal(t, createdTemplate.Name, notificationTemplates[0].Name) }) } func TestGetContactPoints(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("it returns the contact points", func(t *testing.T) { t.Parallel() s := setUpServiceTest(t, false).(*Service) user := &user.SignedInUser{ OrgID: 1, Permissions: map[int64]map[string][]string{ 1: { accesscontrol.ActionAlertingNotificationsRead: nil, accesscontrol.ActionAlertingReceiversReadSecrets: {ac.ScopeReceiversAll}, }, }, } createdContactPoints := createContactPoints(t, ctx, s, user) contactPoints, err := s.getContactPoints(ctx, user) require.NoError(t, err) require.NotNil(t, contactPoints) require.Len(t, contactPoints, len(createdContactPoints)) }) t.Run("it returns an error when user lacks permission to read contact point secrets", func(t *testing.T) { t.Parallel() s := setUpServiceTest(t, false).(*Service) user := &user.SignedInUser{ OrgID: 1, Permissions: map[int64]map[string][]string{ 1: { accesscontrol.ActionAlertingNotificationsRead: nil, }, }, } contactPoints, err := s.getContactPoints(ctx, user) require.Nil(t, contactPoints) gfErr := errutil.Error{} require.ErrorAs(t, err, &gfErr) require.Equal(t, http.StatusForbidden, gfErr.Reason.Status().HTTPStatus()) }) } func TestGetNotificationPolicies(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("it returns the contact points", func(t *testing.T) { t.Parallel() s := setUpServiceTest(t, false).(*Service) user := &user.SignedInUser{OrgID: 1} muteTiming := createMuteTiming(t, ctx, s, user) require.NotEmpty(t, muteTiming.Name) contactPoints := createContactPoints(t, ctx, s, user) require.GreaterOrEqual(t, len(contactPoints), 1) updateNotificationPolicyTree(t, ctx, s, user, contactPoints[0].Name, muteTiming.Name) notificationPolicies, err := s.getNotificationPolicies(ctx, user) require.NoError(t, err) require.NotEmpty(t, notificationPolicies.Routes.Receiver) require.NotNil(t, notificationPolicies.Routes.Routes) }) } func TestGetAlertRules(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("it returns the alert rules", func(t *testing.T) { t.Parallel() s := setUpServiceTest(t, false).(*Service) user := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: alertRulesPermissions}} alertRule := createAlertRule(t, ctx, s, user, false, "") alertRules, err := s.getAlertRules(ctx, user) require.NoError(t, err) require.Len(t, alertRules, 1) require.Equal(t, alertRule.UID, alertRules[0].UID) }) t.Run("when the alert_rules_state config is `paused`, then the alert rules are all returned in `paused` state", func(t *testing.T) { t.Parallel() alertRulesState := func(c *setting.Cfg) { c.CloudMigration.AlertRulesState = setting.GMSAlertRulesPaused } s := setUpServiceTest(t, false, alertRulesState).(*Service) user := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: alertRulesPermissions}} alertRulePaused := createAlertRule(t, ctx, s, user, true, "") require.True(t, alertRulePaused.IsPaused) alertRuleUnpaused := createAlertRule(t, ctx, s, user, false, "") require.False(t, alertRuleUnpaused.IsPaused) alertRules, err := s.getAlertRules(ctx, user) require.NoError(t, err) require.Len(t, alertRules, 2) require.True(t, alertRules[0].IsPaused) require.True(t, alertRules[1].IsPaused) }) } func TestGetAlertRuleGroups(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) t.Run("it returns the alert rule groups", func(t *testing.T) { t.Parallel() s := setUpServiceTest(t, false).(*Service) user := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: alertRulesPermissions}} ruleGroupTitle := "ruleGroupTitle" alertRule1 := createAlertRule(t, ctx, s, user, true, ruleGroupTitle) alertRule2 := createAlertRule(t, ctx, s, user, false, ruleGroupTitle) alertRule3 := createAlertRule(t, ctx, s, user, false, "anotherRuleGroup") createAlertRuleGroup(t, ctx, s, user, ruleGroupTitle, []models.AlertRule{alertRule1, alertRule2}) ruleGroups, err := s.getAlertRuleGroups(ctx, user) require.NoError(t, err) require.Len(t, ruleGroups, 2) for _, ruleGroup := range ruleGroups { alertRuleUIDs := make([]string, 0) for _, alertRule := range ruleGroup.Rules { alertRuleUIDs = append(alertRuleUIDs, alertRule.UID) } if ruleGroup.Title == ruleGroupTitle { require.Len(t, ruleGroup.Rules, 2) require.ElementsMatch(t, []string{alertRule1.UID, alertRule2.UID}, alertRuleUIDs) } else { require.Len(t, ruleGroup.Rules, 1) require.ElementsMatch(t, []string{alertRule3.UID}, alertRuleUIDs) } } }) t.Run("with the alert rules state set to paused, it returns the alert rule groups with alert rules paused", func(t *testing.T) { t.Parallel() alertRulesState := func(c *setting.Cfg) { c.CloudMigration.AlertRulesState = setting.GMSAlertRulesPaused } s := setUpServiceTest(t, false, alertRulesState).(*Service) user := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: alertRulesPermissions}} ruleGroupTitle := "ruleGroupTitle" alertRule1 := createAlertRule(t, ctx, s, user, true, ruleGroupTitle) alertRule2 := createAlertRule(t, ctx, s, user, false, ruleGroupTitle) alertRule3 := createAlertRule(t, ctx, s, user, false, "anotherRuleGroup") createAlertRuleGroup(t, ctx, s, user, ruleGroupTitle, []models.AlertRule{alertRule1, alertRule2}) ruleGroups, err := s.getAlertRuleGroups(ctx, user) require.NoError(t, err) require.Len(t, ruleGroups, 2) for _, ruleGroup := range ruleGroups { alertRuleUIDs := make([]string, 0) for _, alertRule := range ruleGroup.Rules { alertRuleUIDs = append(alertRuleUIDs, alertRule.UID) require.True(t, alertRule.IsPaused) } if ruleGroup.Title == ruleGroupTitle { require.Len(t, ruleGroup.Rules, 2) require.ElementsMatch(t, []string{alertRule1.UID, alertRule2.UID}, alertRuleUIDs) } else { require.Len(t, ruleGroup.Rules, 1) require.ElementsMatch(t, []string{alertRule3.UID}, alertRuleUIDs) } } }) } func createMuteTiming(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) definitions.MuteTimeInterval { t.Helper() muteTiming := `{ "name": "My Unique MuteTiming 1", "time_intervals": [ { "times": [{"start_time": "12:12","end_time": "23:23"}], "weekdays": ["monday","wednesday","friday","sunday"], "days_of_month": ["10:20","25:-1"], "months": ["1:6","10:12"], "years": ["2022:2054"], "location": "Africa/Douala" } ] }` var mt definitions.MuteTimeInterval require.NoError(t, json.Unmarshal([]byte(muteTiming), &mt)) createdTiming, err := service.ngAlert.Api.MuteTimings.CreateMuteTiming(ctx, mt, user.GetOrgID()) require.NoError(t, err) return createdTiming } func createNotificationTemplate(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) definitions.NotificationTemplate { t.Helper() tmpl := definitions.NotificationTemplate{ Name: "MyTestNotificationTemplate", Template: "This is a test template\n{{ .ExternalURL }}", } createdTemplate, err := service.ngAlert.Api.Templates.CreateTemplate(ctx, user.GetOrgID(), tmpl) require.NoError(t, err) return createdTemplate } func createContactPoints(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) []definitions.EmbeddedContactPoint { t.Helper() slackSettings, err := simplejson.NewJson([]byte(`{ "icon_emoji":"iconemoji", "icon_url":"iconurl", "recipient":"recipient", "token":"slack-secret", "username":"user" }`)) require.NoError(t, err) telegramSettings, err := simplejson.NewJson([]byte(`{ "bottoken":"telegram-secret", "chatid":"chat-id", "disable_notification":true, "disable_web_page_preview":false, "message_thread_id":"1234", "parse_mode":"None", "protect_content":true }`)) require.NoError(t, err) nameGroup := "group_1" slackContactPoint := definitions.EmbeddedContactPoint{ Name: nameGroup, Type: "slack", Settings: slackSettings, DisableResolveMessage: false, Provenance: "", } createdSlack, err := service.ngAlert.Api.ContactPointService.CreateContactPoint(ctx, user.GetOrgID(), user, slackContactPoint, "") require.NoError(t, err) telegramContactPoint := definitions.EmbeddedContactPoint{ Name: nameGroup, Type: "telegram", Settings: telegramSettings, DisableResolveMessage: false, Provenance: "", } createdTelegram, err := service.ngAlert.Api.ContactPointService.CreateContactPoint(ctx, user.GetOrgID(), user, telegramContactPoint, "") require.NoError(t, err) return []definitions.EmbeddedContactPoint{ createdSlack, createdTelegram, } } func updateNotificationPolicyTree(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, receiverGroup, muteTiming string) { t.Helper() child := definition.Route{ Continue: true, MuteTimeIntervals: []string{muteTiming}, ObjectMatchers: definition.ObjectMatchers{ {Name: "label1", Type: labels.MatchEqual, Value: "value1"}, {Name: "label2", Type: labels.MatchNotEqual, Value: "value2"}, }, Receiver: receiverGroup, } tree := definition.Route{ Receiver: "grafana-default-email", Routes: []*definition.Route{&child}, } _, _, err := service.ngAlert.Api.Policies.UpdatePolicyTree(ctx, user.GetOrgID(), tree, "", "") require.NoError(t, err) } func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, isPaused bool, ruleGroup string) models.AlertRule { t.Helper() rule := models.AlertRule{ OrgID: user.GetOrgID(), Title: fmt.Sprintf("Alert Rule SLO (Paused: %v) - %v", isPaused, ruleGroup), NamespaceUID: "folderUID", Condition: "A", Data: []models.AlertQuery{ { RefID: "A", Model: []byte(`{"queryType": "a"}`), RelativeTimeRange: models.RelativeTimeRange{ From: models.Duration(60), To: models.Duration(0), }, }, }, IsPaused: isPaused, RuleGroup: ruleGroup, For: time.Minute, IntervalSeconds: 60, NoDataState: models.OK, ExecErrState: models.OkErrState, } createdRule, err := service.ngAlert.Api.AlertRules.CreateAlertRule(ctx, user, rule, "") require.NoError(t, err) return createdRule } func createAlertRuleGroup(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, title string, rules []models.AlertRule) models.AlertRuleGroup { t.Helper() group := models.AlertRuleGroup{ Title: title, FolderUID: "folderUID", Interval: 300, Rules: rules, } err := service.ngAlert.Api.AlertRules.ReplaceRuleGroup(ctx, user, group, "") require.NoError(t, err) return group }