2026-08-21 07:34:45 +03:00
|
|
|
|
# API Control API
|
|
|
|
|
|
|
|
|
|
|
|
Control API — единственная точка входа в систему для `validator-agent`,
|
|
|
|
|
|
`prober` и оператора (администратора). Все данные передаются в формате
|
|
|
|
|
|
JSON, базовый префикс прикладных методов — `/api/v1`.
|
|
|
|
|
|
|
|
|
|
|
|
> **Важно.** На данный момент API не защищён аутентификацией/авторизацией
|
|
|
|
|
|
> — эндпоинты доступны любому, кто может достучаться до порта control-api
|
2026-08-23 20:39:22 +03:00
|
|
|
|
> по сети. Это касается и методов из раздела
|
|
|
|
|
|
> [«Управление очередью и конфигурацией»](#управление-очередью-и-конфигурацией)
|
|
|
|
|
|
> ниже — они меняют, что и как проверяется, без подтверждения личности
|
|
|
|
|
|
> вызывающего. Для эксплуатации за пределами доверенного сегмента сети
|
2026-08-21 07:34:45 +03:00
|
|
|
|
> обязательно ограничьте доступ на уровне сети/файрвола (см.
|
|
|
|
|
|
> [SETUP.md](SETUP.md#сетевые-доступы)). Добавление bearer-токена — известное
|
|
|
|
|
|
> направление доработки, в текущей версии не реализовано.
|
|
|
|
|
|
|
|
|
|
|
|
Базовый 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
|
|
|
|
## Содержание
|
|
|
|
|
|
|
|
|
|
|
|
- [Общие соглашения](#общие-соглашения)
|
|
|
|
|
|
- [Методы для validator-agent](#методы-для-validator-agent)
|
|
|
|
|
|
- [Методы для prober](#методы-для-prober)
|
|
|
|
|
|
- [Служебные и административные методы](#служебные-и-административные-методы)
|
2026-08-23 20:39:22 +03:00
|
|
|
|
- [Управление очередью и конфигурацией](#управление-очередью-и-конфигурацией)
|
2026-08-21 07:34:45 +03:00
|
|
|
|
- [Модель состояний и связь методов с ней](#модель-состояний-и-связь-методов-с-ней)
|
|
|
|
|
|
- [Сквозной пример работы (curl)](#сквозной-пример-работы-curl)
|
|
|
|
|
|
|
|
|
|
|
|
## Общие соглашения
|
|
|
|
|
|
|
|
|
|
|
|
- Тело запроса и ответа — 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` — уже развёрнутая конфигурация проверок (тип + список
|
|
|
|
|
|
целей), агенту не нужно самому сопоставлять группы целей.
|
|
|
|
|
|
|
|
|
|
|
|
### `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
|
|
|
|
|
|
в этом определении не участвует. Важно, что ресурс должен быть именно
|
|
|
|
|
|
внешним: 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,
|
|
|
|
|
|
"detail": "matched"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Ответ: `{"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
|
|
|
|
|
|
(`sites[].site_id`), иначе — `400`.
|
|
|
|
|
|
|
|
|
|
|
|
Запрос:
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"site_id": "site-1", "hostname": "probe-host-1"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Ответ: `{"ok": true, "poll_interval_seconds": 5}`.
|
|
|
|
|
|
|
|
|
|
|
|
### `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}
|
|
|
|
|
|
]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Пустой список `[]`, если сейчас нечего проверять.
|
|
|
|
|
|
|
|
|
|
|
|
### `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},
|
|
|
|
|
|
{"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},
|
|
|
|
|
|
{"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 не будет считать
|
|
|
|
|
|
данные с этой площадки завершёнными.
|
|
|
|
|
|
|
|
|
|
|
|
Ответ: `{"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},
|
|
|
|
|
|
"total_validators": 4
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `GET /api/v1/admin/ips`
|
|
|
|
|
|
|
|
|
|
|
|
Полный список всех IP из очереди со всеми полями (см.
|
|
|
|
|
|
[USAGE.md](USAGE.md#значения-полей-ip) — расшифровка полей и статусов).
|
|
|
|
|
|
|
|
|
|
|
|
### `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`
|
|
|
|
|
|
|
|
|
|
|
|
**Безвозвратное удаление**, в отличие от `cancel` выше: строка `ip_queue`
|
|
|
|
|
|
и вся её история (`checks`, `events`) стираются физически, без возможности
|
|
|
|
|
|
восстановления. Работает из любого состояния, включая активно
|
|
|
|
|
|
проверяемое — если Floating IP привязан, он отвязывается тем же
|
|
|
|
|
|
best-effort способом, что и при `cancel`/обычном завершении, владеющий
|
|
|
|
|
|
валидатор освобождается.
|
|
|
|
|
|
|
|
|
|
|
|
| Метод | Путь | Тело | Успех | Ошибки |
|
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
| 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` удаляет **вообще всё**, что сейчас в
|
|
|
|
|
|
очереди, включая адреса в процессе проверки — самая опасная операция
|
|
|
|
|
|
этого API, используйте с осторожностью.
|
|
|
|
|
|
|
|
|
|
|
|
```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`
|
|
|
|
|
|
|
|
|
|
|
|
Слотов ровно три (`index` ∈ {1, 2, 3}) — это ограничение схемы БД
|
|
|
|
|
|
(`ip_queue.site{1,2,3}_complete`), а не искусственное. Пустой список слотов
|
|
|
|
|
|
— штатный сценарий, отключающий inbound-проверки целиком (см.
|
|
|
|
|
|
[USAGE.md](USAGE.md#управление-площадками-проберами)).
|
|
|
|
|
|
|
|
|
|
|
|
| Метод | Путь | Тело | Успех | Ошибки |
|
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
| GET | `/api/v1/admin/config/sites` | — | `[{"index","site_id"}]` (до 3 строк) | |
|
|
|
|
|
|
| PUT | `/api/v1/admin/config/sites/{index}` | `{"site_id"}` | `200` | `400`, если `index` не 1..3; `409`, если `site_id` уже занят другим слотом |
|
|
|
|
|
|
| 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` |
|
|
|
|
|
|
|
|
|
|
|
|
### Пример: конфигурация целиком через 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-08-21 11:25:52 +03:00
|
|
|
|
Площадки (`siteN_complete`) — опциональны: сколько их учитывается,
|
2026-08-23 20:39:22 +03:00
|
|
|
|
целиком определяется текущим списком `sites` (0–3 записи, управляется
|
|
|
|
|
|
через `/api/v1/admin/config/sites` — см.
|
|
|
|
|
|
[выше](#управление-очередью-и-конфигурацией)). Пустой список — агрегация
|
|
|
|
|
|
ждёт только `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`;
|
|
|
|
|
|
- **`done`/`failed` → `queued` (новая попытка)** — `POST
|
2026-08-23 22:24:55 +03:00
|
|
|
|
/api/v1/admin/ips` с уже завершённым адресом в списке;
|
|
|
|
|
|
- **любое состояние → адрес физически исчезает из очереди**, вместе со
|
|
|
|
|
|
всей историей — `DELETE /api/v1/admin/ips/{ip}`, `POST
|
|
|
|
|
|
/api/v1/admin/ips/delete`, `POST /api/v1/admin/ips/clear` (см.
|
|
|
|
|
|
[выше](#delete-apiv1adminipsip-post-apiv1adminipsdelete-post-apiv1adminipsclear)).
|
|
|
|
|
|
Не путать с cancel — cancel сохраняет запись как историю (`failed`/
|
|
|
|
|
|
`cancelled`), delete стирает её целиком без возможности восстановления.
|
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).
|