Merge remote-tracking branch 'origin/fix/db-off-data-plane-469' into fix/db-off-data-plane-469
tracker-policy-pr / check-pr (pull_request) Successful in 9s
test / integration-docker (pull_request) Successful in 16s
test / unit (pull_request) Successful in 43s
lint / lint (push) Successful in 2m44s
test / integration-firecracker (pull_request) Successful in 3m42s
test / coverage (pull_request) Successful in 15s
test / publish-infra (pull_request) Has been skipped

# Conflicts:
#	tests/unit/test_macos_infra.py
This commit is contained in:
2026-07-25 18:01:49 -04:00
2 changed files with 29 additions and 12 deletions
+17 -5
View File
@@ -168,13 +168,19 @@ def stop() -> None:
recorded VMMs AND any orphaned firecracker still bound to either infra recorded VMMs AND any orphaned firecracker still bound to either infra
config — the PID files drift after crashes / out-of-band kills, and a config — the PID files drift after crashes / out-of-band kills, and a
survivor would hold a link's TAP so the next boot dies with "tap … survivor would hold a link's TAP so the next boot dies with "tap …
Resource busy". Drops the version marker so a stopped pair is never Resource busy". Also reaps a surviving *legacy* single combined-VM (the
treated as adoptable.""" pre-split layout booted from `<infra>/config.json` on the orchestrator
link): the two-VM `stop` otherwise wouldn't know about it, and it would
hold the orchestrator TAP so the first split boot fails — so the cutover
is self-healing, no manual host teardown. Drops the version marker so a
stopped pair is never treated as adoptable."""
_kill_pidfile(_orch_dir()) _kill_pidfile(_orch_dir())
_kill_pidfile(_gw_dir()) _kill_pidfile(_gw_dir())
_kill_pidfile(_infra_dir()) # legacy pre-split combined VM (migration)
_kill_infra_firecrackers() _kill_infra_firecrackers()
_pid_file(_orch_dir()).unlink(missing_ok=True) _pid_file(_orch_dir()).unlink(missing_ok=True)
_pid_file(_gw_dir()).unlink(missing_ok=True) _pid_file(_gw_dir()).unlink(missing_ok=True)
_pid_file(_infra_dir()).unlink(missing_ok=True) # legacy
_version_file().unlink(missing_ok=True) _version_file().unlink(missing_ok=True)
@@ -357,9 +363,15 @@ def _kill_infra_firecrackers(proc_root: Path = Path("/proc")) -> None:
"""SIGKILL any firecracker VMM whose `--config-file` is one of this host's """SIGKILL any firecracker VMM whose `--config-file` is one of this host's
infra configs (orchestrator or gateway), independent of the PID files — infra configs (orchestrator or gateway), independent of the PID files —
reaps orphans it lost track of so both links' TAPs are free to rebind. reaps orphans it lost track of so both links' TAPs are free to rebind.
Scoped to the two infra config paths, so the interactive pool's agent VMs Also matches the *legacy* pre-split combined-VM config (`<infra>/config.json`)
(other config paths) are untouched.""" so a surviving old singleton is cleared off the orchestrator link during the
cfgs = {str(_orch_dir() / "config.json"), str(_gw_dir() / "config.json")} cutover. Scoped to these infra config paths, so the pool's agent VMs (other
config paths) are untouched."""
cfgs = {
str(_orch_dir() / "config.json"),
str(_gw_dir() / "config.json"),
str(_infra_dir() / "config.json"), # legacy pre-split combined VM
}
for entry in proc_root.iterdir(): for entry in proc_root.iterdir():
if not entry.name.isdigit(): if not entry.name.isdigit():
continue continue
+12 -7
View File
@@ -130,9 +130,10 @@ class TestStop(unittest.TestCase):
patch.object(infra_vm, "_kill_pidfile") as kill, \ patch.object(infra_vm, "_kill_pidfile") as kill, \
patch.object(infra_vm, "_kill_infra_firecrackers"): patch.object(infra_vm, "_kill_infra_firecrackers"):
infra_vm.stop() infra_vm.stop()
# Both VMs' pidfiles are reaped, and the version marker is gone so a # Both VMs' pidfiles are reaped — plus the legacy pre-split combined
# stopped pair is never adopted. # VM's — and the version marker is gone so a stopped pair is never
self.assertEqual(2, kill.call_count) # adopted.
self.assertEqual(3, kill.call_count)
self.assertFalse((d / "booted-version").exists()) self.assertFalse((d / "booted-version").exists())
@@ -224,31 +225,35 @@ class TestKillInfraFirecrackers(unittest.TestCase):
(p / "comm").write_text(comm + "\n") (p / "comm").write_text(comm + "\n")
(p / "cmdline").write_bytes(b"\0".join(a.encode() for a in cmdline) + b"\0") (p / "cmdline").write_bytes(b"\0".join(a.encode() for a in cmdline) + b"\0")
def test_kills_both_infra_firecrackers_only(self): def test_kills_both_infra_and_legacy_firecrackers_only(self):
import tempfile import tempfile
with tempfile.TemporaryDirectory() as td, \ with tempfile.TemporaryDirectory() as td, \
tempfile.TemporaryDirectory() as proc: tempfile.TemporaryDirectory() as proc:
infra_dir = Path(td) infra_dir = Path(td)
orch_cfg = str(infra_dir / "orchestrator" / "config.json") orch_cfg = str(infra_dir / "orchestrator" / "config.json")
gw_cfg = str(infra_dir / "gateway" / "config.json") gw_cfg = str(infra_dir / "gateway" / "config.json")
legacy_cfg = str(infra_dir / "config.json") # pre-split combined VM
root = Path(proc) root = Path(proc)
# both infra VMs -> killed # both new infra VMs + the legacy combined VM -> killed
self._fake_proc(root, 111, "firecracker", self._fake_proc(root, 111, "firecracker",
["firecracker", "--no-api", "--config-file", orch_cfg]) ["firecracker", "--no-api", "--config-file", orch_cfg])
self._fake_proc(root, 112, "firecracker", self._fake_proc(root, 112, "firecracker",
["firecracker", "--no-api", "--config-file", gw_cfg]) ["firecracker", "--no-api", "--config-file", gw_cfg])
self._fake_proc(root, 113, "firecracker",
["firecracker", "--no-api", "--config-file", legacy_cfg])
# a firecracker for a different (interactive) VM -> spared # a firecracker for a different (interactive) VM -> spared
self._fake_proc(root, 222, "firecracker", self._fake_proc(root, 222, "firecracker",
["firecracker", "--config-file", "/home/u/other.json"]) ["firecracker", "--config-file", "/home/u/other.json"])
# a non-firecracker process on an infra config path -> spared # a non-firecracker process on an infra config path -> spared
self._fake_proc(root, 333, "python3", ["python3", orch_cfg]) self._fake_proc(root, 333, "python3", ["python3", orch_cfg])
(root / "not-a-pid").mkdir() (root / "not-a-pid").mkdir()
with patch.object(infra_vm, "_orch_dir", return_value=infra_dir / "orchestrator"), \ with patch.object(infra_vm, "_infra_dir", return_value=infra_dir), \
patch.object(infra_vm, "_orch_dir", return_value=infra_dir / "orchestrator"), \
patch.object(infra_vm, "_gw_dir", return_value=infra_dir / "gateway"), \ patch.object(infra_vm, "_gw_dir", return_value=infra_dir / "gateway"), \
patch.object(infra_vm.os, "kill") as kill: patch.object(infra_vm.os, "kill") as kill:
infra_vm._kill_infra_firecrackers(proc_root=root) infra_vm._kill_infra_firecrackers(proc_root=root)
killed = {c.args[0] for c in kill.call_args_list} killed = {c.args[0] for c in kill.call_args_list}
self.assertEqual({111, 112}, killed) self.assertEqual({111, 112, 113}, killed)
if __name__ == "__main__": if __name__ == "__main__":