2015-09-29 18:45:38 +00:00
|
|
|
package test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2015-09-29 20:40:44 +00:00
|
|
|
"fmt"
|
2015-09-29 18:45:38 +00:00
|
|
|
"io/ioutil"
|
2015-09-30 15:33:49 +00:00
|
|
|
"net"
|
2015-09-29 18:45:38 +00:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
"github.com/prometheus/client_golang/api/alertmanager"
|
2015-10-01 13:46:39 +00:00
|
|
|
"github.com/prometheus/common/model"
|
2015-10-01 18:58:46 +00:00
|
|
|
"golang.org/x/net/context"
|
2015-09-29 18:45:38 +00:00
|
|
|
)
|
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
type AcceptanceTest struct {
|
2015-09-29 18:45:38 +00:00
|
|
|
*testing.T
|
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
opts *AcceptanceOpts
|
2015-09-29 18:45:38 +00:00
|
|
|
|
2015-09-29 20:40:44 +00:00
|
|
|
ams []*Alertmanager
|
2015-09-30 14:13:00 +00:00
|
|
|
collectors []*Collector
|
2015-09-29 18:45:38 +00:00
|
|
|
}
|
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
type AcceptanceOpts struct {
|
|
|
|
baseTime time.Time
|
|
|
|
Tolerance time.Duration
|
2015-09-30 12:54:54 +00:00
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
Config string
|
2015-09-29 18:45:38 +00:00
|
|
|
}
|
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
func (opts *AcceptanceOpts) expandTime(rel float64) time.Time {
|
2015-09-30 13:35:52 +00:00
|
|
|
return opts.baseTime.Add(time.Duration(rel * float64(time.Second)))
|
|
|
|
}
|
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
func (opts *AcceptanceOpts) relativeTime(act time.Time) float64 {
|
2015-09-30 13:35:52 +00:00
|
|
|
return float64(act.Sub(opts.baseTime)) / float64(time.Second)
|
|
|
|
}
|
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
func NewAcceptanceTest(t *testing.T, opts *AcceptanceOpts) *AcceptanceTest {
|
|
|
|
test := &AcceptanceTest{
|
2015-09-29 20:40:44 +00:00
|
|
|
T: t,
|
|
|
|
opts: opts,
|
2015-09-29 18:45:38 +00:00
|
|
|
}
|
2015-09-29 20:40:44 +00:00
|
|
|
opts.baseTime = time.Now()
|
|
|
|
|
|
|
|
return test
|
2015-09-29 18:45:38 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 19:28:18 +00:00
|
|
|
var freeAdresses []string
|
|
|
|
|
2015-09-30 15:33:49 +00:00
|
|
|
func freeAddress() string {
|
2015-10-01 19:28:18 +00:00
|
|
|
if len(freeAdresses) == 0 {
|
|
|
|
for i := 0; i < 100; i++ {
|
|
|
|
// Let the OS allocate a free address, close it and hope
|
|
|
|
// it is still free when starting Alertmanager.
|
|
|
|
l, err := net.Listen("tcp", ":0")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer l.Close()
|
|
|
|
|
|
|
|
freeAdresses = append(freeAdresses, l.Addr().String())
|
|
|
|
}
|
2015-09-30 15:33:49 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 19:28:18 +00:00
|
|
|
next := freeAdresses[0]
|
|
|
|
freeAdresses = freeAdresses[1:]
|
|
|
|
|
|
|
|
return next
|
2015-09-30 15:33:49 +00:00
|
|
|
}
|
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
// Alertmanager returns a new structure that allows starting an instance
|
2015-09-29 20:40:44 +00:00
|
|
|
// of Alertmanager on a random port.
|
2015-09-30 14:13:00 +00:00
|
|
|
func (t *AcceptanceTest) Alertmanager() *Alertmanager {
|
2015-09-29 20:40:44 +00:00
|
|
|
am := &Alertmanager{
|
2015-09-30 15:45:37 +00:00
|
|
|
t: t.T,
|
|
|
|
opts: t.opts,
|
|
|
|
actions: map[float64][]func(){},
|
2015-09-29 20:40:44 +00:00
|
|
|
}
|
2015-09-29 18:45:38 +00:00
|
|
|
|
|
|
|
cf, err := ioutil.TempFile("", "am_config")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-09-29 20:40:44 +00:00
|
|
|
am.confFile = cf
|
2015-09-29 18:45:38 +00:00
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
if _, err := cf.WriteString(t.opts.Config); err != nil {
|
2015-09-30 12:54:54 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
am.addr = freeAddress()
|
|
|
|
|
2015-10-01 19:28:18 +00:00
|
|
|
t.Logf("AM on %s", am.addr)
|
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
client, err := alertmanager.New(alertmanager.Config{
|
|
|
|
Address: fmt.Sprintf("http://%s", am.addr),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
am.client = client
|
2015-09-30 15:33:49 +00:00
|
|
|
|
|
|
|
am.cmd = exec.Command("../../alertmanager",
|
|
|
|
"-config.file", cf.Name(),
|
|
|
|
"-log.level", "debug",
|
2015-10-01 18:58:46 +00:00
|
|
|
"-web.listen-address", am.addr,
|
2015-09-30 15:33:49 +00:00
|
|
|
)
|
2015-09-29 20:40:44 +00:00
|
|
|
|
|
|
|
var outb, errb bytes.Buffer
|
|
|
|
am.cmd.Stdout = &outb
|
|
|
|
am.cmd.Stderr = &errb
|
|
|
|
|
|
|
|
t.ams = append(t.ams, am)
|
|
|
|
|
|
|
|
return am
|
|
|
|
}
|
|
|
|
|
2015-09-30 14:13:00 +00:00
|
|
|
func (t *AcceptanceTest) Collector(name string) *Collector {
|
|
|
|
co := &Collector{
|
2015-09-29 20:40:44 +00:00
|
|
|
t: t.T,
|
2015-09-30 13:02:07 +00:00
|
|
|
name: name,
|
2015-09-29 20:40:44 +00:00
|
|
|
opts: t.opts,
|
2015-10-01 13:46:39 +00:00
|
|
|
collected: map[float64][]model.Alerts{},
|
|
|
|
exepected: map[Interval][]model.Alerts{},
|
2015-09-29 20:40:44 +00:00
|
|
|
}
|
|
|
|
t.collectors = append(t.collectors, co)
|
|
|
|
|
|
|
|
return co
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run starts all Alertmanagers and runs queries against them. It then checks
|
|
|
|
// whether all expected notifications have arrived at the expected destination.
|
2015-09-30 14:13:00 +00:00
|
|
|
func (t *AcceptanceTest) Run() {
|
2015-09-29 20:40:44 +00:00
|
|
|
for _, am := range t.ams {
|
|
|
|
am.start()
|
|
|
|
defer am.kill()
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, am := range t.ams {
|
2015-09-30 15:45:37 +00:00
|
|
|
go am.runActions()
|
2015-09-29 20:40:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var latest float64
|
|
|
|
for _, coll := range t.collectors {
|
|
|
|
if l := coll.latest(); l > latest {
|
|
|
|
latest = l
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-30 13:35:52 +00:00
|
|
|
deadline := t.opts.expandTime(latest)
|
2015-09-29 20:40:44 +00:00
|
|
|
time.Sleep(deadline.Sub(time.Now()))
|
|
|
|
|
|
|
|
for _, coll := range t.collectors {
|
|
|
|
report := coll.check()
|
|
|
|
t.Log(report)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, am := range t.ams {
|
2015-09-30 13:02:07 +00:00
|
|
|
t.Logf("stdout:\n%v", am.cmd.Stdout)
|
|
|
|
t.Logf("stderr:\n%v", am.cmd.Stderr)
|
2015-09-29 18:45:38 +00:00
|
|
|
}
|
2015-09-29 20:40:44 +00:00
|
|
|
}
|
2015-09-29 18:45:38 +00:00
|
|
|
|
2015-09-29 20:40:44 +00:00
|
|
|
// Alertmanager encapsulates an Alertmanager process and allows
|
|
|
|
// declaring alerts being pushed to it at fixed points in time.
|
|
|
|
type Alertmanager struct {
|
|
|
|
t *testing.T
|
2015-09-30 14:13:00 +00:00
|
|
|
opts *AcceptanceOpts
|
2015-09-29 18:45:38 +00:00
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
addr string
|
|
|
|
client alertmanager.Client
|
|
|
|
cmd *exec.Cmd
|
2015-09-29 20:40:44 +00:00
|
|
|
confFile *os.File
|
2015-09-29 18:45:38 +00:00
|
|
|
|
2015-09-30 15:45:37 +00:00
|
|
|
actions map[float64][]func()
|
2015-09-29 20:40:44 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
// Push declares alerts that are to be pushed to the Alertmanager
|
2015-09-29 20:40:44 +00:00
|
|
|
// server at a relative point in time.
|
2015-09-30 14:13:00 +00:00
|
|
|
func (am *Alertmanager) Push(at float64, alerts ...*TestAlert) {
|
2015-10-01 13:46:39 +00:00
|
|
|
var nas model.Alerts
|
2015-09-29 20:40:44 +00:00
|
|
|
for _, a := range alerts {
|
2015-09-30 13:35:52 +00:00
|
|
|
nas = append(nas, a.nativeAlert(am.opts))
|
2015-09-29 18:45:38 +00:00
|
|
|
}
|
2015-09-30 15:45:37 +00:00
|
|
|
|
|
|
|
am.Do(at, func() {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := json.NewEncoder(&buf).Encode(nas); err != nil {
|
|
|
|
am.t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
resp, err := http.Post(fmt.Sprintf("http://%s/api/alerts", am.addr), "application/json", &buf)
|
2015-09-30 15:45:37 +00:00
|
|
|
if err != nil {
|
|
|
|
am.t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
resp.Body.Close()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
// SetSilence updates or creates the given Silence.
|
|
|
|
func (am *Alertmanager) SetSilence(at float64, sil *TestSilence) {
|
|
|
|
silences := alertmanager.NewSilenceAPI(am.client)
|
2015-10-01 13:46:39 +00:00
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
am.Do(at, func() {
|
|
|
|
sid, err := silences.Set(context.Background(), sil.nativeSilence(am.opts))
|
|
|
|
if err != nil {
|
|
|
|
am.t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sil.ID = sid
|
|
|
|
})
|
2015-10-01 13:46:39 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
// DelSilence deletes the silence with the sid at the given time.
|
|
|
|
func (am *Alertmanager) DelSilence(at float64, sil *TestSilence) {
|
|
|
|
silences := alertmanager.NewSilenceAPI(am.client)
|
2015-10-01 13:46:39 +00:00
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
am.Do(at, func() {
|
|
|
|
if err := silences.Del(context.Background(), sil.ID); err != nil {
|
|
|
|
am.t.Error(err)
|
|
|
|
}
|
|
|
|
})
|
2015-10-01 13:46:39 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 18:58:46 +00:00
|
|
|
// Do sets the given function to be executed at the given time.
|
2015-09-30 15:45:37 +00:00
|
|
|
func (am *Alertmanager) Do(at float64, f func()) {
|
|
|
|
am.actions[at] = append(am.actions[at], f)
|
2015-09-29 20:40:44 +00:00
|
|
|
}
|
2015-09-29 18:45:38 +00:00
|
|
|
|
2015-09-29 20:40:44 +00:00
|
|
|
// start the alertmanager and wait until it is ready to receive.
|
|
|
|
func (am *Alertmanager) start() {
|
|
|
|
if err := am.cmd.Start(); err != nil {
|
|
|
|
am.t.Fatalf("Starting alertmanager failed: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
}
|
|
|
|
|
2015-09-30 15:45:37 +00:00
|
|
|
// runActions performs the stored actions at the defined times.
|
|
|
|
func (am *Alertmanager) runActions() {
|
2015-09-29 18:45:38 +00:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
2015-09-30 15:45:37 +00:00
|
|
|
for at, fs := range am.actions {
|
2015-09-30 13:35:52 +00:00
|
|
|
ts := am.opts.expandTime(at)
|
2015-09-30 15:45:37 +00:00
|
|
|
wg.Add(len(fs))
|
|
|
|
|
|
|
|
for _, f := range fs {
|
|
|
|
go func() {
|
|
|
|
time.Sleep(ts.Sub(time.Now()))
|
|
|
|
f()
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
}
|
2015-09-29 18:45:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
2015-09-29 20:40:44 +00:00
|
|
|
// kill the underlying Alertmanager process and remove intermediate data.
|
|
|
|
func (am *Alertmanager) kill() {
|
|
|
|
am.cmd.Process.Kill()
|
|
|
|
os.RemoveAll(am.confFile.Name())
|
2015-09-29 18:45:38 +00:00
|
|
|
}
|