cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.31.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/checks/alerts_template_test.go

983lines · 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(job) + 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: "annotation label present on metrics (absent)",
431 content: `
432- alert: Foo Is Missing
433 expr: absent(foo{job="bar", instance="server1"})
434 annotations:
435 summary: '{{ $labels.instance }} on {{ .Labels.job }} is missing'
436`,
437 checker: newTemplateCheck,
438 prometheus: noProm,
439 problems: noProblems,
440 },
441 {
442 description: "annotation label missing from metrics (absent)",
443 content: `
444- alert: Foo Is Missing
445 expr: absent(foo{job="bar"}) AND on(job) foo
446 labels:
447 instance: '{{ $labels.instance }}'
448 annotations:
449 summary: '{{ $labels.instance }} on {{ .Labels.foo }} is missing'
450 help: '{{ $labels.xxx }}'
451`,
452 checker: newTemplateCheck,
453 prometheus: noProm,
454 problems: func(uri string) []checks.Problem {
455 return []checks.Problem{
456 {
457 Fragment: "instance: {{ $labels.instance }}",
458 Lines: []int{3, 5},
459 Reporter: checks.TemplateCheckName,
460 Text: `template is using "instance" label but absent() is not passing it`,
461 Severity: checks.Bug,
462 },
463 {
464 Fragment: `summary: {{ $labels.instance }} on {{ .Labels.foo }} is missing`,
465 Lines: []int{3, 7},
466 Reporter: checks.TemplateCheckName,
467 Text: `template is using "instance" label but absent() is not passing it`,
468 Severity: checks.Bug,
469 },
470 {
471 Fragment: `summary: {{ $labels.instance }} on {{ .Labels.foo }} is missing`,
472 Lines: []int{3, 7},
473 Reporter: checks.TemplateCheckName,
474 Text: `template is using "foo" label but absent() is not passing it`,
475 Severity: checks.Bug,
476 },
477 {
478 Fragment: "help: {{ $labels.xxx }}",
479 Lines: []int{3, 8},
480 Reporter: checks.TemplateCheckName,
481 Text: `template is using "xxx" label but absent() is not passing it`,
482 Severity: checks.Bug,
483 },
484 }
485 },
486 },
487 {
488 description: "annotation label present on metrics (absent(sum))",
489 content: `
490- alert: Foo Is Missing
491 expr: absent(sum(foo) by(job, instance))
492 annotations:
493 summary: '{{ $labels.instance }} on {{ .Labels.job }} is missing'
494`,
495 checker: newTemplateCheck,
496 prometheus: noProm,
497 problems: noProblems,
498 },
499 {
500 description: "annotation label missing from metrics (absent(sum))",
501 content: `
502- alert: Foo Is Missing
503 expr: absent(sum(foo) by(job))
504 annotations:
505 summary: '{{ $labels.instance }} on {{ .Labels.job }} is missing'
506`,
507 checker: newTemplateCheck,
508 prometheus: noProm,
509 problems: func(uri string) []checks.Problem {
510 return []checks.Problem{
511 {
512 Fragment: `summary: {{ $labels.instance }} on {{ .Labels.job }} is missing`,
513 Lines: []int{3, 5},
514 Reporter: checks.TemplateCheckName,
515 Text: `template is using "instance" label but the query removes it`,
516 Severity: checks.Bug,
517 },
518 }
519 },
520 },
521 {
522 description: "annotation label missing from metrics (absent({job=~}))",
523 content: `
524- alert: Foo Is Missing
525 expr: absent({job=~".+"})
526 annotations:
527 summary: '{{ .Labels.job }} is missing'
528`,
529 checker: newTemplateCheck,
530 prometheus: noProm,
531 problems: func(uri string) []checks.Problem {
532 return []checks.Problem{
533 {
534 Fragment: `summary: {{ .Labels.job }} is missing`,
535 Lines: []int{3, 5},
536 Reporter: checks.TemplateCheckName,
537 Text: `template is using "job" label but absent() is not passing it`,
538 Severity: checks.Bug,
539 },
540 }
541 },
542 },
543 {
544 description: "annotation label missing from metrics (absent()) / multiple",
545 content: `
546- alert: Foo Is Missing
547 expr: absent(foo) or absent(bar)
548 annotations:
549 summary: '{{ .Labels.job }} / {{$labels.job}} is missing'
550`,
551 checker: newTemplateCheck,
552 prometheus: noProm,
553 problems: func(uri string) []checks.Problem {
554 return []checks.Problem{
555 {
556 Fragment: `summary: {{ .Labels.job }} / {{$labels.job}} is missing`,
557 Lines: []int{3, 5},
558 Reporter: checks.TemplateCheckName,
559 Text: `template is using "job" label but absent() is not passing it`,
560 Severity: checks.Bug,
561 },
562 {
563 Fragment: `summary: {{ .Labels.job }} / {{$labels.job}} is missing`,
564 Lines: []int{3, 5},
565 Reporter: checks.TemplateCheckName,
566 Text: `template is using "job" label but absent() is not passing it`,
567 Severity: checks.Bug,
568 },
569 }
570 },
571 },
572 {
573 description: "absent() * on() group_left(...) foo",
574 content: `
575- alert: Foo
576 expr: absent(foo{job="xxx"}) * on() group_left(cluster, env) bar
577 annotations:
578 summary: '{{ .Labels.job }} in cluster {{$labels.cluster}}/{{ $labels.env }} is missing'
579`,
580 checker: newTemplateCheck,
581 prometheus: noProm,
582 problems: noProblems,
583 },
584 {
585 description: "absent() * on() group_left() bar",
586 content: `
587- alert: Foo
588 expr: absent(foo{job="xxx"}) * on() group_left() bar
589 annotations:
590 summary: '{{ .Labels.job }} in cluster {{$labels.cluster}}/{{ $labels.env }} is missing'
591`,
592 checker: newTemplateCheck,
593 prometheus: noProm,
594 problems: noProblems,
595 },
596 {
597 description: "bar * on() group_right(...) absent()",
598 content: `
599- alert: Foo
600 expr: bar * on() group_right(cluster, env) absent(foo{job="xxx"})
601 annotations:
602 summary: '{{ .Labels.job }} in cluster {{$labels.cluster}}/{{ $labels.env }} is missing'
603`,
604 checker: newTemplateCheck,
605 prometheus: noProm,
606 problems: noProblems,
607 },
608 {
609 description: "bar * on() group_right() absent()",
610 content: `
611- alert: Foo
612 expr: bar * on() group_right() absent(foo{job="xxx"})
613 annotations:
614 summary: '{{ .Labels.job }} in cluster {{$labels.cluster}}/{{ $labels.env }} is missing'
615`,
616 checker: newTemplateCheck,
617 prometheus: noProm,
618 problems: noProblems,
619 },
620 {
621 description: "foo and on() absent(bar)",
622 content: `
623- alert: Foo
624 expr: foo and on() absent(bar)
625 annotations:
626 summary: '{{ .Labels.job }} is missing'
627`,
628 checker: newTemplateCheck,
629 prometheus: noProm,
630 problems: noProblems,
631 },
632 {
633 description: "no humanize on rate()",
634 content: `
635- alert: Foo
636 expr: rate(errors[2m]) > 0
637 annotations:
638 summary: "Seeing {{ $value }} errors"
639`,
640 checker: newTemplateCheck,
641 prometheus: noProm,
642 problems: func(uri string) []checks.Problem {
643 return []checks.Problem{
644 {
645 Fragment: "rate(errors[2m])",
646 Lines: []int{3, 5},
647 Reporter: checks.TemplateCheckName,
648 Text: humanizeText("rate(errors[2m])"),
649 Severity: checks.Information,
650 },
651 }
652 },
653 },
654 {
655 description: "no humanize on rate() / alias",
656 content: `
657- alert: Foo
658 expr: rate(errors[2m]) > 0
659 annotations:
660 summary: "{{ $foo := $value }}{{ $bar := $foo }} Seeing {{ $bar }} errors"
661`,
662 checker: newTemplateCheck,
663 prometheus: noProm,
664 problems: func(uri string) []checks.Problem {
665 return []checks.Problem{
666 {
667 Fragment: "rate(errors[2m])",
668 Lines: []int{3, 5},
669 Reporter: checks.TemplateCheckName,
670 Text: humanizeText("rate(errors[2m])"),
671 Severity: checks.Information,
672 },
673 }
674 },
675 },
676 {
677 description: "no humanize on irate()",
678 content: `
679- alert: Foo
680 expr: irate(errors[2m]) > 0
681 annotations:
682 summary: "Seeing {{ .Value }} errors"
683`,
684 checker: newTemplateCheck,
685 prometheus: noProm,
686 problems: func(uri string) []checks.Problem {
687 return []checks.Problem{
688 {
689 Fragment: "irate(errors[2m])",
690 Lines: []int{3, 5},
691 Reporter: checks.TemplateCheckName,
692 Text: humanizeText("irate(errors[2m])"),
693 Severity: checks.Information,
694 },
695 }
696 },
697 },
698 {
699 description: "no humanize on irate()",
700 content: `
701- alert: Foo
702 expr: deriv(errors[2m]) > 0
703 annotations:
704 summary: "Seeing {{ .Value }} errors"
705`,
706 checker: newTemplateCheck,
707 prometheus: noProm,
708 problems: func(uri string) []checks.Problem {
709 return []checks.Problem{
710 {
711 Fragment: "deriv(errors[2m])",
712 Lines: []int{3, 5},
713 Reporter: checks.TemplateCheckName,
714 Text: humanizeText("deriv(errors[2m])"),
715 Severity: checks.Information,
716 },
717 }
718 },
719 },
720 {
721 description: "rate() but no $value",
722 content: `
723- alert: Foo
724 expr: rate(errors[2m]) > 0
725 annotations:
726 summary: "Seeing errors"
727`,
728 checker: newTemplateCheck,
729 prometheus: noProm,
730 problems: noProblems,
731 },
732 {
733 description: "humanize passed to value",
734 content: `
735- alert: Foo
736 expr: rate(errors[2m]) > 0
737 annotations:
738 summary: "Seeing {{ $value | humanize }} errors"
739`,
740 checker: newTemplateCheck,
741 prometheus: noProm,
742 problems: noProblems,
743 },
744 {
745 description: "humanizePercentage passed to value",
746 content: `
747- alert: Foo
748 expr: (sum(rate(errors[2m])) / sum(rate(requests[2m]))) > 0.1
749 annotations:
750 summary: "Seeing {{ $value | humanizePercentage }} errors"
751`,
752 checker: newTemplateCheck,
753 prometheus: noProm,
754 problems: noProblems,
755 },
756 {
757 description: "humanizeDuration passed to value",
758 content: `
759- alert: Foo
760 expr: (sum(rate(errors[2m])) / sum(rate(requests[2m]))) > 0.1
761 annotations:
762 summary: "Seeing {{ $value | humanizeDuration }} errors"
763`,
764 checker: newTemplateCheck,
765 prometheus: noProm,
766 problems: noProblems,
767 },
768 {
769 description: "humanize not needed on count()",
770 content: `
771- alert: Foo
772 expr: count(rate(errors[2m]) > 0) > 0
773 annotations:
774 summary: "Seeing {{ $value }} instances with errors"
775`,
776 checker: newTemplateCheck,
777 prometheus: noProm,
778 problems: noProblems,
779 },
780 {
781 description: "humanize not needed on rate() used in RHS",
782 content: `
783- alert: Foo
784 expr: foo > on() sum(rate(errors[2m])
785 annotations:
786 summary: "Seeing {{ $value }} instances with errors"
787`,
788 checker: newTemplateCheck,
789 prometheus: noProm,
790 problems: noProblems,
791 },
792 {
793 description: "humanize not needed on round(rate())",
794 content: `
795- alert: Foo
796 expr: round(rate(errors_total[5m]), 1) > 0
797 annotations:
798 summary: "Seeing {{ $value }} instances with errors"
799`,
800 checker: newTemplateCheck,
801 prometheus: noProm,
802 problems: noProblems,
803 },
804 {
805 description: "toTime",
806 content: `
807- alert: Foo
808 expr: up == 0
809 annotations:
810 summary: "{{ $value | toTime }}"
811`,
812 checker: newTemplateCheck,
813 prometheus: noProm,
814 problems: noProblems,
815 },
816 {
817 description: "template query with syntax error",
818 content: `
819- alert: Foo
820 expr: up == 0
821 annotations:
822 summary: |
823 {{ with printf "sum({job='%s'}) by(" .Labels.job | query }}
824 {{ . | first | label "instance" }}
825 {{ end }}
826`,
827 checker: newTemplateCheck,
828 prometheus: noProm,
829 problems: func(uri string) []checks.Problem {
830 return []checks.Problem{
831 {
832 Fragment: "summary: {{ with printf \"sum({job='%s'}) by(\" .Labels.job | query }}\n{{ . | first | label \"instance\" }}\n{{ end }}\n",
833 Lines: []int{5, 6, 7, 8},
834 Reporter: checks.TemplateCheckName,
835 Text: `template parse error: 163: executing "summary" at <query>: error calling query: 1:18: parse error: unclosed left parenthesis`,
836 Severity: checks.Fatal,
837 },
838 }
839 },
840 },
841 {
842 description: "template query with bogus function",
843 content: `
844- alert: Foo
845 expr: up == 0
846 annotations:
847 summary: |
848 {{ with printf "suz({job='%s'})" .Labels.job | query }}
849 {{ . | first | label "instance" }}
850 {{ end }}
851`,
852 checker: newTemplateCheck,
853 prometheus: noProm,
854 problems: func(uri string) []checks.Problem {
855 return []checks.Problem{
856 {
857 Fragment: "summary: {{ with printf \"suz({job='%s'})\" .Labels.job | query }}\n{{ . | first | label \"instance\" }}\n{{ end }}\n",
858 Lines: []int{5, 6, 7, 8},
859 Reporter: checks.TemplateCheckName,
860 Text: `template parse error: 159: executing "summary" at <query>: error calling query: 1:1: parse error: unknown function with name "suz"`,
861 Severity: checks.Fatal,
862 },
863 }
864 },
865 },
866 {
867 description: "$value | first",
868 content: `
869- alert: Foo
870 expr: rate(errors[2m])
871 annotations:
872 summary: "{{ $value | first }} errors"
873`,
874 checker: newTemplateCheck,
875 prometheus: noProm,
876 problems: func(uri string) []checks.Problem {
877 return []checks.Problem{
878 {
879 Fragment: "summary: {{ $value | first }} errors",
880 Lines: []int{5},
881 Reporter: checks.TemplateCheckName,
882 Text: `template parse error: 124: executing "summary" at <first>: wrong type for value; expected template.queryResult; got float64`,
883 Severity: checks.Fatal,
884 },
885 {
886 Fragment: "rate(errors[2m])",
887 Lines: []int{3, 5},
888 Reporter: checks.TemplateCheckName,
889 Text: humanizeText("rate(errors[2m])"),
890 Severity: checks.Information,
891 },
892 }
893 },
894 },
895 {
896 description: "template query with with bogus range",
897 content: `
898- alert: Foo
899 expr: up == 0
900 annotations:
901 summary: |
902 {{ range query "up xxx" }}
903 {{ .Labels.instance }} {{ .Value }}
904 {{ end }}
905`,
906 checker: newTemplateCheck,
907 prometheus: noProm,
908 problems: func(uri string) []checks.Problem {
909 return []checks.Problem{
910 {
911 Fragment: "summary: {{ range query \"up xxx\" }}\n{{ .Labels.instance }} {{ .Value }}\n{{ end }}\n",
912 Lines: []int{5, 6, 7, 8},
913 Reporter: checks.TemplateCheckName,
914 Text: `template parse error: 121: executing "summary" at <query "up xxx">: error calling query: 1:4: parse error: unexpected identifier "xxx"`,
915 Severity: checks.Fatal,
916 },
917 }
918 },
919 },
920 {
921 description: "template query with valid expr",
922 content: `
923- alert: Foo
924 expr: up{job="bar"} == 0
925 annotations:
926 summary: Instance {{ printf "up{job='bar', instance='%s'}" $labels.instance | query | first | value }} is down'
927`,
928 checker: newTemplateCheck,
929 prometheus: noProm,
930 problems: noProblems,
931 },
932 /*
933 TODO
934 {
935 description: "template query removes instance",
936 content: `
937 - alert: Foo
938 expr: up == 0
939 annotations:
940 summary: |
941 {{ with printf "sum({job='%s'})" .Labels.job | query }}
942 {{ . | first | label "instance" }}
943 {{ end }}
944 `,
945 checker: newTemplateCheck,
946 prometheus: noProm,
947 problems: func(uri string) []checks.Problem {
948 return []checks.Problem{
949 {
950 Fragment: `summary: |
951 {{ with printf "sum({job='%s'})" .Labels.job | query }}
952 {{ . | first | label "instance" }}`,
953 Lines: []int{5, 6, 7, 8},
954 Reporter: checks.TemplateCheckName,
955 Text: `"summary" annotation template sends a query that is using "instance" label but that query removes it`,
956 Severity: checks.Bug,
957 },
958 }
959 },
960 },
961 */
962 {
963 description: "sub aggregation",
964 content: `
965- alert: Foo
966 expr: |
967 (
968 sum(foo:sum > 0) without(notify)
969 * on(job) group_left(notify)
970 job:notify
971 )
972 and on(job)
973 sum(foo:count) by(job) > 20
974 labels:
975 notify: "{{ $labels.notify }}"
976`,
977 checker: newTemplateCheck,
978 prometheus: noProm,
979 problems: noProblems,
980 },
981 }
982 runTests(t, testCases)
983}
984