1
2
3
4
5
6
7
8
9
10
11
12
13
14 package silence
15
16 import (
17 "bytes"
18 "fmt"
19 "os"
20 "runtime"
21 "sort"
22 "testing"
23 "time"
24
25 "github.com/benbjohnson/clock"
26 "github.com/go-kit/log"
27 "github.com/matttproud/golang_protobuf_extensions/pbutil"
28 "github.com/prometheus/client_golang/prometheus"
29 "github.com/prometheus/common/model"
30 "github.com/stretchr/testify/require"
31
32 pb "github.com/prometheus/alertmanager/silence/silencepb"
33 "github.com/prometheus/alertmanager/types"
34 )
35
36 func checkErr(t *testing.T, expected string, got error) {
37 t.Helper()
38
39 if expected == "" {
40 require.NoError(t, got)
41 return
42 }
43
44 if got == nil {
45 t.Errorf("expected error containing %q but got none", expected)
46 return
47 }
48
49 require.Contains(t, got.Error(), expected)
50 }
51
52 func TestOptionsValidate(t *testing.T) {
53 cases := []struct {
54 options *Options
55 err string
56 }{
57 {
58 options: &Options{
59 SnapshotReader: &bytes.Buffer{},
60 },
61 },
62 {
63 options: &Options{
64 SnapshotFile: "test.bkp",
65 },
66 },
67 {
68 options: &Options{
69 SnapshotFile: "test bkp",
70 SnapshotReader: &bytes.Buffer{},
71 },
72 err: "only one of SnapshotFile and SnapshotReader must be set",
73 },
74 }
75
76 for _, c := range cases {
77 checkErr(t, c.err, c.options.validate())
78 }
79 }
80
81 func TestSilencesGC(t *testing.T) {
82 s, err := New(Options{})
83 require.NoError(t, err)
84
85 s.clock = clock.NewMock()
86 now := s.nowUTC()
87
88 newSilence := func(exp time.Time) *pb.MeshSilence {
89 return &pb.MeshSilence{ExpiresAt: exp}
90 }
91 s.st = state{
92 "1": newSilence(now),
93 "2": newSilence(now.Add(-time.Second)),
94 "3": newSilence(now.Add(time.Second)),
95 }
96 want := state{
97 "3": newSilence(now.Add(time.Second)),
98 }
99
100 n, err := s.GC()
101 require.NoError(t, err)
102 require.Equal(t, 2, n)
103 require.Equal(t, want, s.st)
104 }
105
106 func TestSilencesSnapshot(t *testing.T) {
107
108 now := clock.NewMock().Now().UTC()
109
110 cases := []struct {
111 entries []*pb.MeshSilence
112 }{
113 {
114 entries: []*pb.MeshSilence{
115 {
116 Silence: &pb.Silence{
117 Id: "3be80475-e219-4ee7-b6fc-4b65114e362f",
118 Matchers: []*pb.Matcher{
119 {Name: "label1", Pattern: "val1", Type: pb.Matcher_EQUAL},
120 {Name: "label2", Pattern: "val.+", Type: pb.Matcher_REGEXP},
121 },
122 StartsAt: now,
123 EndsAt: now,
124 UpdatedAt: now,
125 },
126 ExpiresAt: now,
127 },
128 {
129 Silence: &pb.Silence{
130 Id: "3dfb2528-59ce-41eb-b465-f875a4e744a4",
131 Matchers: []*pb.Matcher{
132 {Name: "label1", Pattern: "val1", Type: pb.Matcher_NOT_EQUAL},
133 {Name: "label2", Pattern: "val.+", Type: pb.Matcher_NOT_REGEXP},
134 },
135 StartsAt: now,
136 EndsAt: now,
137 UpdatedAt: now,
138 },
139 ExpiresAt: now,
140 },
141 {
142 Silence: &pb.Silence{
143 Id: "4b1e760d-182c-4980-b873-c1a6827c9817",
144 Matchers: []*pb.Matcher{
145 {Name: "label1", Pattern: "val1", Type: pb.Matcher_EQUAL},
146 },
147 StartsAt: now.Add(time.Hour),
148 EndsAt: now.Add(2 * time.Hour),
149 UpdatedAt: now,
150 },
151 ExpiresAt: now.Add(24 * time.Hour),
152 },
153 },
154 },
155 }
156
157 for _, c := range cases {
158 f, err := os.CreateTemp("", "snapshot")
159 require.NoError(t, err, "creating temp file failed")
160
161 s1 := &Silences{st: state{}, metrics: newMetrics(nil, nil)}
162
163 for _, e := range c.entries {
164 s1.st[e.Silence.Id] = e
165 }
166 _, err = s1.Snapshot(f)
167 require.NoError(t, err, "creating snapshot failed")
168
169 require.NoError(t, f.Close(), "closing snapshot file failed")
170
171 f, err = os.Open(f.Name())
172 require.NoError(t, err, "opening snapshot file failed")
173
174
175 s2 := &Silences{mc: matcherCache{}, st: state{}}
176 err = s2.loadSnapshot(f)
177 require.NoError(t, err, "error loading snapshot")
178 require.Equal(t, s1.st, s2.st, "state after loading snapshot did not match snapshotted state")
179
180 require.NoError(t, f.Close(), "closing snapshot file failed")
181 }
182 }
183
184
185 func TestSilences_Maintenance_DefaultMaintenanceFuncDoesntCrash(t *testing.T) {
186 f, err := os.CreateTemp("", "snapshot")
187 require.NoError(t, err, "creating temp file failed")
188 clock := clock.NewMock()
189 s := &Silences{st: state{}, logger: log.NewNopLogger(), clock: clock, metrics: newMetrics(nil, nil)}
190 stopc := make(chan struct{})
191
192 done := make(chan struct{})
193 go func() {
194 s.Maintenance(100*time.Millisecond, f.Name(), stopc, nil)
195 close(done)
196 }()
197 runtime.Gosched()
198
199 clock.Add(100 * time.Millisecond)
200 close(stopc)
201
202 <-done
203 }
204
205 func TestSilences_Maintenance_SupportsCustomCallback(t *testing.T) {
206 f, err := os.CreateTemp("", "snapshot")
207 require.NoError(t, err, "creating temp file failed")
208 clock := clock.NewMock()
209 s := &Silences{st: state{}, logger: log.NewNopLogger(), clock: clock, metrics: newMetrics(nil, nil)}
210 stopc := make(chan struct{})
211
212 called := make(chan struct{}, 5)
213 go func() {
214 s.Maintenance(100*time.Millisecond, f.Name(), stopc, func() (int64, error) {
215 called <- struct{}{}
216 return 0, nil
217 })
218 close(called)
219 }()
220 runtime.Gosched()
221
222 clock.Add(100 * time.Millisecond)
223
224
225 close(stopc)
226 calls := 0
227 for range called {
228 calls++
229 }
230
231 require.EqualValues(t, 2, calls)
232 }
233
234 func TestSilencesSetSilence(t *testing.T) {
235 s, err := New(Options{
236 Retention: time.Minute,
237 })
238 require.NoError(t, err)
239
240 clock := clock.NewMock()
241 s.clock = clock
242
243 nowpb := s.nowUTC()
244
245 sil := &pb.Silence{
246 Id: "some_id",
247 Matchers: []*pb.Matcher{{Name: "abc", Pattern: "def"}},
248 StartsAt: nowpb,
249 EndsAt: nowpb,
250 }
251
252 want := state{
253 "some_id": &pb.MeshSilence{
254 Silence: sil,
255 ExpiresAt: nowpb.Add(time.Minute),
256 },
257 }
258
259 done := make(chan struct{})
260 s.broadcast = func(b []byte) {
261 var e pb.MeshSilence
262 r := bytes.NewReader(b)
263 _, err := pbutil.ReadDelimited(r, &e)
264 require.NoError(t, err)
265
266 require.Equal(t, want["some_id"], &e)
267 close(done)
268 }
269
270
271 func() {
272 s.mtx.Lock()
273 defer s.mtx.Unlock()
274 require.NoError(t, s.setSilence(sil, nowpb))
275 }()
276
277
278 if _, isOpen := <-done; isOpen {
279 t.Fatal("broadcast was not called")
280 }
281
282 require.Equal(t, want, s.st, "Unexpected silence state")
283 }
284
285 func TestSilenceSet(t *testing.T) {
286 s, err := New(Options{
287 Retention: time.Hour,
288 })
289 require.NoError(t, err)
290
291 clock := clock.NewMock()
292 s.clock = clock
293 start1 := s.nowUTC()
294
295
296 sil1 := &pb.Silence{
297 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
298 StartsAt: start1.Add(2 * time.Minute),
299 EndsAt: start1.Add(5 * time.Minute),
300 }
301 id1, err := s.Set(sil1)
302 require.NoError(t, err)
303 require.NotEqual(t, id1, "")
304
305 want := state{
306 id1: &pb.MeshSilence{
307 Silence: &pb.Silence{
308 Id: id1,
309 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
310 StartsAt: start1.Add(2 * time.Minute),
311 EndsAt: start1.Add(5 * time.Minute),
312 UpdatedAt: start1,
313 },
314 ExpiresAt: start1.Add(5*time.Minute + s.retention),
315 },
316 }
317 require.Equal(t, want, s.st, "unexpected state after silence creation")
318
319
320 clock.Add(time.Minute)
321 start2 := s.nowUTC()
322
323 sil2 := &pb.Silence{
324 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
325 EndsAt: start2.Add(1 * time.Minute),
326 }
327 id2, err := s.Set(sil2)
328 require.NoError(t, err)
329 require.NotEqual(t, id2, "")
330
331 want = state{
332 id1: want[id1],
333 id2: &pb.MeshSilence{
334 Silence: &pb.Silence{
335 Id: id2,
336 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
337 StartsAt: start2,
338 EndsAt: start2.Add(1 * time.Minute),
339 UpdatedAt: start2,
340 },
341 ExpiresAt: start2.Add(1*time.Minute + s.retention),
342 },
343 }
344 require.Equal(t, want, s.st, "unexpected state after silence creation")
345
346
347 clock.Add(time.Minute)
348 start3 := s.nowUTC()
349
350 sil3 := cloneSilence(sil2)
351 sil3.EndsAt = start3.Add(100 * time.Minute)
352
353 id3, err := s.Set(sil3)
354 require.NoError(t, err)
355 require.Equal(t, id2, id3)
356
357 want = state{
358 id1: want[id1],
359 id2: &pb.MeshSilence{
360 Silence: &pb.Silence{
361 Id: id2,
362 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
363 StartsAt: start2,
364 EndsAt: start3.Add(100 * time.Minute),
365 UpdatedAt: start3,
366 },
367 ExpiresAt: start3.Add(100*time.Minute + s.retention),
368 },
369 }
370 require.Equal(t, want, s.st, "unexpected state after silence creation")
371
372
373 clock.Add(time.Minute)
374 start4 := s.nowUTC()
375
376 sil4 := cloneSilence(sil3)
377 sil4.Matchers = []*pb.Matcher{{Name: "a", Pattern: "c"}}
378
379 id4, err := s.Set(sil4)
380 require.NoError(t, err)
381
382 require.NotEqual(t, id2, id4)
383
384 want = state{
385 id1: want[id1],
386 id2: &pb.MeshSilence{
387 Silence: &pb.Silence{
388 Id: id2,
389 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
390 StartsAt: start2,
391 EndsAt: start4,
392 UpdatedAt: start4,
393 },
394 ExpiresAt: start4.Add(s.retention),
395 },
396 id4: &pb.MeshSilence{
397 Silence: &pb.Silence{
398 Id: id4,
399 Matchers: []*pb.Matcher{{Name: "a", Pattern: "c"}},
400 StartsAt: start4,
401 EndsAt: start3.Add(100 * time.Minute),
402 UpdatedAt: start4,
403 },
404 ExpiresAt: start3.Add(100*time.Minute + s.retention),
405 },
406 }
407 require.Equal(t, want, s.st, "unexpected state after silence creation")
408
409
410 clock.Add(time.Minute)
411 start5 := s.nowUTC()
412
413 sil5 := cloneSilence(sil3)
414 sil5.StartsAt = start1
415 sil5.EndsAt = start1.Add(5 * time.Minute)
416
417 id5, err := s.Set(sil5)
418 require.NoError(t, err)
419 require.NotEqual(t, id2, id4)
420
421 want = state{
422 id1: want[id1],
423 id2: want[id2],
424 id4: want[id4],
425 id5: &pb.MeshSilence{
426 Silence: &pb.Silence{
427 Id: id5,
428 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
429 StartsAt: start5,
430 EndsAt: start1.Add(5 * time.Minute),
431 UpdatedAt: start5,
432 },
433 ExpiresAt: start1.Add(5*time.Minute + s.retention),
434 },
435 }
436 require.Equal(t, want, s.st, "unexpected state after silence creation")
437 }
438
439 func TestSetActiveSilence(t *testing.T) {
440 s, err := New(Options{
441 Retention: time.Hour,
442 })
443 require.NoError(t, err)
444
445 clock := clock.NewMock()
446 s.clock = clock
447 now := clock.Now()
448
449 startsAt := now.Add(-1 * time.Minute)
450 endsAt := now.Add(5 * time.Minute)
451
452 sil1 := &pb.Silence{
453 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
454 StartsAt: startsAt,
455 EndsAt: endsAt,
456 }
457 id1, _ := s.Set(sil1)
458
459
460
461 newStartsAt := now.Add(2 * time.Nanosecond)
462 newEndsAt := endsAt.Add(2 * time.Minute)
463
464 sil2 := cloneSilence(sil1)
465 sil2.Id = id1
466 sil2.StartsAt = newStartsAt
467 sil2.EndsAt = newEndsAt
468
469 clock.Add(time.Minute)
470 now = s.nowUTC()
471 id2, err := s.Set(sil2)
472 require.NoError(t, err)
473 require.Equal(t, id1, id2)
474
475 want := state{
476 id2: &pb.MeshSilence{
477 Silence: &pb.Silence{
478 Id: id1,
479 Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}},
480 StartsAt: newStartsAt,
481 EndsAt: newEndsAt,
482 UpdatedAt: now,
483 },
484 ExpiresAt: newEndsAt.Add(s.retention),
485 },
486 }
487 require.Equal(t, want, s.st, "unexpected state after silence creation")
488 }
489
490 func TestSilencesSetFail(t *testing.T) {
491 s, err := New(Options{})
492 require.NoError(t, err)
493
494 clock := clock.NewMock()
495 s.clock = clock
496
497 cases := []struct {
498 s *pb.Silence
499 err string
500 }{
501 {
502 s: &pb.Silence{Id: "some_id"},
503 err: ErrNotFound.Error(),
504 }, {
505 s: &pb.Silence{},
506 err: "silence invalid",
507 },
508 }
509 for _, c := range cases {
510 _, err := s.Set(c.s)
511 checkErr(t, c.err, err)
512 }
513 }
514
515 func TestQState(t *testing.T) {
516 now := time.Now().UTC()
517
518 cases := []struct {
519 sil *pb.Silence
520 states []types.SilenceState
521 keep bool
522 }{
523 {
524 sil: &pb.Silence{
525 StartsAt: now.Add(time.Minute),
526 EndsAt: now.Add(time.Hour),
527 },
528 states: []types.SilenceState{types.SilenceStateActive, types.SilenceStateExpired},
529 keep: false,
530 },
531 {
532 sil: &pb.Silence{
533 StartsAt: now.Add(time.Minute),
534 EndsAt: now.Add(time.Hour),
535 },
536 states: []types.SilenceState{types.SilenceStatePending},
537 keep: true,
538 },
539 {
540 sil: &pb.Silence{
541 StartsAt: now.Add(time.Minute),
542 EndsAt: now.Add(time.Hour),
543 },
544 states: []types.SilenceState{types.SilenceStateExpired, types.SilenceStatePending},
545 keep: true,
546 },
547 }
548 for i, c := range cases {
549 q := &query{}
550 QState(c.states...)(q)
551 f := q.filters[0]
552
553 keep, err := f(c.sil, nil, now)
554 require.NoError(t, err)
555 require.Equal(t, c.keep, keep, "unexpected filter result for case %d", i)
556 }
557 }
558
559 func TestQMatches(t *testing.T) {
560 qp := QMatches(model.LabelSet{
561 "job": "test",
562 "instance": "web-1",
563 "path": "/user/profile",
564 "method": "GET",
565 })
566
567 q := &query{}
568 qp(q)
569 f := q.filters[0]
570
571 cases := []struct {
572 sil *pb.Silence
573 drop bool
574 }{
575 {
576 sil: &pb.Silence{
577 Matchers: []*pb.Matcher{
578 {Name: "job", Pattern: "test", Type: pb.Matcher_EQUAL},
579 },
580 },
581 drop: true,
582 },
583 {
584 sil: &pb.Silence{
585 Matchers: []*pb.Matcher{
586 {Name: "job", Pattern: "test", Type: pb.Matcher_NOT_EQUAL},
587 },
588 },
589 drop: false,
590 },
591 {
592 sil: &pb.Silence{
593 Matchers: []*pb.Matcher{
594 {Name: "job", Pattern: "test", Type: pb.Matcher_EQUAL},
595 {Name: "method", Pattern: "POST", Type: pb.Matcher_EQUAL},
596 },
597 },
598 drop: false,
599 },
600 {
601 sil: &pb.Silence{
602 Matchers: []*pb.Matcher{
603 {Name: "job", Pattern: "test", Type: pb.Matcher_EQUAL},
604 {Name: "method", Pattern: "POST", Type: pb.Matcher_NOT_EQUAL},
605 },
606 },
607 drop: true,
608 },
609 {
610 sil: &pb.Silence{
611 Matchers: []*pb.Matcher{
612 {Name: "path", Pattern: "/user/.+", Type: pb.Matcher_REGEXP},
613 },
614 },
615 drop: true,
616 },
617 {
618 sil: &pb.Silence{
619 Matchers: []*pb.Matcher{
620 {Name: "path", Pattern: "/user/.+", Type: pb.Matcher_NOT_REGEXP},
621 },
622 },
623 drop: false,
624 },
625 {
626 sil: &pb.Silence{
627 Matchers: []*pb.Matcher{
628 {Name: "path", Pattern: "/user/.+", Type: pb.Matcher_REGEXP},
629 {Name: "path", Pattern: "/nothing/.+", Type: pb.Matcher_REGEXP},
630 },
631 },
632 drop: false,
633 },
634 }
635 for _, c := range cases {
636 drop, err := f(c.sil, &Silences{mc: matcherCache{}, st: state{}}, time.Time{})
637 require.NoError(t, err)
638 require.Equal(t, c.drop, drop, "unexpected filter result")
639 }
640 }
641
642 func TestSilencesQuery(t *testing.T) {
643 s, err := New(Options{})
644 require.NoError(t, err)
645
646 s.st = state{
647 "1": &pb.MeshSilence{Silence: &pb.Silence{Id: "1"}},
648 "2": &pb.MeshSilence{Silence: &pb.Silence{Id: "2"}},
649 "3": &pb.MeshSilence{Silence: &pb.Silence{Id: "3"}},
650 "4": &pb.MeshSilence{Silence: &pb.Silence{Id: "4"}},
651 "5": &pb.MeshSilence{Silence: &pb.Silence{Id: "5"}},
652 }
653 cases := []struct {
654 q *query
655 exp []*pb.Silence
656 }{
657 {
658
659 q: &query{},
660 exp: []*pb.Silence{
661 {Id: "1"},
662 {Id: "2"},
663 {Id: "3"},
664 {Id: "4"},
665 {Id: "5"},
666 },
667 },
668 {
669
670 q: &query{
671 ids: []string{"2", "5"},
672 },
673 exp: []*pb.Silence{
674 {Id: "2"},
675 {Id: "5"},
676 },
677 },
678 {
679
680 q: &query{
681 filters: []silenceFilter{
682 func(sil *pb.Silence, _ *Silences, _ time.Time) (bool, error) {
683 return sil.Id == "1" || sil.Id == "2", nil
684 },
685 },
686 },
687 exp: []*pb.Silence{
688 {Id: "1"},
689 {Id: "2"},
690 },
691 },
692 {
693
694 q: &query{
695 ids: []string{"2", "5"},
696 filters: []silenceFilter{
697 func(sil *pb.Silence, _ *Silences, _ time.Time) (bool, error) {
698 return sil.Id == "1" || sil.Id == "2", nil
699 },
700 },
701 },
702 exp: []*pb.Silence{
703 {Id: "2"},
704 },
705 },
706 }
707
708 for _, c := range cases {
709
710 res, _, err := s.query(c.q, time.Time{})
711 require.NoError(t, err, "unexpected error on querying")
712
713
714 sort.Sort(silencesByID(c.exp))
715 sort.Sort(silencesByID(res))
716 require.Equal(t, c.exp, res, "unexpected silences in result")
717 }
718 }
719
720 type silencesByID []*pb.Silence
721
722 func (s silencesByID) Len() int { return len(s) }
723 func (s silencesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
724 func (s silencesByID) Less(i, j int) bool { return s[i].Id < s[j].Id }
725
726 func TestSilenceCanUpdate(t *testing.T) {
727 now := time.Now().UTC()
728
729 cases := []struct {
730 a, b *pb.Silence
731 ok bool
732 }{
733
734 {
735 a: &pb.Silence{},
736 b: &pb.Silence{
737 StartsAt: now,
738 EndsAt: now.Add(-time.Minute),
739 },
740 ok: false,
741 },
742
743 {
744 a: &pb.Silence{
745 StartsAt: now.Add(-time.Hour),
746 EndsAt: now.Add(-time.Second),
747 },
748 b: &pb.Silence{
749 StartsAt: now,
750 EndsAt: now,
751 },
752 ok: false,
753 },
754
755 {
756 a: &pb.Silence{
757 StartsAt: now.Add(time.Hour),
758 EndsAt: now.Add(2 * time.Hour),
759 UpdatedAt: now.Add(-time.Hour),
760 },
761 b: &pb.Silence{
762 StartsAt: now.Add(-time.Minute),
763 EndsAt: now.Add(time.Hour),
764 },
765 ok: false,
766 },
767 {
768 a: &pb.Silence{
769 StartsAt: now.Add(time.Hour),
770 EndsAt: now.Add(2 * time.Hour),
771 UpdatedAt: now.Add(-time.Hour),
772 },
773 b: &pb.Silence{
774 StartsAt: now.Add(time.Minute),
775 EndsAt: now.Add(time.Minute),
776 },
777 ok: true,
778 },
779 {
780 a: &pb.Silence{
781 StartsAt: now.Add(time.Hour),
782 EndsAt: now.Add(2 * time.Hour),
783 UpdatedAt: now.Add(-time.Hour),
784 },
785 b: &pb.Silence{
786 StartsAt: now,
787 EndsAt: now.Add(2 * time.Hour),
788 },
789 ok: true,
790 },
791
792 {
793 a: &pb.Silence{
794 StartsAt: now.Add(-time.Hour),
795 EndsAt: now.Add(2 * time.Hour),
796 UpdatedAt: now.Add(-time.Hour),
797 },
798 b: &pb.Silence{
799 StartsAt: now.Add(-time.Minute),
800 EndsAt: now.Add(2 * time.Hour),
801 },
802 ok: false,
803 },
804 {
805 a: &pb.Silence{
806 StartsAt: now.Add(-time.Hour),
807 EndsAt: now.Add(2 * time.Hour),
808 UpdatedAt: now.Add(-time.Hour),
809 },
810 b: &pb.Silence{
811 StartsAt: now.Add(-time.Hour),
812 EndsAt: now.Add(-time.Second),
813 },
814 ok: false,
815 },
816 {
817 a: &pb.Silence{
818 StartsAt: now.Add(-time.Hour),
819 EndsAt: now.Add(2 * time.Hour),
820 UpdatedAt: now.Add(-time.Hour),
821 },
822 b: &pb.Silence{
823 StartsAt: now.Add(-time.Hour),
824 EndsAt: now,
825 },
826 ok: true,
827 },
828 {
829 a: &pb.Silence{
830 StartsAt: now.Add(-time.Hour),
831 EndsAt: now.Add(2 * time.Hour),
832 UpdatedAt: now.Add(-time.Hour),
833 },
834 b: &pb.Silence{
835 StartsAt: now.Add(-time.Hour),
836 EndsAt: now.Add(3 * time.Hour),
837 },
838 ok: true,
839 },
840 }
841 for _, c := range cases {
842 ok := canUpdate(c.a, c.b, now)
843 if ok && !c.ok {
844 t.Errorf("expected not-updateable but was: %v, %v", c.a, c.b)
845 }
846 if ok && !c.ok {
847 t.Errorf("expected updateable but was not: %v, %v", c.a, c.b)
848 }
849 }
850 }
851
852 func TestSilenceExpire(t *testing.T) {
853 s, err := New(Options{Retention: time.Hour})
854 require.NoError(t, err)
855
856 clock := clock.NewMock()
857 s.clock = clock
858 now := s.nowUTC()
859
860 m := &pb.Matcher{Type: pb.Matcher_EQUAL, Name: "a", Pattern: "b"}
861
862 s.st = state{
863 "pending": &pb.MeshSilence{Silence: &pb.Silence{
864 Id: "pending",
865 Matchers: []*pb.Matcher{m},
866 StartsAt: now.Add(time.Minute),
867 EndsAt: now.Add(time.Hour),
868 UpdatedAt: now.Add(-time.Hour),
869 }},
870 "active": &pb.MeshSilence{Silence: &pb.Silence{
871 Id: "active",
872 Matchers: []*pb.Matcher{m},
873 StartsAt: now.Add(-time.Minute),
874 EndsAt: now.Add(time.Hour),
875 UpdatedAt: now.Add(-time.Hour),
876 }},
877 "expired": &pb.MeshSilence{Silence: &pb.Silence{
878 Id: "expired",
879 Matchers: []*pb.Matcher{m},
880 StartsAt: now.Add(-time.Hour),
881 EndsAt: now.Add(-time.Minute),
882 UpdatedAt: now.Add(-time.Hour),
883 }},
884 }
885
886 count, err := s.CountState(types.SilenceStatePending)
887 require.NoError(t, err)
888 require.Equal(t, 1, count)
889
890 count, err = s.CountState(types.SilenceStateExpired)
891 require.NoError(t, err)
892 require.Equal(t, 1, count)
893
894 require.NoError(t, s.Expire("pending"))
895 require.NoError(t, s.Expire("active"))
896
897 require.NoError(t, s.Expire("expired"))
898
899 sil, err := s.QueryOne(QIDs("pending"))
900 require.NoError(t, err)
901 require.Equal(t, &pb.Silence{
902 Id: "pending",
903 Matchers: []*pb.Matcher{m},
904 StartsAt: now,
905 EndsAt: now,
906 UpdatedAt: now,
907 }, sil)
908
909
910 clock.Add(time.Second)
911
912 count, err = s.CountState(types.SilenceStatePending)
913 require.NoError(t, err)
914 require.Equal(t, 0, count)
915
916 count, err = s.CountState(types.SilenceStateExpired)
917 require.NoError(t, err)
918 require.Equal(t, 3, count)
919
920
921
922 silenceState := types.CalcSilenceState(sil.StartsAt, sil.EndsAt)
923 require.Equal(t, silenceState, types.SilenceStateExpired)
924
925 sil, err = s.QueryOne(QIDs("active"))
926 require.NoError(t, err)
927 require.Equal(t, &pb.Silence{
928 Id: "active",
929 Matchers: []*pb.Matcher{m},
930 StartsAt: now.Add(-time.Minute),
931 EndsAt: now,
932 UpdatedAt: now,
933 }, sil)
934
935 sil, err = s.QueryOne(QIDs("expired"))
936 require.NoError(t, err)
937 require.Equal(t, &pb.Silence{
938 Id: "expired",
939 Matchers: []*pb.Matcher{m},
940 StartsAt: now.Add(-time.Hour),
941 EndsAt: now.Add(-time.Minute),
942 UpdatedAt: now.Add(-time.Hour),
943 }, sil)
944 }
945
946
947
948
949 func TestSilenceExpireWithZeroRetention(t *testing.T) {
950 s, err := New(Options{Retention: 0})
951 require.NoError(t, err)
952
953 clock := clock.NewMock()
954 s.clock = clock
955 now := s.nowUTC()
956
957 m := &pb.Matcher{Type: pb.Matcher_EQUAL, Name: "a", Pattern: "b"}
958
959 s.st = state{
960 "pending": &pb.MeshSilence{Silence: &pb.Silence{
961 Id: "pending",
962 Matchers: []*pb.Matcher{m},
963 StartsAt: now.Add(time.Minute),
964 EndsAt: now.Add(time.Hour),
965 UpdatedAt: now.Add(-time.Hour),
966 }},
967 "active": &pb.MeshSilence{Silence: &pb.Silence{
968 Id: "active",
969 Matchers: []*pb.Matcher{m},
970 StartsAt: now.Add(-time.Minute),
971 EndsAt: now.Add(time.Hour),
972 UpdatedAt: now.Add(-time.Hour),
973 }},
974 "expired": &pb.MeshSilence{Silence: &pb.Silence{
975 Id: "expired",
976 Matchers: []*pb.Matcher{m},
977 StartsAt: now.Add(-time.Hour),
978 EndsAt: now.Add(-time.Minute),
979 UpdatedAt: now.Add(-time.Hour),
980 }},
981 }
982
983 count, err := s.CountState(types.SilenceStatePending)
984 require.NoError(t, err)
985 require.Equal(t, 1, count)
986
987 count, err = s.CountState(types.SilenceStateActive)
988 require.NoError(t, err)
989 require.Equal(t, 1, count)
990
991 count, err = s.CountState(types.SilenceStateExpired)
992 require.NoError(t, err)
993 require.Equal(t, 1, count)
994
995
996
997
998 clock.Add(1 * time.Millisecond)
999
1000 require.NoError(t, s.Expire("pending"))
1001 require.NoError(t, s.Expire("active"))
1002 require.NoError(t, s.Expire("expired"))
1003
1004
1005
1006
1007 clock.Add(1 * time.Millisecond)
1008
1009
1010 count, err = s.CountState(types.SilenceStatePending)
1011 require.NoError(t, err)
1012 require.Equal(t, 0, count)
1013
1014 count, err = s.CountState(types.SilenceStateActive)
1015 require.NoError(t, err)
1016 require.Equal(t, 0, count)
1017
1018 count, err = s.CountState(types.SilenceStateExpired)
1019 require.NoError(t, err)
1020 require.Equal(t, 3, count)
1021 }
1022
1023 func TestSilencer(t *testing.T) {
1024 ss, err := New(Options{Retention: time.Hour})
1025 require.NoError(t, err)
1026
1027 clock := clock.NewMock()
1028 ss.clock = clock
1029 now := ss.nowUTC()
1030
1031 m := types.NewMarker(prometheus.NewRegistry())
1032 s := NewSilencer(ss, m, log.NewNopLogger())
1033
1034 require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced without any silences")
1035
1036 _, err = ss.Set(&pb.Silence{
1037 Matchers: []*pb.Matcher{{Name: "foo", Pattern: "baz"}},
1038 StartsAt: now.Add(-time.Hour),
1039 EndsAt: now.Add(5 * time.Minute),
1040 })
1041 require.NoError(t, err)
1042
1043 require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by non-matching silence")
1044
1045 id, err := ss.Set(&pb.Silence{
1046 Matchers: []*pb.Matcher{{Name: "foo", Pattern: "bar"}},
1047 StartsAt: now.Add(-time.Hour),
1048 EndsAt: now.Add(5 * time.Minute),
1049 })
1050 require.NoError(t, err)
1051 require.NotEmpty(t, id)
1052
1053 require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by matching silence")
1054
1055
1056 clock.Add(time.Hour)
1057 now = ss.nowUTC()
1058
1059 require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by expired silence")
1060
1061
1062 _, err = ss.Set(&pb.Silence{
1063 Id: id,
1064 Matchers: []*pb.Matcher{{Name: "foo", Pattern: "bar"}},
1065 StartsAt: now.Add(time.Hour),
1066 EndsAt: now.Add(3 * time.Hour),
1067 })
1068 require.NoError(t, err)
1069
1070 require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by future silence")
1071
1072
1073 clock.Add(2 * time.Hour)
1074 now = ss.nowUTC()
1075
1076
1077 require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by activated silence")
1078
1079 _, err = ss.Set(&pb.Silence{
1080 Matchers: []*pb.Matcher{{Name: "foo", Pattern: "b..", Type: pb.Matcher_REGEXP}},
1081 StartsAt: now.Add(time.Hour),
1082 EndsAt: now.Add(3 * time.Hour),
1083 })
1084 require.NoError(t, err)
1085
1086
1087 require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert still silenced by activated silence")
1088
1089
1090 clock.Add(2 * time.Hour)
1091
1092
1093 require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by activated second silence")
1094 }
1095
1096 func TestValidateMatcher(t *testing.T) {
1097 cases := []struct {
1098 m *pb.Matcher
1099 err string
1100 }{
1101 {
1102 m: &pb.Matcher{
1103 Name: "a",
1104 Pattern: "b",
1105 Type: pb.Matcher_EQUAL,
1106 },
1107 err: "",
1108 }, {
1109 m: &pb.Matcher{
1110 Name: "a",
1111 Pattern: "b",
1112 Type: pb.Matcher_NOT_EQUAL,
1113 },
1114 err: "",
1115 }, {
1116 m: &pb.Matcher{
1117 Name: "a",
1118 Pattern: "b",
1119 Type: pb.Matcher_REGEXP,
1120 },
1121 err: "",
1122 }, {
1123 m: &pb.Matcher{
1124 Name: "a",
1125 Pattern: "b",
1126 Type: pb.Matcher_NOT_REGEXP,
1127 },
1128 err: "",
1129 }, {
1130 m: &pb.Matcher{
1131 Name: "00",
1132 Pattern: "a",
1133 Type: pb.Matcher_EQUAL,
1134 },
1135 err: "invalid label name",
1136 }, {
1137 m: &pb.Matcher{
1138 Name: "a",
1139 Pattern: "((",
1140 Type: pb.Matcher_REGEXP,
1141 },
1142 err: "invalid regular expression",
1143 }, {
1144 m: &pb.Matcher{
1145 Name: "a",
1146 Pattern: "))",
1147 Type: pb.Matcher_NOT_REGEXP,
1148 },
1149 err: "invalid regular expression",
1150 }, {
1151 m: &pb.Matcher{
1152 Name: "a",
1153 Pattern: "\xff",
1154 Type: pb.Matcher_EQUAL,
1155 },
1156 err: "invalid label value",
1157 }, {
1158 m: &pb.Matcher{
1159 Name: "a",
1160 Pattern: "b",
1161 Type: 333,
1162 },
1163 err: "unknown matcher type",
1164 },
1165 }
1166
1167 for _, c := range cases {
1168 checkErr(t, c.err, ValidateMatcher(c.m))
1169 }
1170 }
1171
1172 func TestValidateSilence(t *testing.T) {
1173 var (
1174 now = time.Now().UTC()
1175 zeroTimestamp = time.Time{}
1176 validTimestamp = now
1177 )
1178 cases := []struct {
1179 s *pb.Silence
1180 err string
1181 }{
1182 {
1183 s: &pb.Silence{
1184 Id: "some_id",
1185 Matchers: []*pb.Matcher{
1186 {Name: "a", Pattern: "b"},
1187 },
1188 StartsAt: validTimestamp,
1189 EndsAt: validTimestamp,
1190 UpdatedAt: validTimestamp,
1191 },
1192 err: "",
1193 },
1194 {
1195 s: &pb.Silence{
1196 Id: "",
1197 Matchers: []*pb.Matcher{
1198 {Name: "a", Pattern: "b"},
1199 },
1200 StartsAt: validTimestamp,
1201 EndsAt: validTimestamp,
1202 UpdatedAt: validTimestamp,
1203 },
1204 err: "ID missing",
1205 },
1206 {
1207 s: &pb.Silence{
1208 Id: "some_id",
1209 Matchers: []*pb.Matcher{},
1210 StartsAt: validTimestamp,
1211 EndsAt: validTimestamp,
1212 UpdatedAt: validTimestamp,
1213 },
1214 err: "at least one matcher required",
1215 },
1216 {
1217 s: &pb.Silence{
1218 Id: "some_id",
1219 Matchers: []*pb.Matcher{
1220 {Name: "a", Pattern: "b"},
1221 {Name: "00", Pattern: "b"},
1222 },
1223 StartsAt: validTimestamp,
1224 EndsAt: validTimestamp,
1225 UpdatedAt: validTimestamp,
1226 },
1227 err: "invalid label matcher",
1228 },
1229 {
1230 s: &pb.Silence{
1231 Id: "some_id",
1232 Matchers: []*pb.Matcher{
1233 {Name: "a", Pattern: ""},
1234 {Name: "b", Pattern: ".*", Type: pb.Matcher_REGEXP},
1235 },
1236 StartsAt: validTimestamp,
1237 EndsAt: validTimestamp,
1238 UpdatedAt: validTimestamp,
1239 },
1240 err: "at least one matcher must not match the empty string",
1241 },
1242 {
1243 s: &pb.Silence{
1244 Id: "some_id",
1245 Matchers: []*pb.Matcher{
1246 {Name: "a", Pattern: "b"},
1247 },
1248 StartsAt: now,
1249 EndsAt: now.Add(-time.Second),
1250 UpdatedAt: validTimestamp,
1251 },
1252 err: "end time must not be before start time",
1253 },
1254 {
1255 s: &pb.Silence{
1256 Id: "some_id",
1257 Matchers: []*pb.Matcher{
1258 {Name: "a", Pattern: "b"},
1259 },
1260 StartsAt: zeroTimestamp,
1261 EndsAt: validTimestamp,
1262 UpdatedAt: validTimestamp,
1263 },
1264 err: "invalid zero start timestamp",
1265 },
1266 {
1267 s: &pb.Silence{
1268 Id: "some_id",
1269 Matchers: []*pb.Matcher{
1270 {Name: "a", Pattern: "b"},
1271 },
1272 StartsAt: validTimestamp,
1273 EndsAt: zeroTimestamp,
1274 UpdatedAt: validTimestamp,
1275 },
1276 err: "invalid zero end timestamp",
1277 },
1278 {
1279 s: &pb.Silence{
1280 Id: "some_id",
1281 Matchers: []*pb.Matcher{
1282 {Name: "a", Pattern: "b"},
1283 },
1284 StartsAt: validTimestamp,
1285 EndsAt: validTimestamp,
1286 UpdatedAt: zeroTimestamp,
1287 },
1288 err: "invalid zero update timestamp",
1289 },
1290 }
1291 for _, c := range cases {
1292 checkErr(t, c.err, validateSilence(c.s))
1293 }
1294 }
1295
1296 func TestStateMerge(t *testing.T) {
1297 now := time.Now().UTC()
1298
1299
1300
1301 newSilence := func(id string, ts, exp time.Time) *pb.MeshSilence {
1302 return &pb.MeshSilence{
1303 Silence: &pb.Silence{Id: id, UpdatedAt: ts},
1304 ExpiresAt: exp,
1305 }
1306 }
1307
1308 exp := now.Add(time.Minute)
1309
1310 cases := []struct {
1311 a, b state
1312 final state
1313 }{
1314 {
1315 a: state{
1316 "a1": newSilence("a1", now, exp),
1317 "a2": newSilence("a2", now, exp),
1318 "a3": newSilence("a3", now, exp),
1319 },
1320 b: state{
1321 "b1": newSilence("b1", now, exp),
1322 "a2": newSilence("a2", now.Add(-time.Minute), exp),
1323 "a3": newSilence("a3", now.Add(time.Minute), exp),
1324 "a4": newSilence("a4", now.Add(-time.Minute), now.Add(-time.Millisecond)),
1325 },
1326 final: state{
1327 "a1": newSilence("a1", now, exp),
1328 "a2": newSilence("a2", now, exp),
1329 "a3": newSilence("a3", now.Add(time.Minute), exp),
1330 "b1": newSilence("b1", now, exp),
1331 },
1332 },
1333 }
1334
1335 for _, c := range cases {
1336 for _, e := range c.b {
1337 c.a.merge(e, now)
1338 }
1339
1340 require.Equal(t, c.final, c.a, "Merge result should match expectation")
1341 }
1342 }
1343
1344 func TestStateCoding(t *testing.T) {
1345
1346 now := time.Now().UTC()
1347
1348 cases := []struct {
1349 entries []*pb.MeshSilence
1350 }{
1351 {
1352 entries: []*pb.MeshSilence{
1353 {
1354 Silence: &pb.Silence{
1355 Id: "3be80475-e219-4ee7-b6fc-4b65114e362f",
1356 Matchers: []*pb.Matcher{
1357 {Name: "label1", Pattern: "val1", Type: pb.Matcher_EQUAL},
1358 {Name: "label2", Pattern: "val.+", Type: pb.Matcher_REGEXP},
1359 },
1360 StartsAt: now,
1361 EndsAt: now,
1362 UpdatedAt: now,
1363 },
1364 ExpiresAt: now,
1365 },
1366 {
1367 Silence: &pb.Silence{
1368 Id: "4b1e760d-182c-4980-b873-c1a6827c9817",
1369 Matchers: []*pb.Matcher{
1370 {Name: "label1", Pattern: "val1", Type: pb.Matcher_EQUAL},
1371 },
1372 StartsAt: now.Add(time.Hour),
1373 EndsAt: now.Add(2 * time.Hour),
1374 UpdatedAt: now,
1375 },
1376 ExpiresAt: now.Add(24 * time.Hour),
1377 },
1378 {
1379 Silence: &pb.Silence{
1380 Id: "3dfb2528-59ce-41eb-b465-f875a4e744a4",
1381 Matchers: []*pb.Matcher{
1382 {Name: "label1", Pattern: "val1", Type: pb.Matcher_NOT_EQUAL},
1383 {Name: "label2", Pattern: "val.+", Type: pb.Matcher_NOT_REGEXP},
1384 },
1385 StartsAt: now,
1386 EndsAt: now,
1387 UpdatedAt: now,
1388 },
1389 ExpiresAt: now,
1390 },
1391 },
1392 },
1393 }
1394
1395 for _, c := range cases {
1396
1397 in := state{}
1398 for _, e := range c.entries {
1399 in[e.Silence.Id] = e
1400 }
1401 msg, err := in.MarshalBinary()
1402 require.NoError(t, err)
1403
1404 out, err := decodeState(bytes.NewReader(msg))
1405 require.NoError(t, err, "decoding message failed")
1406
1407 require.Equal(t, in, out, "decoded data doesn't match encoded data")
1408 }
1409 }
1410
1411 func TestStateDecodingError(t *testing.T) {
1412
1413 s := state{"": &pb.MeshSilence{}}
1414
1415 msg, err := s.MarshalBinary()
1416 require.NoError(t, err)
1417
1418 _, err = decodeState(bytes.NewReader(msg))
1419 require.Equal(t, ErrInvalidState, err)
1420 }
1421
1422 func benchmarkSilencesQuery(b *testing.B, numSilences int) {
1423 s, err := New(Options{})
1424 require.NoError(b, err)
1425
1426 clock := clock.NewMock()
1427 s.clock = clock
1428 now := clock.Now()
1429
1430 lset := model.LabelSet{"aaaa": "AAAA", "bbbb": "BBBB", "cccc": "CCCC"}
1431
1432 s.st = state{}
1433 for i := 0; i < numSilences; i++ {
1434 id := fmt.Sprint("ID", i)
1435
1436 patA := "A{4}|" + id
1437 patB := id
1438 if i%10 == 0 {
1439
1440 patB = "B(B|C)B.|" + id
1441 }
1442
1443 s.st[id] = &pb.MeshSilence{Silence: &pb.Silence{
1444 Id: id,
1445 Matchers: []*pb.Matcher{
1446 {Type: pb.Matcher_REGEXP, Name: "aaaa", Pattern: patA},
1447 {Type: pb.Matcher_REGEXP, Name: "bbbb", Pattern: patB},
1448 },
1449 StartsAt: now.Add(-time.Minute),
1450 EndsAt: now.Add(time.Hour),
1451 UpdatedAt: now.Add(-time.Hour),
1452 }}
1453 }
1454
1455
1456 sils, _, err := s.Query(
1457 QState(types.SilenceStateActive),
1458 QMatches(lset),
1459 )
1460 require.NoError(b, err)
1461 require.Equal(b, numSilences/10, len(sils))
1462
1463 b.ResetTimer()
1464 for i := 0; i < b.N; i++ {
1465 sils, _, err := s.Query(
1466 QState(types.SilenceStateActive),
1467 QMatches(lset),
1468 )
1469 require.NoError(b, err)
1470 require.Equal(b, numSilences/10, len(sils))
1471 }
1472 }
1473
1474 func Benchmark100SilencesQuery(b *testing.B) {
1475 benchmarkSilencesQuery(b, 100)
1476 }
1477
1478 func Benchmark1000SilencesQuery(b *testing.B) {
1479 benchmarkSilencesQuery(b, 1000)
1480 }
1481
1482 func Benchmark10000SilencesQuery(b *testing.B) {
1483 benchmarkSilencesQuery(b, 10000)
1484 }
1485
View as plain text