firecracker status(): open /dev/kvm O_RDWR; check kernel, dropbear, mke2fs
test / integration-docker (pull_request) Successful in 19s
tracker-policy-pr / check-pr (pull_request) Successful in 19s
test / unit (pull_request) Successful in 40s
lint / lint (push) Successful in 58s
test / integration-firecracker (pull_request) Successful in 3m20s
test / coverage (pull_request) Successful in 22s
test / publish-infra (pull_request) Has been skipped

Two reviewer findings addressed:

1. _kvm_accessible() now opens /dev/kvm with O_RDWR|O_CLOEXEC instead
   of read-only. VM creation requires write access; a read-only
   descriptor can satisfy KVM_GET_API_VERSION but fails at boot time.

2. status() now checks every hard prerequisite that require_firecracker()
   checks at launch: guest kernel image, static dropbear binary, and
   mke2fs. Previously a host with a configured TAP pool but missing
   artifacts could pass status() and then fail during launch.

Regression coverage: TestFirecrackerKvmCheck gains test_kvm_open_rdwr_fails;
TestFirecrackerArtifactCheck covers each missing artifact; existing
TestFirecrackerStatus fixtures updated to stub the new checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 16:03:51 +00:00
parent 755a11a608
commit 65a49a239e
3 changed files with 127 additions and 19 deletions
+28 -4
View File
@@ -132,32 +132,56 @@ class TestFirecrackerStatus(unittest.TestCase):
rc = fc_setup.status()
return rc, buf.getvalue()
def _stub_artifacts(self, fc_setup: object) -> tuple[MagicMock, MagicMock]:
k: MagicMock = MagicMock()
k.is_file.return_value = True
d: MagicMock = MagicMock()
d.is_file.return_value = True
return k, d
def test_ready_when_taps_present_even_if_nft_unverifiable(self):
from bot_bottle.backend.firecracker import setup as fc_setup
def _which(cmd: str) -> str | None:
return None if cmd == "nft" else f"/usr/bin/{cmd}"
k, d = self._stub_artifacts(fc_setup)
with patch.object(fc_setup.netpool, "missing_taps", return_value=[]), \
patch.object(fc_setup.netpool, "overlapping_routes", return_value=[]), \
patch.object(fc_setup.shutil, "which", return_value=None), \
patch.object(fc_setup.shutil, "which", side_effect=_which), \
patch.object(fc_setup, "_firecracker_binary_ok", return_value=True), \
patch.object(fc_setup, "_kvm_accessible", return_value=True):
patch.object(fc_setup, "_kvm_accessible", return_value=True), \
patch.object(fc_setup.util, "kernel_path", return_value=k), \
patch.object(fc_setup.util, "dropbear_path", return_value=d):
rc, out = self._run()
self.assertEqual(0, rc)
self.assertIn("unverified", out)
def test_not_ready_when_taps_missing(self):
from bot_bottle.backend.firecracker import setup as fc_setup
k, d = self._stub_artifacts(fc_setup)
with patch.object(fc_setup.netpool, "missing_taps", return_value=["bbfc0"]), \
patch.object(fc_setup.netpool, "overlapping_routes", return_value=[]), \
patch.object(fc_setup.shutil, "which", return_value=None):
patch.object(fc_setup.shutil, "which", return_value=None), \
patch.object(fc_setup, "_firecracker_binary_ok", return_value=True), \
patch.object(fc_setup, "_kvm_accessible", return_value=True), \
patch.object(fc_setup.util, "kernel_path", return_value=k), \
patch.object(fc_setup.util, "dropbear_path", return_value=d):
rc, _ = self._run()
self.assertEqual(1, rc)
def test_not_ready_on_range_overlap(self):
from bot_bottle.backend.firecracker import netpool
from bot_bottle.backend.firecracker import setup as fc_setup
k, d = self._stub_artifacts(fc_setup)
conflict = netpool.RouteConflict(dst="10.243.0.0/24", dev="eth0")
with patch.object(fc_setup.netpool, "missing_taps", return_value=[]), \
patch.object(fc_setup.netpool, "overlapping_routes", return_value=[conflict]), \
patch.object(fc_setup.shutil, "which", return_value=None):
patch.object(fc_setup.shutil, "which", return_value=None), \
patch.object(fc_setup, "_firecracker_binary_ok", return_value=True), \
patch.object(fc_setup, "_kvm_accessible", return_value=True), \
patch.object(fc_setup.util, "kernel_path", return_value=k), \
patch.object(fc_setup.util, "dropbear_path", return_value=d):
rc, out = self._run()
self.assertEqual(1, rc)
self.assertIn("CLASHES", out)