Introduce TSDB service (#31520)

* Introduce TSDB service

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
Co-authored-by: Will Browne <will.browne@grafana.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.org>
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
This commit is contained in:
Arve Knudsen
2021-03-08 07:02:49 +01:00
committed by GitHub
parent c899bf3592
commit b79e61656a
203 changed files with 5270 additions and 4777 deletions

View File

@@ -6,7 +6,9 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/prometheus"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
gocontext "context"
@@ -16,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util/errutil"
)
@@ -29,12 +30,11 @@ func init() {
// QueryCondition is responsible for issue and query, reduce the
// timeseries into single values and evaluate if they are firing or not.
type QueryCondition struct {
Index int
Query AlertQuery
Reducer *queryReducer
Evaluator AlertEvaluator
Operator string
HandleRequest tsdb.HandleRequestFunc
Index int
Query AlertQuery
Reducer *queryReducer
Evaluator AlertEvaluator
Operator string
}
// AlertQuery contains information about what datasource a query
@@ -47,10 +47,10 @@ type AlertQuery struct {
}
// Eval evaluates the `QueryCondition`.
func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.ConditionResult, error) {
timeRange := tsdb.NewTimeRange(c.Query.From, c.Query.To)
func (c *QueryCondition) Eval(context *alerting.EvalContext, requestHandler tsdbifaces.RequestHandler) (*alerting.ConditionResult, error) {
timeRange := plugins.NewDataTimeRange(c.Query.From, c.Query.To)
seriesList, err := c.executeQuery(context, timeRange)
seriesList, err := c.executeQuery(context, timeRange, requestHandler)
if err != nil {
return nil, err
}
@@ -109,7 +109,8 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
}, nil
}
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) {
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange plugins.DataTimeRange,
requestHandler tsdbifaces.RequestHandler) (plugins.DataTimeSeriesSlice, error) {
getDsInfo := &models.GetDataSourceQuery{
Id: c.Query.DatasourceID,
OrgId: context.Rule.OrgID,
@@ -125,7 +126,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
}
req := c.getRequestForAlertRule(getDsInfo.Result, timeRange, context.IsDebug)
result := make(tsdb.TimeSeriesSlice, 0)
result := make(plugins.DataTimeSeriesSlice, 0)
if context.IsDebug {
data := simplejson.New()
@@ -139,20 +140,20 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
Model *simplejson.Json `json:"model"`
Datasource *simplejson.Json `json:"datasource"`
MaxDataPoints int64 `json:"maxDataPoints"`
IntervalMs int64 `json:"intervalMs"`
IntervalMS int64 `json:"intervalMs"`
}
queries := []*queryDto{}
for _, q := range req.Queries {
queries = append(queries, &queryDto{
RefID: q.RefId,
RefID: q.RefID,
Model: q.Model,
Datasource: simplejson.NewFromAny(map[string]interface{}{
"id": q.DataSource.Id,
"name": q.DataSource.Name,
}),
MaxDataPoints: q.MaxDataPoints,
IntervalMs: q.IntervalMs,
IntervalMS: q.IntervalMS,
})
}
@@ -164,29 +165,30 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
})
}
resp, err := c.HandleRequest(context.Ctx, getDsInfo.Result, req)
resp, err := requestHandler.HandleRequest(context.Ctx, getDsInfo.Result, req)
if err != nil {
return nil, toCustomError(err)
}
for _, v := range resp.Results {
if v.Error != nil {
return nil, fmt.Errorf("tsdb.HandleRequest() response error %v", v)
return nil, fmt.Errorf("request handler response error %v", v)
}
// If there are dataframes but no series on the result
useDataframes := v.Dataframes != nil && (v.Series == nil || len(v.Series) == 0)
if useDataframes { // convert the dataframes to tsdb.TimeSeries
if useDataframes { // convert the dataframes to plugins.DataTimeSeries
frames, err := v.Dataframes.Decoded()
if err != nil {
return nil, errutil.Wrap("tsdb.HandleRequest() failed to unmarshal arrow dataframes from bytes", err)
return nil, errutil.Wrap("request handler failed to unmarshal arrow dataframes from bytes", err)
}
for _, frame := range frames {
ss, err := FrameToSeriesSlice(frame)
if err != nil {
return nil, errutil.Wrapf(err, `tsdb.HandleRequest() failed to convert dataframe "%v" to tsdb.TimeSeriesSlice`, frame.Name)
return nil, errutil.Wrapf(err,
`request handler failed to convert dataframe "%v" to plugins.DataTimeSeriesSlice`, frame.Name)
}
result = append(result, ss...)
}
@@ -218,13 +220,14 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
return result, nil
}
func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, timeRange *tsdb.TimeRange, debug bool) *tsdb.TsdbQuery {
func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, timeRange plugins.DataTimeRange,
debug bool) plugins.DataQuery {
queryModel := c.Query.Model
req := &tsdb.TsdbQuery{
TimeRange: timeRange,
Queries: []*tsdb.Query{
req := plugins.DataQuery{
TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{
{
RefId: "A",
RefID: "A",
Model: queryModel,
DataSource: datasource,
QueryType: queryModel.Get("queryType").MustString(""),
@@ -242,7 +245,6 @@ func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, t
func newQueryCondition(model *simplejson.Json, index int) (*QueryCondition, error) {
condition := QueryCondition{}
condition.Index = index
condition.HandleRequest = tsdb.HandleRequest
queryJSON := model.Get("query")
@@ -301,23 +303,23 @@ func validateToValue(to string) error {
}
// FrameToSeriesSlice converts a frame that is a valid time series as per data.TimeSeriesSchema()
// to a TimeSeriesSlice.
func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
// to a DataTimeSeriesSlice.
func FrameToSeriesSlice(frame *data.Frame) (plugins.DataTimeSeriesSlice, error) {
tsSchema := frame.TimeSeriesSchema()
if tsSchema.Type == data.TimeSeriesTypeNot {
// If no fields, or only a time field, create an empty tsdb.TimeSeriesSlice with a single
// If no fields, or only a time field, create an empty plugins.DataTimeSeriesSlice with a single
// time series in order to trigger "no data" in alerting.
if len(frame.Fields) == 0 || (len(frame.Fields) == 1 && frame.Fields[0].Type().Time()) {
return tsdb.TimeSeriesSlice{{
return plugins.DataTimeSeriesSlice{{
Name: frame.Name,
Points: make(tsdb.TimeSeriesPoints, 0),
Points: make(plugins.DataTimeSeriesPoints, 0),
}}, nil
}
return nil, fmt.Errorf("input frame is not recognized as a time series")
}
seriesCount := len(tsSchema.ValueIndices)
seriesSlice := make(tsdb.TimeSeriesSlice, 0, seriesCount)
seriesSlice := make(plugins.DataTimeSeriesSlice, 0, seriesCount)
timeField := frame.Fields[tsSchema.TimeIndex]
timeNullFloatSlice := make([]null.Float, timeField.Len())
@@ -331,8 +333,8 @@ func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
for _, fieldIdx := range tsSchema.ValueIndices { // create a TimeSeries for each value Field
field := frame.Fields[fieldIdx]
ts := &tsdb.TimeSeries{
Points: make(tsdb.TimeSeriesPoints, field.Len()),
ts := plugins.DataTimeSeries{
Points: make(plugins.DataTimeSeriesPoints, field.Len()),
}
if len(field.Labels) > 0 {
@@ -355,9 +357,10 @@ func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
for rowIdx := 0; rowIdx < field.Len(); rowIdx++ { // for each value in the field, make a TimePoint
val, err := field.FloatAt(rowIdx)
if err != nil {
return nil, errutil.Wrapf(err, "failed to convert frame to tsdb.series, can not convert value %v to float", field.At(rowIdx))
return nil, errutil.Wrapf(err,
"failed to convert frame to DataTimeSeriesSlice, can not convert value %v to float", field.At(rowIdx))
}
ts.Points[rowIdx] = tsdb.TimePoint{
ts.Points[rowIdx] = plugins.DataTimePoint{
null.FloatFrom(val),
timeNullFloatSlice[rowIdx],
}
@@ -381,5 +384,5 @@ func toCustomError(err error) error {
}
// generic fallback
return fmt.Errorf("tsdb.HandleRequest() error %v", err)
return fmt.Errorf("request handler error: %w", err)
}

View File

@@ -15,18 +15,18 @@ import (
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/tsdb"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require"
"github.com/xorcare/pointer"
)
func newTimeSeriesPointsFromArgs(values ...float64) tsdb.TimeSeriesPoints {
points := make(tsdb.TimeSeriesPoints, 0)
func newTimeSeriesPointsFromArgs(values ...float64) plugins.DataTimeSeriesPoints {
points := make(plugins.DataTimeSeriesPoints, 0)
for i := 0; i < len(values); i += 2 {
points = append(points, tsdb.NewTimePoint(null.FloatFrom(values[i]), values[i+1]))
points = append(points, plugins.DataTimePoint{null.FloatFrom(values[i]), null.FloatFrom(values[i+1])})
}
return points
@@ -60,7 +60,7 @@ func TestQueryCondition(t *testing.T) {
Convey("should fire when avg is above 100", func() {
points := newTimeSeriesPointsFromArgs(120, 0)
ctx.series = tsdb.TimeSeriesSlice{&tsdb.TimeSeries{Name: "test1", Points: points}}
ctx.series = plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "test1", Points: points}}
cr, err := ctx.exec()
So(err, ShouldBeNil)
@@ -80,7 +80,7 @@ func TestQueryCondition(t *testing.T) {
Convey("Should not fire when avg is below 100", func() {
points := newTimeSeriesPointsFromArgs(90, 0)
ctx.series = tsdb.TimeSeriesSlice{&tsdb.TimeSeries{Name: "test1", Points: points}}
ctx.series = plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "test1", Points: points}}
cr, err := ctx.exec()
So(err, ShouldBeNil)
@@ -99,9 +99,9 @@ func TestQueryCondition(t *testing.T) {
})
Convey("Should fire if only first series matches", func() {
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs(120, 0)},
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(0, 0)},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs(120, 0)},
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(0, 0)},
}
cr, err := ctx.exec()
@@ -111,7 +111,7 @@ func TestQueryCondition(t *testing.T) {
Convey("No series", func() {
Convey("Should set NoDataFound when condition is gt", func() {
ctx.series = tsdb.TimeSeriesSlice{}
ctx.series = plugins.DataTimeSeriesSlice{}
cr, err := ctx.exec()
So(err, ShouldBeNil)
@@ -121,7 +121,7 @@ func TestQueryCondition(t *testing.T) {
Convey("Should be firing when condition is no_value", func() {
ctx.evaluator = `{"type": "no_value", "params": []}`
ctx.series = tsdb.TimeSeriesSlice{}
ctx.series = plugins.DataTimeSeriesSlice{}
cr, err := ctx.exec()
So(err, ShouldBeNil)
@@ -132,8 +132,8 @@ func TestQueryCondition(t *testing.T) {
Convey("Empty series", func() {
Convey("Should set Firing if eval match", func() {
ctx.evaluator = `{"type": "no_value", "params": []}`
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
}
cr, err := ctx.exec()
@@ -142,9 +142,9 @@ func TestQueryCondition(t *testing.T) {
})
Convey("Should set NoDataFound both series are empty", func() {
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs()},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs()},
}
cr, err := ctx.exec()
@@ -153,9 +153,9 @@ func TestQueryCondition(t *testing.T) {
})
Convey("Should set NoDataFound both series contains null", func() {
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
&tsdb.TimeSeries{Name: "test2", Points: tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: plugins.DataTimeSeriesPoints{plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
plugins.DataTimeSeries{Name: "test2", Points: plugins.DataTimeSeriesPoints{plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
}
cr, err := ctx.exec()
@@ -164,9 +164,9 @@ func TestQueryCondition(t *testing.T) {
})
Convey("Should not set NoDataFound if one series is empty", func() {
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(120, 0)},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(120, 0)},
}
cr, err := ctx.exec()
@@ -181,7 +181,7 @@ func TestQueryCondition(t *testing.T) {
type queryConditionTestContext struct {
reducer string
evaluator string
series tsdb.TimeSeriesSlice
series plugins.DataTimeSeriesSlice
frame *data.Frame
result *alerting.EvalContext
condition *QueryCondition
@@ -207,25 +207,33 @@ func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error)
ctx.condition = condition
qr := &tsdb.QueryResult{
qr := plugins.DataQueryResult{
Series: ctx.series,
}
if ctx.frame != nil {
qr = &tsdb.QueryResult{
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{ctx.frame}),
qr = plugins.DataQueryResult{
Dataframes: plugins.NewDecodedDataFrames(data.Frames{ctx.frame}),
}
}
condition.HandleRequest = func(context context.Context, dsInfo *models.DataSource, req *tsdb.TsdbQuery) (*tsdb.Response, error) {
return &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
reqHandler := fakeReqHandler{
response: plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"A": qr,
},
}, nil
},
}
return condition.Eval(ctx.result)
return condition.Eval(ctx.result, reqHandler)
}
type fakeReqHandler struct {
response plugins.DataResponse
}
func (rh fakeReqHandler) HandleRequest(context.Context, *models.DataSource, plugins.DataQuery) (
plugins.DataResponse, error) {
return rh.response, nil
}
func queryConditionScenario(desc string, fn queryConditionScenarioFunc) {
@@ -249,7 +257,7 @@ func TestFrameToSeriesSlice(t *testing.T) {
tests := []struct {
name string
frame *data.Frame
seriesSlice tsdb.TimeSeriesSlice
seriesSlice plugins.DataTimeSeriesSlice
Err require.ErrorAssertionFunc
}{
{
@@ -268,21 +276,21 @@ func TestFrameToSeriesSlice(t *testing.T) {
4.0,
})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "Values Int64s {Animal Factor=cat}",
Tags: map[string]string{"Animal Factor": "cat"},
Points: tsdb.TimeSeriesPoints{
tsdb.TimePoint{null.FloatFrom(math.NaN()), null.FloatFrom(1577934240000)},
tsdb.TimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
Points: plugins.DataTimeSeriesPoints{
plugins.DataTimePoint{null.FloatFrom(math.NaN()), null.FloatFrom(1577934240000)},
plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
},
},
&tsdb.TimeSeries{
plugins.DataTimeSeries{
Name: "Values Floats {Animal Factor=sloth}",
Tags: map[string]string{"Animal Factor": "sloth"},
Points: tsdb.TimeSeriesPoints{
tsdb.TimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
tsdb.TimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
Points: plugins.DataTimeSeriesPoints{
plugins.DataTimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
plugins.DataTimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
},
},
},
@@ -295,16 +303,16 @@ func TestFrameToSeriesSlice(t *testing.T) {
data.NewField(`Values Int64s`, data.Labels{"Animal Factor": "cat"}, []*int64{}),
data.NewField(`Values Floats`, data.Labels{"Animal Factor": "sloth"}, []float64{})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "Values Int64s {Animal Factor=cat}",
Tags: map[string]string{"Animal Factor": "cat"},
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
},
&tsdb.TimeSeries{
plugins.DataTimeSeries{
Name: "Values Floats {Animal Factor=sloth}",
Tags: map[string]string{"Animal Factor": "sloth"},
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
},
},
Err: require.NoError,
@@ -315,10 +323,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
data.NewField("Time", data.Labels{}, []time.Time{}),
data.NewField(`Values`, data.Labels{}, []float64{})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "Values",
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
},
},
Err: require.NoError,
@@ -331,10 +339,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
DisplayNameFromDS: "sloth",
})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "sloth",
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
Tags: map[string]string{"Rating": "10"},
},
},
@@ -349,10 +357,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
DisplayNameFromDS: "sloth #2",
})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "sloth #1",
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
},
},
Err: require.NoError,

View File

@@ -6,7 +6,7 @@ import (
"sort"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
// queryReducer reduces a timeseries to a nullable float
@@ -18,7 +18,7 @@ type queryReducer struct {
}
//nolint: gocyclo
func (s *queryReducer) Reduce(series *tsdb.TimeSeries) null.Float {
func (s *queryReducer) Reduce(series plugins.DataTimeSeries) null.Float {
if len(series.Points) == 0 {
return null.FloatFromPtr(nil)
}
@@ -126,7 +126,7 @@ func newSimpleReducer(t string) *queryReducer {
return &queryReducer{Type: t}
}
func calculateDiff(series *tsdb.TimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
func calculateDiff(series plugins.DataTimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
var (
points = series.Points
first float64

View File

@@ -7,7 +7,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
func TestSimpleReducer(t *testing.T) {
@@ -54,16 +54,16 @@ func TestSimpleReducer(t *testing.T) {
Convey("median should ignore null values", func() {
reducer := newSimpleReducer("median")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 3))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(1)), 4))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(2)), 5))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(3)), 6))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(3)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(1)), null.FloatFrom(4)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(2)), null.FloatFrom(5)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(3)), null.FloatFrom(6)})
result := reducer.Reduce(series)
So(result.Valid, ShouldEqual, true)
@@ -77,25 +77,25 @@ func TestSimpleReducer(t *testing.T) {
Convey("avg with only nulls", func() {
reducer := newSimpleReducer("avg")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
Convey("count_non_null", func() {
Convey("with null values and real values", func() {
reducer := newSimpleReducer("count_non_null")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 3))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 4))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(3)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(4)})
So(reducer.Reduce(series).Valid, ShouldEqual, true)
So(reducer.Reduce(series).Float64, ShouldEqual, 2)
@@ -103,12 +103,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("with null values", func() {
reducer := newSimpleReducer("count_non_null")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -116,14 +116,14 @@ func TestSimpleReducer(t *testing.T) {
Convey("avg of number values and null values should ignore nulls", func() {
reducer := newSimpleReducer("avg")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 3))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 4))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(3)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(4)})
So(reducer.Reduce(series).Float64, ShouldEqual, float64(3))
})
@@ -181,12 +181,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("diff with only nulls", func() {
reducer := newSimpleReducer("diff")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -244,12 +244,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("diff_abs with only nulls", func() {
reducer := newSimpleReducer("diff_abs")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -307,12 +307,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("percent_diff with only nulls", func() {
reducer := newSimpleReducer("percent_diff")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -370,12 +370,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("percent_diff_abs with only nulls", func() {
reducer := newSimpleReducer("percent_diff_abs")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -399,12 +399,12 @@ func TestSimpleReducer(t *testing.T) {
func testReducer(reducerType string, datapoints ...float64) float64 {
reducer := newSimpleReducer(reducerType)
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
for idx := range datapoints {
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(datapoints[idx]), 1234134))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(datapoints[idx]), null.FloatFrom(1234134)})
}
return reducer.Reduce(series).Float64

View File

@@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
tlog "github.com/opentracing/opentracing-go/log"
@@ -26,6 +27,7 @@ type AlertEngine struct {
RenderService rendering.Service `inject:""`
Bus bus.Bus `inject:""`
RequestValidator models.PluginRequestValidator `inject:""`
DataService *tsdb.Service `inject:""`
execQueue chan *Job
ticker *Ticker
@@ -50,7 +52,7 @@ func (e *AlertEngine) Init() error {
e.ticker = NewTicker(time.Now(), time.Second*0, clock.New(), 1)
e.execQueue = make(chan *Job, 1000)
e.scheduler = newScheduler()
e.evalHandler = NewEvalHandler()
e.evalHandler = NewEvalHandler(e.DataService)
e.ruleReader = newRuleReader()
e.log = log.New("alerting.engine")
e.resultHandler = newResultHandler(e.RenderService)

View File

@@ -7,19 +7,22 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
)
// DefaultEvalHandler is responsible for evaluating the alert rule.
type DefaultEvalHandler struct {
log log.Logger
alertJobTimeout time.Duration
requestHandler tsdbifaces.RequestHandler
}
// NewEvalHandler is the `DefaultEvalHandler` constructor.
func NewEvalHandler() *DefaultEvalHandler {
func NewEvalHandler(requestHandler tsdbifaces.RequestHandler) *DefaultEvalHandler {
return &DefaultEvalHandler{
log: log.New("alerting.evalHandler"),
alertJobTimeout: time.Second * 5,
requestHandler: requestHandler,
}
}
@@ -31,7 +34,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
for i := 0; i < len(context.Rule.Conditions); i++ {
condition := context.Rule.Conditions[i]
cr, err := condition.Eval(context)
cr, err := condition.Eval(context, e.requestHandler)
if err != nil {
context.Error = err
}

View File

@@ -5,6 +5,7 @@ import (
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
. "github.com/smartystreets/goconvey/convey"
)
@@ -16,13 +17,13 @@ type conditionStub struct {
noData bool
}
func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) {
func (c *conditionStub) Eval(context *EvalContext, reqHandler tsdbifaces.RequestHandler) (*ConditionResult, error) {
return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator, NoDataFound: c.noData}, nil
}
func TestAlertingEvaluationHandler(t *testing.T) {
Convey("Test alert evaluation handler", t, func() {
handler := NewEvalHandler()
handler := NewEvalHandler(nil)
Convey("Show return triggered with single passing condition", func() {
context := NewEvalContext(context.TODO(), &Rule{

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
)
type evalHandler interface {
@@ -59,5 +60,5 @@ type ConditionResult struct {
// Condition is responsible for evaluating an alert condition.
type Condition interface {
Eval(result *EvalContext) (*ConditionResult, error)
Eval(result *EvalContext, requestHandler tsdbifaces.RequestHandler) (*ConditionResult, error)
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -13,7 +14,7 @@ import (
type FakeCondition struct{}
func (f *FakeCondition) Eval(context *EvalContext) (*ConditionResult, error) {
func (f *FakeCondition) Eval(context *EvalContext, reqHandler tsdbifaces.RequestHandler) (*ConditionResult, error) {
return &ConditionResult{}, nil
}

View File

@@ -4,59 +4,39 @@ import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
)
// AlertTestCommand initiates an test evaluation
// of an alert rule.
type AlertTestCommand struct {
Dashboard *simplejson.Json
PanelID int64
OrgID int64
User *models.SignedInUser
// AlertTest makes a test alert.
func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID int64, user *models.SignedInUser) (*EvalContext, error) {
dash := models.NewDashboardFromJson(dashboard)
Result *EvalContext
}
func init() {
bus.AddHandler("alerting", handleAlertTestCommand)
}
func handleAlertTestCommand(cmd *AlertTestCommand) error {
dash := models.NewDashboardFromJson(cmd.Dashboard)
extractor := NewDashAlertExtractor(dash, cmd.OrgID, cmd.User)
extractor := NewDashAlertExtractor(dash, orgID, user)
alerts, err := extractor.GetAlerts()
if err != nil {
return err
return nil, err
}
for _, alert := range alerts {
if alert.PanelId == cmd.PanelID {
rule, err := NewRuleFromDBAlert(alert, true)
if err != nil {
return err
}
cmd.Result = testAlertRule(rule)
return nil
if alert.PanelId != panelID {
continue
}
rule, err := NewRuleFromDBAlert(alert, true)
if err != nil {
return nil, err
}
handler := NewEvalHandler(e.DataService)
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{})
context.IsTestRun = true
context.IsDebug = true
handler.Eval(context)
context.Rule.State = context.GetNewState()
return context, nil
}
return fmt.Errorf("could not find alert with panel ID %d", cmd.PanelID)
}
func testAlertRule(rule *Rule) *EvalContext {
handler := NewEvalHandler()
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{})
context.IsTestRun = true
context.IsDebug = true
handler.Eval(context)
context.Rule.State = context.GetNewState()
return context
return nil, fmt.Errorf("could not find alert with panel ID %d", panelID)
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
@@ -33,9 +34,10 @@ type DashboardProvisioningService interface {
}
// NewService factory for creating a new dashboard service
var NewService = func() DashboardService {
var NewService = func(reqHandler tsdbifaces.RequestHandler) DashboardService {
return &dashboardServiceImpl{
log: log.New("dashboard-service"),
log: log.New("dashboard-service"),
reqHandler: reqHandler,
}
}
@@ -56,9 +58,10 @@ type SaveDashboardDTO struct {
}
type dashboardServiceImpl struct {
orgId int64
user *models.SignedInUser
log log.Logger
orgId int64
user *models.SignedInUser
log log.Logger
reqHandler tsdbifaces.RequestHandler
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
@@ -386,7 +389,7 @@ func (s *FakeDashboardService) DeleteDashboard(dashboardId int64, orgId int64) e
}
func MockDashboardService(mock *FakeDashboardService) {
NewService = func() DashboardService {
NewService = func(tsdbifaces.RequestHandler) DashboardService {
return mock
}
}

View File

@@ -763,7 +763,7 @@ func createDashboard(t *testing.T, user models.SignedInUser, title string, folde
return nil
})
dashboard, err := dashboards.NewService().SaveDashboard(dashItem, true)
dashboard, err := dashboards.NewService(nil).SaveDashboard(dashItem, true)
require.NoError(t, err)
return dashboard

View File

@@ -8,7 +8,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/live/features"
"github.com/grafana/grafana/pkg/setting"
@@ -222,7 +222,7 @@ func (g *GrafanaLive) GetChannelHandlerFactory(scope string, name string) (model
}, nil
}
p, ok := plugins.Plugins[name]
p, ok := manager.Plugins[name]
if ok {
h := &PluginHandler{
Plugin: p,

View File

@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/setting"
@@ -20,6 +21,7 @@ type apiImpl struct {
Cfg *setting.Cfg `inject:""`
DatasourceCache datasources.CacheService `inject:""`
RouteRegister routing.RouteRegister `inject:""`
DataService *tsdb.Service
schedule scheduleService
store store
}
@@ -64,13 +66,13 @@ func (api *apiImpl) conditionEvalEndpoint(c *models.ReqContext, cmd evalAlertCon
}
evaluator := eval.Evaluator{Cfg: api.Cfg}
evalResults, err := evaluator.ConditionEval(&evalCond, timeNow())
evalResults, err := evaluator.ConditionEval(&evalCond, timeNow(), api.DataService)
if err != nil {
return response.Error(400, "Failed to evaluate conditions", err)
}
frame := evalResults.AsDataFrame()
df := tsdb.NewDecodedDataFrames([]*data.Frame{&frame})
df := plugins.NewDecodedDataFrames([]*data.Frame{&frame})
instances, err := df.Encoded()
if err != nil {
return response.Error(400, "Failed to encode result dataframes", err)
@@ -95,13 +97,13 @@ func (api *apiImpl) alertDefinitionEvalEndpoint(c *models.ReqContext) response.R
}
evaluator := eval.Evaluator{Cfg: api.Cfg}
evalResults, err := evaluator.ConditionEval(condition, timeNow())
evalResults, err := evaluator.ConditionEval(condition, timeNow(), api.DataService)
if err != nil {
return response.Error(400, "Failed to evaluate alert", err)
}
frame := evalResults.AsDataFrame()
df := tsdb.NewDecodedDataFrames([]*data.Frame{&frame})
df := plugins.NewDecodedDataFrames([]*data.Frame{&frame})
if err != nil {
return response.Error(400, "Failed to instantiate Dataframes from the decoded frames", err)
}

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
@@ -100,7 +101,7 @@ type AlertExecCtx struct {
}
// execute runs the Condition's expressions or queries.
func (c *Condition) execute(ctx AlertExecCtx, now time.Time) (*ExecutionResults, error) {
func (c *Condition) execute(ctx AlertExecCtx, now time.Time, dataService *tsdb.Service) (*ExecutionResults, error) {
result := ExecutionResults{}
if !c.IsValid() {
return nil, fmt.Errorf("invalid conditions")
@@ -140,7 +141,10 @@ func (c *Condition) execute(ctx AlertExecCtx, now time.Time) (*ExecutionResults,
})
}
exprService := expr.Service{Cfg: &setting.Cfg{ExpressionsEnabled: ctx.ExpressionsEnabled}}
exprService := expr.Service{
Cfg: &setting.Cfg{ExpressionsEnabled: ctx.ExpressionsEnabled},
DataService: dataService,
}
pbRes, err := exprService.TransformData(ctx.Ctx, queryDataReq)
if err != nil {
return &result, err
@@ -218,13 +222,13 @@ func (evalResults Results) AsDataFrame() data.Frame {
}
// ConditionEval executes conditions and evaluates the result.
func (e *Evaluator) ConditionEval(condition *Condition, now time.Time) (Results, error) {
func (e *Evaluator) ConditionEval(condition *Condition, now time.Time, dataService *tsdb.Service) (Results, error) {
alertCtx, cancelFn := context.WithTimeout(context.Background(), alertingEvaluationTimeout)
defer cancelFn()
alertExecCtx := AlertExecCtx{OrgID: condition.OrgID, Ctx: alertCtx, ExpressionsEnabled: e.Cfg.ExpressionsEnabled}
execResult, err := condition.execute(alertExecCtx, now)
execResult, err := condition.execute(alertExecCtx, now, dataService)
if err != nil {
return nil, fmt.Errorf("failed to execute conditions: %w", err)
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/benbjohnson/clock"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
@@ -34,6 +35,7 @@ type AlertNG struct {
DatasourceCache datasources.CacheService `inject:""`
RouteRegister routing.RouteRegister `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
DataService *tsdb.Service `inject:""`
log log.Logger
schedule scheduleService
}
@@ -57,14 +59,16 @@ func (ng *AlertNG) Init() error {
evaluator: eval.Evaluator{Cfg: ng.Cfg},
store: store,
}
ng.schedule = newScheduler(schedCfg)
ng.schedule = newScheduler(schedCfg, ng.DataService)
api := apiImpl{
Cfg: ng.Cfg,
DatasourceCache: ng.DatasourceCache,
RouteRegister: ng.RouteRegister,
DataService: ng.DataService,
schedule: ng.schedule,
store: store}
store: store,
}
api.registerAPIEndpoints()
return nil

View File

@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/tsdb"
"golang.org/x/sync/errgroup"
)
@@ -24,7 +25,8 @@ type scheduleService interface {
overrideCfg(cfg schedulerCfg)
}
func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefinitionKey, evalCh <-chan *evalContext, stopCh <-chan struct{}) error {
func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefinitionKey,
evalCh <-chan *evalContext, stopCh <-chan struct{}) error {
sch.log.Debug("alert definition routine started", "key", key)
evalRunning := false
@@ -58,11 +60,12 @@ func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefi
OrgID: alertDefinition.OrgID,
QueriesAndExpressions: alertDefinition.Data,
}
results, err := sch.evaluator.ConditionEval(&condition, ctx.now)
results, err := sch.evaluator.ConditionEval(&condition, ctx.now, sch.dataService)
end = timeNow()
if err != nil {
// consider saving alert instance on error
sch.log.Error("failed to evaluate alert definition", "title", alertDefinition.Title, "key", key, "attempt", attempt, "now", ctx.now, "duration", end.Sub(start), "error", err)
sch.log.Error("failed to evaluate alert definition", "title", alertDefinition.Title,
"key", key, "attempt", attempt, "now", ctx.now, "duration", end.Sub(start), "error", err)
return err
}
for _, r := range results {
@@ -129,6 +132,8 @@ type schedule struct {
evaluator eval.Evaluator
store store
dataService *tsdb.Service
}
type schedulerCfg struct {
@@ -142,7 +147,7 @@ type schedulerCfg struct {
}
// newScheduler returns a new schedule.
func newScheduler(cfg schedulerCfg) *schedule {
func newScheduler(cfg schedulerCfg, dataService *tsdb.Service) *schedule {
ticker := alerting.NewTicker(cfg.c.Now(), time.Second*0, cfg.c, int64(cfg.baseInterval.Seconds()))
sch := schedule{
registry: alertDefinitionRegistry{alertDefinitionInfo: make(map[alertDefinitionKey]alertDefinitionInfo)},
@@ -155,6 +160,7 @@ func newScheduler(cfg schedulerCfg) *schedule {
stopAppliedFunc: cfg.stopAppliedFunc,
evaluator: cfg.evaluator,
store: cfg.store,
dataService: dataService,
}
return &sch
}

View File

@@ -8,7 +8,7 @@ import (
"strings"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"gopkg.in/yaml.v2"
)
@@ -112,7 +112,7 @@ func validatePluginsConfig(apps []*pluginsAsConfig) error {
}
for _, app := range apps[i].Apps {
if !plugins.IsAppInstalled(app.PluginID) {
if !manager.IsAppInstalled(app.PluginID) {
return fmt.Errorf("app plugin not installed: %s", app.PluginID)
}
}

View File

@@ -6,10 +6,11 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/stretchr/testify/require"
)
var (
const (
incorrectSettings = "./testdata/test-configs/incorrect-settings"
brokenYaml = "./testdata/test-configs/broken-yaml"
emptyFolder = "./testdata/test-configs/empty_folder"
@@ -46,7 +47,7 @@ func TestConfigReader(t *testing.T) {
})
t.Run("Can read correct properties", func(t *testing.T) {
plugins.Apps = map[string]*plugins.AppPlugin{
manager.Apps = map[string]*plugins.AppPlugin{
"test-plugin": {},
"test-plugin-2": {},
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -86,7 +87,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
if rs.pluginAvailable() {
rs.log = rs.log.New("renderer", "plugin")
rs.pluginInfo = plugins.Renderer
rs.pluginInfo = manager.Renderer
if err := rs.startPlugin(ctx); err != nil {
return err
@@ -106,7 +107,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
}
func (rs *RenderingService) pluginAvailable() bool {
return plugins.Renderer != nil
return manager.Renderer != nil
}
func (rs *RenderingService) remoteAvailable() bool {

View File

@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
@@ -34,11 +35,11 @@ func TestIntegratedDashboardService(t *testing.T) {
return nil
})
savedFolder := saveTestFolder("Saved folder", testOrgId)
savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
savedDashInGeneralFolder := saveTestDashboard("Saved dashboard in general folder", testOrgId, 0)
otherSavedFolder := saveTestFolder("Other saved folder", testOrgId)
savedFolder := saveTestFolder(t, "Saved folder", testOrgId)
savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgId, savedFolder.Id)
saveTestDashboard(t, "Other saved dash in folder", testOrgId, savedFolder.Id)
savedDashInGeneralFolder := saveTestDashboard(t, "Saved dashboard in general folder", testOrgId, 0)
otherSavedFolder := saveTestFolder(t, "Other saved folder", testOrgId)
Convey("Should return dashboard model", func() {
So(savedFolder.Title, ShouldEqual, "Saved folder")
@@ -110,7 +111,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: false,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
Convey("It should create a new dashboard in organization B", func() {
So(res, ShouldNotBeNil)
@@ -385,7 +386,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new dashboard", func() {
@@ -409,7 +410,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new dashboard", func() {
@@ -434,7 +435,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new folder", func() {
@@ -459,7 +460,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new dashboard", func() {
@@ -484,7 +485,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new dashboard", func() {
@@ -545,7 +546,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should update dashboard", func() {
@@ -590,7 +591,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should update dashboard", func() {
@@ -676,7 +677,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should update dashboard", func() {
@@ -701,7 +702,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
So(res, ShouldNotBeNil)
Convey("It should update dashboard", func() {
@@ -726,7 +727,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
Convey("It should update dashboard", func() {
So(res, ShouldNotBeNil)
@@ -772,7 +773,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
Convey("It should overwrite existing dashboard", func() {
So(res, ShouldNotBeNil)
@@ -799,7 +800,7 @@ func TestIntegratedDashboardService(t *testing.T) {
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
res := callSaveWithResult(t, cmd)
Convey("It should overwrite existing dashboard", func() {
So(res, ShouldNotBeNil)
@@ -962,19 +963,25 @@ func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenari
dashboardPermissionScenario(desc, mock, fn)
}
func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand) *models.Dashboard {
t.Helper()
dto := toSaveDashboardDto(cmd)
res, _ := dashboards.NewService().SaveDashboard(&dto, false)
res, err := dashboards.NewService(nil).SaveDashboard(&dto, false)
require.NoError(t, err)
return res
}
func callSaveWithError(cmd models.SaveDashboardCommand) error {
dto := toSaveDashboardDto(cmd)
_, err := dashboards.NewService().SaveDashboard(&dto, false)
_, err := dashboards.NewService(nil).SaveDashboard(&dto, false)
return err
}
func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
func saveTestDashboard(t *testing.T, title string, orgId int64, folderId int64) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
@@ -994,13 +1001,14 @@ func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashbo
},
}
res, err := dashboards.NewService().SaveDashboard(&dto, false)
So(err, ShouldBeNil)
res, err := dashboards.NewService(nil).SaveDashboard(&dto, false)
require.NoError(t, err)
return res
}
func saveTestFolder(title string, orgId int64) *models.Dashboard {
func saveTestFolder(t *testing.T, title string, orgId int64) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: 0,
@@ -1020,8 +1028,8 @@ func saveTestFolder(title string, orgId int64) *models.Dashboard {
},
}
res, err := dashboards.NewService().SaveDashboard(&dto, false)
So(err, ShouldBeNil)
res, err := dashboards.NewService(nil).SaveDashboard(&dto, false)
require.NoError(t, err)
return res
}