refactor(smolmachines): drop obsolete docker-sidecar helpers
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
"""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 os
|
||||
import subprocess
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from bot_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 (`bot-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", "bot-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(
|
||||
"bot-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,
|
||||
# Empty daemons_csv → init exits "no daemons selected"
|
||||
# immediately. We just need the container to land on
|
||||
# the network at the right IP before it exits.
|
||||
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()
|
||||
@@ -1,10 +1,9 @@
|
||||
"""Integration: PRD 0023 chunk 2d — end-to-end launch + exec
|
||||
round trip + the acceptance probes.
|
||||
"""Integration: end-to-end smolmachines launch + exec round trip.
|
||||
|
||||
The smoke confirms the launch flow (per-bottle docker bridge →
|
||||
sidecar bundle with host-loopback published ports → smolvm guest
|
||||
with TSI allowlist → exec) plumbs together end to end. The probes confirm the
|
||||
security properties the design pivot was about:
|
||||
The smoke confirms the launch flow (sidecar bundle smolVM →
|
||||
host-loopback forwarders → agent smolVM with TSI allowlist → exec)
|
||||
plumbs together end to end. The probes confirm the security
|
||||
properties the design pivot was about:
|
||||
|
||||
- **localhost-reach probe** — guest tries to dial a service
|
||||
bound on the host's `127.0.0.1`. TSI's per-bottle loopback
|
||||
@@ -14,13 +13,6 @@ security properties the design pivot was about:
|
||||
the injected `HTTPS_PROXY`/`HTTP_PROXY` URL on the per-bottle
|
||||
loopback alias, while direct egress with proxy vars unset fails.
|
||||
|
||||
- **egress-port-bypass probe** — guest tries to dial
|
||||
`<bundle-ip>:9099` (egress's port). TSI permits the IP but
|
||||
the bundle's egress daemon binds `127.0.0.1` inside its
|
||||
container, so the connect refuses at the socket level. The
|
||||
bind-address mitigation is what closes TSI's port-granularity
|
||||
gap.
|
||||
|
||||
Gated on macOS/Linux + smolvm + docker + not GITEA_ACTIONS — the
|
||||
runner can't host libkrun-backed VMs."""
|
||||
|
||||
@@ -114,8 +106,8 @@ class TestSmolmachinesLaunch(unittest.TestCase):
|
||||
|
||||
def test_localhost_reach_probe(self):
|
||||
# Agent dials a 127.0.0.1 service on the host. TSI's
|
||||
# allowlist contains only <bundle-ip>/32, so this must
|
||||
# refuse. We use a port unlikely to be bound on the host
|
||||
# allowlist contains only the per-bottle loopback alias, so
|
||||
# this must refuse. We use a port unlikely to be bound on the host
|
||||
# (high-numbered) so we're confirming TSI refusal, not
|
||||
# just "no service listening."
|
||||
r = self.bottle.exec(
|
||||
@@ -196,28 +188,6 @@ class TestSmolmachinesLaunch(unittest.TestCase):
|
||||
self.assertEqual(0, r.returncode, msg=r.stderr)
|
||||
self.assertEqual(_AGENT_PROMPT, r.stdout.rstrip("\n"))
|
||||
|
||||
def test_egress_port_bypass_probe(self):
|
||||
# Agent dials <bundle-ip>:9099 (egress's port). TSI
|
||||
# permits the IP, but egress will bind 127.0.0.1:9099
|
||||
# inside the bundle in chunk 3, so the connect refuses
|
||||
# at the socket level. NOTE: in chunk 2d the bundle's
|
||||
# daemons aren't running (daemons_csv=""), so nothing
|
||||
# is listening on :9099 anyway — this test asserts the
|
||||
# connect fails, which is the property chunk 3 will
|
||||
# preserve once egress is actually running.
|
||||
r = self.bottle.exec(
|
||||
"env -u HTTPS_PROXY -u HTTP_PROXY -u https_proxy -u http_proxy "
|
||||
f"curl -s --show-error --max-time 3 http://{self.plan.bundle_ip}:9099 "
|
||||
"2>&1 || true"
|
||||
)
|
||||
self.assertTrue(
|
||||
"refused" in r.stdout.lower()
|
||||
or "timed out" in r.stdout.lower()
|
||||
or "unreachable" in r.stdout.lower()
|
||||
or "failed" in r.stdout.lower(),
|
||||
f"expected egress port refusal; got: {r.stdout!r}",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
"""Unit: bundle bringup primitives for the smolmachines backend
|
||||
(PRD 0023 chunk 2c).
|
||||
|
||||
Tests mock `subprocess.run` and assert on the docker argv shape.
|
||||
The end-to-end integration smoke (real docker daemon, real
|
||||
bundle image) lands in chunk 2d."""
|
||||
"""Unit: bundle bringup primitives for the smolmachines backend."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -17,28 +12,12 @@ from bot_bottle.backend.smolmachines.sidecar_bundle import (
|
||||
allocate_raw_host_ports,
|
||||
bundle_container_name,
|
||||
bundle_network_name,
|
||||
create_bundle_network,
|
||||
ensure_bundle_image,
|
||||
start_bundle_vm,
|
||||
remove_bundle_network,
|
||||
start_bundle,
|
||||
stop_bundle,
|
||||
stop_bundle_vm,
|
||||
)
|
||||
|
||||
|
||||
def _ok(stdout: str = "", stderr: str = "") -> subprocess.CompletedProcess: # type: ignore
|
||||
return subprocess.CompletedProcess(
|
||||
args=[], returncode=0, stdout=stdout, stderr=stderr,
|
||||
)
|
||||
|
||||
|
||||
def _fail(stderr: str = "boom") -> subprocess.CompletedProcess: # type: ignore
|
||||
return subprocess.CompletedProcess(
|
||||
args=[], returncode=1, stdout="", stderr=stderr,
|
||||
)
|
||||
|
||||
|
||||
def _spec(**kwargs) -> BundleLaunchSpec: # type: ignore
|
||||
defaults = dict(
|
||||
slug="demo-abc12",
|
||||
@@ -71,123 +50,6 @@ class TestNamingHelpers(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestNetworkLifecycle(unittest.TestCase):
|
||||
def _patch_run(self, **kwargs): # type: ignore
|
||||
return patch(
|
||||
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def test_create_argv_explicit_subnet_and_gateway(self):
|
||||
with self._patch_run(return_value=_ok()) as m:
|
||||
create_bundle_network("nn", "192.168.50.0/24", "192.168.50.1")
|
||||
self.assertEqual(
|
||||
["docker", "network", "create",
|
||||
"--subnet", "192.168.50.0/24",
|
||||
"--gateway", "192.168.50.1",
|
||||
"nn"],
|
||||
m.call_args.args[0],
|
||||
)
|
||||
|
||||
def test_create_treats_existing_network_as_success(self):
|
||||
with self._patch_run(return_value=_fail("network nn already exists")):
|
||||
# No SystemExit.
|
||||
create_bundle_network("nn", "192.168.50.0/24", "192.168.50.1")
|
||||
|
||||
def test_create_other_failure_is_fatal(self):
|
||||
with self._patch_run(return_value=_fail("invalid subnet")):
|
||||
with self.assertRaises(SystemExit):
|
||||
create_bundle_network("nn", "bogus", "bogus")
|
||||
|
||||
def test_remove_missing_network_is_idempotent(self):
|
||||
# No SystemExit / no warn-and-continue noise; missing
|
||||
# network is the expected case during a partial teardown.
|
||||
with self._patch_run(return_value=_fail("Error: No such network: nn")):
|
||||
remove_bundle_network("nn")
|
||||
|
||||
def test_remove_clean_returns_success(self):
|
||||
with self._patch_run(return_value=_ok()):
|
||||
remove_bundle_network("nn")
|
||||
|
||||
|
||||
class TestStartBundle(unittest.TestCase):
|
||||
def _patch_run(self):
|
||||
return patch(
|
||||
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
|
||||
return_value=_ok(),
|
||||
)
|
||||
|
||||
def test_argv_pins_ip_on_network(self):
|
||||
with self._patch_run() as m:
|
||||
start_bundle(_spec())
|
||||
argv = m.call_args.args[0]
|
||||
# --network NETNAME --ip <bundle-ip> on the docker run.
|
||||
self.assertIn("--network", argv)
|
||||
self.assertIn("bot-bottle-bundle-demo-abc12", argv)
|
||||
self.assertIn("--ip", argv)
|
||||
self.assertIn("192.168.50.2", argv)
|
||||
# Detached and auto-removed.
|
||||
self.assertIn("--detach", argv)
|
||||
self.assertIn("--rm", argv)
|
||||
# Container name uses the per-slug bundle prefix.
|
||||
i = argv.index("--name")
|
||||
self.assertEqual("bot-bottle-sidecars-demo-abc12", argv[i + 1])
|
||||
# Image at the end.
|
||||
self.assertEqual("bot-bottle-sidecars:latest", argv[-1])
|
||||
|
||||
def test_daemons_env_passed_in(self):
|
||||
with self._patch_run() as m:
|
||||
start_bundle(_spec(daemons_csv="egress,supervise"))
|
||||
argv = m.call_args.args[0]
|
||||
self.assertIn("-e", argv)
|
||||
self.assertIn(
|
||||
"BOT_BOTTLE_SIDECAR_DAEMONS=egress,supervise",
|
||||
argv,
|
||||
)
|
||||
|
||||
def test_environment_entries_pass_through(self):
|
||||
with self._patch_run() as m:
|
||||
start_bundle(_spec(environment=(
|
||||
"SUPERVISE_BOTTLE_SLUG=demo-abc12",
|
||||
"EGRESS_TOKEN_0", # bare-name → host env inherit
|
||||
)))
|
||||
argv = m.call_args.args[0]
|
||||
self.assertIn("SUPERVISE_BOTTLE_SLUG=demo-abc12", argv)
|
||||
self.assertIn("EGRESS_TOKEN_0", argv)
|
||||
|
||||
def test_volumes_render_with_ro_flag(self):
|
||||
with self._patch_run() as m:
|
||||
start_bundle(_spec(volumes=(
|
||||
("/host/egress-ca.pem", "/home/mitmproxy/.mitmproxy/mitmproxy-ca.pem", True),
|
||||
("/host/queue", "/run/supervise/queue", False),
|
||||
)))
|
||||
argv = m.call_args.args[0]
|
||||
self.assertIn(
|
||||
"/host/egress-ca.pem:/home/mitmproxy/.mitmproxy/mitmproxy-ca.pem:ro",
|
||||
argv,
|
||||
)
|
||||
self.assertIn("/host/queue:/run/supervise/queue", argv)
|
||||
|
||||
def test_failure_dies(self):
|
||||
with patch(
|
||||
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
|
||||
return_value=_fail("invalid mount"),
|
||||
):
|
||||
with self.assertRaises(SystemExit):
|
||||
start_bundle(_spec())
|
||||
|
||||
def test_host_env_inherited_to_subprocess(self):
|
||||
# Bare-name entries in spec.environment rely on the docker
|
||||
# subprocess being run with the host env. Confirm `env=`
|
||||
# threads through.
|
||||
with patch(
|
||||
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
|
||||
return_value=_ok(),
|
||||
) as m:
|
||||
start_bundle(_spec(), env={"FOO": "bar"})
|
||||
self.assertEqual({"FOO": "bar"}, m.call_args.kwargs["env"])
|
||||
|
||||
|
||||
class TestEnsureBundleImage(unittest.TestCase):
|
||||
def test_builds_sidecar_dockerfile_before_plain_docker_run(self):
|
||||
with patch(
|
||||
@@ -263,28 +125,6 @@ class TestStartBundleVm(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestStopBundle(unittest.TestCase):
|
||||
def _patch_run(self, **kwargs): # type: ignore
|
||||
return patch(
|
||||
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def test_argv_force_removes(self):
|
||||
with self._patch_run(return_value=_ok()) as m:
|
||||
stop_bundle("demo-abc12")
|
||||
self.assertEqual(
|
||||
["docker", "rm", "-f", "bot-bottle-sidecars-demo-abc12"],
|
||||
m.call_args.args[0],
|
||||
)
|
||||
|
||||
def test_missing_container_is_idempotent(self):
|
||||
with self._patch_run(return_value=_fail(
|
||||
"Error: No such container: bot-bottle-sidecars-demo-abc12"
|
||||
)):
|
||||
stop_bundle("demo-abc12") # no raise
|
||||
|
||||
|
||||
class TestStopBundleVm(unittest.TestCase):
|
||||
def test_stops_then_deletes_sidecar_vm(self):
|
||||
with patch(
|
||||
|
||||
Reference in New Issue
Block a user