mirror of https://github.com/statping/statping
Downtimes CRUD (#11)
parent
6636fae7bc
commit
ee5a4b8f37
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/statping/statping/types/checkins"
|
||||
"github.com/statping/statping/types/configs"
|
||||
"github.com/statping/statping/types/core"
|
||||
"github.com/statping/statping/types/downtimes"
|
||||
"github.com/statping/statping/types/errors"
|
||||
"github.com/statping/statping/types/groups"
|
||||
"github.com/statping/statping/types/incidents"
|
||||
|
@ -163,6 +164,10 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
|
|||
case *incidents.IncidentUpdate:
|
||||
objName = "incident_update"
|
||||
objId = v.Id
|
||||
case *downtimes.Downtime:
|
||||
objName = "downtime"
|
||||
objId = v.Id
|
||||
|
||||
default:
|
||||
objName = fmt.Sprintf("%T", v)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/statping/statping/types/downtimes"
|
||||
"github.com/statping/statping/types/services"
|
||||
"github.com/statping/statping/utils"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func findDowntime(r *http.Request) (*downtimes.Downtime, error) {
|
||||
vars := mux.Vars(r)
|
||||
id := utils.ToInt(vars["id"])
|
||||
downtime, err := downtimes.Find(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return downtime, nil
|
||||
}
|
||||
|
||||
func apiAllDowntimesForServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
serviceId := utils.ToInt(vars["service_id"])
|
||||
|
||||
ninetyDaysAgo := time.Now().Add(time.Duration(-90*24) * time.Hour)
|
||||
|
||||
downtime, err := downtimes.FindByService(serviceId, ninetyDaysAgo, time.Now())
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
sendJsonAction(downtime, "fetch", w, r)
|
||||
}
|
||||
|
||||
func apiCreateDowntimeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var downtime *downtimes.Downtime
|
||||
if err := DecodeJSON(r, &downtime); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := services.FindFirstFromDB(downtime.ServiceId)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
downtime.Type = "manual"
|
||||
downtime.Id = zeroInt64
|
||||
|
||||
if err := downtime.Create(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if downtime.End == nil {
|
||||
updateFields := map[string]interface{}{
|
||||
"online": false,
|
||||
"current_downtime": downtime.Id,
|
||||
"manual_downtime": true,
|
||||
}
|
||||
|
||||
if err := s.UpdateSpecificFields(updateFields); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sendJsonAction(downtime, "create", w, r)
|
||||
}
|
||||
|
||||
func apiDowntimeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
downtime, err := findDowntime(r)
|
||||
if downtime == nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
sendJsonAction(downtime, "fetch", w, r)
|
||||
}
|
||||
|
||||
func apiPatchDowntimeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
downtime, err := findDowntime(r)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
var req downtimes.Downtime
|
||||
if err := DecodeJSON(r, &req); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := services.FindFirstFromDB(downtime.ServiceId)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if downtime.End != nil && req.End == nil {
|
||||
fmt.Errorf("Cannot reopen a downtime!")
|
||||
}
|
||||
|
||||
if downtime.End == nil && req.End != nil {
|
||||
updateFields := map[string]interface{}{
|
||||
"online": true,
|
||||
"failure_counter": 0,
|
||||
"current_downtime": 0,
|
||||
"manual_downtime": false,
|
||||
}
|
||||
|
||||
if err := s.UpdateSpecificFields(updateFields); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
downtime.Start = req.Start
|
||||
downtime.End = req.End
|
||||
downtime.Type = "manual"
|
||||
downtime.Failures = req.Failures
|
||||
downtime.SubStatus = req.SubStatus
|
||||
|
||||
if err := downtime.Update(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sendJsonAction(downtime, "update", w, r)
|
||||
}
|
||||
|
||||
func apiDeleteDowntimeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
downtime, err := findDowntime(r)
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
if downtime.End == nil {
|
||||
s2, err := services.FindFirstFromDB(downtime.ServiceId)
|
||||
if err != nil {
|
||||
fmt.Errorf("Error updating service")
|
||||
}
|
||||
s2.LastProcessingTime = zeroTime
|
||||
s2.Online = true
|
||||
s2.FailureCounter = 0
|
||||
s2.CurrentDowntime = 0
|
||||
s2.ManualDowntime = false
|
||||
|
||||
if err := s2.Update(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = downtime.Delete()
|
||||
if err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sendJsonAction(downtime, "delete", w, r)
|
||||
}
|
|
@ -192,6 +192,13 @@ func Router() *mux.Router {
|
|||
api.Handle("/api/checkins/{api}", authenticated(checkinDeleteHandler, false)).Methods("DELETE")
|
||||
//r.Handle("/checkin/{api}", http.HandlerFunc(checkinHitHandler))
|
||||
|
||||
// API DOWNTIME Routes
|
||||
api.Handle("/api/service/{service_id}/downtimes", authenticated(apiAllDowntimesForServiceHandler, false)).Methods("GET")
|
||||
api.Handle("/api/downtimes", authenticated(apiCreateDowntimeHandler, false)).Methods("POST")
|
||||
api.Handle("/api/downtimes/{id}", authenticated(apiDowntimeHandler, false)).Methods("GET")
|
||||
api.Handle("/api/downtimes/{id}", authenticated(apiPatchDowntimeHandler, false)).Methods("PATCH")
|
||||
api.Handle("/api/downtimes/{id}", authenticated(apiDeleteDowntimeHandler, false)).Methods("DELETE")
|
||||
|
||||
// API Generic Routes
|
||||
|
||||
r.Handle("/health", http.HandlerFunc(healthCheckHandler))
|
||||
|
|
|
@ -158,6 +158,7 @@ func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
s2.Online = zeroBool
|
||||
s2.FailureCounter = zeroInt
|
||||
s2.CurrentDowntime = zeroInt64
|
||||
s2.ManualDowntime = zeroBool
|
||||
|
||||
if err := s2.Update(); err != nil {
|
||||
sendErrorJson(err, w, r)
|
||||
|
@ -444,27 +445,32 @@ func apiServiceBlockSeriesHandlerCoreV2(r *http.Request, service *services.Servi
|
|||
Status: services.STATUS_UP,
|
||||
Downtimes: &[]services.Downtime{}}
|
||||
|
||||
for _, data := range *downtimesList {
|
||||
now := time.Now()
|
||||
|
||||
if (currentFrameTime.Before(data.Start) && nextFrameTime.After(data.Start)) ||
|
||||
(currentFrameTime.Before(data.End) && nextFrameTime.After(data.End)) ||
|
||||
(currentFrameTime.After(data.Start) && nextFrameTime.Before(data.End)) {
|
||||
for _, data := range *downtimesList {
|
||||
if data.End == nil {
|
||||
data.End = &now
|
||||
}
|
||||
|
||||
if (currentFrameTime.Before(*data.Start) && nextFrameTime.After(*data.Start)) ||
|
||||
(currentFrameTime.Before(*data.End) && nextFrameTime.After(*data.End)) ||
|
||||
(currentFrameTime.After(*data.Start) && nextFrameTime.Before(*data.End)) {
|
||||
|
||||
start := data.Start
|
||||
end := data.End
|
||||
|
||||
if currentFrameTime.After(data.Start) {
|
||||
start = currentFrameTime
|
||||
if currentFrameTime.After(*data.Start) {
|
||||
start = ¤tFrameTime
|
||||
}
|
||||
|
||||
if nextFrameTime.Before(data.End) {
|
||||
end = nextFrameTime
|
||||
if nextFrameTime.Before(*data.End) {
|
||||
end = &nextFrameTime
|
||||
}
|
||||
|
||||
*block.Downtimes = append(*block.Downtimes, services.Downtime{
|
||||
Start: start,
|
||||
End: end,
|
||||
Duration: end.Sub(start).Milliseconds(),
|
||||
Start: *start,
|
||||
End: *end,
|
||||
Duration: end.Sub(*start).Milliseconds(),
|
||||
SubStatus: services.HandleEmptyStatus(data.SubStatus),
|
||||
})
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
zeroInt64 int64
|
||||
)
|
||||
var db database.Database
|
||||
var dbHits database.Database
|
||||
|
||||
|
@ -25,6 +28,29 @@ func Find(id int64) (*Downtime, error) {
|
|||
return &downtime, q.Error()
|
||||
}
|
||||
|
||||
func (c *Downtime) Validate() error {
|
||||
if c.Type == "manual" {
|
||||
if c.End != nil && c.End.After(time.Now()) || c.Start.After(time.Now()) {
|
||||
return fmt.Errorf("Downtime cannot be in future")
|
||||
}
|
||||
if c.ServiceId == zeroInt64 {
|
||||
return fmt.Errorf("Service ID cannot be null")
|
||||
}
|
||||
if c.SubStatus != "down" && c.SubStatus != "degraded" {
|
||||
return fmt.Errorf("SubStatus can only be 'down' or 'degraded'")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Downtime) BeforeCreate() error {
|
||||
return c.Validate()
|
||||
}
|
||||
|
||||
func (c *Downtime) BeforeUpdate() error {
|
||||
return c.Validate()
|
||||
}
|
||||
|
||||
func FindByService(service int64, start time.Time, end time.Time) (*[]Downtime, error) {
|
||||
var downtime []Downtime
|
||||
q := db.Where("service = ? and start BETWEEN ? AND ? ", service, start, end)
|
||||
|
@ -43,10 +69,6 @@ func (c *Downtime) Update() error {
|
|||
}
|
||||
|
||||
func (c *Downtime) Delete() error {
|
||||
q := dbHits.Where("id = ?", c.Id).Delete(&Downtime{})
|
||||
if err := q.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
q = db.Model(&Downtime{}).Delete(c)
|
||||
q := db.Model(&Downtime{}).Delete(c)
|
||||
return q.Error()
|
||||
}
|
||||
|
|
|
@ -6,10 +6,11 @@ import (
|
|||
|
||||
// Checkin struct will allow an application to send a recurring HTTP GET to confirm a service is online
|
||||
type Downtime struct {
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
|
||||
SubStatus string `gorm:"column:sub_status" json:"sub_status"`
|
||||
Failures int `gorm:"column:failures" json:"failures"`
|
||||
Start time.Time `gorm:"index;column:start" json:"start"`
|
||||
End time.Time `gorm:"column:end" json:"end"`
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
|
||||
SubStatus string `gorm:"column:sub_status" json:"sub_status"`
|
||||
Failures int `gorm:"column:failures" json:"failures"`
|
||||
Start *time.Time `gorm:"index;column:start" json:"start"`
|
||||
End *time.Time `gorm:"column:end" json:"end"`
|
||||
Type string `gorm:"default:'auto';column:type" json:"type"`
|
||||
}
|
||||
|
|
|
@ -15,18 +15,6 @@ const (
|
|||
STATUS_DEGRADED = "degraded"
|
||||
)
|
||||
|
||||
const (
|
||||
FAILURE_TYPE_COMPLETE = "complete"
|
||||
FAILURE_TYPE_DEGRADED = "degraded"
|
||||
FAILURE_TYPE_DEFAULT = ""
|
||||
)
|
||||
|
||||
var FailureTypeStatusMap = map[string]string{
|
||||
FAILURE_TYPE_DEFAULT: STATUS_DOWN,
|
||||
FAILURE_TYPE_COMPLETE: STATUS_DOWN,
|
||||
FAILURE_TYPE_DEGRADED: STATUS_DEGRADED,
|
||||
}
|
||||
|
||||
func ApplyStatus(current string, apply string, defaultStatus string) string {
|
||||
switch current {
|
||||
case STATUS_DOWN:
|
||||
|
|
|
@ -142,13 +142,15 @@ func (s *Service) UpdateOrder() (err error) {
|
|||
d := db.Model(s).Where(" id = ? ", s.Id).Updates(updateFields)
|
||||
if err = d.Error(); d.Error() != nil {
|
||||
log.Errorf("[DB ERROR]Failed toservice order : %s %s %s %s", s.Id, s.Name, updateFields, d.Error())
|
||||
return err
|
||||
}
|
||||
if d.RowsAffected() == 0 {
|
||||
err = fmt.Errorf("[Zero]Failed to update service order : %s %s %s %s", s.Id, s.Name, updateFields, d.Error())
|
||||
log.Errorf("[Zero]Failed to update service order : %s %s %s %s", s.Id, s.Name, updateFields, d.Error())
|
||||
return err
|
||||
}
|
||||
log.Infof("Service Order updates Saved : %s %s %s", s.Id, s.Name, updateFields)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Delete() error {
|
||||
|
@ -208,6 +210,20 @@ func (s *Service) acquireServiceRun() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateSpecificFields(updateFields map[string]interface{}) error {
|
||||
d := db.Model(s).Where(" id = ?", s.Id).Updates(updateFields)
|
||||
if d.Error() != nil {
|
||||
log.Errorf("[DB ERROR]Failed to update service : %s %s %s %s", s.Id, s.Name, updateFields, d.Error())
|
||||
return d.Error()
|
||||
}
|
||||
if d.RowsAffected() == 0 {
|
||||
log.Errorf("[Zero]Failed to update service : %s %s %s %s", s.Id, s.Name, updateFields, d.Error())
|
||||
return fmt.Errorf("Failed to update Service fields : %s", s.Id)
|
||||
}
|
||||
log.Infof("Service Updates Saved : %s %s %s", s.Id, s.Name, updateFields)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) markServiceRunProcessed() {
|
||||
updateFields := map[string]interface{}{
|
||||
"online": s.Online,
|
||||
|
@ -218,12 +234,14 @@ func (s *Service) markServiceRunProcessed() {
|
|||
"current_downtime": s.CurrentDowntime,
|
||||
}
|
||||
|
||||
d := db.Model(s).Where(" id = ? ", s.Id).Updates(updateFields)
|
||||
d := db.Model(s).Where(" id = ? and manual_downtime = ?", s.Id, false).Updates(updateFields)
|
||||
if d.Error() != nil {
|
||||
log.Errorf("[DB ERROR]Failed to update service run : %s %s %s %s", s.Id, s.Name, updateFields, d.Error())
|
||||
return
|
||||
}
|
||||
if d.RowsAffected() == 0 {
|
||||
log.Errorf("[Zero]Failed to update service run : %s %s %s %s", s.Id, s.Name, updateFields, d.Error())
|
||||
return
|
||||
}
|
||||
log.Infof("Service Run Updates Saved : %s %s %s", s.Id, s.Name, updateFields)
|
||||
}
|
||||
|
|
|
@ -218,8 +218,12 @@ func addDurations(s []series, on bool) int64 {
|
|||
|
||||
func addDowntimeDurations(d *[]downtimes.Downtime) int64 {
|
||||
var dur int64
|
||||
now := time.Now()
|
||||
for _, v := range *d {
|
||||
dur += v.End.Sub(v.Start).Milliseconds()
|
||||
if v.End == nil {
|
||||
v.End = &now
|
||||
}
|
||||
dur += v.End.Sub(*v.Start).Milliseconds()
|
||||
}
|
||||
return dur
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ func CheckServices() {
|
|||
|
||||
func refreshAllServices() {
|
||||
for {
|
||||
time.Sleep(time.Duration(time.Second * 60))
|
||||
time.Sleep(time.Duration(time.Second * 20))
|
||||
|
||||
newList := all()
|
||||
|
||||
|
@ -71,7 +71,7 @@ CheckLoop:
|
|||
s, er := Find(s.Id)
|
||||
|
||||
if er == nil {
|
||||
if err == nil {
|
||||
if err == nil && !s.ManualDowntime {
|
||||
|
||||
log.Infof("Service Run Started : %s %s", s.Id, s.Name)
|
||||
|
||||
|
@ -579,8 +579,10 @@ func (s *Service) HandleDowntime(err error, record bool) {
|
|||
|
||||
s.Online = false
|
||||
|
||||
t := time.Now().Add(time.Duration(-s.FailureCounter*s.Interval) * (time.Second))
|
||||
|
||||
downtime := &downtimes.Downtime{
|
||||
Start: time.Now().Add(time.Duration(-s.FailureCounter*s.Interval) * (time.Second)),
|
||||
Start: &t,
|
||||
ServiceId: s.Id,
|
||||
}
|
||||
|
||||
|
@ -594,12 +596,16 @@ func (s *Service) HandleDowntime(err error, record bool) {
|
|||
}
|
||||
}
|
||||
|
||||
downtime.End = time.Now()
|
||||
now := time.Now()
|
||||
|
||||
downtime.End = &now
|
||||
newStatus := HandleEmptyStatus(s.LastFailureType)
|
||||
|
||||
start := time.Now().Add(time.Duration(-s.Interval) * (time.Second))
|
||||
|
||||
if downtime.SubStatus != "" && downtime.SubStatus != newStatus {
|
||||
downtime.Id = 0
|
||||
downtime.Start = time.Now().Add(time.Duration(-s.Interval) * (time.Second))
|
||||
downtime.Start = &start
|
||||
}
|
||||
|
||||
downtime.SubStatus = newStatus
|
||||
|
|
|
@ -75,6 +75,7 @@ type Service struct {
|
|||
FailureCounter int `gorm:"column:failure_counter" json:"-" yaml:"-"`
|
||||
CurrentDowntime int64 `gorm:"column:current_downtime" json:"-" yaml:"-"`
|
||||
LastFailureType string `gorm:"-" json:"-" yaml:"-"`
|
||||
ManualDowntime bool `gorm:"default:false;column:manual_downtime" json:"manual_downtime"`
|
||||
}
|
||||
|
||||
// ServiceOrder will reorder the services based on 'order_id' (Order)
|
||||
|
|
Loading…
Reference in New Issue