Prepare 0.1.0 for lab release: durable handlers, HTTP Compose, CI, and pulumi-tests.

- 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)
This commit is contained in:
Sergey Antropoff
2026-07-18 04:18:05 +03:00
parent 777926487b
commit 48df10b17e
172 changed files with 7528 additions and 1208 deletions
+240
View File
@@ -0,0 +1,240 @@
**Language / Язык:** [English](../architecture.md) | [Русский](architecture.md)
# Архитектура
## Цели
`proxmox-api-simulator` — stateful асинхронный эмулятор Proxmox VE API. Главная
цель проектирования — измеримая совместимость с контрактом: маршруты, валидация,
аутентификация, права доступа, формы ответов, переходы состояния и персистентные
долгоживущие задачи проверяются независимо, а не объявляются «универсально
совместимыми». В комплекте majors **69** поставляются с **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 **69** с **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 69 поставляются с полной
регистрацией обработчиков, поэтому объявленные методы не должны попадать на
этот путь при нормальной работе.
6. Лабораторная документация и cookbooks живут в `docs/` и `examples/`; внутренние
research/prompt-заметки не входят в пользовательское руководство.