Skip to content

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-LARAVEL into the DDD layout defined in docs/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

  1. Single integration surface: all outbound Virtuozzo calls flow through backend/app/infrastructure/external/virtuozzo/.
  2. 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 accept session/sessionKey from the client.
  3. Async-first: use a shared httpx.AsyncClient to avoid blocking FastAPI workers. Long-running operations must execute in background tasks/worker jobs.
  4. Config driven: URLs and timeouts load from core.config.VirtuozzoSettings with support for legacy env names (EXTERNAL_AUTH_URL, etc.).
  5. Redaction & telemetry: redact secrets in logs, emit structured metrics (virtuozzo_requests_total, virtuozzo_request_latency_seconds) and propagate trace context for observability (see docs/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.py may not import from FastAPI modules. It depends only on settings, models, errors, and http.
  • service.py exposes 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)) -> VirtuozzoService in backend/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

  1. Define VirtuozzoSettings (Pydantic v2, strict) with fields:
  2. base_url, marketplace_url, binder_url, script_url, signin_url.
  3. Timeout/retry knobs: timeout_seconds, connect_timeout, max_retries, retry_backoff_factor.
  4. Implement http.get_async_client(settings) returning a singleton httpx.AsyncClient with:
  5. Default headers (Accept: application/json).
  6. Retry transport (use httpx Retry via httpx.AsyncClient(event_hooks=...) or backoff decorator).
  7. Create errors.py with typed exceptions. Map HTTP status + Virtuozzo result payloads to these classes.
  8. Scaffold models.py with strict Pydantic schemas for:
  9. OperationResult (result, error, message, response).
  10. EnvironmentInfo, NodeInfo, Addon, LogResponse.
  11. Use field_validator to coerce missing fields to defaults.

Phase B — Endpoint Methods

  1. Port each endpoint into an async method on VirtuozzoClient.
  2. Each method must:
  3. Accept typed arguments (no dicts).
  4. Build URLs via urllib.parse.urljoin.
  5. Send requests with await client.get/post(...) and per-call timeout overrides when necessary.
  6. Parse responses with model_validate and raise VirtuozzoApiError on result != 0.
  7. Annotate docstrings with the matching Virtuozzo doc URL section.
  8. Add unit tests in backend/tests/unit/infrastructure/external/test_virtuozzo_client.py using httpx.MockTransport to assert URL formation, retries, and error mapping.

Phase C — Service Layer Orchestration

  1. Implement VirtuozzoService class that:
  2. Accepts VirtuozzoClient, SessionKeyRepository, RedisCache, JobLogger.
  3. Provides higher-level ops (e.g., provision_environment(team_id, payload)) that:
    • Fetch team-scoped session key (Owner-only) → fetch_session_key already exists in backend.
    • Acquire locks for mutative operations (per AUTH-SYSTEM doc).
    • Call the low-level client and persist any resulting metadata (env IDs, addon statuses).
  4. Add instrumentation hooks (metrics + structured logs) here instead of inside routers.

Phase D — Module Integration

  1. For each domain/module, replace legacy logic with service calls:
  2. modules/environments.service.create_environment enqueues a worker job that calls VirtuozzoService.create_environment.
  3. modules/cache.service.toggle_redis calls VirtuozzoService.run_mbadmin_action.
  4. modules/logs.router uses VirtuozzoService.read_log and enforces membership/role permissions.
  5. Routers remain HTTP-only: validate request models, load dependencies via Annotated[...], call service, return response models.
  6. Ensure background jobs (Celery/Dramatiq) use the same service (inject via worker context) to avoid duplicate transport logic.

Phase E — Decommission Legacy Code

  1. Each time a feature is ported, add an entry to CHANGELOG.md under “Removed legacy Virtuozzo call XYZ”.
  2. Delete the corresponding section from OLD-MB-LARAVEL once 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_action to 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

  1. Redaction: implement a helper redact_query(url: str) -> str to remove session= values before logging. Required in every log that references Virtuozzo URLs.
  2. Metrics:
  3. Counter: virtuozzo_requests_total{endpoint, method, status}.
  4. Histogram: virtuozzo_request_latency_seconds{endpoint}.
  5. Counter: virtuozzo_retries_total{endpoint}.
  6. Tracing: wrap all client calls in @instrument_async("virtuzzo.{endpoint}") span; propagate trace headers via httpx headers={"x-trace-id": ...} if supported.
  7. 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.
  8. 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-SYSTEM doc §151-170).
  9. Job logging: centralize JobLog updates in domains/activity/job_logs to avoid duplication from Laravel. Each service receives a JobContext object with status, 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

  1. ✅ Inventory table covers the feature being migrated.
  2. ✅ Client & service methods implemented with doc references and tests.
  3. ✅ Target FastAPI module wired with DI, response models, and permissions.
  4. ✅ Observability hooks in place (metrics, redaction, logs).
  5. ✅ Job logging + background execution validated.
  6. ✅ 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.***