- 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)
17 KiB
Language / Язык: English | Русский
Архитектура
Цели
proxmox-api-simulator — stateful асинхронный эмулятор Proxmox VE API. Главная
цель проектирования — измеримая совместимость с контрактом: маршруты, валидация,
аутентификация, права доступа, формы ответов, переходы состояния и персистентные
долгоживущие задачи проверяются независимо, а не объявляются «универсально
совместимыми». В комплекте majors 6–9 поставляются с 100% регистрацией
семантических обработчиков для каждого объявленного метода контракта и поддержкой
горячей замены между этими majors во время работы.
Для обычной работы симулятору не нужна живая установка Proxmox. Официальные артефакты API и санитизированные наблюдения импортируются заранее и хранятся как версионируемые снимки.
Контекст системы
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
Архитектура компонентов
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.
Жизненный цикл запроса
- Middleware назначает или проверяет request ID и запускает безопасную структурированную телеметрию.
- Выбранный профиль совместимости разрешает неизменяемый снимок API и версионно-специфичное поведение.
- Аутентификация определяет принципала без раскрытия учётных данных в логах.
- Объявленные контрактом и специфичные для обработчика права проверяются до раскрытия или изменения ресурсов.
- Значения path, query и body валидируются схемами, полученными из контракта.
- Семантический обработчик выполняется через прикладной сервис и явную границу транзакции.
- Долгие операции атомарно обновляют блокировку ресурса и создают персистентную задачу, затем возвращают её UPID.
- Рендерер ответа применяет 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 — внешняя зависимость.
Архитектурные решения
- Маршруты FastAPI регистрируются из нормализованных снимков при старте; сотни вручную поддерживаемых объявлений маршрутов не нужны.
- SQLAlchemy не используется. Прямые asyncpg-репозитории делают поведение транзакций и конкурентности явным.
- Задачи на PostgreSQL — граница долговечности; фоновые задачи FastAPI и in-memory очереди не используются для критичной работы.
- Совместимость capability-driven и версионирована, а не реализована через разбросанные условия по строкам версий.
- Отсутствующие обработчики честно завершаются через
CONTRACT_FALLBACK(по умолчаниюerror→ HTTP 501). Majors 6–9 поставляются с полной регистрацией обработчиков, поэтому объявленные методы не должны попадать на этот путь при нормальной работе. - Лабораторная документация и cookbooks живут в
docs/иexamples/; внутренние research/prompt-заметки не входят в пользовательское руководство.