refactor(backend): InfraService ABC + FirecrackerInfraService
Add a backend-neutral `InfraService` ABC (backend/infra_service.py) so all three backends present the same composer contract: `orchestrator()` / `gateway()` accessors, `ensure_running(*, startup_timeout) -> str` (the host control-plane URL), `stop()`, plus concrete `url()` / `is_healthy()` that delegate to the orchestrator. `DockerInfraService` and `MacosInfraService` now subclass it. Give firecracker the missing class: `FirecrackerInfraService` (backend/firecracker/infra.py) wraps the `infra_vm` substrate as the pair coordinator (adopt-or-boot-both under the singleton flock). `infra_vm` keeps only the plane-agnostic substrate; its `ensure_running` / `_adopt` / `InfraEndpoint` move to the class, and the coordinator helpers it now calls cross-module go public (`singleton_lock` / `expected_version` / `adoptable` / `record_booted_version`). Unify the return type on the way: `ensure_running` returns the URL string everywhere (was `str` for docker, two different `InfraEndpoint`s for macOS/firecracker), and those `InfraEndpoint`s are deleted — the services are the source of truth (callers read `gateway().address()` / `orchestrator().url()`). docker's `url` property becomes the inherited `url()`. backend.py / consolidated_launch / image_builder + tests updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""The per-host infra service contract (PRD 0070).
|
||||
|
||||
`InfraService` is the backend-neutral composer of the per-host **pair**: the
|
||||
`Orchestrator` (control plane) + `Gateway` (data plane) services, brought up as
|
||||
an idempotent per-host singleton. One concrete impl per backend
|
||||
(`backend/*/infra.py`), mirroring how `Orchestrator` and `Gateway` each have a
|
||||
per-backend impl.
|
||||
|
||||
The two backend-specific accessors (`orchestrator()` / `gateway()`) are the
|
||||
source of truth for how to reach each plane; the convenience `url()` /
|
||||
`is_healthy()` just delegate to the orchestrator.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
|
||||
from ..gateway import Gateway
|
||||
from ..orchestrator.lifecycle import DEFAULT_STARTUP_TIMEOUT_SECONDS, Orchestrator
|
||||
|
||||
|
||||
class InfraService(abc.ABC):
|
||||
"""Compose + bring up the per-host orchestrator + gateway pair.
|
||||
|
||||
Backend-neutral: docker/macOS run the pair as two containers, firecracker as
|
||||
two microVMs. Callers read reach-info off `orchestrator()` / `gateway()`."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def orchestrator(self) -> Orchestrator:
|
||||
"""The control-plane service. Cheap to reconstruct."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def gateway(self) -> Gateway:
|
||||
"""The data-plane service. Cheap to reconstruct."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def ensure_running(
|
||||
self, *, startup_timeout: float = DEFAULT_STARTUP_TIMEOUT_SECONDS,
|
||||
) -> str:
|
||||
"""Bring the orchestrator + gateway pair up (idempotent per-host
|
||||
singleton — a healthy, current pair is left running). Returns the host
|
||||
control-plane URL. Raises `OrchestratorStartError` on control-plane
|
||||
startup timeout."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def stop(self) -> None:
|
||||
"""Remove both the orchestrator and the gateway. Idempotent."""
|
||||
|
||||
def url(self) -> str:
|
||||
"""The host-facing control-plane URL the CLI reaches (the orchestrator's)."""
|
||||
return self.orchestrator().url()
|
||||
|
||||
def is_healthy(self) -> bool:
|
||||
"""True iff the control plane answers `/health`."""
|
||||
return self.orchestrator().is_healthy()
|
||||
|
||||
|
||||
__all__ = ["InfraService"]
|
||||
Reference in New Issue
Block a user