feat(firecracker): persistent registry volume for the infra VM (Stage B, 6/n)

The infra VM's rootfs is ephemeral (rebuilt each boot), so the bottle
registry DB needs durable storage across restarts. Give the infra VM a
firecracker analogue of a docker volume: a host-side ext4 file attached as
a second virtio-block device (guest /dev/vdb), mounted at the control
plane's DB dir (/var/lib/bot-bottle, where host_db_path lives at
db/bot-bottle.db).

- firecracker_vm.boot/_config take an optional `data_drive` (a non-root,
  RW second drive).
- infra_vm creates the volume on first use (`mke2fs` an empty ext4 at
  <fc-cache>/infra/registry.ext4) and mounts /dev/vdb in the PID-1 init
  before the control plane starts. It's a plain ext4 file, so
  `sudo mount -o loop <path>` (VM stopped) inspects bot-bottle.db directly.

Verified on a KVM host: register a bottle, restart the infra VM (fresh
rootfs, same volume) — /dev/vdb re-mounts and the registry `db/` dir + a
marker file survive the restart.

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 13:23:42 -04:00
parent 957eb19368
commit b93b14f5c2
4 changed files with 97 additions and 8 deletions
+28
View File
@@ -38,6 +38,34 @@ class TestBuildInfraRootfs(unittest.TestCase):
self.assertIn("bot_bottle.orchestrator", init)
self.assertIn("gateway_init.py", init)
self.assertIn("export PATH=", init)
# Persistent registry volume mounted at the DB dir before the CP starts.
self.assertIn("/dev/vdb", init)
class TestRegistryVolume(unittest.TestCase):
def test_reuses_existing_volume(self):
import tempfile
with tempfile.TemporaryDirectory() as td:
vol = Path(td) / "registry.ext4"
vol.write_bytes(b"") # already present
with patch.object(infra_vm, "registry_volume_path", return_value=vol), \
patch.object(infra_vm.subprocess, "run") as run:
out = infra_vm._ensure_registry_volume()
run.assert_not_called() # no mke2fs when it exists
self.assertEqual(vol, out)
def test_creates_volume_when_missing(self):
import tempfile
from subprocess import CompletedProcess
with tempfile.TemporaryDirectory() as td:
vol = Path(td) / "registry.ext4"
with patch.object(infra_vm, "registry_volume_path", return_value=vol), \
patch.object(infra_vm.subprocess, "run",
return_value=CompletedProcess([], 0)) as run:
infra_vm._ensure_registry_volume()
argv = run.call_args.args[0]
self.assertIn("mke2fs", argv)
self.assertIn(str(vol), argv)
class TestEnsureBuilt(unittest.TestCase):