diff --git a/README.md b/README.md index 431dc84..f0e3914 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ curl -X POST http://localhost:8080/api/auth/login \ | [docs/architecture.md](docs/architecture.md) | Архитектура, диаграмма потоков данных, стек, структура проекта | | [docs/api.md](docs/api.md) | Полная справка REST API + WebSocket: auth, route, nodes, trunks, users, monitoring, примеры | | [docs/scoring.md](docs/scoring.md) | Scoring engine: lethal-условия, weighted score, пример расчёта, кэширование | +| [docs/routing.md](docs/routing.md) | Маршрутизация: режимы балансировки (best / weighted_random), routing rules (node / pool / auto) | | [docs/agent.md](docs/agent.md) | pulse-lets-go-agent: collectors, конфигурация, деплой, troubleshooting | | [docs/deployment.md](docs/deployment.md) | Production деплой (8 шагов), systemd unit, security, rollback | | [docs/freeswitch.md](docs/freeswitch.md) | Интеграция с FreeSWITCH: route.lua, dialplan, ESL, предварительные проверки | diff --git a/docs/api.md b/docs/api.md index 9cf2453..f7d5b3c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -16,6 +16,7 @@ Roles: `admin` (full access), `viewer` (read-only nodes/metrics). | Method | Path | Auth | Description | |--------|------|------|-------------| | `GET` | `/api/route` | – | Route a call. Query: `caller_id`, `dest`, `ingress_trunk` | +| `PUT` | `/api/route/mode` | admin | Set balancing mode: `{"mode":"best\|weighted_random"}` — см. [Routing](routing.md#balancing-modes) | | `GET` | `/api/health` | – | Stats: total nodes, healthy, requests, uptime | | `GET` | `/api/health/live` | – | Liveness probe (always 200) | | `GET` | `/api/health/ready` | – | Readiness probe (NATS + metrics) | @@ -23,9 +24,11 @@ Roles: `admin` (full access), `viewer` (read-only nodes/metrics). ### Route Response (success) ```json -{ "node_id": "pbx-03", "score": 88, "sip_gateway": "sip:pbx03.lan:5060" } +{ "node_id": "pbx-03", "score": 88, "sip_gateway": "sip:pbx03.lan:5060", "matched_rule": "none" } ``` +`matched_rule` — имя сработавшего routing rule, либо `"none"` если применился глобальный scoring. + ### Route Response (fallback — all nodes unhealthy) ```json diff --git a/docs/routing.md b/docs/routing.md new file mode 100644 index 0000000..2b50a8a --- /dev/null +++ b/docs/routing.md @@ -0,0 +1,104 @@ +# Routing + +Маршрутизация звонков состоит из двух уровней: + +1. **Routing Rules** — priority-based правила с regex-матчингом (first-match-wins) +2. **Balancing Mode** — стратегия выбора ноды внутри правила или глобально + +## Routing Rules + +Правила хранятся в `data/routes.json` и проверяются в порядке `Priority` (меньше = раньше). Первое совпавшее правило определяет маршрутизацию. Если правило не сработало (нода мертва / пул пуст) — переход к следующему. Если ни одно правило не совпало — глобальный `PickNode()`. + +### Match Conditions (AND-логика) + +Все непустые поля должны совпасть одновременно. Пустые поля — не проверяются. + +| Поле | Тип | Описание | +|------|-----|----------| +| `match_caller_id` | regex | Caller ID звонящего | +| `match_destination` | regex | Номер назначения | +| `match_ingress_trunk` | regex | ID входящего транка | +| `match_days` | string | Дни недели: `"1-5"` (пн-пт), `"1,3,5"` (пн,ср,пт), `"6"` (сб). Воскресенье = 0 или 7 | +| `match_time_start` | `"HH:MM"` | Начало временного диапазона | +| `match_time_end` | `"HH:MM"` | Конец временного диапазона | + +### Action Types + +| Тип | Поле | Описание | +|-----|------|----------| +| `node` | `action_node_id` | Маршрутизация на конкретную ноду. Если нода dead/stale/lethal — fallthrough к следующему правилу | +| `pool` | `action_pool_ids` | Выбор лучшей ноды из заданного списка (по текущему режиму балансировки). Пустой/нездоровый пул — fallthrough | +| `auto` | — | Fallthrough к глобальному `PickNode()` с учётом режима балансировки | + +### Пример правила + +```json +{ + "id": "rule-001", + "name": "VIP caller to dedicated pool", + "enabled": true, + "priority": 10, + "match_caller_id": "^7495", + "match_destination": "", + "match_ingress_trunk": "", + "match_days": "1-5", + "match_time_start": "09:00", + "match_time_end": "18:00", + "action_type": "pool", + "action_pool_ids": ["pbx-01", "pbx-02"], + "description": "VIP номера 7495 в бизнес-часы на выделенные ноды" +} +``` + +## Balancing Modes + +Режим балансировки определяет, **как** выбирается нода из множества healthy-нод (после отсева lethal). Применяется как в глобальном `PickNode()`, так и внутри `pool`-правил. + +### best + +Выбирает ноду с **максимальным score** из кэша. O(1) — `bestScore` предрассчитывается при каждой метрике. + +- Всегда одна и та же нода при одинаковых метриках — максимально нагружает лучшую ноду +- Хорошо для тестирования и сценариев где нужна предсказуемость + +### weighted_random (по умолчанию) + +Выбирает ноду **пропорционально score**: чем выше score, тем выше вероятность быть выбранной. O(n) — сканирует все ноды. + +Алгоритм: +1. Отсеивает disabled, stale, lethal (score < 0) +2. Суммирует score всех кандидатов (`totalScore`) +3. `pick = rand(0, totalScore)`, идёт по кандидатам, вычитая score +4. Если `totalScore <= 0` — равновероятный выбор + +- Распределяет нагрузку пропорционально готовности +- Предсказуемость ниже, но утилизация выше — менее загруженные ноды получают пропорционально меньше звонков + +### Переключение режима + +**Через API:** +``` +PUT /api/route/mode +Authorization: Bearer +{"mode": "best"} +``` + +**Через конфиг:** +```json +{ "balance_mode": "weighted_random" } +``` + +Допустимые значения: `"best"`, `"weighted_random"`. API-эндпоинт сразу применяет режим и сохраняет в `config.json`. + +## Ответ /api/route + +В ответе маршрутизации появилось поле `matched_rule` — имя сработавшего правила (или `"none"` если правило не применилось): + +```json +{ + "node_id": "pbx-03", + "score": 88, + "sip_gateway": "sip:pbx03.lan:5060", + "matched_rule": "VIP to dedicated" +} +``` diff --git a/docs/scoring.md b/docs/scoring.md index 3e9a66d..a2b00e3 100644 --- a/docs/scoring.md +++ b/docs/scoring.md @@ -50,6 +50,8 @@ score = 83.2×0.40 + 57.5×0.30 + 35.0×0.20 + 99.5×0.10 = 70.1 `best_node_id`, `best_score`, and `fallback_active` are recalculated on every metric update. `/api/route` reads them under RLock — O(1) constant time. +Выбор ноды из кэша зависит от [режима балансировки](routing.md#balancing-modes). + ## Configuration ```json