Add maintenance windows for planned work
This commit is contained in:
125
maintenance_window.go
Normal file
125
maintenance_window.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MaintenanceWindow struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Active bool `json:"active"`
|
||||
StartedAt int64 `json:"startedAt"`
|
||||
EndsAt int64 `json:"endsAt"`
|
||||
Reason string `json:"reason"`
|
||||
AllAlerts bool `json:"allAlerts"`
|
||||
ServiceIDs []int64 `json:"serviceIds"`
|
||||
VMIDs []int `json:"vmids"`
|
||||
}
|
||||
|
||||
func (s *Store) MaintenanceWindow() MaintenanceWindow {
|
||||
var value string
|
||||
var window MaintenanceWindow
|
||||
if s.db.QueryRow(`SELECT value FROM settings WHERE key='maintenance_window'`).Scan(&value) == nil {
|
||||
_ = json.Unmarshal([]byte(value), &window)
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
window.Active = window.Enabled && window.StartedAt <= now && window.EndsAt > now
|
||||
if window.Enabled && window.EndsAt <= now {
|
||||
window.Enabled = false
|
||||
data, _ := json.Marshal(window)
|
||||
_, _ = s.db.Exec(`INSERT INTO settings(key,value) VALUES('maintenance_window',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`, string(data))
|
||||
}
|
||||
return window
|
||||
}
|
||||
|
||||
func (s *Store) SaveMaintenanceWindow(window MaintenanceWindow) (MaintenanceWindow, error) {
|
||||
now := time.Now()
|
||||
window.Reason = strings.TrimSpace(window.Reason)
|
||||
if window.Reason == "" {
|
||||
return window, fmt.Errorf("укажите причину плановых работ")
|
||||
}
|
||||
if window.EndsAt <= now.Unix() || window.EndsAt > now.Add(30*24*time.Hour).Unix() {
|
||||
return window, fmt.Errorf("окончание должно быть в будущем, максимум через 30 дней")
|
||||
}
|
||||
if !window.AllAlerts && len(window.ServiceIDs) == 0 && len(window.VMIDs) == 0 {
|
||||
return window, fmt.Errorf("выберите сервис, VM/LXC или все алерты")
|
||||
}
|
||||
window.Enabled = true
|
||||
window.Active = true
|
||||
window.StartedAt = now.Unix()
|
||||
window.ServiceIDs = uniqueInt64(window.ServiceIDs)
|
||||
window.VMIDs = uniqueInt(window.VMIDs)
|
||||
data, _ := json.Marshal(window)
|
||||
_, err := s.db.Exec(`INSERT INTO settings(key,value) VALUES('maintenance_window',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`, string(data))
|
||||
return window, err
|
||||
}
|
||||
|
||||
func (s *Store) StopMaintenanceWindow() (MaintenanceWindow, error) {
|
||||
window := s.MaintenanceWindow()
|
||||
window.Enabled = false
|
||||
window.Active = false
|
||||
window.EndsAt = time.Now().Unix()
|
||||
data, _ := json.Marshal(window)
|
||||
_, err := s.db.Exec(`INSERT INTO settings(key,value) VALUES('maintenance_window',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`, string(data))
|
||||
return window, err
|
||||
}
|
||||
|
||||
func splitMaintenanceAlerts(alerts []Alert, window MaintenanceWindow) (active, planned []Alert) {
|
||||
if !window.Active {
|
||||
return alerts, nil
|
||||
}
|
||||
for _, alert := range alerts {
|
||||
if maintenanceMatchesAlert(window, alert) {
|
||||
planned = append(planned, alert)
|
||||
} else {
|
||||
active = append(active, alert)
|
||||
}
|
||||
}
|
||||
return active, planned
|
||||
}
|
||||
|
||||
func maintenanceMatchesAlert(window MaintenanceWindow, alert Alert) bool {
|
||||
if window.AllAlerts {
|
||||
return true
|
||||
}
|
||||
for _, id := range window.ServiceIDs {
|
||||
value := strconv.FormatInt(id, 10)
|
||||
if strings.HasPrefix(alert.ID, "service-"+value+"-") || strings.HasPrefix(alert.ID, "service-tls-"+value+"-") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, vmid := range window.VMIDs {
|
||||
value := strconv.Itoa(vmid)
|
||||
if strings.HasSuffix(alert.ID, "-"+value) || strings.Contains(alert.Message, "VMID "+value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func uniqueInt64(values []int64) []int64 {
|
||||
seen := map[int64]bool{}
|
||||
result := make([]int64, 0, len(values))
|
||||
for _, value := range values {
|
||||
if value > 0 && !seen[value] {
|
||||
seen[value] = true
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func uniqueInt(values []int) []int {
|
||||
seen := map[int]bool{}
|
||||
result := make([]int, 0, len(values))
|
||||
for _, value := range values {
|
||||
if value > 0 && !seen[value] {
|
||||
seen[value] = true
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user