Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type Config struct {
Route *Route `yaml:"route,omitempty"`
InhibitRules []*InhibitRule `yaml:"inhibit_rules,omitempty"`
Receivers []*Receiver `yaml:"receivers,omitempty"`
Heartbeats []*Heartbeat `yaml:"heartbeats,omitempty"`
Templates []string `yaml:"templates"`

// Catches all undefined fields and must be empty after parsing.
Expand Down Expand Up @@ -231,6 +232,20 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
names[rcv.Name] = struct{}{}
}

for _, hbts := range c.Heartbeats {
for _, ogc := range hbts.OpsGenieConfigs {
if ogc.APIHost == "" {
if c.Global.OpsGenieAPIHost == "" {
return fmt.Errorf("no global OpsGenie URL set")
}
ogc.APIHost = c.Global.OpsGenieAPIHost
}
if !strings.HasSuffix(ogc.APIHost, "/") {
ogc.APIHost += "/"
}
}
}

// The root route must not have any matchers as it is the fallback node
// for all alerts.
if c.Route == nil {
Expand Down Expand Up @@ -443,6 +458,29 @@ func (c *Receiver) UnmarshalYAML(unmarshal func(interface{}) error) error {
return checkOverflow(c.XXX, "receiver config")
}

// Heartbeat configuration provides configuration on how to ping a heartbeat.
type Heartbeat struct {
// A unique identifier for this heartbeat.
Name string `yaml:"name"`

OpsGenieConfigs []*HeartbeatOpsGenieConfig `yaml:"opsgenie_configs,omitempty"`

// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Heartbeat) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain Heartbeat
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Name == "" {
return fmt.Errorf("missing name in heartbeat")
}
return checkOverflow(c.XXX, "heartbeat config")
}

// Regexp encapsulates a regexp.Regexp and makes it YAML marshalable.
type Regexp struct {
*regexp.Regexp
Expand Down
52 changes: 52 additions & 0 deletions config/heartbeats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.

package config

import (
"fmt"
"time"
)

var (
DefaultHeartbeatOpsGenieConfig = HeartbeatOpsGenieConfig{
Interval: duration(1 * time.Minute),
}
)

// HeartbeatOpsGenieConfig configures heartbeats to OpsGenie.
type HeartbeatOpsGenieConfig struct {
APIKey Secret `yaml:"api_key"`
APIHost string `yaml:"api_host"`
Name string `yaml:"name"`
Interval duration `yaml:"interval,omitempty"`

// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *HeartbeatOpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultHeartbeatOpsGenieConfig
type plain HeartbeatOpsGenieConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Name == "" {
return fmt.Errorf("missing hearbeat name in OpsGenie heartbeat config")
}
if c.APIKey == "" {
return fmt.Errorf("missing API key in OpsGenie heartbeat config")
}
return checkOverflow(c.XXX, "opsgenie heartbeat config")
}
83 changes: 83 additions & 0 deletions heartbeat/heartbeat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2016 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 heartbeat

import (
"time"

"github.com/prometheus/common/log"
)

type Fanout map[string]Heartbeat

type Heartbeat interface {
Interval() time.Duration
SendHeartbeat() error
}

// NewHeartbeatRunner returns a new HeartbeatRunner that runs a list of
// HeartbeatSender.
type HeartbeatRunner struct {
senders map[string]Fanout
done map[string]chan struct{}

log log.Logger
}

// NewHeartbeatSender returns a new HeartbeatSender.
func NewHeartbeatRunner(senders map[string]Fanout) *HeartbeatRunner {
runner := &HeartbeatRunner{
senders: senders,
log: log.With("component", "heartbeat_runner"),
done: make(map[string]chan struct{}),
}
return runner
}

func (r *HeartbeatRunner) Run() {
for _, t := range r.senders {
for k, s := range t {
r.done[k] = make(chan struct{})
go func(h Heartbeat, done chan struct{}) {
var err error
c := time.NewTicker(h.Interval())
defer c.Stop()

for {
select {
case <-c.C:
err = h.SendHeartbeat()
if err != nil {
log.Error(err)
}

case <-done:
return
}
}
}(s, r.done[k])
}
}
}

func (r *HeartbeatRunner) Stop() {
if r == nil {
return
}
for _, t := range r.senders {
for k, _ := range t {
close(r.done[k])
}
}
}
106 changes: 106 additions & 0 deletions heartbeat/impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2016 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 heartbeat

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/prometheus/alertmanager/config"
"github.com/prometheus/common/log"
)

type integration interface {
Heartbeat
name() string
}

const contentTypeJSON = "application/json"

func Build(confs []*config.Heartbeat) map[string]Fanout {
res := map[string]Fanout{}

for _, nc := range confs {
var (
hb = Fanout{}
add = func(i int, on integration) { hb[fmt.Sprintf("%s/%d", on.name(), i)] = on }
)

for i, c := range nc.OpsGenieConfigs {
n := NewOpsGenie(c)
add(i, n)
}
res[nc.Name] = hb
}
return res

}

// OpsGenie represents a OpsGenie implementation of Heartbeat
type OpsGenie struct {
conf *config.HeartbeatOpsGenieConfig
done chan struct{}

log log.Logger
}

// Returns a new OpsGenie object
func NewOpsGenie(conf *config.HeartbeatOpsGenieConfig) *OpsGenie {
return &OpsGenie{
conf: conf,
done: make(chan struct{}),
log: log.With("component", "opsgenie_heartbeat"),
}
}

func (o *OpsGenie) name() string { return fmt.Sprintf("[%s] %s", "opsgenie", o.conf.Name) }

// Represents an OpsGenie heartbeat message
type opsGenieHeartBeatMessage struct {
APIKey string `json:"apiKey"`
Name string `json:"name"`
}

func (o *OpsGenie) Interval() time.Duration {
return time.Duration(o.conf.Interval)
}

func (o *OpsGenie) SendHeartbeat() error {
var msg = &opsGenieHeartBeatMessage{
APIKey: string(o.conf.APIKey),
Name: o.conf.Name,
}
apiURL := o.conf.APIHost + "v1/json/heartbeat/send"

var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
return err
}
log.Debugf("Sending heartbeat to %s: %s", o.conf.APIHost, buf.String())

resp, err := http.Post(apiURL, contentTypeJSON, &buf)
if err != nil {
return err
}
resp.Body.Close()

if resp.StatusCode/100 != 2 {
return fmt.Errorf("unexpected status code %v from %s", resp.StatusCode, apiURL)
}

return nil
}
15 changes: 12 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/prometheus/common/version"

