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

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 17:24:18 +07:00

204 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REST API: обзор и ссылки
Полный контракт запросов и ответов описан в **[openapi.yaml](openapi.yaml)** (OpenAPI 3.1). Этот файл — **источник правды**. Краткий контекст и ранние таблицы — в [evobgp-api-sketches.md](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](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](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](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](access.md)).
## Как смотреть документацию API
- Статическая страница Redoc: [openapi.html](openapi.html) (инструкции для Gitea и пересборки — [OPENAPI-GITEA.md](OPENAPI-GITEA.md)).
- Пересборка после правок YAML (из корня репозитория, PowerShell):
```powershell
.\scripts\build-openapi-html.ps1
```
## Примеры вызовов
PowerShell, список модулей (подставьте свой токен и URL):
```powershell
$base = "http://localhost:8080"
$token = "opkey"
$h = @{ Authorization = "Bearer $token" }
Invoke-RestMethod -Uri "$base/v1/modules" -Headers $h
```
Эквивалент с `curl` (если установлен):
```text
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](openapi.yaml).
## Связанные документы
- [access.md](access.md) — ключи и роли.
- [overview.md](overview.md) — продуктовые возможности.