Alerting: Enable recording rules by default (#105603)

This commit is contained in:
Alexander Akhmetov
2025-06-02 10:56:05 +02:00
committed by GitHub
parent 4cde79e802
commit e256f2d5e2
26 changed files with 113 additions and 347 deletions
+2 -12
View File
@@ -1539,23 +1539,13 @@ max_annotations_to_keep =
rule_query_offset = 1m
[recording_rules]
# Enable recording rules. You must provide write credentials below.
enabled = false
# Target URL (including write path) for recording rules.
url =
# Optional username for basic authentication on recording rule write requests. Can be left blank to disable basic auth
basic_auth_username =
# Optional assword for basic authentication on recording rule write requests. Can be left blank.
basic_auth_password =
# Enable recording rules.
enabled = true
# Request timeout for recording rule writes.
timeout = 10s
# Default data source UID to write to if not specified in the rule definition.
# Only has effect if the grafanaManagedRecordingRulesDatasources feature toggle is enabled.
default_datasource_uid =
# Optional custom headers to include in recording rule write requests.
+2 -12
View File
@@ -1518,23 +1518,13 @@ rule_query_offset = 1m
#################################### Recording Rules #####################
[recording_rules]
# Enable recording rules. You must provide write credentials below.
enabled = false
# Target URL (including write path) for recording rules.
url =
# Optional username for basic authentication on recording rule write requests. Can be left blank to disable basic auth
basic_auth_username =
# Optional assword for basic authentication on recording rule write requests. Can be left blank.
basic_auth_password =
# Enable recording rules.
enabled = true
# Request timeout for recording rule writes.
timeout = 30s
# Default data source UID to write to if not specified in the rule definition.
# Only has effect if the grafanaManagedRecordingRulesDatasources feature toggle is enabled.
default_datasource_uid =
# Optional custom headers to include in recording rule write requests.
@@ -27,15 +27,12 @@ Grafana provides an internal tool in Alerting which allows you to import Mimir a
## Before you begin
The `alertingMigrationUI` [feature flag](/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/) needs to be enabled to use this feature.
To import recording rules, they [must be configured](ref:configure-recording-rules), and the `grafanaManagedRecordingRulesDatasources` [feature flag](/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/) must be enabled.
To use the migration tool, you need the following [RBAC permissions](/docs/grafana/latest/administration/roles-and-permissions/access-control/):
- Alerting: Rules Writer
- Alerting: Set provisioning status
- Datasources: Reader
- Folders: Creator
- Folders: Creator
{{< admonition type="note" >}}
The Folders permission is optional and only necessary if you want to create new folders for your target namespace. If your account doesn't have permissions to view a namespace, the tool creates a new one. It is a best practice to prepare an import plan before you convert all your alert rules.
{{< /admonition >}}
@@ -22,8 +22,6 @@ You can convert data source-managed alert rules to Grafana-managed alert rules w
## Before you begin
To import recording rules, they [must be configured](ref:configure-recording-rules), and the `grafanaManagedRecordingRulesDatasources` [feature flag](/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/) must be enabled.
To import data source-managed alert rules with Grafana Mimirtool, you need to have the Grafana Mimirtool command-line tool installed.
You need a service account the following [RBAC permissions](/docs/grafana/latest/administration/roles-and-permissions/access-control/):
@@ -63,31 +63,18 @@ To configure Grafana-managed recording rules, complete the following steps.
This section only applies to Grafana OSS and Grafana Enterprise.
Enable the feature by setting `enabled = true` in the `[recording_rules]` section of the Grafana configuration file. Provide the URL of your Prometheus-compatible remote-write endpoint in the `url` field, along with optional credentials or headers.
If a rule does not explicitly specify a target data source for writing (for example, if it was created before Grafana 12.1), Grafana will fall back to the `default_datasource_uid` set in the configuration:
```
[recording_rules]
enabled = true
url = http://my-example-prometheus.local:9090/api/prom/push
basic_auth_username = my-user
basic_auth_password = my-pass
[recording_rules.custom_headers]
X-My-Header = MyValue
```
### Per-rule data source
To choose the remote-write Prometheus data source individually for each recording rule, also enable the `grafanaManagedRecordingRulesDatasources` feature flag.
When this flag is on, Grafana does not use the `url` defined in the configuration file, and the rule editor shows a dropdown to select the target data source. If a rule does not specify a target, for example it was created before the flag was enabled, Grafana writes to the data source identified by `default_datasource_uid` in the Grafana configuration:
```
[recording_rules]
default_datasource_uid = my-uid
```
If you previously configured recording rules using the `url` and `basic_auth_*` configuration options, these are no longer supported. You must either:
- Set `default_datasource_uid` in the `[recording_rules]` section of the configuration file to point to the target data source
- Or, before upgrading to Grafana 12.1, enable the `grafanaManagedRecordingRulesDatasources` feature flag and update each recording rule individually to include a target data source
## Add new recording rule
To create a new Grafana-managed recording rule:
@@ -21,67 +21,66 @@ For more information about feature release stages, refer to [Release life cycle
Most [generally available](https://grafana.com/docs/release-life-cycle/#general-availability) features are enabled by default. You can disable these feature by setting the feature flag to "false" in the configuration.
| Feature toggle name | Description | Enabled by default |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
| `publicDashboardsScene` | Enables public dashboard rendering using scenes | Yes |
| `featureHighlights` | Highlight Grafana Enterprise features | |
| `correlations` | Correlations page | Yes |
| `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes |
| `nestedFolders` | Enable folder nesting | Yes |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes |
| `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes |
| `unifiedRequestLog` | Writes error logs to the request logger | Yes |
| `pluginsDetailsRightPanel` | Enables right panel for the plugins details page | Yes |
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes |
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes |
| `transformationsRedesign` | Enables the transformations redesign | Yes |
| `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes |
| `angularDeprecationUI` | Display Angular warnings in dashboards and panels | Yes |
| `dashgpt` | Enable AI powered features in dashboards | Yes |
| `externalCorePlugins` | Allow core plugins to be loaded as external | Yes |
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | Yes |
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
| `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | Yes |
| `dashboardSceneSolo` | Enables rendering dashboards using scenes for solo panels | Yes |
| `dashboardScene` | Enables dashboard rendering using scenes for all roles | Yes |
| `ssoSettingsApi` | Enables the SSO settings API and the OAuth configuration UIs in Grafana | Yes |
| `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | Yes |
| `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes |
| `lokiQueryHints` | Enables query hints for Loki | Yes |
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
| `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate various on-prem resources to your Grafana Cloud stack. | Yes |
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
| `newPDFRendering` | New implementation for the dashboard-to-PDF rendering | Yes |
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
| `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector | Yes |
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
| `newDashboardSharingComponent` | Enables the new sharing drawer design | Yes |
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | Yes |
| `pinNavItems` | Enables pinning of nav items | Yes |
| `failWrongDSUID` | Throws an error if a data source has an invalid UIDs | Yes |
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
| `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | Yes |
| `alertingQueryAndExpressionsStepMode` | Enables step mode for alerting queries and expressions | Yes |
| `useSessionStorageForRedirection` | Use session storage for handling the redirection after login | Yes |
| `pluginsSriChecks` | Enables SRI checks for plugin assets | |
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
| `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes |
| `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes |
| `alertingNotificationsStepMode` | Enables simplified step mode in the notifications section | Yes |
| `lokiLabelNamesQueryApi` | Defaults to using the Loki `/labels` API instead of `/series` | Yes |
| `grafanaManagedRecordingRulesDatasources` | Enables writing to data sources for Grafana-managed recording rules. | |
| `alertingMigrationUI` | Enables the alerting migration UI, to migrate data source-managed rules to Grafana-managed rules | Yes |
| `alertingImportYAMLUI` | Enables a UI feature for importing rules from a Prometheus file to Grafana-managed rules | Yes |
| `unifiedNavbars` | Enables unified navbars | |
| Feature toggle name | Description | Enabled by default |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
| `publicDashboardsScene` | Enables public dashboard rendering using scenes | Yes |
| `featureHighlights` | Highlight Grafana Enterprise features | |
| `correlations` | Correlations page | Yes |
| `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes |
| `nestedFolders` | Enable folder nesting | Yes |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes |
| `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes |
| `unifiedRequestLog` | Writes error logs to the request logger | Yes |
| `pluginsDetailsRightPanel` | Enables right panel for the plugins details page | Yes |
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes |
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes |
| `transformationsRedesign` | Enables the transformations redesign | Yes |
| `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes |
| `angularDeprecationUI` | Display Angular warnings in dashboards and panels | Yes |
| `dashgpt` | Enable AI powered features in dashboards | Yes |
| `externalCorePlugins` | Allow core plugins to be loaded as external | Yes |
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | Yes |
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
| `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | Yes |
| `dashboardSceneSolo` | Enables rendering dashboards using scenes for solo panels | Yes |
| `dashboardScene` | Enables dashboard rendering using scenes for all roles | Yes |
| `ssoSettingsApi` | Enables the SSO settings API and the OAuth configuration UIs in Grafana | Yes |
| `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | Yes |
| `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes |
| `lokiQueryHints` | Enables query hints for Loki | Yes |
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
| `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate various on-prem resources to your Grafana Cloud stack. | Yes |
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
| `newPDFRendering` | New implementation for the dashboard-to-PDF rendering | Yes |
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
| `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector | Yes |
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
| `newDashboardSharingComponent` | Enables the new sharing drawer design | Yes |
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | Yes |
| `pinNavItems` | Enables pinning of nav items | Yes |
| `failWrongDSUID` | Throws an error if a data source has an invalid UIDs | Yes |
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
| `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | Yes |
| `alertingQueryAndExpressionsStepMode` | Enables step mode for alerting queries and expressions | Yes |
| `useSessionStorageForRedirection` | Use session storage for handling the redirection after login | Yes |
| `pluginsSriChecks` | Enables SRI checks for plugin assets | |
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
| `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes |
| `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes |
| `alertingNotificationsStepMode` | Enables simplified step mode in the notifications section | Yes |
| `lokiLabelNamesQueryApi` | Defaults to using the Loki `/labels` API instead of `/series` | Yes |
| `alertingMigrationUI` | Enables the alerting migration UI, to migrate data source-managed rules to Grafana-managed rules | Yes |
| `alertingImportYAMLUI` | Enables a UI feature for importing rules from a Prometheus file to Grafana-managed rules | Yes |
| `unifiedNavbars` | Enables unified navbars | |
## Public preview feature toggles
@@ -907,11 +907,6 @@ export interface FeatureToggles {
*/
alertRuleRestore?: boolean;
/**
* Enables writing to data sources for Grafana-managed recording rules.
* @default false
*/
grafanaManagedRecordingRulesDatasources?: boolean;
/**
* Enables running Infinity queries in parallel
*/
infinityRunQueriesInParallel?: boolean;
-8
View File
@@ -1554,14 +1554,6 @@ var (
Owner: grafanaAlertingSquad,
Expression: "true", // enabled by default
},
{
Name: "grafanaManagedRecordingRulesDatasources",
Description: "Enables writing to data sources for Grafana-managed recording rules.",
Stage: FeatureStageGeneralAvailability,
Owner: grafanaAlertingSquad,
AllowSelfServe: false,
Expression: "false",
},
{
Name: "infinityRunQueriesInParallel",
Description: "Enables running Infinity queries in parallel",
-1
View File
@@ -204,7 +204,6 @@ newShareReportDrawer,experimental,@grafana/sharing-squad,false,false,false
rendererDisableAppPluginsPreload,experimental,@grafana/sharing-squad,false,false,true
assetSriChecks,experimental,@grafana/frontend-ops,false,false,true
alertRuleRestore,preview,@grafana/alerting-squad,false,false,false
grafanaManagedRecordingRulesDatasources,GA,@grafana/alerting-squad,false,false,false
infinityRunQueriesInParallel,privatePreview,@grafana/oss-big-tent,false,false,false
inviteUserExperimental,experimental,@grafana/sharing-squad,false,false,true
alertingMigrationUI,GA,@grafana/alerting-squad,false,false,true
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
204 rendererDisableAppPluginsPreload experimental @grafana/sharing-squad false false true
205 assetSriChecks experimental @grafana/frontend-ops false false true
206 alertRuleRestore preview @grafana/alerting-squad false false false
grafanaManagedRecordingRulesDatasources GA @grafana/alerting-squad false false false
207 infinityRunQueriesInParallel privatePreview @grafana/oss-big-tent false false false
208 inviteUserExperimental experimental @grafana/sharing-squad false false true
209 alertingMigrationUI GA @grafana/alerting-squad false false true
-4
View File
@@ -827,10 +827,6 @@ const (
// Enables the alert rule restore feature
FlagAlertRuleRestore = "alertRuleRestore"
// FlagGrafanaManagedRecordingRulesDatasources
// Enables writing to data sources for Grafana-managed recording rules.
FlagGrafanaManagedRecordingRulesDatasources = "grafanaManagedRecordingRulesDatasources"
// FlagInfinityRunQueriesInParallel
// Enables running Infinity queries in parallel
FlagInfinityRunQueriesInParallel = "infinityRunQueriesInParallel"
@@ -1449,6 +1449,7 @@
"name": "grafanaManagedRecordingRulesDatasources",
"resourceVersion": "1746998568254",
"creationTimestamp": "2025-03-07T13:30:40Z",
"deletionTimestamp": "2025-05-19T10:18:04Z",
"annotations": {
"grafana.app/updatedTimestamp": "2025-05-11 21:22:48.254378 +0000 UTC"
}
@@ -62,11 +62,6 @@ var (
"alerting.recordingRulesNotEnabled",
errutil.WithPublicMessage("Cannot import recording rules: Feature not enabled."),
).Errorf("recording rules not enabled")
errRecordingRulesDatasourcesNotEnabled = errutil.ValidationFailed(
"alerting.recordingRulesDatasourcesNotEnabled",
errutil.WithPublicMessage("Cannot import recording rules: Configuration of target datasources not enabled."),
).Errorf("recording rules target datasources configuration not enabled")
)
func errInvalidHeaderValue(header string, err error) error {
@@ -399,11 +394,6 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroups(c *context
logger.Error("Cannot import recording rules", "error", errRecordingRulesNotEnabled)
return errorToResponse(errRecordingRulesNotEnabled)
}
if !srv.featureToggles.IsEnabledGlobally(featuremgmt.FlagGrafanaManagedRecordingRulesDatasources) {
logger.Error("Cannot import recording rules", "error", errRecordingRulesDatasourcesNotEnabled)
return errorToResponse(errRecordingRulesDatasourcesNotEnabled)
}
}
grafanaGroup, err := srv.convertToGrafanaRuleGroup(
@@ -295,45 +295,25 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
t.Run("with disabled recording rules", func(t *testing.T) {
testCases := []struct {
name string
recordingRules bool
recordingRulesTargetDS bool
expectedStatus int
name string
recordingRules bool
expectedStatus int
}{
{
name: "when recording rules are enabled",
recordingRules: true,
recordingRulesTargetDS: true,
expectedStatus: http.StatusAccepted,
name: "when recording rules are enabled",
recordingRules: true,
expectedStatus: http.StatusAccepted,
},
{
name: "when recording rules are disabled",
recordingRules: false,
recordingRulesTargetDS: true,
expectedStatus: http.StatusBadRequest,
},
{
name: "when target datasources for recording rules are disabled",
recordingRules: true,
recordingRulesTargetDS: false,
expectedStatus: http.StatusBadRequest,
},
{
name: "when both recording rules and target datasources are disabled",
recordingRules: false,
recordingRulesTargetDS: false,
expectedStatus: http.StatusBadRequest,
name: "when recording rules are disabled",
recordingRules: false,
expectedStatus: http.StatusBadRequest,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var features featuremgmt.FeatureToggles
if tc.recordingRulesTargetDS {
features = featuremgmt.WithFeatures(featuremgmt.FlagGrafanaManagedRecordingRulesDatasources)
} else {
features = featuremgmt.WithFeatures()
}
features := featuremgmt.WithFeatures()
srv, _, _, _ := createConvertPrometheusSrv(t, withFeatureToggles(features))
srv.cfg.RecordingRules.Enabled = tc.recordingRules
@@ -1317,7 +1297,6 @@ func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOption
provenanceStore: fakes.NewFakeProvisioningStore(),
fakeAccessControlRuleService: &acfakes.FakeRuleService{},
quotaChecker: quotas,
featureToggles: featuremgmt.WithFeatures(featuremgmt.FlagGrafanaManagedRecordingRulesDatasources),
}
for _, opt := range opts {
+11 -16
View File
@@ -317,7 +317,7 @@ func (ng *AlertNG) init() error {
evalFactory := eval.NewEvaluatorFactory(ng.Cfg.UnifiedAlerting, ng.DataSourceCache, ng.ExpressionService)
conditionValidator := eval.NewConditionValidator(ng.DataSourceCache, ng.ExpressionService, ng.pluginsStore)
recordingWriter, err := createRecordingWriter(ng.FeatureToggles, ng.Cfg.UnifiedAlerting.RecordingRules, ng.httpClientProvider, ng.DataSourceService, clk, ng.Metrics.GetRemoteWriterMetrics())
recordingWriter, err := createRecordingWriter(ng.Cfg.UnifiedAlerting.RecordingRules, ng.httpClientProvider, ng.DataSourceService, clk, ng.Metrics.GetRemoteWriterMetrics())
if err != nil {
return fmt.Errorf("failed to initialize recording writer: %w", err)
}
@@ -649,25 +649,20 @@ func createRemoteAlertmanager(ctx context.Context, cfg remote.AlertmanagerConfig
return remote.NewAlertmanager(ctx, cfg, notifier.NewFileStore(cfg.OrgID, kvstore), decryptFn, autogenFn, m, tracer)
}
func createRecordingWriter(featureToggles featuremgmt.FeatureToggles, settings setting.RecordingRuleSettings, httpClientProvider httpclient.Provider, datasourceService datasources.DataSourceService, clock clock.Clock, m *metrics.RemoteWriter) (schedule.RecordingWriter, error) {
func createRecordingWriter(settings setting.RecordingRuleSettings, httpClientProvider httpclient.Provider, datasourceService datasources.DataSourceService, clock clock.Clock, m *metrics.RemoteWriter) (schedule.RecordingWriter, error) {
logger := log.New("ngalert.writer")
if settings.Enabled {
if featureToggles.IsEnabledGlobally(featuremgmt.FlagGrafanaManagedRecordingRulesDatasources) {
cfg := writer.DatasourceWriterConfig{
Timeout: settings.Timeout,
CustomHeaders: settings.CustomHeaders,
DefaultDatasourceUID: settings.DefaultDatasourceUID,
}
logger.Info("Setting up remote write using data sources",
"timeout", cfg.Timeout, "default_datasource_uid", cfg.DefaultDatasourceUID)
return writer.NewDatasourceWriter(cfg, datasourceService, httpClientProvider, clock, logger, m), nil
} else {
logger.Info("Setting up remote write using static configuration")
return writer.NewPrometheusWriterWithSettings(settings, httpClientProvider, clock, logger, m)
cfg := writer.DatasourceWriterConfig{
Timeout: settings.Timeout,
CustomHeaders: settings.CustomHeaders,
DefaultDatasourceUID: settings.DefaultDatasourceUID,
}
logger.Info("Setting up remote write using data sources",
"timeout", cfg.Timeout, "default_datasource_uid", cfg.DefaultDatasourceUID)
return writer.NewDatasourceWriter(cfg, datasourceService, httpClientProvider, clock, logger, m), nil
}
return writer.NoopWriter{}, nil
@@ -177,13 +177,6 @@ func blankRecordingRuleForTests(ctx context.Context) *recordingRule {
}
func TestRecordingRule_Integration(t *testing.T) {
t.Run("with prometheus writer", func(t *testing.T) {
writeTarget := writer.NewTestRemoteWriteTarget(t)
defer writeTarget.Close()
writerReg := prometheus.NewPedanticRegistry()
writer := setupPrometheusWriter(t, writeTarget, writerReg)
testRecordingRule_Integration(t, writeTarget, writer, writerReg, "")
})
t.Run("with datasource writer", func(t *testing.T) {
writeTarget := writer.NewTestRemoteWriteTarget(t)
defer writeTarget.Close()
@@ -798,14 +791,6 @@ func withQueryForHealth(health string) models.AlertRuleMutator {
}
}
func setupPrometheusWriter(t *testing.T, target *writer.TestRemoteWriteTarget, reg prometheus.Registerer) *writer.PrometheusWriter {
provider := testClientProvider{}
m := metrics.NewNGAlert(reg)
wr, err := writer.NewPrometheusWriterWithSettings(target.ClientSettings(), provider, clock.NewMock(), log.NewNopLogger(), m.GetRemoteWriterMetrics())
require.NoError(t, err)
return wr
}
func setupDatasourceWriter(t *testing.T, target *writer.TestRemoteWriteTarget, reg prometheus.Registerer, dsUID string) *writer.DatasourceWriter {
provider := testClientProvider{}
m := metrics.NewNGAlert(reg)
-58
View File
@@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
@@ -16,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/data"
@@ -250,34 +248,6 @@ type PrometheusWriterConfig struct {
Timeout time.Duration
}
func NewPrometheusWriterWithSettings(
settings setting.RecordingRuleSettings,
httpClientProvider HttpClientProvider,
clock clock.Clock,
l log.Logger,
metrics *metrics.RemoteWriter,
) (*PrometheusWriter, error) {
if err := validateSettings(settings); err != nil {
return nil, err
}
headers := make(http.Header)
for k, v := range settings.CustomHeaders {
headers.Add(k, v)
}
cfg := PrometheusWriterConfig{
URL: settings.URL,
HTTPOptions: httpclient.Options{
BasicAuth: createAuthOpts(settings.BasicAuthUsername, settings.BasicAuthPassword),
Header: headers,
},
Timeout: settings.Timeout,
}
return NewPrometheusWriter(cfg, httpClientProvider, clock, l, metrics)
}
func NewPrometheusWriter(
cfg PrometheusWriterConfig,
httpClientProvider HttpClientProvider,
@@ -310,34 +280,6 @@ func NewPrometheusWriter(
}, nil
}
func validateSettings(settings setting.RecordingRuleSettings) error {
if settings.BasicAuthUsername != "" && settings.BasicAuthPassword == "" {
return fmt.Errorf("basic auth password is required if username is set")
}
if _, err := url.Parse(settings.URL); err != nil {
return fmt.Errorf("invalid URL: %w", err)
}
if settings.Timeout <= 0 {
return fmt.Errorf("timeout must be greater than 0")
}
return nil
}
func createAuthOpts(username, password string) *httpclient.BasicAuthOptions {
// If username is empty, do not use basic auth and ignore password.
if username == "" {
return nil
}
return &httpclient.BasicAuthOptions{
User: username,
Password: password,
}
}
// Write writes the given frames to the Prometheus remote write endpoint.
func (w PrometheusWriter) WriteDatasource(ctx context.Context, dsUID string, name string, t time.Time, frames data.Frames, orgID int64, extraLabels map[string]string) error {
l := w.logger.FromContext(ctx)
-62
View File
@@ -19,74 +19,12 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
)
func TestValidateSettings(t *testing.T) {
for _, tc := range []struct {
name string
settings setting.RecordingRuleSettings
err bool
}{
{
name: "invalid url",
settings: setting.RecordingRuleSettings{
URL: "invalid url",
},
err: true,
},
{
name: "missing password",
settings: setting.RecordingRuleSettings{
URL: "http://localhost:9090",
BasicAuthUsername: "user",
},
err: true,
},
{
name: "timeout is 0",
settings: setting.RecordingRuleSettings{
URL: "http://localhost:9090",
BasicAuthUsername: "user",
BasicAuthPassword: "password",
Timeout: 0,
},
err: true,
},
{
name: "valid settings w/ auth",
settings: setting.RecordingRuleSettings{
URL: "http://localhost:9090",
BasicAuthUsername: "user",
BasicAuthPassword: "password",
Timeout: 10,
},
err: false,
},
{
name: "valid settings w/o auth",
settings: setting.RecordingRuleSettings{
URL: "http://localhost:9090",
Timeout: 10,
},
err: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
err := validateSettings(tc.settings)
if tc.err {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestPointsFromFrames(t *testing.T) {
extraLabels := map[string]string{"extra": "label"}
+1 -4
View File
@@ -71,10 +71,7 @@ func (s *TestRemoteWriteTarget) DatasourceURL() string {
func (s *TestRemoteWriteTarget) ClientSettings() setting.RecordingRuleSettings {
return setting.RecordingRuleSettings{
URL: s.srv.URL + RemoteWriteEndpoint,
Timeout: 1 * time.Second,
BasicAuthUsername: "",
BasicAuthPassword: "",
Timeout: 1 * time.Second,
}
}
+1 -7
View File
@@ -138,9 +138,6 @@ type UnifiedAlertingSettings struct {
type RecordingRuleSettings struct {
Enabled bool
URL string
BasicAuthUsername string
BasicAuthPassword string
CustomHeaders map[string]string
Timeout time.Duration
DefaultDatasourceUID string
@@ -455,10 +452,7 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error {
rr := iniFile.Section("recording_rules")
uaCfgRecordingRules := RecordingRuleSettings{
Enabled: rr.Key("enabled").MustBool(false),
URL: rr.Key("url").MustString(""),
BasicAuthUsername: rr.Key("basic_auth_username").MustString(""),
BasicAuthPassword: rr.Key("basic_auth_password").MustString(""),
Enabled: rr.Key("enabled").MustBool(true),
Timeout: rr.Key("timeout").MustDuration(defaultRecordingRequestTimeout),
DefaultDatasourceUID: rr.Key("default_datasource_uid").MustString(""),
}
@@ -28,7 +28,6 @@ func TestIntegrationConvertPrometheusNotificationSettings(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -113,7 +113,6 @@ func TestIntegrationConvertPrometheusEndpoints_RecordingRuleTargetDatasource(t *
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -181,7 +180,6 @@ func TestIntegrationConvertPrometheusEndpoints(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -387,7 +385,6 @@ func TestIntegrationConvertPrometheusEndpoints_UpdateRule(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -468,7 +465,6 @@ func TestIntegrationConvertPrometheusEndpoints_Conflict(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -550,7 +546,6 @@ func TestIntegrationConvertPrometheusEndpoints_CreatePausedRules(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -658,7 +653,6 @@ func TestIntegrationConvertPrometheusEndpoints_FolderUIDHeader(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -755,7 +749,6 @@ func TestIntegrationConvertPrometheusEndpoints_Provenance(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -865,7 +858,6 @@ func TestIntegrationConvertPrometheusEndpoints_Delete(t *testing.T) {
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources"},
EnableRecordingRules: true,
})
@@ -76,6 +76,12 @@ describe('RuleEditor grafana recording rules', () => {
await user.type(await ui.inputs.name.find(), 'my great new rule');
await user.type(await ui.inputs.metric.find(), 'metricName');
const targetDsField = await ui.inputs.targetDatasource.find();
const dsPickerInput = await ui.inputs.dataSource.find(targetDsField);
await user.click(dsPickerInput);
await user.click(await screen.findByText('Prom'));
await selectFolderAndGroup(user);
await user.click(ui.buttons.save.get());
@@ -94,6 +100,12 @@ describe('RuleEditor grafana recording rules', () => {
const { user } = renderRuleEditor(undefined, 'grafana-recording');
await user.type(await ui.inputs.name.find(), 'my great new rule');
const targetDsField = await ui.inputs.targetDatasource.find();
const dsPickerInput = await ui.inputs.dataSource.find(targetDsField);
await user.click(dsPickerInput);
await user.click(await screen.findByText('Prom'));
await selectFolderAndGroup(user);
await user.click(ui.buttons.save.get());
@@ -106,6 +106,7 @@ exports[`RuleEditor grafana recording rules can create new grafana recording rul
"record": {
"from": "B",
"metric": "metricName",
"target_datasource_uid": "prometheus",
},
"title": "my great new rule",
},
@@ -235,6 +236,7 @@ exports[`RuleEditor grafana recording rules can create new grafana recording rul
"record": {
"from": "B",
"metric": "metricName",
"target_datasource_uid": "prometheus",
},
"title": "my great new rule",
},
@@ -3,7 +3,6 @@ import { Controller, useFormContext } from 'react-hook-form';
import { DataSourceInstanceSettings } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, useTranslate } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Field, Input, Stack, Text } from '@grafana/ui';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
@@ -110,9 +109,10 @@ export const AlertRuleNameAndMetric = () => {
</Field>
)}
{isGrafanaRecordingRule && config.featureToggles.grafanaManagedRecordingRulesDatasources && (
{isGrafanaRecordingRule && (
<Field
id="target-data-source"
data-testid="target-data-source"
label={t('alerting.recording-rules.label-target-data-source', 'Target data source')}
description={t(
'alerting.recording-rules.description-target-data-source',
@@ -4,7 +4,6 @@ import { isUndefined } from 'lodash';
import { GrafanaTheme2, dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data';
import { Trans, useTranslate } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Icon, Link, Stack, Text, TextLink, useStyles2 } from '@grafana/ui';
import { useDatasource } from 'app/features/datasources/hooks';
import { CombinedRule } from 'app/types/unified-alerting';
@@ -64,10 +63,7 @@ export const Details = ({ rule }: DetailsProps) => {
const datasource = useDatasource(targetDatasourceUid);
const showTargetDatasource =
config.featureToggles.grafanaManagedRecordingRulesDatasources &&
targetDatasourceUid &&
targetDatasourceUid !== 'grafana';
const showTargetDatasource = targetDatasourceUid && targetDatasourceUid !== 'grafana';
const evaluationDuration = rule.promRule?.evaluationTime;
const evaluationTimestamp = rule.promRule?.lastEvaluation;
@@ -17,6 +17,7 @@ export const ui = {
inputs: {
name: byRole('textbox', { name: 'name' }),
metric: byRole('textbox', { name: 'metric' }),
targetDatasource: byTestId('target-data-source'),
alertType: byTestId('alert-type-picker'),
dataSource: byTestId(selectors.components.DataSourcePicker.inputV2),
folder: byTestId('folder-picker'),