1// Copyright 2015 Prometheus Team
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package dispatch
15
16import (
17	"encoding/json"
18	"fmt"
19	"sort"
20	"strings"
21	"time"
22
23	"github.com/prometheus/common/model"
24
25	"github.com/prometheus/alertmanager/config"
26	"github.com/prometheus/alertmanager/pkg/labels"
27)
28
29// DefaultRouteOpts are the defaulting routing options which apply
30// to the root route of a routing tree.
31var DefaultRouteOpts = RouteOpts{
32	GroupWait:         30 * time.Second,
33	GroupInterval:     5 * time.Minute,
34	RepeatInterval:    4 * time.Hour,
35	GroupBy:           map[model.LabelName]struct{}{},
36	GroupByAll:        false,
37	MuteTimeIntervals: []string{},
38}
39
40// A Route is a node that contains definitions of how to handle alerts.
41type Route struct {
42	parent *Route
43
44	// The configuration parameters for matches of this route.
45	RouteOpts RouteOpts
46
47	// Matchers an alert has to fulfill to match
48	// this route.
49	Matchers labels.Matchers
50
51	// If true, an alert matches further routes on the same level.
52	Continue bool
53
54	// Children routes of this route.
55	Routes []*Route
56}
57
58// NewRoute returns a new route.
59func NewRoute(cr *config.Route, parent *Route) *Route {
60	// Create default and overwrite with configured settings.
61	opts := DefaultRouteOpts
62	if parent != nil {
63		opts = parent.RouteOpts
64	}
65
66	if cr.Receiver != "" {
67		opts.Receiver = cr.Receiver
68	}
69
70	if cr.GroupBy != nil {
71		opts.GroupBy = map[model.LabelName]struct{}{}
72		for _, ln := range cr.GroupBy {
73			opts.GroupBy[ln] = struct{}{}
74		}
75		opts.GroupByAll = false
76	} else {
77		if cr.GroupByAll {
78			opts.GroupByAll = cr.GroupByAll
79		}
80	}
81
82	if cr.GroupWait != nil {
83		opts.GroupWait = time.Duration(*cr.GroupWait)
84	}
85	if cr.GroupInterval != nil {
86		opts.GroupInterval = time.Duration(*cr.GroupInterval)
87	}
88	if cr.RepeatInterval != nil {
89		opts.RepeatInterval = time.Duration(*cr.RepeatInterval)
90	}
91
92	// Build matchers.
93	var matchers labels.Matchers
94
95	// cr.Match will be deprecated. This for loop appends matchers.
96	for ln, lv := range cr.Match {
97		matcher, err := labels.NewMatcher(labels.MatchEqual, ln, lv)
98		if err != nil {
99			// This error must not happen because the config already validates the yaml.
100			panic(err)
101		}
102		matchers = append(matchers, matcher)
103	}
104
105	// cr.MatchRE will be deprecated. This for loop appends regex matchers.
106	for ln, lv := range cr.MatchRE {
107		matcher, err := labels.NewMatcher(labels.MatchRegexp, ln, lv.String())
108		if err != nil {
109			// This error must not happen because the config already validates the yaml.
110			panic(err)
111		}
112		matchers = append(matchers, matcher)
113	}
114
115	// We append the new-style matchers. This can be simplified once the deprecated matcher syntax is removed.
116	matchers = append(matchers, cr.Matchers...)
117
118	sort.Sort(matchers)
119
120	opts.MuteTimeIntervals = cr.MuteTimeIntervals
121
122	route := &Route{
123		parent:    parent,
124		RouteOpts: opts,
125		Matchers:  matchers,
126		Continue:  cr.Continue,
127	}
128
129	route.Routes = NewRoutes(cr.Routes, route)
130
131	return route
132}
133
134// NewRoutes returns a slice of routes.
135func NewRoutes(croutes []*config.Route, parent *Route) []*Route {
136	res := []*Route{}
137	for _, cr := range croutes {
138		res = append(res, NewRoute(cr, parent))
139	}
140	return res
141}
142
143// Match does a depth-first left-to-right search through the route tree
144// and returns the matching routing nodes.
145func (r *Route) Match(lset model.LabelSet) []*Route {
146	if !r.Matchers.Matches(lset) {
147		return nil
148	}
149
150	var all []*Route
151
152	for _, cr := range r.Routes {
153		matches := cr.Match(lset)
154
155		all = append(all, matches...)
156
157		if matches != nil && !cr.Continue {
158			break
159		}
160	}
161
162	// If no child nodes were matches, the current node itself is a match.
163	if len(all) == 0 {
164		all = append(all, r)
165	}
166
167	return all
168}
169
170// Key returns a key for the route. It does not uniquely identify the route in general.
171func (r *Route) Key() string {
172	b := strings.Builder{}
173
174	if r.parent != nil {
175		b.WriteString(r.parent.Key())
176		b.WriteRune('/')
177	}
178	b.WriteString(r.Matchers.String())
179	return b.String()
180}
181
182// Walk traverses the route tree in depth-first order.
183func (r *Route) Walk(visit func(*Route)) {
184	visit(r)
185	if r.Routes == nil {
186		return
187	}
188	for i := range r.Routes {
189		r.Routes[i].Walk(visit)
190	}
191}
192
193// RouteOpts holds various routing options necessary for processing alerts
194// that match a given route.
195type RouteOpts struct {
196	// The identifier of the associated notification configuration.
197	Receiver string
198
199	// What labels to group alerts by for notifications.
200	GroupBy map[model.LabelName]struct{}
201
202	// Use all alert labels to group.
203	GroupByAll bool
204
205	// How long to wait to group matching alerts before sending
206	// a notification.
207	GroupWait      time.Duration
208	GroupInterval  time.Duration
209	RepeatInterval time.Duration
210
211	// A list of time intervals for which the route is muted.
212	MuteTimeIntervals []string
213}
214
215func (ro *RouteOpts) String() string {
216	var labels []model.LabelName
217	for ln := range ro.GroupBy {
218		labels = append(labels, ln)
219	}
220	return fmt.Sprintf("<RouteOpts send_to:%q group_by:%q group_by_all:%t timers:%q|%q>",
221		ro.Receiver, labels, ro.GroupByAll, ro.GroupWait, ro.GroupInterval)
222}
223
224// MarshalJSON returns a JSON representation of the routing options.
225func (ro *RouteOpts) MarshalJSON() ([]byte, error) {
226	v := struct {
227		Receiver       string           `json:"receiver"`
228		GroupBy        model.LabelNames `json:"groupBy"`
229		GroupByAll     bool             `json:"groupByAll"`
230		GroupWait      time.Duration    `json:"groupWait"`
231		GroupInterval  time.Duration    `json:"groupInterval"`
232		RepeatInterval time.Duration    `json:"repeatInterval"`
233	}{
234		Receiver:       ro.Receiver,
235		GroupByAll:     ro.GroupByAll,
236		GroupWait:      ro.GroupWait,
237		GroupInterval:  ro.GroupInterval,
238		RepeatInterval: ro.RepeatInterval,
239	}
240	for ln := range ro.GroupBy {
241		v.GroupBy = append(v.GroupBy, ln)
242	}
243
244	return json.Marshal(&v)
245}
246