19 KiB
API Control API
Control API — единственная точка входа в систему для validator-agent,
prober и оператора (администратора). Все данные передаются в формате
JSON, базовый префикс прикладных методов — /api/v1.
Важно. На данный момент API не защищён аутентификацией/авторизацией — эндпоинты доступны любому, кто может достучаться до порта control-api по сети. Для эксплуатации за пределами доверенного сегмента сети обязательно ограничьте доступ на уровне сети/файрвола (см. SETUP.md). Добавление bearer-токена — известное направление доработки, в текущей версии не реализовано.
Базовый URL в примерах — http://control-api.internal:8080, замените на
адрес вашего стенда (см. server.listen_addr в конфиге control-api).
Содержание
- Общие соглашения
- Методы для validator-agent
- Методы для prober
- Служебные и административные методы
- Модель состояний и связь методов с ней
- Сквозной пример работы (curl)
Общие соглашения
- Тело запроса и ответа — JSON (
Content-Type: application/json). - Успешные ответы возвращают
200 OK, либо204 No Content(когда данных нет — например, у валидатора сейчас нет назначения). - Ошибки возвращают
4xx/5xxи тело вида:{"error": "текст ошибки"} - Временные метки (
checked_atв запросах) передаются в формате RFC3339/RFC3339Nano, например2026-08-21T09:15:00.123456789Z. Если поле не удалось распарсить, сервер молча подставит текущее время сервера — не полагайтесь на это в продакшене, всегда передавайте валидную метку. validator_idиsite_idв пути запроса должны совпадать со значениями, заданными в конфиге control-api (validators[].validator_id,sites[].site_id) — иначе методы, требующие существующую сущность, вернут404.
Методы для validator-agent
Эти методы вызывает бинарник validator-agent, работающий на ВМ-валидаторе.
Оператору вручную дёргать их обычно не требуется — они приведены для
понимания протокола и для отладки через curl.
POST /api/v1/agents/register
Регистрация/переактивация валидатора. Вызывается один раз при старте агента (и безопасно при каждом рестарте — идемпотентна).
Запрос:
{
"validator_id": "validator_01",
"hostname": "vm-validator-01",
"agent_version": "1.0.0"
}
Ответ:
{"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.
Запрос (тело необязательно, поля информационные):
{"local_state": "idle"}
Ответ: {"ok": true}. 404, если validator_id не зарегистрирован.
GET /api/v1/agents/{id}/assignment
Есть ли у валидатора сейчас работа. Опрашивается в каждом цикле.
204 No Content— заданий нет.200 OKс телом:{ "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. Агент
определяет это сам, обращаясь к внешнему (снаружи облака) IP-echo
сервису (self_check.ip_echo_urls в validator-agent.yaml, например
api.ipify.org) и сравнивая ответ с ip_address из задания — control-api
в этом определении не участвует. Важно, что ресурс должен быть именно
внешним: OpenStack применяет SNAT через Floating IP только к трафику,
уходящему через внешнюю сеть, поэтому обращение к чему-либо внутри
проекта (в том числе к самому control-api, если он в той же внутренней
сети) покажет приватный адрес валидатора независимо от того, правильно
ли привязан FIP.
Запрос:
{
"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
и т.п.
Запрос:
{
"event_type": "config_received",
"ip_id": 42,
"payload": "{\"checks\":2}"
}
Ответ: {"ok": true}.
POST /api/v1/agents/{id}/results
Отчёт о результатах исходящих (egress) проверок. Можно отправлять по одной проверке сразу после выполнения (рекомендуется — так прогресс не теряется при падении агента) либо пачкой.
Запрос:
{
"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 выполнены".
Запрос:
{"ip_id": 42}
Ответ: {"ok": true}.
Методы для prober
Эти методы вызывает бинарник prober, работающий на внешней площадке.
POST /api/v1/probers/register
Регистрация пробера. site_id должен присутствовать в конфиге control-api
(sites[].site_id), иначе — 400.
Запрос:
{"site_id": "site-1", "hostname": "probe-host-1"}
Ответ: {"ok": true, "poll_interval_seconds": 5}.
GET /api/v1/probers/{site_id}/assignments
Список всех IP, которые сейчас находятся в состоянии checking — то есть
всё, что нужно прозондировать на этом цикле опроса (валидаторов может
работать несколько параллельно, поэтому список, а не один IP).
Ответ:
[
{"ip_id": 42, "ip_address": "203.0.113.10", "ports": [22, 80, 443, 8080], "icmp": true}
]
Пустой список [], если сейчас нечего проверять.
POST /api/v1/probers/{site_id}/results
Отчёт о результатах входящих (inbound) проверок с данной площадки.
Запрос:
{
"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 в каком состоянии.
{
"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 — расшифровка полей и статусов).
GET /api/v1/admin/ips/{ip}
Детали по одному адресу: сам объект IP, все проверки текущей попытки и вся история событий по нему.
{
"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, если валидатор
сейчас занят.
Модель состояний и связь методов с ней
queued ──(control-api сам, без вызова API)──▶ assigning_fip
│
OpenStack FIP associate успешен
▼
awaiting_self_check
│
POST .../self-check {success:true}
▼
checking
│ POST .../results (agent) │ POST .../results (prober, по числу настроенных площадок)
▼ ▼
egress_complete=true siteN_complete=true (только для N, перечисленных в sites конфига)
│
egress + все НАСТРОЕННЫЕ площадки complete=true ИЛИ истекло checking_window_seconds
▼
aggregating
│
done (pass/partial/fail)
или failed
Переходы queued → assigning_fip → awaiting_self_check и финальная
агрегация выполняются control-api самостоятельно по таймеру (см.
orchestrator.poll_interval_seconds), явного HTTP-метода для их запуска
нет — это фоновый цикл (Tick), а не запрос/ответ.
Площадки (siteN_complete) — опциональны: сколько их учитывается,
целиком определяется списком sites в конфиге control-api (0–3 записи).
Пустой список — агрегация ждёт только egress_complete, ни одна площадка
не требуется. Подробнее — USAGE.md.
Сквозной пример работы (curl)
Ниже — минимальный ручной прогон одного IP через API, как если бы вы
писали собственного клиента вместо validator-agent/prober. Полезно
для отладки и для понимания протокола.
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":[...]}
# 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)
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.