diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 5f937a5cbd8..06302484cda 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -48,6 +48,7 @@ var ( M_Alerting_Notification_Sent_Victorops Counter M_Alerting_Notification_Sent_OpsGenie Counter M_Alerting_Notification_Sent_Telegram Counter + M_Alerting_Notification_Sent_Sensu Counter M_Aws_CloudWatch_GetMetricStatistics Counter M_Aws_CloudWatch_ListMetrics Counter @@ -116,6 +117,7 @@ func initMetricVars(settings *MetricSettings) { M_Alerting_Notification_Sent_Victorops = RegCounter("alerting.notifications_sent", "type", "victorops") M_Alerting_Notification_Sent_OpsGenie = RegCounter("alerting.notifications_sent", "type", "opsgenie") M_Alerting_Notification_Sent_Telegram = RegCounter("alerting.notifications_sent", "type", "telegram") + M_Alerting_Notification_Sent_Sensu = RegCounter("alerting.notifications_sent", "type", "sensu") M_Aws_CloudWatch_GetMetricStatistics = RegCounter("aws.cloudwatch.get_metric_statistics") M_Aws_CloudWatch_ListMetrics = RegCounter("aws.cloudwatch.list_metrics") diff --git a/pkg/services/alerting/notifiers/sensu.go b/pkg/services/alerting/notifiers/sensu.go new file mode 100644 index 00000000000..dbe31f4cf84 --- /dev/null +++ b/pkg/services/alerting/notifiers/sensu.go @@ -0,0 +1,115 @@ +package notifiers + +import ( + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/metrics" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/alerting" + "strconv" + "strings" +) + +func init() { + alerting.RegisterNotifier(&alerting.NotifierPlugin{ + Type: "sensu", + Name: "Sensu", + Description: "Sends HTTP POST request to a Sensu API", + Factory: NewSensuNotifier, + OptionsTemplate: ` +

Sensu settings

+
+ Url + +
+
+ Username + +
+
+ Password + +
+ `, + }) + +} + +func NewSensuNotifier(model *m.AlertNotification) (alerting.Notifier, error) { + url := model.Settings.Get("url").MustString() + if url == "" { + return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} + } + + return &SensuNotifier{ + NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings), + Url: url, + User: model.Settings.Get("username").MustString(), + Password: model.Settings.Get("password").MustString(), + log: log.New("alerting.notifier.sensu"), + }, nil +} + +type SensuNotifier struct { + NotifierBase + Url string + User string + Password string + log log.Logger +} + +func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error { + this.log.Info("Sending sensu result") + metrics.M_Alerting_Notification_Sent_Sensu.Inc(1) + + bodyJSON := simplejson.New() + bodyJSON.Set("ruleId", evalContext.Rule.Id) + // Sensu alerts cannot have spaces in them + bodyJSON.Set("name", strings.Replace(evalContext.Rule.Name, " ", "_", -1)) + // Sensu alerts require a command + // We set it to the grafana ruleID + bodyJSON.Set("source", "grafana_rule_"+strconv.FormatInt(evalContext.Rule.Id, 10)) + // Finally, sensu expects an output + // We set it to a default output + bodyJSON.Set("output", "Grafana Metric Condition Met") + bodyJSON.Set("evalMatches", evalContext.EvalMatches) + + if evalContext.Rule.State == "alerting" { + bodyJSON.Set("status", 2) + } else if evalContext.Rule.State == "no_data" { + bodyJSON.Set("status", 1) + } else { + bodyJSON.Set("status", 0) + } + + ruleUrl, err := evalContext.GetRuleUrl() + if err == nil { + bodyJSON.Set("ruleUrl", ruleUrl) + } + + if evalContext.ImagePublicUrl != "" { + bodyJSON.Set("imageUrl", evalContext.ImagePublicUrl) + } + + if evalContext.Rule.Message != "" { + bodyJSON.Set("message", evalContext.Rule.Message) + } + + body, _ := bodyJSON.MarshalJSON() + + cmd := &m.SendWebhookSync{ + Url: this.Url, + User: this.User, + Password: this.Password, + Body: string(body), + HttpMethod: "POST", + } + + if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { + this.log.Error("Failed to send sensu event", "error", err, "sensu", this.Name) + return err + } + + return nil +} diff --git a/pkg/services/alerting/notifiers/sensu_test.go b/pkg/services/alerting/notifiers/sensu_test.go new file mode 100644 index 00000000000..ffbdcfaf15c --- /dev/null +++ b/pkg/services/alerting/notifiers/sensu_test.go @@ -0,0 +1,52 @@ +package notifiers + +import ( + "testing" + + "github.com/grafana/grafana/pkg/components/simplejson" + m "github.com/grafana/grafana/pkg/models" + . "github.com/smartystreets/goconvey/convey" +) + +func TestSensuNotifier(t *testing.T) { + Convey("Sensu notifier tests", t, func() { + + Convey("Parsing alert notification from settings", func() { + Convey("empty settings should return error", func() { + json := `{ }` + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &m.AlertNotification{ + Name: "sensu", + Type: "sensu", + Settings: settingsJSON, + } + + _, err := NewSensuNotifier(model) + So(err, ShouldNotBeNil) + }) + + Convey("from settings", func() { + json := ` + { + "url": "http://sensu-api.example.com:4567/results" + }` + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &m.AlertNotification{ + Name: "sensu", + Type: "sensu", + Settings: settingsJSON, + } + + not, err := NewSensuNotifier(model) + sensuNotifier := not.(*SensuNotifier) + + So(err, ShouldBeNil) + So(sensuNotifier.Name, ShouldEqual, "sensu") + So(sensuNotifier.Type, ShouldEqual, "sensu") + So(sensuNotifier.Url, ShouldEqual, "http://sensu-api.example.com:4567/results") + }) + }) + }) +}