feat: run smolmachines sidecar as vm
lint / lint (push) Successful in 2m9s
test / unit (pull_request) Successful in 58s
test / integration (pull_request) Successful in 20s
test / coverage (pull_request) Failing after 1m9s

This commit is contained in:
2026-07-09 16:10:42 +00:00
parent 11c2db1ee5
commit 1a2d46ee56
12 changed files with 739 additions and 113 deletions
@@ -0,0 +1,58 @@
from __future__ import annotations
import unittest
from unittest.mock import patch
from bot_bottle.backend.egress_apply import EgressApplyError
from bot_bottle.backend.smolmachines import egress_apply
from bot_bottle.backend.smolmachines.smolvm import SmolvmRunResult
class TestFetchCurrentRoutes(unittest.TestCase):
def test_reads_routes_from_sidecar_vm(self):
with patch.object(
egress_apply._smolvm,
"machine_exec",
return_value=SmolvmRunResult(0, "routes", ""),
) as exec_:
self.assertEqual("routes", egress_apply.fetch_current_routes("dev-abc"))
exec_.assert_called_once_with(
"bot-bottle-sidecars-dev-abc",
["cat", "/etc/egress/routes.yaml"],
)
def test_read_failure_raises_apply_error(self):
with patch.object(
egress_apply._smolvm,
"machine_exec",
return_value=SmolvmRunResult(1, "", "nope"),
):
with self.assertRaises(EgressApplyError):
egress_apply.fetch_current_routes("dev-abc")
class TestReload(unittest.TestCase):
def test_sends_hup_to_sidecar_init(self):
with patch.object(
egress_apply._smolvm,
"machine_exec",
return_value=SmolvmRunResult(0, "", ""),
) as exec_:
egress_apply.SmolmachinesEgressApplicator()._signal_bundle_reload("dev-abc")
exec_.assert_called_once_with(
"bot-bottle-sidecars-dev-abc",
["sh", "-c", "kill -HUP 1"],
)
def test_reload_failure_raises_apply_error(self):
with patch.object(
egress_apply._smolvm,
"machine_exec",
return_value=SmolvmRunResult(1, "", "nope"),
):
with self.assertRaises(EgressApplyError):
egress_apply.SmolmachinesEgressApplicator()._signal_bundle_reload("dev-abc")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,98 @@
from __future__ import annotations
import socket
import threading
import unittest
from bot_bottle.backend.smolmachines.port_forward import (
ForwardSpec,
start_forwarder,
stop_forwarder,
)
def _echo_server() -> tuple[socket.socket, int]:
listener = socket.create_server(("127.0.0.1", 0))
port = int(listener.getsockname()[1])
def run() -> None:
with listener:
conn, _ = listener.accept()
with conn:
while True:
data = conn.recv(65536)
if not data:
return
conn.sendall(data)
threading.Thread(target=run, daemon=True).start()
return listener, port
class TestForwarderValidation(unittest.TestCase):
def test_rejects_generic_localhost_listener(self):
with self.assertRaises(SystemExit):
start_forwarder((
ForwardSpec(
label="egress",
listen_host="127.0.0.1",
listen_port=0,
target_host="127.0.0.1",
target_port=9099,
),
))
def test_rejects_wildcard_listener(self):
with self.assertRaises(SystemExit):
start_forwarder((
ForwardSpec(
label="egress",
listen_host="0.0.0.0",
listen_port=0,
target_host="127.0.0.1",
target_port=9099,
),
))
def test_rejects_non_loopback_target(self):
with self.assertRaises(SystemExit):
start_forwarder((
ForwardSpec(
label="egress",
listen_host="127.0.0.16",
listen_port=0,
target_host="192.0.2.10",
target_port=9099,
),
))
class TestForwarding(unittest.TestCase):
def test_forwards_bytes_both_directions(self):
try:
probe = socket.create_server(("127.0.0.16", 0))
except OSError:
self.skipTest("127.0.0.16 is not bound on this host")
else:
probe.close()
_listener, target_port = _echo_server()
handle = start_forwarder((
ForwardSpec(
label="egress",
listen_host="127.0.0.16",
listen_port=0,
target_host="127.0.0.1",
target_port=target_port,
),
))
try:
bound = handle.forwards[0]
with socket.create_connection((bound.listen_host, bound.listen_port)) as sock:
sock.sendall(b"hello")
self.assertEqual(b"hello", sock.recv(5))
finally:
stop_forwarder(handle)
if __name__ == "__main__":
unittest.main()
+15 -24
View File
@@ -432,12 +432,7 @@ class TestBundleLaunchSpec(unittest.TestCase):
def test_canary_env_visible_to_smolvm_guest(self):
plan = _plan(canary=True)
with patch.object(
_launch._bundle,
"bundle_host_port",
return_value="65000",
):
stamped = _launch._discover_urls(plan, "127.0.0.16")
stamped = _launch._discover_urls(plan, "127.0.0.16", {9099: 65000})
self.assertEqual(
"fake-canary-value",
@@ -507,21 +502,11 @@ class TestProvisionGitUser(unittest.TestCase):
class TestProxyHost(unittest.TestCase):
"""_proxy_host returns the bridge gateway on Linux and the loopback
alias on other platforms."""
"""_proxy_host returns the per-bottle address used by the forwarder."""
def test_linux_returns_bundle_gateway(self):
def test_returns_loopback_alias(self):
plan = _plan()
with patch("bot_bottle.backend.smolmachines.launch.platform.system",
return_value="Linux"):
result = _launch._proxy_host(plan, "127.0.0.16")
self.assertEqual(plan.bundle_gateway, result)
def test_non_linux_returns_loopback(self):
plan = _plan()
with patch("bot_bottle.backend.smolmachines.launch.platform.system",
return_value="Darwin"):
result = _launch._proxy_host(plan, "127.0.0.16")
result = _launch._proxy_host(plan, "127.0.0.16")
self.assertEqual("127.0.0.16", result)
@@ -544,14 +529,20 @@ class TestDiscoverUrls(unittest.TestCase):
),),
),
)
with patch.object(_launch._bundle, "bundle_host_port", return_value="9420"):
stamped = _launch._discover_urls(plan, "127.0.0.16")
self.assertEqual("127.0.0.16:9420", stamped.agent_git_gate_host)
stamped = _launch._discover_urls(
plan,
"127.0.0.16",
{9099: 65000, 9420: 65001},
)
self.assertEqual("127.0.0.16:65001", stamped.agent_git_gate_host)
def test_supervise_url_set_when_supervise_present(self):
plan = _plan(supervise=True)
with patch.object(_launch._bundle, "bundle_host_port", return_value="55556"):
stamped = _launch._discover_urls(plan, "127.0.0.16")
stamped = _launch._discover_urls(
plan,
"127.0.0.16",
{9099: 65000, 9100: 55556},
)
self.assertEqual("http://127.0.0.16:55556/", stamped.agent_supervise_url)
@@ -18,9 +18,11 @@ from bot_bottle.backend.smolmachines.sidecar_bundle import (
bundle_network_name,
create_bundle_network,
ensure_bundle_image,
start_bundle_vm,
remove_bundle_network,
start_bundle,
stop_bundle,
stop_bundle_vm,
)
@@ -200,6 +202,57 @@ class TestEnsureBundleImage(unittest.TestCase):
self.assertEqual("Dockerfile.sidecars", kwargs["dockerfile"])
class TestStartBundleVm(unittest.TestCase):
def test_creates_sidecar_vm_with_ports_volumes_and_env(self):
calls: list[tuple[str, tuple[object, ...], dict[str, object]]] = []
def record(name): # type: ignore
def inner(*args, **kwargs): # type: ignore
calls.append((name, args, kwargs))
return inner
with patch(
"bot_bottle.backend.smolmachines.sidecar_bundle.allocate_raw_host_ports",
return_value={9099: 55001, 9420: 55002},
), patch(
"bot_bottle.backend.smolmachines.sidecar_bundle._smolvm.machine_create",
side_effect=record("create"),
), patch(
"bot_bottle.backend.smolmachines.sidecar_bundle._smolvm.machine_start",
side_effect=record("start"),
), patch(
"bot_bottle.backend.smolmachines.sidecar_bundle._smolvm.wait_exec_ready",
side_effect=record("wait"),
):
launch = start_bundle_vm(
_spec(
daemons_csv="egress,git-http",
environment=("FOO=bar", "TOKEN"),
volumes=(("/host/routes", "/etc/egress", True),),
ports_to_publish=(9099, 9420),
),
from_path=Path("/cache/sidecar.smolmachine"),
host_env={"TOKEN": "secret"},
)
self.assertEqual({9099: 55001, 9420: 55002}, launch.raw_ports)
create = calls[0]
self.assertEqual("create", create[0])
self.assertEqual(("bot-bottle-sidecars-demo-abc12",), create[1])
self.assertEqual(Path("/cache/sidecar.smolmachine"), create[2]["from_path"])
self.assertTrue(create[2]["net"])
self.assertEqual(((55001, 9099), (55002, 9420)), create[2]["ports"])
self.assertEqual((("/host/routes", "/etc/egress", True),), create[2]["volumes"])
self.assertEqual(
{
"BOT_BOTTLE_SIDECAR_DAEMONS": "egress,git-http",
"FOO": "bar",
"TOKEN": "secret",
},
create[2]["env"],
)
class TestStopBundle(unittest.TestCase):
def _patch_run(self, **kwargs): # type: ignore
return patch(
@@ -222,5 +275,17 @@ class TestStopBundle(unittest.TestCase):
stop_bundle("demo-abc12") # no raise
class TestStopBundleVm(unittest.TestCase):
def test_stops_then_deletes_sidecar_vm(self):
with patch(
"bot_bottle.backend.smolmachines.sidecar_bundle._smolvm.machine_stop",
) as stop, patch(
"bot_bottle.backend.smolmachines.sidecar_bundle._smolvm.machine_delete",
) as delete:
stop_bundle_vm("demo-abc12")
stop.assert_called_once_with("bot-bottle-sidecars-demo-abc12")
delete.assert_called_once_with("bot-bottle-sidecars-demo-abc12")
if __name__ == "__main__":
unittest.main()
+16
View File
@@ -106,6 +106,22 @@ class TestArgvShapes(unittest.TestCase):
self.assertIn("--name", argv)
self.assertIn("agent-xyz", argv)
def test_machine_create_with_net_ports_and_volumes(self):
with self._patch_run() as m:
machine_create(
"sidecar-xyz",
from_path=Path("/stage/sidecar.smolmachine"),
net=True,
ports=((55000, 9099),),
volumes=(("/host/routes", "/etc/egress", True),),
)
argv = m.call_args.args[0]
self.assertIn("--net", argv)
self.assertIn("-p", argv)
self.assertIn("55000:9099", argv)
self.assertIn("-v", argv)
self.assertIn("/host/routes:/etc/egress:ro", argv)
def test_machine_create_omits_net_when_no_allow_cidrs(self):
with self._patch_run() as m:
machine_create("agent-xyz", from_path=Path("/x.smolmachine"))