initial alert routing tree

This commit is contained in:
Fabian Reinartz 2015-07-01 17:56:53 +02:00
parent 59b28fedda
commit bbe679b432
8 changed files with 247 additions and 8 deletions

24
main.go
View File

@ -14,17 +14,37 @@
package main package main
import ( import (
"flag"
"fmt"
"net/http" "net/http"
"github.com/prometheus/common/route" "github.com/prometheus/common/route"
// "gopkg.in/yaml.v2"
"github.com/prometheus/alertmanager/manager"
)
var (
configFile = flag.String("config.file", "config.yml", "The configuration file")
) )
func main() { func main() {
state := NewMemState() conf, err := manager.LoadFile(*configFile)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(conf)
for _, r := range conf.Routes {
fmt.Println(r)
}
state := manager.NewMemState()
router := route.New() router := route.New()
NewAPI(router.WithPrefix("/api"), state) manager.NewAPI(router.WithPrefix("/api"), state)
http.ListenAndServe(":9091", router) http.ListenAndServe(":9091", router)
} }

View File

@ -1,4 +1,4 @@
package main package manager
import ( import (
"encoding/json" "encoding/json"

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package manager
import ( import (
"fmt" "fmt"
@ -64,7 +64,7 @@ func LoadFile(filename string) (*Config, error) {
// Config is the top-level configuration for Alertmanager's config files. // Config is the top-level configuration for Alertmanager's config files.
type Config struct { type Config struct {
AggrRules []*AggrRule `yaml:"aggregation_rules,omitempty"` Routes []*Route `yaml:"routes,omitempty"`
InhibitRules []*InhibitRule `yaml:"inhibit_rules,omitempty"` InhibitRules []*InhibitRule `yaml:"inhibit_rules,omitempty"`
NotificationConfigs []*NotificationConfig `yaml:"notification_configs,omitempty"` NotificationConfigs []*NotificationConfig `yaml:"notification_configs,omitempty"`

View File

@ -11,9 +11,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main package manager
import ( import (
"fmt"
"regexp" "regexp"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -28,6 +29,13 @@ type Matcher struct {
regex *regexp.Regexp regex *regexp.Regexp
} }
func (m *Matcher) String() string {
if m.isRegex {
return fmt.Sprintf("<RegexMatcher %s:%q>", m.Name, m.regex)
}
return fmt.Sprintf("<Matcher %s:%q>", m.Name, m.Value)
}
// IsRegex returns true of the matcher compares against a regular expression. // IsRegex returns true of the matcher compares against a regular expression.
func (m *Matcher) IsRegex() bool { func (m *Matcher) IsRegex() bool {
return m.isRegex return m.isRegex

153
manager/route.go Normal file
View File

@ -0,0 +1,153 @@
package manager
import (
"fmt"
"time"
"github.com/prometheus/common/model"
)
// A Route is a node that contains definitions of how to handle alerts.
type Route struct {
// The configuration parameters for matches of this route.
RouteOpts RouteOpts
// Equality or regex matchers an alert has to fulfill to match
// this route.
Matchers Matchers
// If true, an alert matches further routes on the same level.
Continue bool
// Children routes of this route.
Routes []*Route
}
// Match does a depth-first left-to-right search through the route tree
// and returns the flattened configuration for the reached node.
func (r *Route) Match(lset model.LabelSet) []*RouteOpts {
if !r.Matchers.MatchAll(lset) {
return nil
}
var allMatches []*RouteOpts
for _, cr := range r.Routes {
matches := cr.Match(lset)
for _, ro := range matches {
ro.populateDefault(&r.RouteOpts)
}
allMatches = append(allMatches, matches...)
if matches != nil && !r.Continue {
break
}
}
if len(allMatches) == 0 {
allMatches = append(allMatches, &r.RouteOpts)
}
return allMatches
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error {
type route struct {
SendTo string `yaml:"send_to,omitempty"`
GroupBy []model.LabelName `yaml:"group_by,omitempty"`
GroupWait *model.Duration `yaml:"group_wait,omitempty"`
Match map[string]string `yaml:"match,omitempty"`
MatchRE map[string]string `yaml:"match_re,omitempty"`
Continue bool `yaml:"continue,omitempty"`
Routes []*Route `yaml:"routes,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
var v route
if err := unmarshal(&v); err != nil {
return err
}
for k, val := range v.Match {
if !model.LabelNameRE.MatchString(k) {
fmt.Errorf("invalid label name %q", k)
}
ln := model.LabelName(k)
r.Matchers = append(r.Matchers, NewMatcher(ln, val))
}
for k, val := range v.MatchRE {
if !model.LabelNameRE.MatchString(k) {
fmt.Errorf("invalid label name %q", k)
}
ln := model.LabelName(k)
m, err := NewRegexMatcher(ln, val)
if err != nil {
return err
}
r.Matchers = append(r.Matchers, m)
}
r.RouteOpts.GroupBy = make(map[model.LabelName]struct{}, len(v.GroupBy))
for _, ln := range v.GroupBy {
if _, ok := r.RouteOpts.GroupBy[ln]; ok {
return fmt.Errorf("duplicated label %q in group_by", ln)
}
r.RouteOpts.GroupBy[ln] = struct{}{}
}
r.RouteOpts.groupWait = (*time.Duration)(v.GroupWait)
r.RouteOpts.SendTo = v.SendTo
r.Continue = v.Continue
r.Routes = v.Routes
return checkOverflow(v.XXX, "route")
}
type RouteOpts struct {
// The identifier of the associated notification configuration
SendTo string
// 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
groupWait *time.Duration
}
func (ro *RouteOpts) String() string {
var labels []model.LabelName
for ln := range ro.GroupBy {
labels = append(labels, ln)
}
return fmt.Sprintf("<RouteOpts send_to:%q group_by:%q group_wait:%q>", ro.SendTo, labels, ro.groupWait)
}
func (ro *RouteOpts) GroupWait() time.Duration {
if ro.groupWait == nil {
return 0
}
return *ro.groupWait
}
func (ro *RouteOpts) populateDefault(parent *RouteOpts) {
for ln := range parent.GroupBy {
if _, ok := ro.GroupBy[ln]; !ok {
ro.GroupBy[ln] = struct{}{}
}
}
if ro.SendTo == "" {
ro.SendTo = parent.SendTo
}
if ro.groupWait == nil {
ro.groupWait = parent.groupWait
}
}

58
manager/route_test.go Normal file
View File

@ -0,0 +1,58 @@
package manager
import (
"reflect"
"testing"
"github.com/prometheus/common/model"
"gopkg.in/yaml.v2"
)
func TestRouteMatch(t *testing.T) {
in := `
send_to: 'notify-def'
routes:
- match:
owner: 'team-A'
send_to: 'notify-A'
- match_re:
owner: '^team-(B|C)$'
send_to: 'notify-BC'
`
var tree Route
if err := yaml.Unmarshal([]byte(in), &tree); err != nil {
t.Fatal(err)
}
var emptySet = map[model.LabelName]struct{}{}
tests := []struct {
input model.LabelSet
result []*RouteOpts
}{
{
input: model.LabelSet{
"owner": "team-A",
},
result: []*RouteOpts{
{
SendTo: "notify-A",
GroupBy: emptySet,
},
},
},
}
for _, test := range tests {
matches := tree.Match(test.input)
if !reflect.DeepEqual(matches, test.result) {
t.Errorf("expected:\n%v\n\ngot:\n%v", test.result, matches)
}
}
}

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main package manager
import ( import (
"time" "time"

View File

@ -1,4 +1,4 @@
package main package manager
import ( import (
"fmt" "fmt"