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:
2026-07-25 16:10:21 -04:00
parent cb2d778a8f
commit afb92ca155
19 changed files with 337 additions and 309 deletions
+7 -8
View File
@@ -442,8 +442,7 @@ class TestIsBackendReady(unittest.TestCase):
class TestEnsureOrchestrator(unittest.TestCase):
"""The backend-agnostic orchestrator bring-up entry point. Docker starts
the orchestrator + gateway containers; firecracker boots the infra VM;
macos-container starts the infra container."""
each backend's InfraService brings up its orchestrator + gateway pair."""
def test_docker_delegates_to_infra_service(self):
b = get_bottle_backend("docker")
@@ -457,23 +456,23 @@ class TestEnsureOrchestrator(unittest.TestCase):
self.assertEqual(url, "http://127.0.0.1:8099")
service_cls.return_value.ensure_running.assert_called_once_with()
def test_firecracker_delegates_to_infra_vm(self):
def test_firecracker_delegates_to_infra_service(self):
b = get_bottle_backend("firecracker")
with patch(
"bot_bottle.backend.firecracker.infra_vm.ensure_running"
) as ensure_running:
ensure_running.return_value.orchestrator_url = (
"bot_bottle.backend.firecracker.infra.FirecrackerInfraService"
) as service_cls:
service_cls.return_value.ensure_running.return_value = (
"http://10.243.255.1:8099"
)
url = b.ensure_orchestrator()
self.assertEqual(url, "http://10.243.255.1:8099")
def test_macos_delegates_to_infra_container(self):
def test_macos_delegates_to_infra_service(self):
b = get_bottle_backend("macos-container")
with patch(
"bot_bottle.backend.macos_container.infra.MacosInfraService"
) as service_cls:
service_cls.return_value.ensure_running.return_value.orchestrator_url = (
service_cls.return_value.ensure_running.return_value = (
"http://192.168.128.2:8099"
)
url = b.ensure_orchestrator()