1
2
3
4
5
6
7
8
9
10
11
12
13
14 package v2
15
16 import (
17 "bytes"
18 "fmt"
19 "io"
20 "net/http"
21 "net/http/httptest"
22 "strconv"
23 "testing"
24 "time"
25
26 "github.com/go-openapi/runtime"
27 "github.com/go-openapi/strfmt"
28 "github.com/prometheus/common/model"
29 "github.com/stretchr/testify/require"
30
31 open_api_models "github.com/prometheus/alertmanager/api/v2/models"
32 general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general"
33 silence_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence"
34 "github.com/prometheus/alertmanager/config"
35 "github.com/prometheus/alertmanager/pkg/labels"
36 "github.com/prometheus/alertmanager/silence"
37 "github.com/prometheus/alertmanager/silence/silencepb"
38 "github.com/prometheus/alertmanager/types"
39
40 "github.com/go-kit/log"
41 )
42
43
44
45
46 func TestGetStatusHandlerWithNilPeer(t *testing.T) {
47 api := API{
48 uptime: time.Now(),
49 peer: nil,
50 alertmanagerConfig: &config.Config{},
51 }
52
53
54 status := api.getStatusHandler(general_ops.GetStatusParams{}).(*general_ops.GetStatusOK)
55
56 c := status.Payload.Cluster
57
58 if c == nil || c.Status == nil {
59 t.Fatal("expected cluster status not to be nil, violating the openapi specification")
60 }
61
62 if c.Peers == nil {
63 t.Fatal("expected cluster peers to be not nil when api.peer is nil, violating the openapi specification")
64 }
65 if len(c.Peers) != 0 {
66 t.Fatal("expected cluster peers to be empty when api.peer is nil, violating the openapi specification")
67 }
68
69 if c.Name != "" {
70 t.Fatal("expected cluster name to be empty, violating the openapi specification")
71 }
72 }
73
74 func assertEqualStrings(t *testing.T, expected, actual string) {
75 if expected != actual {
76 t.Fatal("expected: ", expected, ", actual: ", actual)
77 }
78 }
79
80 var (
81 testComment = "comment"
82 createdBy = "test"
83 )
84
85 func newSilences(t *testing.T) *silence.Silences {
86 silences, err := silence.New(silence.Options{})
87 require.NoError(t, err)
88
89 return silences
90 }
91
92 func gettableSilence(id, state string,
93 updatedAt, start, end string,
94 ) *open_api_models.GettableSilence {
95 updAt, err := strfmt.ParseDateTime(updatedAt)
96 if err != nil {
97 panic(err)
98 }
99 strAt, err := strfmt.ParseDateTime(start)
100 if err != nil {
101 panic(err)
102 }
103 endAt, err := strfmt.ParseDateTime(end)
104 if err != nil {
105 panic(err)
106 }
107 return &open_api_models.GettableSilence{
108 Silence: open_api_models.Silence{
109 StartsAt: &strAt,
110 EndsAt: &endAt,
111 Comment: &testComment,
112 CreatedBy: &createdBy,
113 },
114 ID: &id,
115 UpdatedAt: &updAt,
116 Status: &open_api_models.SilenceStatus{
117 State: &state,
118 },
119 }
120 }
121
122 func TestGetSilencesHandler(t *testing.T) {
123 updateTime := "2019-01-01T12:00:00+00:00"
124 silences := []*open_api_models.GettableSilence{
125 gettableSilence("silence-6-expired", "expired", updateTime,
126 "2019-01-01T12:00:00+00:00", "2019-01-01T11:00:00+00:00"),
127 gettableSilence("silence-1-active", "active", updateTime,
128 "2019-01-01T12:00:00+00:00", "2019-01-01T13:00:00+00:00"),
129 gettableSilence("silence-7-expired", "expired", updateTime,
130 "2019-01-01T12:00:00+00:00", "2019-01-01T10:00:00+00:00"),
131 gettableSilence("silence-5-expired", "expired", updateTime,
132 "2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
133 gettableSilence("silence-0-active", "active", updateTime,
134 "2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
135 gettableSilence("silence-4-pending", "pending", updateTime,
136 "2019-01-01T13:00:00+00:00", "2019-01-01T12:00:00+00:00"),
137 gettableSilence("silence-3-pending", "pending", updateTime,
138 "2019-01-01T12:00:00+00:00", "2019-01-01T12:00:00+00:00"),
139 gettableSilence("silence-2-active", "active", updateTime,
140 "2019-01-01T12:00:00+00:00", "2019-01-01T14:00:00+00:00"),
141 }
142 SortSilences(open_api_models.GettableSilences(silences))
143
144 for i, sil := range silences {
145 assertEqualStrings(t, "silence-"+strconv.Itoa(i)+"-"+*sil.Status.State, *sil.ID)
146 }
147 }
148
149 func TestDeleteSilenceHandler(t *testing.T) {
150 now := time.Now()
151 silences := newSilences(t)
152
153 m := &silencepb.Matcher{Type: silencepb.Matcher_EQUAL, Name: "a", Pattern: "b"}
154
155 unexpiredSil := &silencepb.Silence{
156 Matchers: []*silencepb.Matcher{m},
157 StartsAt: now,
158 EndsAt: now.Add(time.Hour),
159 UpdatedAt: now,
160 }
161 unexpiredSid, err := silences.Set(unexpiredSil)
162 require.NoError(t, err)
163
164 expiredSil := &silencepb.Silence{
165 Matchers: []*silencepb.Matcher{m},
166 StartsAt: now.Add(-time.Hour),
167 EndsAt: now.Add(time.Hour),
168 UpdatedAt: now,
169 }
170 expiredSid, err := silences.Set(expiredSil)
171 require.NoError(t, err)
172 require.NoError(t, silences.Expire(expiredSid))
173
174 for i, tc := range []struct {
175 sid string
176 expectedCode int
177 }{
178 {
179 "unknownSid",
180 500,
181 },
182 {
183 unexpiredSid,
184 200,
185 },
186 {
187 expiredSid,
188 200,
189 },
190 } {
191 api := API{
192 uptime: time.Now(),
193 silences: silences,
194 logger: log.NewNopLogger(),
195 }
196
197 r, err := http.NewRequest("DELETE", "/api/v2/silence/${tc.sid}", nil)
198 require.NoError(t, err)
199
200 w := httptest.NewRecorder()
201 p := runtime.TextProducer()
202 responder := api.deleteSilenceHandler(silence_ops.DeleteSilenceParams{
203 SilenceID: strfmt.UUID(tc.sid),
204 HTTPRequest: r,
205 })
206 responder.WriteResponse(w, p)
207 body, _ := io.ReadAll(w.Result().Body)
208
209 require.Equal(t, tc.expectedCode, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body)))
210 }
211 }
212
213 func TestPostSilencesHandler(t *testing.T) {
214 now := time.Now()
215 silences := newSilences(t)
216
217 m := &silencepb.Matcher{Type: silencepb.Matcher_EQUAL, Name: "a", Pattern: "b"}
218
219 unexpiredSil := &silencepb.Silence{
220 Matchers: []*silencepb.Matcher{m},
221 StartsAt: now,
222 EndsAt: now.Add(time.Hour),
223 UpdatedAt: now,
224 }
225 unexpiredSid, err := silences.Set(unexpiredSil)
226 require.NoError(t, err)
227
228 expiredSil := &silencepb.Silence{
229 Matchers: []*silencepb.Matcher{m},
230 StartsAt: now.Add(-time.Hour),
231 EndsAt: now.Add(time.Hour),
232 UpdatedAt: now,
233 }
234 expiredSid, err := silences.Set(expiredSil)
235 require.NoError(t, err)
236 require.NoError(t, silences.Expire(expiredSid))
237
238 t.Run("Silences CRUD", func(t *testing.T) {
239 for i, tc := range []struct {
240 name string
241 sid string
242 start, end time.Time
243 expectedCode int
244 }{
245 {
246 "with an non-existent silence ID - it returns 404",
247 "unknownSid",
248 now.Add(time.Hour),
249 now.Add(time.Hour * 2),
250 404,
251 },
252 {
253 "with no silence ID - it creates the silence",
254 "",
255 now.Add(time.Hour),
256 now.Add(time.Hour * 2),
257 200,
258 },
259 {
260 "with an active silence ID - it extends the silence",
261 unexpiredSid,
262 now.Add(time.Hour),
263 now.Add(time.Hour * 2),
264 200,
265 },
266 {
267 "with an expired silence ID - it re-creates the silence",
268 expiredSid,
269 now.Add(time.Hour),
270 now.Add(time.Hour * 2),
271 200,
272 },
273 } {
274 t.Run(tc.name, func(t *testing.T) {
275 silence, silenceBytes := createSilence(t, tc.sid, "silenceCreator", tc.start, tc.end)
276
277 api := API{
278 uptime: time.Now(),
279 silences: silences,
280 logger: log.NewNopLogger(),
281 }
282
283 r, err := http.NewRequest("POST", "/api/v2/silence/${tc.sid}", bytes.NewReader(silenceBytes))
284 require.NoError(t, err)
285
286 w := httptest.NewRecorder()
287 p := runtime.TextProducer()
288 responder := api.postSilencesHandler(silence_ops.PostSilencesParams{
289 HTTPRequest: r,
290 Silence: &silence,
291 })
292 responder.WriteResponse(w, p)
293 body, _ := io.ReadAll(w.Result().Body)
294
295 require.Equal(t, tc.expectedCode, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body)))
296 })
297 }
298 })
299 }
300
301 func TestCheckSilenceMatchesFilterLabels(t *testing.T) {
302 type test struct {
303 silenceMatchers []*silencepb.Matcher
304 filterMatchers []*labels.Matcher
305 expected bool
306 }
307
308 tests := []test{
309 {
310 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL)},
311 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchEqual)},
312 true,
313 },
314 {
315 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL)},
316 []*labels.Matcher{createLabelMatcher(t, "label", "novalue", labels.MatchEqual)},
317 false,
318 },
319 {
320 []*silencepb.Matcher{createSilenceMatcher(t, "label", "(foo|bar)", silencepb.Matcher_REGEXP)},
321 []*labels.Matcher{createLabelMatcher(t, "label", "(foo|bar)", labels.MatchRegexp)},
322 true,
323 },
324 {
325 []*silencepb.Matcher{createSilenceMatcher(t, "label", "foo", silencepb.Matcher_REGEXP)},
326 []*labels.Matcher{createLabelMatcher(t, "label", "(foo|bar)", labels.MatchRegexp)},
327 false,
328 },
329
330 {
331 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL)},
332 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchRegexp)},
333 false,
334 },
335 {
336 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_REGEXP)},
337 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchEqual)},
338 false,
339 },
340 {
341 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_NOT_EQUAL)},
342 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotEqual)},
343 true,
344 },
345 {
346 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_NOT_REGEXP)},
347 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotRegexp)},
348 true,
349 },
350 {
351 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL)},
352 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotEqual)},
353 false,
354 },
355 {
356 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_REGEXP)},
357 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotRegexp)},
358 false,
359 },
360 {
361 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_NOT_EQUAL)},
362 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotRegexp)},
363 false,
364 },
365 {
366 []*silencepb.Matcher{createSilenceMatcher(t, "label", "value", silencepb.Matcher_NOT_REGEXP)},
367 []*labels.Matcher{createLabelMatcher(t, "label", "value", labels.MatchNotEqual)},
368 false,
369 },
370 {
371 []*silencepb.Matcher{
372 createSilenceMatcher(t, "label", "(foo|bar)", silencepb.Matcher_REGEXP),
373 createSilenceMatcher(t, "label", "value", silencepb.Matcher_EQUAL),
374 },
375 []*labels.Matcher{createLabelMatcher(t, "label", "(foo|bar)", labels.MatchRegexp)},
376 true,
377 },
378 }
379
380 for _, test := range tests {
381 silence := silencepb.Silence{
382 Matchers: test.silenceMatchers,
383 }
384 actual := CheckSilenceMatchesFilterLabels(&silence, test.filterMatchers)
385 if test.expected != actual {
386 t.Fatal("unexpected match result between silence and filter. expected:", test.expected, ", actual:", actual)
387 }
388 }
389 }
390
391 func convertDateTime(ts time.Time) *strfmt.DateTime {
392 dt := strfmt.DateTime(ts)
393 return &dt
394 }
395
396 func TestAlertToOpenAPIAlert(t *testing.T) {
397 var (
398 start = time.Now().Add(-time.Minute)
399 updated = time.Now()
400 active = "active"
401 fp = "0223b772b51c29e1"
402 receivers = []string{"receiver1", "receiver2"}
403
404 alert = &types.Alert{
405 Alert: model.Alert{
406 Labels: model.LabelSet{"severity": "critical", "alertname": "alert1"},
407 StartsAt: start,
408 },
409 UpdatedAt: updated,
410 }
411 )
412 openAPIAlert := AlertToOpenAPIAlert(alert, types.AlertStatus{State: types.AlertStateActive}, receivers)
413 require.Equal(t, &open_api_models.GettableAlert{
414 Annotations: open_api_models.LabelSet{},
415 Alert: open_api_models.Alert{
416 Labels: open_api_models.LabelSet{"severity": "critical", "alertname": "alert1"},
417 },
418 Status: &open_api_models.AlertStatus{
419 State: &active,
420 InhibitedBy: []string{},
421 SilencedBy: []string{},
422 },
423 StartsAt: convertDateTime(start),
424 EndsAt: convertDateTime(time.Time{}),
425 UpdatedAt: convertDateTime(updated),
426 Fingerprint: &fp,
427 Receivers: []*open_api_models.Receiver{
428 {Name: &receivers[0]},
429 {Name: &receivers[1]},
430 },
431 }, openAPIAlert)
432 }
433
434 func TestMatchFilterLabels(t *testing.T) {
435 sms := map[string]string{
436 "foo": "bar",
437 }
438
439 testCases := []struct {
440 matcher labels.MatchType
441 name string
442 val string
443 expected bool
444 }{
445 {labels.MatchEqual, "foo", "bar", true},
446 {labels.MatchEqual, "baz", "", true},
447 {labels.MatchEqual, "baz", "qux", false},
448 {labels.MatchEqual, "baz", "qux|", false},
449 {labels.MatchRegexp, "foo", "bar", true},
450 {labels.MatchRegexp, "baz", "", true},
451 {labels.MatchRegexp, "baz", "qux", false},
452 {labels.MatchRegexp, "baz", "qux|", true},
453 {labels.MatchNotEqual, "foo", "bar", false},
454 {labels.MatchNotEqual, "baz", "", false},
455 {labels.MatchNotEqual, "baz", "qux", true},
456 {labels.MatchNotEqual, "baz", "qux|", true},
457 {labels.MatchNotRegexp, "foo", "bar", false},
458 {labels.MatchNotRegexp, "baz", "", false},
459 {labels.MatchNotRegexp, "baz", "qux", true},
460 {labels.MatchNotRegexp, "baz", "qux|", false},
461 }
462
463 for _, tc := range testCases {
464 m, err := labels.NewMatcher(tc.matcher, tc.name, tc.val)
465 require.NoError(t, err)
466
467 ms := []*labels.Matcher{m}
468 require.Equal(t, tc.expected, matchFilterLabels(ms, sms))
469 }
470 }
471
View as plain text