mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
committed by
GitHub
parent
2dedbcd3c3
commit
012d4f0905
@@ -387,7 +387,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
})
|
||||
|
||||
apiRoute.Get("/alert-notifiers", reqEditorRole, routing.Wrap(
|
||||
GetAlertNotifiers(hs.Cfg.IsNgAlertEnabled())),
|
||||
GetAlertNotifiers(hs.Cfg.UnifiedAlerting.Enabled)),
|
||||
)
|
||||
|
||||
apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
|
||||
|
||||
@@ -268,6 +268,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
||||
"caching": map[string]bool{
|
||||
"enabled": hs.Cfg.SectionWithEnvOverrides("caching").Key("enabled").MustBool(true),
|
||||
},
|
||||
"unifiedAlertingEnabled": hs.Cfg.UnifiedAlerting.Enabled,
|
||||
}
|
||||
|
||||
if hs.Cfg.GeomapDefaultBaseLayerConfig != nil {
|
||||
|
||||
@@ -205,16 +205,16 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
||||
navTree = append(navTree, hs.getProfileNode(c))
|
||||
}
|
||||
|
||||
if setting.AlertingEnabled {
|
||||
if setting.AlertingEnabled || hs.Cfg.UnifiedAlerting.Enabled {
|
||||
alertChildNavs := []*dtos.NavLink{
|
||||
{Text: "Alert rules", Id: "alert-list", Url: hs.Cfg.AppSubURL + "/alerting/list", Icon: "list-ul"},
|
||||
}
|
||||
if hs.Cfg.IsNgAlertEnabled() {
|
||||
if hs.Cfg.UnifiedAlerting.Enabled {
|
||||
alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Alert groups", Id: "groups", Url: hs.Cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"})
|
||||
alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Silences", Id: "silences", Url: hs.Cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"})
|
||||
}
|
||||
if c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR {
|
||||
if hs.Cfg.IsNgAlertEnabled() {
|
||||
if hs.Cfg.UnifiedAlerting.Enabled {
|
||||
alertChildNavs = append(alertChildNavs, &dtos.NavLink{
|
||||
Text: "Contact points", Id: "receivers", Url: hs.Cfg.AppSubURL + "/alerting/notifications",
|
||||
Icon: "comment-alt-share",
|
||||
@@ -227,7 +227,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
||||
})
|
||||
}
|
||||
}
|
||||
if c.OrgRole == models.ROLE_ADMIN && hs.Cfg.IsNgAlertEnabled() {
|
||||
if c.OrgRole == models.ROLE_ADMIN && hs.Cfg.UnifiedAlerting.Enabled {
|
||||
alertChildNavs = append(alertChildNavs, &dtos.NavLink{
|
||||
Text: "Admin", Id: "alerting-admin", Url: hs.Cfg.AppSubURL + "/alerting/admin",
|
||||
Icon: "cog",
|
||||
|
||||
@@ -209,7 +209,7 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
cfg.Quota.Enabled = false
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org alert quota reached and ngalert enabled", func(t *testing.T, sc *scenarioContext) {
|
||||
middlewareScenario(t, "org alert quota reached and unified alerting is enabled", func(t *testing.T, sc *scenarioContext) {
|
||||
setUp(sc)
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "alert_rule")
|
||||
@@ -219,11 +219,11 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.FeatureToggles = map[string]bool{"ngalert": true}
|
||||
cfg.UnifiedAlerting.Enabled = true
|
||||
cfg.Quota.Org.AlertRule = quotaUsed
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org alert quota not reached and ngalert enabled", func(t *testing.T, sc *scenarioContext) {
|
||||
middlewareScenario(t, "org alert quota not reached and unified alerting is enabled", func(t *testing.T, sc *scenarioContext) {
|
||||
setUp(sc)
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "alert_rule")
|
||||
@@ -233,7 +233,7 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.FeatureToggles = map[string]bool{"ngalert": true}
|
||||
cfg.UnifiedAlerting.Enabled = true
|
||||
cfg.Quota.Org.AlertRule = quotaUsed + 1
|
||||
})
|
||||
|
||||
|
||||
@@ -44,38 +44,38 @@ type GlobalQuotaDTO struct {
|
||||
}
|
||||
|
||||
type GetOrgQuotaByTargetQuery struct {
|
||||
Target string
|
||||
OrgId int64
|
||||
Default int64
|
||||
IsNgAlertEnabled bool
|
||||
Result *OrgQuotaDTO
|
||||
Target string
|
||||
OrgId int64
|
||||
Default int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result *OrgQuotaDTO
|
||||
}
|
||||
|
||||
type GetOrgQuotasQuery struct {
|
||||
OrgId int64
|
||||
IsNgAlertEnabled bool
|
||||
Result []*OrgQuotaDTO
|
||||
OrgId int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result []*OrgQuotaDTO
|
||||
}
|
||||
|
||||
type GetUserQuotaByTargetQuery struct {
|
||||
Target string
|
||||
UserId int64
|
||||
Default int64
|
||||
IsNgAlertEnabled bool
|
||||
Result *UserQuotaDTO
|
||||
Target string
|
||||
UserId int64
|
||||
Default int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result *UserQuotaDTO
|
||||
}
|
||||
|
||||
type GetUserQuotasQuery struct {
|
||||
UserId int64
|
||||
IsNgAlertEnabled bool
|
||||
Result []*UserQuotaDTO
|
||||
UserId int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result []*UserQuotaDTO
|
||||
}
|
||||
|
||||
type GetGlobalQuotaByTargetQuery struct {
|
||||
Target string
|
||||
Default int64
|
||||
IsNgAlertEnabled bool
|
||||
Result *GlobalQuotaDTO
|
||||
Target string
|
||||
Default int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result *GlobalQuotaDTO
|
||||
}
|
||||
|
||||
type UpdateOrgQuotaCmd struct {
|
||||
|
||||
@@ -42,7 +42,7 @@ type AlertEngine struct {
|
||||
|
||||
// IsDisabled returns true if the alerting service is disable for this instance.
|
||||
func (e *AlertEngine) IsDisabled() bool {
|
||||
return !setting.AlertingEnabled || !setting.ExecuteAlerts || e.Cfg.IsNgAlertEnabled()
|
||||
return !setting.AlertingEnabled || !setting.ExecuteAlerts || e.Cfg.UnifiedAlerting.Enabled
|
||||
}
|
||||
|
||||
// ProvideAlertEngine returns a new AlertEngine.
|
||||
|
||||
28
pkg/services/ngalert/api/test-data/admin_config.http
Normal file
28
pkg/services/ngalert/api/test-data/admin_config.http
Normal file
@@ -0,0 +1,28 @@
|
||||
###
|
||||
# set external Alertmanager
|
||||
POST http://admin:admin@localhost:3000/api/v1/ngalert/admin_config
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"alertmanagers": ["http://localhost:9093"]
|
||||
}
|
||||
|
||||
###
|
||||
GET http://admin:admin@localhost:3000/api/v1/ngalert/admin_config
|
||||
|
||||
###
|
||||
# after a few minutes it should be discovered
|
||||
GET http://admin:admin@localhost:3000/api/v1/ngalert/alertmanagers
|
||||
|
||||
###
|
||||
# remove it
|
||||
POST http://admin:admin@localhost:3000/api/v1/ngalert/admin_config
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"alertmanagers": []
|
||||
}
|
||||
|
||||
###
|
||||
# check again
|
||||
GET http://admin:admin@localhost:3000/api/v1/ngalert/alertmanagers
|
||||
@@ -135,6 +135,7 @@ type GetAlertRuleByUIDQuery struct {
|
||||
type ListAlertRulesQuery struct {
|
||||
OrgID int64
|
||||
NamespaceUIDs []string
|
||||
ExcludeOrgs []int64
|
||||
|
||||
Result []*AlertRule
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ func (ng *AlertNG) init() error {
|
||||
MultiOrgNotifier: ng.MultiOrgAlertmanager,
|
||||
Metrics: ng.Metrics.GetSchedulerMetrics(),
|
||||
AdminConfigPollInterval: ng.Cfg.UnifiedAlerting.AdminConfigPollInterval,
|
||||
DisabledOrgs: ng.Cfg.UnifiedAlerting.DisabledOrgs,
|
||||
MinRuleInterval: ng.getRuleMinInterval(),
|
||||
}
|
||||
stateManager := state.NewManager(ng.Log, ng.Metrics.GetStateMetrics(), store, store)
|
||||
@@ -173,7 +174,7 @@ func (ng *AlertNG) IsDisabled() bool {
|
||||
if ng.Cfg == nil {
|
||||
return true
|
||||
}
|
||||
return !ng.Cfg.IsNgAlertEnabled()
|
||||
return !ng.Cfg.UnifiedAlerting.Enabled
|
||||
}
|
||||
|
||||
// getRuleDefaultIntervalSeconds returns the default rule interval if the interval is not set.
|
||||
|
||||
@@ -149,9 +149,14 @@ func (moa *MultiOrgAlertmanager) SyncAlertmanagersForOrgs(ctx context.Context, o
|
||||
}
|
||||
moa.alertmanagersMtx.Lock()
|
||||
for _, orgID := range orgIDs {
|
||||
if _, isDisabledOrg := moa.settings.UnifiedAlerting.DisabledOrgs[orgID]; isDisabledOrg {
|
||||
moa.logger.Debug("skipping syncing Alertmanger for disabled org", "org", orgID)
|
||||
continue
|
||||
}
|
||||
orgsFound[orgID] = struct{}{}
|
||||
|
||||
alertmanager, found := moa.alertmanagers[orgID]
|
||||
|
||||
if !found {
|
||||
// These metrics are not exported by Grafana and are mostly a placeholder.
|
||||
// To export them, we need to translate the metrics from each individual registry and,
|
||||
|
||||
@@ -32,8 +32,12 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) {
|
||||
reg := prometheus.NewPedanticRegistry()
|
||||
m := metrics.NewNGAlert(reg)
|
||||
cfg := &setting.Cfg{
|
||||
DataPath: tmpDir,
|
||||
UnifiedAlerting: setting.UnifiedAlertingSettings{AlertmanagerConfigPollInterval: 3 * time.Minute, DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration()}, // do not poll in tests.
|
||||
DataPath: tmpDir,
|
||||
UnifiedAlerting: setting.UnifiedAlertingSettings{
|
||||
AlertmanagerConfigPollInterval: 3 * time.Minute,
|
||||
DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(),
|
||||
DisabledOrgs: map[int64]struct{}{5: {}},
|
||||
}, // do not poll in tests.
|
||||
}
|
||||
mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger"))
|
||||
require.NoError(t, err)
|
||||
@@ -82,6 +86,12 @@ grafana_alerting_active_configurations 4
|
||||
grafana_alerting_discovered_configurations 4
|
||||
`), "grafana_alerting_discovered_configurations", "grafana_alerting_active_configurations"))
|
||||
}
|
||||
// if the disabled org comes back, it should not detect it.
|
||||
{
|
||||
orgStore.orgs = []int64{1, 2, 3, 4, 5}
|
||||
require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))
|
||||
require.Len(t, mam.alertmanagers, 4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiOrgAlertmanager_AlertmanagerFor(t *testing.T) {
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
func (sch *schedule) fetchAllDetails() []*models.AlertRule {
|
||||
q := models.ListAlertRulesQuery{}
|
||||
func (sch *schedule) fetchAllDetails(disabledOrgs []int64) []*models.AlertRule {
|
||||
q := models.ListAlertRulesQuery{
|
||||
ExcludeOrgs: disabledOrgs,
|
||||
}
|
||||
err := sch.ruleStore.GetAlertRulesForScheduling(&q)
|
||||
if err != nil {
|
||||
sch.log.Error("failed to fetch alert definitions", "err", err)
|
||||
|
||||
@@ -84,6 +84,7 @@ type schedule struct {
|
||||
sendersCfgHash map[int64]string
|
||||
senders map[int64]*sender.Sender
|
||||
adminConfigPollInterval time.Duration
|
||||
disabledOrgs map[int64]struct{}
|
||||
minRuleInterval time.Duration
|
||||
}
|
||||
|
||||
@@ -103,6 +104,7 @@ type SchedulerCfg struct {
|
||||
MultiOrgNotifier *notifier.MultiOrgAlertmanager
|
||||
Metrics *metrics.Scheduler
|
||||
AdminConfigPollInterval time.Duration
|
||||
DisabledOrgs map[int64]struct{}
|
||||
MinRuleInterval time.Duration
|
||||
}
|
||||
|
||||
@@ -132,6 +134,7 @@ func NewScheduler(cfg SchedulerCfg, dataService *tsdb.Service, appURL string, st
|
||||
senders: map[int64]*sender.Sender{},
|
||||
sendersCfgHash: map[int64]string{},
|
||||
adminConfigPollInterval: cfg.AdminConfigPollInterval,
|
||||
disabledOrgs: cfg.DisabledOrgs,
|
||||
minRuleInterval: cfg.MinRuleInterval,
|
||||
}
|
||||
return &sch
|
||||
@@ -190,6 +193,12 @@ func (sch *schedule) SyncAndApplyConfigFromDatabase() error {
|
||||
orgsFound := make(map[int64]struct{}, len(cfgs))
|
||||
sch.sendersMtx.Lock()
|
||||
for _, cfg := range cfgs {
|
||||
_, isDisabledOrg := sch.disabledOrgs[cfg.OrgID]
|
||||
if isDisabledOrg {
|
||||
sch.log.Debug("skipping starting sender for disabled org", "org", cfg.OrgID)
|
||||
continue
|
||||
}
|
||||
|
||||
orgsFound[cfg.OrgID] = struct{}{} // keep track of the which senders we need to keep.
|
||||
|
||||
existing, ok := sch.senders[cfg.OrgID]
|
||||
@@ -318,8 +327,12 @@ func (sch *schedule) ruleEvaluationLoop(ctx context.Context) error {
|
||||
select {
|
||||
case tick := <-sch.heartbeat.C:
|
||||
tickNum := tick.Unix() / int64(sch.baseInterval.Seconds())
|
||||
alertRules := sch.fetchAllDetails()
|
||||
sch.log.Debug("alert rules fetched", "count", len(alertRules))
|
||||
disabledOrgs := make([]int64, 0, len(sch.disabledOrgs))
|
||||
for disabledOrg := range sch.disabledOrgs {
|
||||
disabledOrgs = append(disabledOrgs, disabledOrg)
|
||||
}
|
||||
alertRules := sch.fetchAllDetails(disabledOrgs)
|
||||
sch.log.Debug("alert rules fetched", "count", len(alertRules), "disabled_orgs", disabledOrgs)
|
||||
|
||||
// registeredDefinitions is a map used for finding deleted alert rules
|
||||
// initially it is assigned to all known alert rules from the previous cycle
|
||||
|
||||
@@ -37,7 +37,8 @@ func TestWarmStateCache(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
_, dbstore := tests.SetupTestEnv(t, 1)
|
||||
|
||||
rule := tests.CreateTestAlertRule(t, dbstore, 600)
|
||||
const mainOrgID int64 = 1
|
||||
rule := tests.CreateTestAlertRule(t, dbstore, 600, mainOrgID)
|
||||
|
||||
expectedEntries := []*state.State{
|
||||
{
|
||||
@@ -123,8 +124,11 @@ func TestAlertingTicker(t *testing.T) {
|
||||
|
||||
alerts := make([]*models.AlertRule, 0)
|
||||
|
||||
// create alert rule with one second interval
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, 1))
|
||||
const mainOrgID int64 = 1
|
||||
// create alert rule under main org with one second interval
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, 1, mainOrgID))
|
||||
|
||||
const disabledOrgID int64 = 3
|
||||
|
||||
evalAppliedCh := make(chan evalAppliedInfo, len(alerts))
|
||||
stopAppliedCh := make(chan models.AlertRuleKey, len(alerts))
|
||||
@@ -146,6 +150,9 @@ func TestAlertingTicker(t *testing.T) {
|
||||
Logger: log.New("ngalert schedule test"),
|
||||
Metrics: testMetrics.GetSchedulerMetrics(),
|
||||
AdminConfigPollInterval: 10 * time.Minute, // do not poll in unit tests.
|
||||
DisabledOrgs: map[int64]struct{}{
|
||||
disabledOrgID: {},
|
||||
},
|
||||
}
|
||||
st := state.NewManager(schedCfg.Logger, testMetrics.GetStateMetrics(), dbstore, dbstore)
|
||||
sched := schedule.NewScheduler(schedCfg, nil, "http://localhost", st)
|
||||
@@ -164,9 +171,9 @@ func TestAlertingTicker(t *testing.T) {
|
||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
|
||||
})
|
||||
|
||||
// change alert rule interval to three seconds
|
||||
// add alert rule under main org with three seconds interval
|
||||
var threeSecInterval int64 = 3
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, threeSecInterval))
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, threeSecInterval, mainOrgID))
|
||||
t.Logf("alert rule: %v added with interval: %d", alerts[1].GetKey(), threeSecInterval)
|
||||
|
||||
expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[0].GetKey()}
|
||||
@@ -187,9 +194,10 @@ func TestAlertingTicker(t *testing.T) {
|
||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
|
||||
})
|
||||
|
||||
key := alerts[0].GetKey()
|
||||
err := dbstore.DeleteAlertRuleByUID(alerts[0].OrgID, alerts[0].UID)
|
||||
require.NoError(t, err)
|
||||
t.Logf("alert rule: %v deleted", alerts[1].GetKey())
|
||||
t.Logf("alert rule: %v deleted", key)
|
||||
|
||||
expectedAlertRulesEvaluated = []models.AlertRuleKey{}
|
||||
t.Run(fmt.Sprintf("on 5th tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
|
||||
@@ -208,13 +216,22 @@ func TestAlertingTicker(t *testing.T) {
|
||||
})
|
||||
|
||||
// create alert rule with one second interval
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, 1))
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, 1, mainOrgID))
|
||||
|
||||
expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[2].GetKey()}
|
||||
t.Run(fmt.Sprintf("on 7th tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
|
||||
tick := advanceClock(t, mockedClock)
|
||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
|
||||
})
|
||||
|
||||
// create alert rule with one second interval under disabled org
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, 1, disabledOrgID))
|
||||
|
||||
expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[2].GetKey()}
|
||||
t.Run(fmt.Sprintf("on 8th tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
|
||||
tick := advanceClock(t, mockedClock)
|
||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
|
||||
})
|
||||
}
|
||||
|
||||
func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys ...models.AlertRuleKey) {
|
||||
@@ -229,13 +246,12 @@ func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys
|
||||
select {
|
||||
case info := <-ch:
|
||||
_, ok := expected[info.alertDefKey]
|
||||
if !ok {
|
||||
t.Fatal(fmt.Sprintf("alert rule: %v should not have been evaluated at: %v", info.alertDefKey, info.now))
|
||||
}
|
||||
t.Logf("alert rule: %v evaluated at: %v", info.alertDefKey, info.now)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, tick, info.now)
|
||||
delete(expected, info.alertDefKey)
|
||||
if len(expected) == 0 {
|
||||
return
|
||||
}
|
||||
case <-timeout:
|
||||
if len(expected) == 0 {
|
||||
return
|
||||
|
||||
@@ -873,7 +873,8 @@ func TestStaleResultsHandler(t *testing.T) {
|
||||
|
||||
_, dbstore := tests.SetupTestEnv(t, 1)
|
||||
|
||||
rule := tests.CreateTestAlertRule(t, dbstore, 600)
|
||||
const mainOrgID int64 = 1
|
||||
rule := tests.CreateTestAlertRule(t, dbstore, 600, mainOrgID)
|
||||
|
||||
saveCmd1 := &models.SaveAlertInstanceCommand{
|
||||
RuleOrgID: rule.OrgID,
|
||||
|
||||
@@ -422,10 +422,12 @@ func (st DBstore) GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
alerts := make([]*ngmodels.AlertRule, 0)
|
||||
q := "SELECT uid, org_id, interval_seconds, version FROM alert_rule"
|
||||
if len(query.ExcludeOrgs) > 0 {
|
||||
q = fmt.Sprintf("%s WHERE org_id NOT IN (%s)", q, strings.Join(strings.Split(strings.Trim(fmt.Sprint(query.ExcludeOrgs), "[]"), " "), ","))
|
||||
}
|
||||
if err := sess.SQL(q).Find(&alerts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = alerts
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -28,16 +28,18 @@ func mockTimeNow() {
|
||||
func TestAlertInstanceOperations(t *testing.T) {
|
||||
_, dbstore := tests.SetupTestEnv(t, baseIntervalSeconds)
|
||||
|
||||
alertRule1 := tests.CreateTestAlertRule(t, dbstore, 60)
|
||||
const mainOrgID int64 = 1
|
||||
|
||||
alertRule1 := tests.CreateTestAlertRule(t, dbstore, 60, mainOrgID)
|
||||
orgID := alertRule1.OrgID
|
||||
|
||||
alertRule2 := tests.CreateTestAlertRule(t, dbstore, 60)
|
||||
alertRule2 := tests.CreateTestAlertRule(t, dbstore, 60, mainOrgID)
|
||||
require.Equal(t, orgID, alertRule2.OrgID)
|
||||
|
||||
alertRule3 := tests.CreateTestAlertRule(t, dbstore, 60)
|
||||
alertRule3 := tests.CreateTestAlertRule(t, dbstore, 60, mainOrgID)
|
||||
require.Equal(t, orgID, alertRule3.OrgID)
|
||||
|
||||
alertRule4 := tests.CreateTestAlertRule(t, dbstore, 60)
|
||||
alertRule4 := tests.CreateTestAlertRule(t, dbstore, 60, mainOrgID)
|
||||
require.Equal(t, orgID, alertRule4.OrgID)
|
||||
|
||||
t.Run("can save and read new alert instance", func(t *testing.T) {
|
||||
|
||||
@@ -30,9 +30,8 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.AlertingBaseInterval = baseInterval
|
||||
// AlertNG is disabled by default and only if it's enabled
|
||||
// its database migrations run and the relative database tables are created
|
||||
cfg.FeatureToggles = map[string]bool{"ngalert": true}
|
||||
// AlertNG database migrations run and the relative database tables are created only when it's enabled
|
||||
cfg.UnifiedAlerting.Enabled = true
|
||||
|
||||
m := metrics.NewNGAlert(prometheus.NewRegistry())
|
||||
ng, err := ngalert.ProvideService(cfg, nil, routing.NewRouteRegister(), sqlstore.InitTestDB(t), nil, nil, nil, nil, m)
|
||||
@@ -45,11 +44,11 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *
|
||||
}
|
||||
|
||||
// CreateTestAlertRule creates a dummy alert definition to be used by the tests.
|
||||
func CreateTestAlertRule(t *testing.T, dbstore *store.DBstore, intervalSeconds int64) *models.AlertRule {
|
||||
func CreateTestAlertRule(t *testing.T, dbstore *store.DBstore, intervalSeconds int64, orgID int64) *models.AlertRule {
|
||||
d := rand.Intn(1000)
|
||||
ruleGroup := fmt.Sprintf("ruleGroup-%d", d)
|
||||
err := dbstore.UpdateRuleGroup(store.UpdateRuleGroupCmd{
|
||||
OrgID: 1,
|
||||
OrgID: orgID,
|
||||
NamespaceUID: "namespace",
|
||||
RuleGroupConfig: apimodels.PostableRuleGroupConfig{
|
||||
Name: ruleGroup,
|
||||
@@ -84,7 +83,7 @@ func CreateTestAlertRule(t *testing.T, dbstore *store.DBstore, intervalSeconds i
|
||||
require.NoError(t, err)
|
||||
|
||||
q := models.ListRuleGroupAlertRulesQuery{
|
||||
OrgID: 1,
|
||||
OrgID: orgID,
|
||||
NamespaceUID: "namespace",
|
||||
RuleGroup: ruleGroup,
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
|
||||
}
|
||||
continue
|
||||
}
|
||||
query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target, IsNgAlertEnabled: qs.Cfg.IsNgAlertEnabled()}
|
||||
query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target, UnifiedAlertingEnabled: qs.Cfg.UnifiedAlerting.Enabled}
|
||||
if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil {
|
||||
return true, err
|
||||
}
|
||||
@@ -74,10 +74,10 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
|
||||
continue
|
||||
}
|
||||
query := models.GetOrgQuotaByTargetQuery{
|
||||
OrgId: c.OrgId,
|
||||
Target: scope.Target,
|
||||
Default: scope.DefaultLimit,
|
||||
IsNgAlertEnabled: qs.Cfg.IsNgAlertEnabled(),
|
||||
OrgId: c.OrgId,
|
||||
Target: scope.Target,
|
||||
Default: scope.DefaultLimit,
|
||||
UnifiedAlertingEnabled: qs.Cfg.UnifiedAlerting.Enabled,
|
||||
}
|
||||
if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil {
|
||||
return true, err
|
||||
@@ -96,7 +96,7 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
|
||||
if !c.IsSignedIn || c.UserId == 0 {
|
||||
continue
|
||||
}
|
||||
query := models.GetUserQuotaByTargetQuery{UserId: c.UserId, Target: scope.Target, Default: scope.DefaultLimit, IsNgAlertEnabled: qs.Cfg.IsNgAlertEnabled()}
|
||||
query := models.GetUserQuotaByTargetQuery{UserId: c.UserId, Target: scope.Target, Default: scope.DefaultLimit, UnifiedAlertingEnabled: qs.Cfg.UnifiedAlerting.Enabled}
|
||||
if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
@@ -49,10 +49,8 @@ func AddDashAlertMigration(mg *migrator.Migrator) {
|
||||
|
||||
_, migrationRun := logs[migTitle]
|
||||
|
||||
ngEnabled := mg.Cfg.IsNgAlertEnabled()
|
||||
|
||||
switch {
|
||||
case ngEnabled && !migrationRun:
|
||||
case mg.Cfg.UnifiedAlerting.Enabled && !migrationRun:
|
||||
// Remove the migration entry that removes all unified alerting data. This is so when the feature
|
||||
// flag is removed in future the "remove unified alerting data" migration will be run again.
|
||||
mg.AddMigration(fmt.Sprintf(clearMigrationEntryTitle, rmMigTitle), &clearMigrationEntry{
|
||||
@@ -67,7 +65,7 @@ func AddDashAlertMigration(mg *migrator.Migrator) {
|
||||
portedChannelGroupsPerOrg: make(map[int64]map[string]string),
|
||||
silences: make(map[int64][]*pb.MeshSilence),
|
||||
})
|
||||
case !ngEnabled && migrationRun:
|
||||
case !mg.Cfg.UnifiedAlerting.Enabled && migrationRun:
|
||||
// Remove the migration entry that creates unified alerting data. This is so when the feature
|
||||
// flag is enabled in the future the migration "move dashboard alerts to unified alerting" will be run again.
|
||||
mg.AddMigration(fmt.Sprintf(clearMigrationEntryTitle, migTitle), &clearMigrationEntry{
|
||||
@@ -92,7 +90,7 @@ func RerunDashAlertMigration(mg *migrator.Migrator) {
|
||||
cloneMigTitle := fmt.Sprintf("clone %s", migTitle)
|
||||
|
||||
_, migrationRun := logs[cloneMigTitle]
|
||||
ngEnabled := mg.Cfg.IsNgAlertEnabled()
|
||||
ngEnabled := mg.Cfg.UnifiedAlerting.Enabled
|
||||
|
||||
switch {
|
||||
case ngEnabled && !migrationRun:
|
||||
|
||||
@@ -43,7 +43,7 @@ func (ss *SQLStore) GetOrgQuotaByTarget(ctx context.Context, query *models.GetOr
|
||||
}
|
||||
|
||||
var used int64
|
||||
if query.Target != alertRuleTarget || query.IsNgAlertEnabled {
|
||||
if query.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// get quota used.
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM %s WHERE org_id=?",
|
||||
dialect.Quote(query.Target))
|
||||
@@ -97,7 +97,7 @@ func (ss *SQLStore) GetOrgQuotas(ctx context.Context, query *models.GetOrgQuotas
|
||||
result := make([]*models.OrgQuotaDTO, len(quotas))
|
||||
for i, q := range quotas {
|
||||
var used int64
|
||||
if q.Target != alertRuleTarget || query.IsNgAlertEnabled {
|
||||
if q.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// get quota used.
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(q.Target))
|
||||
resp := make([]*targetCount, 0)
|
||||
@@ -163,7 +163,7 @@ func (ss *SQLStore) GetUserQuotaByTarget(ctx context.Context, query *models.GetU
|
||||
}
|
||||
|
||||
var used int64
|
||||
if query.Target != alertRuleTarget || query.IsNgAlertEnabled {
|
||||
if query.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// get quota used.
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(query.Target))
|
||||
resp := make([]*targetCount, 0)
|
||||
@@ -211,7 +211,7 @@ func (ss *SQLStore) GetUserQuotas(ctx context.Context, query *models.GetUserQuot
|
||||
result := make([]*models.UserQuotaDTO, len(quotas))
|
||||
for i, q := range quotas {
|
||||
var used int64
|
||||
if q.Target != alertRuleTarget || query.IsNgAlertEnabled {
|
||||
if q.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// get quota used.
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(q.Target))
|
||||
resp := make([]*targetCount, 0)
|
||||
@@ -266,7 +266,7 @@ func (ss *SQLStore) UpdateUserQuota(ctx context.Context, cmd *models.UpdateUserQ
|
||||
func (ss *SQLStore) GetGlobalQuotaByTarget(ctx context.Context, query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
return ss.WithDbSession(ctx, func(sess *DBSession) error {
|
||||
var used int64
|
||||
if query.Target != alertRuleTarget || query.IsNgAlertEnabled {
|
||||
if query.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// get quota used.
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM %s",
|
||||
dialect.Quote(query.Target))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -20,10 +20,13 @@ import (
|
||||
)
|
||||
|
||||
func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
|
||||
const disableOrgID int64 = 3
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
NGAlertAdminConfigPollInterval: 2 * time.Second,
|
||||
UnifiedAlertingDisabledOrgs: []int64{disableOrgID}, // disable unified alerting for organisation 3
|
||||
})
|
||||
|
||||
grafanaListedAddr, s := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -31,15 +34,29 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
|
||||
s.Bus = bus.GetBus()
|
||||
|
||||
// Create a user to make authenticated requests
|
||||
createUser(t, s, models.CreateUserCommand{
|
||||
userID := createUser(t, s, models.CreateUserCommand{
|
||||
DefaultOrgRole: string(models.ROLE_ADMIN),
|
||||
Login: "grafana",
|
||||
Password: "password",
|
||||
})
|
||||
|
||||
// create another organisation
|
||||
orgID := createOrg(t, s, "another org", userID)
|
||||
// ensure that the orgID is 3 (the disabled org)
|
||||
require.Equal(t, disableOrgID, orgID)
|
||||
|
||||
// create user under different organisation
|
||||
createUser(t, s, models.CreateUserCommand{
|
||||
DefaultOrgRole: string(models.ROLE_ADMIN),
|
||||
Password: "admin-42",
|
||||
Login: "admin-42",
|
||||
OrgId: orgID,
|
||||
})
|
||||
|
||||
// Create a couple of "fake" Alertmanagers
|
||||
fakeAM1 := schedule.NewFakeExternalAlertmanager(t)
|
||||
fakeAM2 := schedule.NewFakeExternalAlertmanager(t)
|
||||
fakeAM3 := schedule.NewFakeExternalAlertmanager(t)
|
||||
|
||||
// Now, let's test the configuration API.
|
||||
{
|
||||
@@ -50,7 +67,7 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
|
||||
require.JSONEq(t, string(b), "{\"message\": \"no admin configuration available\"}")
|
||||
}
|
||||
|
||||
// Now, lets re-set external Alertmanagers.
|
||||
// Now, lets re-set external Alertmanagers for main organisation.
|
||||
{
|
||||
ac := apimodels.PostableNGalertConfig{
|
||||
Alertmanagers: []string{fakeAM1.URL(), fakeAM2.URL()},
|
||||
@@ -76,7 +93,7 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
|
||||
require.JSONEq(t, string(b), fmt.Sprintf("{\"alertmanagers\":[\"%s\",\"%s\"]}\n", fakeAM1.URL(), fakeAM2.URL()))
|
||||
}
|
||||
|
||||
// With the configuration set, we should eventually discover those Alertmanagers set.
|
||||
// With the configuration set, we should eventually discover those Alertmanagers.
|
||||
{
|
||||
alertsURL := fmt.Sprintf("http://grafana:password@%s/api/v1/ngalert/alertmanagers", grafanaListedAddr)
|
||||
require.Eventually(t, func() bool {
|
||||
@@ -88,7 +105,7 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(b, &alertmanagers))
|
||||
|
||||
return len(alertmanagers.Data.Active) == 2
|
||||
}, 80*time.Second, 4*time.Second)
|
||||
}, 16*time.Second, 8*time.Second) // the sync interval is 2s so after 8s all alertmanagers most probably are started
|
||||
}
|
||||
|
||||
// Now, let's set an alert that should fire as quickly as possible.
|
||||
@@ -148,4 +165,45 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
|
||||
return fakeAM1.AlertsCount() == 1 && fakeAM2.AlertsCount() == 1
|
||||
}, 60*time.Second, 5*time.Second)
|
||||
}
|
||||
|
||||
// Now, lets re-set external Alertmanagers for the other organisation.
|
||||
{
|
||||
ac := apimodels.PostableNGalertConfig{
|
||||
Alertmanagers: []string{fakeAM3.URL()},
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode(&ac)
|
||||
require.NoError(t, err)
|
||||
|
||||
alertsURL := fmt.Sprintf("http://admin-42:admin-42@%s/api/v1/ngalert/admin_config", grafanaListedAddr)
|
||||
resp := postRequest(t, alertsURL, buf.String(), http.StatusCreated) // nolint
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(b), "{\"message\": \"admin configuration updated\"}")
|
||||
}
|
||||
|
||||
// If we get the configuration again, it shows us what we've set.
|
||||
{
|
||||
alertsURL := fmt.Sprintf("http://admin-42:admin-42@%s/api/v1/ngalert/admin_config", grafanaListedAddr)
|
||||
resp := getRequest(t, alertsURL, http.StatusOK) // nolint
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(b), fmt.Sprintf("{\"alertmanagers\":[\"%s\"]}\n", fakeAM3.URL()))
|
||||
}
|
||||
|
||||
// With the configuration set, we should eventually not discover Alertmanagers.
|
||||
{
|
||||
alertsURL := fmt.Sprintf("http://admin-42:admin-42@%s/api/v1/ngalert/alertmanagers", grafanaListedAddr)
|
||||
require.Eventually(t, func() bool {
|
||||
resp := getRequest(t, alertsURL, http.StatusOK) // nolint
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var alertmanagers apimodels.GettableAlertmanagers
|
||||
require.NoError(t, json.Unmarshal(b, &alertmanagers))
|
||||
|
||||
return len(alertmanagers.Data.Active) == 0
|
||||
}, 16*time.Second, 8*time.Second) // the sync interval is 2s so after 8s all alertmanagers (if any) most probably are started
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
|
||||
func TestAlertmanagerConfigurationIsTransactional(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
NGAlertAlertmanagerConfigPollInterval: 2 * time.Second,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
@@ -127,8 +128,9 @@ func TestAlertmanagerConfigurationIsTransactional(t *testing.T) {
|
||||
|
||||
func TestAlertmanagerConfigurationPersistSecrets(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
|
||||
@@ -29,8 +29,9 @@ import (
|
||||
|
||||
func TestAMConfigAccess(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -387,8 +388,9 @@ func TestAMConfigAccess(t *testing.T) {
|
||||
|
||||
func TestAlertAndGroupsQuery(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -552,10 +554,11 @@ func TestAlertAndGroupsQuery(t *testing.T) {
|
||||
func TestRulerAccess(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
ViewersCanEdit: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
ViewersCanEdit: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -678,10 +681,11 @@ func TestRulerAccess(t *testing.T) {
|
||||
func TestDeleteFolderWithRules(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
ViewersCanEdit: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
ViewersCanEdit: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -837,9 +841,10 @@ func TestDeleteFolderWithRules(t *testing.T) {
|
||||
func TestAlertRuleCRUD(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -1905,7 +1910,8 @@ func TestAlertRuleCRUD(t *testing.T) {
|
||||
func TestAlertmanagerStatus(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, _ := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -1965,9 +1971,10 @@ func TestAlertmanagerStatus(t *testing.T) {
|
||||
func TestQuota(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -2207,9 +2214,10 @@ func TestQuota(t *testing.T) {
|
||||
func TestEval(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
|
||||
@@ -15,8 +15,9 @@ import (
|
||||
|
||||
func TestAvailableChannels(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
|
||||
@@ -34,7 +34,8 @@ func TestTestReceivers(t *testing.T) {
|
||||
t.Run("assert no receivers returns 400 Bad Request", func(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -64,7 +65,8 @@ func TestTestReceivers(t *testing.T) {
|
||||
t.Run("assert working receiver returns OK", func(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -131,7 +133,8 @@ func TestTestReceivers(t *testing.T) {
|
||||
t.Run("assert invalid receiver returns 400 Bad Request", func(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -194,7 +197,8 @@ func TestTestReceivers(t *testing.T) {
|
||||
t.Run("assert timed out receiver returns 408 Request Timeout", func(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -266,7 +270,8 @@ func TestTestReceivers(t *testing.T) {
|
||||
t.Run("assert multiple different errors returns 207 Multi Status", func(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -359,8 +364,9 @@ func TestTestReceivers(t *testing.T) {
|
||||
|
||||
func TestNotificationChannels(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, s := testinfra.StartGrafana(t, dir, path)
|
||||
|
||||
@@ -21,8 +21,9 @@ import (
|
||||
|
||||
func TestPrometheusRules(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -265,8 +266,9 @@ func TestPrometheusRules(t *testing.T) {
|
||||
|
||||
func TestPrometheusRulesPermissions(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
|
||||
@@ -22,8 +22,9 @@ import (
|
||||
func TestAlertRulePermissions(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
DisableAnonymous: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
@@ -306,10 +307,11 @@ func createRule(t *testing.T, grafanaListedAddr string, folder string, user, pas
|
||||
func TestAlertRuleConflictingTitle(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{"ngalert"},
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
ViewersCanEdit: true,
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
ViewersCanEdit: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
|
||||
@@ -194,6 +194,14 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
|
||||
_, err = alertingSect.NewKey("max_attempts", "3")
|
||||
require.NoError(t, err)
|
||||
|
||||
getOrCreateSection := func(name string) (*ini.Section, error) {
|
||||
section, err := cfg.GetSection(name)
|
||||
if err != nil {
|
||||
return cfg.NewSection(name)
|
||||
}
|
||||
return section, err
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
if o.EnableCSP {
|
||||
securitySect, err := cfg.NewSection("security")
|
||||
@@ -214,7 +222,7 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if o.NGAlertAlertmanagerConfigPollInterval != 0 {
|
||||
ngalertingSection, err := cfg.NewSection("unified_alerting")
|
||||
ngalertingSection, err := getOrCreateSection("unified_alerting")
|
||||
require.NoError(t, err)
|
||||
_, err = ngalertingSection.NewKey("alertmanager_config_poll_interval", o.NGAlertAlertmanagerConfigPollInterval.String())
|
||||
require.NoError(t, err)
|
||||
@@ -247,6 +255,25 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
|
||||
_, err = usersSection.NewKey("viewers_can_edit", "true")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if o.DisableLegacyAlerting {
|
||||
alertingSection, err := cfg.GetSection("alerting")
|
||||
require.NoError(t, err)
|
||||
_, err = alertingSection.NewKey("enabled", "false")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if o.EnableUnifiedAlerting {
|
||||
unifiedAlertingSection, err := getOrCreateSection("unified_alerting")
|
||||
require.NoError(t, err)
|
||||
_, err = unifiedAlertingSection.NewKey("enabled", "true")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if len(o.UnifiedAlertingDisabledOrgs) > 0 {
|
||||
unifiedAlertingSection, err := getOrCreateSection("unified_alerting")
|
||||
require.NoError(t, err)
|
||||
disableOrgStr := strings.Join(strings.Split(strings.Trim(fmt.Sprint(o.UnifiedAlertingDisabledOrgs), "[]"), " "), ",")
|
||||
_, err = unifiedAlertingSection.NewKey("disabled_orgs", disableOrgStr)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
cfgPath := filepath.Join(cfgDir, "test.ini")
|
||||
@@ -270,4 +297,7 @@ type GrafanaOpts struct {
|
||||
CatalogAppEnabled bool
|
||||
ViewersCanEdit bool
|
||||
PluginAdminEnabled bool
|
||||
DisableLegacyAlerting bool
|
||||
EnableUnifiedAlerting bool
|
||||
UnifiedAlertingDisabledOrgs []int64
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user