1
2
3
4
5
6
7
8
9
10
11
12
13
14 package v1
15
16 import (
17 "bytes"
18 "encoding/json"
19 "errors"
20 "fmt"
21 "io"
22 "net/http"
23 "net/http/httptest"
24 "regexp"
25 "testing"
26 "time"
27
28 "github.com/prometheus/common/model"
29 "github.com/stretchr/testify/require"
30
31 "github.com/prometheus/alertmanager/config"
32 "github.com/prometheus/alertmanager/dispatch"
33 "github.com/prometheus/alertmanager/pkg/labels"
34 "github.com/prometheus/alertmanager/provider"
35 "github.com/prometheus/alertmanager/types"
36 )
37
38
39 type fakeAlerts struct {
40 fps map[model.Fingerprint]int
41 alerts []*types.Alert
42 err error
43 }
44
45 func newFakeAlerts(alerts []*types.Alert, withErr bool) *fakeAlerts {
46 fps := make(map[model.Fingerprint]int)
47 for i, a := range alerts {
48 fps[a.Fingerprint()] = i
49 }
50 f := &fakeAlerts{
51 alerts: alerts,
52 fps: fps,
53 }
54 if withErr {
55 f.err = errors.New("error occurred")
56 }
57 return f
58 }
59
60 func (f *fakeAlerts) Subscribe() provider.AlertIterator { return nil }
61 func (f *fakeAlerts) Get(model.Fingerprint) (*types.Alert, error) { return nil, nil }
62 func (f *fakeAlerts) Put(alerts ...*types.Alert) error {
63 return f.err
64 }
65
66 func (f *fakeAlerts) GetPending() provider.AlertIterator {
67 ch := make(chan *types.Alert)
68 done := make(chan struct{})
69 go func() {
70 defer close(ch)
71 for _, a := range f.alerts {
72 ch <- a
73 }
74 }()
75 return provider.NewAlertIterator(ch, done, f.err)
76 }
77
78 func newGetAlertStatus(f *fakeAlerts) func(model.Fingerprint) types.AlertStatus {
79 return func(fp model.Fingerprint) types.AlertStatus {
80 status := types.AlertStatus{SilencedBy: []string{}, InhibitedBy: []string{}}
81
82 i, ok := f.fps[fp]
83 if !ok {
84 return status
85 }
86 alert := f.alerts[i]
87 switch alert.Labels["state"] {
88 case "active":
89 status.State = types.AlertStateActive
90 case "unprocessed":
91 status.State = types.AlertStateUnprocessed
92 case "suppressed":
93 status.State = types.AlertStateSuppressed
94 }
95 if alert.Labels["silenced_by"] != "" {
96 status.SilencedBy = append(status.SilencedBy, string(alert.Labels["silenced_by"]))
97 }
98 if alert.Labels["inhibited_by"] != "" {
99 status.InhibitedBy = append(status.InhibitedBy, string(alert.Labels["inhibited_by"]))
100 }
101 return status
102 }
103 }
104
105 func TestAddAlerts(t *testing.T) {
106 now := func(offset int) time.Time {
107 return time.Now().Add(time.Duration(offset) * time.Second)
108 }
109
110 for i, tc := range []struct {
111 start, end time.Time
112 err bool
113 code int
114 }{
115 {time.Time{}, time.Time{}, false, 200},
116 {now(0), time.Time{}, false, 200},
117 {time.Time{}, now(-1), false, 200},
118 {time.Time{}, now(0), false, 200},
119 {time.Time{}, now(1), false, 200},
120 {now(-2), now(-1), false, 200},
121 {now(1), now(2), false, 200},
122 {now(1), now(0), false, 400},
123 {now(0), time.Time{}, true, 500},
124 } {
125 alerts := []model.Alert{{
126 StartsAt: tc.start,
127 EndsAt: tc.end,
128 Labels: model.LabelSet{"label1": "test1"},
129 Annotations: model.LabelSet{"annotation1": "some text"},
130 }}
131 b, err := json.Marshal(&alerts)
132 if err != nil {
133 t.Errorf("Unexpected error %v", err)
134 }
135
136 alertsProvider := newFakeAlerts([]*types.Alert{}, tc.err)
137 api := New(alertsProvider, nil, newGetAlertStatus(alertsProvider), nil, nil, nil)
138 defaultGlobalConfig := config.DefaultGlobalConfig()
139 route := config.Route{}
140 api.Update(&config.Config{
141 Global: &defaultGlobalConfig,
142 Route: &route,
143 })
144
145 r, err := http.NewRequest("POST", "/api/v1/alerts", bytes.NewReader(b))
146 w := httptest.NewRecorder()
147 if err != nil {
148 t.Errorf("Unexpected error %v", err)
149 }
150
151 api.addAlerts(w, r)
152 res := w.Result()
153 body, _ := io.ReadAll(res.Body)
154
155 require.Equal(t, tc.code, w.Code, fmt.Sprintf("test case: %d, StartsAt %v, EndsAt %v, Response: %s", i, tc.start, tc.end, string(body)))
156 }
157 }
158
159 func TestListAlerts(t *testing.T) {
160 now := time.Now()
161 alerts := []*types.Alert{
162 {
163 Alert: model.Alert{
164 Labels: model.LabelSet{"state": "active", "alertname": "alert1"},
165 StartsAt: now.Add(-time.Minute),
166 },
167 },
168 {
169 Alert: model.Alert{
170 Labels: model.LabelSet{"state": "unprocessed", "alertname": "alert2"},
171 StartsAt: now.Add(-time.Minute),
172 },
173 },
174 {
175 Alert: model.Alert{
176 Labels: model.LabelSet{"state": "suppressed", "silenced_by": "abc", "alertname": "alert3"},
177 StartsAt: now.Add(-time.Minute),
178 },
179 },
180 {
181 Alert: model.Alert{
182 Labels: model.LabelSet{"state": "suppressed", "inhibited_by": "abc", "alertname": "alert4"},
183 StartsAt: now.Add(-time.Minute),
184 },
185 },
186 {
187 Alert: model.Alert{
188 Labels: model.LabelSet{"alertname": "alert5"},
189 StartsAt: now.Add(-2 * time.Minute),
190 EndsAt: now.Add(-time.Minute),
191 },
192 },
193 }
194
195 for i, tc := range []struct {
196 err bool
197 params map[string]string
198
199 code int
200 anames []string
201 }{
202 {
203 false,
204 map[string]string{},
205 200,
206 []string{"alert1", "alert2", "alert3", "alert4"},
207 },
208 {
209 false,
210 map[string]string{"active": "true", "unprocessed": "true", "silenced": "true", "inhibited": "true"},
211 200,
212 []string{"alert1", "alert2", "alert3", "alert4"},
213 },
214 {
215 false,
216 map[string]string{"active": "false", "unprocessed": "true", "silenced": "true", "inhibited": "true"},
217 200,
218 []string{"alert2", "alert3", "alert4"},
219 },
220 {
221 false,
222 map[string]string{"active": "true", "unprocessed": "false", "silenced": "true", "inhibited": "true"},
223 200,
224 []string{"alert1", "alert3", "alert4"},
225 },
226 {
227 false,
228 map[string]string{"active": "true", "unprocessed": "true", "silenced": "false", "inhibited": "true"},
229 200,
230 []string{"alert1", "alert2", "alert4"},
231 },
232 {
233 false,
234 map[string]string{"active": "true", "unprocessed": "true", "silenced": "true", "inhibited": "false"},
235 200,
236 []string{"alert1", "alert2", "alert3"},
237 },
238 {
239 false,
240 map[string]string{"filter": "{alertname=\"alert3\""},
241 200,
242 []string{"alert3"},
243 },
244 {
245 false,
246 map[string]string{"filter": "{alertname"},
247 400,
248 []string{},
249 },
250 {
251 false,
252 map[string]string{"receiver": "other"},
253 200,
254 []string{},
255 },
256 {
257 false,
258 map[string]string{"active": "invalid"},
259 400,
260 []string{},
261 },
262 {
263 true,
264 map[string]string{},
265 500,
266 []string{},
267 },
268 } {
269 alertsProvider := newFakeAlerts(alerts, tc.err)
270 api := New(alertsProvider, nil, newGetAlertStatus(alertsProvider), nil, nil, nil)
271 api.route = dispatch.NewRoute(&config.Route{Receiver: "def-receiver"}, nil)
272
273 r, err := http.NewRequest("GET", "/api/v1/alerts", nil)
274 if err != nil {
275 t.Fatalf("Unexpected error %v", err)
276 }
277 q := r.URL.Query()
278 for k, v := range tc.params {
279 q.Add(k, v)
280 }
281 r.URL.RawQuery = q.Encode()
282 w := httptest.NewRecorder()
283
284 api.listAlerts(w, r)
285 body, _ := io.ReadAll(w.Result().Body)
286
287 var res response
288 err = json.Unmarshal(body, &res)
289 if err != nil {
290 t.Fatalf("Unexpected error %v", err)
291 }
292
293 require.Equal(t, tc.code, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body)))
294 if w.Code != 200 {
295 continue
296 }
297
298
299 b, err := json.Marshal(res.Data)
300 if err != nil {
301 t.Fatalf("Unexpected error %v", err)
302 }
303 retAlerts := []*Alert{}
304 err = json.Unmarshal(b, &retAlerts)
305 if err != nil {
306 t.Fatalf("Unexpected error %v", err)
307 }
308
309 anames := []string{}
310 for _, a := range retAlerts {
311 name, ok := a.Labels["alertname"]
312 if ok {
313 anames = append(anames, string(name))
314 }
315 }
316 require.Equal(t, tc.anames, anames, fmt.Sprintf("test case: %d, alert names are not equal", i))
317 }
318 }
319
320 func TestAlertFiltering(t *testing.T) {
321 type test struct {
322 alert *model.Alert
323 msg string
324 expected bool
325 }
326
327
328 equal, err := labels.NewMatcher(labels.MatchEqual, "label1", "test1")
329 if err != nil {
330 t.Errorf("Unexpected error %v", err)
331 }
332
333 tests := []test{
334 {&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1=test1", true},
335 {&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1=test2", false},
336 {&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2=test2", false},
337 }
338
339 for _, test := range tests {
340 actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{equal})
341 msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
342 require.Equal(t, test.expected, actual, msg)
343 }
344
345
346 notEqual, err := labels.NewMatcher(labels.MatchNotEqual, "label1", "test1")
347 if err != nil {
348 t.Errorf("Unexpected error %v", err)
349 }
350
351 tests = []test{
352 {&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1!=test1", false},
353 {&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1!=test2", true},
354 {&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2!=test2", true},
355 }
356
357 for _, test := range tests {
358 actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{notEqual})
359 msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
360 require.Equal(t, test.expected, actual, msg)
361 }
362
363
364 regexpEqual, err := labels.NewMatcher(labels.MatchRegexp, "label1", "tes.*")
365 if err != nil {
366 t.Errorf("Unexpected error %v", err)
367 }
368
369 tests = []test{
370 {&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1=~test1", true},
371 {&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1=~test2", true},
372 {&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2=~test2", false},
373 }
374
375 for _, test := range tests {
376 actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{regexpEqual})
377 msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
378 require.Equal(t, test.expected, actual, msg)
379 }
380
381
382 regexpNotEqual, err := labels.NewMatcher(labels.MatchNotRegexp, "label1", "tes.*")
383 if err != nil {
384 t.Errorf("Unexpected error %v", err)
385 }
386
387 tests = []test{
388 {&model.Alert{Labels: model.LabelSet{"label1": "test1"}}, "label1!~test1", false},
389 {&model.Alert{Labels: model.LabelSet{"label1": "test2"}}, "label1!~test2", false},
390 {&model.Alert{Labels: model.LabelSet{"label2": "test2"}}, "label2!~test2", true},
391 }
392
393 for _, test := range tests {
394 actual := alertMatchesFilterLabels(test.alert, []*labels.Matcher{regexpNotEqual})
395 msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
396 require.Equal(t, test.expected, actual, msg)
397 }
398 }
399
400 func TestSilenceFiltering(t *testing.T) {
401 type test struct {
402 silence *types.Silence
403 msg string
404 expected bool
405 }
406
407
408 equal, err := labels.NewMatcher(labels.MatchEqual, "label1", "test1")
409 if err != nil {
410 t.Errorf("Unexpected error %v", err)
411 }
412
413 tests := []test{
414 {
415 &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
416 "label1=test1",
417 true,
418 },
419 {
420 &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
421 "label1=test2",
422 false,
423 },
424 {
425 &types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
426 "label2=test2",
427 false,
428 },
429 }
430
431 for _, test := range tests {
432 actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{equal})
433 msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
434 require.Equal(t, test.expected, actual, msg)
435 }
436
437
438 notEqual, err := labels.NewMatcher(labels.MatchNotEqual, "label1", "test1")
439 if err != nil {
440 t.Errorf("Unexpected error %v", err)
441 }
442
443 tests = []test{
444 {
445 &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
446 "label1!=test1",
447 false,
448 },
449 {
450 &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
451 "label1!=test2",
452 true,
453 },
454 {
455 &types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
456 "label2!=test2",
457 true,
458 },
459 }
460
461 for _, test := range tests {
462 actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{notEqual})
463 msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
464 require.Equal(t, test.expected, actual, msg)
465 }
466
467
468 regexpEqual, err := labels.NewMatcher(labels.MatchRegexp, "label1", "tes.*")
469 if err != nil {
470 t.Errorf("Unexpected error %v", err)
471 }
472
473 tests = []test{
474 {
475 &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
476 "label1=~test1",
477 true,
478 },
479 {
480 &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
481 "label1=~test2",
482 true,
483 },
484 {
485 &types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
486 "label2=~test2",
487 false,
488 },
489 }
490
491 for _, test := range tests {
492 actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{regexpEqual})
493 msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
494 require.Equal(t, test.expected, actual, msg)
495 }
496
497
498 regexpNotEqual, err := labels.NewMatcher(labels.MatchNotRegexp, "label1", "tes.*")
499 if err != nil {
500 t.Errorf("Unexpected error %v", err)
501 }
502
503 tests = []test{
504 {
505 &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test1"})},
506 "label1!~test1",
507 false,
508 },
509 {
510 &types.Silence{Matchers: newMatcher(model.LabelSet{"label1": "test2"})},
511 "label1!~test2",
512 false,
513 },
514 {
515 &types.Silence{Matchers: newMatcher(model.LabelSet{"label2": "test2"})},
516 "label2!~test2",
517 true,
518 },
519 }
520
521 for _, test := range tests {
522 actual := silenceMatchesFilterLabels(test.silence, []*labels.Matcher{regexpNotEqual})
523 msg := fmt.Sprintf("Expected %t for %s", test.expected, test.msg)
524 require.Equal(t, test.expected, actual, msg)
525 }
526 }
527
528 func TestReceiversMatchFilter(t *testing.T) {
529 receivers := []string{"pagerduty", "slack", "pushover"}
530
531 filter, err := regexp.Compile(fmt.Sprintf("^(?:%s)$", "push.*"))
532 if err != nil {
533 t.Errorf("Unexpected error %v", err)
534 }
535 require.True(t, receiversMatchFilter(receivers, filter))
536
537 filter, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", "push"))
538 if err != nil {
539 t.Errorf("Unexpected error %v", err)
540 }
541 require.False(t, receiversMatchFilter(receivers, filter))
542 }
543
544 func TestMatchFilterLabels(t *testing.T) {
545 testCases := []struct {
546 matcher labels.MatchType
547 expected bool
548 }{
549 {labels.MatchEqual, true},
550 {labels.MatchRegexp, true},
551 {labels.MatchNotEqual, false},
552 {labels.MatchNotRegexp, false},
553 }
554
555 for _, tc := range testCases {
556 l, err := labels.NewMatcher(tc.matcher, "foo", "")
557 require.NoError(t, err)
558 sms := map[string]string{
559 "baz": "bar",
560 }
561 ls := []*labels.Matcher{l}
562
563 require.Equal(t, tc.expected, matchFilterLabels(ls, sms))
564
565 l, err = labels.NewMatcher(tc.matcher, "foo", "")
566 require.NoError(t, err)
567 sms = map[string]string{
568 "baz": "bar",
569 "foo": "quux",
570 }
571 ls = []*labels.Matcher{l}
572 require.NotEqual(t, tc.expected, matchFilterLabels(ls, sms))
573 }
574 }
575
576 func newMatcher(labelSet model.LabelSet) labels.Matchers {
577 matchers := make([]*labels.Matcher, 0, len(labelSet))
578 for key, val := range labelSet {
579 matchers = append(matchers, &labels.Matcher{
580 Type: labels.MatchEqual,
581 Name: string(key),
582 Value: string(val),
583 })
584 }
585 return matchers
586 }
587
View as plain text