2013-08-16 12:50:51 +00:00
|
|
|
// Copyright 2013 Prometheus Team
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package manager
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2015-03-13 15:13:20 +00:00
|
|
|
"crypto/tls"
|
2015-05-20 22:01:17 +00:00
|
|
|
"encoding/json"
|
2015-03-13 15:13:20 +00:00
|
|
|
"fmt"
|
2015-07-15 13:11:13 +00:00
|
|
|
"html"
|
2015-05-20 22:01:17 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2015-03-13 15:13:20 +00:00
|
|
|
"os"
|
2015-05-20 22:01:17 +00:00
|
|
|
"reflect"
|
2015-07-20 16:09:30 +00:00
|
|
|
"strconv"
|
2013-08-16 12:50:51 +00:00
|
|
|
"testing"
|
2015-04-17 18:50:30 +00:00
|
|
|
"time"
|
2015-05-20 22:01:17 +00:00
|
|
|
|
|
|
|
pb "github.com/prometheus/alertmanager/config/generated"
|
2013-08-16 12:50:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestWriteEmailBody(t *testing.T) {
|
2013-08-27 13:32:08 +00:00
|
|
|
event := &Alert{
|
2013-08-16 12:50:51 +00:00
|
|
|
Summary: "Testsummary",
|
|
|
|
Description: "Test alert description, something went wrong here.",
|
2015-06-25 15:59:00 +00:00
|
|
|
Runbook: "http://runbook/",
|
2013-08-27 13:32:08 +00:00
|
|
|
Labels: AlertLabelSet{
|
2013-08-16 12:50:51 +00:00
|
|
|
"alertname": "TestAlert",
|
|
|
|
"grouping_label1": "grouping_value1",
|
|
|
|
"grouping_label2": "grouping_value2",
|
|
|
|
},
|
2013-08-27 13:32:08 +00:00
|
|
|
Payload: AlertPayload{
|
2013-08-16 12:50:51 +00:00
|
|
|
"payload_label1": "payload_value1",
|
|
|
|
"payload_label2": "payload_value2",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
buf := &bytes.Buffer{}
|
2015-04-17 18:50:30 +00:00
|
|
|
location, _ := time.LoadLocation("Europe/Amsterdam")
|
|
|
|
moment := time.Date(1918, 11, 11, 11, 0, 3, 0, location)
|
2015-06-25 15:59:00 +00:00
|
|
|
writeEmailBodyWithTime(buf, "from@prometheus.io", "to@prometheus.io", "ALERT", event, moment, "http://alertmanager/")
|
2013-08-16 12:50:51 +00:00
|
|
|
|
2015-02-16 19:35:45 +00:00
|
|
|
expected := `From: Prometheus Alertmanager <from@prometheus.io>
|
|
|
|
To: to@prometheus.io
|
2015-04-17 18:50:30 +00:00
|
|
|
Date: Mon, 11 Nov 1918 11:00:03 +0019
|
2015-02-16 19:35:45 +00:00
|
|
|
Subject: [ALERT] TestAlert: Testsummary
|
2013-08-16 12:50:51 +00:00
|
|
|
|
|
|
|
Test alert description, something went wrong here.
|
|
|
|
|
2015-06-25 15:59:00 +00:00
|
|
|
Alertmanager: http://alertmanager/
|
|
|
|
Runbook entry: http://runbook/
|
|
|
|
|
2013-08-16 12:50:51 +00:00
|
|
|
Grouping labels:
|
|
|
|
|
|
|
|
alertname = "TestAlert"
|
|
|
|
grouping_label1 = "grouping_value1"
|
|
|
|
grouping_label2 = "grouping_value2"
|
|
|
|
|
|
|
|
Payload labels:
|
|
|
|
|
|
|
|
payload_label1 = "payload_value1"
|
|
|
|
payload_label2 = "payload_value2"`
|
|
|
|
|
|
|
|
if buf.String() != expected {
|
|
|
|
t.Fatalf("Expected:\n%s\n\nGot:\n%s", expected, buf.String())
|
|
|
|
}
|
|
|
|
}
|
2015-03-13 15:13:20 +00:00
|
|
|
|
|
|
|
type authTestCase struct {
|
|
|
|
hasAuth bool
|
|
|
|
mechs string
|
|
|
|
expAuthType string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc *authTestCase) test(t *testing.T) {
|
|
|
|
auth, cfg, err := getSMTPAuth(tc.hasAuth, tc.mechs)
|
|
|
|
if err != nil {
|
|
|
|
tc.fail(t, "unexpected error: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if tc.expAuthType == "" {
|
|
|
|
if auth != nil {
|
|
|
|
tc.fail(t, "expected auth to be nil, got %T", auth)
|
|
|
|
}
|
|
|
|
if cfg != nil {
|
|
|
|
tc.fail(t, "expected tls config to be nil, got %v", cfg)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if fmt.Sprintf("%T", auth) != tc.expAuthType {
|
|
|
|
tc.fail(t, "expected auth to be %s, got %T", tc.expAuthType, auth)
|
|
|
|
}
|
|
|
|
if tc.expAuthType == "*smtp.plainAuth" {
|
|
|
|
if cfg == nil {
|
|
|
|
tc.fail(t, "expected tls config")
|
|
|
|
} else if cfg.ServerName != "testSMTPHost" {
|
|
|
|
tc.fail(t, "expected tls config to be %v, got %v",
|
|
|
|
&tls.Config{ServerName: "testSMTPHost"}, cfg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc *authTestCase) fail(t *testing.T, format string, args ...interface{}) {
|
|
|
|
t.Errorf("{auth test: %#v}: %s", tc, fmt.Sprintf(format, args...))
|
|
|
|
}
|
|
|
|
|
|
|
|
func runAuthTests(t *testing.T, tcs []authTestCase) {
|
|
|
|
for _, tc := range tcs {
|
|
|
|
tc.test(t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetSMTPAuth(t *testing.T) {
|
|
|
|
// Save and clear environment.
|
|
|
|
vars := []string{"SMTP_AUTH_USERNAME", "SMTP_AUTH_PASSWORD", "SMTP_AUTH_SECRET", "SMTP_AUTH_IDENTITY"}
|
|
|
|
saved := map[string]string{}
|
|
|
|
for _, k := range vars {
|
|
|
|
saved[k] = os.Getenv(k)
|
|
|
|
os.Setenv(k, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up deferred restoration of environment.
|
|
|
|
defer func() {
|
|
|
|
for k, v := range saved {
|
|
|
|
os.Setenv(k, v)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Auth never occurs without environment variables set.
|
|
|
|
runAuthTests(t, []authTestCase{
|
|
|
|
{false, "", ""},
|
|
|
|
{false, "PLAIN", ""},
|
|
|
|
{false, "CRAM-MD5", ""},
|
|
|
|
})
|
|
|
|
|
|
|
|
// Simple single-mechanism cases.
|
|
|
|
*smtpSmartHost = "testSMTPHost:port"
|
|
|
|
os.Setenv("SMTP_AUTH_USERNAME", "u")
|
|
|
|
os.Setenv("SMTP_AUTH_PASSWORD", "p") // for PLAIN
|
|
|
|
os.Setenv("SMTP_AUTH_SECRET", "s") // for CRAM-MD5
|
|
|
|
runAuthTests(t, []authTestCase{
|
|
|
|
{true, "", ""},
|
|
|
|
{true, "CRAM-MD5", "*smtp.cramMD5Auth"},
|
|
|
|
{true, "PLAIN", "*smtp.plainAuth"},
|
|
|
|
|
|
|
|
// If all mechanisms are valid, return the first one that qualifies.
|
|
|
|
{true, "CRAM-MD5 PLAIN", "*smtp.cramMD5Auth"},
|
|
|
|
{true, "PLAIN CRAM-MD5", "*smtp.plainAuth"},
|
|
|
|
})
|
|
|
|
|
|
|
|
// Skip CRAM-MD5.
|
|
|
|
os.Setenv("SMTP_AUTH_SECRET", "")
|
|
|
|
runAuthTests(t, []authTestCase{
|
|
|
|
{true, "CRAM-MD5 PLAIN", "*smtp.plainAuth"},
|
|
|
|
})
|
|
|
|
|
|
|
|
// Skip PLAIN.
|
|
|
|
os.Setenv("SMTP_AUTH_PASSWORD", "")
|
|
|
|
os.Setenv("SMTP_AUTH_SECRET", "s")
|
|
|
|
runAuthTests(t, []authTestCase{
|
|
|
|
{true, "PLAIN CRAM-MD5", "*smtp.cramMD5Auth"},
|
|
|
|
})
|
|
|
|
|
|
|
|
// Error should be returned if we try to use PLAIN auth with an invalid -smtpSmartHost.
|
|
|
|
*smtpSmartHost = "testSMTPHost"
|
|
|
|
runAuthTests(t, []authTestCase{
|
|
|
|
{true, "PLAIN", ""},
|
|
|
|
})
|
|
|
|
os.Setenv("SMTP_AUTH_PASSWORD", "p")
|
|
|
|
if auth, cfg, err := getSMTPAuth(true, "PLAIN"); err == nil {
|
|
|
|
t.Errorf("PLAIN auth with bad host-port: expected error but got %T, %v", auth, cfg)
|
|
|
|
}
|
|
|
|
}
|
2015-05-20 22:01:17 +00:00
|
|
|
|
|
|
|
func TestSendWebhookNotification(t *testing.T) {
|
|
|
|
var body []byte
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var err error
|
|
|
|
body, err = ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error reading webhook notification: %s", err)
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
config := &pb.WebhookConfig{
|
|
|
|
Url: &ts.URL,
|
|
|
|
}
|
|
|
|
alert := &Alert{
|
|
|
|
Summary: "Testsummary",
|
|
|
|
Description: "Test alert description, something went wrong here.",
|
|
|
|
Labels: AlertLabelSet{
|
|
|
|
"alertname": "TestAlert",
|
|
|
|
},
|
|
|
|
Payload: AlertPayload{
|
|
|
|
"payload_label1": "payload_value1",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
n := ¬ifier{}
|
|
|
|
err := n.sendWebhookNotification(notificationOpTrigger, config, alert)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error sending webhook notification: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var msg webhookMessage
|
|
|
|
err = json.Unmarshal(body, &msg)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error unmarshalling webhook notification: %s", err)
|
|
|
|
}
|
|
|
|
expected := webhookMessage{
|
|
|
|
Version: "1",
|
|
|
|
Status: "firing",
|
|
|
|
Alerts: []Alert{*alert},
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(msg, expected) {
|
|
|
|
t.Errorf("incorrect webhook notification: Expected: %s Actual: %s", expected, msg)
|
|
|
|
}
|
|
|
|
}
|
2015-07-15 13:11:13 +00:00
|
|
|
|
|
|
|
func TestSendFlowdockNotification(t *testing.T) {
|
|
|
|
var body []byte
|
|
|
|
var url string
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var err error
|
|
|
|
url = r.URL.String()
|
|
|
|
body, err = ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error reading flowdock notification: %s", err)
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
defer ts.Close()
|
|
|
|
flowdockURL = &ts.URL
|
|
|
|
testApiToken := "123"
|
|
|
|
testFromAddress := "from@prometheus.io"
|
|
|
|
testTag := []string{"T1", "T2"}
|
|
|
|
|
|
|
|
config := &pb.FlowdockConfig{
|
|
|
|
ApiToken: &testApiToken,
|
|
|
|
FromAddress: &testFromAddress,
|
|
|
|
Tag: testTag,
|
|
|
|
}
|
|
|
|
alert := &Alert{
|
|
|
|
Summary: "Testsummary",
|
|
|
|
Description: "Test alert description, something went wrong here.",
|
|
|
|
Labels: AlertLabelSet{
|
|
|
|
"alertname": "TestAlert",
|
|
|
|
},
|
|
|
|
Payload: AlertPayload{
|
|
|
|
"payload_label1": "payload_value1",
|
|
|
|
"generatorURL": "http://graph",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
n := ¬ifier{}
|
|
|
|
err := n.sendFlowdockNotification(notificationOpTrigger, config, alert)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error sending flowdock notification: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var msg flowdockMessage
|
|
|
|
|
|
|
|
expectedUrl := "/" + testApiToken
|
|
|
|
if url != expectedUrl {
|
|
|
|
t.Error("Flowdock message POSTed to wrong URL, expected %s and got %s", expectedUrl, url)
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(body, &msg)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error unmarshalling flowdock notification: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
contentBuf := &bytes.Buffer{}
|
|
|
|
err = contentTmpl.Execute(contentBuf, struct{ Alert *Alert }{Alert: alert})
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error expanding expected HTML content for Flowdock message: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := flowdockMessage{
|
|
|
|
Source: "Prometheus",
|
|
|
|
FromAddress: testFromAddress,
|
|
|
|
Subject: html.EscapeString(alert.Summary),
|
|
|
|
Format: "html",
|
|
|
|
Content: contentBuf.String(),
|
|
|
|
Link: "http://graph",
|
|
|
|
Tags: append(testTag, "firing"),
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(msg, expected) {
|
|
|
|
t.Errorf("incorrect Flowdock notification: Expected: %s Actual: %s", expected, msg)
|
|
|
|
}
|
|
|
|
}
|
2015-07-20 16:09:30 +00:00
|
|
|
|
|
|
|
func TestSendOpsGenieNotification(t *testing.T) {
|
|
|
|
var body []byte
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var err error
|
|
|
|
body, err = ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error reading webhook notification: %s", err)
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
defer ts.Close()
|
|
|
|
opsgenieAPIURL = &ts.URL
|
|
|
|
apikey := "AAAB"
|
|
|
|
config := &pb.OpsGenieConfig{
|
|
|
|
ApiKey: &apikey,
|
|
|
|
LabelsToTags: []string{"alertname"},
|
|
|
|
Teams: []string{"prometheus"},
|
|
|
|
}
|
|
|
|
alert := &Alert{
|
|
|
|
Summary: "Testsummary",
|
|
|
|
Description: "Test alert description, something went wrong here.",
|
|
|
|
Labels: AlertLabelSet{
|
|
|
|
"alertname": "TestAlert",
|
|
|
|
},
|
|
|
|
Payload: AlertPayload{
|
|
|
|
"payload_label1": "payload_value1",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
n := ¬ifier{}
|
|
|
|
err := n.sendOpsGenieNotification(notificationOpTrigger, config, alert)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error sending OpsGenie notification: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var msg opsGenieMessageCreate
|
|
|
|
err = json.Unmarshal(body, &msg)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error unmarshalling OpsGenie notification: %s", err)
|
|
|
|
}
|
|
|
|
expected := opsGenieMessageCreate{
|
|
|
|
ApiKey: "AAAB",
|
|
|
|
Message: "Testsummary",
|
|
|
|
Description: "Test alert description, something went wrong here.",
|
|
|
|
Tags: []string{"TestAlert"},
|
|
|
|
Teams: []string{"prometheus"},
|
|
|
|
Alias: strconv.FormatUint(uint64(alert.Fingerprint()), 10),
|
|
|
|
Details: map[string]interface{}{
|
|
|
|
"extra_labels": convertPayloadMap(alert.Payload),
|
|
|
|
"grouping_labels": convertAlertLabelSetMap(alert.Labels),
|
|
|
|
"runbook": alert.Runbook,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(msg, expected) {
|
|
|
|
t.Errorf("incorrect OpsGenie notification: Expected: %s Actual: %s", expected, msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertPayloadMap(m AlertPayload) map[string]interface{} {
|
|
|
|
m1 := map[string]interface{}{}
|
|
|
|
for k, v := range m {
|
|
|
|
m1[k] = v
|
|
|
|
}
|
|
|
|
return m1
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertAlertLabelSetMap(m AlertLabelSet) map[string]interface{} {
|
|
|
|
m1 := map[string]interface{}{}
|
|
|
|
for k, v := range m {
|
|
|
|
m1[k] = v
|
|
|
|
}
|
|
|
|
return m1
|
|
|
|
}
|