docs: define simulator architecture
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
# Architecture
|
||||
|
||||
## Goals
|
||||
|
||||
`proxmox-api-simulator` is a stateful, asynchronous Proxmox VE API emulator. Its
|
||||
primary design goal is measurable contract compatibility: routes, validation,
|
||||
authentication, permissions, response shapes, state transitions, and persistent
|
||||
long-running tasks are verified independently instead of being described as
|
||||
universally compatible.
|
||||
|
||||
The simulator does not require a live Proxmox installation during normal
|
||||
operation. Official API artifacts and sanitized observations are imported ahead
|
||||
of time and stored as versioned snapshots.
|
||||
|
||||
## System context
|
||||
|
||||
```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 and /_simulator"| API
|
||||
Docs -->|"explicit import only"| Importer
|
||||
Importer --> Contract
|
||||
Contract --> DB
|
||||
API --> Contract
|
||||
API --> Engine
|
||||
Engine --> DB
|
||||
Engine --> Worker
|
||||
Worker --> DB
|
||||
API --> Obs
|
||||
Worker --> Obs
|
||||
```
|
||||
|
||||
## Component architecture
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Boundaries and dependency direction
|
||||
|
||||
The contract plane owns declared API facts. It imports source artifacts, retains
|
||||
unknown source fields, produces deterministic normalized JSON, and exposes
|
||||
immutable versioned contracts. It does not know about VM state or execute
|
||||
operations.
|
||||
|
||||
The simulation plane owns mutable cluster state and operation semantics. It uses
|
||||
domain models and repositories that do not depend on FastAPI or source-specific
|
||||
contract structures. PostgreSQL is the system of record for resources, security
|
||||
state, locks, scenarios, and tasks.
|
||||
|
||||
The API layer is an adapter. It authenticates, authorizes, validates against the
|
||||
selected contract, dispatches to a semantic handler, and renders a
|
||||
version-compatible response. A route without a semantic handler is explicitly
|
||||
reported as unsupported unless an operator enables a non-default fallback mode.
|
||||
|
||||
Dependencies point inward: HTTP and CLI adapters depend on application services;
|
||||
application services depend on domain interfaces; PostgreSQL, contract files,
|
||||
metrics, and clocks implement those interfaces. Domain services never import
|
||||
FastAPI.
|
||||
|
||||
## Request lifecycle
|
||||
|
||||
1. Middleware assigns or validates a request ID and starts safe structured
|
||||
telemetry.
|
||||
2. The selected compatibility profile resolves an immutable API snapshot and
|
||||
version-specific behavior.
|
||||
3. Authentication resolves a principal without exposing credentials in logs.
|
||||
4. Contract-declared and handler-specific permissions are evaluated before
|
||||
revealing or mutating resources.
|
||||
5. Path, query, and body values are validated by contract-derived schemas.
|
||||
6. The semantic handler executes through an application service and explicit
|
||||
transaction boundary.
|
||||
7. Long operations atomically update the resource lock and create a persistent
|
||||
task, then return its UPID.
|
||||
8. The response renderer applies the Proxmox envelope, headers, cookies, and
|
||||
version-specific error templates.
|
||||
|
||||
## Persistence and concurrency
|
||||
|
||||
`asyncpg` is used directly. Repositories accept an explicit connection or
|
||||
transaction context; SQL is parameterized and kept near its repository. Mutable
|
||||
process globals are not authoritative state.
|
||||
|
||||
Workers claim tasks using `FOR UPDATE SKIP LOCKED`, establish renewable leases,
|
||||
and use idempotency metadata to recover after process failure. Resource state,
|
||||
resource locks, and task creation are changed in one transaction when required.
|
||||
Optimistic version columns detect concurrent updates, while database constraints
|
||||
protect invariants such as VMID uniqueness within a cluster.
|
||||
|
||||
Application lifespan owns the connection pool and bounded asyncio worker tasks.
|
||||
Shutdown stops claims, lets in-flight work reach a safe boundary, cancels only
|
||||
after a configured grace period, and closes the pool.
|
||||
|
||||
## Contract acquisition and trust
|
||||
|
||||
Network access is confined to explicit import and recorder commands. Importers
|
||||
enforce HTTPS, an official-host allowlist by default, response-size and redirect
|
||||
limits, timeouts, and bounded retries. Every raw artifact is immutable and has a
|
||||
SHA-256 checksum. Its manifest records provenance, version, parser warnings, and
|
||||
normalized checksum. Local snapshots keep startup and tests offline.
|
||||
|
||||
Declared documentation and sanitized observed behavior remain distinct. A
|
||||
compatibility profile chooses `strict-docs`, `observed`, or `hybrid` behavior
|
||||
without scattering version checks through services.
|
||||
|
||||
## Security model
|
||||
|
||||
- Passwords and API-token secrets are stored only as password hashes.
|
||||
- Tickets are signed, short-lived, and redacted from telemetry.
|
||||
- Ticket-authenticated mutations require CSRF validation; API tokens follow the
|
||||
selected Proxmox compatibility profile.
|
||||
- Simulator administration uses a separate prefix and credential and can be
|
||||
disabled completely.
|
||||
- Recorder mode is opt-in, verifies TLS by default, restricts routes and methods,
|
||||
and sanitizes secrets and personal identifiers before writing fixtures.
|
||||
- Containers run as a non-root user and support a read-only root filesystem.
|
||||
|
||||
## Observability
|
||||
|
||||
JSON logs contain request ID, route template, status, duration, safe principal
|
||||
identity, task type, and sanitized resource identifiers. Metrics avoid VMID,
|
||||
UPID, and username labels. OpenTelemetry is optional and has a no-op
|
||||
implementation so tracing is never required for startup.
|
||||
|
||||
## Testing strategy
|
||||
|
||||
Unit tests cover deterministic contract processing and domain rules. Integration
|
||||
tests exercise repositories, transactions, workers, and application lifespan
|
||||
against PostgreSQL. Contract tests traverse imported endpoints and ensure no
|
||||
native FastAPI validation response escapes. Compatibility tests compare golden or
|
||||
live-lab observations after normalizing dynamic values. Concurrency and
|
||||
property-based tests target task leases, state transitions, serialization, and
|
||||
parsers.
|
||||
|
||||
The first vertical release deliberately supports a small set of endpoints with
|
||||
complete stateful semantics. All other imported endpoints remain visibly
|
||||
unsupported until their handlers and compatibility tests exist.
|
||||
|
||||
## Deployment model
|
||||
|
||||
One Uvicorn process runs per container. Horizontal replicas coordinate through
|
||||
PostgreSQL rather than local queues. Database migrations and seed operations are
|
||||
explicit commands and become separate jobs in Kubernetes. PostgreSQL is included
|
||||
in local Docker Compose but is an external dependency in the production chart.
|
||||
|
||||
## Architectural decisions
|
||||
|
||||
1. FastAPI routes are registered from normalized snapshots at startup; hundreds
|
||||
of hand-maintained route declarations are avoided.
|
||||
2. SQLAlchemy is not used. Direct asyncpg repositories keep transaction and
|
||||
concurrency behavior explicit.
|
||||
3. PostgreSQL-backed tasks are the durability boundary; FastAPI background tasks
|
||||
and in-memory queues are not used for critical work.
|
||||
4. Compatibility is capability-driven and versioned, not implemented through
|
||||
scattered version string conditions.
|
||||
5. Unsupported semantics fail honestly by default; schema-derived or proxy
|
||||
responses require an explicit operator mode.
|
||||
Reference in New Issue
Block a user