notify: rework handling of PagerDuty responses

Signed-off-by: Simon Pasquier <spasquie@redhat.com>
This commit is contained in:
Simon Pasquier 2019-03-26 11:12:21 +01:00
parent 5b09060431
commit f0763e2047
2 changed files with 71 additions and 22 deletions

View File

@ -358,16 +358,7 @@ func (n *PagerDuty) notifyV2(
}
defer resp.Body.Close()
// See: https://v2.developer.pagerduty.com/docs/events-api-v2#api-response-codes--retry-logic
if resp.StatusCode == http.StatusBadRequest {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, fmt.Errorf("failed to read error response from PagerDuty (status: %d): %v", resp.StatusCode, err)
}
level.Debug(n.logger).Log("msg", "Received error response from PagerDuty", "incident", key, "code", resp.StatusCode, "body", string(body))
}
return n.retryV2(resp.StatusCode)
return n.retryV2(resp)
}
// Notify implements the Notifier interface.
@ -415,32 +406,43 @@ func (n *PagerDuty) Notify(ctx context.Context, as ...*types.Alert) (bool, error
return n.notifyV2(ctx, c, eventType, key, data, details, as...)
}
func pagerDutyErr(status int, body io.Reader) error {
// See https://v2.developer.pagerduty.com/docs/trigger-events for the v1 events API.
// See https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 for the v2 events API.
type pagerDutyResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Errors []string `json:"errors"`
}
if status == http.StatusBadRequest && body != nil {
var r pagerDutyResponse
if err := json.NewDecoder(body).Decode(&r); err == nil {
return fmt.Errorf("%s: %s", r.Message, strings.Join(r.Errors, ","))
}
}
return fmt.Errorf("unexpected status code: %v", status)
}
func (n *PagerDuty) retryV1(resp *http.Response) (bool, error) {
// Retrying can solve the issue on 403 (rate limiting) and 5xx response codes.
// 2xx response codes indicate a successful request.
// https://v2.developer.pagerduty.com/docs/trigger-events
statusCode := resp.StatusCode
if statusCode == 400 && resp.Body != nil {
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, fmt.Errorf("unexpected status code %v : problem reading response: %v", statusCode, err)
}
return false, fmt.Errorf("bad request (status code %v): %v", statusCode, string(bs))
}
if statusCode/100 != 2 {
return (statusCode == 403 || statusCode/100 == 5), fmt.Errorf("unexpected status code %v", statusCode)
return (statusCode == http.StatusForbidden || statusCode/100 == 5), pagerDutyErr(statusCode, resp.Body)
}
return false, nil
}
func (n *PagerDuty) retryV2(statusCode int) (bool, error) {
func (n *PagerDuty) retryV2(resp *http.Response) (bool, error) {
// Retrying can solve the issue on 429 (rate limiting) and 5xx response codes.
// 2xx response codes indicate a successful request.
// https://v2.developer.pagerduty.com/docs/events-api-v2#api-response-codes--retry-logic
statusCode := resp.StatusCode
if statusCode/100 != 2 {
return (statusCode == 429 || statusCode/100 == 5), fmt.Errorf("unexpected status code %v", statusCode)
return (statusCode == http.StatusTooManyRequests || statusCode/100 == 5), pagerDutyErr(statusCode, resp.Body)
}
return false, nil

View File

@ -14,9 +14,11 @@
package notify
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
@ -62,11 +64,56 @@ func TestPagerDutyRetryV2(t *testing.T) {
retryCodes := append(defaultRetryCodes(), http.StatusTooManyRequests)
for statusCode, expected := range retryTests(retryCodes) {
actual, _ := notifier.retryV2(statusCode)
resp := &http.Response{
StatusCode: statusCode,
}
actual, _ := notifier.retryV2(resp)
require.Equal(t, expected, actual, fmt.Sprintf("retryv2 - error on status %d", statusCode))
}
}
func TestPagerDutyErr(t *testing.T) {
for _, tc := range []struct {
status int
body io.Reader
exp string
}{
{
status: http.StatusBadRequest,
body: bytes.NewBuffer([]byte(
`{"status":"invalid event","message":"Event object is invalid","errors":["Length of 'routing_key' is incorrect (should be 32 characters)"]}`,
)),
exp: "Length of 'routing_key' is incorrect",
},
{
status: http.StatusBadRequest,
body: bytes.NewBuffer([]byte(`{"status"}`)),
exp: "unexpected status code: 400",
},
{
status: http.StatusBadRequest,
body: nil,
exp: "unexpected status code: 400",
},
{
status: http.StatusTooManyRequests,
body: bytes.NewBuffer([]byte("")),
exp: "unexpected status code: 429",
},
} {
tc := tc
t.Run("", func(t *testing.T) {
err := pagerDutyErr(tc.status, tc.body)
require.Contains(t, err.Error(), tc.exp)
})
}
}
func TestSlackRetry(t *testing.T) {
notifier := new(Slack)
for statusCode, expected := range retryTests(defaultRetryCodes()) {