48df10b17e
- Harden DB-backed handlers and seed profiles; align client wire shapes for cluster resources, QEMU config, and node SSL fields - Serve plain HTTP on Compose :8006; keep TLS optional (--profile tls) and terminate HTTPS at Kubernetes Ingress - Add pulumi-tests (full contract surface majors 6–9 + BPG lifecycle) and make pulumi-tests - Ship bilingual docs, CHANGELOG, SECURITY, CONTRIBUTING, and GitHub Actions (make ci + Compose/Helm validation)
241 lines
17 KiB
Markdown
241 lines
17 KiB
Markdown
**Language / Язык:** [English](../architecture.md) | [Русский](architecture.md)
|
||
|
||
# Архитектура
|
||
|
||
## Цели
|
||
|
||
`proxmox-api-simulator` — stateful асинхронный эмулятор Proxmox VE API. Главная
|
||
цель проектирования — измеримая совместимость с контрактом: маршруты, валидация,
|
||
аутентификация, права доступа, формы ответов, переходы состояния и персистентные
|
||
долгоживущие задачи проверяются независимо, а не объявляются «универсально
|
||
совместимыми». В комплекте majors **6–9** поставляются с **100%** регистрацией
|
||
семантических обработчиков для каждого объявленного метода контракта и поддержкой
|
||
горячей замены между этими majors во время работы.
|
||
|
||
Для обычной работы симулятору не нужна живая установка Proxmox. Официальные
|
||
артефакты API и санитизированные наблюдения импортируются заранее и хранятся как
|
||
версионируемые снимки.
|
||
|
||
## Контекст системы
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Client["API clients<br/>proxmoxer / Terraform / Ansible"]
|
||
Admin["Simulator operator"]
|
||
Docs["Official Proxmox API Viewer"]
|
||
API["FastAPI application"]
|
||
Importer["Contract importer and CLI"]
|
||
Contract["Versioned API contract"]
|
||
Engine["Simulation engine"]
|
||
Worker["Persistent task workers"]
|
||
DB[(PostgreSQL)]
|
||
Obs["Logs / Prometheus / OpenTelemetry"]
|
||
|
||
Client -->|"/api2/json"| API
|
||
Admin -->|"CLI, Make/Helm, Web UI /ui/api"| API
|
||
Docs -->|"explicit import only"| Importer
|
||
Importer --> Contract
|
||
Contract --> DB
|
||
API --> Contract
|
||
API --> Engine
|
||
Engine --> DB
|
||
Engine --> Worker
|
||
Worker --> DB
|
||
API --> Obs
|
||
Worker --> Obs
|
||
```
|
||
|
||
## Архитектура компонентов
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph ContractPlane["API contract plane"]
|
||
Sources["Remote, local, and recorded sources"] --> Parse["Source adapters and parser"]
|
||
Parse --> Normalize["Version-independent normalized model"]
|
||
Normalize --> Validate["Validation, checksums, manifests"]
|
||
Validate --> Registry["Contract registry"]
|
||
Registry --> Diff["Semantic version diff"]
|
||
Registry --> Routes["Dynamic route and schema factory"]
|
||
Registry --> Reports["Compatibility reports"]
|
||
end
|
||
|
||
subgraph RequestPlane["Request plane"]
|
||
Middleware["Request ID, logging, metrics"] --> Auth["Ticket or API-token authentication"]
|
||
Auth --> Permission["ACL and privilege evaluation"]
|
||
Permission --> Input["Contract-driven request validation"]
|
||
Input --> Handler["Semantic handler registry"]
|
||
Handler --> Render["Proxmox response and error renderer"]
|
||
end
|
||
|
||
subgraph SimulationPlane["Simulation plane"]
|
||
Handler --> Services["Node, QEMU, LXC, storage services"]
|
||
Services --> State["State machines and resource locks"]
|
||
Services --> Tasks["Transactional persistent tasks"]
|
||
Tasks --> Workers["asyncio workers with PostgreSQL leases"]
|
||
Faults["Scenarios, faults, virtual clock"] --> Services
|
||
end
|
||
|
||
Routes --> Input
|
||
Registry --> Permission
|
||
State --> PG[(PostgreSQL)]
|
||
Workers --> PG
|
||
Auth --> PG
|
||
```
|
||
|
||
## Границы и направление зависимостей
|
||
|
||
Плоскость контракта владеет объявленными фактами API. Она импортирует
|
||
исходные артефакты, сохраняет неизвестные поля источника, формирует
|
||
детерминированный нормализованный JSON и предоставляет неизменяемые
|
||
версионируемые контракты. Она не знает о состоянии ВМ и не выполняет операции.
|
||
|
||
Плоскость симуляции владеет изменяемым состоянием кластера и семантикой
|
||
операций. Она использует доменные модели и репозитории, не зависящие от FastAPI
|
||
и структур контракта, специфичных для источника. PostgreSQL — система записи
|
||
для ресурсов, состояния безопасности, блокировок, сценариев и задач.
|
||
|
||
Долговечные задачи подтверждаются только после совместной фиксации строки задачи,
|
||
события, ключа идемпотентности и опциональной блокировки ресурса. Воркеры
|
||
захватывают задачи через `SKIP LOCKED`, продлевают аренды в реальном времени,
|
||
сохраняют прогресс и append-only логи/события и позволяют повторно захватить
|
||
просроченную работу после сбоя процесса. Lifespan владеет ограниченным набором
|
||
asyncio-воркеров и ждёт упорядоченного завершения; PostgreSQL остаётся очередью
|
||
и источником истины между репликами.
|
||
|
||
Длительности симуляции используют внедрённые часы: реальные, ускоренные или
|
||
продвигаемые вручную. Операции ВМ — явные переходы конечного автомата, а
|
||
засеянные правила сбоев оцениваются детерминированно. Аренды воркеров намеренно
|
||
исключены из виртуального времени: они используют wall time PostgreSQL и
|
||
monotonic sleep процесса, чтобы приостановленный или ускоренный сценарий не
|
||
нарушил безопасность распределённых воркеров.
|
||
|
||
Секреты аутентификации хранятся как salted scrypt-хеши. Сессионные тикеты
|
||
подписаны и имеют срок действия; мутационные запросы используют CSRF-токены,
|
||
привязанные к тикету. Привилегии API-токена пересекаются с эффективными
|
||
распространёнными ACL владельца-принципала, поэтому токен не может эскалировать
|
||
права владельца. Логи редактируют распознанные представления тикетов, паролей и
|
||
токенов перед записью.
|
||
|
||
API-слой — адаптер. Он аутентифицирует, авторизует, валидирует по выбранному
|
||
контракту, диспетчеризует семантический обработчик и формирует
|
||
версионно-совместимый ответ. Маршрут без семантического обработчика явно
|
||
сообщается как неподдерживаемый, если оператор не включил нестандартный режим
|
||
fallback.
|
||
|
||
Зависимости направлены внутрь: HTTP- и CLI-адаптеры зависят от прикладных
|
||
сервисов; прикладные сервисы — от доменных интерфейсов; PostgreSQL, файлы
|
||
контрактов, метрики и часы реализуют эти интерфейсы. Доменные сервисы никогда не
|
||
импортируют FastAPI.
|
||
|
||
## Жизненный цикл запроса
|
||
|
||
1. Middleware назначает или проверяет request ID и запускает безопасную
|
||
структурированную телеметрию.
|
||
2. Выбранный профиль совместимости разрешает неизменяемый снимок API и
|
||
версионно-специфичное поведение.
|
||
3. Аутентификация определяет принципала без раскрытия учётных данных в логах.
|
||
4. Объявленные контрактом и специфичные для обработчика права проверяются до
|
||
раскрытия или изменения ресурсов.
|
||
5. Значения path, query и body валидируются схемами, полученными из контракта.
|
||
6. Семантический обработчик выполняется через прикладной сервис и явную границу
|
||
транзакции.
|
||
7. Долгие операции атомарно обновляют блокировку ресурса и создают
|
||
персистентную задачу, затем возвращают её UPID.
|
||
8. Рендерер ответа применяет Proxmox-обёртку, заголовки, cookies и
|
||
версионно-специфичные шаблоны ошибок.
|
||
|
||
## Персистентность и конкурентность
|
||
|
||
Используется `asyncpg` напрямую. Репозитории принимают явное соединение или
|
||
контекст транзакции; SQL параметризован и расположен рядом с репозиторием.
|
||
Изменяемые глобальные переменные процесса не являются авторитетным состоянием.
|
||
|
||
Воркеры захватывают задачи через `FOR UPDATE SKIP LOCKED`, устанавливают
|
||
продлеваемые аренды и используют метаданные идемпотентности для восстановления
|
||
после сбоя процесса. Состояние ресурса, блокировки ресурсов и создание задачи
|
||
изменяются в одной транзакции, когда это требуется. Оптимистичные колонки версии
|
||
обнаруживают конкурентные обновления, а ограничения БД защищают инварианты,
|
||
например уникальность VMID в пределах кластера.
|
||
|
||
Application lifespan владеет пулом соединений и ограниченным набором
|
||
asyncio-задач воркеров. При shutdown захват прекращается, выполняемая работа
|
||
достигает безопасной границы, отмена происходит только после настроенного grace
|
||
period, затем пул закрывается.
|
||
|
||
## Получение контракта и доверие
|
||
|
||
Сетевой доступ ограничен явными командами import и recorder. Импортёры
|
||
принудительно используют HTTPS, по умолчанию allowlist официальных хостов,
|
||
лимиты размера ответа и редиректов, таймауты и ограниченные повторы. Каждый
|
||
сырой артефакт неизменяем и имеет SHA-256 checksum. Его manifest фиксирует
|
||
происхождение, версию, предупреждения парсера и checksum нормализованного
|
||
снимка. Локальные снимки позволяют запуску и тестам работать офлайн.
|
||
|
||
Объявленная документация и санитизированное наблюдаемое поведение остаются
|
||
разделёнными. Профиль совместимости выбирает поведение `strict-docs`, `observed`
|
||
или `hybrid` без разброса проверок версий по сервисам.
|
||
|
||
## Модель безопасности
|
||
|
||
- Пароли и секреты API-токенов хранятся только как password hash.
|
||
- Тикеты подписаны, краткоживущие и редактируются в телеметрии.
|
||
- Мутации с ticket-аутентификацией требуют CSRF-валидации; запросы с API-токеном
|
||
CSRF не требуют.
|
||
- Интерактивный Web UI и вспомогательные `/admin/compatibility*` — лабораторные
|
||
поверхности без отдельного admin-токена в текущей сборке; границей доверия
|
||
является сетевая экспозиция.
|
||
- Контейнеры в упакованных образах работают от непривилегированного пользователя.
|
||
|
||
## Горячая замена контракта во время работы
|
||
|
||
При холодном старте загружается `CONTRACT_SNAPSHOT`. Операторы могут заменить
|
||
таблицу маршрутов в памяти для majors 6–9 через
|
||
`POST /ui/api/contract/apply?major=N` (также доступно в Web UI). Замена
|
||
обновляет `/version`, OpenAPI и состояние совместимости и действует только в
|
||
пределах процесса (перезапуск восстанавливает снимок из env).
|
||
|
||
## Наблюдаемость
|
||
|
||
JSON-логи содержат request ID, шаблон маршрута, статус, длительность и
|
||
редактированные поля идентичности. Процессные экспортёры Prometheus/OpenTelemetry
|
||
пока не поставляются; обработчики Proxmox `/cluster/metrics*` симулируют только
|
||
конфигурацию metrics-server PVE.
|
||
|
||
## Стратегия тестирования
|
||
|
||
Unit-тесты покрывают обработку контракта и доменные правила. Интеграционные
|
||
тесты проверяют репозитории, транзакции, воркеров и lifespan на PostgreSQL.
|
||
Наборы contract и compatibility нацелены на majors **6–9** с **100%** покрытием
|
||
реестра обработчиков. Внешний proxmoxer smoke выполняется против TLS-шлюза
|
||
Compose. Concurrency-тесты проверяют аренды задач и переходы состояния.
|
||
|
||
Готовность БД включает последнюю упакованную версию миграции, а не только
|
||
успешный connectivity-запрос. Воркеры повторяют неудачные захваты, пока не
|
||
появятся таблицы миграций. Нормализованные записи ресурсов используют
|
||
compare-and-swap обновления версии через типизированный репозиторий, поэтому
|
||
устаревшие писатели получают domain conflict.
|
||
|
||
## Модель развёртывания
|
||
|
||
На контейнер приходится один процесс Uvicorn. Горизонтальные реплики
|
||
координируются через PostgreSQL, а не через локальные очереди. Миграции БД и
|
||
операции seed — явные команды и в Kubernetes становятся отдельными job. PostgreSQL
|
||
включён в локальный Docker Compose, но в production chart — внешняя зависимость.
|
||
|
||
## Архитектурные решения
|
||
|
||
1. Маршруты FastAPI регистрируются из нормализованных снимков при старте;
|
||
сотни вручную поддерживаемых объявлений маршрутов не нужны.
|
||
2. SQLAlchemy не используется. Прямые asyncpg-репозитории делают поведение
|
||
транзакций и конкурентности явным.
|
||
3. Задачи на PostgreSQL — граница долговечности; фоновые задачи FastAPI и
|
||
in-memory очереди не используются для критичной работы.
|
||
4. Совместимость capability-driven и версионирована, а не реализована через
|
||
разбросанные условия по строкам версий.
|
||
5. Отсутствующие обработчики честно завершаются через `CONTRACT_FALLBACK`
|
||
(по умолчанию `error` → HTTP 501). Majors 6–9 поставляются с полной
|
||
регистрацией обработчиков, поэтому объявленные методы не должны попадать на
|
||
этот путь при нормальной работе.
|
||
6. Лабораторная документация и cookbooks живут в `docs/` и `examples/`; внутренние
|
||
research/prompt-заметки не входят в пользовательское руководство.
|