Alerting: Remove ngalert feature toggle and introduce two new settings for enabling Grafana 8 alerts and disabling them for specific organisations (#38746)
* Remove `ngalert` feature toggle * Update frontend Remove all references of ngalert feature toggle * Update docs * Disable unified alerting for specific orgs * Add backend tests * Apply suggestions from code review Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Disabled unified alerting by default * Ensure backward compatibility with old ngalert feature toggle * Apply suggestions from code review Co-authored-by: gotjosh <josue@grafana.com>
This commit is contained in:
co-authored by
achatterjee-grafana
gotjosh
parent
2dedbcd3c3
commit
012d4f0905
+17
-11
@@ -427,11 +427,6 @@ func (cfg Cfg) IsLiveConfigEnabled() bool {
|
||||
return cfg.FeatureToggles["live-config"]
|
||||
}
|
||||
|
||||
// IsNgAlertEnabled returns whether the standalone alerts feature is enabled.
|
||||
func (cfg Cfg) IsNgAlertEnabled() bool {
|
||||
return cfg.FeatureToggles["ngalert"]
|
||||
}
|
||||
|
||||
// IsTrimDefaultsEnabled returns whether the standalone trim dashboard default feature is enabled.
|
||||
func (cfg Cfg) IsTrimDefaultsEnabled() bool {
|
||||
return cfg.FeatureToggles["trimDefaults"]
|
||||
@@ -938,12 +933,12 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
|
||||
cfg.PluginAdminEnabled = pluginsSection.Key("plugin_admin_enabled").MustBool(false)
|
||||
cfg.PluginAdminExternalManageEnabled = pluginsSection.Key("plugin_admin_external_manage_enabled").MustBool(false)
|
||||
|
||||
// Read and populate feature toggles list
|
||||
featureTogglesSection := iniFile.Section("feature_toggles")
|
||||
cfg.FeatureToggles = make(map[string]bool)
|
||||
featuresTogglesStr := valueAsString(featureTogglesSection, "enable", "")
|
||||
for _, feature := range util.SplitString(featuresTogglesStr) {
|
||||
cfg.FeatureToggles[feature] = true
|
||||
if err := cfg.readFeatureToggles(iniFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cfg.ReadUnifiedAlertingSettings(iniFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check old location for this option
|
||||
@@ -1372,6 +1367,17 @@ func (cfg *Cfg) readRenderingSettings(iniFile *ini.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readFeatureToggles(iniFile *ini.File) error {
|
||||
// Read and populate feature toggles list
|
||||
featureTogglesSection := iniFile.Section("feature_toggles")
|
||||
cfg.FeatureToggles = make(map[string]bool)
|
||||
featuresTogglesStr := valueAsString(featureTogglesSection, "enable", "")
|
||||
for _, feature := range util.SplitString(featuresTogglesStr) {
|
||||
cfg.FeatureToggles[feature] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAlertingSettings(iniFile *ini.File) error {
|
||||
alerting := iniFile.Section("alerting")
|
||||
AlertingEnabled = alerting.Key("enabled").MustBool(true)
|
||||
|
||||
@@ -68,7 +68,7 @@ func (cfg *Cfg) readQuotaSettings() {
|
||||
|
||||
var alertOrgQuota int64
|
||||
var alertGlobalQuota int64
|
||||
if cfg.IsNgAlertEnabled() {
|
||||
if cfg.UnifiedAlerting.Enabled {
|
||||
alertOrgQuota = quota.Key("org_alert_rule").MustInt64(100)
|
||||
alertGlobalQuota = quota.Key("global_alert_rule").MustInt64(-1)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -427,3 +428,253 @@ func TestGetCDNPathWithAlphaVersion(t *testing.T) {
|
||||
require.Equal(t, "http://cdn.grafana.com/grafana-oss/v7.5.0-alpha.11124/", cfg.GetContentDeliveryURL("grafana-oss"))
|
||||
require.Equal(t, "http://cdn.grafana.com/grafana/v7.5.0-alpha.11124/", cfg.GetContentDeliveryURL("grafana"))
|
||||
}
|
||||
|
||||
func TestAlertingEnabled(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
unifiedAlertingEnabled string
|
||||
legacyAlertingEnabled string
|
||||
featureToggleSet bool
|
||||
verifyCfg func(*testing.T, Cfg, *ini.File)
|
||||
}{
|
||||
{
|
||||
desc: "when legacy alerting is enabled and unified is disabled",
|
||||
legacyAlertingEnabled: "true",
|
||||
unifiedAlertingEnabled: "false",
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, false)
|
||||
assert.Equal(t, AlertingEnabled, true)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is disabled and unified is enabled",
|
||||
legacyAlertingEnabled: "false",
|
||||
unifiedAlertingEnabled: "true",
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, true)
|
||||
assert.Equal(t, AlertingEnabled, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when both alerting are enabled, it should error",
|
||||
legacyAlertingEnabled: "true",
|
||||
unifiedAlertingEnabled: "true",
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is invalid and unified is disabled",
|
||||
legacyAlertingEnabled: "invalid",
|
||||
unifiedAlertingEnabled: "false",
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, false)
|
||||
assert.Equal(t, AlertingEnabled, true)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is invalid and unified is enabled",
|
||||
legacyAlertingEnabled: "invalid",
|
||||
unifiedAlertingEnabled: "true",
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is enabled and unified is invalid",
|
||||
legacyAlertingEnabled: "true",
|
||||
unifiedAlertingEnabled: "invalid",
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, false)
|
||||
assert.Equal(t, AlertingEnabled, true)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is disabled and unified is invalid",
|
||||
legacyAlertingEnabled: "false",
|
||||
unifiedAlertingEnabled: "invalid",
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, false)
|
||||
assert.Equal(t, AlertingEnabled, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when both are invalid",
|
||||
legacyAlertingEnabled: "invalid",
|
||||
unifiedAlertingEnabled: "invalid",
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, false)
|
||||
assert.Equal(t, AlertingEnabled, true)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is enabled and unified is disabled and feature toggle is set",
|
||||
legacyAlertingEnabled: "true",
|
||||
unifiedAlertingEnabled: "false",
|
||||
featureToggleSet: true,
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, true)
|
||||
assert.Equal(t, AlertingEnabled, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is disabled and unified is disabled and feature toggle is set",
|
||||
legacyAlertingEnabled: "false",
|
||||
unifiedAlertingEnabled: "false",
|
||||
featureToggleSet: true,
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, true)
|
||||
assert.Equal(t, AlertingEnabled, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is disabled and unified is invalid and feature toggle is set",
|
||||
legacyAlertingEnabled: "false",
|
||||
unifiedAlertingEnabled: "invalid",
|
||||
featureToggleSet: true,
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, true)
|
||||
assert.Equal(t, AlertingEnabled, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is invalid and unified is disabled and feature toggle is set",
|
||||
legacyAlertingEnabled: "invalid",
|
||||
unifiedAlertingEnabled: "false",
|
||||
featureToggleSet: true,
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, true)
|
||||
assert.Equal(t, AlertingEnabled, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when legacy alerting is invalid and unified is enabled and feature toggle is set",
|
||||
legacyAlertingEnabled: "invalid",
|
||||
unifiedAlertingEnabled: "true",
|
||||
featureToggleSet: true,
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "when both are invalid and feature toggle is set",
|
||||
legacyAlertingEnabled: "invalid",
|
||||
unifiedAlertingEnabled: "invalid",
|
||||
featureToggleSet: true,
|
||||
verifyCfg: func(t *testing.T, cfg Cfg, f *ini.File) {
|
||||
err := readAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.readFeatureToggles(f)
|
||||
require.NoError(t, err)
|
||||
err = cfg.ReadUnifiedAlertingSettings(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.UnifiedAlerting.Enabled, true)
|
||||
assert.Equal(t, AlertingEnabled, false)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
AlertingEnabled = false
|
||||
})
|
||||
|
||||
f := ini.Empty()
|
||||
cfg := NewCfg()
|
||||
unifiedAlertingSec, err := f.NewSection("unified_alerting")
|
||||
require.NoError(t, err)
|
||||
_, err = unifiedAlertingSec.NewKey("enabled", tc.unifiedAlertingEnabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
alertingSec, err := f.NewSection("alerting")
|
||||
require.NoError(t, err)
|
||||
_, err = alertingSec.NewKey("enabled", tc.legacyAlertingEnabled)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.featureToggleSet {
|
||||
alertingSec, err := f.NewSection("feature_toggles")
|
||||
require.NoError(t, err)
|
||||
_, err = alertingSec.NewKey("enable", "ngalert")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
tc.verifyCfg(t, *cfg, f)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/prometheus/alertmanager/cluster"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -60,6 +63,8 @@ type UnifiedAlertingSettings struct {
|
||||
EvaluationTimeout time.Duration
|
||||
ExecuteAlerts bool
|
||||
DefaultConfiguration string
|
||||
Enabled bool
|
||||
DisabledOrgs map[int64]struct{}
|
||||
}
|
||||
|
||||
// ReadUnifiedAlertingSettings reads both the `unified_alerting` and `alerting` sections of the configuration while preferring configuration the `alerting` section.
|
||||
@@ -67,6 +72,29 @@ type UnifiedAlertingSettings struct {
|
||||
func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error {
|
||||
uaCfg := UnifiedAlertingSettings{}
|
||||
ua := iniFile.Section("unified_alerting")
|
||||
uaCfg.Enabled = ua.Key("enabled").MustBool(false)
|
||||
|
||||
// TODO: Deprecate this in v8.4, if the old feature toggle ngalert is set, enable Grafana 8 Unified Alerting anyway.
|
||||
if !uaCfg.Enabled && cfg.FeatureToggles["ngalert"] {
|
||||
cfg.Logger.Warn("ngalert feature flag is deprecated: use unified alerting enabled setting instead")
|
||||
uaCfg.Enabled = true
|
||||
AlertingEnabled = false
|
||||
}
|
||||
|
||||
if uaCfg.Enabled && AlertingEnabled {
|
||||
return errors.New("both legacy and Grafana 8 Alerts are enabled")
|
||||
}
|
||||
|
||||
uaCfg.DisabledOrgs = make(map[int64]struct{})
|
||||
orgsStr := valueAsString(ua, "disabled_orgs", "")
|
||||
for _, org := range util.SplitString(orgsStr) {
|
||||
orgID, err := strconv.ParseInt(org, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uaCfg.DisabledOrgs[orgID] = struct{}{}
|
||||
}
|
||||
|
||||
var err error
|
||||
uaCfg.AdminConfigPollInterval, err = gtime.ParseDuration(valueAsString(ua, "admin_config_poll_interval", (schedulerDefaultAdminConfigPollInterval).String()))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user