Routing rules: - RouteRule model: 3 match fields (caller_id, dest, ingress_trunk regex), 3 action types (node/pool/auto), time-based (days of week, time range) - Engine router.go: CompiledRule with pre-compiled regex, PickNodeForCall with cascade (dead node → next rule), pickNodeFromPool - Separate sync.RWMutex for Router — zero contention admin vs traffic - Validate-before-save pattern: engine validates before JSON save - Graceful degradation: corrupted rules skipped, no rules → PickNode() Persistence: - data/routing.json with atomic save (tmp→rename), config.Manager - Auto-created empty on first start API (admin only): - GET/POST/PUT/DELETE /api/routing/rules - PUT /api/routing/rules/:id/toggle - PUT /api/routing/rules/reorder Integration: - /api/route now uses PickNodeForCall with caller_id/dest/ingress_trunk query params (already sent by route.lua) - RouteResponse includes matched_rule field for debugging - Main.go loads routing rules on startup with graceful fallback Web UI: - /routing page: table with priority arrows, inline edit, create form, regex test tool, toggle, delete, action badges, hits counter Tests: - 28 tests: matchRule, time matching, PickNodeForCall cascade, pool scoring, validation, hit counting, compilation
274 lines
9.8 KiB
Go
274 lines
9.8 KiB
Go
// Package models содержит общие типы данных pulse-lets-go.
|
||
package models
|
||
|
||
import "time"
|
||
|
||
// --- NATS: метрика от нижестоящей АТС ---
|
||
|
||
// NodeMetric — сообщение, публикуемое нодой в NATS каждые 5 сек.
|
||
type NodeMetric struct {
|
||
NodeID string `json:"node_id"`
|
||
TS int64 `json:"ts"` // unix timestamp
|
||
Status string `json:"status"` // "ok", "degraded", "down"
|
||
ActiveCalls int `json:"active_calls"`
|
||
MaxCalls int `json:"max_calls"`
|
||
IdleCPU float64 `json:"idle_cpu"` // 0..100 %
|
||
LoadAvg float64 `json:"load_avg"` // system load average (1m)
|
||
CallFailureRate float64 `json:"call_failure_rate"` // 0.0..100.0 %
|
||
SIPGateway string `json:"sip_gateway,omitempty"` // SIP-адрес ноды для авто-создания транка
|
||
}
|
||
|
||
// --- In-memory состояние ноды в engine ---
|
||
|
||
// NodeState — полное состояние ноды в памяти балансировщика.
|
||
// Содержит данные последней метрики + поля ручного управления.
|
||
type NodeState struct {
|
||
NodeMetric
|
||
Disabled bool `json:"disabled"`
|
||
DisabledReason string `json:"disabled_reason"`
|
||
Score float64 `json:"score"`
|
||
LethalReason string `json:"lethal_reason,omitempty"` // причина, если score = -100
|
||
}
|
||
|
||
// --- Trunk ---
|
||
|
||
// TrunkType — тип транка.
|
||
type TrunkType string
|
||
|
||
const (
|
||
TrunkIngress TrunkType = "ingress"
|
||
TrunkBalance TrunkType = "balance"
|
||
TrunkFallback TrunkType = "fallback"
|
||
)
|
||
|
||
// Trunk — модель транка (из trunks.json).
|
||
type Trunk struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Type TrunkType `json:"type"`
|
||
NodeID string `json:"node_id,omitempty"` // только для balance
|
||
Gateway string `json:"gateway"`
|
||
Codecs []string `json:"codecs,omitempty"`
|
||
Context string `json:"context,omitempty"`
|
||
Enabled bool `json:"enabled"`
|
||
Description string `json:"description,omitempty"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
// --- User ---
|
||
|
||
// UserRole — роль пользователя.
|
||
type UserRole string
|
||
|
||
const (
|
||
RoleAdmin UserRole = "admin"
|
||
RoleViewer UserRole = "viewer"
|
||
)
|
||
|
||
// User — модель пользователя (из users.json).
|
||
type User struct {
|
||
ID string `json:"id"`
|
||
Username string `json:"username"`
|
||
PasswordHash string `json:"password_hash"`
|
||
Role UserRole `json:"role"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
// --- Auth ---
|
||
|
||
// LoginRequest — тело запроса на /api/auth/login.
|
||
type LoginRequest struct {
|
||
Username string `json:"username"`
|
||
Password string `json:"password"`
|
||
}
|
||
|
||
// LoginResponse — тело ответа /api/auth/login.
|
||
type LoginResponse struct {
|
||
AccessToken string `json:"access_token"`
|
||
RefreshToken string `json:"refresh_token"`
|
||
ExpiresIn int64 `json:"expires_in"`
|
||
}
|
||
|
||
// --- Routing Rules ---
|
||
|
||
// RouteRule — правило маршрутизации: CID-based, destination-based, ingress-trunk routing.
|
||
// Правила проверяются по порядку Priority (first-match-wins).
|
||
// Все непустые Match-поля должны совпасть (AND-логика).
|
||
// Пустые Match-поля — не проверяются.
|
||
type RouteRule struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Enabled bool `json:"enabled"`
|
||
Priority int `json:"priority"`
|
||
|
||
// Match conditions — regex, пустая строка = не проверять
|
||
MatchCallerID string `json:"match_caller_id,omitempty"`
|
||
MatchDestination string `json:"match_destination,omitempty"`
|
||
MatchIngressTrunk string `json:"match_ingress_trunk,omitempty"`
|
||
|
||
// Time-based match — пустые = не проверять
|
||
MatchDaysOfWeek string `json:"match_days,omitempty"` // "1-5" | "1,3,5"
|
||
MatchTimeStart string `json:"match_time_start,omitempty"` // "HH:MM"
|
||
MatchTimeEnd string `json:"match_time_end,omitempty"` // "HH:MM"
|
||
|
||
// Action
|
||
ActionType string `json:"action_type"` // "node" | "pool" | "auto"
|
||
ActionNodeID string `json:"action_node_id,omitempty"`
|
||
ActionPoolIDs []string `json:"action_pool_ids,omitempty"`
|
||
|
||
Description string `json:"description,omitempty"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
// --- /api/route ---
|
||
|
||
// RouteResponse — ответ на запрос маршрутизации.
|
||
type RouteResponse struct {
|
||
// Успех
|
||
NodeID string `json:"node_id,omitempty"`
|
||
Score float64 `json:"score,omitempty"`
|
||
SIPGateway string `json:"sip_gateway,omitempty"`
|
||
|
||
// Какое правило маршрутизации применилось (пустая строка = none)
|
||
MatchedRule string `json:"matched_rule,omitempty"`
|
||
|
||
// Fallback
|
||
Fallback bool `json:"fallback,omitempty"`
|
||
Reason string `json:"reason,omitempty"`
|
||
|
||
// Общая информация о нодах
|
||
Nodes []RouteNodeInfo `json:"nodes,omitempty"`
|
||
|
||
// Ошибка
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
// RouteNodeInfo — информация о ноде в ответе маршрутизации.
|
||
type RouteNodeInfo struct {
|
||
ID string `json:"id"`
|
||
Score float64 `json:"score"`
|
||
Reason string `json:"reason,omitempty"`
|
||
Status string `json:"status"`
|
||
}
|
||
|
||
// --- API: Nodes ---
|
||
|
||
// ToggleNodeRequest — запрос на включение/выключение ноды.
|
||
type ToggleNodeRequest struct {
|
||
Disabled bool `json:"disabled"`
|
||
Reason string `json:"reason,omitempty"`
|
||
}
|
||
|
||
// NodeInfo — информация о ноде для фронта.
|
||
type NodeInfo struct {
|
||
NodeID string `json:"node_id"`
|
||
TS int64 `json:"ts"`
|
||
Status string `json:"status"`
|
||
ActiveCalls int `json:"active_calls"`
|
||
MaxCalls int `json:"max_calls"`
|
||
IdleCPU float64 `json:"idle_cpu"`
|
||
LoadAvg float64 `json:"load_avg"`
|
||
CallFailureRate float64 `json:"call_failure_rate"`
|
||
Disabled bool `json:"disabled"`
|
||
DisabledReason string `json:"disabled_reason,omitempty"`
|
||
Score float64 `json:"score"`
|
||
LethalReason string `json:"lethal_reason,omitempty"`
|
||
IsStale bool `json:"is_stale"`
|
||
SecondsAgo float64 `json:"seconds_ago"`
|
||
}
|
||
|
||
// --- API: Users ---
|
||
|
||
// CreateUserRequest — запрос на создание пользователя.
|
||
type CreateUserRequest struct {
|
||
Username string `json:"username"`
|
||
Password string `json:"password"`
|
||
Role UserRole `json:"role"`
|
||
}
|
||
|
||
// UpdateUserRequest — запрос на обновление пользователя.
|
||
type UpdateUserRequest struct {
|
||
Username *string `json:"username,omitempty"`
|
||
Password *string `json:"password,omitempty"`
|
||
Role *UserRole `json:"role,omitempty"`
|
||
}
|
||
|
||
// --- API: Trunks ---
|
||
|
||
// CreateTrunkRequest — запрос на создание транка.
|
||
type CreateTrunkRequest struct {
|
||
Name string `json:"name"`
|
||
Type TrunkType `json:"type"`
|
||
NodeID string `json:"node_id,omitempty"`
|
||
Gateway string `json:"gateway"`
|
||
Codecs []string `json:"codecs,omitempty"`
|
||
Context string `json:"context,omitempty"`
|
||
Enabled bool `json:"enabled"`
|
||
Description string `json:"description,omitempty"`
|
||
}
|
||
|
||
// UpdateTrunkRequest — запрос на обновление транка.
|
||
type UpdateTrunkRequest struct {
|
||
Name *string `json:"name,omitempty"`
|
||
Type *TrunkType `json:"type,omitempty"`
|
||
NodeID *string `json:"node_id,omitempty"`
|
||
Gateway *string `json:"gateway,omitempty"`
|
||
Codecs []string `json:"codecs,omitempty"`
|
||
Context *string `json:"context,omitempty"`
|
||
Enabled *bool `json:"enabled,omitempty"`
|
||
Description *string `json:"description,omitempty"`
|
||
}
|
||
|
||
// --- Monitoring ---
|
||
|
||
// ZabbixNode — элемент LLD (Low-Level Discovery) для Zabbix.
|
||
type ZabbixNode struct {
|
||
NodeID string `json:"{#NODE_ID}"`
|
||
Status string `json:"{#STATUS}"`
|
||
ActiveCalls int `json:"{#ACTIVE_CALLS}"`
|
||
MaxCalls int `json:"{#MAX_CALLS}"`
|
||
IdleCPU float64 `json:"{#IDLE_CPU}"`
|
||
LoadAvg float64 `json:"{#LOAD_AVG}"`
|
||
CallFailureRate float64 `json:"{#CALL_FAILURE_RATE}"`
|
||
Score float64 `json:"{#SCORE}"`
|
||
Disabled bool `json:"{#DISABLED}"`
|
||
IsStale bool `json:"{#IS_STALE}"`
|
||
SecondsAgo float64 `json:"{#SECONDS_AGO}"`
|
||
}
|
||
|
||
// ZabbixResponse — ответ на запрос Zabbix LLD.
|
||
type ZabbixResponse struct {
|
||
Data []ZabbixNode `json:"data"`
|
||
}
|
||
|
||
// --- Balancer ---
|
||
|
||
// BalancerInfo — состояние балансировщика для дашборда.
|
||
type BalancerInfo struct {
|
||
EslConnected bool `json:"esl_connected"`
|
||
EslUptimeSec int64 `json:"esl_uptime_sec,omitempty"`
|
||
EslReconnects int64 `json:"esl_reconnects,omitempty"`
|
||
NatsConnected bool `json:"nats_connected"`
|
||
GatewaysTotal int `json:"gateways_total"`
|
||
GatewaysUp int `json:"gateways_up"`
|
||
GatewaysDown int `json:"gateways_down"`
|
||
FsActiveCalls int `json:"fs_active_calls"`
|
||
FsMaxSessions int `json:"fs_max_sessions"`
|
||
FsUptime string `json:"fs_uptime"`
|
||
RouteTotal int64 `json:"route_total"`
|
||
RouteFallbacks int64 `json:"route_fallbacks"`
|
||
UptimeSec int64 `json:"uptime_sec"`
|
||
BalanceMode string `json:"balance_mode"`
|
||
}
|
||
|
||
// --- WebSocket ---
|
||
|
||
// WsMetricsMessage — сообщение, отправляемое по WebSocket.
|
||
type WsMetricsMessage struct {
|
||
Type string `json:"type"` // "nodes_update", "balancer_update", "gateway_update", etc
|
||
Payload interface{} `json:"payload"`
|
||
}
|