mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Update state manager to change all current states in the case when Error\NoData is executed as Ok\Nomal (#68142)
This commit is contained in:
parent
2848be9035
commit
0717ec11d6
@ -119,4 +119,5 @@ export interface FeatureToggles {
|
|||||||
configurableSchedulerTick?: boolean;
|
configurableSchedulerTick?: boolean;
|
||||||
influxdbSqlSupport?: boolean;
|
influxdbSqlSupport?: boolean;
|
||||||
noBasicRole?: boolean;
|
noBasicRole?: boolean;
|
||||||
|
alertingNoDataErrorExecution?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -699,5 +699,13 @@ var (
|
|||||||
Owner: grafanaAuthnzSquad,
|
Owner: grafanaAuthnzSquad,
|
||||||
RequiresRestart: true,
|
RequiresRestart: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "alertingNoDataErrorExecution",
|
||||||
|
Description: "Changes how Alerting state manager handles execution of NoData/Error",
|
||||||
|
Stage: FeatureStagePrivatePreview,
|
||||||
|
FrontendOnly: false,
|
||||||
|
Owner: grafanaAlertingSquad,
|
||||||
|
RequiresRestart: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -100,3 +100,4 @@ prometheusConfigOverhaulAuth,experimental,@grafana/observability-metrics,false,f
|
|||||||
configurableSchedulerTick,experimental,@grafana/alerting-squad,false,false,true,false
|
configurableSchedulerTick,experimental,@grafana/alerting-squad,false,false,true,false
|
||||||
influxdbSqlSupport,experimental,@grafana/observability-metrics,false,false,false,false
|
influxdbSqlSupport,experimental,@grafana/observability-metrics,false,false,false,false
|
||||||
noBasicRole,experimental,@grafana/grafana-authnz-team,false,false,true,true
|
noBasicRole,experimental,@grafana/grafana-authnz-team,false,false,true,true
|
||||||
|
alertingNoDataErrorExecution,privatePreview,@grafana/alerting-squad,false,false,true,false
|
||||||
|
|
@ -410,4 +410,8 @@ const (
|
|||||||
// FlagNoBasicRole
|
// FlagNoBasicRole
|
||||||
// Enables a new role that has no permissions by default
|
// Enables a new role that has no permissions by default
|
||||||
FlagNoBasicRole = "noBasicRole"
|
FlagNoBasicRole = "noBasicRole"
|
||||||
|
|
||||||
|
// FlagAlertingNoDataErrorExecution
|
||||||
|
// Changes how Alerting state manager handles execution of NoData/Error
|
||||||
|
FlagAlertingNoDataErrorExecution = "alertingNoDataErrorExecution"
|
||||||
)
|
)
|
||||||
|
@ -142,6 +142,7 @@ type ExecutionResults struct {
|
|||||||
// Results is a slice of evaluated alert instances states.
|
// Results is a slice of evaluated alert instances states.
|
||||||
type Results []Result
|
type Results []Result
|
||||||
|
|
||||||
|
// HasErrors returns true when Results contains at least one element with error
|
||||||
func (evalResults Results) HasErrors() bool {
|
func (evalResults Results) HasErrors() bool {
|
||||||
for _, r := range evalResults {
|
for _, r := range evalResults {
|
||||||
if r.State == Error {
|
if r.State == Error {
|
||||||
@ -151,6 +152,26 @@ func (evalResults Results) HasErrors() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasErrors returns true when Results contains at least one element and all elements are errors
|
||||||
|
func (evalResults Results) IsError() bool {
|
||||||
|
for _, r := range evalResults {
|
||||||
|
if r.State != Error {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(evalResults) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNoData returns true when all items are NoData or Results is empty
|
||||||
|
func (evalResults Results) IsNoData() bool {
|
||||||
|
for _, result := range evalResults {
|
||||||
|
if result.State != NoData {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Result contains the evaluated State of an alert instance
|
// Result contains the evaluated State of an alert instance
|
||||||
// identified by its labels.
|
// identified by its labels.
|
||||||
type Result struct {
|
type Result struct {
|
||||||
|
@ -212,14 +212,15 @@ func (ng *AlertNG) init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cfg := state.ManagerCfg{
|
cfg := state.ManagerCfg{
|
||||||
Metrics: ng.Metrics.GetStateMetrics(),
|
Metrics: ng.Metrics.GetStateMetrics(),
|
||||||
ExternalURL: appUrl,
|
ExternalURL: appUrl,
|
||||||
InstanceStore: ng.store,
|
InstanceStore: ng.store,
|
||||||
Images: ng.ImageService,
|
Images: ng.ImageService,
|
||||||
Clock: clk,
|
Clock: clk,
|
||||||
Historian: history,
|
Historian: history,
|
||||||
DoNotSaveNormalState: ng.FeatureToggles.IsEnabled(featuremgmt.FlagAlertingNoNormalState),
|
DoNotSaveNormalState: ng.FeatureToggles.IsEnabled(featuremgmt.FlagAlertingNoNormalState),
|
||||||
MaxStateSaveConcurrency: ng.Cfg.UnifiedAlerting.MaxStateSaveConcurrency,
|
MaxStateSaveConcurrency: ng.Cfg.UnifiedAlerting.MaxStateSaveConcurrency,
|
||||||
|
ApplyNoDataAndErrorToAllStates: ng.FeatureToggles.IsEnabled(featuremgmt.FlagAlertingNoDataErrorExecution),
|
||||||
}
|
}
|
||||||
stateManager := state.NewManager(cfg)
|
stateManager := state.NewManager(cfg)
|
||||||
scheduler := schedule.NewScheduler(schedCfg, stateManager)
|
scheduler := schedule.NewScheduler(schedCfg, stateManager)
|
||||||
|
@ -40,8 +40,9 @@ type Manager struct {
|
|||||||
historian Historian
|
historian Historian
|
||||||
externalURL *url.URL
|
externalURL *url.URL
|
||||||
|
|
||||||
doNotSaveNormalState bool
|
doNotSaveNormalState bool
|
||||||
maxStateSaveConcurrency int
|
maxStateSaveConcurrency int
|
||||||
|
applyNoDataAndErrorToAllStates bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ManagerCfg struct {
|
type ManagerCfg struct {
|
||||||
@ -55,25 +56,33 @@ type ManagerCfg struct {
|
|||||||
DoNotSaveNormalState bool
|
DoNotSaveNormalState bool
|
||||||
// MaxStateSaveConcurrency controls the number of goroutines (per rule) that can save alert state in parallel.
|
// MaxStateSaveConcurrency controls the number of goroutines (per rule) that can save alert state in parallel.
|
||||||
MaxStateSaveConcurrency int
|
MaxStateSaveConcurrency int
|
||||||
|
|
||||||
|
// ApplyNoDataAndErrorToAllStates makes state manager to apply exceptional results (NoData and Error)
|
||||||
|
// to all states when corresponding execution in the rule definition is set to either `Alerting` or `OK`
|
||||||
|
ApplyNoDataAndErrorToAllStates bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(cfg ManagerCfg) *Manager {
|
func NewManager(cfg ManagerCfg) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
cache: newCache(),
|
cache: newCache(),
|
||||||
ResendDelay: ResendDelay, // TODO: make this configurable
|
ResendDelay: ResendDelay, // TODO: make this configurable
|
||||||
log: log.New("ngalert.state.manager"),
|
log: log.New("ngalert.state.manager"),
|
||||||
metrics: cfg.Metrics,
|
metrics: cfg.Metrics,
|
||||||
instanceStore: cfg.InstanceStore,
|
instanceStore: cfg.InstanceStore,
|
||||||
images: cfg.Images,
|
images: cfg.Images,
|
||||||
historian: cfg.Historian,
|
historian: cfg.Historian,
|
||||||
clock: cfg.Clock,
|
clock: cfg.Clock,
|
||||||
externalURL: cfg.ExternalURL,
|
externalURL: cfg.ExternalURL,
|
||||||
doNotSaveNormalState: cfg.DoNotSaveNormalState,
|
doNotSaveNormalState: cfg.DoNotSaveNormalState,
|
||||||
maxStateSaveConcurrency: cfg.MaxStateSaveConcurrency,
|
maxStateSaveConcurrency: cfg.MaxStateSaveConcurrency,
|
||||||
|
applyNoDataAndErrorToAllStates: cfg.ApplyNoDataAndErrorToAllStates,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *Manager) Run(ctx context.Context) error {
|
func (st *Manager) Run(ctx context.Context) error {
|
||||||
|
if st.applyNoDataAndErrorToAllStates {
|
||||||
|
st.log.Info("Running in alternative execution of Error/NoData mode")
|
||||||
|
}
|
||||||
ticker := st.clock.Ticker(MetricsScrapeInterval)
|
ticker := st.clock.Ticker(MetricsScrapeInterval)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -244,12 +253,8 @@ func (st *Manager) ResetStateByRuleUID(ctx context.Context, rule *ngModels.Alert
|
|||||||
func (st *Manager) ProcessEvalResults(ctx context.Context, evaluatedAt time.Time, alertRule *ngModels.AlertRule, results eval.Results, extraLabels data.Labels) []StateTransition {
|
func (st *Manager) ProcessEvalResults(ctx context.Context, evaluatedAt time.Time, alertRule *ngModels.AlertRule, results eval.Results, extraLabels data.Labels) []StateTransition {
|
||||||
logger := st.log.FromContext(ctx)
|
logger := st.log.FromContext(ctx)
|
||||||
logger.Debug("State manager processing evaluation results", "resultCount", len(results))
|
logger.Debug("State manager processing evaluation results", "resultCount", len(results))
|
||||||
states := make([]StateTransition, 0, len(results))
|
states := st.setNextStateForRule(ctx, alertRule, results, extraLabels, logger)
|
||||||
|
|
||||||
for _, result := range results {
|
|
||||||
s := st.setNextState(ctx, alertRule, result, extraLabels, logger)
|
|
||||||
states = append(states, s)
|
|
||||||
}
|
|
||||||
staleStates := st.deleteStaleStatesFromCache(ctx, logger, evaluatedAt, alertRule)
|
staleStates := st.deleteStaleStatesFromCache(ctx, logger, evaluatedAt, alertRule)
|
||||||
st.deleteAlertStates(ctx, logger, staleStates)
|
st.deleteAlertStates(ctx, logger, staleStates)
|
||||||
|
|
||||||
@ -262,10 +267,42 @@ func (st *Manager) ProcessEvalResults(ctx context.Context, evaluatedAt time.Time
|
|||||||
return allChanges
|
return allChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current state based on evaluation results
|
func (st *Manager) setNextStateForRule(ctx context.Context, alertRule *ngModels.AlertRule, results eval.Results, extraLabels data.Labels, logger log.Logger) []StateTransition {
|
||||||
func (st *Manager) setNextState(ctx context.Context, alertRule *ngModels.AlertRule, result eval.Result, extraLabels data.Labels, logger log.Logger) StateTransition {
|
if st.applyNoDataAndErrorToAllStates && results.IsNoData() && (alertRule.NoDataState == ngModels.Alerting || alertRule.NoDataState == ngModels.OK) { // If it is no data, check the mapping and switch all results to the new state
|
||||||
currentState := st.cache.getOrCreate(ctx, logger, alertRule, result, extraLabels, st.externalURL)
|
// TODO aggregate UID of datasources that returned NoData into one and provide as auxiliary info, probably annotation
|
||||||
|
transitions := st.setNextStateForAll(ctx, alertRule, results[0], logger)
|
||||||
|
if len(transitions) > 0 {
|
||||||
|
return transitions // if there are no current states for the rule. Create ones for each result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if st.applyNoDataAndErrorToAllStates && results.IsError() && (alertRule.ExecErrState == ngModels.AlertingErrState || alertRule.ExecErrState == ngModels.OkErrState) {
|
||||||
|
// TODO squash all errors into one, and provide as annotation
|
||||||
|
transitions := st.setNextStateForAll(ctx, alertRule, results[0], logger)
|
||||||
|
if len(transitions) > 0 {
|
||||||
|
return transitions // if there are no current states for the rule. Create ones for each result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transitions := make([]StateTransition, 0, len(results))
|
||||||
|
for _, result := range results {
|
||||||
|
currentState := st.cache.getOrCreate(ctx, logger, alertRule, result, extraLabels, st.externalURL)
|
||||||
|
s := st.setNextState(ctx, alertRule, currentState, result, logger)
|
||||||
|
transitions = append(transitions, s)
|
||||||
|
}
|
||||||
|
return transitions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Manager) setNextStateForAll(ctx context.Context, alertRule *ngModels.AlertRule, result eval.Result, logger log.Logger) []StateTransition {
|
||||||
|
currentStates := st.cache.getStatesForRuleUID(alertRule.OrgID, alertRule.UID, false)
|
||||||
|
transitions := make([]StateTransition, 0, len(currentStates))
|
||||||
|
for _, currentState := range currentStates {
|
||||||
|
t := st.setNextState(ctx, alertRule, currentState, result, logger)
|
||||||
|
transitions = append(transitions, t)
|
||||||
|
}
|
||||||
|
return transitions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the current state based on evaluation results
|
||||||
|
func (st *Manager) setNextState(ctx context.Context, alertRule *ngModels.AlertRule, currentState *State, result eval.Result, logger log.Logger) StateTransition {
|
||||||
currentState.LastEvaluationTime = result.EvaluatedAt
|
currentState.LastEvaluationTime = result.EvaluatedAt
|
||||||
currentState.EvaluationDuration = result.EvaluationDuration
|
currentState.EvaluationDuration = result.EvaluationDuration
|
||||||
currentState.Results = append(currentState.Results, Evaluation{
|
currentState.Results = append(currentState.Results, Evaluation{
|
||||||
@ -288,7 +325,7 @@ func (st *Manager) setNextState(ctx context.Context, alertRule *ngModels.AlertRu
|
|||||||
// Usually, it happens in the case of classic conditions when the evalResult does not have labels.
|
// Usually, it happens in the case of classic conditions when the evalResult does not have labels.
|
||||||
//
|
//
|
||||||
// This is temporary change to make sure that the labels are not persistent in the state after it was in Error state
|
// This is temporary change to make sure that the labels are not persistent in the state after it was in Error state
|
||||||
// TODO yuri. Remove it in https://github.com/grafana/grafana/pull/68142
|
// TODO yuri. Remove it when correct Error result with labels is provided
|
||||||
if currentState.State == eval.Error && result.State != eval.Error {
|
if currentState.State == eval.Error && result.State != eval.Error {
|
||||||
// This is possible because state was updated after the CacheID was calculated.
|
// This is possible because state was updated after the CacheID was calculated.
|
||||||
_, curOk := currentState.Labels["ref_id"]
|
_, curOk := currentState.Labels["ref_id"]
|
||||||
|
@ -194,6 +194,8 @@ func TestManager_saveAlertStates(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// t1[1:normal] t2[1:alerting] and 'for'=2 at t2
|
// t1[1:normal] t2[1:alerting] and 'for'=2 at t2
|
||||||
// t1[{}:alerting] t2[{}:normal] t3[NoData] at t2,t3
|
// t1[{}:alerting] t2[{}:normal] t3[NoData] at t2,t3
|
||||||
|
//
|
||||||
|
//nolint:gocyclo
|
||||||
func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||||
evaluationDuration := 10 * time.Millisecond
|
evaluationDuration := 10 * time.Millisecond
|
||||||
evaluationInterval := 10 * time.Second
|
evaluationInterval := 10 * time.Second
|
||||||
@ -304,7 +306,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executeTest := func(t *testing.T, alertRule *ngmodels.AlertRule, resultsAtTime map[time.Time]eval.Results, expectedTransitionsAtTime map[time.Time][]StateTransition) {
|
executeTest := func(t *testing.T, alertRule *ngmodels.AlertRule, resultsAtTime map[time.Time]eval.Results, expectedTransitionsAtTime map[time.Time][]StateTransition, applyNoDataErrorToAllStates bool) {
|
||||||
clk := clock.NewMock()
|
clk := clock.NewMock()
|
||||||
|
|
||||||
cfg := ManagerCfg{
|
cfg := ManagerCfg{
|
||||||
@ -315,6 +317,8 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
Clock: clk,
|
Clock: clk,
|
||||||
Historian: &FakeHistorian{},
|
Historian: &FakeHistorian{},
|
||||||
MaxStateSaveConcurrency: 1,
|
MaxStateSaveConcurrency: 1,
|
||||||
|
|
||||||
|
ApplyNoDataAndErrorToAllStates: applyNoDataErrorToAllStates,
|
||||||
}
|
}
|
||||||
st := NewManager(cfg)
|
st := NewManager(cfg)
|
||||||
|
|
||||||
@ -856,7 +860,12 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
executeTest(t, tc.alertRule, tc.results, tc.expectedTransitions)
|
t.Run("applyNoDataErrorToAllStates=true", func(t *testing.T) {
|
||||||
|
executeTest(t, tc.alertRule, tc.results, tc.expectedTransitions, true)
|
||||||
|
})
|
||||||
|
t.Run("applyNoDataErrorToAllStates=false", func(t *testing.T) {
|
||||||
|
executeTest(t, tc.alertRule, tc.results, tc.expectedTransitions, false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -872,6 +881,8 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
ruleMutators []ngmodels.AlertRuleMutator
|
ruleMutators []ngmodels.AlertRuleMutator
|
||||||
results map[time.Time]eval.Results
|
results map[time.Time]eval.Results
|
||||||
expectedTransitions map[ngmodels.NoDataState]map[time.Time][]StateTransition
|
expectedTransitions map[ngmodels.NoDataState]map[time.Time][]StateTransition
|
||||||
|
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates map[ngmodels.NoDataState]map[time.Time][]StateTransition
|
||||||
}
|
}
|
||||||
|
|
||||||
executeForEachRule := func(t *testing.T, tc noDataTestCase) {
|
executeForEachRule := func(t *testing.T, tc noDataTestCase) {
|
||||||
@ -885,11 +896,25 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Run(fmt.Sprintf("execute as %s", stateExec), func(t *testing.T) {
|
t.Run(fmt.Sprintf("execute as %s", stateExec), func(t *testing.T) {
|
||||||
expectedTransitions, ok := tc.expectedTransitions[stateExec]
|
expectedTransitions, ok := tc.expectedTransitionsApplyNoDataErrorToAllStates[stateExec]
|
||||||
|
overridden := "[*]"
|
||||||
|
if !ok {
|
||||||
|
expectedTransitions, ok = tc.expectedTransitions[stateExec]
|
||||||
|
overridden = ""
|
||||||
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
require.Fail(t, "no expected state transitions")
|
require.Fail(t, "no expected state transitions")
|
||||||
}
|
}
|
||||||
executeTest(t, r, tc.results, expectedTransitions)
|
t.Run("applyNoDataErrorToAllStates=true"+overridden, func(t *testing.T) {
|
||||||
|
executeTest(t, r, tc.results, expectedTransitions, true)
|
||||||
|
})
|
||||||
|
t.Run("applyNoDataErrorToAllStates=false", func(t *testing.T) {
|
||||||
|
expectedTransitions, ok := tc.expectedTransitions[stateExec]
|
||||||
|
if !ok {
|
||||||
|
require.Fail(t, "no expected state transitions")
|
||||||
|
}
|
||||||
|
executeTest(t, r, tc.results, expectedTransitions, false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1023,9 +1048,49 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.Alerting: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OK: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t1,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "t1[1:normal,2:alerting] t2[NoData] t3[NoData] at t3",
|
desc: "t1[1:normal,2:alerting] t2[NoData] t3[NoData] at t2,t3",
|
||||||
results: map[time.Time]eval.Results{
|
results: map[time.Time]eval.Results{
|
||||||
t1: {
|
t1: {
|
||||||
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
|
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
|
||||||
@ -1040,6 +1105,21 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedTransitions: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
expectedTransitions: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
ngmodels.NoData: {
|
ngmodels.NoData: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + no-data"],
|
||||||
|
State: eval.NoData,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
t3: {
|
t3: {
|
||||||
{
|
{
|
||||||
PreviousState: eval.Normal,
|
PreviousState: eval.Normal,
|
||||||
@ -1185,6 +1265,149 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.Alerting: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreviousState: eval.Alerting,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels2"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Alerting),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Alerting,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreviousState: eval.Alerting,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels2"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Alerting),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OK: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t1,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreviousState: eval.Alerting,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels2"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Alerting),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
Resolved: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t1,
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels2"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Alerting),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2,
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "t1[1:normal,2:alerting] t2[NoData] t3[NoData] and 'for'=1 at t2*,t3",
|
desc: "t1[1:normal,2:alerting] t2[NoData] t3[NoData] and 'for'=1 at t2*,t3",
|
||||||
@ -1203,6 +1426,21 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedTransitions: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
expectedTransitions: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
ngmodels.NoData: {
|
ngmodels.NoData: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + no-data"],
|
||||||
|
State: eval.NoData,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
t3: {
|
t3: {
|
||||||
{
|
{
|
||||||
PreviousState: eval.Normal,
|
PreviousState: eval.Normal,
|
||||||
@ -1358,6 +1596,136 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.Alerting: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Pending,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreviousState: eval.Pending,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels2"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Pending,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t3,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreviousState: eval.Alerting,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels2"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OK: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t1,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreviousState: eval.Pending,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels2"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t1,
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels2"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2,
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "t1[1:alerting] t2[NoData] t3[1:alerting] and 'for'=2 at t3",
|
desc: "t1[1:alerting] t2[NoData] t3[1:alerting] and 'for'=2 at t3",
|
||||||
@ -1429,6 +1797,46 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.Alerting: {
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Pending,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.Alerting),
|
||||||
|
},
|
||||||
|
StartsAt: t3,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OK: {
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Pending,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.Alerting),
|
||||||
|
},
|
||||||
|
StartsAt: t3,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "t1[NoData] t2[1:normal] t3[1:normal] at t3",
|
desc: "t1[NoData] t2[1:normal] t3[1:normal] at t3",
|
||||||
@ -1610,6 +2018,46 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.Alerting: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OK: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t1,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "t1[{}:alerting] t2[NoData] t3[NoData] at t3",
|
desc: "t1[{}:alerting] t2[NoData] t3[NoData] at t3",
|
||||||
@ -1626,6 +2074,21 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedTransitions: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
expectedTransitions: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
ngmodels.NoData: {
|
ngmodels.NoData: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + no-data"],
|
||||||
|
State: eval.NoData,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
t3: {
|
t3: {
|
||||||
{
|
{
|
||||||
PreviousState: eval.Alerting,
|
PreviousState: eval.Alerting,
|
||||||
@ -1729,9 +2192,88 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.Alerting: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Alerting,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Alerting),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Alerting,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Alerting),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OK: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Alerting,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Alerting),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
Resolved: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.NoData.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Alerting),
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2,
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "t1[{}:alerting] t2[NoData] t3[{}:alerting] and 'for'=2 at t3",
|
desc: "t1[{}:alerting] t2[NoData] t3[{}:alerting] and 'for'=2 at t2*,t3",
|
||||||
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(2)},
|
ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(2)},
|
||||||
results: map[time.Time]eval.Results{
|
results: map[time.Time]eval.Results{
|
||||||
t1: {
|
t1: {
|
||||||
@ -1746,6 +2288,21 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedTransitions: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
expectedTransitions: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
ngmodels.NoData: {
|
ngmodels.NoData: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + no-data"],
|
||||||
|
State: eval.NoData,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
t3: {
|
t3: {
|
||||||
{
|
{
|
||||||
PreviousState: eval.Pending,
|
PreviousState: eval.Pending,
|
||||||
@ -1800,6 +2357,46 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.NoDataState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.Alerting: {
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Pending,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.Alerting),
|
||||||
|
},
|
||||||
|
StartsAt: t3,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OK: {
|
||||||
|
t3: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
PreviousStateReason: eval.NoData.String(),
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule"],
|
||||||
|
State: eval.Pending,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.NoData),
|
||||||
|
newEvaluation(t3, eval.Alerting),
|
||||||
|
},
|
||||||
|
StartsAt: t3,
|
||||||
|
EndsAt: t3.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1831,6 +2428,8 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
ruleMutators []ngmodels.AlertRuleMutator
|
ruleMutators []ngmodels.AlertRuleMutator
|
||||||
results map[time.Time]eval.Results
|
results map[time.Time]eval.Results
|
||||||
expectedTransitions map[ngmodels.ExecutionErrorState]map[time.Time][]StateTransition
|
expectedTransitions map[ngmodels.ExecutionErrorState]map[time.Time][]StateTransition
|
||||||
|
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates map[ngmodels.ExecutionErrorState]map[time.Time][]StateTransition
|
||||||
}
|
}
|
||||||
|
|
||||||
executeForEachRule := func(t *testing.T, tc errorTestCase) {
|
executeForEachRule := func(t *testing.T, tc errorTestCase) {
|
||||||
@ -1844,11 +2443,25 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Run(fmt.Sprintf("execute as %s", stateExec), func(t *testing.T) {
|
t.Run(fmt.Sprintf("execute as %s", stateExec), func(t *testing.T) {
|
||||||
expectedTransitions, ok := tc.expectedTransitions[stateExec]
|
expectedTransitions, ok := tc.expectedTransitionsApplyNoDataErrorToAllStates[stateExec]
|
||||||
|
overridden := "[*]"
|
||||||
|
if !ok {
|
||||||
|
expectedTransitions, ok = tc.expectedTransitions[stateExec]
|
||||||
|
overridden = ""
|
||||||
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
require.Fail(t, "no expected state transitions")
|
require.Fail(t, "no expected state transitions")
|
||||||
}
|
}
|
||||||
executeTest(t, r, tc.results, expectedTransitions)
|
t.Run("applyNoDataErrorToAllStates=true"+overridden, func(t *testing.T) {
|
||||||
|
executeTest(t, r, tc.results, expectedTransitions, true)
|
||||||
|
})
|
||||||
|
t.Run("applyNoDataErrorToAllStates=false", func(t *testing.T) {
|
||||||
|
expectedTransitions, ok := tc.expectedTransitions[stateExec]
|
||||||
|
if !ok {
|
||||||
|
require.Fail(t, "no expected state transitions")
|
||||||
|
}
|
||||||
|
executeTest(t, r, tc.results, expectedTransitions, false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2063,6 +2676,45 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.ExecutionErrorState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.AlertingErrState: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Pending,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.Error.String(),
|
||||||
|
Error: datasourceError,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.Error),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OkErrState: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Pending,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.Error.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t2, eval.Error),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "t1[1:normal] t2[QueryError] at t2",
|
desc: "t1[1:normal] t2[QueryError] at t2",
|
||||||
@ -2135,6 +2787,47 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectedTransitionsApplyNoDataErrorToAllStates: map[ngmodels.ExecutionErrorState]map[time.Time][]StateTransition{
|
||||||
|
ngmodels.AlertingErrState: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Alerting,
|
||||||
|
StateReason: eval.Error.String(),
|
||||||
|
Error: datasourceError,
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.Error),
|
||||||
|
},
|
||||||
|
StartsAt: t2,
|
||||||
|
EndsAt: t2.Add(ResendDelay * 3),
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ngmodels.OkErrState: {
|
||||||
|
t2: {
|
||||||
|
{
|
||||||
|
PreviousState: eval.Normal,
|
||||||
|
State: &State{
|
||||||
|
Labels: labels["system + rule + labels1"],
|
||||||
|
State: eval.Normal,
|
||||||
|
StateReason: eval.Error.String(),
|
||||||
|
Results: []Evaluation{
|
||||||
|
newEvaluation(t1, eval.Normal),
|
||||||
|
newEvaluation(t2, eval.Error),
|
||||||
|
},
|
||||||
|
StartsAt: t1,
|
||||||
|
EndsAt: t1,
|
||||||
|
LastEvaluationTime: t2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "t1[QueryError] t2[1:normal] t3[1:normal] at t3",
|
desc: "t1[QueryError] t2[1:normal] t3[1:normal] at t3",
|
||||||
|
Loading…
Reference in New Issue
Block a user