[Alerting]: Update scheduler to evaluate rules created by the unified API (#32589)

* Update scheduler

* Fix tests

* Fixes after code review feedback

* lint - add uncommitted modifications

Co-authored-by: kyle <kyle@grafana.com>
This commit is contained in:
Sofia Papagiannaki
2021-04-03 20:13:29 +03:00
committed by GitHub
co-authored by kyle
parent 455fbce020
commit daabf64aa1
11 changed files with 275 additions and 212 deletions
-3
View File
@@ -184,12 +184,9 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
// TODO check quota // TODO check quota
// TODO validate UID uniqueness in the payload // TODO validate UID uniqueness in the payload
ruleGroup := ruleGroupConfig.Name
if err := srv.store.UpdateRuleGroup(store.UpdateRuleGroupCmd{ if err := srv.store.UpdateRuleGroup(store.UpdateRuleGroupCmd{
OrgID: c.SignedInUser.OrgId, OrgID: c.SignedInUser.OrgId,
NamespaceUID: namespaceUID, NamespaceUID: namespaceUID,
RuleGroup: ruleGroup,
RuleGroupConfig: ruleGroupConfig, RuleGroupConfig: ruleGroupConfig,
}); err != nil { }); err != nil {
return response.Error(http.StatusInternalServerError, "failed to update rule group", err) return response.Error(http.StatusInternalServerError, "failed to update rule group", err)
@@ -74,7 +74,7 @@
"condition": "B", "condition": "B",
"data": [ "data": [
{ {
"refId": "query", "refId": "A",
"queryType": "", "queryType": "",
"relativeTimeRange": { "relativeTimeRange": {
"from": 18000, "from": 18000,
@@ -82,7 +82,7 @@
}, },
"model": { "model": {
"alias": "just-testing", "alias": "just-testing",
"datasource": "000000004", "datasource": "gdev-testdata",
"datasourceUid": "000000004", "datasourceUid": "000000004",
"intervalMs": 1000, "intervalMs": 1000,
"maxDataPoints": 100, "maxDataPoints": 100,
@@ -74,7 +74,7 @@
"condition": "B", "condition": "B",
"data": [ "data": [
{ {
"refId": "query", "refId": "A",
"queryType": "", "queryType": "",
"relativeTimeRange": { "relativeTimeRange": {
"from": 18000, "from": 18000,
@@ -82,7 +82,7 @@
}, },
"model": { "model": {
"alias": "just-testing", "alias": "just-testing",
"datasource": "000000004", "datasource": "gdev-testdata",
"datasourceUid": "000000004", "datasourceUid": "000000004",
"intervalMs": 1000, "intervalMs": 1000,
"maxDataPoints": 100, "maxDataPoints": 100,
+7 -1
View File
@@ -25,13 +25,19 @@ const (
InstanceStateFiring InstanceStateType = "Alerting" InstanceStateFiring InstanceStateType = "Alerting"
// InstanceStateNormal is for a normal alert. // InstanceStateNormal is for a normal alert.
InstanceStateNormal InstanceStateType = "Normal" InstanceStateNormal InstanceStateType = "Normal"
// InstanceStateNoData is for an alert with no data.
InstanceStateNoData InstanceStateType = "NoData"
// InstanceStateError is for a erroring alert.
InstanceStateError InstanceStateType = "Error"
) )
// IsValid checks that the value of InstanceStateType is a valid // IsValid checks that the value of InstanceStateType is a valid
// string. // string.
func (i InstanceStateType) IsValid() bool { func (i InstanceStateType) IsValid() bool {
return i == InstanceStateFiring || return i == InstanceStateFiring ||
i == InstanceStateNormal i == InstanceStateNormal ||
i == InstanceStateNoData ||
i == InstanceStateError
} }
// SaveAlertInstanceCommand is the query for saving a new alert instance. // SaveAlertInstanceCommand is the query for saving a new alert instance.
+1
View File
@@ -69,6 +69,7 @@ func (ng *AlertNG) Init() error {
MaxAttempts: maxAttempts, MaxAttempts: maxAttempts,
Evaluator: eval.Evaluator{Cfg: ng.Cfg}, Evaluator: eval.Evaluator{Cfg: ng.Cfg},
Store: store, Store: store,
RuleStore: store,
Notifier: ng.Alertmanager, Notifier: ng.Alertmanager,
} }
ng.schedule = schedule.NewScheduler(schedCfg, ng.DataService) ng.schedule = schedule.NewScheduler(schedCfg, ng.DataService)
+4 -6
View File
@@ -1,16 +1,14 @@
package schedule package schedule
import ( import (
"time"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
) )
func (sch *schedule) fetchAllDetails(now time.Time) []*models.AlertDefinition { func (sch *schedule) fetchAllDetails() []*models.AlertRule {
q := models.ListAlertDefinitionsQuery{} q := models.ListAlertRulesQuery{}
err := sch.store.GetAlertDefinitions(&q) err := sch.ruleStore.GetAlertRulesForScheduling(&q)
if err != nil { if err != nil {
sch.log.Error("failed to fetch alert definitions", "now", now, "err", err) sch.log.Error("failed to fetch alert definitions", "err", err)
return nil return nil
} }
return q.Result return q.Result
+76 -76
View File
@@ -34,18 +34,18 @@ type ScheduleService interface {
WarmStateCache(*state.StateTracker) WarmStateCache(*state.StateTracker)
// the following are used by tests only used for tests // the following are used by tests only used for tests
evalApplied(models.AlertDefinitionKey, time.Time) evalApplied(models.AlertRuleKey, time.Time)
stopApplied(models.AlertDefinitionKey) stopApplied(models.AlertRuleKey)
overrideCfg(cfg SchedulerCfg) overrideCfg(cfg SchedulerCfg)
} }
func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key models.AlertDefinitionKey, evalCh <-chan *evalContext, stopCh <-chan struct{}, stateTracker *state.StateTracker) error { func (sch *schedule) ruleRoutine(grafanaCtx context.Context, key models.AlertRuleKey, evalCh <-chan *evalContext, stopCh <-chan struct{}, stateTracker *state.StateTracker) error {
sch.log.Debug("alert definition routine started", "key", key) sch.log.Debug("alert rule routine started", "key", key)
evalRunning := false evalRunning := false
var start, end time.Time var start, end time.Time
var attempt int64 var attempt int64
var alertDefinition *models.AlertDefinition var alertRule *models.AlertRule
for { for {
select { select {
case ctx := <-evalCh: case ctx := <-evalCh:
@@ -56,33 +56,33 @@ func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key models.Al
evaluate := func(attempt int64) error { evaluate := func(attempt int64) error {
start = timeNow() start = timeNow()
// fetch latest alert definition version // fetch latest alert rule version
if alertDefinition == nil || alertDefinition.Version < ctx.version { if alertRule == nil || alertRule.Version < ctx.version {
q := models.GetAlertDefinitionByUIDQuery{OrgID: key.OrgID, UID: key.DefinitionUID} q := models.GetAlertRuleByUIDQuery{OrgID: key.OrgID, UID: key.UID}
err := sch.store.GetAlertDefinitionByUID(&q) err := sch.ruleStore.GetAlertRuleByUID(&q)
if err != nil { if err != nil {
sch.log.Error("failed to fetch alert definition", "key", key) sch.log.Error("failed to fetch alert rule", "key", key)
return err return err
} }
alertDefinition = q.Result alertRule = q.Result
sch.log.Debug("new alert definition version fetched", "title", alertDefinition.Title, "key", key, "version", alertDefinition.Version) sch.log.Debug("new alert rule version fetched", "title", alertRule.Title, "key", key, "version", alertRule.Version)
} }
condition := models.Condition{ condition := models.Condition{
Condition: alertDefinition.Condition, Condition: alertRule.Condition,
OrgID: alertDefinition.OrgID, OrgID: alertRule.OrgID,
Data: alertDefinition.Data, Data: alertRule.Data,
} }
results, err := sch.evaluator.ConditionEval(&condition, ctx.now, sch.dataService) results, err := sch.evaluator.ConditionEval(&condition, ctx.now, sch.dataService)
end = timeNow() end = timeNow()
if err != nil { if err != nil {
// consider saving alert instance on error // consider saving alert instance on error
sch.log.Error("failed to evaluate alert definition", "title", alertDefinition.Title, sch.log.Error("failed to evaluate alert rule", "title", alertRule.Title,
"key", key, "attempt", attempt, "now", ctx.now, "duration", end.Sub(start), "error", err) "key", key, "attempt", attempt, "now", ctx.now, "duration", end.Sub(start), "error", err)
return err return err
} }
processedStates := stateTracker.ProcessEvalResults(key.DefinitionUID, results, condition) processedStates := stateTracker.ProcessEvalResults(key.UID, results, condition)
sch.saveAlertStates(processedStates) sch.saveAlertStates(processedStates)
alerts := FromAlertStateToPostableAlerts(processedStates) alerts := FromAlertStateToPostableAlerts(processedStates)
sch.log.Debug("sending alerts to notifier", "count", len(alerts)) sch.log.Debug("sending alerts to notifier", "count", len(alerts))
@@ -109,7 +109,7 @@ func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key models.Al
}() }()
case <-stopCh: case <-stopCh:
sch.stopApplied(key) sch.stopApplied(key)
sch.log.Debug("stopping alert definition routine", "key", key) sch.log.Debug("stopping alert rule routine", "key", key)
// interrupt evaluation if it's running // interrupt evaluation if it's running
return nil return nil
case <-grafanaCtx.Done(): case <-grafanaCtx.Done():
@@ -127,8 +127,8 @@ type schedule struct {
// base tick rate (fastest possible configured check) // base tick rate (fastest possible configured check)
baseInterval time.Duration baseInterval time.Duration
// each alert definition gets its own channel and routine // each alert rule gets its own channel and routine
registry alertDefinitionRegistry registry alertRuleRegistry
maxAttempts int64 maxAttempts int64
@@ -139,12 +139,12 @@ type schedule struct {
// evalApplied is only used for tests: test code can set it to non-nil // evalApplied is only used for tests: test code can set it to non-nil
// function, and then it'll be called from the event loop whenever the // function, and then it'll be called from the event loop whenever the
// message from evalApplied is handled. // message from evalApplied is handled.
evalAppliedFunc func(models.AlertDefinitionKey, time.Time) evalAppliedFunc func(models.AlertRuleKey, time.Time)
// stopApplied is only used for tests: test code can set it to non-nil // stopApplied is only used for tests: test code can set it to non-nil
// function, and then it'll be called from the event loop whenever the // function, and then it'll be called from the event loop whenever the
// message from stopApplied is handled. // message from stopApplied is handled.
stopAppliedFunc func(models.AlertDefinitionKey) stopAppliedFunc func(models.AlertRuleKey)
log log.Logger log log.Logger
@@ -152,6 +152,8 @@ type schedule struct {
store store.Store store store.Store
ruleStore store.RuleStore
dataService *tsdb.Service dataService *tsdb.Service
notifier Notifier notifier Notifier
@@ -162,11 +164,12 @@ type SchedulerCfg struct {
C clock.Clock C clock.Clock
BaseInterval time.Duration BaseInterval time.Duration
Logger log.Logger Logger log.Logger
EvalAppliedFunc func(models.AlertDefinitionKey, time.Time) EvalAppliedFunc func(models.AlertRuleKey, time.Time)
MaxAttempts int64 MaxAttempts int64
StopAppliedFunc func(models.AlertDefinitionKey) StopAppliedFunc func(models.AlertRuleKey)
Evaluator eval.Evaluator Evaluator eval.Evaluator
Store store.Store Store store.Store
RuleStore store.RuleStore
Notifier Notifier Notifier Notifier
} }
@@ -174,7 +177,7 @@ type SchedulerCfg struct {
func NewScheduler(cfg SchedulerCfg, dataService *tsdb.Service) *schedule { func NewScheduler(cfg SchedulerCfg, dataService *tsdb.Service) *schedule {
ticker := alerting.NewTicker(cfg.C.Now(), time.Second*0, cfg.C, int64(cfg.BaseInterval.Seconds())) ticker := alerting.NewTicker(cfg.C.Now(), time.Second*0, cfg.C, int64(cfg.BaseInterval.Seconds()))
sch := schedule{ sch := schedule{
registry: alertDefinitionRegistry{alertDefinitionInfo: make(map[models.AlertDefinitionKey]alertDefinitionInfo)}, registry: alertRuleRegistry{alertRuleInfo: make(map[models.AlertRuleKey]alertRuleInfo)},
maxAttempts: cfg.MaxAttempts, maxAttempts: cfg.MaxAttempts,
clock: cfg.C, clock: cfg.C,
baseInterval: cfg.BaseInterval, baseInterval: cfg.BaseInterval,
@@ -184,6 +187,7 @@ func NewScheduler(cfg SchedulerCfg, dataService *tsdb.Service) *schedule {
stopAppliedFunc: cfg.StopAppliedFunc, stopAppliedFunc: cfg.StopAppliedFunc,
evaluator: cfg.Evaluator, evaluator: cfg.Evaluator,
store: cfg.Store, store: cfg.Store,
ruleStore: cfg.RuleStore,
dataService: dataService, dataService: dataService,
notifier: cfg.Notifier, notifier: cfg.Notifier,
} }
@@ -198,7 +202,7 @@ func (sch *schedule) overrideCfg(cfg SchedulerCfg) {
sch.stopAppliedFunc = cfg.StopAppliedFunc sch.stopAppliedFunc = cfg.StopAppliedFunc
} }
func (sch *schedule) evalApplied(alertDefKey models.AlertDefinitionKey, now time.Time) { func (sch *schedule) evalApplied(alertDefKey models.AlertRuleKey, now time.Time) {
if sch.evalAppliedFunc == nil { if sch.evalAppliedFunc == nil {
return return
} }
@@ -206,7 +210,7 @@ func (sch *schedule) evalApplied(alertDefKey models.AlertDefinitionKey, now time
sch.evalAppliedFunc(alertDefKey, now) sch.evalAppliedFunc(alertDefKey, now)
} }
func (sch *schedule) stopApplied(alertDefKey models.AlertDefinitionKey) { func (sch *schedule) stopApplied(alertDefKey models.AlertRuleKey) {
if sch.stopAppliedFunc == nil { if sch.stopAppliedFunc == nil {
return return
} }
@@ -219,7 +223,7 @@ func (sch *schedule) Pause() error {
return fmt.Errorf("scheduler is not initialised") return fmt.Errorf("scheduler is not initialised")
} }
sch.heartbeat.Pause() sch.heartbeat.Pause()
sch.log.Info("alert definition scheduler paused", "now", sch.clock.Now()) sch.log.Info("alert rule scheduler paused", "now", sch.clock.Now())
return nil return nil
} }
@@ -228,7 +232,7 @@ func (sch *schedule) Unpause() error {
return fmt.Errorf("scheduler is not initialised") return fmt.Errorf("scheduler is not initialised")
} }
sch.heartbeat.Unpause() sch.heartbeat.Unpause()
sch.log.Info("alert definition scheduler unpaused", "now", sch.clock.Now()) sch.log.Info("alert rule scheduler unpaused", "now", sch.clock.Now())
return nil return nil
} }
@@ -238,50 +242,46 @@ func (sch *schedule) Ticker(grafanaCtx context.Context, stateTracker *state.Stat
select { select {
case tick := <-sch.heartbeat.C: case tick := <-sch.heartbeat.C:
tickNum := tick.Unix() / int64(sch.baseInterval.Seconds()) tickNum := tick.Unix() / int64(sch.baseInterval.Seconds())
alertDefinitions := sch.fetchAllDetails(tick) alertRules := sch.fetchAllDetails()
sch.log.Debug("alert definitions fetched", "count", len(alertDefinitions)) sch.log.Debug("alert rules fetched", "count", len(alertRules))
// registeredDefinitions is a map used for finding deleted alert definitions // registeredDefinitions is a map used for finding deleted alert rules
// initially it is assigned to all known alert definitions from the previous cycle // initially it is assigned to all known alert rules from the previous cycle
// each alert definition found also in this cycle is removed // each alert rule found also in this cycle is removed
// so, at the end, the remaining registered alert definitions are the deleted ones // so, at the end, the remaining registered alert rules are the deleted ones
registeredDefinitions := sch.registry.keyMap() registeredDefinitions := sch.registry.keyMap()
type readyToRunItem struct { type readyToRunItem struct {
key models.AlertDefinitionKey key models.AlertRuleKey
definitionInfo alertDefinitionInfo ruleInfo alertRuleInfo
} }
readyToRun := make([]readyToRunItem, 0) readyToRun := make([]readyToRunItem, 0)
for _, item := range alertDefinitions { for _, item := range alertRules {
if item.Paused {
continue
}
key := item.GetKey() key := item.GetKey()
itemVersion := item.Version itemVersion := item.Version
newRoutine := !sch.registry.exists(key) newRoutine := !sch.registry.exists(key)
definitionInfo := sch.registry.getOrCreateInfo(key, itemVersion) ruleInfo := sch.registry.getOrCreateInfo(key, itemVersion)
invalidInterval := item.IntervalSeconds%int64(sch.baseInterval.Seconds()) != 0 invalidInterval := item.IntervalSeconds%int64(sch.baseInterval.Seconds()) != 0
if newRoutine && !invalidInterval { if newRoutine && !invalidInterval {
dispatcherGroup.Go(func() error { dispatcherGroup.Go(func() error {
return sch.definitionRoutine(ctx, key, definitionInfo.evalCh, definitionInfo.stopCh, stateTracker) return sch.ruleRoutine(ctx, key, ruleInfo.evalCh, ruleInfo.stopCh, stateTracker)
}) })
} }
if invalidInterval { if invalidInterval {
// this is expected to be always false // this is expected to be always false
// give that we validate interval during alert definition updates // give that we validate interval during alert rule updates
sch.log.Debug("alert definition with invalid interval will be ignored: interval should be divided exactly by scheduler interval", "key", key, "interval", time.Duration(item.IntervalSeconds)*time.Second, "scheduler interval", sch.baseInterval) sch.log.Debug("alert rule with invalid interval will be ignored: interval should be divided exactly by scheduler interval", "key", key, "interval", time.Duration(item.IntervalSeconds)*time.Second, "scheduler interval", sch.baseInterval)
continue continue
} }
itemFrequency := item.IntervalSeconds / int64(sch.baseInterval.Seconds()) itemFrequency := item.IntervalSeconds / int64(sch.baseInterval.Seconds())
if item.IntervalSeconds != 0 && tickNum%itemFrequency == 0 { if item.IntervalSeconds != 0 && tickNum%itemFrequency == 0 {
readyToRun = append(readyToRun, readyToRunItem{key: key, definitionInfo: definitionInfo}) readyToRun = append(readyToRun, readyToRunItem{key: key, ruleInfo: ruleInfo})
} }
// remove the alert definition from the registered alert definitions // remove the alert rule from the registered alert rules
delete(registeredDefinitions, key) delete(registeredDefinitions, key)
} }
@@ -294,18 +294,18 @@ func (sch *schedule) Ticker(grafanaCtx context.Context, stateTracker *state.Stat
item := readyToRun[i] item := readyToRun[i]
time.AfterFunc(time.Duration(int64(i)*step), func() { time.AfterFunc(time.Duration(int64(i)*step), func() {
item.definitionInfo.evalCh <- &evalContext{now: tick, version: item.definitionInfo.version} item.ruleInfo.evalCh <- &evalContext{now: tick, version: item.ruleInfo.version}
}) })
} }
// unregister and stop routines of the deleted alert definitions // unregister and stop routines of the deleted alert rules
for key := range registeredDefinitions { for key := range registeredDefinitions {
definitionInfo, err := sch.registry.get(key) ruleInfo, err := sch.registry.get(key)
if err != nil { if err != nil {
sch.log.Error("failed to get alert definition routine information", "err", err) sch.log.Error("failed to get alert rule routine information", "err", err)
continue continue
} }
definitionInfo.stopCh <- struct{}{} ruleInfo.stopCh <- struct{}{}
sch.registry.del(key) sch.registry.del(key)
} }
case <-grafanaCtx.Done(): case <-grafanaCtx.Done():
@@ -394,63 +394,63 @@ func translateInstanceState(state models.InstanceStateType) eval.State {
} }
} }
type alertDefinitionRegistry struct { type alertRuleRegistry struct {
mu sync.Mutex mu sync.Mutex
alertDefinitionInfo map[models.AlertDefinitionKey]alertDefinitionInfo alertRuleInfo map[models.AlertRuleKey]alertRuleInfo
} }
// getOrCreateInfo returns the channel for the specific alert definition // getOrCreateInfo returns the channel for the specific alert rule
// if it does not exists creates one and returns it // if it does not exists creates one and returns it
func (r *alertDefinitionRegistry) getOrCreateInfo(key models.AlertDefinitionKey, definitionVersion int64) alertDefinitionInfo { func (r *alertRuleRegistry) getOrCreateInfo(key models.AlertRuleKey, ruleVersion int64) alertRuleInfo {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
info, ok := r.alertDefinitionInfo[key] info, ok := r.alertRuleInfo[key]
if !ok { if !ok {
r.alertDefinitionInfo[key] = alertDefinitionInfo{evalCh: make(chan *evalContext), stopCh: make(chan struct{}), version: definitionVersion} r.alertRuleInfo[key] = alertRuleInfo{evalCh: make(chan *evalContext), stopCh: make(chan struct{}), version: ruleVersion}
return r.alertDefinitionInfo[key] return r.alertRuleInfo[key]
} }
info.version = definitionVersion info.version = ruleVersion
r.alertDefinitionInfo[key] = info r.alertRuleInfo[key] = info
return info return info
} }
// get returns the channel for the specific alert definition // get returns the channel for the specific alert rule
// if the key does not exist returns an error // if the key does not exist returns an error
func (r *alertDefinitionRegistry) get(key models.AlertDefinitionKey) (*alertDefinitionInfo, error) { func (r *alertRuleRegistry) get(key models.AlertRuleKey) (*alertRuleInfo, error) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
info, ok := r.alertDefinitionInfo[key] info, ok := r.alertRuleInfo[key]
if !ok { if !ok {
return nil, fmt.Errorf("%v key not found", key) return nil, fmt.Errorf("%v key not found", key)
} }
return &info, nil return &info, nil
} }
func (r *alertDefinitionRegistry) exists(key models.AlertDefinitionKey) bool { func (r *alertRuleRegistry) exists(key models.AlertRuleKey) bool {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
_, ok := r.alertDefinitionInfo[key] _, ok := r.alertRuleInfo[key]
return ok return ok
} }
func (r *alertDefinitionRegistry) del(key models.AlertDefinitionKey) { func (r *alertRuleRegistry) del(key models.AlertRuleKey) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
delete(r.alertDefinitionInfo, key) delete(r.alertRuleInfo, key)
} }
func (r *alertDefinitionRegistry) iter() <-chan models.AlertDefinitionKey { func (r *alertRuleRegistry) iter() <-chan models.AlertRuleKey {
c := make(chan models.AlertDefinitionKey) c := make(chan models.AlertRuleKey)
f := func() { f := func() {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
for k := range r.alertDefinitionInfo { for k := range r.alertRuleInfo {
c <- k c <- k
} }
close(c) close(c)
@@ -460,15 +460,15 @@ func (r *alertDefinitionRegistry) iter() <-chan models.AlertDefinitionKey {
return c return c
} }
func (r *alertDefinitionRegistry) keyMap() map[models.AlertDefinitionKey]struct{} { func (r *alertRuleRegistry) keyMap() map[models.AlertRuleKey]struct{} {
definitionsIDs := make(map[models.AlertDefinitionKey]struct{}) definitionsIDs := make(map[models.AlertRuleKey]struct{})
for k := range r.iter() { for k := range r.iter() {
definitionsIDs[k] = struct{}{} definitionsIDs[k] = struct{}{}
} }
return definitionsIDs return definitionsIDs
} }
type alertDefinitionInfo struct { type alertRuleInfo struct {
evalCh chan *evalContext evalCh chan *evalContext
stopCh chan struct{} stopCh chan struct{}
version int64 version int64
+8 -8
View File
@@ -25,7 +25,6 @@ const AlertRuleMaxRuleGroupNameLength = 190
type UpdateRuleGroupCmd struct { type UpdateRuleGroupCmd struct {
OrgID int64 OrgID int64
NamespaceUID string NamespaceUID string
RuleGroup string
RuleGroupConfig apimodels.PostableRuleGroupConfig RuleGroupConfig apimodels.PostableRuleGroupConfig
} }
@@ -40,7 +39,7 @@ type RuleStore interface {
DeleteNamespaceAlertRules(orgID int64, namespaceUID string) error DeleteNamespaceAlertRules(orgID int64, namespaceUID string) error
DeleteRuleGroupAlertRules(orgID int64, namespaceUID string, ruleGroup string) error DeleteRuleGroupAlertRules(orgID int64, namespaceUID string, ruleGroup string) error
GetAlertRuleByUID(*ngmodels.GetAlertRuleByUIDQuery) error GetAlertRuleByUID(*ngmodels.GetAlertRuleByUIDQuery) error
GetAlertRules(query *ngmodels.ListAlertRulesQuery) error GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery) error
GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error
GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRulesQuery) error GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRulesQuery) error
GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error
@@ -324,12 +323,12 @@ func (st DBstore) GetNamespaceByUID(UID string, orgID int64, user *models.Signed
return folder.Title, nil return folder.Title, nil
} }
// GetAlertRules returns alert rule identifier, interval, version and pause state // GetAlertRulesForScheduling returns alert rule info (identifier, interval, version state)
// that are useful for it's scheduling. // that is useful for it's scheduling.
func (st DBstore) GetAlertRules(query *ngmodels.ListAlertRulesQuery) error { func (st DBstore) GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
alerts := make([]*ngmodels.AlertRule, 0) alerts := make([]*ngmodels.AlertRule, 0)
q := "SELECT uid, org_id, interval_seconds, version, paused FROM alert_rule" q := "SELECT uid, org_id, interval_seconds, version FROM alert_rule"
if err := sess.SQL(q).Find(&alerts); err != nil { if err := sess.SQL(q).Find(&alerts); err != nil {
return err return err
} }
@@ -391,10 +390,11 @@ func (st DBstore) ValidateAlertRule(alertRule ngmodels.AlertRule, requireData bo
// UpdateRuleGroup creates new rules and updates and/or deletes existing rules // UpdateRuleGroup creates new rules and updates and/or deletes existing rules
func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error { func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error { return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
ruleGroup := cmd.RuleGroupConfig.Name
q := &ngmodels.ListRuleGroupAlertRulesQuery{ q := &ngmodels.ListRuleGroupAlertRulesQuery{
OrgID: cmd.OrgID, OrgID: cmd.OrgID,
NamespaceUID: cmd.NamespaceUID, NamespaceUID: cmd.NamespaceUID,
RuleGroup: cmd.RuleGroup, RuleGroup: ruleGroup,
} }
if err := st.GetRuleGroupAlertRules(q); err != nil { if err := st.GetRuleGroupAlertRules(q); err != nil {
return err return err
@@ -421,7 +421,7 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
UID: r.GrafanaManagedAlert.UID, UID: r.GrafanaManagedAlert.UID,
IntervalSeconds: int64(time.Duration(cmd.RuleGroupConfig.Interval).Seconds()), IntervalSeconds: int64(time.Duration(cmd.RuleGroupConfig.Interval).Seconds()),
NamespaceUID: cmd.NamespaceUID, NamespaceUID: cmd.NamespaceUID,
RuleGroup: cmd.RuleGroup, RuleGroup: ruleGroup,
NoDataState: ngmodels.NoDataState(r.GrafanaManagedAlert.NoDataState), NoDataState: ngmodels.NoDataState(r.GrafanaManagedAlert.NoDataState),
ExecErrState: ngmodels.ExecutionErrorState(r.GrafanaManagedAlert.ExecErrState), ExecErrState: ngmodels.ExecutionErrorState(r.GrafanaManagedAlert.ExecErrState),
}, },
@@ -5,6 +5,8 @@ package tests
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"math/rand"
"testing" "testing"
"time" "time"
@@ -418,3 +420,31 @@ func getLongString(n int) string {
} }
return string(b) return string(b)
} }
// createTestAlertDefinition creates a dummy alert definition to be used by the tests.
func createTestAlertDefinition(t *testing.T, dbstore *store.DBstore, intervalSeconds int64) *models.AlertDefinition {
cmd := models.SaveAlertDefinitionCommand{
OrgID: 1,
Title: fmt.Sprintf("an alert definition %d", rand.Intn(1000)),
Condition: "A",
Data: []models.AlertQuery{
{
Model: json.RawMessage(`{
"datasource": "__expr__",
"type":"math",
"expression":"2 + 2 > 1"
}`),
RelativeTimeRange: models.RelativeTimeRange{
From: models.Duration(5 * time.Hour),
To: models.Duration(3 * time.Hour),
},
RefID: "A",
},
},
IntervalSeconds: &intervalSeconds,
}
err := dbstore.SaveAlertDefinition(&cmd)
require.NoError(t, err)
t.Logf("alert definition: %v with interval: %d created", cmd.Result.GetKey(), intervalSeconds)
return cmd.Result
}
+61 -92
View File
@@ -26,7 +26,7 @@ import (
) )
type evalAppliedInfo struct { type evalAppliedInfo struct {
alertDefKey models.AlertDefinitionKey alertDefKey models.AlertRuleKey
now time.Time now time.Time
} }
@@ -109,15 +109,15 @@ func TestAlertingTicker(t *testing.T) {
dbstore := setupTestEnv(t, 1) dbstore := setupTestEnv(t, 1)
t.Cleanup(registry.ClearOverrides) t.Cleanup(registry.ClearOverrides)
alerts := make([]*models.AlertDefinition, 0) alerts := make([]*models.AlertRule, 0)
// create alert definition with zero interval (should never run) // create alert rule with zero interval (should never run)
alerts = append(alerts, createTestAlertDefinition(t, dbstore, 0)) alerts = append(alerts, createTestAlertRule(t, dbstore, 0))
// create alert definition with one second interval // create alert rule with one second interval
alerts = append(alerts, createTestAlertDefinition(t, dbstore, 1)) alerts = append(alerts, createTestAlertRule(t, dbstore, 1))
evalAppliedCh := make(chan evalAppliedInfo, len(alerts)) evalAppliedCh := make(chan evalAppliedInfo, len(alerts))
stopAppliedCh := make(chan models.AlertDefinitionKey, len(alerts)) stopAppliedCh := make(chan models.AlertRuleKey, len(alerts))
mockedClock := clock.NewMock() mockedClock := clock.NewMock()
baseInterval := time.Second baseInterval := time.Second
@@ -125,14 +125,15 @@ func TestAlertingTicker(t *testing.T) {
schedCfg := schedule.SchedulerCfg{ schedCfg := schedule.SchedulerCfg{
C: mockedClock, C: mockedClock,
BaseInterval: baseInterval, BaseInterval: baseInterval,
EvalAppliedFunc: func(alertDefKey models.AlertDefinitionKey, now time.Time) { EvalAppliedFunc: func(alertDefKey models.AlertRuleKey, now time.Time) {
evalAppliedCh <- evalAppliedInfo{alertDefKey: alertDefKey, now: now} evalAppliedCh <- evalAppliedInfo{alertDefKey: alertDefKey, now: now}
}, },
StopAppliedFunc: func(alertDefKey models.AlertDefinitionKey) { StopAppliedFunc: func(alertDefKey models.AlertRuleKey) {
stopAppliedCh <- alertDefKey stopAppliedCh <- alertDefKey
}, },
Store: dbstore, Store: dbstore,
Logger: log.New("ngalert schedule test"), RuleStore: dbstore,
Logger: log.New("ngalert schedule test"),
} }
sched := schedule.NewScheduler(schedCfg, nil) sched := schedule.NewScheduler(schedCfg, nil)
@@ -145,101 +146,69 @@ func TestAlertingTicker(t *testing.T) {
}() }()
runtime.Gosched() runtime.Gosched()
expectedAlertDefinitionsEvaluated := []models.AlertDefinitionKey{alerts[1].GetKey()} expectedAlertRulesEvaluated := []models.AlertRuleKey{alerts[1].GetKey()}
t.Run(fmt.Sprintf("on 1st tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) { t.Run(fmt.Sprintf("on 1st tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock) tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...) assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
}) })
// change alert definition interval to three seconds // change alert rule interval to three seconds
var threeSecInterval int64 = 3 var threeSecInterval int64 = 3
err := dbstore.UpdateAlertDefinition(&models.UpdateAlertDefinitionCommand{ alerts[0] = updateTestAlertRuleIntervalSeconds(t, dbstore, alerts[0], threeSecInterval)
UID: alerts[0].UID, t.Logf("alert rule: %v interval reset to: %d", alerts[0].GetKey(), threeSecInterval)
IntervalSeconds: &threeSecInterval,
OrgID: alerts[0].OrgID, expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[1].GetKey()}
t.Run(fmt.Sprintf("on 2nd tick alert rule: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
}) })
expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[1].GetKey(), alerts[0].GetKey()}
t.Run(fmt.Sprintf("on 3rd tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
})
expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[1].GetKey()}
t.Run(fmt.Sprintf("on 4th tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
})
err := dbstore.DeleteAlertRuleByUID(alerts[1].OrgID, alerts[1].UID)
require.NoError(t, err) require.NoError(t, err)
t.Logf("alert definition: %v interval reset to: %d", alerts[0].GetKey(), threeSecInterval) t.Logf("alert rule: %v deleted", alerts[1].GetKey())
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[1].GetKey()} expectedAlertRulesEvaluated = []models.AlertRuleKey{}
t.Run(fmt.Sprintf("on 2nd tick alert definition: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) { t.Run(fmt.Sprintf("on 5th tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock) tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...) assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
})
expectedAlertRulesStopped := []models.AlertRuleKey{alerts[1].GetKey()}
t.Run(fmt.Sprintf("on 5th tick alert rules: %s should be stopped", concatenate(expectedAlertRulesStopped)), func(t *testing.T) {
assertStopRun(t, stopAppliedCh, expectedAlertRulesStopped...)
}) })
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[1].GetKey(), alerts[0].GetKey()} expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[0].GetKey()}
t.Run(fmt.Sprintf("on 3rd tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) { t.Run(fmt.Sprintf("on 6th tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock) tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...) assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
}) })
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[1].GetKey()} // create alert rule with one second interval
t.Run(fmt.Sprintf("on 4th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) { alerts = append(alerts, createTestAlertRule(t, dbstore, 1))
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) tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...) assertEvalRun(t, evalAppliedCh, tick, expectedAlertRulesEvaluated...)
})
err = dbstore.DeleteAlertDefinitionByUID(&models.DeleteAlertDefinitionByUIDCommand{UID: alerts[1].UID, OrgID: alerts[1].OrgID})
require.NoError(t, err)
t.Logf("alert definition: %v deleted", alerts[1].GetKey())
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{}
t.Run(fmt.Sprintf("on 5th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
})
expectedAlertDefinitionsStopped := []models.AlertDefinitionKey{alerts[1].GetKey()}
t.Run(fmt.Sprintf("on 5th tick alert definitions: %s should be stopped", concatenate(expectedAlertDefinitionsStopped)), func(t *testing.T) {
assertStopRun(t, stopAppliedCh, expectedAlertDefinitionsStopped...)
})
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[0].GetKey()}
t.Run(fmt.Sprintf("on 6th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
})
// create alert definition with one second interval
alerts = append(alerts, createTestAlertDefinition(t, dbstore, 1))
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[2].GetKey()}
t.Run(fmt.Sprintf("on 7th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
})
// pause alert definition
err = dbstore.UpdateAlertDefinitionPaused(&models.UpdateAlertDefinitionPausedCommand{UIDs: []string{alerts[2].UID}, OrgID: alerts[2].OrgID, Paused: true})
require.NoError(t, err)
t.Logf("alert definition: %v paused", alerts[2].GetKey())
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{}
t.Run(fmt.Sprintf("on 8th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
})
expectedAlertDefinitionsStopped = []models.AlertDefinitionKey{alerts[2].GetKey()}
t.Run(fmt.Sprintf("on 8th tick alert definitions: %s should be stopped", concatenate(expectedAlertDefinitionsStopped)), func(t *testing.T) {
assertStopRun(t, stopAppliedCh, expectedAlertDefinitionsStopped...)
})
// unpause alert definition
err = dbstore.UpdateAlertDefinitionPaused(&models.UpdateAlertDefinitionPausedCommand{UIDs: []string{alerts[2].UID}, OrgID: alerts[2].OrgID, Paused: false})
require.NoError(t, err)
t.Logf("alert definition: %v unpaused", alerts[2].GetKey())
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[0].GetKey(), alerts[2].GetKey()}
t.Run(fmt.Sprintf("on 9th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
tick := advanceClock(t, mockedClock)
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
}) })
} }
func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys ...models.AlertDefinitionKey) { func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys ...models.AlertRuleKey) {
timeout := time.After(time.Second) timeout := time.After(time.Second)
expected := make(map[models.AlertDefinitionKey]struct{}, len(keys)) expected := make(map[models.AlertRuleKey]struct{}, len(keys))
for _, k := range keys { for _, k := range keys {
expected[k] = struct{}{} expected[k] = struct{}{}
} }
@@ -248,7 +217,7 @@ func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys
select { select {
case info := <-ch: case info := <-ch:
_, ok := expected[info.alertDefKey] _, ok := expected[info.alertDefKey]
t.Logf("alert definition: %v evaluated at: %v", info.alertDefKey, info.now) t.Logf("alert rule: %v evaluated at: %v", info.alertDefKey, info.now)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, tick, info.now) assert.Equal(t, tick, info.now)
delete(expected, info.alertDefKey) delete(expected, info.alertDefKey)
@@ -264,10 +233,10 @@ func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys
} }
} }
func assertStopRun(t *testing.T, ch <-chan models.AlertDefinitionKey, keys ...models.AlertDefinitionKey) { func assertStopRun(t *testing.T, ch <-chan models.AlertRuleKey, keys ...models.AlertRuleKey) {
timeout := time.After(time.Second) timeout := time.After(time.Second)
expected := make(map[models.AlertDefinitionKey]struct{}, len(keys)) expected := make(map[models.AlertRuleKey]struct{}, len(keys))
for _, k := range keys { for _, k := range keys {
expected[k] = struct{}{} expected[k] = struct{}{}
} }
@@ -276,7 +245,7 @@ func assertStopRun(t *testing.T, ch <-chan models.AlertDefinitionKey, keys ...mo
select { select {
case alertDefKey := <-ch: case alertDefKey := <-ch:
_, ok := expected[alertDefKey] _, ok := expected[alertDefKey]
t.Logf("alert definition: %v stopped", alertDefKey) t.Logf("alert rule: %v stopped", alertDefKey)
assert.True(t, ok) assert.True(t, ok)
delete(expected, alertDefKey) delete(expected, alertDefKey)
if len(expected) == 0 { if len(expected) == 0 {
@@ -297,7 +266,7 @@ func advanceClock(t *testing.T, mockedClock *clock.Mock) time.Time {
// t.Logf("Tick: %v", mockedClock.Now()) // t.Logf("Tick: %v", mockedClock.Now())
} }
func concatenate(keys []models.AlertDefinitionKey) string { func concatenate(keys []models.AlertRuleKey) string {
s := make([]string, len(keys)) s := make([]string, len(keys))
for _, k := range keys { for _, k := range keys {
s = append(s, k.String()) s = append(s, k.String())
+84 -22
View File
@@ -7,6 +7,9 @@ import (
"testing" "testing"
"time" "time"
apimodels "github.com/grafana/alerting-api/pkg/api"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/ngalert"
@@ -61,30 +64,89 @@ func overrideAlertNGInRegistry(t *testing.T, cfg *setting.Cfg) ngalert.AlertNG {
return ng return ng
} }
// createTestAlertDefinition creates a dummy alert definition to be used by the tests. // createTestAlertRule creates a dummy alert definition to be used by the tests.
func createTestAlertDefinition(t *testing.T, store *store.DBstore, intervalSeconds int64) *models.AlertDefinition { func createTestAlertRule(t *testing.T, dbstore *store.DBstore, intervalSeconds int64) *models.AlertRule {
cmd := models.SaveAlertDefinitionCommand{ d := rand.Intn(1000)
OrgID: 1, ruleGroup := fmt.Sprintf("ruleGroup-%d", d)
Title: fmt.Sprintf("an alert definition %d", rand.Intn(1000)), err := dbstore.UpdateRuleGroup(store.UpdateRuleGroupCmd{
Condition: "A", OrgID: 1,
Data: []models.AlertQuery{ NamespaceUID: "namespace",
{ RuleGroupConfig: apimodels.PostableRuleGroupConfig{
Model: json.RawMessage(`{ Name: ruleGroup,
"datasource": "__expr__", Interval: model.Duration(time.Duration(intervalSeconds) * time.Second),
"type":"math", Rules: []apimodels.PostableExtendedRuleNode{
"expression":"2 + 2 > 1" {
}`), GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
RelativeTimeRange: models.RelativeTimeRange{ OrgID: 1,
From: models.Duration(5 * time.Hour), Title: fmt.Sprintf("an alert definition %d", d),
To: models.Duration(3 * time.Hour), Condition: "A",
Data: []models.AlertQuery{
{
Model: json.RawMessage(`{
"datasource": "__expr__",
"type":"math",
"expression":"2 + 2 > 1"
}`),
RelativeTimeRange: models.RelativeTimeRange{
From: models.Duration(5 * time.Hour),
To: models.Duration(3 * time.Hour),
},
RefID: "A",
},
},
},
}, },
RefID: "A",
}, },
}, },
IntervalSeconds: &intervalSeconds, })
}
err := store.SaveAlertDefinition(&cmd)
require.NoError(t, err) require.NoError(t, err)
t.Logf("alert definition: %v with interval: %d created", cmd.Result.GetKey(), intervalSeconds)
return cmd.Result q := models.ListRuleGroupAlertRulesQuery{
OrgID: 1,
NamespaceUID: "namespace",
RuleGroup: ruleGroup,
}
err = dbstore.GetRuleGroupAlertRules(&q)
require.NoError(t, err)
require.NotEmpty(t, q.Result)
rule := q.Result[0]
t.Logf("alert definition: %v with interval: %d created", rule.GetKey(), rule.IntervalSeconds)
return rule
}
// updateTestAlertRule update a dummy alert definition to be used by the tests.
func updateTestAlertRuleIntervalSeconds(t *testing.T, dbstore *store.DBstore, existingRule *models.AlertRule, intervalSeconds int64) *models.AlertRule {
cmd := store.UpdateRuleGroupCmd{
OrgID: 1,
NamespaceUID: "namespace",
RuleGroupConfig: apimodels.PostableRuleGroupConfig{
Name: existingRule.RuleGroup,
Interval: model.Duration(time.Duration(intervalSeconds) * time.Second),
Rules: []apimodels.PostableExtendedRuleNode{
{
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
OrgID: 1,
UID: existingRule.UID,
},
},
},
},
}
err := dbstore.UpdateRuleGroup(cmd)
require.NoError(t, err)
q := models.ListRuleGroupAlertRulesQuery{
OrgID: 1,
NamespaceUID: "namespace",
RuleGroup: existingRule.RuleGroup,
}
err = dbstore.GetRuleGroupAlertRules(&q)
require.NoError(t, err)
require.NotEmpty(t, q.Result)
rule := q.Result[0]
t.Logf("alert definition: %v with interval: %d created", rule.GetKey(), rule.IntervalSeconds)
return rule
} }