Return no error when deleting expired silence (#2817)

* Changed Silences.expire(id) to not return error for already expired silence

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Added comment explaining idempotency change for Silences.expire()

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Trigger build

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Trigger build

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Fixed typo in comment

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Trigger build

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Trigger build

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Fixed another typo in comment

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Promoted comment to function-level

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Added API v2 test for DeleteSilence, PostSilence

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Fixed lint errors

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Trigger build

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Trigger build

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>

* Trigger build

Signed-off-by: Soon-Ping Phang <soonping@amazon.com>
This commit is contained in:
Soon-Ping 2022-02-22 04:34:21 -08:00 committed by GitHub
parent 4030e3670b
commit a2d18c93de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 187 additions and 7 deletions

View File

@ -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,

View File

@ -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:

View File

@ -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)