...

Source file src/github.com/prometheus/alertmanager/notify/pagerduty/pagerduty_test.go

Documentation: github.com/prometheus/alertmanager/notify/pagerduty

     1  // Copyright 2019 Prometheus Team
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package pagerduty
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"net/url"
    25  	"os"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/go-kit/log"
    31  	commoncfg "github.com/prometheus/common/config"
    32  	"github.com/prometheus/common/model"
    33  	"github.com/stretchr/testify/require"
    34  
    35  	"github.com/prometheus/alertmanager/config"
    36  	"github.com/prometheus/alertmanager/notify"
    37  	"github.com/prometheus/alertmanager/notify/test"
    38  	"github.com/prometheus/alertmanager/types"
    39  )
    40  
    41  func TestPagerDutyRetryV1(t *testing.T) {
    42  	notifier, err := New(
    43  		&config.PagerdutyConfig{
    44  			ServiceKey: config.Secret("01234567890123456789012345678901"),
    45  			HTTPConfig: &commoncfg.HTTPClientConfig{},
    46  		},
    47  		test.CreateTmpl(t),
    48  		log.NewNopLogger(),
    49  	)
    50  	require.NoError(t, err)
    51  
    52  	retryCodes := append(test.DefaultRetryCodes(), http.StatusForbidden)
    53  	for statusCode, expected := range test.RetryTests(retryCodes) {
    54  		actual, _ := notifier.retrier.Check(statusCode, nil)
    55  		require.Equal(t, expected, actual, fmt.Sprintf("retryv1 - error on status %d", statusCode))
    56  	}
    57  }
    58  
    59  func TestPagerDutyRetryV2(t *testing.T) {
    60  	notifier, err := New(
    61  		&config.PagerdutyConfig{
    62  			RoutingKey: config.Secret("01234567890123456789012345678901"),
    63  			HTTPConfig: &commoncfg.HTTPClientConfig{},
    64  		},
    65  		test.CreateTmpl(t),
    66  		log.NewNopLogger(),
    67  	)
    68  	require.NoError(t, err)
    69  
    70  	retryCodes := append(test.DefaultRetryCodes(), http.StatusTooManyRequests)
    71  	for statusCode, expected := range test.RetryTests(retryCodes) {
    72  		actual, _ := notifier.retrier.Check(statusCode, nil)
    73  		require.Equal(t, expected, actual, fmt.Sprintf("retryv2 - error on status %d", statusCode))
    74  	}
    75  }
    76  
    77  func TestPagerDutyRedactedURLV1(t *testing.T) {
    78  	ctx, u, fn := test.GetContextWithCancelingURL()
    79  	defer fn()
    80  
    81  	key := "01234567890123456789012345678901"
    82  	notifier, err := New(
    83  		&config.PagerdutyConfig{
    84  			ServiceKey: config.Secret(key),
    85  			HTTPConfig: &commoncfg.HTTPClientConfig{},
    86  		},
    87  		test.CreateTmpl(t),
    88  		log.NewNopLogger(),
    89  	)
    90  	require.NoError(t, err)
    91  	notifier.apiV1 = u.String()
    92  
    93  	test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
    94  }
    95  
    96  func TestPagerDutyRedactedURLV2(t *testing.T) {
    97  	ctx, u, fn := test.GetContextWithCancelingURL()
    98  	defer fn()
    99  
   100  	key := "01234567890123456789012345678901"
   101  	notifier, err := New(
   102  		&config.PagerdutyConfig{
   103  			URL:        &config.URL{URL: u},
   104  			RoutingKey: config.Secret(key),
   105  			HTTPConfig: &commoncfg.HTTPClientConfig{},
   106  		},
   107  		test.CreateTmpl(t),
   108  		log.NewNopLogger(),
   109  	)
   110  	require.NoError(t, err)
   111  
   112  	test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
   113  }
   114  
   115  func TestPagerDutyV1ServiceKeyFromFile(t *testing.T) {
   116  	key := "01234567890123456789012345678901"
   117  	f, err := os.CreateTemp("", "pagerduty_test")
   118  	require.NoError(t, err, "creating temp file failed")
   119  	_, err = f.WriteString(key)
   120  	require.NoError(t, err, "writing to temp file failed")
   121  
   122  	ctx, u, fn := test.GetContextWithCancelingURL()
   123  	defer fn()
   124  
   125  	notifier, err := New(
   126  		&config.PagerdutyConfig{
   127  			ServiceKeyFile: f.Name(),
   128  			HTTPConfig:     &commoncfg.HTTPClientConfig{},
   129  		},
   130  		test.CreateTmpl(t),
   131  		log.NewNopLogger(),
   132  	)
   133  	require.NoError(t, err)
   134  	notifier.apiV1 = u.String()
   135  
   136  	test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
   137  }
   138  
   139  func TestPagerDutyV2RoutingKeyFromFile(t *testing.T) {
   140  	key := "01234567890123456789012345678901"
   141  	f, err := os.CreateTemp("", "pagerduty_test")
   142  	require.NoError(t, err, "creating temp file failed")
   143  	_, err = f.WriteString(key)
   144  	require.NoError(t, err, "writing to temp file failed")
   145  
   146  	ctx, u, fn := test.GetContextWithCancelingURL()
   147  	defer fn()
   148  
   149  	notifier, err := New(
   150  		&config.PagerdutyConfig{
   151  			URL:            &config.URL{URL: u},
   152  			RoutingKeyFile: f.Name(),
   153  			HTTPConfig:     &commoncfg.HTTPClientConfig{},
   154  		},
   155  		test.CreateTmpl(t),
   156  		log.NewNopLogger(),
   157  	)
   158  	require.NoError(t, err)
   159  
   160  	test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
   161  }
   162  
   163  func TestPagerDutyTemplating(t *testing.T) {
   164  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   165  		dec := json.NewDecoder(r.Body)
   166  		out := make(map[string]interface{})
   167  		err := dec.Decode(&out)
   168  		if err != nil {
   169  			panic(err)
   170  		}
   171  	}))
   172  	defer srv.Close()
   173  	u, _ := url.Parse(srv.URL)
   174  
   175  	for _, tc := range []struct {
   176  		title string
   177  		cfg   *config.PagerdutyConfig
   178  
   179  		retry  bool
   180  		errMsg string
   181  	}{
   182  		{
   183  			title: "full-blown message",
   184  			cfg: &config.PagerdutyConfig{
   185  				RoutingKey: config.Secret("01234567890123456789012345678901"),
   186  				Images: []config.PagerdutyImage{
   187  					{
   188  						Src:  "{{ .Status }}",
   189  						Alt:  "{{ .Status }}",
   190  						Href: "{{ .Status }}",
   191  					},
   192  				},
   193  				Links: []config.PagerdutyLink{
   194  					{
   195  						Href: "{{ .Status }}",
   196  						Text: "{{ .Status }}",
   197  					},
   198  				},
   199  				Details: map[string]string{
   200  					"firing":       `{{ template "pagerduty.default.instances" .Alerts.Firing }}`,
   201  					"resolved":     `{{ template "pagerduty.default.instances" .Alerts.Resolved }}`,
   202  					"num_firing":   `{{ .Alerts.Firing | len }}`,
   203  					"num_resolved": `{{ .Alerts.Resolved | len }}`,
   204  				},
   205  			},
   206  		},
   207  		{
   208  			title: "details with templating errors",
   209  			cfg: &config.PagerdutyConfig{
   210  				RoutingKey: config.Secret("01234567890123456789012345678901"),
   211  				Details: map[string]string{
   212  					"firing":       `{{ template "pagerduty.default.instances" .Alerts.Firing`,
   213  					"resolved":     `{{ template "pagerduty.default.instances" .Alerts.Resolved }}`,
   214  					"num_firing":   `{{ .Alerts.Firing | len }}`,
   215  					"num_resolved": `{{ .Alerts.Resolved | len }}`,
   216  				},
   217  			},
   218  			errMsg: "failed to template",
   219  		},
   220  		{
   221  			title: "v2 message with templating errors",
   222  			cfg: &config.PagerdutyConfig{
   223  				RoutingKey: config.Secret("01234567890123456789012345678901"),
   224  				Severity:   "{{ ",
   225  			},
   226  			errMsg: "failed to template",
   227  		},
   228  		{
   229  			title: "v1 message with templating errors",
   230  			cfg: &config.PagerdutyConfig{
   231  				ServiceKey: config.Secret("01234567890123456789012345678901"),
   232  				Client:     "{{ ",
   233  			},
   234  			errMsg: "failed to template",
   235  		},
   236  		{
   237  			title: "routing key cannot be empty",
   238  			cfg: &config.PagerdutyConfig{
   239  				RoutingKey: config.Secret(`{{ "" }}`),
   240  			},
   241  			errMsg: "routing key cannot be empty",
   242  		},
   243  		{
   244  			title: "service_key cannot be empty",
   245  			cfg: &config.PagerdutyConfig{
   246  				ServiceKey: config.Secret(`{{ "" }}`),
   247  			},
   248  			errMsg: "service key cannot be empty",
   249  		},
   250  	} {
   251  		t.Run(tc.title, func(t *testing.T) {
   252  			tc.cfg.URL = &config.URL{URL: u}
   253  			tc.cfg.HTTPConfig = &commoncfg.HTTPClientConfig{}
   254  			pd, err := New(tc.cfg, test.CreateTmpl(t), log.NewNopLogger())
   255  			require.NoError(t, err)
   256  			if pd.apiV1 != "" {
   257  				pd.apiV1 = u.String()
   258  			}
   259  
   260  			ctx := context.Background()
   261  			ctx = notify.WithGroupKey(ctx, "1")
   262  
   263  			ok, err := pd.Notify(ctx, []*types.Alert{
   264  				{
   265  					Alert: model.Alert{
   266  						Labels: model.LabelSet{
   267  							"lbl1": "val1",
   268  						},
   269  						StartsAt: time.Now(),
   270  						EndsAt:   time.Now().Add(time.Hour),
   271  					},
   272  				},
   273  			}...)
   274  			if tc.errMsg == "" {
   275  				require.NoError(t, err)
   276  			} else {
   277  				require.Error(t, err)
   278  				require.Contains(t, err.Error(), tc.errMsg)
   279  			}
   280  			require.Equal(t, tc.retry, ok)
   281  		})
   282  	}
   283  }
   284  
   285  func TestErrDetails(t *testing.T) {
   286  	for _, tc := range []struct {
   287  		status int
   288  		body   io.Reader
   289  
   290  		exp string
   291  	}{
   292  		{
   293  			status: http.StatusBadRequest,
   294  			body: bytes.NewBuffer([]byte(
   295  				`{"status":"invalid event","message":"Event object is invalid","errors":["Length of 'routing_key' is incorrect (should be 32 characters)"]}`,
   296  			)),
   297  
   298  			exp: "Length of 'routing_key' is incorrect",
   299  		},
   300  		{
   301  			status: http.StatusBadRequest,
   302  			body:   bytes.NewBuffer([]byte(`{"status"}`)),
   303  
   304  			exp: "",
   305  		},
   306  		{
   307  			status: http.StatusBadRequest,
   308  
   309  			exp: "",
   310  		},
   311  		{
   312  			status: http.StatusTooManyRequests,
   313  
   314  			exp: "",
   315  		},
   316  	} {
   317  		tc := tc
   318  		t.Run("", func(t *testing.T) {
   319  			err := errDetails(tc.status, tc.body)
   320  			require.Contains(t, err, tc.exp)
   321  		})
   322  	}
   323  }
   324  
   325  func TestEventSizeEnforcement(t *testing.T) {
   326  	bigDetails := map[string]string{
   327  		"firing": strings.Repeat("a", 513000),
   328  	}
   329  
   330  	// V1 Messages
   331  	msgV1 := &pagerDutyMessage{
   332  		ServiceKey: "01234567890123456789012345678901",
   333  		EventType:  "trigger",
   334  		Details:    bigDetails,
   335  	}
   336  
   337  	notifierV1, err := New(
   338  		&config.PagerdutyConfig{
   339  			ServiceKey: config.Secret("01234567890123456789012345678901"),
   340  			HTTPConfig: &commoncfg.HTTPClientConfig{},
   341  		},
   342  		test.CreateTmpl(t),
   343  		log.NewNopLogger(),
   344  	)
   345  	require.NoError(t, err)
   346  
   347  	encodedV1, err := notifierV1.encodeMessage(msgV1)
   348  	require.NoError(t, err)
   349  	require.Contains(t, encodedV1.String(), `"details":{"error":"Custom details have been removed because the original event exceeds the maximum size of 512KB"}`)
   350  
   351  	// V2 Messages
   352  	msgV2 := &pagerDutyMessage{
   353  		RoutingKey:  "01234567890123456789012345678901",
   354  		EventAction: "trigger",
   355  		Payload: &pagerDutyPayload{
   356  			CustomDetails: bigDetails,
   357  		},
   358  	}
   359  
   360  	notifierV2, err := New(
   361  		&config.PagerdutyConfig{
   362  			RoutingKey: config.Secret("01234567890123456789012345678901"),
   363  			HTTPConfig: &commoncfg.HTTPClientConfig{},
   364  		},
   365  		test.CreateTmpl(t),
   366  		log.NewNopLogger(),
   367  	)
   368  	require.NoError(t, err)
   369  
   370  	encodedV2, err := notifierV2.encodeMessage(msgV2)
   371  	require.NoError(t, err)
   372  	require.Contains(t, encodedV2.String(), `"custom_details":{"error":"Custom details have been removed because the original event exceeds the maximum size of 512KB"}`)
   373  }
   374  
   375  func TestPagerDutyEmptySrcHref(t *testing.T) {
   376  	type pagerDutyEvent struct {
   377  		RoutingKey  string           `json:"routing_key"`
   378  		EventAction string           `json:"event_action"`
   379  		DedupKey    string           `json:"dedup_key"`
   380  		Payload     pagerDutyPayload `json:"payload"`
   381  		Images      []pagerDutyImage
   382  		Links       []pagerDutyLink
   383  	}
   384  
   385  	images := []config.PagerdutyImage{
   386  		{
   387  			Src:  "",
   388  			Alt:  "Empty src",
   389  			Href: "https://example.com/",
   390  		},
   391  		{
   392  			Src:  "https://example.com/cat.jpg",
   393  			Alt:  "Empty href",
   394  			Href: "",
   395  		},
   396  		{
   397  			Src:  "https://example.com/cat.jpg",
   398  			Alt:  "",
   399  			Href: "https://example.com/",
   400  		},
   401  	}
   402  
   403  	links := []config.PagerdutyLink{
   404  		{
   405  			Href: "",
   406  			Text: "Empty href",
   407  		},
   408  		{
   409  			Href: "https://example.com/",
   410  			Text: "",
   411  		},
   412  	}
   413  
   414  	expectedImages := make([]pagerDutyImage, 0, len(images))
   415  	for _, image := range images {
   416  		if image.Src == "" {
   417  			continue
   418  		}
   419  		expectedImages = append(expectedImages, pagerDutyImage{
   420  			Src:  image.Src,
   421  			Alt:  image.Alt,
   422  			Href: image.Href,
   423  		})
   424  	}
   425  
   426  	expectedLinks := make([]pagerDutyLink, 0, len(links))
   427  	for _, link := range links {
   428  		if link.Href == "" {
   429  			continue
   430  		}
   431  		expectedLinks = append(expectedLinks, pagerDutyLink{
   432  			HRef: link.Href,
   433  			Text: link.Text,
   434  		})
   435  	}
   436  
   437  	server := httptest.NewServer(http.HandlerFunc(
   438  		func(w http.ResponseWriter, r *http.Request) {
   439  			decoder := json.NewDecoder(r.Body)
   440  			var event pagerDutyEvent
   441  			if err := decoder.Decode(&event); err != nil {
   442  				panic(err)
   443  			}
   444  
   445  			if event.RoutingKey == "" || event.EventAction == "" {
   446  				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   447  				return
   448  			}
   449  
   450  			for _, image := range event.Images {
   451  				if image.Src == "" {
   452  					http.Error(w, "Event object is invalid: 'image src' is missing or blank", http.StatusBadRequest)
   453  					return
   454  				}
   455  			}
   456  
   457  			for _, link := range event.Links {
   458  				if link.HRef == "" {
   459  					http.Error(w, "Event object is invalid: 'link href' is missing or blank", http.StatusBadRequest)
   460  					return
   461  				}
   462  			}
   463  
   464  			require.Equal(t, expectedImages, event.Images)
   465  			require.Equal(t, expectedLinks, event.Links)
   466  		},
   467  	))
   468  	defer server.Close()
   469  
   470  	url, err := url.Parse(server.URL)
   471  	require.NoError(t, err)
   472  
   473  	pagerDutyConfig := config.PagerdutyConfig{
   474  		HTTPConfig: &commoncfg.HTTPClientConfig{},
   475  		RoutingKey: config.Secret("01234567890123456789012345678901"),
   476  		URL:        &config.URL{URL: url},
   477  		Images:     images,
   478  		Links:      links,
   479  	}
   480  
   481  	pagerDuty, err := New(&pagerDutyConfig, test.CreateTmpl(t), log.NewNopLogger())
   482  	require.NoError(t, err)
   483  
   484  	ctx := context.Background()
   485  	ctx = notify.WithGroupKey(ctx, "1")
   486  
   487  	_, err = pagerDuty.Notify(ctx, []*types.Alert{
   488  		{
   489  			Alert: model.Alert{
   490  				Labels: model.LabelSet{
   491  					"lbl1": "val1",
   492  				},
   493  				StartsAt: time.Now(),
   494  				EndsAt:   time.Now().Add(time.Hour),
   495  			},
   496  		},
   497  	}...)
   498  	require.NoError(t, err)
   499  }
   500  

View as plain text