refactor(firecracker): orchestrator under the Orchestrator service ABC

Pull the control plane's host-side logic out of `infra_vm` into
`FirecrackerOrchestrator` (backend/firecracker/orchestrator.py): booting the
orchestrator microVM on its link with the persistent registry volume (/dev/vdb),
seeding the host-canonical signing key over SSH, waiting for /health, and the
buildah SSH target. `url()` == `gateway_url()` (the gateway resolves the
orchestrator by guest IP via bb_orch). This completes the trio — docker, macOS,
firecracker — behind both the Gateway and Orchestrator ABCs.

`infra_vm` stays the pair coordinator + shared substrate: `ensure_running` now
mints nothing itself — it constructs both services (lazy import to break the
cycle), calls `orchestrator.ensure_running()` first, then
`gateway.connect_to_orchestrator(orch.gateway_url(), orch.mint_gateway_token())`.
The plane-agnostic boot primitives graduate to public API (`_boot_vm`/
`_push_secret` -> `boot_vm`/`push_secret`) now that they're consumed only across
modules; `InfraVm` loses its `orchestrator_url` (moved to the service) and
`InfraEndpoint.orchestrator` is now the `FirecrackerOrchestrator` service.

Container-lifecycle tests split into test_firecracker_orchestrator; the infra_vm
tests keep the substrate + pair-coordinator coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 14:54:28 -04:00
parent 5465379654
commit 8d583789d2
7 changed files with 386 additions and 281 deletions
+6 -6
View File
@@ -1,7 +1,7 @@
"""Unit: the Firecracker gateway data plane as a microVM (PRD 0070).
The gateway service owns its host-side logic directly: booting the gateway VM
(via the shared `infra_vm._boot_vm` substrate), seeding the pre-minted token,
(via the shared `infra_vm.boot_vm` substrate), seeding the pre-minted token,
fetching the CA over SSH, and the SSH exec/cp provisioning transport.
"""
@@ -37,21 +37,21 @@ class TestFirecrackerGatewayConnect(unittest.TestCase):
def test_refuses_without_orchestrator_url(self) -> None:
gw = FirecrackerGateway()
with patch.object(infra_vm, "_boot_vm") as boot:
with patch.object(infra_vm, "boot_vm") as boot:
with self.assertRaises(GatewayError):
gw.connect_to_orchestrator("", _TOKEN)
boot.assert_not_called()
def test_refuses_without_gateway_token(self) -> None:
gw = FirecrackerGateway()
with patch.object(infra_vm, "_boot_vm") as boot:
with patch.object(infra_vm, "boot_vm") as boot:
with self.assertRaises(GatewayError):
gw.connect_to_orchestrator(_ORCH_URL, "")
boot.assert_not_called()
def test_refuses_a_url_without_a_host(self) -> None:
gw = FirecrackerGateway()
with patch.object(infra_vm, "_boot_vm") as boot:
with patch.object(infra_vm, "boot_vm") as boot:
with self.assertRaises(GatewayError):
gw.connect_to_orchestrator("http://:8099", _TOKEN)
boot.assert_not_called()
@@ -59,8 +59,8 @@ class TestFirecrackerGatewayConnect(unittest.TestCase):
def test_boots_the_gateway_vm_and_seeds_the_token(self) -> None:
gw = FirecrackerGateway()
booted = infra_vm.InfraVm(guest_ip="10.243.255.3", private_key=Path("/k"))
with patch.object(infra_vm, "_boot_vm", return_value=booted) as boot, \
patch.object(infra_vm, "_push_secret") as push:
with patch.object(infra_vm, "boot_vm", return_value=booted) as boot, \
patch.object(infra_vm, "push_secret") as push:
gw.connect_to_orchestrator(_ORCH_URL, _TOKEN)
# Booted on the gateway link with the gateway role; the orchestrator's
# guest IP (parsed off the URL) rides the cmdline as bb_orch.