2015-10-11 15:24:49 +00:00
|
|
|
// Copyright 2015 Prometheus Team
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2015-09-25 16:14:46 +00:00
|
|
|
package main
|
2015-07-01 15:56:53 +00:00
|
|
|
|
|
|
|
import (
|
2015-10-22 07:49:16 +00:00
|
|
|
"encoding/json"
|
2015-07-01 15:56:53 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/prometheus/common/model"
|
2015-09-25 16:14:46 +00:00
|
|
|
|
|
|
|
"github.com/prometheus/alertmanager/config"
|
|
|
|
"github.com/prometheus/alertmanager/types"
|
2015-07-01 15:56:53 +00:00
|
|
|
)
|
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// DefaultRouteOpts are the defaulting routing options which apply
|
|
|
|
// to the root route of a routing tree.
|
2015-10-19 14:52:54 +00:00
|
|
|
var DefaultRouteOpts = RouteOpts{
|
|
|
|
GroupWait: 30 * time.Second,
|
|
|
|
GroupInterval: 5 * time.Minute,
|
|
|
|
RepeatInterval: 4 * time.Hour,
|
|
|
|
SendResolved: true,
|
2015-10-19 15:35:59 +00:00
|
|
|
GroupBy: map[model.LabelName]struct{}{
|
|
|
|
model.AlertNameLabel: struct{}{},
|
|
|
|
},
|
2015-10-19 14:52:54 +00:00
|
|
|
}
|
|
|
|
|
2015-07-01 15:56:53 +00:00
|
|
|
// A Route is a node that contains definitions of how to handle alerts.
|
|
|
|
type Route struct {
|
2015-10-21 11:08:53 +00:00
|
|
|
parent *Route
|
|
|
|
|
2015-07-01 15:56:53 +00:00
|
|
|
// The configuration parameters for matches of this route.
|
|
|
|
RouteOpts RouteOpts
|
|
|
|
|
|
|
|
// Equality or regex matchers an alert has to fulfill to match
|
|
|
|
// this route.
|
2015-09-25 16:14:46 +00:00
|
|
|
Matchers types.Matchers
|
2015-07-02 16:38:05 +00:00
|
|
|
|
2015-07-01 15:56:53 +00:00
|
|
|
// If true, an alert matches further routes on the same level.
|
|
|
|
Continue bool
|
|
|
|
|
|
|
|
// Children routes of this route.
|
2015-10-19 14:17:15 +00:00
|
|
|
Routes []*Route
|
2015-07-01 15:56:53 +00:00
|
|
|
}
|
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// NewRoute returns a new route.
|
2015-10-21 11:08:53 +00:00
|
|
|
func NewRoute(cr *config.Route, parent *Route) *Route {
|
2015-10-08 08:50:37 +00:00
|
|
|
// Create default and overwrite with configured settings.
|
2015-10-21 11:08:53 +00:00
|
|
|
opts := DefaultRouteOpts
|
|
|
|
if parent != nil {
|
|
|
|
opts = parent.RouteOpts
|
|
|
|
}
|
2015-10-08 08:50:37 +00:00
|
|
|
|
|
|
|
if cr.SendTo != "" {
|
|
|
|
opts.SendTo = cr.SendTo
|
2015-09-25 16:14:46 +00:00
|
|
|
}
|
2015-10-19 15:35:59 +00:00
|
|
|
if cr.GroupBy != nil {
|
|
|
|
opts.GroupBy = map[model.LabelName]struct{}{}
|
|
|
|
for _, ln := range cr.GroupBy {
|
|
|
|
opts.GroupBy[ln] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
2015-10-08 08:50:37 +00:00
|
|
|
if cr.GroupWait != nil {
|
2015-09-25 16:14:46 +00:00
|
|
|
opts.GroupWait = time.Duration(*cr.GroupWait)
|
|
|
|
}
|
2015-10-08 08:50:37 +00:00
|
|
|
if cr.GroupInterval != nil {
|
2015-09-26 16:03:54 +00:00
|
|
|
opts.GroupInterval = time.Duration(*cr.GroupInterval)
|
2015-09-25 16:14:46 +00:00
|
|
|
}
|
2015-10-08 08:50:37 +00:00
|
|
|
if cr.RepeatInterval != nil {
|
|
|
|
opts.RepeatInterval = time.Duration(*cr.RepeatInterval)
|
|
|
|
}
|
|
|
|
if cr.SendResolved != nil {
|
|
|
|
opts.SendResolved = *cr.SendResolved
|
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2015-10-08 08:50:37 +00:00
|
|
|
// Build matchers.
|
2015-09-25 16:14:46 +00:00
|
|
|
var matchers types.Matchers
|
|
|
|
|
|
|
|
for ln, lv := range cr.Match {
|
|
|
|
matchers = append(matchers, types.NewMatcher(model.LabelName(ln), lv))
|
|
|
|
}
|
|
|
|
for ln, lv := range cr.MatchRE {
|
|
|
|
m, err := types.NewRegexMatcher(model.LabelName(ln), lv.String())
|
|
|
|
if err != nil {
|
|
|
|
// Must have been sanitized during config validation.
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
matchers = append(matchers, m)
|
|
|
|
}
|
|
|
|
|
2015-10-19 09:45:52 +00:00
|
|
|
route := &Route{
|
2015-10-21 11:08:53 +00:00
|
|
|
parent: parent,
|
2015-09-25 16:14:46 +00:00
|
|
|
RouteOpts: opts,
|
|
|
|
Matchers: matchers,
|
|
|
|
Continue: cr.Continue,
|
|
|
|
}
|
2015-10-19 09:45:52 +00:00
|
|
|
|
2015-10-21 11:08:53 +00:00
|
|
|
route.Routes = NewRoutes(cr.Routes, route)
|
|
|
|
|
2015-10-19 09:45:52 +00:00
|
|
|
return route
|
2015-09-25 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// NewRoutes returns a slice of routes.
|
2015-10-21 11:08:53 +00:00
|
|
|
func NewRoutes(croutes []*config.Route, parent *Route) []*Route {
|
2015-10-19 14:17:15 +00:00
|
|
|
res := []*Route{}
|
2015-09-25 16:14:46 +00:00
|
|
|
for _, cr := range croutes {
|
2015-10-08 08:50:37 +00:00
|
|
|
res = append(res, NewRoute(cr, parent))
|
2015-09-25 16:14:46 +00:00
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2015-07-01 15:56:53 +00:00
|
|
|
// Match does a depth-first left-to-right search through the route tree
|
2015-11-07 13:30:21 +00:00
|
|
|
// and returns the matching routing nodes.
|
|
|
|
func (r *Route) Match(lset model.LabelSet) []*Route {
|
2015-07-09 13:01:38 +00:00
|
|
|
if !r.Matchers.Match(lset) {
|
2015-07-01 15:56:53 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-07 13:30:21 +00:00
|
|
|
var all []*Route
|
2015-07-01 15:56:53 +00:00
|
|
|
|
|
|
|
for _, cr := range r.Routes {
|
|
|
|
matches := cr.Match(lset)
|
|
|
|
|
2015-07-02 16:38:05 +00:00
|
|
|
all = append(all, matches...)
|
2015-07-01 15:56:53 +00:00
|
|
|
|
2015-07-01 16:24:08 +00:00
|
|
|
if matches != nil && !cr.Continue {
|
2015-07-01 15:56:53 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-21 11:08:53 +00:00
|
|
|
// If no child nodes were matches, the current node itself is
|
|
|
|
// a match.
|
2015-07-02 16:38:05 +00:00
|
|
|
if len(all) == 0 {
|
2015-11-07 13:30:21 +00:00
|
|
|
all = append(all, r)
|
2015-07-01 15:56:53 +00:00
|
|
|
}
|
|
|
|
|
2015-07-02 16:38:05 +00:00
|
|
|
return all
|
2015-07-01 15:56:53 +00:00
|
|
|
}
|
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// SquashMatchers returns the total set of matchers on the path of the tree
|
|
|
|
// that have to apply to reach the route.
|
2015-10-21 11:08:53 +00:00
|
|
|
func (r *Route) SquashMatchers() types.Matchers {
|
|
|
|
var res types.Matchers
|
|
|
|
res = append(res, r.Matchers...)
|
|
|
|
|
|
|
|
if r.parent == nil {
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
pm := r.parent.SquashMatchers()
|
|
|
|
res = append(pm, res...)
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// Fingerprint returns a hash of the Route based on its grouping labels,
|
|
|
|
// routing options and the total set of matchers necessary to reach this route.
|
2015-10-21 11:08:53 +00:00
|
|
|
func (r *Route) Fingerprint() model.Fingerprint {
|
|
|
|
lset := make(model.LabelSet, len(r.RouteOpts.GroupBy))
|
|
|
|
|
|
|
|
for ln := range r.RouteOpts.GroupBy {
|
|
|
|
lset[ln] = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return r.SquashMatchers().Fingerprint() ^ lset.Fingerprint()
|
|
|
|
}
|
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// RouteOpts holds various routing options necessary for processing alerts
|
|
|
|
// that match a given route.
|
2015-07-01 15:56:53 +00:00
|
|
|
type RouteOpts struct {
|
|
|
|
// The identifier of the associated notification configuration
|
2015-10-08 08:50:37 +00:00
|
|
|
SendTo string
|
|
|
|
SendResolved bool
|
2015-07-01 15:56:53 +00:00
|
|
|
|
|
|
|
// What labels to group alerts by for notifications.
|
|
|
|
GroupBy map[model.LabelName]struct{}
|
|
|
|
|
|
|
|
// How long to wait to group matching alerts before sending
|
|
|
|
// a notificaiton
|
2015-10-08 08:50:37 +00:00
|
|
|
GroupWait time.Duration
|
|
|
|
GroupInterval time.Duration
|
|
|
|
RepeatInterval time.Duration
|
2015-07-01 15:56:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ro *RouteOpts) String() string {
|
|
|
|
var labels []model.LabelName
|
|
|
|
for ln := range ro.GroupBy {
|
|
|
|
labels = append(labels, ln)
|
|
|
|
}
|
2015-09-26 16:03:54 +00:00
|
|
|
return fmt.Sprintf("<RouteOpts send_to:%q group_by:%q timers:%q|%q>", ro.SendTo, labels, ro.GroupWait, ro.GroupInterval)
|
2015-07-01 15:56:53 +00:00
|
|
|
}
|
2015-10-22 07:49:16 +00:00
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// MarshalJSON returns a JSON representation of the routing options.
|
2015-10-22 07:49:16 +00:00
|
|
|
func (ro *RouteOpts) MarshalJSON() ([]byte, error) {
|
|
|
|
v := struct {
|
|
|
|
SendTo string `json:"sendTo"`
|
|
|
|
SendResolved bool `json:"sendResolved"`
|
|
|
|
GroupBy model.LabelNames `json:"groupBy"`
|
|
|
|
GroupWait time.Duration `json:"groupWait"`
|
|
|
|
GroupInterval time.Duration `json:"groupInterval"`
|
|
|
|
RepeatInterval time.Duration `json:"repeatInterval"`
|
|
|
|
}{
|
|
|
|
SendTo: ro.SendTo,
|
|
|
|
SendResolved: ro.SendResolved,
|
|
|
|
GroupWait: ro.GroupWait,
|
|
|
|
GroupInterval: ro.GroupInterval,
|
|
|
|
RepeatInterval: ro.RepeatInterval,
|
|
|
|
}
|
|
|
|
for ln := range ro.GroupBy {
|
|
|
|
v.GroupBy = append(v.GroupBy, ln)
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Marshal(&v)
|
|
|
|
}
|