Просмотр исходного кода

Merge branch 'alerting' of github.com:grafana/grafana into alerting

Torkel Ödegaard 9 лет назад
Родитель
Сommit
7a5dda9e7c

+ 11 - 2
conf/defaults.ini

@@ -383,8 +383,17 @@ interval_seconds  = 60
 [grafana_net]
 url = https://grafana.net
 
-#################################### S3 Temp Store ##########################
-[s3-temp-image-store]
+#################################### External image storage ##########################
+[external_image_storage]
+# You can choose between (s3, webdav or internal)
+provider = s3
+
+[external_image_storage.s3]
 bucket_url =
 access_key =
 secret_key =
+
+[external_image_storage.webdav]
+url =
+username =
+password =

+ 16 - 0
conf/sample.ini

@@ -312,3 +312,19 @@ enabled           = true
 # Url used to to import dashboards directly from Grafana.net
 [grafana_net]
 url = https://grafana.net
+
+#################################### External image storage ##########################
+[external_image_storage]
+# Used for uploading images to public servers so they can be included in slack/email messages.
+# you can choose between (s3, webdav or internal)
+provider = s3
+
+[external_image_storage.s3]
+bucket_url =
+access_key =
+secret_key =
+
+[external_image_storage.webdav]
+url =
+username =
+password =

+ 37 - 37
pkg/components/imguploader/imguploader.go

@@ -1,57 +1,57 @@
 package imguploader
 
 import (
-	"io/ioutil"
-	"net/http"
+	"fmt"
 
-	"github.com/grafana/grafana/pkg/util"
-	"github.com/kr/s3/s3util"
+	"github.com/grafana/grafana/pkg/setting"
 )
 
