111 lines
3.8 KiB
Go
111 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type ServiceLink struct {
|
|
ServiceID int64 `json:"serviceId"`
|
|
AgentID string `json:"agentId"`
|
|
VMID int `json:"vmid"`
|
|
ComponentType string `json:"componentType"`
|
|
ComponentName string `json:"componentName"`
|
|
Port int `json:"port"`
|
|
}
|
|
|
|
func (s *Store) ServiceLinks() ([]ServiceLink, error) {
|
|
rows, err := s.db.Query(`SELECT service_id,agent_id,vmid,component_type,component_name,port FROM service_links ORDER BY service_id`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []ServiceLink
|
|
for rows.Next() {
|
|
var v ServiceLink
|
|
if err = rows.Scan(&v.ServiceID, &v.AgentID, &v.VMID, &v.ComponentType, &v.ComponentName, &v.Port); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, v)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) SaveServiceLink(v ServiceLink) (ServiceLink, error) {
|
|
v.AgentID = strings.TrimSpace(v.AgentID)
|
|
v.ComponentType = strings.TrimSpace(v.ComponentType)
|
|
v.ComponentName = strings.TrimSpace(v.ComponentName)
|
|
if v.ServiceID <= 0 {
|
|
return v, fmt.Errorf("выберите сервис")
|
|
}
|
|
if v.AgentID == "" && v.VMID <= 0 {
|
|
return v, fmt.Errorf("выберите агент или VMID")
|
|
}
|
|
if v.ComponentType != "" && v.ComponentType != "systemd" && v.ComponentType != "docker" && v.ComponentType != "process" {
|
|
return v, fmt.Errorf("неизвестный тип компонента")
|
|
}
|
|
if v.Port < 0 || v.Port > 65535 {
|
|
return v, fmt.Errorf("некорректный порт")
|
|
}
|
|
_, err := s.db.Exec(`INSERT INTO service_links(service_id,agent_id,vmid,component_type,component_name,port) VALUES(?,?,?,?,?,?) ON CONFLICT(service_id) DO UPDATE SET agent_id=excluded.agent_id,vmid=excluded.vmid,component_type=excluded.component_type,component_name=excluded.component_name,port=excluded.port`, v.ServiceID, v.AgentID, v.VMID, v.ComponentType, v.ComponentName, v.Port)
|
|
return v, err
|
|
}
|
|
func (s *Store) DeleteServiceLink(serviceID int64) error {
|
|
_, err := s.db.Exec(`DELETE FROM service_links WHERE service_id=?`, serviceID)
|
|
return err
|
|
}
|
|
|
|
func groupLinkedAlerts(metrics dashboardMetrics, alerts []Alert) []Alert {
|
|
if len(metrics.ServiceLinks) == 0 {
|
|
return alerts
|
|
}
|
|
agentOffline := map[string]ManagedAgent{}
|
|
for _, agent := range metrics.Agents {
|
|
if !agent.Online {
|
|
agentOffline[agent.ID] = agent
|
|
}
|
|
}
|
|
affected := map[string][]string{}
|
|
remove := map[string]bool{}
|
|
for _, link := range metrics.ServiceLinks {
|
|
agent, offline := agentOffline[link.AgentID]
|
|
if !offline {
|
|
continue
|
|
}
|
|
for _, service := range metrics.Services.Services {
|
|
if service.ID != link.ServiceID {
|
|
continue
|
|
}
|
|
for _, endpoint := range service.Endpoints {
|
|
if !endpoint.Up {
|
|
key := fmt.Sprintf("service-%d-%s", service.ID, endpoint.Kind)
|
|
remove[key] = true
|
|
affected[agent.ID] = append(affected[agent.ID], service.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(affected) == 0 {
|
|
return alerts
|
|
}
|
|
out := make([]Alert, 0, len(alerts))
|
|
for _, alert := range alerts {
|
|
skip := remove[alert.ID]
|
|
for agentID := range affected {
|
|
if alert.ID == "agent-offline-"+agentID+fmt.Sprintf("-%d", agentOffline[agentID].VMID) {
|
|
skip = true
|
|
}
|
|
}
|
|
if !skip {
|
|
out = append(out, alert)
|
|
}
|
|
}
|
|
for agentID, names := range affected {
|
|
sort.Strings(names)
|
|
agent := agentOffline[agentID]
|
|
out = append(out, Alert{ID: "group-agent-offline-" + agentID + fmt.Sprintf("-%d", agent.VMID), Severity: "critical", Source: "Корреляция", Title: agent.Name + ": агент недоступен, связанные сервисы не отвечают", Message: fmt.Sprintf("VMID %d. Затронуто сервисов: %d — %s. Вероятная первопричина: недоступна гостевая система или агент.", agent.VMID, len(names), strings.Join(names, ", "))})
|
|
}
|
|
return out
|
|
}
|