Files
EvoBGP/docs/api.md
T
DenozordecandCursor 240d83fc61 docs(docs): describe durable jobs, telemetry, and bundle limits
Зафиксированы очередь job, семантика метрик, лимиты бандла и исходящих запросов, а также чеклист продакшена для пула соединений.

Co-authored-by: Cursor <[email protected]>
2026-11-02 17:24:18 +07:00

17 KiB
Raw Blame History

REST API: обзор и ссылки

Полный контракт запросов и ответов описан в openapi.yaml (OpenAPI 3.1). Этот файл — источник правды. Краткий контекст и ранние таблицы — в evobgp-api-sketches.md (черновик, не заменяет OpenAPI).

Базовый URL и версия

  • Все функциональные маршруты API используют префикс /v1 (например https://api.example.com/v1/modules).
  • Версия сборки: GET /v1/version (публичный маршрут, без Bearer).

Публичные маршруты (без Authorization)

Метод Путь Назначение
GET /v1/health Liveness
GET /v1/ready Readiness (зависимости, например БД)
GET /v1/version Версия / метаданные сборки

Дополнительно на корне сервера (вне /v1):

Метод Путь Назначение
GET /metrics Метрики Prometheus

Все остальные запросы под /v1/..., кроме перечисленных выше трёх GET, проходят через middleware и требуют Authorization: Bearer <api_key> (см. access.md).

Ограничение тела запроса

После аутентификации все непубличные /v1/... запросы проверяются целиком до вызова handler: максимум 8 MiB (8388608 bytes). Проверка действует для любого метода, включая GET, без зависимости от Content-Type или Content-Length; JSON с большим хвостом whitespace не позволяет выполнить мутацию до обнаружения превышения. Ответ — 413 application/problem+json, без выполнения запрошенной мутации. Ошибка чтения возвращает 400, отмена/таймаут чтения — 408.

Запросы с телом допускаются без очереди ожидания: по умолчанию 4 одновременно на API instance. EVOBGP_API_BODY_MAX_CONCURRENT задаёт число от 1 до 64; невалидное значение выбирает default 4. Слот удерживается до завершения handler, пока его request buffer может использоваться. При занятых слотах — немедленный 429 с Retry-After: 1. Пустые http.NoBody запросы, включая обычные node GET/pull, слота не занимают; указание Content-Length: 0 при фактически существующем body не отключает ограничение.

CSV сохраняет отдельные ограничения своей операции: 8 MiB, 10000 строк данных, 64 поля. Тело запроса проверяется до импорта; SSE ограничивается только на входе и продолжает потоковый ответ. Предел буферов ограничивает память входящих тел, а не всю память разбора JSON, работы handler или response.

Группы маршрутов (соответствие тегам OpenAPI)

Ниже — обзор того, что реализовано в коде (internal/httpapi/routes.go, routes_crud.go). Детали тел, кодов ответов и схем — только в OpenAPI.

Modules

  • GET /v1/modules, GET /v1/modules/{module_id}
  • POST /v1/modules, PATCH /v1/modules/{module_id}, DELETE /v1/modules/{module_id}
  • GET|POST|PATCH|DELETE для .../cdn-sources, .../as-entries, .../domain-entries, .../ip-range-entries
  • POST /v1/modules/{module_id}/refresh
  • GET /v1/router-lists/catalog — агрегированный каталог модулей/entries/communities

Lookup

  • GET /v1/lookup?q= — быстрая проверка IP или FQDN в списках (viewer+).
    • Слой entry: IP_RANGES (CIDR.Contains) / DOMAINS (нормализованный FQDN).
    • Слой snapshot: материализованные module_prefix_snapshot (для IP — Contains по всем модулям; для домена — source=domain у matched DOMAINS-модулей).
    • В каждом матче — community (community_id / значение / title).
    • Live DoH не выполняется. Контракт: OpenAPI lookupMembership.

DoH profiles

  • GET|POST /v1/doh-profiles
  • GET|PATCH|DELETE /v1/doh-profiles/{id}

Communities

  • GET|POST /v1/communities
  • GET|PATCH|DELETE /v1/communities/{id}

API keys

  • GET /v1/auth/session — tenant и роль текущего ключа
  • GET|POST /v1/api-keys — список и создание (operator)
  • GET|PATCH|DELETE /v1/api-keys/{id}, POST /v1/api-keys/{id}/rotate

Peers

  • GET /v1/peers, POST /v1/peers
  • GET|PATCH|DELETE /v1/peers/{id}
  • Для POST|PATCH|DELETE peer запускается быстрый job peer_reconcile (без module ingest/сбора префиксов); после него автоматически ставится apply на спикеры.

Live-состояние следует читать вместе с live_observation (схема LiveObservation в OpenAPI). Отсутствующее наблюдение не означает ноль сессий или отказ BGP: session_state может быть null, а сохранённое значение вынесено в last_known_session_state. Для назначенного пира состояние вычисляется по его speaker; успешный опрос другого узла не делает его healthy. У speakers отдельно представлены качество опроса BGP и агента; счётчики без успешного опроса nullable.

Speakers

  • GET /v1/speakers, POST /v1/speakers
  • GET|PATCH /v1/speakers/{speaker_id} (в коде идентификатор в пути — speaker_id; в части маршрутов apply используется {id} — смотрите OpenAPI и реализацию)

Уточнение по коду: для apply на одном спикере зарегистрирован маршрут POST /speakers/{id}/apply внутри v1 mux → POST /v1/speakers/{id}/apply.

Revisions

  • GET /v1/revisions, GET /v1/revisions/{revision_id}
  • GET /v1/revisions/{revision_id}/prefixes
  • GET /v1/revisions/{revision_id}/preview
  • GET /v1/revisions/{revision_a}/diff/{revision_b}
  • POST /v1/revisions/{revision_id}/rollback

Deploy и BIRD

  • POST /v1/apply
  • POST /v1/speakers/{id}/apply
  • POST /v1/bird/reload

Ответ 202 подтверждает принятие задания. Результат доставки удалённому агенту не равен применению: старый 2xx без явного revision ID имеет уровень unconfirmed; совпадающий явный ACK — agent_reported. Локальный успешный apply — local_applied. Подтверждение хеша работающего BIRD-конфига (verified) пока не реализовано. Job содержит частичные результаты по speakers; ошибка одного узла не скрывается общим успехом, отмена сохраняет выполненную часть и останавливает публикацию следующим узлам. Границы совместимости и одного активного deploy writer описаны в backend-compatibility.md.

Jobs

  • GET /v1/jobs, GET /v1/jobs/{job_id}
  • POST /v1/jobs/{job_id}/cancel

Node (роль node)

  • GET /v1/speakers/{speaker_id}/revisions/latest
  • GET /v1/speakers/{speaker_id}/bundle/{revision_id}
  • POST /v1/nodes/enroll

Settings

  • GET /v1/settings, PATCH /v1/settings — tenant KV (global_settings): BIRD, revision_retention_minutes, runtime_logs_* (автоочистка FS), произвольные ключи. Чтение — viewer+; PATCH — operator+.

RuntimeLogs

Файловые логи Docker-сервисов (sidecar stack-runtime-logs). FS API только в процессе evobgp-all при EVOBGP_SERVICE=evobgp-all и EVOBGP_RUNTIME_LOGS_DIR (см. access.md). Иначе GET/DELETE по файлам → 503 (runtime_logs_unavailable).

Метод Путь Роль Назначение
GET /v1/runtime-logs/files viewer+ Список *.log (имя, размер, mtime)
GET /v1/runtime-logs/files/{filename} viewer+ Хвост файла (?lines=, ?bytes=, ?grep=)
DELETE /v1/runtime-logs/files/{filename} operator+ Синхронная очистка (?mode=truncate|delete, default truncate); max 512 MiB
GET /v1/runtime-logs/cleanup-audit viewer+ Пагинированный audit очистки (cursor, limit); без FS volume
GET /v1/runtime-logs/auto-estimate operator+ Файлы выше порога из tenant settings
POST /v1/runtime-logs/auto-run operator+ Немедленный прогон (?dry_run=true); audit с actor_prefix=auto:scheduler

{filename} — только basename, паттерн ^[a-z0-9][a-z0-9_.-]*\.log$. Очистка пишет строку в таблицу runtime_log_cleanup_audit (миграция 000026).

CRUD audit (/v1/audit)

Локальный журнал изменений CRUD (modules, peers, settings, API keys, …). Миграция 000030_audit_log. Чтение — bgp:monitoring:read (viewer+).

Метод Путь Роль Назначение
GET /v1/audit viewer+ Пагинированный audit (cursor, limit, опционально action, severity)

При AUTH_PORTAL_URL + AUTH_AUDIT_INGEST_SECRET каждая запись дополнительно отправляется в auth-portal (POST /api/v1/ingest/audit, source_app=bgp).

Соглашения из OpenAPI

  • Ошибки в стиле RFC 9457 (application/problem+json): type, title, status, detail, и т.д.
  • Пагинация списков: query-параметры cursor, limit; в ответе часто items, next_cursor, has_more.
  • Заголовок Idempotency-Key для идемпотентных мутаций (рекомендации — в описаниях операций в OpenAPI).
  • Заголовок X-Tenant-Id описан в спецификации для супер-ролей; в текущей реализации Go tenant берётся только из API-ключа, заголовок в обработчиках не переключает контекст (см. access.md).

Как смотреть документацию API

  • Статическая страница Redoc: openapi.html (инструкции для Gitea и пересборки — OPENAPI-GITEA.md).
  • Пересборка после правок YAML (из корня репозитория, PowerShell):
.\scripts\build-openapi-html.ps1

Примеры вызовов

PowerShell, список модулей (подставьте свой токен и URL):

$base = "http://localhost:8080"
$token = "opkey"
$h = @{ Authorization = "Bearer $token" }
Invoke-RestMethod -Uri "$base/v1/modules" -Headers $h

Эквивалент с curl (если установлен):

curl -s -H "Authorization: Bearer opkey" http://localhost:8080/v1/modules

CORS для браузерных клиентов настраивается переменной EVOBGP_CORS_ORIGINS на стороне API.

Квоты preview и lookup

Синхронные GET /lookup, CDN preview и revision preview используют общий локальный пул: по умолчанию 4 активных операции на процесс API, не более 2 на tenant. Для каждого tenant действуют отдельные минутные счётчики: 120 lookup, 30 CDN preview и 30 revision preview. При заполнении возвращается 429 с Retry-After, без очереди ожидания. Отмена клиента не освобождает место, пока обработчик действительно не завершил работу. Retry-After доступен также клиентам через CORS.

Переменная По умолчанию Допустимый диапазон
EVOBGP_API_EXPENSIVE_MAX_CONCURRENT 4 1–64
EVOBGP_API_EXPENSIVE_TENANT_CONCURRENT 2 1–64
EVOBGP_API_LOOKUP_PER_MINUTE 120 1–100000
EVOBGP_API_CDN_PREVIEW_PER_MINUTE 30 1–100000
EVOBGP_API_REVISION_PREVIEW_PER_MINUTE 30 1–100000

Некорректное значение заменяется значением по умолчанию. Состояние ограничено 4096 tenant; действующие счётчики и активная работа не вытесняются ради новых tenant. Квоты локальны: несколько API-реплик умножают доступную нагрузку. Они не являются общей кластерной квотой. Refresh/retry управляются отдельной durable queue с её лимитами и replay исходного запроса; legacy bundle/latest endpoints не используют квоту preview/lookup. Общий admission входного тела может вернуть 429 до проверки replay, поэтому такой POST можно повторить с тем же idempotency key.

Ограничения CSV-импорта

CSV-импорт ограничен 8 MiB полного тела запроса, 10000 строк данных без заголовка и 64 полями в одной записи. Превышение возвращает 413 до создания записей, включая запросы без Content-Length; отправляйте большие списки частями. Синтаксически корректный префикс слишком большого файла больше не импортируется молча. Отмена запроса останавливает последующие записи. Ошибки данных или БД после начала импорта всё ещё могут оставить уже созданные строки: импорт не является общей транзакцией.

Сохранённый отчёт доставки

GET /v1/jobs/{job_id}/report требует bgp:operations:read и возвращает deployment_progress независимо от промежуточной meta. Для recovery job поле source_deployment_job_id указывает исходную операцию в том же tenant. Пока собственных записей нет, отчёт показывает исходный прогресс; после начала recovery — собственный. Каждый элемент содержит job_id, поэтому происхождение результата видно явно.

health_received_at — время получения наблюдения control plane, health_source_at — необязательная отметка агента. Отсутствие этого поля у 2.56.2 не препятствует доставке. unknown и unconfirmed нельзя интерпретировать как подтверждённое применение или отказ BIRD. Полный HTTP-контракт: OpenAPI, getJobReport.

Связанные документы

  • access.md — ключи и роли.
  • overview.md — продуктовые возможности.