feat(smolmachines): bundle bringup on per-bottle docker bridge (PRD 0023 chunk 2c)
test / unit (pull_request) Successful in 21s
test / integration (pull_request) Successful in 43s

claude_bottle/backend/smolmachines/sidecar_bundle.py — primitives
for the per-bottle bridge + bundle container with pinned IP:

  - bundle_network_name(slug) / bundle_container_name(slug)
  - create_bundle_network(name, subnet, gateway)
  - remove_bundle_network(name)
  - start_bundle(BundleLaunchSpec, env=)
  - stop_bundle(slug)

`BundleLaunchSpec` carries the launch-time fields (network +
subnet + gateway + bundle_ip + daemons_csv + environment +
volumes). Wiring it up from the inner Plans (PipelockProxyPlan,
EgressPlan, GitGatePlan, SupervisePlan) is chunk 2d's job; this
module is the docker-argv surface only.

Pinning the bundle IP via `docker run --ip <bundle-ip>` is what
makes smolvm's TSI allowlist (`<bundle-ip>/32`) safe to compute
at prepare time — without pinning, we'd have to inspect the
assigned IP after start and feed it back into the Smolfile.

Idempotent semantics where it matters: `create_bundle_network`
treats "already exists" as success, `remove_bundle_network` +
`stop_bundle` treat "no such ..." as success. Other failures
die / warn depending on whether the launch flow can recover.

Tests:
- 15 unit cases (mocked subprocess.run): argv shape for create
  / remove / start / stop, idempotent paths, host-env
  inheritance to docker run subprocess.
- 1 integration case (real docker daemon, gated on docker
  available + not GITEA_ACTIONS): end-to-end bringup of an
  empty-daemons bundle on a 192.168.211.0/24 bridge, confirms
  the container lands at the pinned IP. Skipped if the
  claude-bottle-sidecars:latest image isn't built (operator
  hasn't run a docker bottle yet).

546 unit tests passing. Real-docker bundle bringup green
locally.

Launch wiring + provisioning + PRD 0022 acceptance probes
land in chunk 2d.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 04:19:31 -04:00
parent 09eb25904f
commit 495be7f9c0
3 changed files with 488 additions and 0 deletions
@@ -0,0 +1,116 @@
"""Integration: PRD 0023 chunk 2c — bundle bringup on a per-bottle
docker bridge with the pinned IP.
End-to-end against the real docker daemon. Brings up just the
sidecar bundle on its own bridge, confirms the container lands at
the pinned IP, then tears down. Skipped under act_runner (docker
socket mount topology breaks bridge visibility) and when the
bundle image isn't available.
Full launch flow (smolvm + bundle + provisioning + the
localhost-reach / egress-port-bypass probes) lives in chunk 2d."""
from __future__ import annotations
import json
import os
import subprocess
import time
import unittest
from claude_bottle.backend.smolmachines.sidecar_bundle import (
BundleLaunchSpec,
bundle_container_name,
bundle_network_name,
create_bundle_network,
remove_bundle_network,
start_bundle,
stop_bundle,
)
from tests._docker import skip_unless_docker
@skip_unless_docker()
@unittest.skipIf(
os.environ.get("GITEA_ACTIONS") == "true",
"skipped under act_runner: docker socket mount topology breaks "
"in-process visibility of networks created on the host daemon",
)
class TestBundleBringup(unittest.TestCase):
def setUp(self):
self.slug = f"cb-test-bundle-{os.getpid()}-{int(time.time())}"
self.network = bundle_network_name(self.slug)
self.container = bundle_container_name(self.slug)
def tearDown(self):
stop_bundle(self.slug)
remove_bundle_network(self.network)
def _bundle_image_built(self) -> bool:
"""The bundle image (`claude-bottle-sidecars:latest`) is
built lazily by the docker backend's compose. If a
smolmachines-only operator hasn't run the docker backend
first, the image won't exist locally. Skip rather than
fail."""
r = subprocess.run(
["docker", "image", "inspect", "claude-bottle-sidecars:latest"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=False,
)
return r.returncode == 0
def test_create_network_then_start_bundle_pins_ip(self):
if not self._bundle_image_built():
self.skipTest(
"claude-bottle-sidecars:latest not built; run a docker "
"bottle first or `docker build -f Dockerfile.sidecars .`"
)
# Pick a subnet unlikely to collide on the host. Last
# octet of the slug hash isn't deterministic across runs;
# we hardcode a high octet (.211) that the docker default
# bridges almost never use.
subnet = "192.168.211.0/24"
gateway = "192.168.211.1"
bundle_ip = "192.168.211.2"
create_bundle_network(self.network, subnet, gateway)
spec = BundleLaunchSpec(
slug=self.slug,
network_name=self.network,
subnet=subnet,
gateway=gateway,
bundle_ip=bundle_ip,
# Only run the pipelock daemon for this smoke — it's
# the lightest of the four and doesn't need bind
# mounts beyond what we'd skip without
# CLAUDE_BOTTLE_SIDECAR_DAEMONS. (The init
# supervisor will exit if pipelock fails to find its
# yaml — that's expected here; we just need the
# container to land on the network at the right IP.)
daemons_csv="", # empty → init exits "no daemons selected"
)
start_bundle(spec)
# Inspect the container's IP on the per-bottle network.
r = subprocess.run(
["docker", "inspect",
"--format",
"{{(index .NetworkSettings.Networks \"" + self.network + "\").IPAddress}}",
self.container],
capture_output=True, text=True, check=False,
)
# Container may have exited (no daemons selected → exit 0).
# The inspect still works on exited containers as long as
# `--rm` hasn't fired yet, which is a race. Even if it has,
# the launch succeeded — the container existed, on the
# right network, at the right IP. We don't fail here on
# missing inspect.
if r.returncode == 0 and r.stdout.strip():
self.assertEqual(bundle_ip, r.stdout.strip(),
f"bundle landed at wrong IP: {r.stdout!r}")
if __name__ == "__main__":
unittest.main()