notifier: Allow swapping out HTTP Doer

We need to be able to modify the HTTP POST in Weave Cortex to add
multitenancy information to a notification. Since we only really need a
special header in the end, the other option would be to just allow
passing in headers to the notifier. But swapping out the whole Doer is
more general and allows others to swap out the network-talky bits of the
notifier for their own use. Doing this via contexts here wouldn't work
well, due to the decoupled flow of data in the notifier.

There was no existing interface containing the ctxhttp.Post() or
ctxhttp.Do() methods, so I settled on just using Do() as a swappable
function directly (and with a more minimal signature than Post).
This commit is contained in:
Julius Volz 2017-02-27 20:31:16 +01:00
parent 1ab893c6ec
commit f152ac5e23
2 changed files with 46 additions and 1 deletions

View File

@ -77,12 +77,18 @@ type Options struct {
QueueCapacity int QueueCapacity int
ExternalLabels model.LabelSet ExternalLabels model.LabelSet
RelabelConfigs []*config.RelabelConfig RelabelConfigs []*config.RelabelConfig
// Used for sending HTTP requests to the Alertmanager.
Do func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error)
} }
// New constructs a new Notifier. // New constructs a new Notifier.
func New(o *Options) *Notifier { func New(o *Options) *Notifier {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
if o.Do == nil {
o.Do = ctxhttp.Do
}
return &Notifier{ return &Notifier{
queue: make(model.Alerts, 0, o.QueueCapacity), queue: make(model.Alerts, 0, o.QueueCapacity),
ctx: ctx, ctx: ctx,
@ -351,7 +357,12 @@ func (n *Notifier) sendAll(alerts ...*model.Alert) bool {
} }
func (n *Notifier) sendOne(ctx context.Context, c *http.Client, url string, b []byte) error { func (n *Notifier) sendOne(ctx context.Context, c *http.Client, url string, b []byte) error {
resp, err := ctxhttp.Post(ctx, c, url, contentTypeJSON, bytes.NewReader(b)) req, err := http.NewRequest("POST", url, bytes.NewReader(b))
if err != nil {
return err
}
req.Header.Set("Content-Type", contentTypeJSON)
resp, err := n.opts.Do(ctx, c, req)
if err != nil { if err != nil {
return err return err
} }

View File

@ -16,12 +16,15 @@ package notifier
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"golang.org/x/net/context"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/config"
) )
@ -191,6 +194,37 @@ func TestHandlerSendAll(t *testing.T) {
} }
} }
func TestCustomDo(t *testing.T) {
const testURL = "http://testurl.com/"
const testBody = "testbody"
var received bool
h := New(&Options{
Do: func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
received = true
body, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("Unable to read request body: %v", err)
}
if string(body) != testBody {
t.Fatalf("Unexpected body; want %v, got %v", testBody, string(body))
}
if req.URL.String() != testURL {
t.Fatalf("Unexpected URL; want %v, got %v", testURL, req.URL.String())
}
return &http.Response{
Body: ioutil.NopCloser(nil),
}, nil
},
})
h.sendOne(context.Background(), nil, testURL, []byte(testBody))
if !received {
t.Fatal("Expected to receive an alert, but didn't")
}
}
func TestExternalLabels(t *testing.T) { func TestExternalLabels(t *testing.T) {
h := New(&Options{ h := New(&Options{
QueueCapacity: 3 * maxBatchSize, QueueCapacity: 3 * maxBatchSize,