api: support zero StartsAt for alerts (#1238)

When the API receives alerts where StartsAt is zero, it updates the
value to EndsAt (if not zero itself) or "now". This ensures that the
alert validation will not fail since StartsAt has to be less than or
equal to EndsAt.
This commit is contained in:
pasquier-s 2018-02-13 16:26:34 +01:00 committed by stuart nelson
parent 32f90a02ca
commit 382a0d8089
2 changed files with 96 additions and 11 deletions

View File

@ -103,6 +103,10 @@ func New(
peer *cluster.Peer,
l log.Logger,
) *API {
if l == nil {
l = log.NewNopLogger()
}
return &API{
alerts: alerts,
silences: silences,
@ -466,7 +470,11 @@ func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...*
// Ensure StartsAt is set.
if alert.StartsAt.IsZero() {
alert.StartsAt = now
if alert.EndsAt.IsZero() {
alert.StartsAt = now
} else {
alert.StartsAt = alert.EndsAt
}
}
// If no end time is defined, set a timeout after which an alert
// is marked resolved if it is not updated.

View File

@ -1,16 +1,93 @@
package api
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"time"
"github.com/prometheus/alertmanager/dispatch"
"github.com/prometheus/alertmanager/provider"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/stretchr/testify/require"
)
type fakeAlerts struct {
putWithErr bool
}
func newEmptyIterator() provider.AlertIterator {
return provider.NewAlertIterator(make(chan *types.Alert), make(chan struct{}), nil)
}
func (f *fakeAlerts) Subscribe() provider.AlertIterator { return newEmptyIterator() }
func (f *fakeAlerts) GetPending() provider.AlertIterator { return newEmptyIterator() }
func (f *fakeAlerts) Get(model.Fingerprint) (*types.Alert, error) { return nil, nil }
func (f *fakeAlerts) Put(alerts ...*types.Alert) error {
if f.putWithErr {
return errors.New("Error occured")
}
return nil
}
func groupAlerts([]*labels.Matcher) dispatch.AlertOverview { return dispatch.AlertOverview{} }
func getAlertStatus(model.Fingerprint) types.AlertStatus { return types.AlertStatus{} }
func TestAddAlerts(t *testing.T) {
now := func(offset int) time.Time {
return time.Now().Add(time.Duration(offset) * time.Second)
}
for i, tc := range []struct {
start, end time.Time
err bool
code int
}{
{time.Time{}, time.Time{}, false, 200},
{now(0), time.Time{}, false, 200},
{time.Time{}, now(-1), false, 200},
{time.Time{}, now(0), false, 200},
{time.Time{}, now(1), false, 200},
{now(-2), now(-1), false, 200},
{now(1), now(2), false, 200},
{now(1), now(0), false, 400},
{now(0), time.Time{}, true, 500},
} {
alerts := []model.Alert{{
StartsAt: tc.start,
EndsAt: tc.end,
Labels: model.LabelSet{"label1": "test1"},
Annotations: model.LabelSet{"annotation1": "some text"},
}}
b, err := json.Marshal(&alerts)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
api := New(&fakeAlerts{putWithErr: tc.err}, nil, groupAlerts, getAlertStatus, nil, nil)
r, err := http.NewRequest("POST", "/api/v1/alerts", bytes.NewReader(b))
w := httptest.NewRecorder()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
api.addAlerts(w, r)
res := w.Result()
body, _ := ioutil.ReadAll(res.Body)
require.Equal(t, tc.code, w.Code, fmt.Sprintf("test case: %d, StartsAt %v, EndsAt %v, Response: %s", i, tc.start, tc.end, string(body)))
}
}
func TestAlertFiltering(t *testing.T) {
type test struct {
alert *model.Alert
@ -21,7 +98,7 @@ func TestAlertFiltering(t *testing.T) {
// Equal
equal, err := labels.NewMatcher(labels.MatchEqual, "label1", "test1")
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
tests := []test{
@ -39,7 +116,7 @@ func TestAlertFiltering(t *testing.T) {
// Not Equal
notEqual, err := labels.NewMatcher(labels.MatchNotEqual, "label1", "test1")
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
tests = []test{
@ -57,7 +134,7 @@ func TestAlertFiltering(t *testing.T) {
// Regexp Equal
regexpEqual, err := labels.NewMatcher(labels.MatchRegexp, "label1", "tes.*")
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
tests = []test{
@ -75,7 +152,7 @@ func TestAlertFiltering(t *testing.T) {
// Regexp Not Equal
regexpNotEqual, err := labels.NewMatcher(labels.MatchNotRegexp, "label1", "tes.*")
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
tests = []test{
@ -101,7 +178,7 @@ func TestSilenceFiltering(t *testing.T) {
// Equal
equal, err := labels.NewMatcher(labels.MatchEqual, "label1", "test1")
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
tests := []test{
@ -131,7 +208,7 @@ func TestSilenceFiltering(t *testing.T) {
// Not Equal
notEqual, err := labels.NewMatcher(labels.MatchNotEqual, "label1", "test1")
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
tests = []test{
@ -161,7 +238,7 @@ func TestSilenceFiltering(t *testing.T) {
// Regexp Equal
regexpEqual, err := labels.NewMatcher(labels.MatchRegexp, "label1", "tes.*")
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
tests = []test{
@ -191,7 +268,7 @@ func TestSilenceFiltering(t *testing.T) {
// Regexp Not Equal
regexpNotEqual, err := labels.NewMatcher(labels.MatchNotRegexp, "label1", "tes.*")
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
tests = []test{
@ -224,13 +301,13 @@ func TestReceiversMatchFilter(t *testing.T) {
filter, err := regexp.Compile(fmt.Sprintf("^(?:%s)$", "hip.*"))
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
require.True(t, receiversMatchFilter(receivers, filter))
filter, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", "hip"))
if err != nil {
t.Error("Unexpected error %v", err)
t.Errorf("Unexpected error %v", err)
}
require.False(t, receiversMatchFilter(receivers, filter))
}