huge refactor
- extendable backends - better project structure - better cli interface
This commit is contained in:
288
backends/cachet/backend.go
Normal file
288
backends/cachet/backend.go
Normal file
@@ -0,0 +1,288 @@
|
||||
package cachetbackend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/castawaylabs/cachet-monitor/monitors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const DefaultTimeFormat = "15:04:05 Jan 2 MST"
|
||||
|
||||
type CachetBackend struct {
|
||||
URL string `json:"url" yaml:"url"`
|
||||
Token string `json:"token" yaml:"token"`
|
||||
Insecure bool `json:"insecure" yaml:"insecure"`
|
||||
DateFormat string `json:"date_format" yaml:"date_format"`
|
||||
}
|
||||
|
||||
type CachetResponse struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
func (api CachetBackend) ValidateMonitor(mon *monitors.AbstractMonitor) []string {
|
||||
errs := []string{}
|
||||
|
||||
params := mon.Params
|
||||
|
||||
componentID, componentIDOk := params["component_id"]
|
||||
metricID, metricIDOk := params["metric_id"]
|
||||
if !componentIDOk && !metricIDOk {
|
||||
errs = append(errs, "component_id and metric_id is unset")
|
||||
}
|
||||
|
||||
if _, ok := componentID.(int); !ok && componentIDOk {
|
||||
errs = append(errs, "component_id not integer")
|
||||
}
|
||||
if _, ok := metricID.(int); !ok && metricIDOk {
|
||||
errs = append(errs, "metric_id not integer")
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (api CachetBackend) Validate() []string {
|
||||
errs := []string{}
|
||||
|
||||
if len(api.URL) == 0 {
|
||||
errs = append(errs, "Cachet API URL invalid")
|
||||
}
|
||||
if len(api.Token) == 0 {
|
||||
errs = append(errs, "Cachet API Token invalid")
|
||||
}
|
||||
|
||||
if len(api.DateFormat) == 0 {
|
||||
api.DateFormat = DefaultTimeFormat
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (api CachetBackend) Ping() error {
|
||||
resp, _, err := api.NewRequest("GET", "/ping", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("API Responded with non-200 status code")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
// NewRequest wraps http.NewRequest
|
||||
func (api CachetBackend) NewRequest(requestType, url string, reqBody []byte) (*http.Response, interface{}, error) {
|
||||
req, err := http.NewRequest(requestType, api.URL+url, bytes.NewBuffer(reqBody))
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Cachet-Token", api.Token)
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport)
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: api.Insecure}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, CachetResponse{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
defer req.Body.Close()
|
||||
|
||||
var body struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
err = json.NewDecoder(res.Body).Decode(&body)
|
||||
|
||||
return res, body, err
|
||||
}
|
||||
|
||||
func (mon CachetBackend) Describe() []string {
|
||||
features := []string{"Cachet API"}
|
||||
|
||||
return features
|
||||
}
|
||||
|
||||
func (api CachetBackend) SendMetric(monitor monitors.MonitorInterface, lag int64) error {
|
||||
mon := monitor.GetMonitor()
|
||||
if _, ok := mon.Params["metric_id"]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
metricID := mon.Params["metric_id"].(int)
|
||||
|
||||
// report lag
|
||||
logrus.Debugf("Sending lag metric ID: %d RTT %vms", metricID, lag)
|
||||
|
||||
jsonBytes, _ := json.Marshal(map[string]interface{}{
|
||||
"value": lag,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
|
||||
resp, _, err := api.NewRequest("POST", "/metrics/"+strconv.Itoa(metricID)+"/points", jsonBytes)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
logrus.Warnf("Could not log metric! ID: %d, err: %v", metricID, err)
|
||||
}
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api CachetBackend) UpdateMonitor(mon monitors.MonitorInterface, status, previousStatus monitors.MonitorStatus, errs []error) error {
|
||||
monitor := mon.GetMonitor()
|
||||
l := logrus.WithFields(logrus.Fields{
|
||||
"monitor": monitor.Name,
|
||||
"time": time.Now().Format(api.DateFormat),
|
||||
})
|
||||
|
||||
errors := make([]string, len(errs))
|
||||
for i, err := range errs {
|
||||
errors[i] = err.Error()
|
||||
}
|
||||
|
||||
fmt.Println("errs", errs)
|
||||
|
||||
componentID := monitor.Params["component_id"].(int)
|
||||
incident, err := api.findIncident(componentID)
|
||||
if err != nil {
|
||||
l.Errorf("Couldn't find existing incidents: %v", err)
|
||||
}
|
||||
|
||||
if incident == nil {
|
||||
// create a new one
|
||||
incident = &Incident{
|
||||
Name: "",
|
||||
ComponentID: componentID,
|
||||
Message: "",
|
||||
Notify: true,
|
||||
}
|
||||
} else {
|
||||
// find component status
|
||||
component, err := api.getComponent(incident.ComponentID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
incident.ComponentStatus = component.Status
|
||||
}
|
||||
|
||||
tpls := monitor.Template
|
||||
tplData := api.getTemplateData(monitor)
|
||||
var tpl monitors.MessageTemplate
|
||||
|
||||
if status == monitors.MonitorStatusDown {
|
||||
tpl = tpls.Investigating
|
||||
tplData["FailReason"] = strings.Join(errors, "\n - ")
|
||||
l.Warnf("updating component. Monitor is down: %v", tplData["FailReason"])
|
||||
} else {
|
||||
// was down, created an incident, its now ok, make it resolved.
|
||||
tpl = tpls.Fixed
|
||||
l.Warn("Resolving incident")
|
||||
}
|
||||
|
||||
tplData["incident"] = incident
|
||||
subject, message := tpl.Exec(tplData)
|
||||
|
||||
if incident.ID == 0 {
|
||||
incident.Name = subject
|
||||
incident.Message = message
|
||||
} else {
|
||||
incident.Message += "\n\n---\n\n" + subject + ":\n\n" + message
|
||||
}
|
||||
|
||||
if status == monitors.MonitorStatusDown && (incident.ComponentStatus == 0 || incident.ComponentStatus > 2) {
|
||||
incident.Status = 1
|
||||
fmt.Println("incident status", incident.ComponentStatus)
|
||||
if incident.ComponentStatus >= 3 {
|
||||
// major outage
|
||||
incident.ComponentStatus = 4
|
||||
} else {
|
||||
incident.ComponentStatus = 3
|
||||
}
|
||||
} else if status == monitors.MonitorStatusUp {
|
||||
incident.Status = 4
|
||||
incident.ComponentStatus = 1
|
||||
}
|
||||
incident.Notify = true
|
||||
|
||||
// create/update incident
|
||||
if err := incident.Send(api); err != nil {
|
||||
l.Errorf("Error sending incident: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api CachetBackend) Tick(monitor monitors.MonitorInterface, status monitors.MonitorStatus, errs []error, lag int64) {
|
||||
mon := monitor.GetMonitor()
|
||||
if mon.GetLastStatus() == status || status == monitors.MonitorStatusNotSaturated {
|
||||
return
|
||||
}
|
||||
|
||||
logrus.Infof("updating backend for monitor")
|
||||
lastStatus := mon.UpdateLastStatus(status)
|
||||
|
||||
api.UpdateMonitor(monitor, status, lastStatus, errs)
|
||||
|
||||
if _, ok := mon.Params["metric_id"]; ok && lag > 0 {
|
||||
api.SendMetric(monitor, lag)
|
||||
}
|
||||
}
|
||||
|
||||
func (api CachetBackend) getComponent(componentID int) (*Component, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (api CachetBackend) findIncident(componentID int) (*Incident, error) {
|
||||
// fetch watching, identified & investigating
|
||||
statuses := []int{3, 2, 1}
|
||||
for _, status := range statuses {
|
||||
incidents, err := api.findIncidents(componentID, status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, incident := range incidents {
|
||||
incident.Status = status
|
||||
return incident, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (api CachetBackend) findIncidents(componentID int, status int) ([]*Incident, error) {
|
||||
resp, body, err := api.NewRequest("GET", "/incidents?component_id="+strconv.Itoa(componentID)+"&status="+strconv.Itoa(status), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data []*Incident
|
||||
if err := json.Unmarshal(body.(CachetResponse).Data, &data); err != nil {
|
||||
return nil, fmt.Errorf("Cannot find incidents: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("Could not fetch incidents! %v", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
Reference in New Issue
Block a user