1
2
3
4
5
6
7
8
9
10
11
12
13
14 package v1
15
16 import (
17 "encoding/json"
18 "errors"
19 "fmt"
20 "net/http"
21 "regexp"
22 "sort"
23 "sync"
24 "time"
25
26 "github.com/go-kit/log"
27 "github.com/go-kit/log/level"
28 "github.com/prometheus/client_golang/prometheus"
29 "github.com/prometheus/common/model"
30 "github.com/prometheus/common/route"
31 "github.com/prometheus/common/version"
32
33 "github.com/prometheus/alertmanager/api/metrics"
34 "github.com/prometheus/alertmanager/cluster"
35 "github.com/prometheus/alertmanager/config"
36 "github.com/prometheus/alertmanager/dispatch"
37 "github.com/prometheus/alertmanager/pkg/labels"
38 "github.com/prometheus/alertmanager/provider"
39 "github.com/prometheus/alertmanager/silence"
40 "github.com/prometheus/alertmanager/silence/silencepb"
41 "github.com/prometheus/alertmanager/types"
42 )
43
44 var corsHeaders = map[string]string{
45 "Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, Origin",
46 "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
47 "Access-Control-Allow-Origin": "*",
48 "Access-Control-Expose-Headers": "Date",
49 "Cache-Control": "no-cache, no-store, must-revalidate",
50 }
51
52
53
54 type Alert struct {
55 *model.Alert
56 Status types.AlertStatus `json:"status"`
57 Receivers []string `json:"receivers"`
58 Fingerprint string `json:"fingerprint"`
59 }
60
61
62 func setCORS(w http.ResponseWriter) {
63 for h, v := range corsHeaders {
64 w.Header().Set(h, v)
65 }
66 }
67
68
69 type API struct {
70 alerts provider.Alerts
71 silences *silence.Silences
72 config *config.Config
73 route *dispatch.Route
74 uptime time.Time
75 peer cluster.ClusterPeer
76 logger log.Logger
77 m *metrics.Alerts
78
79 getAlertStatus getAlertStatusFn
80
81 mtx sync.RWMutex
82 }
83
84 type getAlertStatusFn func(model.Fingerprint) types.AlertStatus
85
86
87 func New(
88 alerts provider.Alerts,
89 silences *silence.Silences,
90 sf getAlertStatusFn,
91 peer cluster.ClusterPeer,
92 l log.Logger,
93 r prometheus.Registerer,
94 ) *API {
95 if l == nil {
96 l = log.NewNopLogger()
97 }
98
99 return &API{
100 alerts: alerts,
101 silences: silences,
102 getAlertStatus: sf,
103 uptime: time.Now(),
104 peer: peer,
105 logger: l,
106 m: metrics.NewAlerts("v1", r),
107 }
108 }
109
110
111
112 func (api *API) Register(r *route.Router) {
113 wrap := func(f http.HandlerFunc) http.HandlerFunc {
114 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
115 setCORS(w)
116 f(w, r)
117 })
118 }
119
120 r.Options("/*path", wrap(func(w http.ResponseWriter, r *http.Request) {}))
121
122 r.Get("/status", wrap(api.status))
123 r.Get("/receivers", wrap(api.receivers))
124
125 r.Get("/alerts", wrap(api.listAlerts))
126 r.Post("/alerts", wrap(api.addAlerts))
127
128 r.Get("/silences", wrap(api.listSilences))
129 r.Post("/silences", wrap(api.setSilence))
130 r.Get("/silence/:sid", wrap(api.getSilence))
131 r.Del("/silence/:sid", wrap(api.delSilence))
132 }
133
134
135 func (api *API) Update(cfg *config.Config) {
136 api.mtx.Lock()
137 defer api.mtx.Unlock()
138
139 api.config = cfg
140 api.route = dispatch.NewRoute(cfg.Route, nil)
141 }
142
143 type errorType string
144
145 const (
146 errorInternal errorType = "server_error"
147 errorBadData errorType = "bad_data"
148 )
149
150 type apiError struct {
151 typ errorType
152 err error
153 }
154
155 func (e *apiError) Error() string {
156 return fmt.Sprintf("%s: %s", e.typ, e.err)
157 }
158
159 func (api *API) receivers(w http.ResponseWriter, req *http.Request) {
160 api.mtx.RLock()
161 defer api.mtx.RUnlock()
162
163 receivers := make([]string, 0, len(api.config.Receivers))
164 for _, r := range api.config.Receivers {
165 receivers = append(receivers, r.Name)
166 }
167
168 api.respond(w, receivers)
169 }
170
171 func (api *API) status(w http.ResponseWriter, req *http.Request) {
172 api.mtx.RLock()
173
174 status := struct {
175 ConfigYAML string `json:"configYAML"`
176 ConfigJSON *config.Config `json:"configJSON"`
177 VersionInfo map[string]string `json:"versionInfo"`
178 Uptime time.Time `json:"uptime"`
179 ClusterStatus *clusterStatus `json:"clusterStatus"`
180 }{
181 ConfigYAML: api.config.String(),
182 ConfigJSON: api.config,
183 VersionInfo: map[string]string{
184 "version": version.Version,
185 "revision": version.Revision,
186 "branch": version.Branch,
187 "buildUser": version.BuildUser,
188 "buildDate": version.BuildDate,
189 "goVersion": version.GoVersion,
190 },
191 Uptime: api.uptime,
192 ClusterStatus: getClusterStatus(api.peer),
193 }
194
195 api.mtx.RUnlock()
196
197 api.respond(w, status)
198 }
199
200 type peerStatus struct {
201 Name string `json:"name"`
202 Address string `json:"address"`
203 }
204
205 type clusterStatus struct {
206 Name string `json:"name"`
207 Status string `json:"status"`
208 Peers []peerStatus `json:"peers"`
209 }
210
211 func getClusterStatus(p cluster.ClusterPeer) *clusterStatus {
212 if p == nil {
213 return nil
214 }
215 s := &clusterStatus{Name: p.Name(), Status: p.Status()}
216
217 for _, n := range p.Peers() {
218 s.Peers = append(s.Peers, peerStatus{
219 Name: n.Name(),
220 Address: n.Address(),
221 })
222 }
223 return s
224 }
225
226 func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
227 var (
228 err error
229 receiverFilter *regexp.Regexp
230
231
232 res = []*Alert{}
233 matchers = []*labels.Matcher{}
234 ctx = r.Context()
235
236 showActive, showInhibited bool
237 showSilenced, showUnprocessed bool
238 )
239
240 getBoolParam := func(name string) (bool, error) {
241 v := r.FormValue(name)
242 if v == "" {
243 return true, nil
244 }
245 if v == "false" {
246 return false, nil
247 }
248 if v != "true" {
249 err := fmt.Errorf("parameter %q can either be 'true' or 'false', not %q", name, v)
250 api.respondError(w, apiError{
251 typ: errorBadData,
252 err: err,
253 }, nil)
254 return false, err
255 }
256 return true, nil
257 }
258
259 if filter := r.FormValue("filter"); filter != "" {
260 matchers, err = labels.ParseMatchers(filter)
261 if err != nil {
262 api.respondError(w, apiError{
263 typ: errorBadData,
264 err: err,
265 }, nil)
266 return
267 }
268 }
269
270 showActive, err = getBoolParam("active")
271 if err != nil {
272 return
273 }
274
275 showSilenced, err = getBoolParam("silenced")
276 if err != nil {
277 return
278 }
279
280 showInhibited, err = getBoolParam("inhibited")
281 if err != nil {
282 return
283 }
284
285 showUnprocessed, err = getBoolParam("unprocessed")
286 if err != nil {
287 return
288 }
289
290 if receiverParam := r.FormValue("receiver"); receiverParam != "" {
291 receiverFilter, err = regexp.Compile("^(?:" + receiverParam + ")$")
292 if err != nil {
293 api.respondError(w, apiError{
294 typ: errorBadData,
295 err: fmt.Errorf(
296 "failed to parse receiver param: %s",
297 receiverParam,
298 ),
299 }, nil)
300 return
301 }
302 }
303
304 alerts := api.alerts.GetPending()
305 defer alerts.Close()
306
307 api.mtx.RLock()
308 for a := range alerts.Next() {
309 if err = alerts.Err(); err != nil {
310 break
311 }
312 if err = ctx.Err(); err != nil {
313 break
314 }
315
316 routes := api.route.Match(a.Labels)
317 receivers := make([]string, 0, len(routes))
318 for _, r := range routes {
319 receivers = append(receivers, r.RouteOpts.Receiver)
320 }
321
322 if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
323 continue
324 }
325
326 if !alertMatchesFilterLabels(&a.Alert, matchers) {
327 continue
328 }
329
330
331 if !a.Alert.EndsAt.IsZero() && a.Alert.EndsAt.Before(time.Now()) {
332 continue
333 }
334
335 status := api.getAlertStatus(a.Fingerprint())
336
337 if !showActive && status.State == types.AlertStateActive {
338 continue
339 }
340
341 if !showUnprocessed && status.State == types.AlertStateUnprocessed {
342 continue
343 }
344
345 if !showSilenced && len(status.SilencedBy) != 0 {
346 continue
347 }
348
349 if !showInhibited && len(status.InhibitedBy) != 0 {
350 continue
351 }
352
353 alert := &Alert{
354 Alert: &a.Alert,
355 Status: status,
356 Receivers: receivers,
357 Fingerprint: a.Fingerprint().String(),
358 }
359
360 res = append(res, alert)
361 }
362 api.mtx.RUnlock()
363
364 if err != nil {
365 api.respondError(w, apiError{
366 typ: errorInternal,
367 err: err,
368 }, nil)
369 return
370 }
371 sort.Slice(res, func(i, j int) bool {
372 return res[i].Fingerprint < res[j].Fingerprint
373 })
374 api.respond(w, res)
375 }
376
377 func receiversMatchFilter(receivers []string, filter *regexp.Regexp) bool {
378 for _, r := range receivers {
379 if filter.MatchString(r) {
380 return true
381 }
382 }
383
384 return false
385 }
386
387 func alertMatchesFilterLabels(a *model.Alert, matchers []*labels.Matcher) bool {
388 sms := make(map[string]string)
389 for name, value := range a.Labels {
390 sms[string(name)] = string(value)
391 }
392 return matchFilterLabels(matchers, sms)
393 }
394
395 func (api *API) addAlerts(w http.ResponseWriter, r *http.Request) {
396 var alerts []*types.Alert
397 if err := api.receive(r, &alerts); err != nil {
398 api.respondError(w, apiError{
399 typ: errorBadData,
400 err: err,
401 }, nil)
402 return
403 }
404
405 api.insertAlerts(w, r, alerts...)
406 }
407
408 func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...*types.Alert) {
409 now := time.Now()
410
411 api.mtx.RLock()
412 resolveTimeout := time.Duration(api.config.Global.ResolveTimeout)
413 api.mtx.RUnlock()
414
415 for _, alert := range alerts {
416 alert.UpdatedAt = now
417
418
419 if alert.StartsAt.IsZero() {
420 if alert.EndsAt.IsZero() {
421 alert.StartsAt = now
422 } else {
423 alert.StartsAt = alert.EndsAt
424 }
425 }
426
427
428 if alert.EndsAt.IsZero() {
429 alert.Timeout = true
430 alert.EndsAt = now.Add(resolveTimeout)
431 }
432 if alert.EndsAt.After(time.Now()) {
433 api.m.Firing().Inc()
434 } else {
435 api.m.Resolved().Inc()
436 }
437 }
438
439
440 var (
441 validAlerts = make([]*types.Alert, 0, len(alerts))
442 validationErrs = &types.MultiError{}
443 )
444 for _, a := range alerts {
445 removeEmptyLabels(a.Labels)
446
447 if err := a.Validate(); err != nil {
448 validationErrs.Add(err)
449 api.m.Invalid().Inc()
450 continue
451 }
452 validAlerts = append(validAlerts, a)
453 }
454 if err := api.alerts.Put(validAlerts...); err != nil {
455 api.respondError(w, apiError{
456 typ: errorInternal,
457 err: err,
458 }, nil)
459 return
460 }
461
462 if validationErrs.Len() > 0 {
463 api.respondError(w, apiError{
464 typ: errorBadData,
465 err: validationErrs,
466 }, nil)
467 return
468 }
469
470 api.respond(w, nil)
471 }
472
473 func removeEmptyLabels(ls model.LabelSet) {
474 for k, v := range ls {
475 if string(v) == "" {
476 delete(ls, k)
477 }
478 }
479 }
480
481 func (api *API) setSilence(w http.ResponseWriter, r *http.Request) {
482 var sil types.Silence
483 if err := api.receive(r, &sil); err != nil {
484 api.respondError(w, apiError{
485 typ: errorBadData,
486 err: err,
487 }, nil)
488 return
489 }
490
491
492
493
494
495 if sil.Expired() {
496 api.respondError(w, apiError{
497 typ: errorBadData,
498 err: errors.New("start time must not be equal to end time"),
499 }, nil)
500 return
501 }
502
503 if sil.EndsAt.Before(time.Now()) {
504 api.respondError(w, apiError{
505 typ: errorBadData,
506 err: errors.New("end time can't be in the past"),
507 }, nil)
508 return
509 }
510
511 psil, err := silenceToProto(&sil)
512 if err != nil {
513 api.respondError(w, apiError{
514 typ: errorBadData,
515 err: err,
516 }, nil)
517 return
518 }
519
520 sid, err := api.silences.Set(psil)
521 if err != nil {
522 api.respondError(w, apiError{
523 typ: errorBadData,
524 err: err,
525 }, nil)
526 return
527 }
528
529 api.respond(w, struct {
530 SilenceID string `json:"silenceId"`
531 }{
532 SilenceID: sid,
533 })
534 }
535
536 func (api *API) getSilence(w http.ResponseWriter, r *http.Request) {
537 sid := route.Param(r.Context(), "sid")
538
539 sils, _, err := api.silences.Query(silence.QIDs(sid))
540 if err != nil || len(sils) == 0 {
541 http.Error(w, fmt.Sprint("Error getting silence: ", err), http.StatusNotFound)
542 return
543 }
544 sil, err := silenceFromProto(sils[0])
545 if err != nil {
546 api.respondError(w, apiError{
547 typ: errorInternal,
548 err: err,
549 }, nil)
550 return
551 }
552
553 api.respond(w, sil)
554 }
555
556 func (api *API) delSilence(w http.ResponseWriter, r *http.Request) {
557 sid := route.Param(r.Context(), "sid")
558
559 if err := api.silences.Expire(sid); err != nil {
560 api.respondError(w, apiError{
561 typ: errorBadData,
562 err: err,
563 }, nil)
564 return
565 }
566 api.respond(w, nil)
567 }
568
569 func (api *API) listSilences(w http.ResponseWriter, r *http.Request) {
570 psils, _, err := api.silences.Query()
571 if err != nil {
572 api.respondError(w, apiError{
573 typ: errorInternal,
574 err: err,
575 }, nil)
576 return
577 }
578
579 matchers := []*labels.Matcher{}
580 if filter := r.FormValue("filter"); filter != "" {
581 matchers, err = labels.ParseMatchers(filter)
582 if err != nil {
583 api.respondError(w, apiError{
584 typ: errorBadData,
585 err: err,
586 }, nil)
587 return
588 }
589 }
590
591 sils := []*types.Silence{}
592 for _, ps := range psils {
593 s, err := silenceFromProto(ps)
594 if err != nil {
595 api.respondError(w, apiError{
596 typ: errorInternal,
597 err: err,
598 }, nil)
599 return
600 }
601
602 if !silenceMatchesFilterLabels(s, matchers) {
603 continue
604 }
605 sils = append(sils, s)
606 }
607
608 var active, pending, expired []*types.Silence
609
610 for _, s := range sils {
611 switch s.Status.State {
612 case types.SilenceStateActive:
613 active = append(active, s)
614 case types.SilenceStatePending:
615 pending = append(pending, s)
616 case types.SilenceStateExpired:
617 expired = append(expired, s)
618 }
619 }
620
621 sort.Slice(active, func(i, j int) bool {
622 return active[i].EndsAt.Before(active[j].EndsAt)
623 })
624 sort.Slice(pending, func(i, j int) bool {
625 return pending[i].StartsAt.Before(pending[j].EndsAt)
626 })
627 sort.Slice(expired, func(i, j int) bool {
628 return expired[i].EndsAt.After(expired[j].EndsAt)
629 })
630
631
632
633 silences := []*types.Silence{}
634 silences = append(silences, active...)
635 silences = append(silences, pending...)
636 silences = append(silences, expired...)
637
638 api.respond(w, silences)
639 }
640
641 func silenceMatchesFilterLabels(s *types.Silence, matchers []*labels.Matcher) bool {
642 sms := make(map[string]string)
643 for _, m := range s.Matchers {
644 sms[m.Name] = m.Value
645 }
646
647 return matchFilterLabels(matchers, sms)
648 }
649
650 func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool {
651 for _, m := range matchers {
652 v, prs := sms[m.Name]
653 switch m.Type {
654 case labels.MatchNotRegexp, labels.MatchNotEqual:
655 if string(m.Value) == "" && prs {
656 continue
657 }
658 if !m.Matches(string(v)) {
659 return false
660 }
661 default:
662 if string(m.Value) == "" && !prs {
663 continue
664 }
665 if !m.Matches(string(v)) {
666 return false
667 }
668 }
669 }
670
671 return true
672 }
673
674 func silenceToProto(s *types.Silence) (*silencepb.Silence, error) {
675 sil := &silencepb.Silence{
676 Id: s.ID,
677 StartsAt: s.StartsAt,
678 EndsAt: s.EndsAt,
679 UpdatedAt: s.UpdatedAt,
680 Comment: s.Comment,
681 CreatedBy: s.CreatedBy,
682 }
683 for _, m := range s.Matchers {
684 matcher := &silencepb.Matcher{
685 Name: m.Name,
686 Pattern: m.Value,
687 }
688 switch m.Type {
689 case labels.MatchEqual:
690 matcher.Type = silencepb.Matcher_EQUAL
691 case labels.MatchNotEqual:
692 matcher.Type = silencepb.Matcher_NOT_EQUAL
693 case labels.MatchRegexp:
694 matcher.Type = silencepb.Matcher_REGEXP
695 case labels.MatchNotRegexp:
696 matcher.Type = silencepb.Matcher_NOT_REGEXP
697 }
698 sil.Matchers = append(sil.Matchers, matcher)
699 }
700 return sil, nil
701 }
702
703 func silenceFromProto(s *silencepb.Silence) (*types.Silence, error) {
704 sil := &types.Silence{
705 ID: s.Id,
706 StartsAt: s.StartsAt,
707 EndsAt: s.EndsAt,
708 UpdatedAt: s.UpdatedAt,
709 Status: types.SilenceStatus{
710 State: types.CalcSilenceState(s.StartsAt, s.EndsAt),
711 },
712 Comment: s.Comment,
713 CreatedBy: s.CreatedBy,
714 }
715 for _, m := range s.Matchers {
716 var t labels.MatchType
717 switch m.Type {
718 case silencepb.Matcher_EQUAL:
719 t = labels.MatchEqual
720 case silencepb.Matcher_NOT_EQUAL:
721 t = labels.MatchNotEqual
722 case silencepb.Matcher_REGEXP:
723 t = labels.MatchRegexp
724 case silencepb.Matcher_NOT_REGEXP:
725 t = labels.MatchNotRegexp
726 }
727 matcher, err := labels.NewMatcher(t, m.Name, m.Pattern)
728 if err != nil {
729 return nil, err
730 }
731
732 sil.Matchers = append(sil.Matchers, matcher)
733 }
734
735 return sil, nil
736 }
737
738 type status string
739
740 const (
741 statusSuccess status = "success"
742 statusError status = "error"
743 )
744
745 type response struct {
746 Status status `json:"status"`
747 Data interface{} `json:"data,omitempty"`
748 ErrorType errorType `json:"errorType,omitempty"`
749 Error string `json:"error,omitempty"`
750 }
751
752 func (api *API) respond(w http.ResponseWriter, data interface{}) {
753 w.Header().Set("Content-Type", "application/json")
754 w.WriteHeader(200)
755
756 b, err := json.Marshal(&response{
757 Status: statusSuccess,
758 Data: data,
759 })
760 if err != nil {
761 level.Error(api.logger).Log("msg", "Error marshaling JSON", "err", err)
762 return
763 }
764
765 if _, err := w.Write(b); err != nil {
766 level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err)
767 }
768 }
769
770 func (api *API) respondError(w http.ResponseWriter, apiErr apiError, data interface{}) {
771 w.Header().Set("Content-Type", "application/json")
772
773 switch apiErr.typ {
774 case errorBadData:
775 w.WriteHeader(http.StatusBadRequest)
776 case errorInternal:
777 w.WriteHeader(http.StatusInternalServerError)
778 default:
779 panic(fmt.Sprintf("unknown error type %q", apiErr.Error()))
780 }
781
782 b, err := json.Marshal(&response{
783 Status: statusError,
784 ErrorType: apiErr.typ,
785 Error: apiErr.err.Error(),
786 Data: data,
787 })
788 if err != nil {
789 return
790 }
791 level.Error(api.logger).Log("msg", "API error", "err", apiErr.Error())
792
793 if _, err := w.Write(b); err != nil {
794 level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err)
795 }
796 }
797
798 func (api *API) receive(r *http.Request, v interface{}) error {
799 dec := json.NewDecoder(r.Body)
800 defer r.Body.Close()
801
802 err := dec.Decode(v)
803 if err != nil {
804 level.Debug(api.logger).Log("msg", "Decoding request failed", "err", err)
805 return err
806 }
807 return nil
808 }
809
View as plain text