2026-08-21 07:34:45 +03:00
# API Control API
Control API — единственная точка входа в систему для `validator-agent` ,
`prober` и оператора (администратора). Все данные передаются в формате
JSON, базовый префикс прикладных методов — `/api/v1` .
2026-10-01 11:35:24 +03:00
> **Аутентификация.** Доступ к API определяется двумя статическими
> bearer-токенами — см. [«Аутентификация»](#аутентификация). Токен задаётся
> переменной окружения; **если токен не задан, соответствующий уровень остаётся
> открытым** (control-api стартует с предупреждением в логе) — так сделано для
> обратной совместимости. Поэтому для эксплуатации за пределами доверенного
> сегмента сети токены нужно задать, а доступ дополнительно ограничить на уровне
> сети/файрвола (см. [SETUP.md](SETUP.md#сетевые-доступы)).
2026-08-21 07:34:45 +03:00
Базовый URL в примерах — `http://control-api.internal:8080` , замените на
адрес вашего стенда (см. `server.listen_addr` в конфиге control-api).
2026-08-23 20:39:22 +03:00
> Для работы из браузера вместо `curl` есть `admin-dashboard` — веб-панель,
> дающая графический доступ ко всему административному API ниже, см.
> [DASHBOARD.md](DASHBOARD.md).
2026-08-21 07:34:45 +03:00
## Содержание
2026-10-01 11:35:24 +03:00
- [Аутентификация ](#аутентификация )
2026-08-21 07:34:45 +03:00
- [Общие соглашения ](#общие-соглашения )
- [Методы для validator-agent ](#методы-для-validator-agent )
- [Методы для prober ](#методы-для-prober )
- [Служебные и административные методы ](#служебные-и-административные-методы )
2026-08-23 20:39:22 +03:00
- [Управление очередью и конфигурацией ](#управление-очередью-и-конфигурацией )
2026-10-01 10:28:53 +03:00
- [Автоматический цикл проверок ](#автоматический-цикл-проверок )
2026-09-23 09:52:01 +03:00
- [Реестр адресов и история проверок ](#реестр-адресов-и-история-проверок )
2026-08-21 07:34:45 +03:00
- [Модель состояний и связь методов с ней ](#модель-состояний-и-связь-методов-с-ней )
- [Сквозной пример работы (curl) ](#сквозной-пример-работы-curl )
2026-10-01 11:35:24 +03:00
## Аутентификация
Токен передаётся заголовком `Authorization: Bearer <токен>` . Токены статические, **без срока жизни** ; ротация — смена
переменной окружения и перезапуск. Сравнение выполняется в константное время.
| Уровень | Токен (переменная на control-api) | Какие методы |
|---|---|---|
| **admin** | `CONTROL_API_ADMIN_TOKEN` | все `/api/v1/admin/*` (очередь, реестр, автоцикл, `config/*` ) |
| **agent** | `CONTROL_API_AGENT_TOKEN` | запись результатов: `POST /agents/{id}/self-check` , `/events` , `/results` , `/complete` и `POST /probers/{site_id}/results` |
2026-10-02 03:24:20 +03:00
| **открыто** | — | `GET /healthz` ; `POST /agents/register` , `POST /agents/{id}/heartbeat` , `GET /agents/{id}/assignment` , `GET /agents/{id}/observed-ip` ; `POST /probers/register` , `POST /probers/{site_id}/heartbeat` , `GET /probers/{site_id}/assignments` |
2026-10-01 11:35:24 +03:00
- Токены разные: токен администратора **не** подходит для методов агентов, и наоборот.
2026-10-02 03:24:20 +03:00
- Валидатор и пробер могут без токена зарегистрироваться, слать heartbeat и забирать задание (настройку); валидатор также может спросить, с какого адреса его видит control-api (`observed-ip` ); отправка результатов без токена агентов — `401` .
2026-10-01 11:35:24 +03:00
- Имена переменных меняются в секции `auth` конфига control-api (`admin_token_env` , `agent_token_env` ); сами значения в YAML не хранятся.
- Ответ при отказе: `401 {"error": "unauthorized"}` с заголовком `WWW-Authenticate: Bearer` .
- Токен не задан (пустая переменная) — уровень открыт; в логе control-api при старте предупреждение. Токены нужно генерировать случайными: `openssl rand -hex 32` .
- Токены уходят открытым текстом, если TLS не терминируется перед control-api, — публикуйте API через reverse-proxy с TLS.
```bash
export ADMIN_TOKEN = ... # значение CONTROL_API_ADMIN_TOKEN
curl -s -H "Authorization: Bearer $ADMIN_TOKEN " http://<control-api>:8080/api/v1/admin/status
```
> Во всех примерах `curl` ниже заголовок `Authorization` для краткости опущен; если токен администратора задан, добавляйте его к методам `/api/v1/admin/*`.
2026-08-21 07:34:45 +03:00
## Общие соглашения
- Тело запроса и ответа — JSON (`Content-Type: application/json` ).
- Успешные ответы возвращают `200 OK` , либо `204 No Content` (когда
данных нет — например, у валидатора сейчас нет назначения).
- Ошибки возвращают `4xx` /`5xx` и тело вида:
```json
{"error": "текст ошибки"}
` ``
- Временные метки (` checked_at` в запросах) передаются в формате
RFC3339/RFC3339Nano, например ` 2026-08-21T09:15:00.123456789Z`. Если поле
не удалось распарсить, сервер молча подставит текущее время сервера — не
полагайтесь на это в продакшене, всегда передавайте валидную метку.
- ` validator_id` и ` site_id` в пути запроса должны совпадать со
2026-08-23 20:39:22 +03:00
значениями, известными control-api — заданными в ` control-api.yaml`
при первом запуске (пустая база) либо созданными позже через
` /api/v1/admin/config/*` (см.
[«Управление очередью и конфигурацией»](#управление-очередью-и-конфигурацией))
— иначе методы, требующие существующую сущность, вернут ` 404`.
2026-08-21 07:34:45 +03:00
## Методы для validator-agent
Эти методы вызывает бинарник ` validator-agent`, работающий на ВМ-валидаторе.
Оператору вручную дёргать их обычно не требуется — они приведены для
понимания протокола и для отладки через curl.
### ` POST /api/v1/agents/register`
Регистрация/переактивация валидатора. Вызывается один раз при старте
агента (и безопасно при каждом рестарте — идемпотентна).
Запрос:
` ``json
{
"validator_id": "validator_01",
"hostname": "vm-validator-01",
"agent_version": "1.0.0"
}
` ``
Ответ:
` ``json
{"ok": true, "poll_interval_seconds": 5}
` ``
` poll_interval_seconds` — рекомендованный интервал опроса, значение берётся
из ` orchestrator.poll_interval_seconds` конфига control-api.
> ` validator_id` должен быть заранее описан в конфиге control-api
> (` validators[].validator_id`) вместе с ` os_port_id` — сам агент порт ID
> не передаёт и не может его сменить через API.
### ` POST /api/v1/agents/{id}/heartbeat`
"Я жив". Обновляет ` last_heartbeat_at` валидатора. Если валидатор не
присылает heartbeat дольше ` orchestrator.heartbeat_timeout_seconds`, он
помечается ` unreachable`.
Запрос (тело необязательно, поля информационные):
` ``json
{"local_state": "idle"}
` ``
Ответ: ` {"ok": true}`. ` 404`, если ` validator_id` не зарегистрирован.
### ` GET /api/v1/agents/{id}/assignment`
Есть ли у валидатора сейчас работа. Опрашивается в каждом цикле.
- ` 204 No Content` — заданий нет.
- ` 200 OK` с телом:
` ``json
{
"ip_id": 42,
"ip_address": "203.0.113.10",
"phase": "awaiting_self_check",
"check_config": [
{"type": "https", "targets": ["https://hub.docker.com", "https://github.com", "https://packages.ubuntu.com"]},
{"type": "icmp", "targets": ["https://hub.docker.com", "https://github.com", "https://packages.ubuntu.com"]}
]
}
` ``
` phase` — ` awaiting_self_check` (нужно выполнить self-check) либо
` checking` (self-check уже пройден, можно/нужно выполнять проверки).
` check_config` — уже развёрнутая конфигурация проверок (тип + список
целей), агенту не нужно самому сопоставлять группы целей.
2026-10-02 03:24:20 +03:00
### ` GET /api/v1/agents/{id}/observed-ip`
С какого адреса control-api видит соединение валидатора. Используется
self-check способом ` control_api` (` self_check.methods` в
` validator-agent.yaml`) как альтернатива внешнему IP-echo сервису. Уровень
доступа — открыто: отдаётся только адрес самого вызывающего.
Ответ ` 200`:
` ``json
{"ip": "203.0.113.10", "source": "remote_addr"}
` ``
- Адрес берётся только из адреса TCP-соединения (` RemoteAddr`), приведённого
к каноничному виду (` ::ffff:1.2.3.4` → ` 1.2.3.4`). Заголовки
` X-Forwarded-For` / ` X-Real-IP` **не учитываются**: иначе валидатор мог бы
подделать адрес и пройти проверку. Метод рассчитан на прямое подключение
без обратного прокси.
- ` 404`, если ` validator_id` не зарегистрирован.
- Способ корректен, только если соединение выходит через внешнюю сеть
(SNAT Floating IP). Если control-api достижим из облака по внутренней
сети, он увидит приватный адрес валидатора. Если порт control-api
опубликован через Docker, проверьте, что ручка показывает внешний адрес
клиента, а не адрес шлюза Docker.
2026-08-21 07:34:45 +03:00
### ` POST /api/v1/agents/{id}/self-check`
Отчёт о результате self-check — подтверждение, что исходящий трафик
валидатора действительно идёт через только что назначенный FIP. Агент
2026-08-21 11:04:49 +03:00
определяет это **сам**, обращаясь к внешнему (снаружи облака) IP-echo
сервису (` self_check.ip_echo_urls` в ` validator-agent.yaml`, например
` api.ipify.org`) и сравнивая ответ с ` ip_address` из задания — control-api
2026-10-02 03:24:20 +03:00
в этом определении не участвует. Дополнительно можно включить способ
` control_api` (` self_check.methods`): агент спрашивает у control-api через
` GET /agents/{id}/observed-ip`, с какого адреса тот его видит. Способы
пробуются по приоритету, достаточно подтверждения любым из них; без
настройки работает только IP-echo. Важно, что ресурс должен быть именно
2026-08-21 11:04:49 +03:00
внешним: OpenStack применяет SNAT через Floating IP только к трафику,
уходящему через внешнюю сеть, поэтому обращение к чему-либо внутри
проекта (в том числе к самому control-api, если он в той же внутренней
сети) покажет приватный адрес валидатора независимо от того, правильно
ли привязан FIP.
2026-08-21 07:34:45 +03:00
Запрос:
` ``json
{
"ip_id": 42,
"detected_egress_ip": "203.0.113.10",
"success": true,
2026-10-02 03:24:20 +03:00
"detail": "matched (control_api)"
2026-08-21 07:34:45 +03:00
}
` ``
Ответ: ` {"ok": true}`. При ` success: false` control-api сам решает —
повторить попытку назначения FIP или пометить IP как ` failed` (после
исчерпания ` orchestrator.max_self_check_retries`).
### ` POST /api/v1/agents/{id}/events`
Произвольная запись в журнал аудита, привязанная (опционально) к IP.
Используется агентом для событий ` config_received`, ` self_check_result`
и т.п.
Запрос:
` ``json
{
"event_type": "config_received",
"ip_id": 42,
"payload": "{\"checks\":2}"
}
` ``
Ответ: ` {"ok": true}`.
### ` POST /api/v1/agents/{id}/results`
Отчёт о результатах исходящих (egress) проверок. Можно отправлять по
одной проверке сразу после выполнения (рекомендуется — так прогресс не
теряется при падении агента) либо пачкой.
Запрос:
` ``json
{
"results": [
{
"ip_id": 42,
"check_type": "https",
"target": "https://github.com",
"success": true,
"latency_ms": 87,
"detail": "ok",
"checked_at": "2026-08-21T09:15:00.123Z"
}
]
}
` ``
Ответ: ` {"ok": true}`. Повторная отправка того же ` (ip_id, check_type,
target)` в рамках текущей попытки — безопасна и просто перезапишет
результат (upsert по уникальному ключу).
### ` POST /api/v1/agents/{id}/complete`
Сигнал "все исходящие проверки для этого IP выполнены".
Запрос:
` ``json
{"ip_id": 42}
` ``
Ответ: ` {"ok": true}`.
## Методы для prober
Эти методы вызывает бинарник ` prober`, работающий на внешней площадке.
### ` POST /api/v1/probers/register`
Регистрация пробера. ` site_id` должен присутствовать в конфиге control-api
2026-08-26 20:47:54 +03:00
(` sites[].site_id`), иначе — ` 400`. Помимо привычной проверки, запоминает
` hostname` и переводит площадку в состояние ` idle` (если она была
` unregistered`/` unreachable`) — то же самое, что делает ` validator-agent`
при своей регистрации (см.
[«Управление площадками»](USAGE.md#управление-площадками-проберами) в
USAGE.md про состояния площадки).
2026-08-21 07:34:45 +03:00
Запрос:
` ``json
{"site_id": "site-1", "hostname": "probe-host-1"}
` ``
Ответ: ` {"ok": true, "poll_interval_seconds": 5}`.
2026-08-26 20:47:54 +03:00
### ` POST /api/v1/probers/{site_id}/heartbeat`
«Я жив». Обновляет ` last_heartbeat_at` площадки. Если площадка не
присылает heartbeat дольше ` orchestrator.heartbeat_timeout_seconds`
(тот же параметр, что и для валидаторов), она помечается ` unreachable`.
Полный аналог ` POST /api/v1/agents/{id}/heartbeat` для пробера.
Запрос: тело не требуется.
Ответ: ` {"ok": true}`. ` 404`, если ` site_id` не сконфигурирован.
2026-08-21 07:34:45 +03:00
### ` GET /api/v1/probers/{site_id}/assignments`
Список всех IP, которые сейчас находятся в состоянии ` checking` — то есть
всё, что нужно прозондировать на этом цикле опроса (валидаторов может
работать несколько параллельно, поэтому список, а не один IP).
Ответ:
` ``json
[
{"ip_id": 42, "ip_address": "203.0.113.10", "ports": [22, 80, 443, 8080], "icmp": true}
]
` ``
2026-08-26 19:45:48 +03:00
Пустой список ` []`, если сейчас нечего проверять. ` ports`/` icmp` — текущая
конфигурация типов проверок пробера, одна и та же для каждого IP в ответе;
управляется через
[` /api/v1/admin/config/inbound-checks`](#типы-проверок-пробера-apiv1adminconfiginbound-checks)
и меняется без рестарта control-api.
2026-08-21 07:34:45 +03:00
### ` POST /api/v1/probers/{site_id}/results`
Отчёт о результатах входящих (inbound) проверок с данной площадки.
Запрос:
` ``json
{
"results": [
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tcp-22", "success": true, "latency_ms": 12, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
2026-08-26 23:52:31 +03:00
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "ssh", "success": true, "latency_ms": 15, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
2026-08-21 07:34:45 +03:00
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tcp-80", "success": true, "latency_ms": 9, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tcp-443", "success": true, "latency_ms": 10, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
2026-08-26 23:52:31 +03:00
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tls-443", "success": true, "latency_ms": 34, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
2026-08-21 07:34:45 +03:00
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "tcp-8080","success": false,"latency_ms": 0, "checked_at": "2026-08-21T09:15:01Z", "complete": false},
{"ip_id": 42, "ip_address": "203.0.113.10", "check_type": "icmp", "success": true, "latency_ms": 5, "checked_at": "2026-08-21T09:15:01Z", "complete": true}
]
}
` ``
` complete: true` нужно проставить ровно на одном (обычно последнем)
результате в пачке — это сигнал "площадка N закончила зондирование этого
IP на данном проходе". До этого момента control-api не будет считать
данные с этой площадки завершёнными.
2026-08-26 23:52:31 +03:00
Порты ` 22` и ` 443` в ` ports` — особый случай: если они присутствуют в
списке, ` prober` дополнительно к базовой ` tcp-<port>`-проверке доступности
выполняет **настоящий** протокольный чек — ` ssh` (обмен SSH-банером на
порту 22) и ` tls-443` (полноценный TLS-хендшейк на порту 443)
соответственно. Это не отдельно настраиваемые типы проверок — они
включаются/выключаются автоматически вместе с самим портом, без
дополнительного флага. Сертификат в ` tls-443` не валидируется
(` InsecureSkipVerify`) — проверяется только сам факт TLS-хендшейка, не
доверие к серверу.
2026-08-21 07:34:45 +03:00
Ответ: ` {"ok": true}`.
## Служебные и административные методы
### ` GET /healthz`
Проверка живости процесса. Ответ: ` {"ok": true}`. Используется в systemd/
внешних системах мониторинга.
### ` GET /api/v1/admin/status`
Сводка по очереди — сколько IP в каком состоянии.
` ``json
{
"total_ips": 25,
"ips_by_state": {"queued": 10, "checking": 3, "done": 11, "failed": 1},
2026-10-01 19:31:11 +03:00
"results_by_overall": {"pass": 8, "partial": 3, "fail": 0, "cancelled": 0},
2026-08-21 07:34:45 +03:00
"total_validators": 4
}
` ``
2026-10-01 19:31:11 +03:00
` results_by_overall` — сколько адресов с каким итогом (всегда все четыре ключа). Счётчики считаются
запросами ` GROUP BY` на стороне БД, а не загрузкой всей очереди, поэтому метод быстрый и при тысячах адресов.
2026-08-21 07:34:45 +03:00
### ` GET /api/v1/admin/ips`
2026-10-01 19:31:11 +03:00
Список IP из очереди со всеми полями (см.
2026-08-21 07:34:45 +03:00
[USAGE.md](USAGE.md#значения-полей-ip) — расшифровка полей и статусов).
2026-10-01 19:31:11 +03:00
**Без параметров** — как раньше: весь список одним массивом (при тысячах адресов это мегабайты — для больших очередей
используйте постраничный режим). **С ` limit`** — постраничный режим: ответ — конверт
` ``json
{"items": [ ... ], "total": 6440, "limit": 50, "offset": 0}
` ``
| Параметр | Значение |
|---|---|
| ` limit` | размер страницы, ` 1`…` 1000` (иначе ` 400`); включает постраничный режим |
| ` offset` | смещение, ` >= 0` (без ` limit` — ` 400`) |
| ` state` | одно или несколько состояний через запятую (` queued`, ` assigning_fip`, ` awaiting_self_check`, ` checking`, ` aggregating`, ` done`, ` failed`, ` occupied`) |
| ` q` | подстрока адреса |
| ` result` | итог: ` pass`, ` partial`, ` fail`, ` cancelled` |
| ` order` | ` sequence` (по умолчанию, порядок очереди) или ` aggregated_at_desc` (последние завершённые) |
` total` — число записей после фильтров. Параметры фильтров без ` limit` возвращают отфильтрованный массив.
2026-08-21 07:34:45 +03:00
### ` GET /api/v1/admin/ips/{ip}`
Детали по одному адресу: сам объект IP, все проверки текущей попытки и
вся история событий по нему.
` ``json
{
"ip": { "ID": 42, "IPAddress": "203.0.113.10", "State": "done", "OverallResult": "pass", "...": "..." },
"checks": [ {"Source": "egress", "CheckType": "https", "Target": "https://github.com", "Success": true, "...": "..."} ],
"events": [ {"EventType": "fip_associated", "OccurredAt": "...", "...": "..."} ]
}
` ``
> Обратите внимание: вложенные объекты ` ip`/` checks`/` events` сериализуются
> без переопределения имён полей (используются имена Go-структур, например
> ` IPAddress`, ` State`, ` Success`) — в отличие от методов для
> agent/prober, где поля в ` snake_case`. Это осознанная асимметрия:
> административные методы — для человека/дашборда, а не для машинного
> протокола.
### ` GET /api/v1/admin/validators`
Список всех валидаторов с их текущим состоянием (` unregistered`, ` idle`,
` assigned`, ` checking`, ` unreachable`) и ` CurrentIPID`, если валидатор
сейчас занят.
2026-08-23 20:39:22 +03:00
## Управление очередью и конфигурацией
Методы этого раздела — единственный способ менять состав очереди
(` ip_addresses`), список валидаторов, площадок (` sites`) и целей
проверки (` targets`/` check_types`) **без остановки процесса**: изменения
применяются немедленно и переживают последующий рестарт control-api. Все
тела запросов/ответов — ` snake_case` (в отличие от ` GET
/admin/status|ips|validators` выше, которые отдают сырые поля Go-структур
в PascalCase — эти два стиля сосуществуют осознанно, см. примечание к
` GET /api/v1/admin/ips/{ip}`).
**Источник истины.** ` control-api.yaml` используется только как
одноразовый bootstrap для пустой базы данных: секции ` validators`,
` sites`, ` targets`, ` check_types` читаются из YAML один раз, при самом
первом старте на пустых таблицах. Как только в соответствующей таблице
появилась хотя бы одна строка (через bootstrap либо через методы ниже) —
YAML для этой секции больше не перечитывается ни при одном последующем
рестарте; правки нужно вносить через API. Список IP-адресов
(` ip_addresses` в YAML) — исключение, он остаётся отдельным, всегда
аддитивным путём постановки в очередь при каждом старте (см.
[SETUP.md](SETUP.md#развёртывание-control-api)); он не конфликтует с
` POST /api/v1/admin/ips` ниже.
### ` POST /api/v1/admin/ips`
Единая точка для двух задач: добавить новые адреса в очередь **и**
принудительно перепроверить уже завершённые — один и тот же вызов, разница
только в текущем состоянии каждого конкретного адреса. Список
обрабатывается в one transaction, в порядке следования адресов:
- адрес неизвестен control-api → добавляется в очередь как новый
(` queued`);
- адрес сейчас ` done`/` failed` → принудительно перезапускается: сбрасывается
результат, ` attempt_number` увеличивается, ` retry_count` обнуляется,
адрес снова становится ` queued`;
- адрес сейчас ` queued` (ещё не взят в работу) → только переупорядочивается
под порядок текущего списка, повторно не добавляется;
- адрес сейчас активно проверяется (` assigning_fip` / ` awaiting_self_check`
/ ` checking` / ` aggregating`) → не трогается вообще — нельзя запустить
вторую параллельную проверку одного и того же адреса.
Порядок обработки внутри одного вызова соответствует порядку адресов в
списке; повторная отправка того же списка позже даёт тот же относительный
порядок прогона.
Запрос:
` ``json
{"addresses": ["203.0.113.10", "203.0.113.11"]}
` ``
Ответ (` 200`):
` ``json
{
"added": ["203.0.113.11"],
"requeued": ["203.0.113.10"],
"reordered": [],
"skipped_in_progress": []
}
` ``
` 400`, если ` addresses` пуст.
### ` POST /api/v1/admin/ips/{ip}/cancel`
Принудительно останавливает проверку конкретного адреса, не дожидаясь
` checking_window_seconds` — работает из любого нетерминального состояния,
включая ` queued` (в этом случае это просто удаление ещё не начатой
проверки из очереди). Если Floating IP уже привязан — отвязывается
(best-effort, как и при обычном завершении проверки); владеющий валидатор
освобождается. Итог записывается как ` overall_result: "cancelled"`
(состояние ` failed`).
Ответ: ` {"ok": true}`. ` 404`, если адрес неизвестен. ` 409`, если адрес уже
в терминальном состоянии (` done`/` failed`/уже отменён) — отменять нечего.
2026-08-23 22:24:55 +03:00
### ` DELETE /api/v1/admin/ips/{ip}`, ` POST /api/v1/admin/ips/delete`, ` POST /api/v1/admin/ips/clear`
2026-09-23 09:52:01 +03:00
Удаляет строку ` ip_queue` — адрес пропадает из очереди/` GET
/api/v1/admin/ips*` — безвозвратно, без возможности восстановить именно
эту строку. Работает из любого состояния, включая активно проверяемое —
если Floating IP привязан, он отвязывается тем же best-effort способом,
что и при ` cancel`/обычном завершении, владеющий валидатор освобождается.
**Накопленная история адреса при этом не теряется**: ` checks`/` events`
остаются в реестре (` ip_registry`, см. раздел [«Реестр
адресов»](#реестр-адресов-и-история-проверок) ниже) и доступны через ` GET
/api/v1/admin/registry/{ip}` даже после удаления строки из очереди — в
отличие от ` ip_queue`, реестровая запись никогда не удаляется этими
методами.
2026-08-23 22:24:55 +03:00
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
| DELETE | ` /api/v1/admin/ips/{ip}` | — | ` 200 {"ok":true}` | ` 404` неизвестный адрес |
| POST | ` /api/v1/admin/ips/delete` | ` {"addresses":[...]}` | ` 200 {"deleted":[...],"not_found":[...]}` | ` 400` пустой список |
| POST | ` /api/v1/admin/ips/clear` | — | ` 200 {"deleted":[...]}` | — |
` POST .../delete` удаляет ровно перечисленный список (неизвестные адреса
идут в ` not_found`, не ошибка — тот же терпимый стиль, что у ` POST
/api/v1/admin/ips`). ` POST .../clear` удаляет **вообще всё**, что сейчас в
очереди, включая адреса в процессе проверки — самая опасная операция
2026-09-23 09:52:01 +03:00
этого API, используйте с осторожностью (и, опять же, ничья история при
этом физически не стирается — см. выше).
### ` POST /api/v1/admin/ips/scan`
2026-10-01 19:31:11 +03:00
Запускает **фоновое** сканирование проекта OpenStack: находит все свободные (не привязанные ни к одному порту) Floating IP
и ставит их в очередь — тот же add/requeue/reorder, что и ` POST /api/v1/admin/ips`. Не принимает тело запроса и **сразу отвечает**
` 202` со статусом задания; ход сканирования смотрите через ` GET /api/v1/admin/ips/scan`.
Почему в фоне: в проекте может быть тысячи Floating IP (на стенде — около 6,4 тыс.), Neutron отдаёт такой список минуты. Control-api читает
его **страницами** (по ` openstack.list_page_size`, по умолчанию 200, по ` marker`), повторяет страницу при обрыве соединения/5xx/429,
сначала обнаруживает **все** адреса и только потом ставит их в очередь кусками по 500 в порядке возрастания IP. Если чтение не удалось
(после повторов), в очередь не попадает ничего — очередь остаётся как была, а статус задания — ` error`.
| Параметр | Значение |
|---|---|
| ` dry_run=true` | только найти и посчитать свободные адреса; очередь не меняется (безопасная проверка, итог — в статусе) |
| ` wait=true` | дождаться окончания и ответить ` 200` прежним телом ` {scanned_free, added[], requeued[], reordered[], skipped_in_progress[]}` (для curl и скриптов; при ошибке ` 502`) |
Одновременно идёт одно сканирование: повторный запрос во время работы **присоединяется** к текущему и тоже отвечает ` 202` с его статусом.
### ` GET /api/v1/admin/ips/scan`
Статус и прогресс сканирования (admin-токен).
2026-09-23 09:52:01 +03:00
` ``json
{
2026-10-01 19:31:11 +03:00
"state": "listing",
"running": true,
"dry_run": false,
"pages": 12,
"discovered": 2400,
"free": 2399,
"added": 0,
"requeued": 0,
"reordered": 0,
"skipped_in_progress": 0,
"started_at": "2026-10-01T15:47:40.759Z",
"finished_at": null,
"error": ""
2026-09-23 09:52:01 +03:00
}
` ``
2026-10-01 19:31:11 +03:00
` state`: ` idle` (в этом процессе сканирования ещё не было), ` clearing` (очистка очереди — только в автоцикле), ` listing` (чтение страниц),
` enqueuing` (постановка в очередь), ` done`, ` error` (причина в ` error`), ` cancelled`. ` discovered` — сколько Floating IP прочитано
(свободных и занятых), ` free` — из них свободных, ` added`/` requeued`/` reordered`/` skipped_in_progress` — итог постановки в очередь
(как в ` POST /admin/ips`). Статус хранится в памяти процесса: после перезапуска control-api он снова ` idle`.
2026-09-23 09:52:01 +03:00
2026-10-01 19:31:11 +03:00
Помимо ручного вызова, сканирование можно включить по расписанию — ` orchestrator.fip_scan_interval_seconds` в ` control-api.yaml`
(0, по умолчанию, — только по запросу через эту ручку или кнопку «Сканировать Floating IP» в дашборде). Пока включён
[автоматический цикл](#автоматический-цикл-проверок), периодический скан не выполняется.
2026-10-01 10:28:53 +03:00
## Автоматический цикл проверок
Опциональный повторяющийся сценарий «очистить очередь → просканировать
Floating IP → дождаться завершения всех проверок → пауза → заново» (описание
для оператора — [USAGE.md](USAGE.md#автоматический-цикл-проверок)). По умолчанию
выключен. Состояние и параметры хранятся в базе; перезапуск control-api их
не сбрасывает.
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
| ` GET` | ` /api/v1/admin/auto-cycle` | — | ` 200`, статус | — |
| ` PUT` | ` /api/v1/admin/auto-cycle` | ` {"interval_seconds": N, "max_run_seconds": M}` — любое поле можно опустить | ` 200`, статус | ` 400` — ` interval_seconds < 60` или ` max_run_seconds < 0` (ничего не применяется) |
| ` POST` | ` /api/v1/admin/auto-cycle/start` | — | ` 200`, статус | — |
| ` POST` | ` /api/v1/admin/auto-cycle/stop` | — | ` 200`, статус | — |
Каждый метод отвечает полным объектом статуса:
` ``json
{
"enabled": true,
"interval_seconds": 3600,
"max_run_seconds": 0,
"phase": "waiting",
"run_started_at": null,
"next_run_at": "2026-10-01T08:00:12.345Z",
"last_run_started_at": "2026-10-01T07:00:01.100Z",
"last_run_finished_at": "2026-10-01T07:00:12.345Z",
"last_outcome": "completed",
"last_error": "",
"last_scanned_free": 12,
"runs_total": 5
}
` ``
- ` phase` — ` idle` (выключен или ещё не стартовал), ` running` (идут
проверки), ` waiting` (пауза до ` next_run_at`).
- ` last_outcome` — ` completed`, ` no_free_ips`, ` timeout`, ` error` или
` stopped`; пустая строка, пока не завершился ни один цикл. Для ` error`
причина — в ` last_error`.
- ` last_scanned_free` — сколько свободных Floating IP нашёл скан последнего
цикла; ` runs_total` — сколько циклов завершилось исходом ` completed`.
- ` max_run_seconds = 0` — без ограничения времени ожидания проверок.
- Времена — RFC 3339 (UTC), ` null`, пока не наступили.
` POST .../start` идемпотентен: у уже включённого цикла ничего не меняется
(идущий цикл не перезапускается). Включённый цикл начинает первый прогон на
ближайшем шаге оркестратора (порядка ` orchestrator.poll_interval_seconds`).
` POST .../stop` выключает цикл и переводит его в ` idle`; проверки, которые
уже идут, не прерываются. Новое значение ` interval_seconds` начинает действовать
со следующей паузы.
События цикла (` auto_cycle_started`, ` auto_cycle_completed`,
` auto_cycle_timeout`, ` auto_cycle_error`, ` auto_cycle_stopped`) попадают в
общий журнал событий; шаги цикла записывают обычные ` queue_cleared` и
` fip_scan`.
` ``bash
curl -s -X PUT http://<control-api>:8080/api/v1/admin/auto-cycle \
-d '{"interval_seconds": 7200, "max_run_seconds": 1800}'
curl -s -X POST http://<control-api>:8080/api/v1/admin/auto-cycle/start
curl -s http://<control-api>:8080/api/v1/admin/auto-cycle
curl -s -X POST http://<control-api>:8080/api/v1/admin/auto-cycle/stop
` ``
2026-09-23 09:52:01 +03:00
## Реестр адресов и история проверок
В отличие от ` ip_queue` (текущая рабочая очередь, см. выше), реестр —
` ip_registry` — это накопительная запись **обо всех адресах, когда-либо
поставленных на проверку**, вне зависимости от того, стоят ли они сейчас в
очереди. Запись в реестре переживает удаление адреса из ` ip_queue` (` DELETE
/api/v1/admin/ips/{ip}` и т.п.) и повторное добавление того же адреса
позже — обе истории (до и после) остаются доступны и не перекрывают друг
друга (каждой постановке на проверку соответствует свой ` cycle_id`,
уникальный в пределах адреса на всё время).
### ` GET /api/v1/admin/registry`
2026-10-01 19:31:11 +03:00
Список адресов реестра с краткой сводкой по каждому. Без параметров — все адреса одним массивом; **с ` limit`** (` 1`…` 1000`) —
постраничный конверт ` {"items": [...], "total": N, "limit": L, "offset": O}`, параметры ` offset`, ` q` (подстрока адреса) и
` last_result` (` pass`/` partial`/` fail`/` cancelled`). Страница и фильтры применяются в SQL до расчёта сводки, поэтому
реестр из тысяч адресов отдаётся за доли секунды.
2026-09-23 09:52:01 +03:00
` ``json
[
{
"ip_address": "203.0.113.10",
"first_seen_at": "2026-01-10T12:00:00Z",
"last_seen_at": "2026-02-01T09:00:00Z",
"total_cycles": 4,
"last_result": "pass",
"last_checked_at": "2026-02-01T09:05:00Z",
"in_queue": true,
"current_state": "done"
}
]
` ``
` in_queue`/` current_state` отражают, есть ли у адреса сейчас живая строка в
` ip_queue`, а не только в реестре.
### ` GET /api/v1/admin/registry/{ip}`
Реестровая запись по одному адресу плюс вся сохранённая история проверок
по нему, по всем циклам (не только текущему — в отличие от ` GET
/api/v1/admin/ips/{ip}`, который отдаёт проверки только текущей попытки).
Порядок — от новых циклов к старым.
` ``json
{
"registry": { "ip_address": "203.0.113.10", "total_cycles": 4, "...": "..." },
"checks": [
{"CycleID": 4, "Source": "egress", "CheckType": "https", "Success": true, "...": "..."},
{"CycleID": 3, "Source": "egress", "CheckType": "https", "Success": false, "...": "..."}
]
}
` ``
` 404`, если адрес никогда не ставился на проверку.
**Глубина хранения.** Сколько последних циклов на адрес хранится в
` checks` (и синхронно — в ` events`), управляется полем
` history_retention_cycles` в ` GET`/` PUT /api/v1/admin/config/orchestrator`
(0, по умолчанию, — без ограничения). Сама реестровая запись (` ip_address`,
` first_seen_at`, счётчик циклов) не удаляется никогда, независимо от этой
настройки — она лишь ограничивает глубину детальной истории проверок.
2026-08-23 22:24:55 +03:00
` ``bash
curl -s -X DELETE "$BASE/api/v1/admin/ips/203.0.113.10"
curl -s -X POST "$BASE/api/v1/admin/ips/delete" -d '{"addresses":["203.0.113.10","203.0.113.11"]}'
curl -s -X POST "$BASE/api/v1/admin/ips/clear"
` ``
2026-08-23 20:39:22 +03:00
### Валидаторы: ` /api/v1/admin/config/validators`
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
| GET | ` /api/v1/admin/config/validators` | — | ` [{"validator_id","os_port_id","state"}]` | |
| POST | ` /api/v1/admin/config/validators` | ` {"validator_id","os_port_id"}` | ` 201` | ` 409`, если ` validator_id` уже существует |
| PUT | ` /api/v1/admin/config/validators/{id}` | ` {"os_port_id"}` | ` 200` | ` 404` |
| DELETE | ` /api/v1/admin/config/validators/{id}` | — | ` 200` | ` 404`; ` 409`, если валидатор сейчас владеет IP |
### Площадки: ` /api/v1/admin/config/sites`
2026-08-26 20:47:54 +03:00
Число слотов не ограничено — ` index` может быть любым целым ` >= 1`,
столько площадок, сколько нужно оператору. Пустой список слотов — штатный
сценарий, отключающий inbound-проверки целиком (см.
2026-08-23 20:39:22 +03:00
[USAGE.md](USAGE.md#управление-площадками-проберами)).
2026-08-26 20:47:54 +03:00
Помимо ` index`/` site_id`, объект площадки несёт состояние подключения
пробера — ` hostname`/` state`/` last_heartbeat_at`, тот же смысл, что у
аналогичных полей валидатора (` unregistered`/` idle`/` unreachable`,
обновляются через ` POST /api/v1/probers/register` и ` POST
/api/v1/probers/{site_id}/heartbeat`, см. выше). ` PUT` на слот всегда
сбрасывает эти три поля к значениям «ещё не подключался» — новый (или
даже тот же) ` site_id` трактуется как новая идентичность пробера.
2026-08-23 20:39:22 +03:00
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
2026-08-26 20:47:54 +03:00
| GET | ` /api/v1/admin/config/sites` | — | ` [{"index","site_id","hostname","state","last_heartbeat_at"}]` | |
| PUT | ` /api/v1/admin/config/sites/{index}` | ` {"site_id"}` | ` 200` | ` 400`, если ` index < 1`; ` 409`, если ` site_id` уже занят другим слотом |
2026-08-23 20:39:22 +03:00
| DELETE | ` /api/v1/admin/config/sites/{index}` | — | ` 200` | ` 404` |
### Группы целей: ` /api/v1/admin/config/targets`
Группа целей — именованный список URL/адресов (например ` stub-targets: [
"https://hub.docker.com", ...]`), на который затем ссылаются типы
проверок.
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
| GET | ` /api/v1/admin/config/targets` | — | ` [{"name","targets"}]` | |
| PUT | ` /api/v1/admin/config/targets/{group}` | ` {"targets":[...]}` | ` 200` | ` 400`, если список пуст |
| DELETE | ` /api/v1/admin/config/targets/{group}` | — | ` 200` | ` 404`; ` 409`, если группа используется каким-то ` check_type` |
### Типы проверок: ` /api/v1/admin/config/check-types`
Тип проверки (` https`, ` icmp`, ` ssh`, ...) ссылается на одну или несколько
групп целей по имени; именно развёрнутый список отсюда validator-agent
получает в ` check_config` при ` GET /api/v1/agents/{id}/assignment`.
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
| GET | ` /api/v1/admin/config/check-types` | — | ` [{"name","enabled","targets"}]` (` targets` — имена групп) | |
| PUT | ` /api/v1/admin/config/check-types/{name}` | ` {"enabled","targets":["group",...]}` | ` 200` | ` 400`, если названа несуществующая группа |
| DELETE | ` /api/v1/admin/config/check-types/{name}` | — | ` 200` | ` 404` |
2026-08-24 10:29:08 +03:00
### Настройки оркестратора: ` /api/v1/admin/config/orchestrator`
2026-09-23 09:52:01 +03:00
Два параметра:
- ` fip_settle_seconds` — пауза между привязкой Floating IP к валидатору и
моментом, когда self-check по этому адресу становится доступен агенту
(` GET /api/v1/agents/{id}/assignment` до истечения паузы отдаёт ` 204`,
как если бы валидатору просто нечего было делать — никаких изменений в
протоколе агента). Нужна, чтобы дать data plane OpenStack время реально
начать пропускать трафик через только что привязанный адрес, прежде чем
запускать по нему проверки. ` 0` — без паузы (поведение по умолчанию, как
до появления этого параметра).
- ` history_retention_cycles` — сколько последних циклов проверки хранить
на адрес в реестре (` GET /api/v1/admin/registry/{ip}`, см.
[«Реестр адресов»](#реестр-адресов-и-история-проверок)). ` 0` — без
ограничения (поведение по умолчанию).
2026-08-24 10:29:08 +03:00
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
2026-09-23 09:52:01 +03:00
| GET | ` /api/v1/admin/config/orchestrator` | — | ` {"fip_settle_seconds":N,"history_retention_cycles":M}` | |
| PUT | ` /api/v1/admin/config/orchestrator` | ` {"fip_settle_seconds":N,"history_retention_cycles":M}` | ` 200` | ` 400`, если ` N < 0` или ` M < 0`, или если ` fip_settle_seconds + self_check_timeout_seconds >= lease_ttl_seconds` (пауза не должна съедать весь лизинг адреса — иначе self-check не успеет пройти до истечения ` lease_ttl_seconds`, и адрес будет вечно возвращаться в очередь) |
2026-08-24 10:29:08 +03:00
Как и остальные разделы этой группы, YAML-поле ` orchestrator.
fip_settle_seconds` в ` control-api.yaml` — только одноразовый bootstrap
для пустой БД; дальше источник истины — сама база, менять значение нужно
через ` PUT` выше (или страницу ` /settings` в дашборде).
2026-09-23 09:52:01 +03:00
` history_retention_cycles` не имеет YAML-эквивалента вообще — управляется
только через ` PUT` выше/дашборд, значение по умолчанию ` 0` всегда
применяется на пустой БД.
2026-08-24 10:29:08 +03:00
2026-08-26 19:45:48 +03:00
### Типы проверок пробера: ` /api/v1/admin/config/inbound-checks`
Единый глобальный набор TCP-портов и флага ICMP, которые ` prober`
проверяет на каждой настроенной площадке для каждого адреса в состоянии
` checking` — то же самое ` ports`/` icmp`, что отдаётся в ответе ` GET
/api/v1/probers/{site_id}/assignments` (см.
[«Методы для prober»](#методы-для-prober) выше). Один набор общий для всех
площадок; список [«площадок»](#площадки-apiv1adminconfigsites) отдельно
решает, *сколько* точек его применяют, а не что именно они проверяют.
Пустой список портов и ` icmp: false` одновременно — допустимая
конфигурация: временно отключает inbound-проверки, не трогая список
` sites`.
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
| GET | ` /api/v1/admin/config/inbound-checks` | — | ` {"ports":[...],"icmp":bool}` | |
| PUT | ` /api/v1/admin/config/inbound-checks` | ` {"ports":[...],"icmp":bool}` | ` 200` | ` 400`, если какой-то порт вне диапазона ` 1..65535` или порты повторяются |
Изменение вступает в силу немедленно — как для следующего ответа ` GET
/api/v1/probers/{site_id}/assignments`, так и для агрегации уже идущих
проверок (см. [«Управление площадками»](USAGE.md#управление-площадками-проберами)
в USAGE.md про тот же принцип для ` sites`/` targets`/` check_types`). Как и
остальные разделы этой группы, YAML-поле ` orchestrator.inbound_checks` в
` control-api.yaml` — только одноразовый bootstrap для пустой БД; дальше
источник истины — сама база, менять значение нужно через ` PUT` выше (или
страницу ` /settings` в дашборде).
2026-08-23 20:39:22 +03:00
### Пример: конфигурация целиком через API, без единой строки в YAML
` ``bash
BASE=http://127.0.0.1:8080
curl -s -X POST "$BASE/api/v1/admin/config/validators" \
-d '{"validator_id":"validator_01","os_port_id":"port-abc123"}'
curl -s -X PUT "$BASE/api/v1/admin/config/targets/web" \
-d '{"targets":["https://hub.docker.com","https://github.com"]}'
curl -s -X PUT "$BASE/api/v1/admin/config/check-types/https" \
-d '{"enabled":true,"targets":["web"]}'
curl -s -X PUT "$BASE/api/v1/admin/config/sites/1" -d '{"site_id":"site-1"}'
# Поставить адрес в очередь и, отдельным вызовом позже, принудительно
# перепроверить его ещё раз — тот же метод, разница только в состоянии:
curl -s -X POST "$BASE/api/v1/admin/ips" -d '{"addresses":["203.0.113.10"]}'
curl -s -X POST "$BASE/api/v1/admin/ips" -d '{"addresses":["203.0.113.10"]}' # forced recheck
# Остановить проверку, не дожидаясь checking_window_seconds:
curl -s -X POST "$BASE/api/v1/admin/ips/203.0.113.10/cancel"
` ``
2026-08-21 07:34:45 +03:00
## Модель состояний и связь методов с ней
` ``
queued ──(control-api сам, без вызова API)──▶ assigning_fip
│
OpenStack FIP associate успешен
▼
awaiting_self_check
│
POST .../self-check {success:true}
▼
checking
2026-08-21 11:25:52 +03:00
│ POST .../results (agent) │ POST .../results (prober, по числу настроенных площадок)
2026-08-21 07:34:45 +03:00
▼ ▼
2026-08-21 11:25:52 +03:00
egress_complete=true siteN_complete=true (только для N, перечисленных в sites конфига)
2026-08-21 07:34:45 +03:00
│
2026-08-21 11:25:52 +03:00
egress + все НАСТРОЕННЫЕ площадки complete=true ИЛИ истекло checking_window_seconds
2026-08-21 07:34:45 +03:00
▼
aggregating
│
done (pass/partial/fail)
или failed
` ``
Переходы ` queued → assigning_fip → awaiting_self_check` и финальная
агрегация выполняются control-api самостоятельно по таймеру (см.
` orchestrator.poll_interval_seconds`), явного HTTP-метода для их запуска
нет — это фоновый цикл (` Tick`), а не запрос/ответ.
2026-09-13 23:54:35 +03:00
Из ` assigning_fip` есть и второй, терминальный исход: если на момент
попытки ассоциации Floating IP уже привязан к чужому порту (облако живое —
список адресов мог разойтись с реальностью с момента постановки в
очередь, либо адрес был ошибочно передан занятым), control-api переводит
адрес в состояние ` occupied` вместо продолжения в ` awaiting_self_check` —
цикл проверки для этой попытки не запускается вовсе. Это отдельное
терминальное состояние, а не ` failed`: ` failed` означает «проверка
стартовала и не прошла», ` occupied` — «проверка не стартовала, потому что
адрес занят кем-то другим». В аудит-логе адреса (` events`) фиксируется
строка ` fip_occupied`. ` overall_result` для этого состояния остаётся
пустым. Как и ` done`/` failed`, ` occupied` сбрасывается обратно в ` queued`
повторной постановкой через ` POST /api/v1/admin/ips` — этим способом
оператор возвращает адрес в работу, убедившись, что конфликт в облаке
разрешился.
2026-08-24 10:29:08 +03:00
Вход в ` awaiting_self_check` не означает мгновенную видимость агенту: если
настроена пауза (` fip_settle_seconds`, см.
[«Настройки оркестратора»](#настройки-оркестратора-apiv1adminconfigorchestrator)
выше), ` GET /api/v1/agents/{id}/assignment` продолжает отдавать ` 204` до
истечения паузы, и только потом начинает отдавать assignment — состояние
в БД при этом уже ` awaiting_self_check`.
2026-08-21 11:25:52 +03:00
Площадки (` siteN_complete`) — опциональны: сколько их учитывается,
2026-08-26 20:47:54 +03:00
целиком определяется текущим списком ` sites` (без ограничения по числу
записей, управляется через ` /api/v1/admin/config/sites` — см.
2026-08-23 20:39:22 +03:00
[выше](#управление-очередью-и-конфигурацией)). Пустой список — агрегация
ждёт только ` egress_complete`, ни одна площадка не требуется. Подробнее —
[USAGE.md](USAGE.md#управление-площадками-проберами).
2026-08-23 22:24:55 +03:00
Три дополнительных перехода, все инициируются оператором через
` /api/v1/admin/ips*`, а не самим оркестратором:
2026-08-23 20:39:22 +03:00
- **любое нетерминальное состояние → ` failed` (` overall_result:
"cancelled"`)** — ` POST /api/v1/admin/ips/{ip}/cancel`;
2026-09-13 23:54:35 +03:00
- **` done`/` failed`/` occupied` → ` queued` (новая попытка)** — ` POST
/api/v1/admin/ips` с уже завершённым (или занятым) адресом в списке;
2026-09-23 09:52:01 +03:00
- **любое состояние → адрес физически исчезает из очереди** — ` DELETE
/api/v1/admin/ips/{ip}`, ` POST /api/v1/admin/ips/delete`, ` POST
/api/v1/admin/ips/clear` (см.
2026-08-23 22:24:55 +03:00
[выше](#delete-apiv1adminipsip-post-apiv1adminipsdelete-post-apiv1adminipsclear)).
Не путать с cancel — cancel сохраняет запись как историю (` failed`/
2026-09-23 09:52:01 +03:00
` cancelled`) прямо в ` ip_queue`; delete убирает саму строку ` ip_queue`
безвозвратно, но накопленная история проверок остаётся в реестре (` GET
/api/v1/admin/registry/{ip}`) — см.
[«Реестр адресов»](#реестр-адресов-и-история-проверок).
2026-08-21 11:25:52 +03:00
2026-08-21 07:34:45 +03:00
## Сквозной пример работы (curl)
Ниже — минимальный ручной прогон одного IP через API, как если бы вы
писали собственного клиента вместо ` validator-agent`/` prober`. Полезно
для отладки и для понимания протокола.
` ``bash
BASE=http://127.0.0.1:8080
# 1. Регистрация валидатора (validator_01 уже должен быть в конфиге control-api)
curl -s -X POST "$BASE/api/v1/agents/register" \
-d '{"validator_id":"validator_01","hostname":"debug-host","agent_version":"manual"}'
# 2. Подождать, пока control-api (фоновым тиком) назначит IP и привяжет FIP —
# проверяем через admin/status или admin/ips, либо просто опрашиваем assignment
curl -s "$BASE/api/v1/agents/validator_01/assignment"
# => {"ip_id":1,"ip_address":"203.0.113.10","phase":"awaiting_self_check","check_config":[...]}
2026-08-21 11:04:49 +03:00
# 3. Self-check: спросить у ВНЕШНЕГО (вне облака) IP-echo сервиса, каким
# адресом мы наружу выглядим — это делает сам агент, control-api тут
# ни при чём (см. self_check.ip_echo_urls в validator-agent.yaml)
curl -s "https://api.ipify.org"
# => 203.0.113.10 (в реальном стенде это и есть проверка через FIP)
2026-08-21 07:34:45 +03:00
curl -s -X POST "$BASE/api/v1/agents/validator_01/self-check" \
-d '{"ip_id":1,"detected_egress_ip":"203.0.113.10","success":true,"detail":"matched"}'
# 4. Отправить результаты egress-проверок (по одному check_config пункту)
curl -s -X POST "$BASE/api/v1/agents/validator_01/results" \
-d '{"results":[{"ip_id":1,"check_type":"https","target":"https://github.com","success":true,"latency_ms":80,"checked_at":"2026-08-21T09:00:00Z"}]}'
# 5. Сообщить, что все egress-проверки выполнены
curl -s -X POST "$BASE/api/v1/agents/validator_01/complete" -d '{"ip_id":1}'
# 6. Со стороны пробера: узнать, что сейчас проверяется, и отправить результат
curl -s -X POST "$BASE/api/v1/probers/register" -d '{"site_id":"site-1"}'
curl -s "$BASE/api/v1/probers/site-1/assignments"
curl -s -X POST "$BASE/api/v1/probers/site-1/results" \
-d '{"results":[{"ip_id":1,"ip_address":"203.0.113.10","check_type":"icmp","success":true,"latency_ms":5,"checked_at":"2026-08-21T09:00:01Z","complete":true}]}'
# 7. Проверить итоговый результат (после того как control-api агрегирует)
curl -s "$BASE/api/v1/admin/ips/203.0.113.10" | python3 -m json.tool
` ``
Для полностью автоматизированного локального прогона (без ручных curl)
см. ` scripts/run-local-e2e.sh` и [docs/LOCAL_E2E.md ](LOCAL_E2E.md ).