diff --git a/config/Makefile b/config/Makefile new file mode 100644 index 0000000000..737f7ad40a --- /dev/null +++ b/config/Makefile @@ -0,0 +1,17 @@ +# Copyright 2013 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. + +all: generated/config.pb.go + +generated/config.pb.go: config.proto + protoc --go_out=generated/ config.proto diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000000..c20ae47486 --- /dev/null +++ b/config/config.go @@ -0,0 +1,69 @@ +// Copyright 2013 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. + +package config + +import ( + "fmt" + "time" + + "code.google.com/p/goprotobuf/proto" + + pb "github.com/prometheus/alert_manager/config/generated" + + "github.com/prometheus/alert_manager/manager" +) + +// Config encapsulates the configuration of an Alert Manager instance. It wraps +// the raw configuration protocol buffer to be able to add custom methods to +// it. +type Config struct { + // The protobuf containing the actual configuration values. + pb.AlertManagerConfig +} + +// String returns an ASCII serialization of the loaded configuration protobuf. +func (c Config) String() string { + return proto.MarshalTextString(&c.AlertManagerConfig) +} + +// Validate checks an entire parsed Config for the validity of its fields. +func (c Config) Validate() error { + for _, a := range c.AggregationRule { + for _, f := range a.Filter { + if f.NameRe == nil { + return fmt.Errorf("Missing name pattern (name_re) in filter definition: %s", proto.MarshalTextString(f)) + } + if f.ValueRe == nil { + return fmt.Errorf("Missing value pattern (value_re) in filter definition: %s", proto.MarshalTextString(f)) + } + } + } + return nil +} + +// Rules returns all the AggregationRules in a Config object. +func (c Config) AggregationRules() manager.AggregationRules { + rules := make(manager.AggregationRules, 0, len(c.AggregationRule)) + for _, r := range c.AggregationRule { + filters := make(manager.Filters, 0, len(r.Filter)) + for _, filter := range r.Filter { + filters = append(filters, manager.NewFilter(filter.GetNameRe(), filter.GetValueRe())) + } + rules = append(rules, &manager.AggregationRule{ + Filters: filters, + RepeatRate: time.Duration(r.GetRepeatRateSeconds()) * time.Second, + }) + } + return rules +} diff --git a/config/config.proto b/config/config.proto new file mode 100644 index 0000000000..87aa84f5d5 --- /dev/null +++ b/config/config.proto @@ -0,0 +1,31 @@ +// Copyright 2013 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. + +package io.prometheus.alert_manager; + +// A regex-based label filter used in aggregations. +message Filter { + // The regex matching the label name. + optional string name_re = 1; + // The regex matching the label value. + optional string value_re = 2; +} + +message AggregationRule { + repeated Filter filter = 1; + optional int32 repeat_rate_seconds = 2 [default = 7200]; +} + +message AlertManagerConfig { + repeated AggregationRule aggregation_rule = 1; +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000000..803d7e118e --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,64 @@ +// Copyright 2013 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. + +package config + +import ( + "path" + "strings" + "testing" +) + +var fixturesPath = "fixtures" + +type configTest struct { + inputFile string + shouldFail bool + errContains string +} + +func (ct *configTest) test(i int, t *testing.T) { + _, err := LoadFromFile(path.Join(fixturesPath, ct.inputFile)) + + if err != nil { + if !ct.shouldFail { + t.Fatalf("%d. Error parsing config %v: %v", i, ct.inputFile, err) + } else { + if !strings.Contains(err.Error(), ct.errContains) { + t.Fatalf("%d. Expected error containing '%v', got: %v", i, ct.errContains, err) + } + } + } +} + +func TestConfigs(t *testing.T) { + var configTests = []configTest{ + { + inputFile: "empty.conf.input", + }, { + inputFile: "sample.conf.input", + }, { + inputFile: "missing_name_re.conf.input", + shouldFail: true, + errContains: "Missing name pattern", + }, { + inputFile: "invalid_proto_format.conf.input", + shouldFail: true, + errContains: "unknown field name", + }, + } + + for i, ct := range configTests { + ct.test(i, t) + } +} diff --git a/config/fixtures/empty.conf.input b/config/fixtures/empty.conf.input new file mode 100644 index 0000000000..e69de29bb2 diff --git a/config/fixtures/invalid_proto_format.conf.input b/config/fixtures/invalid_proto_format.conf.input new file mode 100644 index 0000000000..f2ec221aee --- /dev/null +++ b/config/fixtures/invalid_proto_format.conf.input @@ -0,0 +1,18 @@ +aggregation_rule { + filter { + label_re: "service" // Unknown protobuf field name. + value_re: "discovery" + } + filter { + name_re: "zone" + value_re: "aa" + } + repeat_rate: 3600 +} + +aggregation_rule { + filter { + name_re: "service" + value_re: "test" + } +} diff --git a/config/fixtures/sample.conf.input b/config/fixtures/sample.conf.input new file mode 100644 index 0000000000..4922f502a8 --- /dev/null +++ b/config/fixtures/sample.conf.input @@ -0,0 +1,18 @@ +aggregation_rule { + filter { + name_re: "service" + value_re: "discovery" + } + filter { + name_re: "zone" + value_re: "aa" + } + repeat_rate_seconds: 3600 +} + +aggregation_rule { + filter { + name_re: "service" + value_re: "test" + } +} diff --git a/config/generated/config.pb.go b/config/generated/config.pb.go new file mode 100644 index 0000000000..ea62880093 --- /dev/null +++ b/config/generated/config.pb.go @@ -0,0 +1,83 @@ +// Code generated by protoc-gen-go. +// source: config.proto +// DO NOT EDIT! + +package io_prometheus_alert_manager + +import proto "code.google.com/p/goprotobuf/proto" +import json "encoding/json" +import math "math" + +// Reference proto, json, and math imports to suppress error if they are not otherwise used. +var _ = proto.Marshal +var _ = &json.SyntaxError{} +var _ = math.Inf + +type Filter struct { + NameRe *string `protobuf:"bytes,1,opt,name=name_re" json:"name_re,omitempty"` + ValueRe *string `protobuf:"bytes,2,opt,name=value_re" json:"value_re,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Filter) Reset() { *m = Filter{} } +func (m *Filter) String() string { return proto.CompactTextString(m) } +func (*Filter) ProtoMessage() {} + +func (m *Filter) GetNameRe() string { + if m != nil && m.NameRe != nil { + return *m.NameRe + } + return "" +} + +func (m *Filter) GetValueRe() string { + if m != nil && m.ValueRe != nil { + return *m.ValueRe + } + return "" +} + +type AggregationRule struct { + Filter []*Filter `protobuf:"bytes,1,rep,name=filter" json:"filter,omitempty"` + RepeatRateSeconds *int32 `protobuf:"varint,2,opt,name=repeat_rate_seconds,def=7200" json:"repeat_rate_seconds,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *AggregationRule) Reset() { *m = AggregationRule{} } +func (m *AggregationRule) String() string { return proto.CompactTextString(m) } +func (*AggregationRule) ProtoMessage() {} + +const Default_AggregationRule_RepeatRateSeconds int32 = 7200 + +func (m *AggregationRule) GetFilter() []*Filter { + if m != nil { + return m.Filter + } + return nil +} + +func (m *AggregationRule) GetRepeatRateSeconds() int32 { + if m != nil && m.RepeatRateSeconds != nil { + return *m.RepeatRateSeconds + } + return Default_AggregationRule_RepeatRateSeconds +} + +type AlertManagerConfig struct { + AggregationRule []*AggregationRule `protobuf:"bytes,1,rep,name=aggregation_rule" json:"aggregation_rule,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *AlertManagerConfig) Reset() { *m = AlertManagerConfig{} } +func (m *AlertManagerConfig) String() string { return proto.CompactTextString(m) } +func (*AlertManagerConfig) ProtoMessage() {} + +func (m *AlertManagerConfig) GetAggregationRule() []*AggregationRule { + if m != nil { + return m.AggregationRule + } + return nil +} + +func init() { +} diff --git a/config/load.go b/config/load.go new file mode 100644 index 0000000000..88c86a38bd --- /dev/null +++ b/config/load.go @@ -0,0 +1,43 @@ +// Copyright 2013 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. + +package config + +import ( + "io/ioutil" + + "code.google.com/p/goprotobuf/proto" + + pb "github.com/prometheus/alert_manager/config/generated" +) + +func LoadFromString(configStr string) (Config, error) { + configProto := pb.AlertManagerConfig{} + if err := proto.UnmarshalText(configStr, &configProto); err != nil { + return Config{}, err + } + + config := Config{AlertManagerConfig: configProto} + err := config.Validate() + + return config, err +} + +func LoadFromFile(fileName string) (Config, error) { + configStr, err := ioutil.ReadFile(fileName) + if err != nil { + return Config{}, err + } + + return LoadFromString(string(configStr)) +} diff --git a/config/load_test.go b/config/load_test.go new file mode 100644 index 0000000000..3c03110b50 --- /dev/null +++ b/config/load_test.go @@ -0,0 +1,25 @@ +// Copyright 2013 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. + +package config + +import ( + "testing" +) + +func TestLoadFromFile(t *testing.T) { + _, err := LoadFromFile("file-does-not-exist.conf") + if err == nil { + t.Error(err) + } +} diff --git a/main.go b/main.go index 47515fe590..f7c5263335 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,24 @@ import ( "flag" "log" + "github.com/prometheus/alert_manager/config" "github.com/prometheus/alert_manager/manager" "github.com/prometheus/alert_manager/web" "github.com/prometheus/alert_manager/web/api" ) +var ( + configFile = flag.String("configFile", "alertmanager.conf", "Alert Manager configuration file name.") +) + func main() { flag.Parse() + conf, err := config.LoadFromFile(*configFile) + if err != nil { + log.Fatalf("Error loading configuration from %s: %s", *configFile, err) + } + suppressor := manager.NewSuppressor() defer suppressor.Close() @@ -50,21 +60,7 @@ func main() { } go webService.ServeForever() - // BEGIN EXAMPLE CODE - replace with config loading later. - done := make(chan bool) - go func() { - rules := manager.AggregationRules{ - &manager.AggregationRule{ - Filters: manager.Filters{manager.NewFilter("service", "discovery")}, - }, - } - - aggregator.SetRules(rules) - - done <- true - }() - <-done - // END EXAMPLE CODE + aggregator.SetRules(conf.AggregationRules()) log.Println("Running summary dispatcher...") summarizer.Dispatch(suppressor)