"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/heartbeat"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/provider/boltmem"
"github.com/prometheus/alertmanager/template"
Expand Down Expand Up @@ -104,9 +105,10 @@ func main() {
defer silences.Close()

var (
inhibitor *Inhibitor
tmpl *template.Template
disp *Dispatcher
inhibitor *Inhibitor
tmpl *template.Template
disp *Dispatcher
heartbeatRunner *heartbeat.HeartbeatRunner
)
defer disp.Stop()

Expand Down Expand Up @@ -146,6 +148,10 @@ func main() {
log.Fatal(err)
}

heartbeat_builder := func(hrbts []*config.Heartbeat) *heartbeat.HeartbeatRunner {
return heartbeat.NewHeartbeatRunner(heartbeat.Build(hrbts))
}

reload := func() (err error) {
log.With("file", *configFile).Infof("Loading configuration file")
defer func() {
Expand Down Expand Up @@ -173,12 +179,15 @@ func main() {

inhibitor.Stop()
disp.Stop()
heartbeatRunner.Stop()

inhibitor = NewInhibitor(alerts, conf.InhibitRules, marker)
disp = NewDispatcher(alerts, NewRoute(conf.Route, nil), build(conf.Receivers), marker)
heartbeatRunner = heartbeat_builder(conf.Heartbeats)

go disp.Run()
go inhibitor.Run()
heartbeatRunner.Run()

return nil
}
Expand Down