diff --git a/silence/silence_test.go b/silence/silence_test.go index 71dcbb2b..880a5b2e 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -716,7 +716,7 @@ func TestSilenceCanUpdate(t *testing.T) { } func TestSilenceExpire(t *testing.T) { - s, err := New(Options{}) + s, err := New(Options{Retention: time.Hour}) require.NoError(t, err) now := time.Now() @@ -752,10 +752,14 @@ func TestSilenceExpire(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, count) - require.NoError(t, s.expire("pending")) - require.NoError(t, s.expire("active")) + count, err = s.CountState(types.SilenceStateExpired) + require.NoError(t, err) + require.Equal(t, 1, count) - err = s.expire("expired") + 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") @@ -769,10 +773,17 @@ func TestSilenceExpire(t *testing.T) { UpdatedAt: now, }, sil) + // Let time pass... + s.now = func() time.Time { return now.Add(time.Second) } + count, err = s.CountState(types.SilenceStatePending) require.NoError(t, err) require.Equal(t, 0, count) + count, err = s.CountState(types.SilenceStateExpired) + require.NoError(t, err) + require.Equal(t, 3, count) + // Expiring a pending Silence should make the API return the // SilenceStateExpired Silence state. silenceState := types.CalcSilenceState(sil.StartsAt, sil.EndsAt) @@ -797,6 +808,72 @@ func TestSilenceExpire(t *testing.T) { EndsAt: now.Add(-time.Minute), UpdatedAt: now.Add(-time.Hour), }, sil) + +} + +// TestSilenceExpireWithZeroRetention covers the problem that, with zero +// retention time, a silence explicitly set to expired will also immediately +// expire from the silence storage. +func TestSilenceExpireWithZeroRetention(t *testing.T) { + s, err := New(Options{}) + require.NoError(t, err) + + now := time.Now() + // But don't set s.now here because we need a changing time to + // demonstrate expiration from the silence storage. + + m := &pb.Matcher{Type: pb.Matcher_EQUAL, Name: "a", Pattern: "b"} + + s.st = state{ + "pending": &pb.MeshSilence{Silence: &pb.Silence{ + Id: "pending", + Matchers: []*pb.Matcher{m}, + StartsAt: now.Add(time.Minute), + EndsAt: now.Add(time.Hour), + UpdatedAt: now.Add(-time.Hour), + }}, + "active": &pb.MeshSilence{Silence: &pb.Silence{ + Id: "active", + Matchers: []*pb.Matcher{m}, + StartsAt: now.Add(-time.Minute), + EndsAt: now.Add(time.Hour), + UpdatedAt: now.Add(-time.Hour), + }}, + "expired": &pb.MeshSilence{Silence: &pb.Silence{ + Id: "expired", + Matchers: []*pb.Matcher{m}, + StartsAt: now.Add(-time.Hour), + EndsAt: now.Add(-time.Minute), + UpdatedAt: now.Add(-time.Hour), + }}, + } + + count, err := s.CountState(types.SilenceStatePending) + require.NoError(t, err) + require.Equal(t, 1, count) + + count, err = s.CountState(types.SilenceStateExpired) + require.NoError(t, err) + require.Equal(t, 1, count) + + 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") + + _, err = s.QueryOne(QIDs("pending")) + require.Error(t, err) + require.Contains(t, err.Error(), "silence not found") // It is expired from the storage, too. + + count, err = s.CountState(types.SilenceStatePending) + require.NoError(t, err) + require.Equal(t, 0, count) + + count, err = s.CountState(types.SilenceStateExpired) + require.NoError(t, err) + require.Equal(t, 1, count) // As the two explicitly expired silences have immediately expired. } func TestValidateMatcher(t *testing.T) {