2018-05-14 12:36:24 +00:00
// Copyright 2018 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.
2017-11-11 14:45:11 +00:00
package notify
import (
2018-11-09 09:00:23 +00:00
"context"
2019-01-15 10:59:05 +00:00
"encoding/json"
2017-11-11 14:45:11 +00:00
"fmt"
2017-07-27 08:48:24 +00:00
"io/ioutil"
2017-11-11 14:45:11 +00:00
"net/http"
2017-07-27 08:48:24 +00:00
"net/url"
2017-11-11 14:45:11 +00:00
"testing"
2018-02-06 12:45:59 +00:00
"time"
2017-11-11 14:45:11 +00:00
2018-02-06 12:45:59 +00:00
"github.com/go-kit/kit/log"
2017-11-11 14:45:11 +00:00
"github.com/stretchr/testify/require"
2018-02-06 12:45:59 +00:00
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/template"
2018-02-11 19:09:47 +00:00
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
2017-11-11 14:45:11 +00:00
)
func TestWebhookRetry ( t * testing . T ) {
2018-07-26 10:39:33 +00:00
u , err := url . Parse ( "http://example.com" )
if err != nil {
t . Fatalf ( "failed to parse URL: %v" , err )
}
notifier := & Webhook { conf : & config . WebhookConfig { URL : & config . URL { u } } }
2017-11-11 14:45:11 +00:00
for statusCode , expected := range retryTests ( defaultRetryCodes ( ) ) {
actual , _ := notifier . retry ( statusCode )
require . Equal ( t , expected , actual , fmt . Sprintf ( "error on status %d" , statusCode ) )
}
}
func TestPagerDutyRetryV1 ( t * testing . T ) {
notifier := new ( PagerDuty )
retryCodes := append ( defaultRetryCodes ( ) , http . StatusForbidden )
for statusCode , expected := range retryTests ( retryCodes ) {
2018-07-30 10:25:52 +00:00
resp := & http . Response {
StatusCode : statusCode ,
}
actual , _ := notifier . retryV1 ( resp )
2017-11-11 14:45:11 +00:00
require . Equal ( t , expected , actual , fmt . Sprintf ( "retryv1 - error on status %d" , statusCode ) )
}
}
func TestPagerDutyRetryV2 ( t * testing . T ) {
notifier := new ( PagerDuty )
retryCodes := append ( defaultRetryCodes ( ) , http . StatusTooManyRequests )
for statusCode , expected := range retryTests ( retryCodes ) {
actual , _ := notifier . retryV2 ( statusCode )
require . Equal ( t , expected , actual , fmt . Sprintf ( "retryv2 - error on status %d" , statusCode ) )
}
}
func TestSlackRetry ( t * testing . T ) {
notifier := new ( Slack )
for statusCode , expected := range retryTests ( defaultRetryCodes ( ) ) {
actual , _ := notifier . retry ( statusCode )
require . Equal ( t , expected , actual , fmt . Sprintf ( "error on status %d" , statusCode ) )
}
}
func TestHipchatRetry ( t * testing . T ) {
notifier := new ( Hipchat )
retryCodes := append ( defaultRetryCodes ( ) , http . StatusTooManyRequests )
for statusCode , expected := range retryTests ( retryCodes ) {
actual , _ := notifier . retry ( statusCode )
require . Equal ( t , expected , actual , fmt . Sprintf ( "error on status %d" , statusCode ) )
}
}
func TestOpsGenieRetry ( t * testing . T ) {
notifier := new ( OpsGenie )
retryCodes := append ( defaultRetryCodes ( ) , http . StatusTooManyRequests )
for statusCode , expected := range retryTests ( retryCodes ) {
actual , _ := notifier . retry ( statusCode )
require . Equal ( t , expected , actual , fmt . Sprintf ( "error on status %d" , statusCode ) )
}
}
func TestVictorOpsRetry ( t * testing . T ) {
notifier := new ( VictorOps )
for statusCode , expected := range retryTests ( defaultRetryCodes ( ) ) {
actual , _ := notifier . retry ( statusCode )
require . Equal ( t , expected , actual , fmt . Sprintf ( "error on status %d" , statusCode ) )
}
}
func TestPushoverRetry ( t * testing . T ) {
notifier := new ( Pushover )
for statusCode , expected := range retryTests ( defaultRetryCodes ( ) ) {
actual , _ := notifier . retry ( statusCode )
require . Equal ( t , expected , actual , fmt . Sprintf ( "error on status %d" , statusCode ) )
}
}
func retryTests ( retryCodes [ ] int ) map [ int ] bool {
tests := map [ int ] bool {
// 1xx
http . StatusContinue : false ,
http . StatusSwitchingProtocols : false ,
http . StatusProcessing : false ,
// 2xx
http . StatusOK : false ,
http . StatusCreated : false ,
http . StatusAccepted : false ,
http . StatusNonAuthoritativeInfo : false ,
http . StatusNoContent : false ,
http . StatusResetContent : false ,
http . StatusPartialContent : false ,
http . StatusMultiStatus : false ,
http . StatusAlreadyReported : false ,
http . StatusIMUsed : false ,
// 3xx
http . StatusMultipleChoices : false ,
http . StatusMovedPermanently : false ,
http . StatusFound : false ,
http . StatusSeeOther : false ,
http . StatusNotModified : false ,
http . StatusUseProxy : false ,
http . StatusTemporaryRedirect : false ,
http . StatusPermanentRedirect : false ,
// 4xx
http . StatusBadRequest : false ,
http . StatusUnauthorized : false ,
http . StatusPaymentRequired : false ,
http . StatusForbidden : false ,
http . StatusNotFound : false ,
http . StatusMethodNotAllowed : false ,
http . StatusNotAcceptable : false ,
http . StatusProxyAuthRequired : false ,
http . StatusRequestTimeout : false ,
http . StatusConflict : false ,
http . StatusGone : false ,
http . StatusLengthRequired : false ,
http . StatusPreconditionFailed : false ,
http . StatusRequestEntityTooLarge : false ,
http . StatusRequestURITooLong : false ,
http . StatusUnsupportedMediaType : false ,
http . StatusRequestedRangeNotSatisfiable : false ,
http . StatusExpectationFailed : false ,
http . StatusTeapot : false ,
http . StatusUnprocessableEntity : false ,
http . StatusLocked : false ,
http . StatusFailedDependency : false ,
http . StatusUpgradeRequired : false ,
http . StatusPreconditionRequired : false ,
http . StatusTooManyRequests : false ,
http . StatusRequestHeaderFieldsTooLarge : false ,
http . StatusUnavailableForLegalReasons : false ,
// 5xx
http . StatusInternalServerError : false ,
http . StatusNotImplemented : false ,
http . StatusBadGateway : false ,
http . StatusServiceUnavailable : false ,
http . StatusGatewayTimeout : false ,
http . StatusHTTPVersionNotSupported : false ,
http . StatusVariantAlsoNegotiates : false ,
http . StatusInsufficientStorage : false ,
http . StatusLoopDetected : false ,
http . StatusNotExtended : false ,
http . StatusNetworkAuthenticationRequired : false ,
}
for _ , statusCode := range retryCodes {
tests [ statusCode ] = true
}
return tests
}
func defaultRetryCodes ( ) [ ] int {
return [ ] int {
http . StatusInternalServerError ,
http . StatusNotImplemented ,
http . StatusBadGateway ,
http . StatusServiceUnavailable ,
http . StatusGatewayTimeout ,
http . StatusHTTPVersionNotSupported ,
http . StatusVariantAlsoNegotiates ,
http . StatusInsufficientStorage ,
http . StatusLoopDetected ,
http . StatusNotExtended ,
http . StatusNetworkAuthenticationRequired ,
}
}
2018-02-06 12:45:59 +00:00
func createTmpl ( t * testing . T ) * template . Template {
tmpl , err := template . FromGlobs ( )
require . NoError ( t , err )
tmpl . ExternalURL , _ = url . Parse ( "http://am" )
return tmpl
}
2018-02-11 19:09:47 +00:00
func readBody ( t * testing . T , r * http . Request ) string {
2018-02-06 12:45:59 +00:00
body , err := ioutil . ReadAll ( r . Body )
require . NoError ( t , err )
return string ( body )
}
func TestOpsGenie ( t * testing . T ) {
2018-07-26 10:39:33 +00:00
u , err := url . Parse ( "https://opsgenie/api" )
if err != nil {
t . Fatalf ( "failed to parse URL: %v" , err )
}
2018-02-06 12:45:59 +00:00
logger := log . NewNopLogger ( )
tmpl := createTmpl ( t )
conf := & config . OpsGenieConfig {
NotifierConfig : config . NotifierConfig {
VSendResolved : true ,
} ,
Message : ` {{ .CommonLabels .Message }} ` ,
Description : ` {{ .CommonLabels .Description }} ` ,
Source : ` {{ .CommonLabels .Source }} ` ,
2018-02-11 19:09:47 +00:00
Teams : ` {{ .CommonLabels .Teams }} ` ,
2018-02-06 12:45:59 +00:00
Tags : ` {{ .CommonLabels .Tags }} ` ,
Note : ` {{ .CommonLabels .Note }} ` ,
Priority : ` {{ .CommonLabels .Priority }} ` ,
2018-10-23 11:53:57 +00:00
APIKey : ` {{ .ExternalURL }} ` ,
2018-07-26 10:39:33 +00:00
APIURL : & config . URL { u } ,
2018-02-06 12:45:59 +00:00
}
notifier := NewOpsGenie ( conf , tmpl , logger )
ctx := context . Background ( )
ctx = WithGroupKey ( ctx , "1" )
2018-07-23 12:04:40 +00:00
expectedURL , _ := url . Parse ( "https://opsgenie/apiv2/alerts" )
2018-02-06 12:45:59 +00:00
// Empty alert.
2018-02-11 19:09:47 +00:00
alert1 := & types . Alert {
Alert : model . Alert {
StartsAt : time . Now ( ) ,
EndsAt : time . Now ( ) . Add ( time . Hour ) ,
} ,
2018-02-06 12:45:59 +00:00
}
expectedBody := ` { "alias" : "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" , "message" : "" , "details" : { } , "source" : "" }
`
req , retry , err := notifier . createRequest ( ctx , alert1 )
require . NoError ( t , err )
require . Equal ( t , true , retry )
2018-07-23 12:04:40 +00:00
require . Equal ( t , expectedURL , req . URL )
2018-10-23 11:53:57 +00:00
require . Equal ( t , "GenieKey http://am" , req . Header . Get ( "Authorization" ) )
2018-02-06 12:45:59 +00:00
require . Equal ( t , expectedBody , readBody ( t , req ) )
// Fully defined alert.
2018-02-11 19:09:47 +00:00
alert2 := & types . Alert {
2018-02-06 12:45:59 +00:00
Alert : model . Alert {
Labels : model . LabelSet {
2018-02-11 19:09:47 +00:00
"Message" : "message" ,
2018-02-06 12:45:59 +00:00
"Description" : "description" ,
2018-02-11 19:09:47 +00:00
"Source" : "http://prometheus" ,
"Teams" : "TeamA,TeamB," ,
"Tags" : "tag1,tag2" ,
"Note" : "this is a note" ,
"Priotity" : "P1" ,
} ,
2018-02-06 12:45:59 +00:00
StartsAt : time . Now ( ) ,
2018-02-11 19:09:47 +00:00
EndsAt : time . Now ( ) . Add ( time . Hour ) ,
2018-02-06 12:45:59 +00:00
} ,
}
expectedBody = ` { "alias" : "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" , "message" : "message" , "description" : "description" , "details" : { } , "source" : "http://prometheus" , "teams" : [ { "name" : "TeamA" } , { "name" : "TeamB" } ] , "tags" : [ "tag1" , "tag2" ] , "note" : "this is a note" }
`
req , retry , err = notifier . createRequest ( ctx , alert2 )
2018-02-28 16:42:32 +00:00
require . NoError ( t , err )
require . Equal ( t , true , retry )
2018-02-06 12:45:59 +00:00
require . Equal ( t , expectedBody , readBody ( t , req ) )
2018-10-23 11:53:57 +00:00
// Broken API Key Template.
conf . APIKey = "{{ kaput "
_ , _ , err = notifier . createRequest ( ctx , alert2 )
require . Error ( t , err )
require . Equal ( t , err . Error ( ) , "templating error: template: :1: function \"kaput\" not defined" )
2018-02-11 19:09:47 +00:00
}
2018-11-09 12:45:07 +00:00
func TestEmailConfigNoAuthMechs ( t * testing . T ) {
email := & Email {
2019-03-01 14:53:18 +00:00
conf : & config . EmailConfig { AuthUsername : "test" } , tmpl : & template . Template { } , logger : log . NewNopLogger ( ) ,
2018-11-09 12:45:07 +00:00
}
_ , err := email . auth ( "" )
require . Error ( t , err )
require . Equal ( t , err . Error ( ) , "unknown auth mechanism: " )
}
func TestEmailConfigMissingAuthParam ( t * testing . T ) {
2019-03-01 14:53:18 +00:00
conf := & config . EmailConfig { AuthUsername : "test" }
2018-11-09 12:45:07 +00:00
email := & Email {
2019-03-01 14:53:18 +00:00
conf : conf , tmpl : & template . Template { } , logger : log . NewNopLogger ( ) ,
2018-11-09 12:45:07 +00:00
}
_ , err := email . auth ( "CRAM-MD5" )
require . Error ( t , err )
require . Equal ( t , err . Error ( ) , "missing secret for CRAM-MD5 auth mechanism" )
_ , err = email . auth ( "PLAIN" )
require . Error ( t , err )
require . Equal ( t , err . Error ( ) , "missing password for PLAIN auth mechanism" )
_ , err = email . auth ( "LOGIN" )
require . Error ( t , err )
require . Equal ( t , err . Error ( ) , "missing password for LOGIN auth mechanism" )
_ , err = email . auth ( "PLAIN LOGIN" )
require . Error ( t , err )
require . Equal ( t , err . Error ( ) , "missing password for PLAIN auth mechanism; missing password for LOGIN auth mechanism" )
}
2019-01-15 10:59:05 +00:00
2019-03-01 14:53:18 +00:00
func TestEmailNoUsernameStillOk ( t * testing . T ) {
email := & Email {
conf : & config . EmailConfig { } , tmpl : & template . Template { } , logger : log . NewNopLogger ( ) ,
}
a , err := email . auth ( "CRAM-MD5" )
require . NoError ( t , err )
require . Nil ( t , a )
}
2019-01-15 10:59:05 +00:00
func TestVictorOpsCustomFields ( t * testing . T ) {
logger := log . NewNopLogger ( )
tmpl := createTmpl ( t )
url , err := url . Parse ( "http://nowhere.com" )
require . NoError ( t , err , "unexpected error parsing mock url" )
conf := & config . VictorOpsConfig {
APIKey : ` 12345 ` ,
APIURL : & config . URL { url } ,
EntityDisplayName : ` {{ .CommonLabels .Message }} ` ,
StateMessage : ` {{ .CommonLabels .Message }} ` ,
RoutingKey : ` test ` ,
MessageType : ` ` ,
MonitoringTool : ` AM ` ,
CustomFields : map [ string ] string {
"Field_A" : "{{ .CommonLabels.Message }}" ,
} ,
}
notifier := NewVictorOps ( conf , tmpl , logger )
ctx := context . Background ( )
ctx = WithGroupKey ( ctx , "1" )
alert := & types . Alert {
Alert : model . Alert {
Labels : model . LabelSet {
"Message" : "message" ,
} ,
StartsAt : time . Now ( ) ,
EndsAt : time . Now ( ) . Add ( time . Hour ) ,
} ,
}
msg , err := notifier . createVictorOpsPayload ( ctx , alert )
require . NoError ( t , err )
var m map [ string ] string
err = json . Unmarshal ( msg . Bytes ( ) , & m )
require . NoError ( t , err )
// Verify that a custom field was added to the payload and templatized.
require . Equal ( t , "message" , m [ "Field_A" ] )
}
2019-02-11 17:07:14 +00:00
func TestTruncate ( t * testing . T ) {
testCases := [ ] struct {
in string
n int
out string
trunc bool
} {
{
in : "" ,
n : 5 ,
out : "" ,
trunc : false ,
} ,
{
in : "abcde" ,
n : 2 ,
out : "ab" ,
trunc : true ,
} ,
{
in : "abcde" ,
n : 4 ,
out : "a..." ,
trunc : true ,
} ,
{
in : "abcde" ,
n : 5 ,
out : "abcde" ,
trunc : false ,
} ,
{
in : "abcdefgh" ,
n : 5 ,
out : "ab..." ,
trunc : true ,
} ,
{
in : "a⌘cde" ,
n : 5 ,
out : "a⌘cde" ,
trunc : false ,
} ,
{
in : "a⌘cdef" ,
n : 5 ,
out : "a⌘..." ,
trunc : true ,
} ,
}
for _ , tc := range testCases {
t . Run ( fmt . Sprintf ( "truncate(%s,%d)" , tc . in , tc . n ) , func ( t * testing . T ) {
s , trunc := truncate ( tc . in , tc . n )
require . Equal ( t , tc . trunc , trunc )
require . Equal ( t , tc . out , s )
} )
}
}