feat(orchestrator): slice 4 — consolidated per-host sidecar (#352)

The core consolidation win: one persistent sidecar per host, shared by
every bottle, instead of a sidecar bundle per bottle. Safe to share
because the attribution invariant (source IP + identity token) lets the
sidecar map each request to the right bottle.

  * orchestrator/sidecar.py — a backend-neutral `Sidecar` lifecycle
    contract (mirrors LaunchBroker) + a `DockerSidecar` impl. The defining
    behaviour is idempotent singleton: `ensure_running` starts the instance
    if absent and is a no-op if it's already up, so N launches never spawn
    N sidecars; `stop` is idempotent.
  * orchestrator/dockerutil.py — a shared `run_docker` helper; DockerBroker
    now uses it too (DRY with slice 3).
  * service.py — the Orchestrator holds an optional `Sidecar`, exposes
    `ensure_sidecar()` + `sidecar_status()`.
  * control_plane.py — `GET /sidecar` reports it; __main__ gains
    `--sidecar-image` and ensures the single sidecar on startup.

Tests: unit (docker mocked) — is_running, ensure idempotent (no-op when up,
starts when absent), failure raises, stop idempotent; Orchestrator sidecar
wiring/status; control-plane /sidecar; integration (gated) — ensure is a
real idempotent singleton (one container after two ensures), stop removes.
Full suite green (only pre-existing /bin/sleep errors); integration
verified locally against real docker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
2026-07-13 15:28:29 -04:00
parent b70f3228ca
commit bc52c3525c
11 changed files with 331 additions and 9 deletions
+11 -2
View File
@@ -10,10 +10,15 @@ backend-neutral "consolidation core" that needs no VM packaging:
* `broker` — the signed, structured launch-request contract + a
`LaunchBroker` (stub for the harness) that verifies
provenance before acting.
* `sidecar` — the consolidated per-host sidecar: a `Sidecar`
lifecycle contract (idempotent singleton) + a
`DockerSidecar` impl. One sidecar shared by all
bottles instead of one per bottle.
* `service` — the `Orchestrator`: owns the registry, brokers the
launch lifecycle (launch/teardown), attributes.
launch lifecycle (launch/teardown), manages the
shared sidecar, attributes.
* `control_plane` — the HTTP control-plane RPC (launch / teardown /
list / attribute / health).
list / attribute / sidecar / health).
The actual backend-native launch (a real docker/firecracker broker) and
the egress/git/supervise data plane land in later slices once this core is
@@ -33,6 +38,7 @@ from .broker import (
verify_request,
)
from .docker_broker import DockerBroker, DockerBrokerError
from .sidecar import DockerSidecar, Sidecar, SidecarError
from .service import Orchestrator
from .control_plane import ControlPlaneServer, dispatch, make_server
@@ -46,6 +52,9 @@ __all__ = [
"StubBroker",
"DockerBroker",
"DockerBrokerError",
"Sidecar",
"DockerSidecar",
"SidecarError",
"sign_request",
"verify_request",
"Orchestrator",