From 5dee2ed24c4a361c64c8e153b230dd5afffd0345 Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Fri, 17 Jun 2022 13:10:49 -0400 Subject: [PATCH] Alerting: Add first Grafana reserved label grafana_folder (#50262) * Alerting: Add first Grafana reserved label g_label g_label holds the title of the folder container the alert. The intention of this label is to use it as part of the new default notification policy groupBy. * Add nil check on updateRule labels map * Disable gocyclo lint on schedule.ruleRoutine will remove later in a separate refactoring PR to reduce complexity. * Address doc suggestions * Update g_folder for rules in folder when folder title changes * Remove global bus in FolderService * Modify tests to fit new common g_folder label * Add changelog entry * Fix merge conflicts * Switch GrafanaReservedLabelPrefix from `g_` to `grafana_` --- .../annotation-label/how-to-use-labels.md | 12 +- pkg/bus/mock/mock.go | 11 ++ pkg/events/events.go | 8 ++ .../dashboards/service/folder_service.go | 20 ++- .../dashboards/service/folder_service_test.go | 4 +- .../libraryelements/libraryelements_test.go | 5 +- .../librarypanels/librarypanels_test.go | 6 +- pkg/services/ngalert/CHANGELOG.md | 1 + pkg/services/ngalert/models/alert_rule.go | 8 ++ pkg/services/ngalert/ngalert.go | 9 +- pkg/services/ngalert/schedule/schedule.go | 122 +++++++++++++----- .../ngalert/schedule/schedule_mock.go | 54 ++++---- .../ngalert/schedule/schedule_test.go | 3 +- .../ngalert/schedule/schedule_unit_test.go | 20 ++- pkg/services/ngalert/store/alert_rule.go | 11 ++ pkg/services/ngalert/store/testing.go | 15 +++ pkg/services/ngalert/tests/util.go | 7 +- .../alerting/api_notification_channel_test.go | 91 ++++++------- 18 files changed, 291 insertions(+), 116 deletions(-) create mode 100644 pkg/bus/mock/mock.go diff --git a/docs/sources/alerting/fundamentals/annotation-label/how-to-use-labels.md b/docs/sources/alerting/fundamentals/annotation-label/how-to-use-labels.md index f2030094b15..36236a822e1 100644 --- a/docs/sources/alerting/fundamentals/annotation-label/how-to-use-labels.md +++ b/docs/sources/alerting/fundamentals/annotation-label/how-to-use-labels.md @@ -19,6 +19,16 @@ This topic explains why labels are a fundamental component of alerting. - The Alertmanager uses labels to match alerts for [silences]({{< relref "../../silences/" >}}) and [alert groups]({{< relref "../../alert-groups/" >}}) in [notification policies]({{< relref "../../notifications/" >}}). - The alerting UI shows labels for every alert instance generated during evaluation of that rule. - Contact points can access labels to dynamically generate notifications that contain information specific to the alert that is resulting in a notification. -- Labels can be added to an [alerting rule]({{< relref "../../alerting-rules/" >}}). These manually configured labels are able to use template functions and reference other labels. Labels added to an alerting rule take precedence in the event of a collision between labels. +- You can add labels to an [alerting rule]({{< relref "../../alerting-rules/" >}}). Labels are manually configurable, use template functions, and can reference other labels. Labels added to an alerting rule take precedence in the event of a collision between labels (except in the case of [Grafana reserved labels](#grafana-reserved-labels)). {{< figure src="/static/img/docs/alerting/unified/rule-edit-details-8-0.png" max-width="550px" caption="Alert details" >}} + +# Grafana reserved labels + +> **Note:** Labels prefixed with `grafana_` are reserved by Grafana for special use. If a manually configured label is added beginning with `grafana_` it may be overwritten in case of collision. + +Grafana reserved labels can be used in the same way as manually configured labels. The current list of available reserved labels are: + +| Label | Description | +| -------------- | ----------------------------------------- | +| grafana_folder | Title of the folder containing the alert. | diff --git a/pkg/bus/mock/mock.go b/pkg/bus/mock/mock.go new file mode 100644 index 00000000000..f65e9b6b122 --- /dev/null +++ b/pkg/bus/mock/mock.go @@ -0,0 +1,11 @@ +package mock + +import ( + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/infra/tracing" +) + +func New() bus.Bus { + tracer := tracing.InitializeTracerForTest() + return bus.ProvideBus(tracer) +} diff --git a/pkg/events/events.go b/pkg/events/events.go index dcd145d171c..fd77a9a4061 100644 --- a/pkg/events/events.go +++ b/pkg/events/events.go @@ -62,3 +62,11 @@ type DataSourceCreated struct { UID string `json:"uid"` OrgID int64 `json:"org_id"` } + +type FolderUpdated struct { + Timestamp time.Time `json:"timestamp"` + Title string `json:"name"` + ID int64 `json:"id"` + UID string `json:"uid"` + OrgID int64 `json:"org_id"` +} diff --git a/pkg/services/dashboards/service/folder_service.go b/pkg/services/dashboards/service/folder_service.go index 26deb893058..c48c5bf7655 100644 --- a/pkg/services/dashboards/service/folder_service.go +++ b/pkg/services/dashboards/service/folder_service.go @@ -4,7 +4,10 @@ import ( "context" "errors" "strings" + "time" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -23,12 +26,15 @@ type FolderServiceImpl struct { searchService *search.SearchService features featuremgmt.FeatureToggles permissions accesscontrol.FolderPermissionsService + + // bus is currently used to publish events that cause scheduler to update rules. + bus bus.Bus } func ProvideFolderService( cfg *setting.Cfg, dashboardService dashboards.DashboardService, dashboardStore dashboards.Store, searchService *search.SearchService, features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService, - ac accesscontrol.AccessControl, + ac accesscontrol.AccessControl, bus bus.Bus, ) *FolderServiceImpl { ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore)) @@ -41,6 +47,7 @@ func ProvideFolderService( searchService: searchService, features: features, permissions: folderPermissionsService, + bus: bus, } } @@ -222,6 +229,17 @@ func (f *FolderServiceImpl) UpdateFolder(ctx context.Context, user *models.Signe return err } cmd.Result = folder + + if err := f.bus.Publish(ctx, &events.FolderUpdated{ + Timestamp: time.Now(), + Title: folder.Title, + ID: dash.Id, + UID: dash.Uid, + OrgID: orgID, + }); err != nil { + f.log.Error("failed to publish FolderUpdated event", "folder", folder.Title, "user", user.UserId, "error", err) + } + return nil } diff --git a/pkg/services/dashboards/service/folder_service_test.go b/pkg/services/dashboards/service/folder_service_test.go index f0869be2ea4..99cf9168ab7 100644 --- a/pkg/services/dashboards/service/folder_service_test.go +++ b/pkg/services/dashboards/service/folder_service_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + busmock "github.com/grafana/grafana/pkg/bus/mock" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" @@ -30,7 +31,7 @@ func TestIntegrationProvideFolderService(t *testing.T) { cfg := setting.NewCfg() ac := acmock.New() - ProvideFolderService(cfg, nil, nil, nil, nil, nil, ac) + ProvideFolderService(cfg, nil, nil, nil, nil, nil, ac, busmock.New()) require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2) }) @@ -57,6 +58,7 @@ func TestIntegrationFolderService(t *testing.T) { searchService: nil, features: features, permissions: folderPermissions, + bus: busmock.New(), } t.Run("Given user has no permissions", func(t *testing.T) { diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index b935b611bdf..b4f70e53442 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/api/response" + busmock "github.com/grafana/grafana/pkg/bus/mock" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" @@ -236,7 +237,7 @@ func createFolderWithACL(t *testing.T, sqlStore *sqlstore.SQLStore, title string ) s := dashboardservice.ProvideFolderService( cfg, d, dashboardStore, nil, - features, folderPermissions, ac, + features, folderPermissions, ac, busmock.New(), ) t.Logf("Creating folder with title and UID %q", title) folder, err := s.CreateFolder(context.Background(), &user, user.OrgId, title, title) @@ -340,7 +341,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo SQLStore: sqlStore, folderService: dashboardservice.ProvideFolderService( cfg, dashboardService, dashboardStore, nil, - features, folderPermissions, ac, + features, folderPermissions, ac, busmock.New(), ), } diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 7d6bef6e3ea..af28610b4ce 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/api/routing" + busmock "github.com/grafana/grafana/pkg/bus/mock" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" @@ -1393,7 +1394,7 @@ func createFolderWithACL(t *testing.T, sqlStore *sqlstore.SQLStore, title string dashboardPermissions := acmock.NewMockedPermissionsService() dashboardStore := database.ProvideDashboardStore(sqlStore) d := dashboardservice.ProvideDashboardService(cfg, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac) - s := dashboardservice.ProvideFolderService(cfg, d, dashboardStore, nil, features, folderPermissions, ac) + s := dashboardservice.ProvideFolderService(cfg, d, dashboardStore, nil, features, folderPermissions, ac, busmock.New()) t.Logf("Creating folder with title and UID %q", title) folder, err := s.CreateFolder(context.Background(), user, user.OrgId, title, title) @@ -1494,10 +1495,9 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo cfg, dashboardStore, &alerting.DashAlertExtractorService{}, features, folderPermissions, dashboardPermissions, ac, ) - folderService := dashboardservice.ProvideFolderService( cfg, dashboardService, dashboardStore, nil, - features, folderPermissions, ac, + features, folderPermissions, ac, busmock.New(), ) elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService) diff --git a/pkg/services/ngalert/CHANGELOG.md b/pkg/services/ngalert/CHANGELOG.md index ca1c33d975c..3013d378a80 100644 --- a/pkg/services/ngalert/CHANGELOG.md +++ b/pkg/services/ngalert/CHANGELOG.md @@ -46,6 +46,7 @@ Scopes must have an order to ensure consistency and ease of search, this helps u ## Grafana Alerting - main / unreleased - [CHANGE] Rule API to reject request to update rules that affects provisioned rules #50835 +- [FEATURE] Add first Grafana reserved label, grafana_folder is created during runtime and stores an alert's folder/namespace title #50262 - [FEATURE] use optimistic lock by version field when updating alert rules #50274 - [ENHANCEMENT] Scheduler: Drop ticks if rule evaluation is too slow and adds a metric grafana_alerting_schedule_rule_evaluations_missed_total to track missed evaluations per rule #48885 - [ENHANCEMENT] Ticker to tick at predictable time #50197 diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 4d6b0932138..073c1c259fc 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -87,6 +87,14 @@ const ( // This isn't a hard-coded secret token, hence the nolint. //nolint:gosec ScreenshotTokenAnnotation = "__alertScreenshotToken__" + + // GrafanaReservedLabelPrefix contains the prefix for Grafana reserved labels. These differ from "__