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")
+ })
+ })
+ })
+}