-type Uploader interface {
+type ImageUploader interface {
 	Upload(path string) (string, error)
 }
 
-type S3Uploader struct {
-	bucket    string
-	secretKey string
-	accessKey string
-}
+func NewImageUploader() (ImageUploader, error) {
 
-func NewS3Uploader(bucket, accessKey, secretKey string) *S3Uploader {
-	return &S3Uploader{
-		bucket:    bucket,
-		accessKey: accessKey,
-		secretKey: secretKey,
-	}
-}
+	switch setting.ImageUploadProvider {
+	case "s3":
+		s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
+		if err != nil {
+			return nil, err
+		}
 
-func (u *S3Uploader) Upload(path string) (string, error) {
+		bucket := s3sec.Key("secret_key").String()
+		accessKey := s3sec.Key("access_key").String()
+		secretKey := s3sec.Key("secret_key").String()
 
-	s3util.DefaultConfig.AccessKey = u.accessKey
-	s3util.DefaultConfig.SecretKey = u.secretKey
+		if bucket == "" {
+			return nil, fmt.Errorf("Could not find bucket setting for image.uploader.s3")
+		}
 
-	header := make(http.Header)
-	header.Add("x-amz-acl", "public-read")
-	header.Add("Content-Type", "image/png")
+		if accessKey == "" {
+			return nil, fmt.Errorf("Could not find accessKey setting for image.uploader.s3")
+		}
 
-	fullUrl := u.bucket + util.GetRandomString(20) + ".png"
-	writer, err := s3util.Create(fullUrl, header, nil)
-	if err != nil {
-		return "", err
-	}
+		if secretKey == "" {
+			return nil, fmt.Errorf("Could not find secretKey setting for image.uploader.s3")
+		}
 
-	defer writer.Close()
+		return NewS3Uploader(bucket, accessKey, secretKey), nil
+	case "webdav":
+		webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav")
+		if err != nil {
+			return nil, err
+		}
 
-	imgData, err := ioutil.ReadFile(path)
-	if err != nil {
-		return "", err
-	}
+		url := webdavSec.Key("url").String()
+		if url == "" {
+			return nil, fmt.Errorf("Could not find url key for image.uploader.webdav")
+		}
+
+		username := webdavSec.Key("username").String()
+		password := webdavSec.Key("password").String()
 
-	_, err = writer.Write(imgData)
-	if err != nil {
-		return "", err
+		return NewWebdavImageUploader(url, username, password)
 	}
 
-	return fullUrl, nil
+	return nil, fmt.Errorf("could not find specified provider")
 }

+ 53 - 0
pkg/components/imguploader/imguploader_test.go

@@ -0,0 +1,53 @@
+package imguploader
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/grafana/grafana/pkg/setting"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestImageUploaderFactory(t *testing.T) {
+	Convey("Can create image uploader for ", t, func() {
+		Convey("S3ImageUploader", func() {
+			var err error
+			setting.NewConfigContext(&setting.CommandLineArgs{
+				HomePath: "../../../",
+			})
+
+			setting.ImageUploadProvider = "s3"
+
+			s3sec, err := setting.Cfg.GetSection("external_image_storage.s3")
+			s3sec.NewKey("bucket_url", "bucket_url")
+			s3sec.NewKey("access_key", "access_key")
+			s3sec.NewKey("secret_key", "secret_key")
+
+			uploader, err := NewImageUploader()
+
+			So(err, ShouldBeNil)
+			So(reflect.TypeOf(uploader), ShouldEqual, reflect.TypeOf(&S3Uploader{}))
+		})
+
+		Convey("Webdav uploader", func() {
+			var err error
+
+			setting.NewConfigContext(&setting.CommandLineArgs{
+				HomePath: "../../../",
+			})
+
+			setting.ImageUploadProvider = "webdav"
+
+			webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav")
+			webdavSec.NewKey("url", "webdavUrl")
+			webdavSec.NewKey("username", "username")
+			webdavSec.NewKey("password", "password")
+
+			uploader, err := NewImageUploader()
+
+			So(err, ShouldBeNil)
+			So(reflect.TypeOf(uploader), ShouldEqual, reflect.TypeOf(&WebdavUploader{}))
+		})
+	})
+}

+ 53 - 0
pkg/components/imguploader/s3uploader.go

@@ -0,0 +1,53 @@
+package imguploader
+
+import (
+	"io/ioutil"
+	"net/http"
+
+	"github.com/grafana/grafana/pkg/util"
+	"github.com/kr/s3/s3util"
+)
+
+type S3Uploader struct {
+	bucket    string
+	secretKey string
+	accessKey string
+}
+
+func NewS3Uploader(bucket, accessKey, secretKey string) *S3Uploader {
+	return &S3Uploader{
+		bucket:    bucket,
+		accessKey: accessKey,
+		secretKey: secretKey,
+	}
+}
+
+func (u *S3Uploader) Upload(path string) (string, error) {
+
+	s3util.DefaultConfig.AccessKey = u.accessKey
+	s3util.DefaultConfig.SecretKey = u.secretKey
+
+	header := make(http.Header)
+	header.Add("x-amz-acl", "public-read")
+	header.Add("Content-Type", "image/png")
+
+	fullUrl := u.bucket + util.GetRandomString(20) + ".png"
+	writer, err := s3util.Create(fullUrl, header, nil)
+	if err != nil {
+		return "", err
+	}
+
+	defer writer.Close()
+
+	imgData, err := ioutil.ReadFile(path)
+	if err != nil {
+		return "", err
+	}
+
+	_, err = writer.Write(imgData)
+	if err != nil {
+		return "", err
+	}
+
+	return fullUrl, nil
+}

+ 51 - 0
pkg/components/imguploader/webdavuploader.go

@@ -0,0 +1,51 @@
+package imguploader
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"path"
+	"time"
+
+	"github.com/grafana/grafana/pkg/log"
+	"github.com/grafana/grafana/pkg/util"
+)
+
+type WebdavUploader struct {
+	url      string
+	username string
+	password string
+}
+
+func (u *WebdavUploader) Upload(pa string) (string, error) {
+	log.Error2("Hej")
+	client := http.Client{Timeout: time.Duration(10 * time.Second)}
+
+	url, _ := url.Parse(u.url)
+	url.Path = path.Join(url.Path, util.GetRandomString(20)+".png")
+
+	imgData, err := ioutil.ReadFile(pa)
+	req, err := http.NewRequest("PUT", url.String(), bytes.NewReader(imgData))
+	res, err := client.Do(req)
+
+	if err != nil {
+		return "", err
+	}
+
+	if res.StatusCode != http.StatusCreated {
+		body, _ := ioutil.ReadAll(res.Body)
+		return "", fmt.Errorf("Failed to upload image. Returned statuscode %v body %s", res.StatusCode, body)
+	}
+
+	return url.String(), nil
+}
+
+func NewWebdavImageUploader(url, username, passwrod string) (*WebdavUploader, error) {
+	return &WebdavUploader{
+		url:      url,
+		username: username,
+		password: passwrod,
+	}, nil
+}

+ 18 - 0
pkg/components/imguploader/webdavuploader_test.go

@@ -0,0 +1,18 @@
+package imguploader
+
+import (
+	"testing"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestUploadToWebdav(t *testing.T) {
+	webdavUploader, _ := NewWebdavImageUploader("http://localhost:9998/dav/", "username", "password")
+
+	SkipConvey("[Integration test] for external_image_store.webdav", t, func() {
+		path, err := webdavUploader.Upload("../../../public/img/logo_transparent_400x.png")
+
+		So(err, ShouldBeNil)
+		So(path, ShouldNotEqual, "")
+	})
+}

+ 1 - 1
pkg/metrics/graphite.go

@@ -21,7 +21,7 @@ type GraphitePublisher struct {
 func CreateGraphitePublisher() (*GraphitePublisher, error) {
 	graphiteSection, err := setting.Cfg.GetSection("metrics.graphite")
 	if err != nil {
-		return nil, err
+		return nil, nil
 	}
 
 	publisher := &GraphitePublisher{}

+ 39 - 20
pkg/metrics/metrics.go

@@ -9,29 +9,38 @@ func init() {
 }
 
 var (
-	M_Instance_Start                  Counter
-	M_Page_Status_200                 Counter
-	M_Page_Status_500                 Counter
-	M_Page_Status_404                 Counter
-	M_Api_Status_500                  Counter
-	M_Api_Status_404                  Counter
-	M_Api_User_SignUpStarted          Counter
-	M_Api_User_SignUpCompleted        Counter
-	M_Api_User_SignUpInvite           Counter
-	M_Api_Dashboard_Save              Timer
-	M_Api_Dashboard_Get               Timer
-	M_Api_Dashboard_Search            Timer
-	M_Api_Admin_User_Create           Counter
-	M_Api_Login_Post                  Counter
-	M_Api_Login_OAuth                 Counter
-	M_Api_Org_Create                  Counter
-	M_Api_Dashboard_Snapshot_Create   Counter
-	M_Api_Dashboard_Snapshot_External Counter
-	M_Api_Dashboard_Snapshot_Get      Counter
-	M_Models_Dashboard_Insert         Counter
+	M_Instance_Start                     Counter
+	M_Page_Status_200                    Counter
+	M_Page_Status_500                    Counter
+	M_Page_Status_404                    Counter
+	M_Api_Status_500                     Counter
+	M_Api_Status_404                     Counter
+	M_Api_User_SignUpStarted             Counter
+	M_Api_User_SignUpCompleted           Counter
+	M_Api_User_SignUpInvite              Counter
+	M_Api_Dashboard_Save                 Timer
+	M_Api_Dashboard_Get                  Timer
+	M_Api_Dashboard_Search               Timer
+	M_Api_Admin_User_Create              Counter
+	M_Api_Login_Post                     Counter
+	M_Api_Login_OAuth                    Counter
+	M_Api_Org_Create                     Counter
+	M_Api_Dashboard_Snapshot_Create      Counter
+	M_Api_Dashboard_Snapshot_External    Counter
+	M_Api_Dashboard_Snapshot_Get         Counter
+	M_Models_Dashboard_Insert            Counter
+	M_Alerting_Result_Critical           Counter
+	M_Alerting_Result_Warning            Counter
+	M_Alerting_Result_Info               Counter
+	M_Alerting_Result_Ok                 Counter
+	M_Alerting_Active_Alerts             Counter
+	M_Alerting_Notification_Sent_Slack   Counter
+	M_Alerting_Notification_Sent_Email   Counter
+	M_Alerting_Notification_Sent_Webhook Counter
 
 	// Timers
 	M_DataSource_ProxyReq_Timer Timer
+	M_Alerting_Exeuction_Time   Timer
 )
 
 func initMetricVars(settings *MetricSettings) {
@@ -66,6 +75,16 @@ func initMetricVars(settings *MetricSettings) {
 
 	M_Models_Dashboard_Insert = RegCounter("models.dashboard.insert")
 
+	M_Alerting_Result_Critical = RegCounter("alerting.result", "severity", "critical")
+	M_Alerting_Result_Warning = RegCounter("alerting.result", "severity", "warning")
+	M_Alerting_Result_Info = RegCounter("alerting.result", "severity", "info")
+	M_Alerting_Result_Ok = RegCounter("alerting.result", "severity", "ok")
+	M_Alerting_Active_Alerts = RegCounter("alerting.active_alerts")
+	M_Alerting_Notification_Sent_Slack = RegCounter("alerting.notifcations_sent", "type", "slack")
+	M_Alerting_Notification_Sent_Email = RegCounter("alerting.notifcations_sent", "type", "email")
+	M_Alerting_Notification_Sent_Webhook = RegCounter("alerting.notifcations_sent", "type", "webhook")
+
 	// Timers
 	M_DataSource_ProxyReq_Timer = RegTimer("api.dataproxy.request.all")
+	M_Alerting_Exeuction_Time = RegTimer("alerting.execution_time")
 }

+ 3 - 4
pkg/services/alerting/eval_handler.go

@@ -5,10 +5,7 @@ import (
 	"time"
 
 	"github.com/grafana/grafana/pkg/log"
-)
-
-var (
-	descriptionFmt = "Actual value: %1.2f for %s. "
+	"github.com/grafana/grafana/pkg/metrics"
 )
 
 type DefaultEvalHandler struct {
@@ -55,5 +52,7 @@ func (e *DefaultEvalHandler) eval(context *EvalContext) {
 	}
 
 	context.EndTime = time.Now()
+	elapsedTime := context.EndTime.Sub(context.StartTime)
+	metrics.M_Alerting_Exeuction_Time.Update(elapsedTime)
 	context.DoneChan <- true
 }

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

@@ -8,7 +8,6 @@ import (
 	"github.com/grafana/grafana/pkg/components/renderer"
 	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/setting"
 )
 
 type RootNotifier struct {
@@ -49,15 +48,14 @@ func (n *RootNotifier) Notify(context *EvalContext) {
 
 	for _, notifier := range notifiers {
 		n.log.Info("Sending notification", "firing", context.Firing, "type", notifier.GetType())
+
 		go notifier.Notify(context)
 	}
 }
 
 func (n *RootNotifier) uploadImage(context *EvalContext) error {
-	uploader := imguploader.NewS3Uploader(
-		setting.S3TempImageStoreBucketUrl,
-		setting.S3TempImageStoreAccessKey,
-		setting.S3TempImageStoreSecretKey)
+
+	uploader, _ := imguploader.NewImageUploader()
 
 	imageUrl, err := context.GetImageUrl()
 	if err != nil {

+ 2 - 0
pkg/services/alerting/notifiers/email.go

@@ -5,6 +5,7 @@ import (
 
 	"github.com/grafana/grafana/pkg/bus"
 	"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"
 )
@@ -38,6 +39,7 @@ func NewEmailNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 
 func (this *EmailNotifier) Notify(context *alerting.EvalContext) {
 	this.log.Info("Sending alert notification to", "addresses", this.Addresses)
+	metrics.M_Alerting_Notification_Sent_Email.Inc(1)
 
 	ruleUrl, err := context.GetRuleUrl()
 	if err != nil {

+ 2 - 0
pkg/services/alerting/notifiers/slack.go

@@ -6,6 +6,7 @@ import (
 
 	"github.com/grafana/grafana/pkg/bus"
 	"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"
 )
@@ -38,6 +39,7 @@ type SlackNotifier struct {
 
 func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
 	this.log.Info("Executing slack notification", "ruleId", context.Rule.Id, "notification", this.Name)
+	metrics.M_Alerting_Notification_Sent_Slack.Inc(1)
 
 	ruleUrl, err := context.GetRuleUrl()
 	if err != nil {

+ 2 - 0
pkg/services/alerting/notifiers/webhook.go

@@ -4,6 +4,7 @@ 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"
 )
@@ -40,6 +41,7 @@ type WebhookNotifier struct {
 
 func (this *WebhookNotifier) Notify(context *alerting.EvalContext) {
 	this.log.Info("Sending webhook")
+	metrics.M_Alerting_Notification_Sent_Webhook.Inc(1)
 
 	bodyJSON := simplejson.New()
 	bodyJSON.Set("title", context.GetNotificationTitle())

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

@@ -6,6 +6,7 @@ import (
 
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/log"
+	"github.com/grafana/grafana/pkg/metrics"
 	m "github.com/grafana/grafana/pkg/models"
 )
 
@@ -58,6 +59,7 @@ func (arr *DefaultRuleReader) Fetch() []*Rule {
 		}
 	}
 
+	metrics.M_Alerting_Active_Alerts.Inc(int64(len(res)))
 	return res
 }
 

+ 15 - 0
pkg/services/alerting/result_handler.go

@@ -5,6 +5,7 @@ import (
 
 	"github.com/grafana/grafana/pkg/bus"
 	"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/annotations"
 )
@@ -37,6 +38,7 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
 		ctx.Rule.State = m.AlertStateOK
 	}
 
+	countSeverity(ctx.Rule.Severity)
 	if ctx.Rule.State != oldState {
 		handler.log.Info("New state change", "alertId", ctx.Rule.Id, "newState", ctx.Rule.State, "oldState", oldState)
 
@@ -69,3 +71,16 @@ func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
 		handler.notifier.Notify(ctx)
 	}
 }
+
+func countSeverity(state m.AlertSeverityType) {
+	switch state {
+	case m.AlertSeverityOK:
+		metrics.M_Alerting_Result_Ok.Inc(1)
+	case m.AlertSeverityInfo:
+		metrics.M_Alerting_Result_Info.Inc(1)
+	case m.AlertSeverityWarning:
+		metrics.M_Alerting_Result_Warning.Inc(1)
+	case m.AlertSeverityCritical:
+		metrics.M_Alerting_Result_Critical.Inc(1)
+	}
+}

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

@@ -75,7 +75,7 @@ func TestAlertRuleModel(t *testing.T) {
 			alertRule, err := NewRuleFromDBAlert(alert)
 			So(err, ShouldBeNil)
 
-			So(alertRule.Conditions, ShouldHaveLength, 1)
+			So(len(alertRule.Conditions), ShouldEqual, 1)
 
 			Convey("Can read notifications", func() {
 				So(len(alertRule.Notifications), ShouldEqual, 2)

+ 4 - 4
pkg/setting/setting.go

@@ -153,6 +153,8 @@ var (
 	S3TempImageStoreBucketUrl string
 	S3TempImageStoreAccessKey string
 	S3TempImageStoreSecretKey string
+
+	ImageUploadProvider string
 )
 
 type CommandLineArgs struct {
@@ -539,10 +541,8 @@ func NewConfigContext(args *CommandLineArgs) error {
 
 	GrafanaNetUrl = Cfg.Section("grafana.net").Key("url").MustString("https://grafana.net")
 
-	s3temp := Cfg.Section("s3-temp-image-store")
-	S3TempImageStoreBucketUrl = s3temp.Key("bucket_url").String()
-	S3TempImageStoreAccessKey = s3temp.Key("access_key").String()
-	S3TempImageStoreSecretKey = s3temp.Key("secret_key").String()
+	imageUploadingSection := Cfg.Section("external_image_storage")
+	ImageUploadProvider = imageUploadingSection.Key("provider").MustString("internal")
 	return nil
 }