mirror of https://github.com/grafana/grafana.git
				
				
				
			Merge branch 'master' of github.com:grafana/grafana
This commit is contained in:
		
						commit
						a7068d835a
					
				| 
						 | 
				
			
			@ -23,7 +23,8 @@
 | 
			
		|||
* **Prometheus**: Align $__interval with the step parameters. [#9226](https://github.com/grafana/grafana/pull/9226), thx [@alin-amana](https://github.com/alin-amana)
 | 
			
		||||
* **Prometheus**: Autocomplete for label name and label value [#9208](https://github.com/grafana/grafana/pull/9208), thx [@mtanda](https://github.com/mtanda)
 | 
			
		||||
* **Postgres**: New Postgres data source [#9209](https://github.com/grafana/grafana/pull/9209), thx [@svenklemm](https://github.com/svenklemm)
 | 
			
		||||
* **Datasources**: closes [#9371](https://github.com/grafana/grafana/issues/9371), [#5334](https://github.com/grafana/grafana/issues/5334), [#8812](https://github.com/grafana/grafana/issues/8812), thx [@mattbostock](https://github.com/mattbostock)
 | 
			
		||||
* **Datasources**: Make datasource HTTP requests verify TLS by default. closes [#9371](https://github.com/grafana/grafana/issues/9371), [#5334](https://github.com/grafana/grafana/issues/5334), [#8812](https://github.com/grafana/grafana/issues/8812), thx [@mattbostock](https://github.com/mattbostock)
 | 
			
		||||
* **OAuth**: Verify TLS during OAuth callback [#9373](https://github.com/grafana/grafana/issues/9373), thx [@mattbostock](https://github.com/mattbostock)
 | 
			
		||||
 | 
			
		||||
## Minor
 | 
			
		||||
* **SMTP**: Make it possible to set specific EHLO for smtp client. [#9319](https://github.com/grafana/grafana/issues/9319)
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +35,7 @@
 | 
			
		|||
* **Opsgenie**: Use their latest API instead of old version [#9399](https://github.com/grafana/grafana/pull/9399), thx [@cglrkn](https://github.com/cglrkn)
 | 
			
		||||
* **Table**: Add support for displaying the timestamp with milliseconds [#9429](https://github.com/grafana/grafana/pull/9429), thx [@s1061123](https://github.com/s1061123)
 | 
			
		||||
* **Hipchat**: Add metrics, message and image to hipchat notifications [#9110](https://github.com/grafana/grafana/issues/9110), thx [@eloo](https://github.com/eloo)
 | 
			
		||||
* **Kafka**: Add support for sending alert notifications to kafka [#7104](https://github.com/grafana/grafana/issues/7104), thx [@utkarshcmu](https://github.com/utkarshcmu)
 | 
			
		||||
 | 
			
		||||
## Tech
 | 
			
		||||
* **Go**: Grafana is now built using golang 1.9
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -115,6 +115,17 @@ In DingTalk PC Client:
 | 
			
		|||
 | 
			
		||||
Dingtalk supports the following "message type": `text`, `link` and `markdown`. Only the `text` message type is supported.
 | 
			
		||||
 | 
			
		||||
### Kafka
 | 
			
		||||
 | 
			
		||||
Notifications can be sent to a Kafka topic from Grafana using [Kafka REST Proxy](https://docs.confluent.io/1.0/kafka-rest/docs/index.html).
 | 
			
		||||
There are couple of configurations options which need to be set in Grafana UI under Kafka Settings:
 | 
			
		||||
 | 
			
		||||
1. Kafka REST Proxy endpoint.
 | 
			
		||||
 | 
			
		||||
2. Kafka Topic.
 | 
			
		||||
 | 
			
		||||
Once these two properties are set, you can send the alerts to Kafka for further processing or throttling them.
 | 
			
		||||
 | 
			
		||||
### Other Supported Notification Channels
 | 
			
		||||
 | 
			
		||||
Grafana also supports the following Notification Channels:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,8 @@ import (
 | 
			
		|||
	"path"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/prometheus/client_golang/prometheus"
 | 
			
		||||
 | 
			
		||||
	"github.com/prometheus/client_golang/prometheus/promhttp"
 | 
			
		||||
 | 
			
		||||
	gocache "github.com/patrickmn/go-cache"
 | 
			
		||||
| 
						 | 
				
			
			@ -187,7 +189,9 @@ func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	promhttp.Handler().ServeHTTP(ctx.Resp, ctx.Req.Request)
 | 
			
		||||
	promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{
 | 
			
		||||
		DisableCompression: true,
 | 
			
		||||
	}).ServeHTTP(ctx.Resp, ctx.Req.Request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,120 @@
 | 
			
		|||
package notifiers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/grafana/grafana/pkg/bus"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/components/simplejson"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/log"
 | 
			
		||||
	m "github.com/grafana/grafana/pkg/models"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/services/alerting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
 | 
			
		||||
		Type:        "kafka",
 | 
			
		||||
		Name:        "Kafka REST Proxy",
 | 
			
		||||
		Description: "Sends notifications to Kafka Rest Proxy",
 | 
			
		||||
		Factory:     NewKafkaNotifier,
 | 
			
		||||
		OptionsTemplate: `
 | 
			
		||||
      <h3 class="page-heading">Kafka settings</h3>
 | 
			
		||||
      <div class="gf-form">
 | 
			
		||||
        <span class="gf-form-label width-14">Kafka REST Proxy</span>
 | 
			
		||||
        <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.kafkaRestProxy" placeholder="http://localhost:8082"></input>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="gf-form">
 | 
			
		||||
        <span class="gf-form-label width-14">Topic</span>
 | 
			
		||||
        <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.kafkaTopic" placeholder="topic1"></input>
 | 
			
		||||
      </div>
 | 
			
		||||
    `,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewKafkaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 | 
			
		||||
	endpoint := model.Settings.Get("kafkaRestProxy").MustString()
 | 
			
		||||
	if endpoint == "" {
 | 
			
		||||
		return nil, alerting.ValidationError{Reason: "Could not find kafka rest proxy endpoint property in settings"}
 | 
			
		||||
	}
 | 
			
		||||
	topic := model.Settings.Get("kafkaTopic").MustString()
 | 
			
		||||
	if topic == "" {
 | 
			
		||||
		return nil, alerting.ValidationError{Reason: "Could not find kafka topic property in settings"}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &KafkaNotifier{
 | 
			
		||||
		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
 | 
			
		||||
		Endpoint:     endpoint,
 | 
			
		||||
		Topic:        topic,
 | 
			
		||||
		log:          log.New("alerting.notifier.kafka"),
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type KafkaNotifier struct {
 | 
			
		||||
	NotifierBase
 | 
			
		||||
	Endpoint string
 | 
			
		||||
	Topic    string
 | 
			
		||||
	log      log.Logger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
 | 
			
		||||
 | 
			
		||||
	state := evalContext.Rule.State
 | 
			
		||||
 | 
			
		||||
	customData := "Triggered metrics:\n\n"
 | 
			
		||||
	for _, evt := range evalContext.EvalMatches {
 | 
			
		||||
		customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.log.Info("Notifying Kafka", "alert_state", state)
 | 
			
		||||
 | 
			
		||||
	recordJSON := simplejson.New()
 | 
			
		||||
	records := make([]interface{}, 1)
 | 
			
		||||
 | 
			
		||||
	bodyJSON := simplejson.New()
 | 
			
		||||
	bodyJSON.Set("description", evalContext.Rule.Name+" - "+evalContext.Rule.Message)
 | 
			
		||||
	bodyJSON.Set("client", "Grafana")
 | 
			
		||||
	bodyJSON.Set("details", customData)
 | 
			
		||||
	bodyJSON.Set("incident_key", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10))
 | 
			
		||||
 | 
			
		||||
	ruleUrl, err := evalContext.GetRuleUrl()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.log.Error("Failed get rule link", "error", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	bodyJSON.Set("client_url", ruleUrl)
 | 
			
		||||
 | 
			
		||||
	if evalContext.ImagePublicUrl != "" {
 | 
			
		||||
		contexts := make([]interface{}, 1)
 | 
			
		||||
		imageJSON := simplejson.New()
 | 
			
		||||
		imageJSON.Set("type", "image")
 | 
			
		||||
		imageJSON.Set("src", evalContext.ImagePublicUrl)
 | 
			
		||||
		contexts[0] = imageJSON
 | 
			
		||||
		bodyJSON.Set("contexts", contexts)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	valueJSON := simplejson.New()
 | 
			
		||||
	valueJSON.Set("value", bodyJSON)
 | 
			
		||||
	records[0] = valueJSON
 | 
			
		||||
	recordJSON.Set("records", records)
 | 
			
		||||
	body, _ := recordJSON.MarshalJSON()
 | 
			
		||||
 | 
			
		||||
	topicUrl := this.Endpoint + "/topics/" + this.Topic
 | 
			
		||||
 | 
			
		||||
	cmd := &m.SendWebhookSync{
 | 
			
		||||
		Url:        topicUrl,
 | 
			
		||||
		Body:       string(body),
 | 
			
		||||
		HttpMethod: "POST",
 | 
			
		||||
		HttpHeader: map[string]string{
 | 
			
		||||
			"Content-Type": "application/vnd.kafka.json.v2+json",
 | 
			
		||||
			"Accept":       "application/vnd.kafka.v2+json",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
 | 
			
		||||
		this.log.Error("Failed to send notification to Kafka", "error", err, "body", string(body))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
package notifiers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/grafana/grafana/pkg/components/simplejson"
 | 
			
		||||
	m "github.com/grafana/grafana/pkg/models"
 | 
			
		||||
	. "github.com/smartystreets/goconvey/convey"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestKafkaNotifier(t *testing.T) {
 | 
			
		||||
	Convey("Kafka 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:     "kafka_testing",
 | 
			
		||||
					Type:     "kafka",
 | 
			
		||||
					Settings: settingsJSON,
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				_, err := NewKafkaNotifier(model)
 | 
			
		||||
				So(err, ShouldNotBeNil)
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			Convey("settings should send an event to kafka", func() {
 | 
			
		||||
				json := `
 | 
			
		||||
				{
 | 
			
		||||
					"kafkaRestProxy": "http://localhost:8082",
 | 
			
		||||
					"kafkaTopic": "topic1"
 | 
			
		||||
				}`
 | 
			
		||||
 | 
			
		||||
				settingsJSON, _ := simplejson.NewJson([]byte(json))
 | 
			
		||||
				model := &m.AlertNotification{
 | 
			
		||||
					Name:     "kafka_testing",
 | 
			
		||||
					Type:     "kafka",
 | 
			
		||||
					Settings: settingsJSON,
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				not, err := NewKafkaNotifier(model)
 | 
			
		||||
				kafkaNotifier := not.(*KafkaNotifier)
 | 
			
		||||
 | 
			
		||||
				So(err, ShouldBeNil)
 | 
			
		||||
				So(kafkaNotifier.Name, ShouldEqual, "kafka_testing")
 | 
			
		||||
				So(kafkaNotifier.Type, ShouldEqual, "kafka")
 | 
			
		||||
				So(kafkaNotifier.Endpoint, ShouldEqual, "http://localhost:8082")
 | 
			
		||||
				So(kafkaNotifier.Topic, ShouldEqual, "topic1")
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +94,7 @@ export class AlertTabCtrl {
 | 
			
		|||
      case "opsgenie": return "fa fa-bell";
 | 
			
		||||
      case "hipchat": return "fa fa-mail-forward";
 | 
			
		||||
      case "pushover": return "fa fa-mobile";
 | 
			
		||||
      case "kafka": return "fa fa-random";
 | 
			
		||||
    }
 | 
			
		||||
    return 'fa fa-bell';
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue