소스 검색

feat(alerting): refactoring conditions out to seperate package

Torkel Ödegaard 9 년 전
부모
커밋
6aaf4c97a2

+ 15 - 5
pkg/services/alerting/alert_rule.go

@@ -79,13 +79,15 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
 
 	for index, condition := range ruleDef.Settings.Get("conditions").MustArray() {
 		conditionModel := simplejson.NewFromAny(condition)
-		switch conditionModel.Get("type").MustString() {
-		case "query":
-			queryCondition, err := NewQueryCondition(conditionModel, index)
-			if err != nil {
+		conditionType := conditionModel.Get("type").MustString()
+		if factory, exist := conditionFactories[conditionType]; !exist {
+			return nil, AlertValidationError{Reason: "Unknown alert condition: " + conditionType}
+		} else {
+			if queryCondition, err := factory(conditionModel, index); err != nil {
 				return nil, err
+			} else {
+				model.Conditions = append(model.Conditions, queryCondition)
 			}
-			model.Conditions = append(model.Conditions, queryCondition)
 		}
 	}
 
@@ -95,3 +97,11 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
 
 	return model, nil
 }
+
+type ConditionFactory func(model *simplejson.Json, index int) (AlertCondition, error)
+
+var conditionFactories map[string]ConditionFactory = make(map[string]ConditionFactory)
+
+func RegisterCondition(typeName string, factory ConditionFactory) {
+	conditionFactories[typeName] = factory
+}

+ 10 - 29
pkg/services/alerting/alert_rule_test.go

@@ -8,9 +8,17 @@ import (
 	. "github.com/smartystreets/goconvey/convey"
 )
 
+type FakeCondition struct{}
+
+func (f *FakeCondition) Eval(context *AlertResultContext) {}
+
 func TestAlertRuleModel(t *testing.T) {
 	Convey("Testing alert rule", t, func() {
 
+		RegisterCondition("test", func(model *simplejson.Json, index int) (AlertCondition, error) {
+			return &FakeCondition{}, nil
+		})
+
 		Convey("Can parse seconds", func() {
 			seconds := getTimeDurationStringToSeconds("10s")
 			So(seconds, ShouldEqual, 10)
@@ -41,14 +49,8 @@ func TestAlertRuleModel(t *testing.T) {
 				"frequency": "60s",
         "conditions": [
           {
-            "type": "query",
-            "query":  {
-              "params": ["A", "5m", "now"],
-              "datasourceId": 1,
-              "model": {"target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
-            },
-            "reducer": {"type": "avg", "params": []},
-            "evaluator": {"type": ">", "params": [100]}
+            "type": "test",
+            "prop": 123
 					}
         ],
         "notifications": [
@@ -75,27 +77,6 @@ func TestAlertRuleModel(t *testing.T) {
 
 			So(alertRule.Conditions, ShouldHaveLength, 1)
 
-			Convey("Can read query condition from json model", func() {
-				queryCondition, ok := alertRule.Conditions[0].(*QueryCondition)
-				So(ok, ShouldBeTrue)
-
-				So(queryCondition.Query.From, ShouldEqual, "5m")
-				So(queryCondition.Query.To, ShouldEqual, "now")
-				So(queryCondition.Query.DatasourceId, ShouldEqual, 1)
-
-				Convey("Can read query reducer", func() {
-					reducer, ok := queryCondition.Reducer.(*SimpleReducer)
-					So(ok, ShouldBeTrue)
-					So(reducer.Type, ShouldEqual, "avg")
-				})
-
-				Convey("Can read evaluator", func() {
-					evaluator, ok := queryCondition.Evaluator.(*DefaultAlertEvaluator)
-					So(ok, ShouldBeTrue)
-					So(evaluator.Type, ShouldEqual, ">")
-				})
-			})
-
 			Convey("Can read notifications", func() {
 				So(len(alertRule.Notifications), ShouldEqual, 2)
 			})

+ 1 - 0
pkg/services/alerting/conditions/common.go

@@ -0,0 +1 @@
+package conditions

+ 51 - 0
pkg/services/alerting/conditions/evaluator.go

@@ -0,0 +1,51 @@
+package conditions
+
+import (
+	"encoding/json"
+
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	"github.com/grafana/grafana/pkg/services/alerting"
+	"github.com/grafana/grafana/pkg/tsdb"
+)
+
+type AlertEvaluator interface {
+	Eval(timeSeries *tsdb.TimeSeries, reducedValue float64) bool
+}
+
+type DefaultAlertEvaluator struct {
+	Type      string
+	Threshold float64
+}
+
+func (e *DefaultAlertEvaluator) Eval(series *tsdb.TimeSeries, reducedValue float64) bool {
+	switch e.Type {
+	case ">":
+		return reducedValue > e.Threshold
+	case "<":
+		return reducedValue < e.Threshold
+	}
+
+	return false
+}
+
+func NewDefaultAlertEvaluator(model *simplejson.Json) (*DefaultAlertEvaluator, error) {
+	evaluator := &DefaultAlertEvaluator{}
+
+	evaluator.Type = model.Get("type").MustString()
+	if evaluator.Type == "" {
+		return nil, alerting.AlertValidationError{Reason: "Evaluator missing type property"}
+	}
+
+	params := model.Get("params").MustArray()
+	if len(params) == 0 {
+		return nil, alerting.AlertValidationError{Reason: "Evaluator missing threshold parameter"}
+	}
+
+	threshold, ok := params[0].(json.Number)
+	if !ok {
+		return nil, alerting.AlertValidationError{Reason: "Evaluator has invalid threshold parameter"}
+	}
+
+	evaluator.Threshold, _ = threshold.Float64()
+	return evaluator, nil
+}

+ 20 - 67
pkg/services/alerting/conditions.go → pkg/services/alerting/conditions/query.go

@@ -1,15 +1,21 @@
-package alerting
+package conditions
 
 import (
-	"encoding/json"
 	"fmt"
 
 	"github.com/grafana/grafana/pkg/bus"
 	"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"
 )
 
+func init() {
+	alerting.RegisterCondition("query", func(model *simplejson.Json, index int) (alerting.AlertCondition, error) {
+		return NewQueryCondition(model, index)
+	})
+}
+
 type QueryCondition struct {
 	Index         int
 	Query         AlertQuery
@@ -18,7 +24,14 @@ type QueryCondition struct {
 	HandleRequest tsdb.HandleRequestFunc
 }
 
-func (c *QueryCondition) Eval(context *AlertResultContext) {
+type AlertQuery struct {
+	Model        *simplejson.Json
+	DatasourceId int64
+	From         string
+	To           string
+}
+
+func (c *QueryCondition) Eval(context *alerting.AlertResultContext) {
 	seriesList, err := c.executeQuery(context)
 	if err != nil {
 		context.Error = err
@@ -30,13 +43,13 @@ func (c *QueryCondition) Eval(context *AlertResultContext) {
 		pass := c.Evaluator.Eval(series, reducedValue)
 
 		if context.IsTestRun {
-			context.Logs = append(context.Logs, &AlertResultLogEntry{
+			context.Logs = append(context.Logs, &alerting.AlertResultLogEntry{
 				Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %1.3f", c.Index, pass, series.Name, reducedValue),
 			})
 		}
 
 		if pass {
-			context.Events = append(context.Events, &AlertEvent{
+			context.Events = append(context.Events, &alerting.AlertEvent{
 				Metric: series.Name,
 				Value:  reducedValue,
 			})
@@ -46,7 +59,7 @@ func (c *QueryCondition) Eval(context *AlertResultContext) {
 	}
 }
 
-func (c *QueryCondition) executeQuery(context *AlertResultContext) (tsdb.TimeSeriesSlice, error) {
+func (c *QueryCondition) executeQuery(context *alerting.AlertResultContext) (tsdb.TimeSeriesSlice, error) {
 	getDsInfo := &m.GetDataSourceByIdQuery{
 		Id:    c.Query.DatasourceId,
 		OrgId: context.Rule.OrgId,
@@ -72,7 +85,7 @@ func (c *QueryCondition) executeQuery(context *AlertResultContext) (tsdb.TimeSer
 		result = append(result, v.Series...)
 
 		if context.IsTestRun {
-			context.Logs = append(context.Logs, &AlertResultLogEntry{
+			context.Logs = append(context.Logs, &alerting.AlertResultLogEntry{
 				Message: fmt.Sprintf("Condition[%d]: Query Result", c.Index),
 				Data:    v.Series,
 			})
@@ -129,63 +142,3 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro
 	condition.Evaluator = evaluator
 	return &condition, nil
 }
-
-type SimpleReducer struct {
-	Type string
-}
-
-func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) float64 {
-	var value float64 = 0
-
-	switch s.Type {
-	case "avg":
-		for _, point := range series.Points {
-			value += point[0]
-		}
-		value = value / float64(len(series.Points))
-	}
-
-	return value
-}
-
-func NewSimpleReducer(typ string) *SimpleReducer {
-	return &SimpleReducer{Type: typ}
-}
-
-type DefaultAlertEvaluator struct {
-	Type      string
-	Threshold float64
-}
-
-func (e *DefaultAlertEvaluator) Eval(series *tsdb.TimeSeries, reducedValue float64) bool {
-	switch e.Type {
-	case ">":
-		return reducedValue > e.Threshold
-	case "<":
-		return reducedValue < e.Threshold
-	}
-
-	return false
-}
-
-func NewDefaultAlertEvaluator(model *simplejson.Json) (*DefaultAlertEvaluator, error) {
-	evaluator := &DefaultAlertEvaluator{}
-
-	evaluator.Type = model.Get("type").MustString()
-	if evaluator.Type == "" {
-		return nil, AlertValidationError{Reason: "Evaluator missing type property"}
-	}
-
-	params := model.Get("params").MustArray()
-	if len(params) == 0 {
-		return nil, AlertValidationError{Reason: "Evaluator missing threshold parameter"}
-	}
-
-	threshold, ok := params[0].(json.Number)
-	if !ok {
-		return nil, AlertValidationError{Reason: "Evaluator has invalid threshold parameter"}
-	}
-
-	evaluator.Threshold, _ = threshold.Float64()
-	return evaluator, nil
-}

+ 28 - 4
pkg/services/alerting/conditions_test.go → pkg/services/alerting/conditions/query_test.go

@@ -1,4 +1,4 @@
-package alerting
+package conditions
 
 import (
 	"testing"
@@ -6,6 +6,7 @@ import (
 	"github.com/grafana/grafana/pkg/bus"
 	"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"
 )
@@ -19,6 +20,26 @@ func TestQueryCondition(t *testing.T) {
 			ctx.reducer = `{"type": "avg"}`
 			ctx.evaluator = `{"type": ">", "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.(*DefaultAlertEvaluator)
+					So(ok, ShouldBeTrue)
+					So(evaluator.Type, ShouldEqual, ">")
+				})
+			})
+
 			Convey("should fire when avg is above 100", func() {
 				ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", [][2]float64{{120, 0}})}
 				ctx.exec()
@@ -42,7 +63,8 @@ type queryConditionTestContext struct {
 	reducer   string
 	evaluator string
 	series    tsdb.TimeSeriesSlice
-	result    *AlertResultContext
+	result    *alerting.AlertResultContext
+	condition *QueryCondition
 }
 
 type queryConditionScenarioFunc func(c *queryConditionTestContext)
@@ -63,6 +85,8 @@ func (ctx *queryConditionTestContext) exec() {
 	condition, err := NewQueryCondition(jsonModel, 0)
 	So(err, ShouldBeNil)
 
+	ctx.condition = condition
+
 	condition.HandleRequest = func(req *tsdb.Request) (*tsdb.Response, error) {
 		return &tsdb.Response{
 			Results: map[string]*tsdb.QueryResult{
@@ -83,8 +107,8 @@ func queryConditionScenario(desc string, fn queryConditionScenarioFunc) {
 		})
 
 		ctx := &queryConditionTestContext{}
-		ctx.result = &AlertResultContext{
-			Rule: &AlertRule{},
+		ctx.result = &alerting.AlertResultContext{
+			Rule: &alerting.AlertRule{},
 		}
 
 		fn(ctx)

+ 29 - 0
pkg/services/alerting/conditions/reducer.go

@@ -0,0 +1,29 @@
+package conditions
+
+import "github.com/grafana/grafana/pkg/tsdb"
+
+type QueryReducer interface {
+	Reduce(timeSeries *tsdb.TimeSeries) float64
+}
+
+type SimpleReducer struct {
+	Type string
+}
+
+func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) float64 {
+	var value float64 = 0
+
+	switch s.Type {
+	case "avg":
+		for _, point := range series.Points {
+			value += point[0]
+		}
+		value = value / float64(len(series.Points))
+	}
+
+	return value
+}
+
+func NewSimpleReducer(typ string) *SimpleReducer {
+	return &SimpleReducer{Type: typ}
+}

+ 5 - 0
pkg/services/alerting/extractor_test.go

@@ -12,6 +12,11 @@ import (
 func TestAlertRuleExtraction(t *testing.T) {
 
 	Convey("Parsing alert rules  from dashboard json", t, func() {
+
+		RegisterCondition("query", func(model *simplejson.Json, index int) (AlertCondition, error) {
+			return &FakeCondition{}, nil
+		})
+
 		Convey("Parsing and validating alerts from dashboards", func() {
 			json := `{
         "id": 57,

+ 1 - 0
pkg/services/alerting/init/init.go

@@ -2,6 +2,7 @@ package init
 
 import (
 	"github.com/grafana/grafana/pkg/services/alerting"
+	_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
 	_ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
 	"github.com/grafana/grafana/pkg/setting"
 	_ "github.com/grafana/grafana/pkg/tsdb/graphite"

+ 1 - 13
pkg/services/alerting/interfaces.go

@@ -1,10 +1,6 @@
 package alerting
 
-import (
-	"time"
-
-	"github.com/grafana/grafana/pkg/tsdb"
-)
+import "time"
 
 type AlertHandler interface {
 	Execute(context *AlertResultContext)
@@ -23,11 +19,3 @@ type Notifier interface {
 type AlertCondition interface {
 	Eval(result *AlertResultContext)
 }
-
-type QueryReducer interface {
-	Reduce(timeSeries *tsdb.TimeSeries) float64
-}
-
-type AlertEvaluator interface {
-	Eval(timeSeries *tsdb.TimeSeries, reducedValue float64) bool
-}

+ 0 - 8
pkg/services/alerting/models.go

@@ -3,7 +3,6 @@ package alerting
 import (
 	"time"
 
-	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/log"
 )
 
@@ -61,10 +60,3 @@ type Level struct {
 	Operator string
 	Value    float64
 }
-
-type AlertQuery struct {
-	Model        *simplejson.Json
-	DatasourceId int64
-	From         string
-	To           string
-}