1
2
3
4
5
6
7
8
9
10
11
12
13
14 package dispatch
15
16 import (
17 "reflect"
18 "testing"
19 "time"
20
21 "github.com/prometheus/common/model"
22 "github.com/stretchr/testify/require"
23 "gopkg.in/yaml.v2"
24
25 "github.com/prometheus/alertmanager/config"
26 )
27
28 func TestRouteMatch(t *testing.T) {
29 in := `
30 receiver: 'notify-def'
31
32 routes:
33 - match:
34 owner: 'team-A'
35
36 receiver: 'notify-A'
37
38 routes:
39 - match:
40 env: 'testing'
41
42 receiver: 'notify-testing'
43 group_by: [...]
44
45 - match:
46 env: "production"
47
48 receiver: 'notify-productionA'
49 group_wait: 1m
50
51 continue: true
52
53 - match_re:
54 env: "produ.*"
55 job: ".*"
56
57 receiver: 'notify-productionB'
58 group_wait: 30s
59 group_interval: 5m
60 repeat_interval: 1h
61 group_by: ['job']
62
63 - match_re:
64 owner: 'team-(B|C)'
65
66 group_by: ['foo', 'bar']
67 group_wait: 2m
68 receiver: 'notify-BC'
69
70 - match:
71 group_by: 'role'
72 group_by: ['role']
73
74 routes:
75 - match:
76 env: 'testing'
77 receiver: 'notify-testing'
78 routes:
79 - match:
80 wait: 'long'
81 group_wait: 2m
82 `
83
84 var ctree config.Route
85 if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
86 t.Fatal(err)
87 }
88 var (
89 def = DefaultRouteOpts
90 tree = NewRoute(&ctree, nil)
91 )
92 lset := func(labels ...string) map[model.LabelName]struct{} {
93 s := map[model.LabelName]struct{}{}
94 for _, ls := range labels {
95 s[model.LabelName(ls)] = struct{}{}
96 }
97 return s
98 }
99
100 tests := []struct {
101 input model.LabelSet
102 result []*RouteOpts
103 keys []string
104 }{
105 {
106 input: model.LabelSet{
107 "owner": "team-A",
108 },
109 result: []*RouteOpts{
110 {
111 Receiver: "notify-A",
112 GroupBy: def.GroupBy,
113 GroupByAll: false,
114 GroupWait: def.GroupWait,
115 GroupInterval: def.GroupInterval,
116 RepeatInterval: def.RepeatInterval,
117 },
118 },
119 keys: []string{"{}/{owner=\"team-A\"}"},
120 },
121 {
122 input: model.LabelSet{
123 "owner": "team-A",
124 "env": "unset",
125 },
126 result: []*RouteOpts{
127 {
128 Receiver: "notify-A",
129 GroupBy: def.GroupBy,
130 GroupByAll: false,
131 GroupWait: def.GroupWait,
132 GroupInterval: def.GroupInterval,
133 RepeatInterval: def.RepeatInterval,
134 },
135 },
136 keys: []string{"{}/{owner=\"team-A\"}"},
137 },
138 {
139 input: model.LabelSet{
140 "owner": "team-C",
141 },
142 result: []*RouteOpts{
143 {
144 Receiver: "notify-BC",
145 GroupBy: lset("foo", "bar"),
146 GroupByAll: false,
147 GroupWait: 2 * time.Minute,
148 GroupInterval: def.GroupInterval,
149 RepeatInterval: def.RepeatInterval,
150 },
151 },
152 keys: []string{"{}/{owner=~\"^(?:team-(B|C))$\"}"},
153 },
154 {
155 input: model.LabelSet{
156 "owner": "team-A",
157 "env": "testing",
158 },
159 result: []*RouteOpts{
160 {
161 Receiver: "notify-testing",
162 GroupBy: lset(),
163 GroupByAll: true,
164 GroupWait: def.GroupWait,
165 GroupInterval: def.GroupInterval,
166 RepeatInterval: def.RepeatInterval,
167 },
168 },
169 keys: []string{"{}/{owner=\"team-A\"}/{env=\"testing\"}"},
170 },
171 {
172 input: model.LabelSet{
173 "owner": "team-A",
174 "env": "production",
175 },
176 result: []*RouteOpts{
177 {
178 Receiver: "notify-productionA",
179 GroupBy: def.GroupBy,
180 GroupByAll: false,
181 GroupWait: 1 * time.Minute,
182 GroupInterval: def.GroupInterval,
183 RepeatInterval: def.RepeatInterval,
184 },
185 {
186 Receiver: "notify-productionB",
187 GroupBy: lset("job"),
188 GroupByAll: false,
189 GroupWait: 30 * time.Second,
190 GroupInterval: 5 * time.Minute,
191 RepeatInterval: 1 * time.Hour,
192 },
193 },
194 keys: []string{
195 "{}/{owner=\"team-A\"}/{env=\"production\"}",
196 "{}/{owner=\"team-A\"}/{env=~\"^(?:produ.*)$\",job=~\"^(?:.*)$\"}",
197 },
198 },
199 {
200 input: model.LabelSet{
201 "group_by": "role",
202 },
203 result: []*RouteOpts{
204 {
205 Receiver: "notify-def",
206 GroupBy: lset("role"),
207 GroupByAll: false,
208 GroupWait: def.GroupWait,
209 GroupInterval: def.GroupInterval,
210 RepeatInterval: def.RepeatInterval,
211 },
212 },
213 keys: []string{"{}/{group_by=\"role\"}"},
214 },
215 {
216 input: model.LabelSet{
217 "env": "testing",
218 "group_by": "role",
219 },
220 result: []*RouteOpts{
221 {
222 Receiver: "notify-testing",
223 GroupBy: lset("role"),
224 GroupByAll: false,
225 GroupWait: def.GroupWait,
226 GroupInterval: def.GroupInterval,
227 RepeatInterval: def.RepeatInterval,
228 },
229 },
230 keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}"},
231 },
232 {
233 input: model.LabelSet{
234 "env": "testing",
235 "group_by": "role",
236 "wait": "long",
237 },
238 result: []*RouteOpts{
239 {
240 Receiver: "notify-testing",
241 GroupBy: lset("role"),
242 GroupByAll: false,
243 GroupWait: 2 * time.Minute,
244 GroupInterval: def.GroupInterval,
245 RepeatInterval: def.RepeatInterval,
246 },
247 },
248 keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}/{wait=\"long\"}"},
249 },
250 }
251
252 for _, test := range tests {
253 var matches []*RouteOpts
254 var keys []string
255
256 for _, r := range tree.Match(test.input) {
257 matches = append(matches, &r.RouteOpts)
258 keys = append(keys, r.Key())
259 }
260
261 if !reflect.DeepEqual(matches, test.result) {
262 t.Errorf("\nexpected:\n%v\ngot:\n%v", test.result, matches)
263 }
264
265 if !reflect.DeepEqual(keys, test.keys) {
266 t.Errorf("\nexpected:\n%v\ngot:\n%v", test.keys, keys)
267 }
268 }
269 }
270
271 func TestRouteWalk(t *testing.T) {
272 in := `
273 receiver: 'notify-def'
274
275 routes:
276 - match:
277 owner: 'team-A'
278
279 receiver: 'notify-A'
280
281 routes:
282 - match:
283 env: 'testing'
284
285 receiver: 'notify-testing'
286 group_by: [...]
287
288 - match:
289 env: "production"
290
291 receiver: 'notify-productionA'
292 group_wait: 1m
293
294 continue: true
295
296 - match_re:
297 env: "produ.*"
298 job: ".*"
299
300 receiver: 'notify-productionB'
301 group_wait: 30s
302 group_interval: 5m
303 repeat_interval: 1h
304 group_by: ['job']
305
306
307 - match_re:
308 owner: 'team-(B|C)'
309
310 group_by: ['foo', 'bar']
311 group_wait: 2m
312 receiver: 'notify-BC'
313
314 - match:
315 group_by: 'role'
316 group_by: ['role']
317
318 routes:
319 - match:
320 env: 'testing'
321 receiver: 'notify-testing'
322 routes:
323 - match:
324 wait: 'long'
325 group_wait: 2m
326 `
327
328 var ctree config.Route
329 if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
330 t.Fatal(err)
331 }
332 tree := NewRoute(&ctree, nil)
333
334 expected := []string{
335 "notify-def",
336 "notify-A",
337 "notify-testing",
338 "notify-productionA",
339 "notify-productionB",
340 "notify-BC",
341 "notify-def",
342 "notify-testing",
343 "notify-testing",
344 }
345
346 var got []string
347 tree.Walk(func(r *Route) {
348 got = append(got, r.RouteOpts.Receiver)
349 })
350
351 if !reflect.DeepEqual(got, expected) {
352 t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, got)
353 }
354 }
355
356 func TestInheritParentGroupByAll(t *testing.T) {
357 in := `
358 routes:
359 - match:
360 env: 'parent'
361 group_by: ['...']
362
363 routes:
364 - match:
365 env: 'child1'
366
367 - match:
368 env: 'child2'
369 group_by: ['foo']
370 `
371
372 var ctree config.Route
373 if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
374 t.Fatal(err)
375 }
376
377 tree := NewRoute(&ctree, nil)
378 parent := tree.Routes[0]
379 child1 := parent.Routes[0]
380 child2 := parent.Routes[1]
381 require.Equal(t, parent.RouteOpts.GroupByAll, true)
382 require.Equal(t, child1.RouteOpts.GroupByAll, true)
383 require.Equal(t, child2.RouteOpts.GroupByAll, false)
384 }
385
386 func TestRouteMatchers(t *testing.T) {
387 in := `
388 receiver: 'notify-def'
389
390 routes:
391 - matchers: ['{owner="team-A"}', '{level!="critical"}']
392
393 receiver: 'notify-A'
394
395 routes:
396 - matchers: ['{env="testing"}', '{baz!~".*quux"}']
397
398 receiver: 'notify-testing'
399 group_by: [...]
400
401 - matchers: ['{env="production"}']
402
403 receiver: 'notify-productionA'
404 group_wait: 1m
405
406 continue: true
407
408 - matchers: [ env=~"produ.*", job=~".*"]
409
410 receiver: 'notify-productionB'
411 group_wait: 30s
412 group_interval: 5m
413 repeat_interval: 1h
414 group_by: ['job']
415
416
417 - matchers: [owner=~"team-(B|C)"]
418
419 group_by: ['foo', 'bar']
420 group_wait: 2m
421 receiver: 'notify-BC'
422
423 - matchers: [group_by="role"]
424 group_by: ['role']
425
426 routes:
427 - matchers: ['{env="testing"}']
428 receiver: 'notify-testing'
429 routes:
430 - matchers: [wait="long"]
431 group_wait: 2m
432 `
433
434 var ctree config.Route
435 if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
436 t.Fatal(err)
437 }
438 var (
439 def = DefaultRouteOpts
440 tree = NewRoute(&ctree, nil)
441 )
442 lset := func(labels ...string) map[model.LabelName]struct{} {
443 s := map[model.LabelName]struct{}{}
444 for _, ls := range labels {
445 s[model.LabelName(ls)] = struct{}{}
446 }
447 return s
448 }
449
450 tests := []struct {
451 input model.LabelSet
452 result []*RouteOpts
453 keys []string
454 }{
455 {
456 input: model.LabelSet{
457 "owner": "team-A",
458 },
459 result: []*RouteOpts{
460 {
461 Receiver: "notify-A",
462 GroupBy: def.GroupBy,
463 GroupByAll: false,
464 GroupWait: def.GroupWait,
465 GroupInterval: def.GroupInterval,
466 RepeatInterval: def.RepeatInterval,
467 },
468 },
469 keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}"},
470 },
471 {
472 input: model.LabelSet{
473 "owner": "team-A",
474 "env": "unset",
475 },
476 result: []*RouteOpts{
477 {
478 Receiver: "notify-A",
479 GroupBy: def.GroupBy,
480 GroupByAll: false,
481 GroupWait: def.GroupWait,
482 GroupInterval: def.GroupInterval,
483 RepeatInterval: def.RepeatInterval,
484 },
485 },
486 keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}"},
487 },
488 {
489 input: model.LabelSet{
490 "owner": "team-C",
491 },
492 result: []*RouteOpts{
493 {
494 Receiver: "notify-BC",
495 GroupBy: lset("foo", "bar"),
496 GroupByAll: false,
497 GroupWait: 2 * time.Minute,
498 GroupInterval: def.GroupInterval,
499 RepeatInterval: def.RepeatInterval,
500 },
501 },
502 keys: []string{"{}/{owner=~\"team-(B|C)\"}"},
503 },
504 {
505 input: model.LabelSet{
506 "owner": "team-A",
507 "env": "testing",
508 },
509 result: []*RouteOpts{
510 {
511 Receiver: "notify-testing",
512 GroupBy: lset(),
513 GroupByAll: true,
514 GroupWait: def.GroupWait,
515 GroupInterval: def.GroupInterval,
516 RepeatInterval: def.RepeatInterval,
517 },
518 },
519 keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}/{baz!~\".*quux\",env=\"testing\"}"},
520 },
521 {
522 input: model.LabelSet{
523 "owner": "team-A",
524 "env": "production",
525 },
526 result: []*RouteOpts{
527 {
528 Receiver: "notify-productionA",
529 GroupBy: def.GroupBy,
530 GroupByAll: false,
531 GroupWait: 1 * time.Minute,
532 GroupInterval: def.GroupInterval,
533 RepeatInterval: def.RepeatInterval,
534 },
535 {
536 Receiver: "notify-productionB",
537 GroupBy: lset("job"),
538 GroupByAll: false,
539 GroupWait: 30 * time.Second,
540 GroupInterval: 5 * time.Minute,
541 RepeatInterval: 1 * time.Hour,
542 },
543 },
544 keys: []string{
545 "{}/{level!=\"critical\",owner=\"team-A\"}/{env=\"production\"}",
546 "{}/{level!=\"critical\",owner=\"team-A\"}/{env=~\"produ.*\",job=~\".*\"}",
547 },
548 },
549 {
550 input: model.LabelSet{
551 "group_by": "role",
552 },
553 result: []*RouteOpts{
554 {
555 Receiver: "notify-def",
556 GroupBy: lset("role"),
557 GroupByAll: false,
558 GroupWait: def.GroupWait,
559 GroupInterval: def.GroupInterval,
560 RepeatInterval: def.RepeatInterval,
561 },
562 },
563 keys: []string{"{}/{group_by=\"role\"}"},
564 },
565 {
566 input: model.LabelSet{
567 "env": "testing",
568 "group_by": "role",
569 },
570 result: []*RouteOpts{
571 {
572 Receiver: "notify-testing",
573 GroupBy: lset("role"),
574 GroupByAll: false,
575 GroupWait: def.GroupWait,
576 GroupInterval: def.GroupInterval,
577 RepeatInterval: def.RepeatInterval,
578 },
579 },
580 keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}"},
581 },
582 {
583 input: model.LabelSet{
584 "env": "testing",
585 "group_by": "role",
586 "wait": "long",
587 },
588 result: []*RouteOpts{
589 {
590 Receiver: "notify-testing",
591 GroupBy: lset("role"),
592 GroupByAll: false,
593 GroupWait: 2 * time.Minute,
594 GroupInterval: def.GroupInterval,
595 RepeatInterval: def.RepeatInterval,
596 },
597 },
598 keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}/{wait=\"long\"}"},
599 },
600 }
601
602 for _, test := range tests {
603 var matches []*RouteOpts
604 var keys []string
605
606 for _, r := range tree.Match(test.input) {
607 matches = append(matches, &r.RouteOpts)
608 keys = append(keys, r.Key())
609 }
610
611 if !reflect.DeepEqual(matches, test.result) {
612 t.Errorf("\nexpected:\n%v\ngot:\n%v", test.result, matches)
613 }
614
615 if !reflect.DeepEqual(keys, test.keys) {
616 t.Errorf("\nexpected:\n%v\ngot:\n%v", test.keys, keys)
617 }
618 }
619 }
620
621 func TestRouteMatchersAndMatch(t *testing.T) {
622 in := `
623 receiver: 'notify-def'
624
625 routes:
626 - matchers: ['{owner="team-A"}', '{level!="critical"}']
627
628 receiver: 'notify-A'
629
630 routes:
631 - matchers: ['{env="testing"}', '{baz!~".*quux"}']
632
633 receiver: 'notify-testing'
634 group_by: [...]
635
636 - match:
637 env: "production"
638
639 receiver: 'notify-productionA'
640 group_wait: 1m
641
642 continue: true
643
644 - matchers: [ env=~"produ.*", job=~".*"]
645
646 receiver: 'notify-productionB'
647 group_wait: 30s
648 group_interval: 5m
649 repeat_interval: 1h
650 group_by: ['job']
651
652 - match_re:
653 owner: 'team-(B|C)'
654
655 group_by: ['foo', 'bar']
656 group_wait: 2m
657 receiver: 'notify-BC'
658
659 - matchers: [group_by="role"]
660 group_by: ['role']
661
662 routes:
663 - matchers: ['{env="testing"}']
664 receiver: 'notify-testing'
665 routes:
666 - matchers: [wait="long"]
667 group_wait: 2m
668 `
669
670 var ctree config.Route
671 if err := yaml.UnmarshalStrict([]byte(in), &ctree); err != nil {
672 t.Fatal(err)
673 }
674 var (
675 def = DefaultRouteOpts
676 tree = NewRoute(&ctree, nil)
677 )
678 lset := func(labels ...string) map[model.LabelName]struct{} {
679 s := map[model.LabelName]struct{}{}
680 for _, ls := range labels {
681 s[model.LabelName(ls)] = struct{}{}
682 }
683 return s
684 }
685
686 tests := []struct {
687 input model.LabelSet
688 result []*RouteOpts
689 keys []string
690 }{
691 {
692 input: model.LabelSet{
693 "owner": "team-A",
694 },
695 result: []*RouteOpts{
696 {
697 Receiver: "notify-A",
698 GroupBy: def.GroupBy,
699 GroupByAll: false,
700 GroupWait: def.GroupWait,
701 GroupInterval: def.GroupInterval,
702 RepeatInterval: def.RepeatInterval,
703 },
704 },
705 keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}"},
706 },
707 {
708 input: model.LabelSet{
709 "owner": "team-A",
710 "env": "unset",
711 },
712 result: []*RouteOpts{
713 {
714 Receiver: "notify-A",
715 GroupBy: def.GroupBy,
716 GroupByAll: false,
717 GroupWait: def.GroupWait,
718 GroupInterval: def.GroupInterval,
719 RepeatInterval: def.RepeatInterval,
720 },
721 },
722 keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}"},
723 },
724 {
725 input: model.LabelSet{
726 "owner": "team-C",
727 },
728 result: []*RouteOpts{
729 {
730 Receiver: "notify-BC",
731 GroupBy: lset("foo", "bar"),
732 GroupByAll: false,
733 GroupWait: 2 * time.Minute,
734 GroupInterval: def.GroupInterval,
735 RepeatInterval: def.RepeatInterval,
736 },
737 },
738 keys: []string{"{}/{owner=~\"^(?:team-(B|C))$\"}"},
739 },
740 {
741 input: model.LabelSet{
742 "owner": "team-A",
743 "env": "testing",
744 },
745 result: []*RouteOpts{
746 {
747 Receiver: "notify-testing",
748 GroupBy: lset(),
749 GroupByAll: true,
750 GroupWait: def.GroupWait,
751 GroupInterval: def.GroupInterval,
752 RepeatInterval: def.RepeatInterval,
753 },
754 },
755 keys: []string{"{}/{level!=\"critical\",owner=\"team-A\"}/{baz!~\".*quux\",env=\"testing\"}"},
756 },
757 {
758 input: model.LabelSet{
759 "owner": "team-A",
760 "env": "production",
761 },
762 result: []*RouteOpts{
763 {
764 Receiver: "notify-productionA",
765 GroupBy: def.GroupBy,
766 GroupByAll: false,
767 GroupWait: 1 * time.Minute,
768 GroupInterval: def.GroupInterval,
769 RepeatInterval: def.RepeatInterval,
770 },
771 {
772 Receiver: "notify-productionB",
773 GroupBy: lset("job"),
774 GroupByAll: false,
775 GroupWait: 30 * time.Second,
776 GroupInterval: 5 * time.Minute,
777 RepeatInterval: 1 * time.Hour,
778 },
779 },
780 keys: []string{
781 "{}/{level!=\"critical\",owner=\"team-A\"}/{env=\"production\"}",
782 "{}/{level!=\"critical\",owner=\"team-A\"}/{env=~\"produ.*\",job=~\".*\"}",
783 },
784 },
785 {
786 input: model.LabelSet{
787 "group_by": "role",
788 },
789 result: []*RouteOpts{
790 {
791 Receiver: "notify-def",
792 GroupBy: lset("role"),
793 GroupByAll: false,
794 GroupWait: def.GroupWait,
795 GroupInterval: def.GroupInterval,
796 RepeatInterval: def.RepeatInterval,
797 },
798 },
799 keys: []string{"{}/{group_by=\"role\"}"},
800 },
801 {
802 input: model.LabelSet{
803 "env": "testing",
804 "group_by": "role",
805 },
806 result: []*RouteOpts{
807 {
808 Receiver: "notify-testing",
809 GroupBy: lset("role"),
810 GroupByAll: false,
811 GroupWait: def.GroupWait,
812 GroupInterval: def.GroupInterval,
813 RepeatInterval: def.RepeatInterval,
814 },
815 },
816 keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}"},
817 },
818 {
819 input: model.LabelSet{
820 "env": "testing",
821 "group_by": "role",
822 "wait": "long",
823 },
824 result: []*RouteOpts{
825 {
826 Receiver: "notify-testing",
827 GroupBy: lset("role"),
828 GroupByAll: false,
829 GroupWait: 2 * time.Minute,
830 GroupInterval: def.GroupInterval,
831 RepeatInterval: def.RepeatInterval,
832 },
833 },
834 keys: []string{"{}/{group_by=\"role\"}/{env=\"testing\"}/{wait=\"long\"}"},
835 },
836 }
837
838 for _, test := range tests {
839 var matches []*RouteOpts
840 var keys []string
841
842 for _, r := range tree.Match(test.input) {
843 matches = append(matches, &r.RouteOpts)
844 keys = append(keys, r.Key())
845 }
846
847 if !reflect.DeepEqual(matches, test.result) {
848 t.Errorf("\nexpected:\n%v\ngot:\n%v", test.result, matches)
849 }
850
851 if !reflect.DeepEqual(keys, test.keys) {
852 t.Errorf("\nexpected:\n%v\ngot:\n%v", test.keys, keys)
853 }
854 }
855 }
856
View as plain text