From 24ac73af5d90a436042db97081b23d092c7099cb Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Fri, 26 Jul 2013 03:04:53 +0200 Subject: [PATCH 1/2] Add loading configuration from file. --- config/Makefile | 17 ++++ config/config.go | 58 +++++++++++++ config/config.proto | 31 +++++++ config/config_test.go | 54 ++++++++++++ config/fixtures/empty.conf.input | 0 .../fixtures/invalid_proto_format.conf.input | 18 ++++ config/fixtures/sample.conf.input | 18 ++++ config/generated/config.pb.go | 83 +++++++++++++++++++ config/load.go | 42 ++++++++++ config/load_test.go | 25 ++++++ main.go | 26 +++--- 11 files changed, 357 insertions(+), 15 deletions(-) create mode 100644 config/Makefile create mode 100644 config/config.go create mode 100644 config/config.proto create mode 100644 config/config_test.go create mode 100644 config/fixtures/empty.conf.input create mode 100644 config/fixtures/invalid_proto_format.conf.input create mode 100644 config/fixtures/sample.conf.input create mode 100644 config/generated/config.pb.go create mode 100644 config/load.go create mode 100644 config/load_test.go 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..6a729c9bca --- /dev/null +++ b/config/config.go @@ -0,0 +1,58 @@ +// 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 ( + "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 { + // BUG: Nothing to do here for now. + 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.GetRepeatRate()) * time.Second, + }) + } + return rules +} diff --git a/config/config.proto b/config/config.proto new file mode 100644 index 0000000000..d5f9d5c3cd --- /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. + required string name_re = 1; + // The regex matching the label value. + required string value_re = 2; +} + +message AggregationRule { + repeated Filter filter = 1; + optional int32 repeat_rate = 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..a12e22d035 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,54 @@ +// 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" + +var configTests = []struct { + inputFile string + shouldFail bool + errContains string +}{ + { + inputFile: "empty.conf.input", + }, { + inputFile: "sample.conf.input", + }, { + inputFile: "invalid_proto_format.conf.input", + shouldFail: true, + errContains: "unknown field name", + }, +} + +func TestConfigs(t *testing.T) { + for i, configTest := range configTests { + _, err := LoadFromFile(path.Join(fixturesPath, configTest.inputFile)) + + if err != nil { + if !configTest.shouldFail { + t.Fatalf("%d. Error parsing config %v: %v", i, configTest.inputFile, err) + } else { + if !strings.Contains(err.Error(), configTest.errContains) { + t.Fatalf("%d. Expected error containing '%v', got: %v", i, configTest.errContains, err) + } + } + } + } +} 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..214c63ac03 --- /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: 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..886c90033e --- /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,req,name=name_re" json:"name_re,omitempty"` + ValueRe *string `protobuf:"bytes,2,req,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"` + RepeatRate *int32 `protobuf:"varint,2,opt,name=repeat_rate,def=7200" json:"repeat_rate,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_RepeatRate int32 = 7200 + +func (m *AggregationRule) GetFilter() []*Filter { + if m != nil { + return m.Filter + } + return nil +} + +func (m *AggregationRule) GetRepeatRate() int32 { + if m != nil && m.RepeatRate != nil { + return *m.RepeatRate + } + return Default_AggregationRule_RepeatRate +} + +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..c037f0085b --- /dev/null +++ b/config/load.go @@ -0,0 +1,42 @@ +// 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 ( + "code.google.com/p/goprotobuf/proto" + + pb "github.com/prometheus/alert_manager/config/generated" + "io/ioutil" +) + +func LoadFromString(configStr string) (Config, error) { + configProto := pb.AlertManagerConfig{} + if err := proto.UnmarshalText(configStr, &configProto); err != nil { + return Config{}, err + } + + config := Config{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..70cd1d4f44 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: %v", *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) From db599b6d26048e6157b9b4d7fd59ef8bd2844df9 Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Fri, 26 Jul 2013 13:02:51 +0200 Subject: [PATCH 2/2] PR comments fixups. --- config/config.go | 31 ++++++++++++------ config/config.proto | 6 ++-- config/config_test.go | 54 ++++++++++++++++++------------- config/fixtures/sample.conf.input | 2 +- config/generated/config.pb.go | 20 ++++++------ config/load.go | 5 +-- main.go | 2 +- 7 files changed, 71 insertions(+), 49 deletions(-) diff --git a/config/config.go b/config/config.go index 6a729c9bca..c20ae47486 100644 --- a/config/config.go +++ b/config/config.go @@ -14,11 +14,13 @@ package config import ( - "time" + "fmt" + "time" "code.google.com/p/goprotobuf/proto" pb "github.com/prometheus/alert_manager/config/generated" + "github.com/prometheus/alert_manager/manager" ) @@ -37,22 +39,31 @@ func (c Config) String() string { // Validate checks an entire parsed Config for the validity of its fields. func (c Config) Validate() error { - // BUG: Nothing to do here for now. + 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)) + 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())) - } + 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.GetRepeatRate()) * time.Second, - }) + Filters: filters, + RepeatRate: time.Duration(r.GetRepeatRateSeconds()) * time.Second, + }) } return rules } diff --git a/config/config.proto b/config/config.proto index d5f9d5c3cd..87aa84f5d5 100644 --- a/config/config.proto +++ b/config/config.proto @@ -16,14 +16,14 @@ package io.prometheus.alert_manager; // A regex-based label filter used in aggregations. message Filter { // The regex matching the label name. - required string name_re = 1; + optional string name_re = 1; // The regex matching the label value. - required string value_re = 2; + optional string value_re = 2; } message AggregationRule { repeated Filter filter = 1; - optional int32 repeat_rate = 2 [default = 7200]; + optional int32 repeat_rate_seconds = 2 [default = 7200]; } message AlertManagerConfig { diff --git a/config/config_test.go b/config/config_test.go index a12e22d035..803d7e118e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -21,34 +21,44 @@ import ( var fixturesPath = "fixtures" -var configTests = []struct { +type configTest struct { inputFile string shouldFail bool errContains string -}{ - { - inputFile: "empty.conf.input", - }, { - inputFile: "sample.conf.input", - }, { - inputFile: "invalid_proto_format.conf.input", - shouldFail: true, - errContains: "unknown field name", - }, } -func TestConfigs(t *testing.T) { - for i, configTest := range configTests { - _, err := LoadFromFile(path.Join(fixturesPath, configTest.inputFile)) - - if err != nil { - if !configTest.shouldFail { - t.Fatalf("%d. Error parsing config %v: %v", i, configTest.inputFile, err) - } else { - if !strings.Contains(err.Error(), configTest.errContains) { - t.Fatalf("%d. Expected error containing '%v', got: %v", i, configTest.errContains, err) - } +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/sample.conf.input b/config/fixtures/sample.conf.input index 214c63ac03..4922f502a8 100644 --- a/config/fixtures/sample.conf.input +++ b/config/fixtures/sample.conf.input @@ -7,7 +7,7 @@ aggregation_rule { name_re: "zone" value_re: "aa" } - repeat_rate: 3600 + repeat_rate_seconds: 3600 } aggregation_rule { diff --git a/config/generated/config.pb.go b/config/generated/config.pb.go index 886c90033e..ea62880093 100644 --- a/config/generated/config.pb.go +++ b/config/generated/config.pb.go @@ -14,8 +14,8 @@ var _ = &json.SyntaxError{} var _ = math.Inf type Filter struct { - NameRe *string `protobuf:"bytes,1,req,name=name_re" json:"name_re,omitempty"` - ValueRe *string `protobuf:"bytes,2,req,name=value_re" json:"value_re,omitempty"` + 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:"-"` } @@ -38,16 +38,16 @@ func (m *Filter) GetValueRe() string { } type AggregationRule struct { - Filter []*Filter `protobuf:"bytes,1,rep,name=filter" json:"filter,omitempty"` - RepeatRate *int32 `protobuf:"varint,2,opt,name=repeat_rate,def=7200" json:"repeat_rate,omitempty"` - XXX_unrecognized []byte `json:"-"` + 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_RepeatRate int32 = 7200 +const Default_AggregationRule_RepeatRateSeconds int32 = 7200 func (m *AggregationRule) GetFilter() []*Filter { if m != nil { @@ -56,11 +56,11 @@ func (m *AggregationRule) GetFilter() []*Filter { return nil } -func (m *AggregationRule) GetRepeatRate() int32 { - if m != nil && m.RepeatRate != nil { - return *m.RepeatRate +func (m *AggregationRule) GetRepeatRateSeconds() int32 { + if m != nil && m.RepeatRateSeconds != nil { + return *m.RepeatRateSeconds } - return Default_AggregationRule_RepeatRate + return Default_AggregationRule_RepeatRateSeconds } type AlertManagerConfig struct { diff --git a/config/load.go b/config/load.go index c037f0085b..88c86a38bd 100644 --- a/config/load.go +++ b/config/load.go @@ -14,10 +14,11 @@ package config import ( + "io/ioutil" + "code.google.com/p/goprotobuf/proto" pb "github.com/prometheus/alert_manager/config/generated" - "io/ioutil" ) func LoadFromString(configStr string) (Config, error) { @@ -26,7 +27,7 @@ func LoadFromString(configStr string) (Config, error) { return Config{}, err } - config := Config{configProto} + config := Config{AlertManagerConfig: configProto} err := config.Validate() return config, err diff --git a/main.go b/main.go index 70cd1d4f44..f7c5263335 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ func main() { conf, err := config.LoadFromFile(*configFile) if err != nil { - log.Fatalf("Error loading configuration from %s: %v", *configFile, err) + log.Fatalf("Error loading configuration from %s: %s", *configFile, err) } suppressor := manager.NewSuppressor()