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:
@@ -106,7 +106,7 @@ class MacosContainerBottleBackend(
|
||||
(`supervise`) call when no control plane is running yet. Mirrors
|
||||
firecracker's infra bring-up."""
|
||||
from .infra import MacosInfraService
|
||||
return MacosInfraService().ensure_running().orchestrator_url
|
||||
return MacosInfraService().ensure_running()
|
||||
|
||||
def prepare_cleanup(self) -> MacosContainerBottleCleanupPlan:
|
||||
return _cleanup.prepare_cleanup()
|
||||
|
||||
@@ -87,10 +87,10 @@ def ensure_gateway(
|
||||
launches share it. Call before starting the agent container: the agent's
|
||||
proxy env needs `gateway_ip` at run time."""
|
||||
service = service or MacosInfraService()
|
||||
infra = service.ensure_running()
|
||||
orchestrator_url = service.ensure_running()
|
||||
endpoint = GatewayEndpoint(
|
||||
orchestrator_url=infra.orchestrator_url,
|
||||
gateway_ip=infra.gateway_ip,
|
||||
orchestrator_url=orchestrator_url,
|
||||
gateway_ip=service.gateway().address(),
|
||||
gateway_ca_pem=service.ca_cert_pem(),
|
||||
network=service.network,
|
||||
)
|
||||
|
||||
@@ -22,7 +22,6 @@ so they have no route to the control plane (the L3 block, not just the JWT).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from ...orchestrator.lifecycle import (
|
||||
@@ -30,6 +29,7 @@ from ...orchestrator.lifecycle import (
|
||||
DEFAULT_STARTUP_TIMEOUT_SECONDS,
|
||||
OrchestratorStartError,
|
||||
)
|
||||
from ..infra_service import InfraService
|
||||
from . import util as container_mod
|
||||
from .gateway import (
|
||||
CONTROL_NETWORK,
|
||||
@@ -59,19 +59,10 @@ INFRA_DB_VOLUME = ORCHESTRATOR_DB_VOLUME
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InfraEndpoint:
|
||||
"""How to reach the running pair. `orchestrator_url` is the orchestrator's
|
||||
control-network address (host CLI + registration); `gateway_ip` is the
|
||||
gateway's agent-network address (proxy / git-http / MCP target)."""
|
||||
|
||||
orchestrator_url: str
|
||||
gateway_ip: str
|
||||
|
||||
|
||||
class MacosInfraService:
|
||||
class MacosInfraService(InfraService):
|
||||
"""Composes the per-host orchestrator + gateway containers. Callers use
|
||||
`ensure_running()` (returns the endpoint) and `ca_cert_pem()`."""
|
||||
`ensure_running()` (returns the control-plane URL), the `orchestrator()` /
|
||||
`gateway()` accessors, and `ca_cert_pem()`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -129,20 +120,12 @@ class MacosInfraService:
|
||||
repo_root=self._repo_root,
|
||||
)
|
||||
|
||||
def _resolve_gateway_ip(self) -> str:
|
||||
"""The gateway's agent-network address, or "" while it has none."""
|
||||
return container_mod.try_container_ipv4_on_network(
|
||||
self._gateway_name, self.network)
|
||||
|
||||
def is_healthy(self) -> bool:
|
||||
return self.orchestrator().is_healthy()
|
||||
|
||||
def ensure_running(
|
||||
self, *, startup_timeout: float = DEFAULT_STARTUP_TIMEOUT_SECONDS,
|
||||
) -> InfraEndpoint:
|
||||
"""Ensure the orchestrator + gateway containers are up; return how to
|
||||
reach them. Idempotent per-host singleton — a healthy orchestrator on
|
||||
current source is left untouched. Raises `OrchestratorStartError` on
|
||||
) -> str:
|
||||
"""Ensure the orchestrator + gateway containers are up; return the host
|
||||
control-plane URL. Idempotent per-host singleton — a healthy orchestrator
|
||||
on current source is left untouched. Raises `OrchestratorStartError` on
|
||||
control-plane startup timeout."""
|
||||
# The networks (host-only agent + NAT egress + host-only control) are a
|
||||
# shared concern — create them before either plane comes up.
|
||||
@@ -162,7 +145,7 @@ class MacosInfraService:
|
||||
# (#469).
|
||||
url = orchestrator.url()
|
||||
gateway.connect_to_orchestrator(url, orchestrator.mint_gateway_token())
|
||||
return InfraEndpoint(orchestrator_url=url, gateway_ip=self._resolve_gateway_ip())
|
||||
return url
|
||||
|
||||
def ca_cert_pem(self, *, timeout: float = DEFAULT_CA_TIMEOUT_SECONDS) -> str:
|
||||
"""The gateway's mitmproxy CA (PEM) agents install to trust its TLS
|
||||
@@ -178,7 +161,6 @@ class MacosInfraService:
|
||||
|
||||
__all__ = [
|
||||
"MacosInfraService",
|
||||
"InfraEndpoint",
|
||||
"OrchestratorStartError",
|
||||
"GatewayError",
|
||||
"ORCHESTRATOR_NAME",
|
||||
|
||||
Reference in New Issue
Block a user