diff --git a/api/v2/api_test.go b/api/v2/api_test.go index 5ff72fee66..2527c96d47 100644 --- a/api/v2/api_test.go +++ b/api/v2/api_test.go @@ -14,20 +14,31 @@ package v2 import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" "strconv" "testing" "time" + "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" open_api_models "github.com/prometheus/alertmanager/api/v2/models" general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general" + silence_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/pkg/labels" + "github.com/prometheus/alertmanager/silence" "github.com/prometheus/alertmanager/silence/silencepb" "github.com/prometheus/alertmanager/types" + + "github.com/go-kit/log" ) // If api.peers == nil, Alertmanager cluster feature is disabled. Make sure to @@ -72,6 +83,13 @@ var ( createdBy = "test" ) +func newSilences(t *testing.T) *silence.Silences { + silences, err := silence.New(silence.Options{}) + require.NoError(t, err) + + return silences +} + func gettableSilence(id string, state string, updatedAt string, start string, end string, ) *open_api_models.GettableSilence { @@ -131,6 +149,170 @@ func TestGetSilencesHandler(t *testing.T) { } } +func TestDeleteSilenceHandler(t *testing.T) { + now := time.Now() + silences := newSilences(t) + + m := &silencepb.Matcher{Type: silencepb.Matcher_EQUAL, Name: "a", Pattern: "b"} + + unexpiredSil := &silencepb.Silence{ + Matchers: []*silencepb.Matcher{m}, + StartsAt: now, + EndsAt: now.Add(time.Hour), + UpdatedAt: now, + } + unexpiredSid, err := silences.Set(unexpiredSil) + require.NoError(t, err) + + expiredSil := &silencepb.Silence{ + Matchers: []*silencepb.Matcher{m}, + StartsAt: now.Add(-time.Hour), + EndsAt: now.Add(time.Hour), + UpdatedAt: now, + } + expiredSid, err := silences.Set(expiredSil) + require.NoError(t, err) + require.NoError(t, silences.Expire(expiredSid)) + + for i, tc := range []struct { + sid string + expectedCode int + }{ + { + "unknownSid", + 500, + }, + { + unexpiredSid, + 200, + }, + { + expiredSid, + 200, + }, + } { + api := API{ + uptime: time.Now(), + silences: silences, + logger: log.NewNopLogger(), + } + + r, err := http.NewRequest("DELETE", "/api/v2/silence/${tc.sid}", nil) + require.NoError(t, err) + + w := httptest.NewRecorder() + p := runtime.TextProducer() + responder := api.deleteSilenceHandler(silence_ops.DeleteSilenceParams{ + SilenceID: strfmt.UUID(tc.sid), + HTTPRequest: r, + }) + responder.WriteResponse(w, p) + body, _ := ioutil.ReadAll(w.Result().Body) + + require.Equal(t, tc.expectedCode, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body))) + } +} + +func TestPostSilencesHandler(t *testing.T) { + now := time.Now() + silences := newSilences(t) + + m := &silencepb.Matcher{Type: silencepb.Matcher_EQUAL, Name: "a", Pattern: "b"} + + unexpiredSil := &silencepb.Silence{ + Matchers: []*silencepb.Matcher{m}, + StartsAt: now, + EndsAt: now.Add(time.Hour), + UpdatedAt: now, + } + unexpiredSid, err := silences.Set(unexpiredSil) + require.NoError(t, err) + + expiredSil := &silencepb.Silence{ + Matchers: []*silencepb.Matcher{m}, + StartsAt: now.Add(-time.Hour), + EndsAt: now.Add(time.Hour), + UpdatedAt: now, + } + expiredSid, err := silences.Set(expiredSil) + require.NoError(t, err) + require.NoError(t, silences.Expire(expiredSid)) + + for i, tc := range []struct { + sid string + start, end time.Time + expectedCode int + }{ + { + "unknownSid", + now.Add(time.Hour), + now.Add(time.Hour * 2), + 404, + }, + { + "", + now.Add(time.Hour), + now.Add(time.Hour * 2), + 200, + }, + { + unexpiredSid, + now.Add(time.Hour), + now.Add(time.Hour * 2), + 200, + }, + { + expiredSid, + now.Add(time.Hour), + now.Add(time.Hour * 2), + 200, + }, + } { + createdBy := "silenceCreator" + comment := "test" + matcherName := "a" + matcherValue := "b" + isRegex := false + startsAt := strfmt.DateTime(tc.start) + endsAt := strfmt.DateTime(tc.end) + + sil := open_api_models.PostableSilence{ + ID: tc.sid, + Silence: open_api_models.Silence{ + Matchers: open_api_models.Matchers{&open_api_models.Matcher{Name: &matcherName, Value: &matcherValue, IsRegex: &isRegex}}, + StartsAt: &startsAt, + EndsAt: &endsAt, + CreatedBy: &createdBy, + Comment: &comment, + }, + } + b, err := json.Marshal(&sil) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + + api := API{ + uptime: time.Now(), + silences: silences, + logger: log.NewNopLogger(), + } + + r, err := http.NewRequest("POST", "/api/v2/silence/${tc.sid}", bytes.NewReader(b)) + require.NoError(t, err) + + w := httptest.NewRecorder() + p := runtime.TextProducer() + responder := api.postSilencesHandler(silence_ops.PostSilencesParams{ + HTTPRequest: r, + Silence: &sil, + }) + responder.WriteResponse(w, p) + body, _ := ioutil.ReadAll(w.Result().Body) + + require.Equal(t, tc.expectedCode, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body))) + } +} + func createSilenceMatcher(name string, pattern string, matcherType silencepb.Matcher_Type) *silencepb.Matcher { return &silencepb.Matcher{ Name: name, diff --git a/silence/silence.go b/silence/silence.go index 6f3b177dd9..b2bebe93f8 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -612,6 +612,8 @@ func (s *Silences) Expire(id string) error { } // Expire the silence with the given ID immediately. +// It is idempotent, nil is returned if the silence already expired before it is GC'd. +// If the silence is not found an error is returned. func (s *Silences) expire(id string) error { sil, ok := s.getSilence(id) if !ok { @@ -622,7 +624,7 @@ func (s *Silences) expire(id string) error { switch getState(sil, now) { case types.SilenceStateExpired: - return errors.Errorf("silence %s already expired", id) + return nil case types.SilenceStateActive: sil.EndsAt = now case types.SilenceStatePending: diff --git a/silence/silence_test.go b/silence/silence_test.go index c39b7804cf..2f116b5b52 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -835,9 +835,7 @@ func TestSilenceExpire(t *testing.T) { require.NoError(t, s.Expire("pending")) require.NoError(t, s.Expire("active")) - err = s.Expire("expired") - require.Error(t, err) - require.Contains(t, err.Error(), "already expired") + require.NoError(t, s.Expire("expired")) sil, err := s.QueryOne(QIDs("pending")) require.NoError(t, err) @@ -935,9 +933,7 @@ func TestSilenceExpireWithZeroRetention(t *testing.T) { require.NoError(t, s.Expire("pending")) require.NoError(t, s.Expire("active")) - err = s.Expire("expired") - require.Error(t, err) - require.Contains(t, err.Error(), "already expired") + require.NoError(t, s.Expire("expired")) _, err = s.QueryOne(QIDs("pending")) require.NoError(t, err)