mirror of https://github.com/grafana/grafana
commit
dee6b7d150
@ -0,0 +1,2 @@ |
||||
script.inline: on |
||||
script.indexed: on |
@ -0,0 +1,8 @@ |
||||
# You need to run 'sysctl -w vm.max_map_count=262144' on the host machine |
||||
|
||||
elasticsearch5: |
||||
image: elasticsearch:5 |
||||
command: elasticsearch |
||||
ports: |
||||
- "9200:9200" |
||||
- "9300:9300" |
@ -0,0 +1,23 @@ |
||||
+++ |
||||
title = "Alerting Metrics" |
||||
description = "Alerting Metrics Guide" |
||||
keywords = ["Grafana", "alerting", "guide", "metrics"] |
||||
type = "docs" |
||||
[menu.docs] |
||||
name = "Metrics" |
||||
parent = "alerting" |
||||
weight = 2 |
||||
+++ |
||||
|
||||
# Metrics from the alert engine |
||||
|
||||
> Alerting is only available in Grafana v4.0 and above. |
||||
|
||||
The alert engine publish some internal metrics about itself. You can read more about how Grafana published [interal metrics](/installation/configuration/#metrics) |
||||
|
||||
Description | Type | Metric name |
||||
---------- | ----------- | ---------- |
||||
Total number of alerts | counter | `alerting.active_alerts` |
||||
Alert execution result | counter | `alerting.result` |
||||
Notifications sent counter | counter | `alerting.notifications_sent` |
||||
Alert execution timer | timer | `alerting.execution_time` |
@ -1,25 +0,0 @@ |
||||
---- |
||||
page_title: Data Source Overview |
||||
page_description: Data Source Overview |
||||
page_keywords: grafana, graphite, influxDB, KairosDB, OpenTSDB, Prometheus, documentation |
||||
--- |
||||
|
||||
# Data Source Overview |
||||
Grafana supports many different storage backends for your time series data (Data Source). Each Data Source has a specific Query Editor that is customized for the features and capabilities that the particular Data Source exposes. |
||||
|
||||
|
||||
## Querying |
||||
The query language and capabilities of each Data Source are obviously very different. You can combine data from multiple Data Sources onto a single Dashboard, but each Panel is tied to a specific Data Source that belongs to a particular Organization. |
||||
|
||||
## Supported Data Sources |
||||
The following datasources are officially supported: |
||||
|
||||
* [Graphite](/datasources/graphite/) |
||||
* [Elasticsearch](/datasources/elasticsearch/) |
||||
* [CloudWatch](/datasources/cloudwatch/) |
||||
* [InfluxDB](/datasources/influxdb/) |
||||
* [OpenTSDB](/datasources/opentsdb/) |
||||
* [KairosDB](/datasources/kairosdb) |
||||
* [Prometheus](/datasources/prometheus) |
||||
|
||||
Grafana can query any Elasticsearch index for annotation events, but at this time, it's not supported for metric queries. Learn more about [annotations](/reference/annotations/#elasticsearch-annotations) |
@ -1,7 +1,29 @@ |
||||
+++ |
||||
title = "HTTP API" |
||||
description = "Grafana HTTP API" |
||||
keywords = ["grafana", "http", "documentation", "api", "overview"] |
||||
type = "docs" |
||||
[menu.docs] |
||||
name = "HTTP API" |
||||
identifier = "http_api" |
||||
weight = 9 |
||||
+++ |
||||
|
||||
|
||||
# HTTP API Reference |
||||
|
||||
The Grafana backend exposes an HTTP API, the same API is used by the frontend to do everything from saving |
||||
dashboards, creating users and updating data sources. |
||||
|
||||
## Supported HTTP APIs: |
||||
|
||||
|
||||
* [Authentication API]({{< relref "auth.md" >}}) |
||||
* [Dashboard API]({{< relref "dashboard.md" >}}) |
||||
* [Data Source API]({{< relref "data_source.md" >}}) |
||||
* [Organisation API]({{< relref "org.md" >}}) |
||||
* [User API]({{< relref "user.md" >}}) |
||||
* [Admin API]({{< relref "admin.md" >}}) |
||||
* [Snapshot API]({{< relref "snapshot.md" >}}) |
||||
* [Preferences API]({{< relref "preferences.md" >}}) |
||||
* [Other API]({{< relref "other.md" >}}) |
||||
|
@ -1,28 +0,0 @@ |
||||
+++ |
||||
title = "HTTP API " |
||||
description = "Grafana HTTP API" |
||||
keywords = ["grafana", "http", "documentation", "api", "overview"] |
||||
aliases = ["/http_api/overview/"] |
||||
type = "docs" |
||||
[menu.docs] |
||||
name = "Overview" |
||||
parent = "http_api" |
||||
+++ |
||||
|
||||
|
||||
# HTTP API Reference |
||||
|
||||
The Grafana backend exposes an HTTP API, the same API is used by the frontend to do everything from saving |
||||
dashboards, creating users and updating data sources. |
||||
|
||||
## Supported HTTP APIs: |
||||
|
||||
* [Authentication API](/http_api/auth/) |
||||
* [Dashboard API](/http_api/dashboard/) |
||||
* [Data Source API](/http_api/data_source/) |
||||
* [Organisation API](/http_api/org/) |
||||
* [User API](/http_api/user/) |
||||
* [Admin API](/http_api/admin/) |
||||
* [Snapshot API](/http_api/snapshot/) |
||||
* [Preferences API](/http_api/preferences/) |
||||
* [Other API](/http_api/other/) |
@ -1,4 +1,4 @@ |
||||
{ |
||||
"stable": "3.1.1", |
||||
"testing": "3.1.1" |
||||
"stable": "4.0.1", |
||||
"testing": "4.0.1" |
||||
} |
||||
|
@ -0,0 +1,24 @@ |
||||
package securejsondata |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
type SecureJsonData map[string][]byte |
||||
|
||||
func (s SecureJsonData) Decrypt() map[string]string { |
||||
decrypted := make(map[string]string) |
||||
for key, data := range s { |
||||
decrypted[key] = string(util.Decrypt(data, setting.SecretKey)) |
||||
} |
||||
return decrypted |
||||
} |
||||
|
||||
func GetEncryptedJsonData(sjd map[string]string) SecureJsonData { |
||||
encrypted := make(SecureJsonData) |
||||
for key, data := range sjd { |
||||
encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey) |
||||
} |
||||
return encrypted |
||||
} |
@ -0,0 +1,95 @@ |
||||
package models |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"crypto/x509" |
||||
"net" |
||||
"net/http" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
type proxyTransportCache struct { |
||||
cache map[int64]cachedTransport |
||||
sync.Mutex |
||||
} |
||||
|
||||
type cachedTransport struct { |
||||
updated time.Time |
||||
|
||||
*http.Transport |
||||
} |
||||
|
||||
var ptc = proxyTransportCache{ |
||||
cache: make(map[int64]cachedTransport), |
||||
} |
||||
|
||||
func (ds *DataSource) GetHttpClient() (*http.Client, error) { |
||||
transport, err := ds.GetHttpTransport() |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &http.Client{ |
||||
Timeout: time.Duration(30 * time.Second), |
||||
Transport: transport, |
||||
}, nil |
||||
} |
||||
|
||||
func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { |
||||
ptc.Lock() |
||||
defer ptc.Unlock() |
||||
|
||||
if t, present := ptc.cache[ds.Id]; present && ds.Updated.Equal(t.updated) { |
||||
return t.Transport, nil |
||||
} |
||||
|
||||
transport := &http.Transport{ |
||||
TLSClientConfig: &tls.Config{ |
||||
InsecureSkipVerify: true, |
||||
}, |
||||
Proxy: http.ProxyFromEnvironment, |
||||
Dial: (&net.Dialer{ |
||||
Timeout: 30 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).Dial, |
||||
TLSHandshakeTimeout: 10 * time.Second, |
||||
ExpectContinueTimeout: 1 * time.Second, |
||||
MaxIdleConns: 100, |
||||
IdleConnTimeout: 90 * time.Second, |
||||
} |
||||
|
||||
var tlsAuth, tlsAuthWithCACert bool |
||||
if ds.JsonData != nil { |
||||
tlsAuth = ds.JsonData.Get("tlsAuth").MustBool(false) |
||||
tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false) |
||||
} |
||||
|
||||
if tlsAuth { |
||||
transport.TLSClientConfig.InsecureSkipVerify = false |
||||
|
||||
decrypted := ds.SecureJsonData.Decrypt() |
||||
|
||||
if tlsAuthWithCACert && len(decrypted["tlsCACert"]) > 0 { |
||||
caPool := x509.NewCertPool() |
||||
ok := caPool.AppendCertsFromPEM([]byte(decrypted["tlsCACert"])) |
||||
if ok { |
||||
transport.TLSClientConfig.RootCAs = caPool |
||||
} |
||||
} |
||||
|
||||
cert, err := tls.X509KeyPair([]byte(decrypted["tlsClientCert"]), []byte(decrypted["tlsClientKey"])) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
transport.TLSClientConfig.Certificates = []tls.Certificate{cert} |
||||
} |
||||
|
||||
ptc.cache[ds.Id] = cachedTransport{ |
||||
Transport: transport, |
||||
updated: ds.Updated, |
||||
} |
||||
|
||||
return transport, nil |
||||
} |
@ -0,0 +1,157 @@ |
||||
package models |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
|
||||
. "github.com/smartystreets/goconvey/convey" |
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
func TestDataSourceCache(t *testing.T) { |
||||
Convey("When caching a datasource proxy", t, func() { |
||||
clearCache() |
||||
ds := DataSource{ |
||||
Id: 1, |
||||
Url: "http://k8s:8001", |
||||
Type: "Kubernetes", |
||||
} |
||||
|
||||
t1, err := ds.GetHttpTransport() |
||||
So(err, ShouldBeNil) |
||||
|
||||
t2, err := ds.GetHttpTransport() |
||||
So(err, ShouldBeNil) |
||||
|
||||
Convey("Should be using the cached proxy", func() { |
||||
So(t2, ShouldEqual, t1) |
||||
}) |
||||
}) |
||||
|
||||
Convey("When getting kubernetes datasource proxy", t, func() { |
||||
clearCache() |
||||
setting.SecretKey = "password" |
||||
|
||||
json := simplejson.New() |
||||
json.Set("tlsAuth", true) |
||||
json.Set("tlsAuthWithCACert", true) |
||||
|
||||
t := time.Now() |
||||
ds := DataSource{ |
||||
Url: "http://k8s:8001", |
||||
Type: "Kubernetes", |
||||
Updated: t.Add(-2 * time.Minute), |
||||
} |
||||
|
||||
transport, err := ds.GetHttpTransport() |
||||
So(err, ShouldBeNil) |
||||
|
||||
Convey("Should have no cert", func() { |
||||
So(transport.TLSClientConfig.InsecureSkipVerify, ShouldEqual, true) |
||||
}) |
||||
|
||||
ds.JsonData = json |
||||
ds.SecureJsonData = map[string][]byte{ |
||||
"tlsCACert": util.Encrypt([]byte(caCert), "password"), |
||||
"tlsClientCert": util.Encrypt([]byte(clientCert), "password"), |
||||
"tlsClientKey": util.Encrypt([]byte(clientKey), "password"), |
||||
} |
||||
ds.Updated = t.Add(-1 * time.Minute) |
||||
|
||||
transport, err = ds.GetHttpTransport() |
||||
So(err, ShouldBeNil) |
||||
|
||||
Convey("Should add cert", func() { |
||||
So(transport.TLSClientConfig.InsecureSkipVerify, ShouldEqual, false) |
||||
So(len(transport.TLSClientConfig.Certificates), ShouldEqual, 1) |
||||
}) |
||||
|
||||
ds.JsonData = nil |
||||
ds.SecureJsonData = map[string][]byte{} |
||||
ds.Updated = t |
||||
|
||||
transport, err = ds.GetHttpTransport() |
||||
So(err, ShouldBeNil) |
||||
|
||||
Convey("Should remove cert", func() { |
||||
So(transport.TLSClientConfig.InsecureSkipVerify, ShouldEqual, true) |
||||
So(len(transport.TLSClientConfig.Certificates), ShouldEqual, 0) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
func clearCache() { |
||||
ptc.Lock() |
||||
defer ptc.Unlock() |
||||
|
||||
ptc.cache = make(map[int64]cachedTransport) |
||||
} |
||||
|
||||
const caCert string = `-----BEGIN CERTIFICATE----- |
||||
MIIDATCCAemgAwIBAgIJAMQ5hC3CPDTeMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV |
||||
BAMMDGNhLWs4cy1zdGhsbTAeFw0xNjEwMjcwODQyMjdaFw00NDAzMTQwODQyMjda |
||||
MBcxFTATBgNVBAMMDGNhLWs4cy1zdGhsbTCCASIwDQYJKoZIhvcNAQEBBQADggEP |
||||
ADCCAQoCggEBAMLe2AmJ6IleeUt69vgNchOjjmxIIxz5sp1vFu94m1vUip7CqnOg |
||||
QkpUsHeBPrGYv8UGloARCL1xEWS+9FVZeXWQoDmbC0SxXhFwRIESNCET7Q8KMi/4 |
||||
4YPvnMLGZi3Fjwxa8BdUBCN1cx4WEooMVTWXm7RFMtZgDfuOAn3TNXla732sfT/d |
||||
1HNFrh48b0wA+HhmA3nXoBnBEblA665hCeo7lIAdRr0zJxJpnFnWXkyTClsAUTMN |
||||
iL905LdBiiIRenojipfKXvMz88XSaWTI7JjZYU3BvhyXndkT6f12cef3I96NY3WJ |
||||
0uIK4k04WrbzdYXMU3rN6NqlvbHqnI+E7aMCAwEAAaNQME4wHQYDVR0OBBYEFHHx |
||||
2+vSPw9bECHj3O51KNo5VdWOMB8GA1UdIwQYMBaAFHHx2+vSPw9bECHj3O51KNo5 |
||||
VdWOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH2eV5NcV3LBJHs9 |
||||
I+adbiTPg2vyumrGWwy73T0X8Dtchgt8wU7Q9b9Ucg2fOTmSSyS0iMqEu1Yb2ORB |
||||
CknM9mixHC9PwEBbkGCom3VVkqdLwSP6gdILZgyLoH4i8sTUz+S1yGPepi+Vzhs7 |
||||
adOXtryjcGnwft6HdfKPNklMOHFnjw6uqpho54oj/z55jUpicY/8glDHdrr1bh3k |
||||
MHuiWLGewHXPvxfG6UoUx1te65IhifVcJGFZDQwfEmhBflfCmtAJlZEsgTLlBBCh |
||||
FHoXIyGOdq1chmRVocdGBCF8fUoGIbuF14r53rpvcbEKtKnnP8+96luKAZLq0a4n |
||||
3lb92xM= |
||||
-----END CERTIFICATE-----` |
||||
|
||||
const clientCert string = `-----BEGIN CERTIFICATE----- |
||||
MIICsjCCAZoCCQCcd8sOfstQLzANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj |
||||
YS1rOHMtc3RobG0wHhcNMTYxMTAyMDkyNTE1WhcNMTcxMTAyMDkyNTE1WjAfMR0w |
||||
GwYDVQQDDBRhZG0tZGFuaWVsLWs4cy1zdGhsbTCCASIwDQYJKoZIhvcNAQEBBQAD |
||||
ggEPADCCAQoCggEBAOMliaWyNEUJKM37vWCl5bGub3lMicyRAqGQyY/qxD9yKKM2 |
||||
FbucVcmWmg5vvTqQVl5rlQ+c7GI8OD6ptmFl8a26coEki7bFr8bkpSyBSEc5p27b |
||||
Z0ORFSqBHWHQbr9PkxPLYW6T3gZYUtRYv3OQgGxLXlvUh85n/mQfuR3N1FgmShHo |
||||
GtAFi/ht6leXa0Ms+jNSDLCmXpJm1GIEqgyKX7K3+g3vzo9coYqXq4XTa8Efs2v8 |
||||
SCwqWfBC3rHfgs/5DLB8WT4Kul8QzxkytzcaBQfRfzhSV6bkgm7oTzt2/1eRRsf4 |
||||
YnXzLE9YkCC9sAn+Owzqf+TYC1KRluWDfqqBTJUCAwEAATANBgkqhkiG9w0BAQsF |
||||
AAOCAQEAdMsZg6edWGC+xngizn0uamrUg1ViaDqUsz0vpzY5NWLA4MsBc4EtxWRP |
||||
ueQvjUimZ3U3+AX0YWNLIrH1FCVos2jdij/xkTUmHcwzr8rQy+B17cFi+a8jtpgw |
||||
AU6WWoaAIEhhbWQfth/Diz3mivl1ARB+YqiWca2mjRPLTPcKJEURDVddQ423el0Q |
||||
4JNxS5icu7T2zYTYHAo/cT9zVdLZl0xuLxYm3asK1IONJ/evxyVZima3il6MPvhe |
||||
58Hwz+m+HdqHxi24b/1J/VKYbISG4huOQCdLzeNXgvwFlGPUmHSnnKo1/KbQDAR5 |
||||
llG/Sw5+FquFuChaA6l5KWy7F3bQyA== |
||||
-----END CERTIFICATE-----` |
||||
|
||||
const clientKey string = `-----BEGIN RSA PRIVATE KEY----- |
||||
MIIEpQIBAAKCAQEA4yWJpbI0RQkozfu9YKXlsa5veUyJzJECoZDJj+rEP3IoozYV |
||||
u5xVyZaaDm+9OpBWXmuVD5zsYjw4Pqm2YWXxrbpygSSLtsWvxuSlLIFIRzmnbttn |
||||
Q5EVKoEdYdBuv0+TE8thbpPeBlhS1Fi/c5CAbEteW9SHzmf+ZB+5Hc3UWCZKEega |
||||
0AWL+G3qV5drQyz6M1IMsKZekmbUYgSqDIpfsrf6De/Oj1yhiperhdNrwR+za/xI |
||||
LCpZ8ELesd+Cz/kMsHxZPgq6XxDPGTK3NxoFB9F/OFJXpuSCbuhPO3b/V5FGx/hi |
||||
dfMsT1iQIL2wCf47DOp/5NgLUpGW5YN+qoFMlQIDAQABAoIBAQCzy4u312XeW1Cs |
||||
Mx6EuOwmh59/ESFmBkZh4rxZKYgrfE5EWlQ7i5SwG4BX+wR6rbNfy6JSmHDXlTkk |
||||
CKvvToVNcW6fYHEivDnVojhIERFIJ4+rhQmpBtcNLOQ3/4cZ8X/GxE6b+3lb5l+x |
||||
64mnjPLKRaIr5/+TVuebEy0xNTJmjnJ7yiB2HRz7uXEQaVSk/P7KAkkyl/9J3/LM |
||||
8N9AX1w6qDaNQZ4/P0++1H4SQenosM/b/GqGTomarEk/GE0NcB9rzmR9VCXa7FRh |
||||
WV5jyt9vUrwIEiK/6nUnOkGO8Ei3kB7Y+e+2m6WdaNoU5RAfqXmXa0Q/a0lLRruf |
||||
vTMo2WrBAoGBAPRaK4cx76Q+3SJ/wfznaPsMM06OSR8A3ctKdV+ip/lyKtb1W8Pz |
||||
k8MYQDH7GwPtSu5QD8doL00pPjugZL/ba7X9nAsI+pinyEErfnB9y7ORNEjIYYzs |
||||
DiqDKup7ANgw1gZvznWvb9Ge0WUSXvWS0pFkgootQAf+RmnnbWGH6l6RAoGBAO35 |
||||
aGUrLro5u9RD24uSXNU3NmojINIQFK5dHAT3yl0BBYstL43AEsye9lX95uMPTvOQ |
||||
Cqcn42Hjp/bSe3n0ObyOZeXVrWcDFAfE0wwB1BkvL1lpgnFO9+VQORlH4w3Ppnpo |
||||
jcPkR2TFeDaAYtvckhxe/Bk3OnuFmnsQ3VzM75fFAoGBAI6PvS2XeNU+yA3EtA01 |
||||
hg5SQ+zlHswz2TMuMeSmJZJnhY78f5mHlwIQOAPxGQXlf/4iP9J7en1uPpzTK3S0 |
||||
M9duK4hUqMA/w5oiIhbHjf0qDnMYVbG+V1V+SZ+cPBXmCDihKreGr5qBKnHpkfV8 |
||||
v9WL6o1rcRw4wiQvnaV1gsvBAoGBALtzVTczr6gDKCAIn5wuWy+cQSGTsBunjRLX |
||||
xuVm5iEiV+KMYkPvAx/pKzMLP96lRVR3ptyKgAKwl7LFk3u50+zh4gQLr35QH2wL |
||||
Lw7rNc3srAhrItPsFzqrWX6/cGuFoKYVS239l/sZzRppQPXcpb7xVvTp2whHcir0 |
||||
Wtnpl+TdAoGAGqKqo2KU3JoY3IuTDUk1dsNAm8jd9EWDh+s1x4aG4N79mwcss5GD |
||||
FF8MbFPneK7xQd8L6HisKUDAUi2NOyynM81LAftPkvN6ZuUVeFDfCL4vCA0HUXLD |
||||
+VrOhtUZkNNJlLMiVRJuQKUOGlg8PpObqYbstQAf/0/yFJMRHG82Tcg= |
||||
-----END RSA PRIVATE KEY-----` |
@ -0,0 +1,118 @@ |
||||
package notifiers |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strconv" |
||||
|
||||
"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" |
||||
) |
||||
|
||||
func init() { |
||||
alerting.RegisterNotifier("opsgenie", NewOpsGenieNotifier) |
||||
} |
||||
|
||||
var ( |
||||
opsgenieCreateAlertURL string = "https://api.opsgenie.com/v1/json/alert" |
||||
opsgenieCloseAlertURL string = "https://api.opsgenie.com/v1/json/alert/close" |
||||
) |
||||
|
||||
func NewOpsGenieNotifier(model *m.AlertNotification) (alerting.Notifier, error) { |
||||
autoClose := model.Settings.Get("autoClose").MustBool(true) |
||||
apiKey := model.Settings.Get("apiKey").MustString() |
||||
if apiKey == "" { |
||||
return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"} |
||||
} |
||||
|
||||
return &OpsGenieNotifier{ |
||||
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings), |
||||
ApiKey: apiKey, |
||||
AutoClose: autoClose, |
||||
log: log.New("alerting.notifier.opsgenie"), |
||||
}, nil |
||||
} |
||||
|
||||
type OpsGenieNotifier struct { |
||||
NotifierBase |
||||
ApiKey string |
||||
AutoClose bool |
||||
log log.Logger |
||||
} |
||||
|
||||
func (this *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error { |
||||
metrics.M_Alerting_Notification_Sent_OpsGenie.Inc(1) |
||||
|
||||
var err error |
||||
switch evalContext.Rule.State { |
||||
case m.AlertStateOK: |
||||
if this.AutoClose { |
||||
err = this.closeAlert(evalContext) |
||||
} |
||||
case m.AlertStateAlerting: |
||||
err = this.createAlert(evalContext) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error { |
||||
this.log.Info("Creating OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", this.Name) |
||||
|
||||
ruleUrl, err := evalContext.GetRuleUrl() |
||||
if err != nil { |
||||
this.log.Error("Failed get rule link", "error", err) |
||||
return err |
||||
} |
||||
|
||||
bodyJSON := simplejson.New() |
||||
bodyJSON.Set("apiKey", this.ApiKey) |
||||
bodyJSON.Set("message", evalContext.Rule.Name) |
||||
bodyJSON.Set("source", "Grafana") |
||||
bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10)) |
||||
bodyJSON.Set("description", fmt.Sprintf("%s - %s\n%s", evalContext.Rule.Name, ruleUrl, evalContext.Rule.Message)) |
||||
|
||||
details := simplejson.New() |
||||
details.Set("url", ruleUrl) |
||||
if evalContext.ImagePublicUrl != "" { |
||||
details.Set("image", evalContext.ImagePublicUrl) |
||||
} |
||||
|
||||
bodyJSON.Set("details", details) |
||||
body, _ := bodyJSON.MarshalJSON() |
||||
|
||||
cmd := &m.SendWebhookSync{ |
||||
Url: opsgenieCreateAlertURL, |
||||
Body: string(body), |
||||
HttpMethod: "POST", |
||||
} |
||||
|
||||
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { |
||||
this.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body)) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (this *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error { |
||||
this.log.Info("Closing OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", this.Name) |
||||
|
||||
bodyJSON := simplejson.New() |
||||
bodyJSON.Set("apiKey", this.ApiKey) |
||||
bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10)) |
||||
body, _ := bodyJSON.MarshalJSON() |
||||
|
||||
cmd := &m.SendWebhookSync{ |
||||
Url: opsgenieCloseAlertURL, |
||||
Body: string(body), |
||||
HttpMethod: "POST", |
||||
} |
||||
|
||||
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { |
||||
this.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body)) |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -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 TestOpsGenieNotifier(t *testing.T) { |
||||
Convey("OpsGenie 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: "opsgenie_testing", |
||||
Type: "opsgenie", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
_, err := NewOpsGenieNotifier(model) |
||||
So(err, ShouldNotBeNil) |
||||
}) |
||||
|
||||
Convey("settings should trigger incident", func() { |
||||
json := ` |
||||
{ |
||||
"apiKey": "abcdefgh0123456789" |
||||
}` |
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json)) |
||||
model := &m.AlertNotification{ |
||||
Name: "opsgenie_testing", |
||||
Type: "opsgenie", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
not, err := NewOpsGenieNotifier(model) |
||||
opsgenieNotifier := not.(*OpsGenieNotifier) |
||||
|
||||
So(err, ShouldBeNil) |
||||
So(opsgenieNotifier.Name, ShouldEqual, "opsgenie_testing") |
||||
So(opsgenieNotifier.Type, ShouldEqual, "opsgenie") |
||||
So(opsgenieNotifier.ApiKey, ShouldEqual, "abcdefgh0123456789") |
||||
}) |
||||
}) |
||||
}) |
||||
} |
@ -0,0 +1,100 @@ |
||||
package notifiers |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/log" |
||||
"github.com/grafana/grafana/pkg/metrics" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/alerting" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
// AlertStateCritical - Victorops uses "CRITICAL" string to indicate "Alerting" state
|
||||
const AlertStateCritical = "CRITICAL" |
||||
|
||||
func init() { |
||||
alerting.RegisterNotifier("victorops", NewVictoropsNotifier) |
||||
} |
||||
|
||||
// NewVictoropsNotifier creates an instance of VictoropsNotifier that
|
||||
// handles posting notifications to Victorops REST API
|
||||
func NewVictoropsNotifier(model *models.AlertNotification) (alerting.Notifier, error) { |
||||
url := model.Settings.Get("url").MustString() |
||||
if url == "" { |
||||
return nil, alerting.ValidationError{Reason: "Could not find victorops url property in settings"} |
||||
} |
||||
|
||||
return &VictoropsNotifier{ |
||||
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings), |
||||
URL: url, |
||||
log: log.New("alerting.notifier.victorops"), |
||||
}, nil |
||||
} |
||||
|
||||
// VictoropsNotifier defines URL property for Victorops REST API
|
||||
// and handles notification process by formatting POST body according to
|
||||
// Victorops specifications (http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/)
|
||||
type VictoropsNotifier struct { |
||||
NotifierBase |
||||
URL string |
||||
log log.Logger |
||||
} |
||||
|
||||
// Notify sends notification to Victorops via POST to URL endpoint
|
||||
func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { |
||||
this.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) |
||||
metrics.M_Alerting_Notification_Sent_Victorops.Inc(1) |
||||
|
||||
ruleUrl, err := evalContext.GetRuleUrl() |
||||
if err != nil { |
||||
this.log.Error("Failed get rule link", "error", err) |
||||
return err |
||||
} |
||||
|
||||
fields := make([]map[string]interface{}, 0) |
||||
fieldLimitCount := 4 |
||||
for index, evt := range evalContext.EvalMatches { |
||||
fields = append(fields, map[string]interface{}{ |
||||
"title": evt.Metric, |
||||
"value": evt.Value, |
||||
"short": true, |
||||
}) |
||||
if index > fieldLimitCount { |
||||
break |
||||
} |
||||
} |
||||
|
||||
if evalContext.Error != nil { |
||||
fields = append(fields, map[string]interface{}{ |
||||
"title": "Error message", |
||||
"value": evalContext.Error.Error(), |
||||
"short": false, |
||||
}) |
||||
} |
||||
|
||||
messageType := evalContext.Rule.State |
||||
if evalContext.Rule.State == models.AlertStateAlerting { // translate 'Alerting' to 'CRITICAL' (Victorops analog)
|
||||
messageType = AlertStateCritical |
||||
} |
||||
|
||||
body := map[string]interface{}{ |
||||
"message_type": messageType, |
||||
"entity_id": evalContext.Rule.Name, |
||||
"timestamp": time.Now().Unix(), |
||||
"state_start_time": evalContext.StartTime.Unix(), |
||||
"state_message": evalContext.Rule.Message + "\n" + ruleUrl, |
||||
"monitoring_tool": "Grafana v" + setting.BuildVersion, |
||||
} |
||||
|
||||
data, _ := json.Marshal(&body) |
||||
cmd := &models.SendWebhookSync{Url: this.URL, Body: string(data)} |
||||
|
||||
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { |
||||
this.log.Error("Failed to send victorops notification", "error", err, "webhook", this.Name) |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -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 TestVictoropsNotifier(t *testing.T) { |
||||
Convey("Victorops 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: "victorops_testing", |
||||
Type: "victorops", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
_, err := NewVictoropsNotifier(model) |
||||
So(err, ShouldNotBeNil) |
||||
}) |
||||
|
||||
Convey("from settings", func() { |
||||
json := ` |
||||
{ |
||||
"url": "http://google.com" |
||||
}` |
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json)) |
||||
model := &m.AlertNotification{ |
||||
Name: "victorops_testing", |
||||
Type: "victorops", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
not, err := NewVictoropsNotifier(model) |
||||
victoropsNotifier := not.(*VictoropsNotifier) |
||||
|
||||
So(err, ShouldBeNil) |
||||
So(victoropsNotifier.Name, ShouldEqual, "victorops_testing") |
||||
So(victoropsNotifier.Type, ShouldEqual, "victorops") |
||||
So(victoropsNotifier.URL, ShouldEqual, "http://google.com") |
||||
}) |
||||
}) |
||||
}) |
||||
} |
@ -1,29 +0,0 @@ |
||||
package tsdb |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"net" |
||||
"net/http" |
||||
"time" |
||||
) |
||||
|
||||
func GetDefaultClient() *http.Client { |
||||
tr := &http.Transport{ |
||||
Proxy: http.ProxyFromEnvironment, |
||||
DialContext: (&net.Dialer{ |
||||
Timeout: 30 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).DialContext, |
||||
MaxIdleConns: 100, |
||||
IdleConnTimeout: 90 * time.Second, |
||||
TLSHandshakeTimeout: 10 * time.Second, |
||||
ExpectContinueTimeout: 1 * time.Second, |
||||
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, |
||||
} |
||||
|
||||
return &http.Client{ |
||||
Timeout: time.Duration(30 * time.Second), |
||||
Transport: tr, |
||||
} |
||||
} |
@ -1,81 +0,0 @@ |
||||
define([ |
||||
'angular', |
||||
'lodash', |
||||
], |
||||
function (angular, _) { |
||||
'use strict'; |
||||
|
||||
var module = angular.module('grafana.controllers'); |
||||
|
||||
module.controller('DashboardImportCtrl', function($scope, $http, backendSrv, datasourceSrv) { |
||||
|
||||
$scope.init = function() { |
||||
$scope.datasources = []; |
||||
$scope.sourceName = 'grafana'; |
||||
$scope.destName = 'grafana'; |
||||
$scope.imported = []; |
||||
$scope.dashboards = []; |
||||
$scope.infoText = ''; |
||||
$scope.importing = false; |
||||
|
||||
_.each(datasourceSrv.getAll(), function(ds, key) { |
||||
if (ds.type === 'influxdb_08' || ds.type === 'elasticsearch') { |
||||
$scope.sourceName = key; |
||||
$scope.datasources.push(key); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
$scope.startImport = function() { |
||||
datasourceSrv.get($scope.sourceName).then(function(ds) { |
||||
$scope.dashboardSource = ds; |
||||
$scope.dashboardSource.searchDashboards('title:').then(function(results) { |
||||
$scope.dashboards = results.dashboards; |
||||
|
||||
if ($scope.dashboards.length === 0) { |
||||
$scope.infoText = 'No dashboards found'; |
||||
return; |
||||
} |
||||
|
||||
$scope.importing = true; |
||||
$scope.imported = []; |
||||
$scope.next(); |
||||
}, function(err) { |
||||
var resp = err.message || err.statusText || 'Unknown error'; |
||||
var message = "Failed to load dashboards from selected data source, response from server was: " + resp; |
||||
$scope.appEvent('alert-error', ['Import failed', message]); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
$scope.next = function() { |
||||
if ($scope.dashboards.length === 0) { |
||||
$scope.infoText = "Done! Imported " + $scope.imported.length + " dashboards"; |
||||
} |
||||
|
||||
var dash = $scope.dashboards.shift(); |
||||
if (!dash.title) { |
||||
console.log(dash); |
||||
return; |
||||
} |
||||
|
||||
var infoObj = {name: dash.title, info: 'Importing...'}; |
||||
$scope.imported.push(infoObj); |
||||
$scope.infoText = "Importing " + $scope.imported.length + '/' + ($scope.imported.length + $scope.dashboards.length); |
||||
|
||||
$scope.dashboardSource.getDashboard(dash.id).then(function(loadedDash) { |
||||
backendSrv.saveDashboard(loadedDash).then(function() { |
||||
infoObj.info = "Done!"; |
||||
$scope.next(); |
||||
}, function(err) { |
||||
err.isHandled = true; |
||||
infoObj.info = "Error: " + (err.data || { message: 'Unknown' }).message; |
||||
$scope.next(); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
$scope.init(); |
||||
|
||||
}); |
||||
}); |
@ -1,44 +0,0 @@ |
||||
<navbar title="Migrate" title-url="dashboards/migrate" icon="fa fa-download"> |
||||
</navbar> |
||||
|
||||
<div class="page-container"> |
||||
<div class="page-header"> |
||||
<h1> |
||||
Migrate dashboards |
||||
</h1> |
||||
</div> |
||||
|
||||
<h5 class="section-heading"> |
||||
Import dashboards from Elasticsearch or InfluxDB |
||||
</h5> |
||||
|
||||
<div class="gf-form-inline gf-form-group"> |
||||
<div class="gf-form"> |
||||
<div class="gf-form-label">Dashboard source</div> |
||||
<div class="gf-form-select-wrapper"> |
||||
<select class="gf-form-input gf-size-auto" ng-model="sourceName" ng-options="f for f in datasources"></select> |
||||
</div> |
||||
</div> |
||||
<div class="gf-form"> |
||||
<button class="btn btn-success gf-form-btn" ng-click="startImport()">Import</button> |
||||
</div> |
||||
</div> |
||||
|
||||
<h5 class="section-heading" ng-if="importing">{{infoText}}</h5> |
||||
<div class="editor-row" ng-if="importing"> |
||||
<div class="editor-row row"> |
||||
<table class="grafana-options-table span5"> |
||||
<tr ng-repeat="dash in imported"> |
||||
<td>{{dash.name}}</td> |
||||
<td> |
||||
{{dash.info}} |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<div ng-include="'public/app/features/dashboard/partials/graphiteImport.html'"></div> |
||||
|
||||
</div> |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue