huge refactor

- extendable backends
- better project structure
- better cli interface
This commit is contained in:
Matej Kramny
2019-02-20 11:14:45 +08:00
parent df31238a1f
commit 162d55b3f3
17 changed files with 946 additions and 705 deletions

288
backends/cachet/backend.go Normal file
View 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
}