Ver Fonte

Merge branch 'master' of github.com:grafana/grafana

Conflicts:
	pkg/services/alerting/eval_context.go
Torkel Ödegaard há 9 anos atrás
pai
commit
fc8f0721cd

+ 3 - 0
CHANGELOG.md

@@ -11,6 +11,9 @@
 * **Graphite**: Add support for groupByNode, closes [#5613](https://github.com/grafana/grafana/pull/5613)
 * **Influxdb**: Add support for elapsed(), closes [#5827](https://github.com/grafana/grafana/pull/5827)
 
+### Breaking changes
+* **SystemD**: Change systemd description, closes [#5971](https://github.com/grafana/grafana/pull/5971)
+
 # 3.1.2 (unreleased)
 * **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)
 * **Drag&Drop**: Fixed issue with drag and drop in latest Chrome(51+), fixes [#5767](https://github.com/grafana/grafana/issues/5767)

+ 1 - 1
packaging/deb/systemd/grafana-server.service

@@ -1,5 +1,5 @@
 [Unit]
-Description=Starts and stops a single grafana instance on this system
+Description=Grafana instance
 Documentation=http://docs.grafana.org
 Wants=network-online.target
 After=network-online.target

+ 1 - 1
packaging/rpm/systemd/grafana-server.service

@@ -1,5 +1,5 @@
 [Unit]
-Description=Starts and stops a single grafana instance on this system
+Description=Grafana instance
 Documentation=http://docs.grafana.org
 Wants=network-online.target
 After=network-online.target

+ 2 - 0
pkg/services/alerting/eval_context.go

@@ -27,6 +27,7 @@ type EvalContext struct {
 	ImagePublicUrl  string
 	ImageOnDiskPath string
 	NoDataFound     bool
+	RetryCount      int
 }
 
 type StateDescription struct {
@@ -112,5 +113,6 @@ func NewEvalContext(rule *Rule) *EvalContext {
 		DoneChan:    make(chan bool, 1),
 		CancelChan:  make(chan bool, 1),
 		log:         log.New("alerting.evalContext"),
+		RetryCount:  0,
 	}
 }

+ 19 - 2
pkg/services/alerting/eval_handler.go

@@ -8,6 +8,10 @@ import (
 	"github.com/grafana/grafana/pkg/metrics"
 )
 
+var (
+	MaxRetries int = 1
+)
+
 type DefaultEvalHandler struct {
 	log             log.Logger
 	alertJobTimeout time.Duration
@@ -21,7 +25,6 @@ func NewEvalHandler() *DefaultEvalHandler {
 }
 
 func (e *DefaultEvalHandler) Eval(context *EvalContext) {
-
 	go e.eval(context)
 
 	select {
@@ -29,14 +32,28 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
 		context.Error = fmt.Errorf("Timeout")
 		context.EndTime = time.Now()
 		e.log.Debug("Job Execution timeout", "alertId", context.Rule.Id)
+		e.retry(context)
 	case <-context.DoneChan:
 		e.log.Debug("Job Execution done", "timeMs", context.GetDurationMs(), "alertId", context.Rule.Id, "firing", context.Firing)
+
+		if context.Error != nil {
+			e.retry(context)
+		}
 	}
+}
+
+func (e *DefaultEvalHandler) retry(context *EvalContext) {
+	e.log.Debug("Retrying eval exeuction", "alertId", context.Rule.Id)
 
+	context.RetryCount++
+	if context.RetryCount > MaxRetries {
+		context.DoneChan = make(chan bool, 1)
+		context.CancelChan = make(chan bool, 1)
+		e.Eval(context)
+	}
 }
 
 func (e *DefaultEvalHandler) eval(context *EvalContext) {
-
 	for _, condition := range context.Rule.Conditions {
 		condition.Eval(context)
 

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

@@ -40,6 +40,5 @@ func TestAlertingExecutor(t *testing.T) {
 			handler.eval(context)
 			So(context.Firing, ShouldEqual, false)
 		})
-
 	})
 }

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

@@ -1,6 +1,10 @@
 package alerting
 
-import "time"
+import (
+	"time"
+
+	"github.com/grafana/grafana/pkg/models"
+)
 
 type EvalHandler interface {
 	Eval(context *EvalContext)
@@ -15,6 +19,7 @@ type Notifier interface {
 	Notify(alertResult *EvalContext)
 	GetType() string
 	NeedsImage() bool
+	MatchSeverity(result models.AlertSeverityType) bool
 }
 
 type Condition interface {

+ 23 - 5
pkg/services/alerting/notifier.go

@@ -28,10 +28,14 @@ func (n *RootNotifier) NeedsImage() bool {
 	return false
 }
 
+func (n *RootNotifier) MatchSeverity(result m.AlertSeverityType) bool {
+	return false
+}
+
 func (n *RootNotifier) Notify(context *EvalContext) {
 	n.log.Info("Sending notifications for", "ruleId", context.Rule.Id)
 
-	notifiers, err := n.getNotifiers(context.Rule.OrgId, context.Rule.Notifications)
+	notifiers, err := n.getNotifiers(context.Rule.OrgId, context.Rule.Notifications, context)
 	if err != nil {
 		n.log.Error("Failed to read notifications", "error", err)
 		return
@@ -87,7 +91,7 @@ func (n *RootNotifier) uploadImage(context *EvalContext) error {
 	return nil
 }
 
-func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64) ([]Notifier, error) {
+func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64, context *EvalContext) ([]Notifier, error) {
 	query := &m.GetAlertNotificationsToSendQuery{OrgId: orgId, Ids: notificationIds}
 
 	if err := bus.Dispatch(query); err != nil {
@@ -96,17 +100,19 @@ func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64) ([]Not
 
 	var result []Notifier
 	for _, notification := range query.Result {
-		if not, err := n.getNotifierFor(notification); err != nil {
+		if not, err := n.createNotifierFor(notification); err != nil {
 			return nil, err
 		} else {
-			result = append(result, not)
+			if shouldUseNotification(not, context) {
+				result = append(result, not)
+			}
 		}
 	}
 
 	return result, nil
 }
 
-func (n *RootNotifier) getNotifierFor(model *m.AlertNotification) (Notifier, error) {
+func (n *RootNotifier) createNotifierFor(model *m.AlertNotification) (Notifier, error) {
 	factory, found := notifierFactories[model.Type]
 	if !found {
 		return nil, errors.New("Unsupported notification type")
@@ -115,6 +121,18 @@ func (n *RootNotifier) getNotifierFor(model *m.AlertNotification) (Notifier, err
 	return factory(model)
 }
 
+func shouldUseNotification(notifier Notifier, context *EvalContext) bool {
+	if !context.Firing {
+		return true
+	}
+
+	if context.Error != nil {
+		return true
+	}
+
+	return notifier.MatchSeverity(context.Rule.Severity)
+}
+
 type NotifierFactory func(notification *m.AlertNotification) (Notifier, error)
 
 var notifierFactories map[string]NotifierFactory = make(map[string]NotifierFactory)

+ 80 - 112
pkg/services/alerting/notifier_test.go

@@ -1,114 +1,82 @@
 package alerting
 
-// func TestAlertNotificationExtraction(t *testing.T) {
-// 	Convey("Notifier tests", t, func() {
-// 		Convey("rules for sending notifications", func() {
-// 			dummieNotifier := NotifierImpl{}
-//
-// 			result := &AlertResult{
-// 				State: alertstates.Critical,
-// 			}
-//
-// 			notifier := &Notification{
-// 				Name:         "Test Notifier",
-// 				Type:         "TestType",
-// 				SendCritical: true,
-// 				SendWarning:  true,
-// 			}
-//
-// 			Convey("Should send notification", func() {
-// 				So(dummieNotifier.ShouldDispath(result, notifier), ShouldBeTrue)
-// 			})
-//
-// 			Convey("warn:false and state:warn should not send", func() {
-// 				result.State = alertstates.Warn
-// 				notifier.SendWarning = false
-// 				So(dummieNotifier.ShouldDispath(result, notifier), ShouldBeFalse)
-// 			})
-// 		})
-//
-// 		Convey("Parsing alert notification from settings", func() {
-// 			Convey("Parsing email", func() {
-// 				Convey("empty settings should return error", func() {
-// 					json := `{ }`
-//
-// 					settingsJSON, _ := simplejson.NewJson([]byte(json))
-// 					model := &m.AlertNotification{
-// 						Name:     "ops",
-// 						Type:     "email",
-// 						Settings: settingsJSON,
-// 					}
-//
-// 					_, err := NewNotificationFromDBModel(model)
-// 					So(err, ShouldNotBeNil)
-// 				})
-//
-// 				Convey("from settings", func() {
-// 					json := `
-// 				{
-// 					"to": "ops@grafana.org"
-// 				}`
-//
-// 					settingsJSON, _ := simplejson.NewJson([]byte(json))
-// 					model := &m.AlertNotification{
-// 						Name:     "ops",
-// 						Type:     "email",
-// 						Settings: settingsJSON,
-// 					}
-//
-// 					not, err := NewNotificationFromDBModel(model)
-//
-// 					So(err, ShouldBeNil)
-// 					So(not.Name, ShouldEqual, "ops")
-// 					So(not.Type, ShouldEqual, "email")
-// 					So(reflect.TypeOf(not.Notifierr).Elem().String(), ShouldEqual, "alerting.EmailNotifier")
-//
-// 					email := not.Notifierr.(*EmailNotifier)
-// 					So(email.To, ShouldEqual, "ops@grafana.org")
-// 				})
-// 			})
-//
-// 			Convey("Parsing webhook", func() {
-// 				Convey("empty settings should return error", func() {
-// 					json := `{ }`
-//
-// 					settingsJSON, _ := simplejson.NewJson([]byte(json))
-// 					model := &m.AlertNotification{
-// 						Name:     "ops",
-// 						Type:     "webhook",
-// 						Settings: settingsJSON,
-// 					}
-//
-// 					_, err := NewNotificationFromDBModel(model)
-// 					So(err, ShouldNotBeNil)
-// 				})
-//
-// 				Convey("from settings", func() {
-// 					json := `
-// 				{
-// 					"url": "http://localhost:3000",
-// 					"username": "username",
-// 					"password": "password"
-// 				}`
-//
-// 					settingsJSON, _ := simplejson.NewJson([]byte(json))
-// 					model := &m.AlertNotification{
-// 						Name:     "slack",
-// 						Type:     "webhook",
-// 						Settings: settingsJSON,
-// 					}
-//
-// 					not, err := NewNotificationFromDBModel(model)
-//
-// 					So(err, ShouldBeNil)
-// 					So(not.Name, ShouldEqual, "slack")
-// 					So(not.Type, ShouldEqual, "webhook")
-// 					So(reflect.TypeOf(not.Notifierr).Elem().String(), ShouldEqual, "alerting.WebhookNotifier")
-//
-// 					webhook := not.Notifierr.(*WebhookNotifier)
-// 					So(webhook.Url, ShouldEqual, "http://localhost:3000")
-// 				})
-// 			})
-// 		})
-// 	})
-// }
+import (
+	"testing"
+
+	"fmt"
+
+	"github.com/grafana/grafana/pkg/models"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+type FakeNotifier struct {
+	FakeMatchResult bool
+}
+
+func (fn *FakeNotifier) GetType() string {
+	return "FakeNotifier"
+}
+
+func (fn *FakeNotifier) NeedsImage() bool {
+	return true
+}
+
+func (fn *FakeNotifier) Notify(alertResult *EvalContext) {}
+
+func (fn *FakeNotifier) MatchSeverity(result models.AlertSeverityType) bool {
+	return fn.FakeMatchResult
+}
+
+func TestAlertNotificationExtraction(t *testing.T) {
+
+	Convey("Notifier tests", t, func() {
+		Convey("none firing alerts", func() {
+			ctx := &EvalContext{
+				Firing: false,
+				Rule: &Rule{
+					Severity: models.AlertSeverityCritical,
+				},
+			}
+			notifier := &FakeNotifier{FakeMatchResult: false}
+
+			So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
+		})
+
+		Convey("exeuction error cannot be ignored", func() {
+			ctx := &EvalContext{
+				Firing: true,
+				Error:  fmt.Errorf("I used to be a programmer just like you"),
+				Rule: &Rule{
+					Severity: models.AlertSeverityCritical,
+				},
+			}
+			notifier := &FakeNotifier{FakeMatchResult: false}
+
+			So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
+		})
+
+		Convey("firing alert that match", func() {
+			ctx := &EvalContext{
+				Firing: true,
+				Rule: &Rule{
+					Severity: models.AlertSeverityCritical,
+				},
+			}
+			notifier := &FakeNotifier{FakeMatchResult: true}
+
+			So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
+		})
+
+		Convey("firing alert that dont match", func() {
+			ctx := &EvalContext{
+				Firing: true,
+				Rule: &Rule{
+					Severity: models.AlertSeverityCritical,
+				},
+			}
+			notifier := &FakeNotifier{FakeMatchResult: false}
+
+			So(shouldUseNotification(notifier, ctx), ShouldBeFalse)
+		})
+	})
+}

+ 28 - 2
pkg/services/alerting/notifiers/base.go

@@ -1,8 +1,34 @@
 package notifiers
 
+import (
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	"github.com/grafana/grafana/pkg/models"
+)
+
 type NotifierBase struct {
-	Name string
-	Type string
+	Name           string
+	Type           string
+	SeverityFilter models.AlertSeverityType
+}
+
+func NewNotifierBase(name, notifierType string, model *simplejson.Json) NotifierBase {
+	base := NotifierBase{Name: name, Type: notifierType}
+
+	severityFilter := models.AlertSeverityType(model.Get("severityFilter").MustString(""))
+
+	if severityFilter == models.AlertSeverityCritical || severityFilter == models.AlertSeverityWarning {
+		base.SeverityFilter = severityFilter
+	}
+
+	return base
+}
+
+func (n *NotifierBase) MatchSeverity(result models.AlertSeverityType) bool {
+	if !n.SeverityFilter.IsValid() {
+		return true
+	}
+
+	return n.SeverityFilter == result
 }
 
 func (n *NotifierBase) GetType() string {

+ 36 - 0
pkg/services/alerting/notifiers/base_test.go

@@ -0,0 +1,36 @@
+package notifiers
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	m "github.com/grafana/grafana/pkg/models"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestBaseNotifier(t *testing.T) {
+	Convey("Parsing base notification severity", t, func() {
+
+		Convey("matches", func() {
+			json := `
+				{
+					"severityFilter": "critical"
+				}`
+
+			settingsJSON, _ := simplejson.NewJson([]byte(json))
+			not := NewNotifierBase("ops", "email", settingsJSON)
+			So(not.MatchSeverity(m.AlertSeverityCritical), ShouldBeTrue)
+		})
+
+		Convey("does not match", func() {
+			json := `
+				{
+					"severityFilter": "critical"
+				}`
+
+			settingsJSON, _ := simplejson.NewJson([]byte(json))
+			not := NewNotifierBase("ops", "email", settingsJSON)
+			So(not.MatchSeverity(m.AlertSeverityWarning), ShouldBeFalse)
+		})
+	})
+}

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

@@ -1 +0,0 @@
-package notifiers

+ 3 - 6
pkg/services/alerting/notifiers/email.go

@@ -29,12 +29,9 @@ func NewEmailNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &EmailNotifier{
-		NotifierBase: NotifierBase{
-			Name: model.Name,
-			Type: model.Type,
-		},
-		Addresses: strings.Split(addressesString, "\n"),
-		log:       log.New("alerting.notifier.email"),
+		NotifierBase: NewNotifierBase(model.Name, model.Type, model.Settings),
+		Addresses:    strings.Split(addressesString, "\n"),
+		log:          log.New("alerting.notifier.email"),
 	}, nil
 }
 

+ 3 - 6
pkg/services/alerting/notifiers/slack.go

@@ -23,12 +23,9 @@ func NewSlackNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &SlackNotifier{
-		NotifierBase: NotifierBase{
-			Name: model.Name,
-			Type: model.Type,
-		},
-		Url: url,
-		log: log.New("alerting.notifier.slack"),
+		NotifierBase: NewNotifierBase(model.Name, model.Type, model.Settings),
+		Url:          url,
+		log:          log.New("alerting.notifier.slack"),
 	}, nil
 }
 

+ 5 - 8
pkg/services/alerting/notifiers/webhook.go

@@ -20,14 +20,11 @@ func NewWebHookNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 	}
 
 	return &WebhookNotifier{
-		NotifierBase: NotifierBase{
-			Name: model.Name,
-			Type: model.Type,
-		},
-		Url:      url,
-		User:     model.Settings.Get("user").MustString(),
-		Password: model.Settings.Get("password").MustString(),
-		log:      log.New("alerting.notifier.webhook"),
+		NotifierBase: NewNotifierBase(model.Name, model.Type, model.Settings),
+		Url:          url,
+		User:         model.Settings.Get("user").MustString(),
+		Password:     model.Settings.Get("password").MustString(),
+		log:          log.New("alerting.notifier.webhook"),
 	}, nil
 }
 

+ 1 - 1
pkg/services/alerting/test_notification.go

@@ -28,7 +28,7 @@ func handleNotificationTestCommand(cmd *NotificationTestCommand) error {
 		Settings: cmd.Settings,
 	}
 
-	notifiers, err := notifier.getNotifierFor(model)
+	notifiers, err := notifier.createNotifierFor(model)
 
 	if err != nil {
 		log.Error2("Failed to create notifier", "error", err.Error())

+ 7 - 0
public/app/features/alerting/alert_tab_ctrl.ts

@@ -108,6 +108,13 @@ export class AlertTabCtrl {
     }));
   }
 
+  changeTabIndex(newTabIndex) {
+    this.subTabIndex = newTabIndex;
+
+    if (this.subTabIndex === 2) {
+      this.getAlertHistory();
+    }
+  }
 
   notificationAdded() {
     var model = _.findWhere(this.notifications, {name: this.addNotificationSegment.value});

+ 3 - 1
public/app/features/alerting/notification_edit_ctrl.ts

@@ -17,7 +17,9 @@ export class AlertNotificationEditCtrl {
     } else {
       this.model = {
         type: 'email',
-        settings: {},
+        settings: {
+          severityFilter: 'none'
+        },
         isDefault: false
       };
     }

+ 3 - 3
public/app/features/alerting/partials/alert_tab.html

@@ -2,15 +2,15 @@
 	<aside class="edit-sidemenu-aside">
 		<ul class="edit-sidemenu">
 			<li ng-class="{active: ctrl.subTabIndex === 0}">
-				<a ng-click="ctrl.subTabIndex = 0">Alert Config</a>
+				<a ng-click="ctrl.changeTabIndex(0)">Alert Config</a>
 			</li>
 			<li ng-class="{active: ctrl.subTabIndex === 1}">
-				<a ng-click="ctrl.subTabIndex = 1">
+				<a ng-click="ctrl.changeTabIndex(1)">
 					Notifications <span class="muted">({{ctrl.alert.notifications.length}})</span>
 				</a>
 			</li>
 			<li ng-class="{active: ctrl.subTabIndex === 2}">
-				<a ng-click="ctrl.subTabIndex = 2">Alert History</a>
+				<a ng-click="ctrl.changeTabIndex(2)">Alert History</a>
 			</li>
       <li>
 				<a ng-click="ctrl.delete()">Delete</a>

+ 14 - 5
public/app/features/alerting/partials/notification_edit.html

@@ -12,11 +12,11 @@
 
 	<div class="gf-form-group">
 		<div class="gf-form">
-			<span class="gf-form-label width-8">Name</span>
+			<span class="gf-form-label width-12">Name</span>
 			<input type="text" class="gf-form-input max-width-15" ng-model="ctrl.model.name" required></input>
 		</div>
 		<div class="gf-form">
-			<span class="gf-form-label width-8">Type</span>
+			<span class="gf-form-label width-12">Type</span>
 			<div class="gf-form-select-wrapper width-15">
 				<select class="gf-form-input"
 					ng-model="ctrl.model.type"
@@ -25,11 +25,20 @@
 				</select>
 			</div>
 		</div>
+		<div class="gf-form">
+			<span class="gf-form-label width-12">Severity filter</span>
+			<div class="gf-form-select-wrapper width-15">
+				<select class="gf-form-input"
+					ng-model="ctrl.model.settings.severityFilter"
+					ng-options="t for t in ['none', 'critical', 'warning']">
+				</select>
+			</div>
+		</div>
 		<div class="gf-form">
 			<gf-form-switch
 				class="gf-form"
-				label="All alerts"
-				label-class="width-8"
+				label="Send on all alerts"
+				label-class="width-12"
 				checked="ctrl.model.isDefault"
 				tooltip="Use this notification for all alerts">
 			</gf-form-switch>
@@ -91,5 +100,5 @@
 				<button ng-click="ctrl.testNotification()" class="btn btn-secondary">Send</button>
 			</div>
 		</div>
-  </div>
+	</div>
 </div>