cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.44.2

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/checks/alerts_template_test.go

1022lines · modecode

1package checks_test
2
3import (
4 "fmt"
5 "testing"
6
7 "github.com/cloudflare/pint/internal/checks"
8 "github.com/cloudflare/pint/internal/promapi"
9)
10
11func newTemplateCheck(_ *promapi.FailoverGroup) checks.RuleChecker {
12 return checks.NewTemplateCheck()
13}
14
15func humanizeText(call string) string {
16 return fmt.Sprintf("using the value of %s inside this annotation might be hard to read, consider using one of humanize template functions to make it more human friendly", call)
17}
18
19func TestTemplateCheck(t *testing.T) {
20 testCases := []checkTest{
21 {
22 description: "skips recording rule",
23 content: "- record: foo\n expr: sum(foo)\n",
24 checker: newTemplateCheck,
25 prometheus: noProm,
26 problems: noProblems,
27 },
28 {
29 description: "invalid syntax in annotations",
30 content: "- alert: Foo Is Down\n expr: up{job=\"foo\"} == 0\n annotations:\n summary: 'Instance {{ $label.instance }} down'\n",
31 checker: newTemplateCheck,
32 prometheus: noProm,
33 problems: func(uri string) []checks.Problem {
34 return []checks.Problem{
35 {
36 Fragment: `summary: Instance {{ $label.instance }} down`,
37 Lines: []int{4},
38 Reporter: checks.TemplateCheckName,
39 Text: "template parse error: undefined variable \"$label\"",
40 Severity: checks.Fatal,
41 },
42 }
43 },
44 },
45 {
46 description: "invalid function in annotations",
47 content: "- alert: Foo Is Down\n expr: up{job=\"foo\"} == 0\n annotations:\n summary: '{{ $value | xxx }}'\n",
48 checker: newTemplateCheck,
49 prometheus: noProm,
50 problems: func(uri string) []checks.Problem {
51 return []checks.Problem{
52 {
53 Fragment: `summary: {{ $value | xxx }}`,
54 Lines: []int{4},
55 Reporter: checks.TemplateCheckName,
56 Text: "template parse error: function \"xxx\" not defined",
57 Severity: checks.Fatal,
58 },
59 }
60 },
61 },
62 {
63 description: "valid syntax in annotations",
64 content: "- alert: Foo Is Down\n expr: up{job=\"foo\"} == 0\n annotations:\n summary: 'Instance {{ $labels.instance }} down'\n",
65 checker: newTemplateCheck,
66 prometheus: noProm,
67 problems: noProblems,
68 },
69 {
70 description: "invalid syntax in labels",
71 content: "- alert: Foo Is Down\n expr: up{job=\"foo\"} == 0\n labels:\n summary: 'Instance {{ $label.instance }} down'\n",
72 checker: newTemplateCheck,
73 prometheus: noProm,
74 problems: func(uri string) []checks.Problem {
75 return []checks.Problem{
76 {
77 Fragment: `summary: Instance {{ $label.instance }} down`,
78 Lines: []int{4},
79 Reporter: checks.TemplateCheckName,
80 Text: "template parse error: undefined variable \"$label\"",
81 Severity: checks.Fatal,
82 },
83 }
84 },
85 },
86 {
87 description: "invalid function in annotations",
88 content: "- alert: Foo Is Down\n expr: up{job=\"foo\"} == 0\n labels:\n summary: '{{ $value | xxx }}'\n",
89 checker: newTemplateCheck,
90 prometheus: noProm,
91 problems: func(uri string) []checks.Problem {
92 return []checks.Problem{
93 {
94 Fragment: `summary: {{ $value | xxx }}`,
95 Lines: []int{4},
96 Reporter: checks.TemplateCheckName,
97 Text: "template parse error: function \"xxx\" not defined",
98 Severity: checks.Fatal,
99 },
100 }
101 },
102 },
103 {
104 description: "valid syntax in labels",
105 content: "- alert: Foo Is Down\n expr: up{job=\"foo\"} == 0\n labels:\n summary: 'Instance {{ $labels.instance }} down'\n",
106 checker: newTemplateCheck,
107 prometheus: noProm,
108 problems: noProblems,
109 },
110 {
111 description: "{{ $value}} in label key",
112 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n '{{ $value}}': bar\n",
113 checker: newTemplateCheck,
114 prometheus: noProm,
115 problems: func(uri string) []checks.Problem {
116 return []checks.Problem{
117 {
118 Fragment: "{{ $value}}: bar",
119 Lines: []int{5},
120 Reporter: checks.TemplateCheckName,
121 Text: "using $value in labels will generate a new alert on every value change, move it to annotations",
122 Severity: checks.Bug,
123 },
124 }
125 },
126 },
127 {
128 description: "{{ $value }} in label key",
129 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n '{{ $value }}': bar\n",
130 checker: newTemplateCheck,
131 prometheus: noProm,
132 problems: func(uri string) []checks.Problem {
133 return []checks.Problem{
134 {
135 Fragment: "{{ $value }}: bar",
136 Lines: []int{5},
137 Reporter: checks.TemplateCheckName,
138 Text: "using $value in labels will generate a new alert on every value change, move it to annotations",
139 Severity: checks.Bug,
140 },
141 }
142 },
143 },
144 {
145 description: "{{$value}} in label value",
146 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n baz: '{{$value}}'\n",
147 checker: newTemplateCheck,
148 prometheus: noProm,
149 problems: func(uri string) []checks.Problem {
150 return []checks.Problem{
151 {
152 Fragment: "baz: {{$value}}",
153 Lines: []int{5},
154 Reporter: checks.TemplateCheckName,
155 Text: "using $value in labels will generate a new alert on every value change, move it to annotations",
156 Severity: checks.Bug,
157 },
158 }
159 },
160 },
161 {
162 description: "{{$value}} in multiple labels",
163 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: '{{ .Value }}'\n baz: '{{$value}}'\n",
164 checker: newTemplateCheck,
165 prometheus: noProm,
166 problems: func(uri string) []checks.Problem {
167 return []checks.Problem{
168 {
169 Fragment: "foo: {{ .Value }}",
170 Lines: []int{4},
171 Reporter: checks.TemplateCheckName,
172 Text: "using .Value in labels will generate a new alert on every value change, move it to annotations",
173 Severity: checks.Bug,
174 },
175 {
176 Fragment: "baz: {{$value}}",
177 Lines: []int{5},
178 Reporter: checks.TemplateCheckName,
179 Text: "using $value in labels will generate a new alert on every value change, move it to annotations",
180 Severity: checks.Bug,
181 },
182 }
183 },
184 },
185 {
186 description: "{{ $value }} in label value",
187 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n baz: |\n foo is {{ $value | humanizePercentage }}%\n",
188 checker: newTemplateCheck,
189 prometheus: noProm,
190 problems: func(uri string) []checks.Problem {
191 return []checks.Problem{
192 {
193 Fragment: "baz: foo is {{ $value | humanizePercentage }}%\n",
194 Lines: []int{5, 6},
195 Reporter: checks.TemplateCheckName,
196 Text: "using $value in labels will generate a new alert on every value change, move it to annotations",
197 Severity: checks.Bug,
198 },
199 }
200 },
201 },
202 {
203 description: "{{ $value }} in label value",
204 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n baz: |\n foo is {{$value|humanizePercentage}}%\n",
205 checker: newTemplateCheck,
206 prometheus: noProm,
207 problems: func(uri string) []checks.Problem {
208 return []checks.Problem{
209 {
210 Fragment: "baz: foo is {{$value|humanizePercentage}}%\n",
211 Lines: []int{5, 6},
212 Reporter: checks.TemplateCheckName,
213 Text: "using $value in labels will generate a new alert on every value change, move it to annotations",
214 Severity: checks.Bug,
215 },
216 }
217 },
218 },
219 {
220 description: "{{ .Value }} in label value",
221 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n baz: 'value {{ .Value }}'\n",
222 checker: newTemplateCheck,
223 prometheus: noProm,
224 problems: func(uri string) []checks.Problem {
225 return []checks.Problem{
226 {
227 Fragment: "baz: value {{ .Value }}",
228 Lines: []int{5},
229 Reporter: checks.TemplateCheckName,
230 Text: "using .Value in labels will generate a new alert on every value change, move it to annotations",
231 Severity: checks.Bug,
232 },
233 }
234 },
235 },
236 {
237 description: "{{ .Value|humanize }} in label value",
238 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n baz: '{{ .Value|humanize }}'\n",
239 checker: newTemplateCheck,
240 prometheus: noProm,
241 problems: func(uri string) []checks.Problem {
242 return []checks.Problem{
243 {
244 Fragment: "baz: {{ .Value|humanize }}",
245 Lines: []int{5},
246 Reporter: checks.TemplateCheckName,
247 Text: "using .Value in labels will generate a new alert on every value change, move it to annotations",
248 Severity: checks.Bug,
249 },
250 }
251 },
252 },
253 {
254 description: "{{ $foo := $value }} in label value",
255 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n baz: '{{ $foo := $value }}{{ $foo }}'\n",
256 checker: newTemplateCheck,
257 prometheus: noProm,
258 problems: func(uri string) []checks.Problem {
259 return []checks.Problem{
260 {
261 Fragment: "baz: {{ $foo := $value }}{{ $foo }}",
262 Lines: []int{5},
263 Reporter: checks.TemplateCheckName,
264 Text: "using $foo in labels will generate a new alert on every value change, move it to annotations",
265 Severity: checks.Bug,
266 },
267 }
268 },
269 },
270 {
271 description: "{{ $foo := .Value }} in label value",
272 content: "- alert: foo\n expr: sum(foo)\n labels:\n foo: bar\n baz: '{{ $foo := .Value }}{{ $foo }}'\n",
273 checker: newTemplateCheck,
274 prometheus: noProm,
275 problems: func(uri string) []checks.Problem {
276 return []checks.Problem{
277 {
278 Fragment: "baz: {{ $foo := .Value }}{{ $foo }}",
279 Lines: []int{5},
280 Reporter: checks.TemplateCheckName,
281 Text: "using $foo in labels will generate a new alert on every value change, move it to annotations",
282 Severity: checks.Bug,
283 },
284 }
285 },
286 },
287 {
288 description: "annotation label missing from metrics (by)",
289 content: "- alert: Foo Is Down\n expr: sum(foo) > 0\n annotations:\n summary: '{{ $labels.job }}'\n",
290 checker: newTemplateCheck,
291 prometheus: noProm,
292 problems: func(uri string) []checks.Problem {
293 return []checks.Problem{
294 {
295 Fragment: `summary: {{ $labels.job }}`,
296 Lines: []int{2, 4},
297 Reporter: checks.TemplateCheckName,
298 Text: `template is using "job" label but the query removes it`,
299 Severity: checks.Bug,
300 },
301 }
302 },
303 },
304 {
305 description: "annotation label missing from metrics (by)",
306 content: "- alert: Foo Is Down\n expr: sum(foo) > 0\n annotations:\n summary: '{{ .Labels.job }}'\n",
307 checker: newTemplateCheck,
308 prometheus: noProm,
309 problems: func(uri string) []checks.Problem {
310 return []checks.Problem{
311 {
312 Fragment: `summary: {{ .Labels.job }}`,
313 Lines: []int{2, 4},
314 Reporter: checks.TemplateCheckName,
315 Text: `template is using "job" label but the query removes it`,
316 Severity: checks.Bug,
317 },
318 }
319 },
320 },
321 {
322 description: "annotation label missing from metrics (without)",
323 content: "- alert: Foo Is Down\n expr: sum(foo) without(job) > 0\n annotations:\n summary: '{{ $labels.job }}'\n",
324 checker: newTemplateCheck,
325 prometheus: noProm,
326 problems: func(uri string) []checks.Problem {
327 return []checks.Problem{
328 {
329 Fragment: `summary: {{ $labels.job }}`,
330 Lines: []int{2, 4},
331 Reporter: checks.TemplateCheckName,
332 Text: `template is using "job" label but the query removes it`,
333 Severity: checks.Bug,
334 },
335 }
336 },
337 },
338 {
339 description: "annotation label missing from metrics (without)",
340 content: "- alert: Foo Is Down\n expr: sum(foo) without(job) > 0\n annotations:\n summary: '{{ .Labels.job }}'\n",
341 checker: newTemplateCheck,
342 prometheus: noProm,
343 problems: func(uri string) []checks.Problem {
344 return []checks.Problem{
345 {
346 Fragment: `summary: {{ .Labels.job }}`,
347 Lines: []int{2, 4},
348 Reporter: checks.TemplateCheckName,
349 Text: `template is using "job" label but the query removes it`,
350 Severity: checks.Bug,
351 },
352 }
353 },
354 },
355 {
356 description: "label missing from metrics (without)",
357 content: "- alert: Foo Is Down\n expr: sum(foo) without(job) > 0\n labels:\n summary: '{{ $labels.job }}'\n",
358 checker: newTemplateCheck,
359 prometheus: noProm,
360 problems: func(uri string) []checks.Problem {
361 return []checks.Problem{
362 {
363 Fragment: `summary: {{ $labels.job }}`,
364 Lines: []int{2, 4},
365 Reporter: checks.TemplateCheckName,
366 Text: `template is using "job" label but the query removes it`,
367 Severity: checks.Bug,
368 },
369 }
370 },
371 },
372 {
373 description: "annotation label missing from metrics (or)",
374 content: "- alert: Foo Is Down\n expr: sum(foo) by(job) or sum(bar)\n annotations:\n summary: '{{ .Labels.job }}'\n",
375 checker: newTemplateCheck,
376 prometheus: noProm,
377 problems: func(uri string) []checks.Problem {
378 return []checks.Problem{
379 {
380 Fragment: `summary: {{ .Labels.job }}`,
381 Lines: []int{2, 4},
382 Reporter: checks.TemplateCheckName,
383 Text: `template is using "job" label but the query removes it`,
384 Severity: checks.Bug,
385 },
386 }
387 },
388 },
389 {
390 description: "annotation label missing from metrics (1+)",
391 content: "- alert: Foo Is Down\n expr: 1 + sum(foo) by(notjob)\n annotations:\n summary: '{{ .Labels.job }}'\n",
392 checker: newTemplateCheck,
393 prometheus: noProm,
394 problems: func(uri string) []checks.Problem {
395 return []checks.Problem{
396 {
397 Fragment: `summary: {{ .Labels.job }}`,
398 Lines: []int{2, 4},
399 Reporter: checks.TemplateCheckName,
400 Text: `template is using "job" label but the query removes it`,
401 Severity: checks.Bug,
402 },
403 }
404 },
405 },
406 {
407 description: "annotation label missing from metrics (group_left)",
408 content: `
409- alert: Foo Is Down
410 expr: count(build_info) by (instance, version) != ignoring(package) group_left(foo) count(package_installed) by (instance, version, package)
411 annotations:
412 summary: '{{ $labels.instance }} on {{ .Labels.foo }} is down'
413 help: '{{ $labels.ixtance }}'
414`,
415 checker: newTemplateCheck,
416 prometheus: noProm,
417 problems: func(uri string) []checks.Problem {
418 return []checks.Problem{
419 {
420 Fragment: `help: {{ $labels.ixtance }}`,
421 Lines: []int{3, 6},
422 Reporter: checks.TemplateCheckName,
423 Text: `template is using "ixtance" label but the query removes it`,
424 Severity: checks.Bug,
425 },
426 }
427 },
428 },
429 {
430 description: "don't trigger for label_replace() provided labels",
431 content: `
432- alert: label_replace_not_checked_correctly
433 expr: |
434 label_replace(
435 sum by (pod) (pod_status) > 0
436 ,"cluster", "$1", "pod", "(.*)"
437 )
438 annotations:
439 summary: "Some error found in {{ $labels.cluster }}"
440`,
441 checker: newTemplateCheck,
442 prometheus: noProm,
443 problems: noProblems,
444 },
445 {
446 description: "annotation label present on metrics (absent)",
447 content: `
448- alert: Foo Is Missing
449 expr: absent(foo{job="bar", instance="server1"})
450 annotations:
451 summary: '{{ $labels.instance }} on {{ .Labels.job }} is missing'
452`,
453 checker: newTemplateCheck,
454 prometheus: noProm,
455 problems: noProblems,
456 },
457 {
458 description: "annotation label missing from metrics (absent)",
459 content: `
460- alert: Foo Is Missing
461 expr: absent(foo{job="bar"}) AND on(job) foo
462 labels:
463 instance: '{{ $labels.instance }}'
464 annotations:
465 summary: '{{ $labels.instance }} on {{ .Labels.foo }} is missing'
466 help: '{{ $labels.xxx }}'
467`,
468 checker: newTemplateCheck,
469 prometheus: noProm,
470 problems: func(uri string) []checks.Problem {
471 return []checks.Problem{
472 {
473 Fragment: "instance: {{ $labels.instance }}",
474 Lines: []int{3, 5},
475 Reporter: checks.TemplateCheckName,
476 Text: `template is using "instance" label but absent() is not passing it`,
477 Severity: checks.Bug,
478 },
479 {
480 Fragment: `summary: {{ $labels.instance }} on {{ .Labels.foo }} is missing`,
481 Lines: []int{3, 7},
482 Reporter: checks.TemplateCheckName,
483 Text: `template is using "instance" label but absent() is not passing it`,
484 Severity: checks.Bug,
485 },
486 {
487 Fragment: `summary: {{ $labels.instance }} on {{ .Labels.foo }} is missing`,
488 Lines: []int{3, 7},
489 Reporter: checks.TemplateCheckName,
490 Text: `template is using "foo" label but absent() is not passing it`,
491 Severity: checks.Bug,
492 },
493 {
494 Fragment: "help: {{ $labels.xxx }}",
495 Lines: []int{3, 8},
496 Reporter: checks.TemplateCheckName,
497 Text: `template is using "xxx" label but absent() is not passing it`,
498 Severity: checks.Bug,
499 },
500 }
501 },
502 },
503 {
504 description: "annotation label present on metrics (absent(sum))",
505 content: `
506- alert: Foo Is Missing
507 expr: absent(sum(foo) by(job, instance))
508 annotations:
509 summary: '{{ $labels.instance }} on {{ .Labels.job }} is missing'
510`,
511 checker: newTemplateCheck,
512 prometheus: noProm,
513 problems: noProblems,
514 },
515 {
516 description: "annotation label missing from metrics (absent(sum))",
517 content: `
518- alert: Foo Is Missing
519 expr: absent(sum(foo) by(job))
520 annotations:
521 summary: '{{ $labels.instance }} on {{ .Labels.job }} is missing'
522`,
523 checker: newTemplateCheck,
524 prometheus: noProm,
525 problems: func(uri string) []checks.Problem {
526 return []checks.Problem{
527 {
528 Fragment: `summary: {{ $labels.instance }} on {{ .Labels.job }} is missing`,
529 Lines: []int{3, 5},
530 Reporter: checks.TemplateCheckName,
531 Text: `template is using "instance" label but the query removes it`,
532 Severity: checks.Bug,
533 },
534 }
535 },
536 },
537 {
538 description: "annotation label missing from metrics (absent({job=~}))",
539 content: `
540- alert: Foo Is Missing
541 expr: absent({job=~".+"})
542 annotations:
543 summary: '{{ .Labels.job }} is missing'
544`,
545 checker: newTemplateCheck,
546 prometheus: noProm,
547 problems: func(uri string) []checks.Problem {
548 return []checks.Problem{
549 {
550 Fragment: `summary: {{ .Labels.job }} is missing`,
551 Lines: []int{3, 5},
552 Reporter: checks.TemplateCheckName,
553 Text: `template is using "job" label but absent() is not passing it`,
554 Severity: checks.Bug,
555 },
556 }
557 },
558 },
559 {
560 description: "annotation label missing from metrics (absent()) / multiple",
561 content: `
562- alert: Foo Is Missing
563 expr: absent(foo) or absent(bar)
564 annotations:
565 summary: '{{ .Labels.job }} / {{$labels.job}} is missing'
566`,
567 checker: newTemplateCheck,
568 prometheus: noProm,
569 problems: func(uri string) []checks.Problem {
570 return []checks.Problem{
571 {
572 Fragment: `summary: {{ .Labels.job }} / {{$labels.job}} is missing`,
573 Lines: []int{3, 5},
574 Reporter: checks.TemplateCheckName,
575 Text: `template is using "job" label but absent() is not passing it`,
576 Severity: checks.Bug,
577 },
578 {
579 Fragment: `summary: {{ .Labels.job }} / {{$labels.job}} is missing`,
580 Lines: []int{3, 5},
581 Reporter: checks.TemplateCheckName,
582 Text: `template is using "job" label but absent() is not passing it`,
583 Severity: checks.Bug,
584 },
585 }
586 },
587 },
588 {
589 description: "absent() * on() group_left(...) foo",
590 content: `
591- alert: Foo
592 expr: absent(foo{job="xxx"}) * on() group_left(cluster, env) bar
593 annotations:
594 summary: '{{ .Labels.job }} in cluster {{$labels.cluster}}/{{ $labels.env }} is missing'
595`,
596 checker: newTemplateCheck,
597 prometheus: noProm,
598 problems: noProblems,
599 },
600 {
601 description: "absent() * on() group_left() bar",
602 content: `
603- alert: Foo
604 expr: absent(foo{job="xxx"}) * on() group_left() bar
605 annotations:
606 summary: '{{ .Labels.job }} in cluster {{$labels.cluster}}/{{ $labels.env }} is missing'
607`,
608 checker: newTemplateCheck,
609 prometheus: noProm,
610 problems: noProblems,
611 },
612 {
613 description: "bar * on() group_right(...) absent()",
614 content: `
615- alert: Foo
616 expr: bar * on() group_right(cluster, env) absent(foo{job="xxx"})
617 annotations:
618 summary: '{{ .Labels.job }} in cluster {{$labels.cluster}}/{{ $labels.env }} is missing'
619`,
620 checker: newTemplateCheck,
621 prometheus: noProm,
622 problems: noProblems,
623 },
624 {
625 description: "bar * on() group_right() absent()",
626 content: `
627- alert: Foo
628 expr: bar * on() group_right() absent(foo{job="xxx"})
629 annotations:
630 summary: '{{ .Labels.job }} in cluster {{$labels.cluster}}/{{ $labels.env }} is missing'
631`,
632 checker: newTemplateCheck,
633 prometheus: noProm,
634 problems: noProblems,
635 },
636 {
637 description: "foo and on() absent(bar)",
638 content: `
639- alert: Foo
640 expr: foo and on() absent(bar)
641 annotations:
642 summary: '{{ .Labels.job }} is missing'
643`,
644 checker: newTemplateCheck,
645 prometheus: noProm,
646 problems: noProblems,
647 },
648 {
649 description: "no humanize on rate()",
650 content: `
651- alert: Foo
652 expr: rate(errors[2m]) > 0
653 annotations:
654 summary: "Seeing {{ $value }} errors"
655`,
656 checker: newTemplateCheck,
657 prometheus: noProm,
658 problems: func(uri string) []checks.Problem {
659 return []checks.Problem{
660 {
661 Fragment: "rate(errors[2m])",
662 Lines: []int{3, 5},
663 Reporter: checks.TemplateCheckName,
664 Text: humanizeText("rate(errors[2m])"),
665 Severity: checks.Information,
666 },
667 }
668 },
669 },
670 {
671 description: "no humanize on rate() / alias",
672 content: `
673- alert: Foo
674 expr: rate(errors[2m]) > 0
675 annotations:
676 summary: "{{ $foo := $value }}{{ $bar := $foo }} Seeing {{ $bar }} errors"
677`,
678 checker: newTemplateCheck,
679 prometheus: noProm,
680 problems: func(uri string) []checks.Problem {
681 return []checks.Problem{
682 {
683 Fragment: "rate(errors[2m])",
684 Lines: []int{3, 5},
685 Reporter: checks.TemplateCheckName,
686 Text: humanizeText("rate(errors[2m])"),
687 Severity: checks.Information,
688 },
689 }
690 },
691 },
692 {
693 description: "no humanize on irate()",
694 content: `
695- alert: Foo
696 expr: irate(errors[2m]) > 0
697 annotations:
698 summary: "Seeing {{ .Value }} errors"
699`,
700 checker: newTemplateCheck,
701 prometheus: noProm,
702 problems: func(uri string) []checks.Problem {
703 return []checks.Problem{
704 {
705 Fragment: "irate(errors[2m])",
706 Lines: []int{3, 5},
707 Reporter: checks.TemplateCheckName,
708 Text: humanizeText("irate(errors[2m])"),
709 Severity: checks.Information,
710 },
711 }
712 },
713 },
714 {
715 description: "no humanize on irate()",
716 content: `
717- alert: Foo
718 expr: deriv(errors[2m]) > 0
719 annotations:
720 summary: "Seeing {{ .Value }} errors"
721`,
722 checker: newTemplateCheck,
723 prometheus: noProm,
724 problems: func(uri string) []checks.Problem {
725 return []checks.Problem{
726 {
727 Fragment: "deriv(errors[2m])",
728 Lines: []int{3, 5},
729 Reporter: checks.TemplateCheckName,
730 Text: humanizeText("deriv(errors[2m])"),
731 Severity: checks.Information,
732 },
733 }
734 },
735 },
736 {
737 description: "rate() but no $value",
738 content: `
739- alert: Foo
740 expr: rate(errors[2m]) > 0
741 annotations:
742 summary: "Seeing errors"
743`,
744 checker: newTemplateCheck,
745 prometheus: noProm,
746 problems: noProblems,
747 },
748 {
749 description: "humanize passed to value",
750 content: `
751- alert: Foo
752 expr: rate(errors[2m]) > 0
753 annotations:
754 summary: "Seeing {{ $value | humanize }} errors"
755`,
756 checker: newTemplateCheck,
757 prometheus: noProm,
758 problems: noProblems,
759 },
760 {
761 description: "humanizePercentage passed to value",
762 content: `
763- alert: Foo
764 expr: (sum(rate(errors[2m])) / sum(rate(requests[2m]))) > 0.1
765 annotations:
766 summary: "Seeing {{ $value | humanizePercentage }} errors"
767`,
768 checker: newTemplateCheck,
769 prometheus: noProm,
770 problems: noProblems,
771 },
772 {
773 description: "humanizeDuration passed to value",
774 content: `
775- alert: Foo
776 expr: (sum(rate(errors[2m])) / sum(rate(requests[2m]))) > 0.1
777 annotations:
778 summary: "Seeing {{ $value | humanizeDuration }} errors"
779`,
780 checker: newTemplateCheck,
781 prometheus: noProm,
782 problems: noProblems,
783 },
784 {
785 description: "humanize not needed on count()",
786 content: `
787- alert: Foo
788 expr: count(rate(errors[2m]) > 0) > 0
789 annotations:
790 summary: "Seeing {{ $value }} instances with errors"
791`,
792 checker: newTemplateCheck,
793 prometheus: noProm,
794 problems: noProblems,
795 },
796 {
797 description: "humanize not needed on rate() used in RHS",
798 content: `
799- alert: Foo
800 expr: foo > on() sum(rate(errors[2m])
801 annotations:
802 summary: "Seeing {{ $value }} instances with errors"
803`,
804 checker: newTemplateCheck,
805 prometheus: noProm,
806 problems: noProblems,
807 },
808 {
809 description: "humanize not needed on round(rate())",
810 content: `
811- alert: Foo
812 expr: round(rate(errors_total[5m]), 1) > 0
813 annotations:
814 summary: "Seeing {{ $value }} instances with errors"
815`,
816 checker: newTemplateCheck,
817 prometheus: noProm,
818 problems: noProblems,
819 },
820 {
821 description: "toTime",
822 content: `
823- alert: Foo
824 expr: up == 0
825 annotations:
826 summary: "{{ $value | toTime }}"
827`,
828 checker: newTemplateCheck,
829 prometheus: noProm,
830 problems: noProblems,
831 },
832 {
833 description: "template query with syntax error",
834 content: `
835- alert: Foo
836 expr: up == 0
837 annotations:
838 summary: |
839 {{ with printf "sum({job='%s'}) by(" .Labels.job | query }}
840 {{ . | first | label "instance" }}
841 {{ end }}
842`,
843 checker: newTemplateCheck,
844 prometheus: noProm,
845 problems: func(uri string) []checks.Problem {
846 return []checks.Problem{
847 {
848 Fragment: "summary: {{ with printf \"sum({job='%s'}) by(\" .Labels.job | query }}\n{{ . | first | label \"instance\" }}\n{{ end }}\n",
849 Lines: []int{5, 6, 7, 8},
850 Reporter: checks.TemplateCheckName,
851 Text: `template parse error: 163: executing "summary" at <query>: error calling query: 1:18: parse error: unclosed left parenthesis`,
852 Severity: checks.Fatal,
853 },
854 }
855 },
856 },
857 {
858 description: "template query with bogus function",
859 content: `
860- alert: Foo
861 expr: up == 0
862 annotations:
863 summary: |
864 {{ with printf "suz({job='%s'})" .Labels.job | query }}
865 {{ . | first | label "instance" }}
866 {{ end }}
867`,
868 checker: newTemplateCheck,
869 prometheus: noProm,
870 problems: func(uri string) []checks.Problem {
871 return []checks.Problem{
872 {
873 Fragment: "summary: {{ with printf \"suz({job='%s'})\" .Labels.job | query }}\n{{ . | first | label \"instance\" }}\n{{ end }}\n",
874 Lines: []int{5, 6, 7, 8},
875 Reporter: checks.TemplateCheckName,
876 Text: `template parse error: 159: executing "summary" at <query>: error calling query: 1:1: parse error: unknown function with name "suz"`,
877 Severity: checks.Fatal,
878 },
879 }
880 },
881 },
882 {
883 description: "$value | first",
884 content: `
885- alert: Foo
886 expr: rate(errors[2m])
887 annotations:
888 summary: "{{ $value | first }} errors"
889`,
890 checker: newTemplateCheck,
891 prometheus: noProm,
892 problems: func(uri string) []checks.Problem {
893 return []checks.Problem{
894 {
895 Fragment: "summary: {{ $value | first }} errors",
896 Lines: []int{5},
897 Reporter: checks.TemplateCheckName,
898 Text: `template parse error: 124: executing "summary" at <first>: wrong type for value; expected template.queryResult; got float64`,
899 Severity: checks.Fatal,
900 },
901 {
902 Fragment: "rate(errors[2m])",
903 Lines: []int{3, 5},
904 Reporter: checks.TemplateCheckName,
905 Text: humanizeText("rate(errors[2m])"),
906 Severity: checks.Information,
907 },
908 }
909 },
910 },
911 {
912 description: "template query with with bogus range",
913 content: `
914- alert: Foo
915 expr: up == 0
916 annotations:
917 summary: |
918 {{ range query "up xxx" }}
919 {{ .Labels.instance }} {{ .Value }}
920 {{ end }}
921`,
922 checker: newTemplateCheck,
923 prometheus: noProm,
924 problems: func(uri string) []checks.Problem {
925 return []checks.Problem{
926 {
927 Fragment: "summary: {{ range query \"up xxx\" }}\n{{ .Labels.instance }} {{ .Value }}\n{{ end }}\n",
928 Lines: []int{5, 6, 7, 8},
929 Reporter: checks.TemplateCheckName,
930 Text: `template parse error: 121: executing "summary" at <query "up xxx">: error calling query: 1:4: parse error: unexpected identifier "xxx"`,
931 Severity: checks.Fatal,
932 },
933 }
934 },
935 },
936 {
937 description: "template query with valid expr",
938 content: `
939- alert: Foo
940 expr: up{job="bar"} == 0
941 annotations:
942 summary: Instance {{ printf "up{job='bar', instance='%s'}" $labels.instance | query | first | value }} is down'
943`,
944 checker: newTemplateCheck,
945 prometheus: noProm,
946 problems: noProblems,
947 },
948 /*
949 TODO
950 {
951 description: "template query removes instance",
952 content: `
953 - alert: Foo
954 expr: up == 0
955 annotations:
956 summary: |
957 {{ with printf "sum({job='%s'})" .Labels.job | query }}
958 {{ . | first | label "instance" }}
959 {{ end }}
960 `,
961 checker: newTemplateCheck,
962 prometheus: noProm,
963 problems: func(uri string) []checks.Problem {
964 return []checks.Problem{
965 {
966 Fragment: `summary: |
967 {{ with printf "sum({job='%s'})" .Labels.job | query }}
968 {{ . | first | label "instance" }}`,
969 Lines: []int{5, 6, 7, 8},
970 Reporter: checks.TemplateCheckName,
971 Text: `"summary" annotation template sends a query that is using "instance" label but that query removes it`,
972 Severity: checks.Bug,
973 },
974 }
975 },
976 },
977 */
978 {
979 description: "sub aggregation",
980 content: `
981- alert: Foo
982 expr: |
983 (
984 sum(foo:sum > 0) without(notify)
985 * on(job) group_left(notify)
986 job:notify
987 )
988 and on(job)
989 sum(foo:count) by(job) > 20
990 labels:
991 notify: "{{ $labels.notify }}"
992`,
993 checker: newTemplateCheck,
994 prometheus: noProm,
995 problems: noProblems,
996 },
997 {
998 description: "abs / scalar",
999 content: `
1000- alert: ScyllaNonBalancedcqlTraffic
1001 expr: >
1002 abs(rate(scylla_cql_updates{conditional="no"}[1m]) - scalar(avg(rate(scylla_cql_updates{conditional="no"}[1m]))))
1003 /
1004 scalar(stddev(rate(scylla_cql_updates{conditional="no"}[1m])) + 100) > 2
1005 for: 10s
1006 labels:
1007 advisor: balanced
1008 dashboard: cql
1009 severity: moderate
1010 status: "1"
1011 team: team_devops
1012 annotations:
1013 description: CQL queries are not balanced among shards {{ $labels.instance }} shard {{ $labels.shard }}
1014 summary: CQL queries are not balanced
1015`,
1016 checker: newTemplateCheck,
1017 prometheus: noProm,
1018 problems: noProblems,
1019 },
1020 }
1021 runTests(t, testCases)
1022}
1023