Commit f0b591b8 by bergquist

feat(alerting): extract logic state updates and notifications

ref #6444
parent b8879113
...@@ -26,6 +26,7 @@ type EvalContext struct { ...@@ -26,6 +26,7 @@ type EvalContext struct {
ImagePublicUrl string ImagePublicUrl string
ImageOnDiskPath string ImageOnDiskPath string
NoDataFound bool NoDataFound bool
PrevAlertState m.AlertStateType
Ctx context.Context Ctx context.Context
} }
...@@ -63,6 +64,36 @@ func (c *EvalContext) GetStateModel() *StateDescription { ...@@ -63,6 +64,36 @@ func (c *EvalContext) GetStateModel() *StateDescription {
} }
} }
func (c *EvalContext) ShouldUpdateAlertState() bool {
return c.Rule.State != c.PrevAlertState
}
func (c *EvalContext) ShouldSendNotification() bool {
if (c.PrevAlertState == m.AlertStatePending) && (c.Rule.State == m.AlertStateOK) {
return false
}
alertState := c.Rule.State
if c.NoDataFound {
if c.Rule.NoDataState == m.NoDataKeepState {
return false
}
alertState = c.Rule.NoDataState.ToAlertState()
}
if c.Error != nil {
if c.Rule.ExecutionErrorState == m.NoDataKeepState {
return false
}
alertState = c.Rule.ExecutionErrorState.ToAlertState()
}
return alertState != c.PrevAlertState
}
func (a *EvalContext) GetDurationMs() float64 { func (a *EvalContext) GetDurationMs() float64 {
return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000) return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000)
} }
......
package alerting
import (
"context"
"fmt"
"testing"
"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestAlertingEvalContext(t *testing.T) {
Convey("Eval context", t, func() {
ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
err := fmt.Errorf("Dummie error!")
Convey("Should update alert state", func() {
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateAlerting
So(ctx.ShouldUpdateAlertState(), ShouldBeTrue)
})
Convey("ok -> ok", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateOK
So(ctx.ShouldUpdateAlertState(), ShouldBeFalse)
})
})
Convey("Should send notifications", func() {
Convey("pending -> ok", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Rule.State = models.AlertStateOK
So(ctx.ShouldSendNotification(), ShouldBeFalse)
})
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateAlerting
So(ctx.ShouldSendNotification(), ShouldBeTrue)
})
Convey("alerting -> ok", func() {
ctx.PrevAlertState = models.AlertStateAlerting
ctx.Rule.State = models.AlertStateOK
So(ctx.ShouldSendNotification(), ShouldBeTrue)
})
Convey("ok -> no_data(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataSetAlerting
ctx.Rule.State = models.AlertStateAlerting
So(ctx.ShouldSendNotification(), ShouldBeTrue)
})
Convey("ok -> no_data(ok)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataSetOK
ctx.NoDataFound = true
ctx.Rule.State = models.AlertStateNoData
So(ctx.ShouldSendNotification(), ShouldBeFalse)
})
Convey("ok -> no_data(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataKeepState
ctx.Rule.State = models.AlertStateNoData
ctx.NoDataFound = true
So(ctx.ShouldSendNotification(), ShouldBeFalse)
})
Convey("ok -> execution_error(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateExecError
ctx.Rule.ExecutionErrorState = models.NoDataSetAlerting
ctx.Error = err
So(ctx.ShouldSendNotification(), ShouldBeTrue)
})
Convey("ok -> execution_error(ok)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateExecError
ctx.Rule.ExecutionErrorState = models.NoDataSetOK
ctx.Error = err
So(ctx.ShouldSendNotification(), ShouldBeFalse)
})
Convey("ok -> execution_error(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateExecError
ctx.Rule.ExecutionErrorState = models.NoDataKeepState
ctx.Error = err
So(ctx.ShouldSendNotification(), ShouldBeFalse)
})
})
})
}
...@@ -28,7 +28,7 @@ func NewResultHandler() *DefaultResultHandler { ...@@ -28,7 +28,7 @@ func NewResultHandler() *DefaultResultHandler {
} }
func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
oldState := evalContext.Rule.State evalContext.PrevAlertState = evalContext.Rule.State
executionError := "" executionError := ""
annotationData := simplejson.New() annotationData := simplejson.New()
...@@ -51,8 +51,8 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { ...@@ -51,8 +51,8 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
} }
countStateResult(evalContext.Rule.State) countStateResult(evalContext.Rule.State)
if handler.shouldUpdateAlertState(evalContext, oldState) { if evalContext.ShouldUpdateAlertState() {
handler.log.Info("New state change", "alertId", evalContext.Rule.Id, "newState", evalContext.Rule.State, "oldState", oldState) handler.log.Info("New state change", "alertId", evalContext.Rule.Id, "newState", evalContext.Rule.State, "prev state", evalContext.PrevAlertState)
cmd := &m.SetAlertStateCommand{ cmd := &m.SetAlertStateCommand{
AlertId: evalContext.Rule.Id, AlertId: evalContext.Rule.Id,
...@@ -76,7 +76,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { ...@@ -76,7 +76,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
Title: evalContext.Rule.Name, Title: evalContext.Rule.Name,
Text: evalContext.GetStateModel().Text, Text: evalContext.GetStateModel().Text,
NewState: string(evalContext.Rule.State), NewState: string(evalContext.Rule.State),
PrevState: string(oldState), PrevState: string(evalContext.PrevAlertState),
Epoch: time.Now().Unix(), Epoch: time.Now().Unix(),
Data: annotationData, Data: annotationData,
} }
...@@ -86,21 +86,16 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { ...@@ -86,21 +86,16 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
handler.log.Error("Failed to save annotation for new alert state", "error", err) handler.log.Error("Failed to save annotation for new alert state", "error", err)
} }
if (oldState == m.AlertStatePending) && (evalContext.Rule.State == m.AlertStateOK) { if evalContext.ShouldSendNotification() {
handler.log.Info("Notfication not sent", "oldState", oldState, "newState", evalContext.Rule.State)
} else {
handler.notifier.Notify(evalContext) handler.notifier.Notify(evalContext)
} else {
handler.log.Info("Notfication not sent", "prev state", evalContext.PrevAlertState, "new state", evalContext.Rule.State)
} }
} }
return nil return nil
} }
func (handler *DefaultResultHandler) shouldUpdateAlertState(evalContext *EvalContext, oldState m.AlertStateType) bool {
return evalContext.Rule.State != oldState
}
func countStateResult(state m.AlertStateType) { func countStateResult(state m.AlertStateType) {
switch state { switch state {
case m.AlertStatePending: case m.AlertStatePending:
......
...@@ -11,17 +11,18 @@ import ( ...@@ -11,17 +11,18 @@ import (
) )
type Rule struct { type Rule struct {
Id int64 Id int64
OrgId int64 OrgId int64
DashboardId int64 DashboardId int64
PanelId int64 PanelId int64
Frequency int64 Frequency int64
Name string Name string
Message string Message string
NoDataState m.NoDataOption NoDataState m.NoDataOption
State m.AlertStateType ExecutionErrorState m.NoDataOption
Conditions []Condition State m.AlertStateType
Notifications []int64 Conditions []Condition
Notifications []int64
} }
type ValidationError struct { type ValidationError struct {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment