Virtuozzo API Centralization & Migration Guide¶
Authoritative plan for consolidating every Virtuozzo/Mightybox platform call inside the FastAPI codebase. Follow this document when porting logic from
OLD-MB-LARAVELinto the DDD layout defined indocs/architecture/001-hybrid-modular-ddd.md.
0. Official References¶
| Area | Source |
|---|---|
| Virtuozzo REST & Script APIs | https://docs.jelastic.com/api/ |
| Virtuozzo Automator XML API (legacy) | https://support.virtuozzo.com/hc/en-us/articles/6403356380561 |
| Logging & secret handling | OWASP Logging Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html |
Do not rely on undocumented endpoints. Every client method must cite the specific Virtuozzo reference in code comments.
1. Goals & Non-Negotiable Constraints¶
- Single integration surface: all outbound Virtuozzo calls flow through
backend/app/infrastructure/external/virtuozzo/. - Owner-only custody: session keys are issued, cached, and refreshed exclusively on the server (see
docs/architecture/AUTH-SYSTEM/team_management_auth_rbac.md). No router may acceptsession/sessionKeyfrom the client. - Async-first: use a shared
httpx.AsyncClientto avoid blocking FastAPI workers. Long-running operations must execute in background tasks/worker jobs. - Config driven: URLs and timeouts load from
core.config.VirtuozzoSettingswith support for legacy env names (EXTERNAL_AUTH_URL, etc.). - Redaction & telemetry: redact secrets in logs, emit structured metrics (
virtuozzo_requests_total,virtuozzo_request_latency_seconds) and propagate trace context for observability (seedocs/development/OBSERVABILITY-CHANGES-SUMMARY.md).
2. Target Package Layout¶
backend/app/infrastructure/external/virtuozzo/
├── __init__.py
├── settings.py # VirtuozzoSettings (Pydantic v2) with env aliases
├── enums.py # Endpoint identifiers, action allow-lists
├── models.py # Pydantic schemas for Virtuozzo responses/requests
├── errors.py # VirtuozzoAuthError, VirtuozzoApiError, VirtuozzoTimeout
├── http.py # Shared AsyncClient factory + retry/backoff helpers
├── client.py # Low-level endpoint methods (pure transport)
├── mbadmin.py # Helpers for marketplace/installation actions & params
└── service.py # Higher-level orchestration (session fetch, addon install, env lifecycle)
Key rules:
client.pymay not import from FastAPI modules. It depends only onsettings,models,errors, andhttp.service.pyexposes composable coroutines (e.g.install_addon(...),fetch_env_info(...)) used by domain modules (app/modules/environments,app/modules/cache, etc.).- Dependency injection: expose a FastAPI dependency
get_virtuozzo_service(settings: Settings = Depends(get_settings)) -> VirtuozzoServiceinbackend/app/core/dependencies.py.
3. Legacy Surface Inventory → New Owner¶
| Legacy Source | Virtuozzo Endpoint | Purpose | New Client Method | New Consumer |
|---|---|---|---|---|
ApiController::createEnvironment |
environment/control/rest/createenvironment |
Provision env + nodes | VirtuozzoClient.create_environment(payload) |
modules/environments.service.create_environment (job) |
ApiController::mbAdminManager |
marketplace/installation/rest/executeaction |
Cache/relay/opcache toggles | MbAdminExecutor.execute_action(app_unique_name, action) |
modules/cache.service, modules/relay.service |
MbAdminParamsService::handle |
.../executeaction with params |
Parameterized actions | MbAdminExecutor.execute_action_with_params(...) |
Same as above (parametrized ops) |
ApiController::installMBAdmin |
marketplace/jps/rest/install |
Install mbadmin JPS | VirtuozzoClient.install_jps(app_id, jps_url, node_group) |
modules/environments.service.install_mbadmin |
ApiController::installRootAccess / installAddSftp / installLetsEncryptSSL |
marketplace/app/rest/installaddon |
Addon installs | VirtuozzoClient.install_addon(addon_id, settings) |
modules/environments.addons_service |
ApiController::getAppIdByEnvName, getNodeIdByMission, getNodeIds, updateSingleEnvironment, VirtuozzoService::fetchSingleEnvironment |
environment/control/rest/getenvinfo |
Metadata sync | VirtuozzoClient.get_env_info(env_name) |
domains/environments.repository, modules/nodes.service |
VirtuozzoService::fetchEnvironmentsAndNodes |
environment/control/rest/getenvs |
Bulk listing | VirtuozzoClient.list_envs(session_key) |
modules/environments.listing_service |
ApiController::createMountpoint |
environment/file/rest/addmountpointbyid |
Shared storage mount | VirtuozzoClient.add_mountpoint(...) |
modules/storage.service |
ApiController::restartNode, restartEnvironmentServices |
environment/control/rest/restartservices |
Node/service restart | VirtuozzoClient.restart_services(...) |
modules/environments.service.restart |
ApiController::readLog, checkCachedLogs |
environment/control/rest/readlog |
Remote log fetch | VirtuozzoClient.read_log(...) |
modules/logs.service |
VirtuozzoService::cloneEnvironment |
environment/control/rest/cloneenv |
Clone env | VirtuozzoClient.clone_environment(...) |
modules/staging.service |
VirtuozzoService::attachEnvGroup |
environment/control/rest/attachenvgroup |
Tag env groups | VirtuozzoClient.attach_env_group(...) |
modules/environments.service.tag_group |
VirtuozzoService::executeSearchReplace |
script/rest/eval |
Remote script exec (WP-CLI) | VirtuozzoClient.run_script(env_name, script, node_group='cp') |
modules/wordpress.service |
VirtuozzoService::bindExtDomain |
environment/binder/rest/bindextdomain |
Domain binding | VirtuozzoClient.bind_external_domain(...) |
modules/domains.service |
Any legacy endpoint not listed must be added to this table before implementation begins.
4. Step-by-Step Implementation Plan¶
Phase A — Configuration & Client Skeleton¶
- Define
VirtuozzoSettings(Pydantic v2, strict) with fields: base_url,marketplace_url,binder_url,script_url,signin_url.- Timeout/retry knobs:
timeout_seconds,connect_timeout,max_retries,retry_backoff_factor. - Implement
http.get_async_client(settings)returning a singletonhttpx.AsyncClientwith: - Default headers (
Accept: application/json). - Retry transport (use httpx
Retryviahttpx.AsyncClient(event_hooks=...)orbackoffdecorator). - Create
errors.pywith typed exceptions. Map HTTP status + Virtuozzoresultpayloads to these classes. - Scaffold
models.pywith strict Pydantic schemas for: OperationResult(result, error, message, response).EnvironmentInfo,NodeInfo,Addon,LogResponse.- Use
field_validatorto coerce missing fields to defaults.
Phase B — Endpoint Methods¶
- Port each endpoint into an async method on
VirtuozzoClient. - Each method must:
- Accept typed arguments (no dicts).
- Build URLs via
urllib.parse.urljoin. - Send requests with
await client.get/post(...)and per-call timeout overrides when necessary. - Parse responses with
model_validateand raiseVirtuozzoApiErroronresult != 0. - Annotate docstrings with the matching Virtuozzo doc URL section.
- Add unit tests in
backend/tests/unit/infrastructure/external/test_virtuozzo_client.pyusinghttpx.MockTransportto assert URL formation, retries, and error mapping.
Phase C — Service Layer Orchestration¶
- Implement
VirtuozzoServiceclass that: - Accepts
VirtuozzoClient,SessionKeyRepository,RedisCache,JobLogger. - Provides higher-level ops (e.g.,
provision_environment(team_id, payload)) that:- Fetch team-scoped session key (Owner-only) →
fetch_session_keyalready exists in backend. - Acquire locks for mutative operations (per
AUTH-SYSTEMdoc). - Call the low-level client and persist any resulting metadata (env IDs, addon statuses).
- Fetch team-scoped session key (Owner-only) →
- Add instrumentation hooks (metrics + structured logs) here instead of inside routers.
Phase D — Module Integration¶
- For each domain/module, replace legacy logic with service calls:
modules/environments.service.create_environmentenqueues a worker job that callsVirtuozzoService.create_environment.modules/cache.service.toggle_rediscallsVirtuozzoService.run_mbadmin_action.modules/logs.routerusesVirtuozzoService.read_logand enforces membership/role permissions.- Routers remain HTTP-only: validate request models, load dependencies via
Annotated[...], call service, return response models. - Ensure background jobs (Celery/Dramatiq) use the same service (inject via worker context) to avoid duplicate transport logic.
Phase E — Decommission Legacy Code¶
- Each time a feature is ported, add an entry to
CHANGELOG.mdunder “Removed legacy Virtuozzo call XYZ”. - Delete the corresponding section from
OLD-MB-LARAVELonce tests pass.
5. Dependency Injection & Usage Patterns¶
FastAPI Dependency¶
# backend/app/core/dependencies.py
from typing import Annotated
from fastapi import Depends
from app.core.config import Settings
from app.infrastructure.external.virtuozzo.service import VirtuozzoService
async def get_virtuozzo_service(
settings: Annotated[Settings, Depends(get_settings)]
) -> VirtuozzoService:
return VirtuozzoService.from_settings(settings)
Router Usage Example¶
@router.post(
"/teams/{team_id}/virtuozzo/addons/{addon_id}",
response_model=VirtuozzoAddonInstallResponse,
dependencies=[Depends(require_owner_role)],
)
async def install_addon(
team_id: UUID,
addon_id: VirtuozzoAddon,
payload: VirtuozzoAddonInstallRequest,
service: Annotated[VirtuozzoService, Depends(get_virtuozzo_service)],
job_logger: Annotated[JobLogger, Depends(get_job_logger)],
) -> VirtuozzoAddonInstallResponse:
job = await service.install_addon(team_id=team_id, addon_id=addon_id, payload=payload, job_logger=job_logger)
return job.to_response()
Guidelines:
- Never expose Virtuozzo session keys, env IDs, or addon unique names to the client.
- Use typed enums for
addon_id,mbadmin_actionto prevent arbitrary execution. - When a service needs to run on a worker, pass the same dependency via the job payload or re-resolve via DI inside the worker entrypoint.
6. Observability, Security & Resilience¶
- Redaction: implement a helper
redact_query(url: str) -> strto removesession=values before logging. Required in every log that references Virtuozzo URLs. - Metrics:
- Counter:
virtuozzo_requests_total{endpoint, method, status}. - Histogram:
virtuozzo_request_latency_seconds{endpoint}. - Counter:
virtuozzo_retries_total{endpoint}. - Tracing: wrap all client calls in
@instrument_async("virtuzzo.{endpoint}")span; propagate trace headers via httpxheaders={"x-trace-id": ...}if supported. - Retries/backoff: default to 2 retries with exponential backoff for idempotent GETs. For mutating endpoints, retry only on transport errors/timeouts when Virtuozzo docs confirm idempotency.
- Locking: for operations that mutate env state (create/clone/install addon), acquire a per-team Redis lock to prevent concurrent requests from different users (per
AUTH-SYSTEMdoc §151-170). - Job logging: centralize JobLog updates in
domains/activity/job_logsto avoid duplication from Laravel. Each service receives aJobContextobject withstatus,message,progress.
7. Testing & Verification Matrix¶
| Layer | Tests | Notes |
|---|---|---|
client.py |
Unit tests with mocked httpx responses | Verify URL, timeout, retries, error mapping. |
service.py |
Unit tests with fake client + fake repositories | Ensure session retrieval, locking, job logging, state persistence. |
| Modules/Routers | Integration tests using FastAPI test client + dependency overrides | Mock VirtuozzoService to assert HTTP contracts and permission enforcement. |
| Background jobs | Worker tests invoking real service with mocked client | Validate job state transitions and telemetry. |
| Smoke/E2E | Existing /backend/tests/smoke/auth harness with Virtuozzo mock server |
Confirm owner registration + provisioning flows. |
Before removing each legacy PHP endpoint:
1. pytest -q backend/tests/unit/infrastructure/external/test_virtuozzo_client.py
2. pytest -q backend/tests/integration/modules/<feature> (once created)
3. mypy backend/app/infrastructure/external/virtuozzo
4. ruff check backend/app/infrastructure/external/virtuozzo
5. Update docs/CHANGELOG.md.
8. Rollout Checklist¶
- ✅ Inventory table covers the feature being migrated.
- ✅ Client & service methods implemented with doc references and tests.
- ✅ Target FastAPI module wired with DI, response models, and permissions.
- ✅ Observability hooks in place (metrics, redaction, logs).
- ✅ Job logging + background execution validated.
- ✅ Legacy Laravel code path disabled or removed.
Follow this document rigorously to avoid fragmented Virtuozzo integrations and to keep the FastAPI system compliant with the architecture contract. When new Virtuozzo endpoints are required, add them to Section 3 first, then implement following Sections 4–7.***