mirror of
https://github.com/grafana/grafana.git
synced 2024-11-30 12:44:10 -06:00
d354f3a8af
* fix(alerting): fixed evaluation for no_value condition, fixes #7244 * feat(alerting): moving null library into grafana, fixing handling on no value / no series
198 lines
5.6 KiB
Go
198 lines
5.6 KiB
Go
package conditions
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
"github.com/grafana/grafana/pkg/components/null"
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
m "github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/services/alerting"
|
|
"github.com/grafana/grafana/pkg/tsdb"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
)
|
|
|
|
func TestQueryCondition(t *testing.T) {
|
|
|
|
Convey("when evaluating query condition", t, func() {
|
|
|
|
queryConditionScenario("Given avg() and > 100", func(ctx *queryConditionTestContext) {
|
|
|
|
ctx.reducer = `{"type": "avg"}`
|
|
ctx.evaluator = `{"type": "gt", "params": [100]}`
|
|
|
|
Convey("Can read query condition from json model", func() {
|
|
ctx.exec()
|
|
|
|
So(ctx.condition.Query.From, ShouldEqual, "5m")
|
|
So(ctx.condition.Query.To, ShouldEqual, "now")
|
|
So(ctx.condition.Query.DatasourceId, ShouldEqual, 1)
|
|
|
|
Convey("Can read query reducer", func() {
|
|
reducer, ok := ctx.condition.Reducer.(*SimpleReducer)
|
|
So(ok, ShouldBeTrue)
|
|
So(reducer.Type, ShouldEqual, "avg")
|
|
})
|
|
|
|
Convey("Can read evaluator", func() {
|
|
evaluator, ok := ctx.condition.Evaluator.(*ThresholdEvaluator)
|
|
So(ok, ShouldBeTrue)
|
|
So(evaluator.Type, ShouldEqual, "gt")
|
|
})
|
|
})
|
|
|
|
Convey("should fire when avg is above 100", func() {
|
|
points := tsdb.NewTimeSeriesPointsFromArgs(120, 0)
|
|
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.Firing, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Should not fire when avg is below 100", func() {
|
|
points := tsdb.NewTimeSeriesPointsFromArgs(90, 0)
|
|
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.Firing, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Should fire if only first serie matches", func() {
|
|
ctx.series = tsdb.TimeSeriesSlice{
|
|
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
|
|
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(0, 0)),
|
|
}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.Firing, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("No series", func() {
|
|
Convey("Should set NoDataFound when condition is gt", func() {
|
|
ctx.series = tsdb.TimeSeriesSlice{}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.Firing, ShouldBeFalse)
|
|
So(cr.NoDataFound, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Should be firing when condition is no_value", func() {
|
|
ctx.evaluator = `{"type": "no_value", "params": []}`
|
|
ctx.series = tsdb.TimeSeriesSlice{}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.Firing, ShouldBeTrue)
|
|
})
|
|
})
|
|
|
|
Convey("Empty series", func() {
|
|
Convey("Should set Firing if eval match", func() {
|
|
ctx.evaluator = `{"type": "no_value", "params": []}`
|
|
ctx.series = tsdb.TimeSeriesSlice{
|
|
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
|
|
}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.Firing, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Should set NoDataFound both series are empty", func() {
|
|
ctx.series = tsdb.TimeSeriesSlice{
|
|
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
|
|
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs()),
|
|
}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.NoDataFound, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Should set NoDataFound both series contains null", func() {
|
|
ctx.series = tsdb.TimeSeriesSlice{
|
|
tsdb.NewTimeSeries("test1", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
|
|
tsdb.NewTimeSeries("test2", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
|
|
}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.NoDataFound, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Should not set NoDataFound if one serie is empty", func() {
|
|
ctx.series = tsdb.TimeSeriesSlice{
|
|
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
|
|
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
|
|
}
|
|
cr, err := ctx.exec()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(cr.NoDataFound, ShouldBeFalse)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
type queryConditionTestContext struct {
|
|
reducer string
|
|
evaluator string
|
|
series tsdb.TimeSeriesSlice
|
|
result *alerting.EvalContext
|
|
condition *QueryCondition
|
|
}
|
|
|
|
type queryConditionScenarioFunc func(c *queryConditionTestContext)
|
|
|
|
func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error) {
|
|
jsonModel, err := simplejson.NewJson([]byte(`{
|
|
"type": "query",
|
|
"query": {
|
|
"params": ["A", "5m", "now"],
|
|
"datasourceId": 1,
|
|
"model": {"target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
|
|
},
|
|
"reducer":` + ctx.reducer + `,
|
|
"evaluator":` + ctx.evaluator + `
|
|
}`))
|
|
So(err, ShouldBeNil)
|
|
|
|
condition, err := NewQueryCondition(jsonModel, 0)
|
|
So(err, ShouldBeNil)
|
|
|
|
ctx.condition = condition
|
|
|
|
condition.HandleRequest = func(context context.Context, req *tsdb.Request) (*tsdb.Response, error) {
|
|
return &tsdb.Response{
|
|
Results: map[string]*tsdb.QueryResult{
|
|
"A": {Series: ctx.series},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
return condition.Eval(ctx.result)
|
|
}
|
|
|
|
func queryConditionScenario(desc string, fn queryConditionScenarioFunc) {
|
|
Convey(desc, func() {
|
|
|
|
bus.AddHandler("test", func(query *m.GetDataSourceByIdQuery) error {
|
|
query.Result = &m.DataSource{Id: 1, Type: "graphite"}
|
|
return nil
|
|
})
|
|
|
|
ctx := &queryConditionTestContext{}
|
|
ctx.result = &alerting.EvalContext{
|
|
Rule: &alerting.Rule{},
|
|
}
|
|
|
|
fn(ctx)
|
|
})
|
|
}
|