initial alert routing tree
This commit is contained in:
parent
59b28fedda
commit
bbe679b432
24
main.go
24
main.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
|
@ -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"`
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
Loading…
Reference in New Issue