refactor(smolmachines): decompose launch(), add wait_exec_ready, file-lock allocate() (PRD 0032)

Decompose the 207-line launch() into six named helpers: _allocate_resources,
_mint_certs, _start_bundle, _discover_urls, _launch_vm, _init_vm. Each has
explicit inputs/outputs and is independently testable.

Replace time.sleep(1.5) with smolvm.wait_exec_ready(), which polls
`machine exec true` with exponential backoff. Exits as soon as the exec
channel is ready; dies loudly with a timeout message instead of silently
leaving the VM in an unknown state.

File-lock loopback_alias.allocate() with fcntl.flock(LOCK_EX) so concurrent
bottle launches can't race on docker state and claim the same alias.
This commit is contained in:
2026-06-02 06:23:39 +00:00
parent fe97b6014d
commit 0d922371b0
5 changed files with 326 additions and 193 deletions
+41
View File
@@ -12,6 +12,7 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from bot_bottle.backend.smolmachines import smolvm as smolvm_mod
from bot_bottle.backend.smolmachines.smolvm import (
SmolvmError,
SmolvmRunResult,
@@ -23,6 +24,7 @@ from bot_bottle.backend.smolmachines.smolvm import (
machine_start,
machine_stop,
pack_create,
wait_exec_ready,
)
@@ -204,6 +206,45 @@ class TestErrorPath(unittest.TestCase):
self.assertEqual(SmolvmRunResult(42, "", "nope"), r)
class TestWaitExecReady(unittest.TestCase):
"""wait_exec_ready polls machine_exec(name, ["true"]) until it
returns 0, then exits. On timeout it calls die()."""
def test_returns_immediately_when_exec_succeeds_first_try(self):
with patch.object(smolvm_mod, "machine_exec",
return_value=SmolvmRunResult(0, "", "")) as m:
wait_exec_ready("vm-x")
m.assert_called_once_with("vm-x", ["true"])
def test_retries_on_nonzero_and_returns_on_success(self):
results = [
SmolvmRunResult(1, "", "not ready"),
SmolvmRunResult(1, "", "not ready"),
SmolvmRunResult(0, "", ""),
]
with patch.object(smolvm_mod, "machine_exec",
side_effect=results) as m, \
patch.object(smolvm_mod.time, "sleep"):
wait_exec_ready("vm-x")
self.assertEqual(3, m.call_count)
def test_dies_on_timeout(self):
# machine_exec always returns non-zero; monotonic advances past
# the deadline after the first sleep so the loop exits.
ticks = [0.0, 0.0, 10.0] # third call puts us past deadline
with patch.object(smolvm_mod, "machine_exec",
return_value=SmolvmRunResult(1, "", "")), \
patch.object(smolvm_mod.time, "monotonic",
side_effect=ticks), \
patch.object(smolvm_mod.time, "sleep"), \
patch.object(smolvm_mod, "die",
side_effect=SystemExit("die")) as die_mock:
with self.assertRaises(SystemExit):
wait_exec_ready("vm-x", timeout=5.0)
die_mock.assert_called_once()
self.assertIn("vm-x", die_mock.call_args.args[0])
class TestIsAvailable(unittest.TestCase):
def test_true_when_on_path(self):
with patch(