Files
cloud-ip-validator/docs/API.md
T

32 KiB
Raw Blame History

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).

Для работы из браузера вместо curl есть admin-dashboard — веб-панель, дающая графический доступ ко всему административному API ниже, см. DASHBOARD.md.

Содержание

Общие соглашения

  • Тело запроса и ответа — 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 — заданными в control-api.yaml при первом запуске (пустая база) либо созданными позже через /api/v1/admin/config/* (см. «Управление очередью и конфигурацией») — иначе методы, требующие существующую сущность, вернут 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, если валидатор сейчас занят.

Управление очередью и конфигурацией

Методы этого раздела — единственный способ менять состав очереди (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); он не конфликтует с 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) → не трогается вообще — нельзя запустить вторую параллельную проверку одного и того же адреса.

Порядок обработки внутри одного вызова соответствует порядку адресов в списке; повторная отправка того же списка позже даёт тот же относительный порядок прогона.

Запрос:

{"addresses": ["203.0.113.10", "203.0.113.11"]}

Ответ (200):

{
  "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/уже отменён) — отменять нечего.

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, используйте с осторожностью.

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"

Валидаторы: /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).

Метод Путь Тело Успех Ошибки
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

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"

Модель состояний и связь методов с ней

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 (0–3 записи, управляется через /api/v1/admin/config/sites — см. выше). Пустой список — агрегация ждёт только egress_complete, ни одна площадка не требуется. Подробнее — USAGE.md.

Три дополнительных перехода, все инициируются оператором через /api/v1/admin/ips*, а не самим оркестратором:

  • любое нетерминальное состояние → failed (overall_result: "cancelled") — POST /api/v1/admin/ips/{ip}/cancel;
  • done/failed → queued (новая попытка) — POST /api/v1/admin/ips с уже завершённым адресом в списке;
  • любое состояние → адрес физически исчезает из очереди, вместе со всей историей — DELETE /api/v1/admin/ips/{ip}, POST /api/v1/admin/ips/delete, POST /api/v1/admin/ips/clear (см. выше). Не путать с cancel — cancel сохраняет запись как историю (failed/ cancelled), delete стирает её целиком без возможности восстановления.

Сквозной пример работы (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.