feat(firecracker): run the gateway data plane in the infra VM too (Stage B, 2/n)
lint / lint (push) Successful in 2m6s
test / unit (pull_request) Successful in 1m7s
test / integration (pull_request) Successful in 24s
test / coverage (pull_request) Successful in 1m19s

The single infra VM now runs BOTH the orchestrator control plane and the
gateway data plane (egress / supervise / git-http), multi-tenant against
the local control plane — the single-VM shape from the Stage B design.

- Dockerfile.infra: the firecracker infra image = the gateway image +
  the baked control-plane source (FROM bot-bottle-gateway, COPY
  bot_bottle). Reuses the gateway payload rather than copying mitmproxy/
  gitleaks into a third image; the docker backend keeps its two separate
  images.
- infra_vm: build from source (gateway then infra image), and the PID-1
  init now also launches `gateway_init` with
  BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:8099. Adds `gateway_ca_pem`
  (fetch the mitmproxy CA over SSH) and the agent-facing port constants.
- init exports PATH — a bare-init shell resolves its own execs via a
  built-in default path, but that isn't in the environment, so
  gateway_init's `python3 ...` daemons would otherwise fail to spawn.

Verified on a KVM host: infra VM boots, control plane /health -> 200, and
egress:9099 / supervise:9100 / git-http:9420 all listen and are reachable
from the host over the TAP link; the gateway CA is retrievable. (git-gate
stays down until a bottle provisions its per-bottle entrypoint/creds, same
as a fresh docker gateway — non-fatal, the supervisor keeps the rest up.)

Next: agent->gateway VM-to-VM routing so bbfc* VMs reach these ports at
the infra VM and nowhere else.

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-16 12:47:13 -04:00
parent ebaa999c21
commit 8d7e5956e1
3 changed files with 99 additions and 9 deletions
+15 -3
View File
@@ -30,10 +30,22 @@ class TestBuildInfraRootfs(unittest.TestCase):
build.return_value = Path("/cache/rootfs/x-infra")
infra_vm.build_infra_rootfs_dir()
build.assert_called_once()
self.assertEqual(infra_vm._ORCHESTRATOR_IMAGE, build.call_args.args[0])
self.assertEqual(infra_vm._INFRA_IMAGE, build.call_args.args[0])
self.assertEqual("-infra", build.call_args.kwargs["variant"])
# The init runs the control plane, not the SSH-only agent init.
self.assertIn("bot_bottle.orchestrator", build.call_args.kwargs["init_script"])
# The init runs BOTH the control plane and the gateway data plane,
# and exports PATH so gateway_init's subprocess daemons find python3.
init = build.call_args.kwargs["init_script"]
self.assertIn("bot_bottle.orchestrator", init)
self.assertIn("gateway_init.py", init)
self.assertIn("export PATH=", init)
class TestEnsureBuilt(unittest.TestCase):
def test_builds_gateway_then_infra(self):
with patch.object(infra_vm.docker_mod, "build_image") as build:
infra_vm.ensure_built()
tags = [c.args[0] for c in build.call_args_list]
self.assertEqual([infra_vm._GATEWAY_IMAGE, infra_vm._INFRA_IMAGE], tags)
class TestWaitForHealth(unittest.TestCase):