Files
felhom.eu/scripts/test_felhom_restore_beside.py
T
2026-10-04 08:56:56 +02:00

134 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""Tests for felhom-restore-beside.sh (R-834) — a fake `pct` on PATH holds the guest config in a file.
The restored config is the PRODUCTION one (onboot 1, mp8 on the household's drives, mp9 on the
original's bootstrap, NICs up). The script must end with onboot 0, no host-path bind, every NIC
link_down, and never start the guest. Asserted on the consequence: the fake's final config file and
its call log. Red-proof: drop the `pct set --delete` line from the script and test_strips fails.
Run: python3 scripts/test_felhom_restore_beside.py
"""
import os
import pathlib
import stat
import subprocess
import sys
import tempfile
import unittest
SCRIPT = pathlib.Path(__file__).with_name("felhom-restore-beside.sh")
PROD_CONF = """arch: amd64
hostname: demo-hp
onboot: 1
rootfs: nvme-scratch:9299/vm-9299-disk-0.raw,size=16G
mp0: nvme-scratch:9299/vm-9299-disk-1.raw,mp=/var/lib/felhom,backup=1,size=70G
mp8: /mnt/felhom-drives,mp=/mnt/felhom-drives
mp9: /var/lib/felhom-agent/guests/9201/bootstrap,mp=/etc/felhom-bootstrap,ro=1
net0: name=eth0,bridge=vmbr0,hwaddr=BC:24:11:0F:7E:C5,ip=dhcp,type=veth
net1: name=eth1,bridge=vmbr9,hwaddr=BC:24:11:28:B4:F5,ip=169.254.253.2/30,type=veth
"""
# A minimal pct: status (exists iff the conf file exists), restore (writes PROD_CONF, records whether
# --onboot 0 was passed), config, set (--delete / --netN / --onboot), start (recorded — must never run).
FAKE_PCT = r'''#!/usr/bin/env python3
import os, sys
d = os.environ["FAKE_PCT_DIR"]
conf = os.path.join(d, "conf")
with open(os.path.join(d, "calls"), "a") as f:
f.write(" ".join(sys.argv[1:]) + "\n")
cmd = sys.argv[1]
def read():
return [l for l in open(conf).read().splitlines() if l]
def write(lines):
open(conf, "w").write("\n".join(lines) + "\n")
if cmd == "status":
sys.exit(0 if os.path.exists(conf) else 2)
if cmd == "restore":
lines = open(os.path.join(d, "prod")).read().splitlines()
args = sys.argv[3:]
if "--onboot" in args:
v = args[args.index("--onboot") + 1]
lines = [("onboot: " + v) if l.startswith("onboot:") else l for l in lines]
write([l for l in lines if l])
sys.exit(0)
if cmd == "config":
print("\n".join(read()))
sys.exit(0)
if cmd == "set":
lines = read()
a = sys.argv[3:]
while a:
k, v = a[0], a[1]
a = a[2:]
if k == "--delete":
drop = set(v.split(","))
lines = [l for l in lines if l.split(":")[0] not in drop]
else:
key = k.lstrip("-")
lines = [l for l in lines if l.split(":")[0] != key] + [key + ": " + v]
write(lines)
sys.exit(0)
if cmd == "start":
sys.exit(0)
sys.exit(9)
'''
def run(tmp, *args, script=SCRIPT, exists=False):
bindir = pathlib.Path(tmp, "bin")
bindir.mkdir(exist_ok=True)
pct = bindir / "pct"
pct.write_text(FAKE_PCT)
pct.chmod(pct.stat().st_mode | stat.S_IEXEC)
pathlib.Path(tmp, "prod").write_text(PROD_CONF)
if exists:
pathlib.Path(tmp, "conf").write_text(PROD_CONF)
env = dict(os.environ, PATH=f"{bindir}:{os.environ['PATH']}", FAKE_PCT_DIR=tmp)
return subprocess.run(["bash", str(script), *args], env=env, capture_output=True, text=True)
class RestoreBeside(unittest.TestCase):
def test_strips(self):
with tempfile.TemporaryDirectory() as tmp:
r = run(tmp, "9299", "tmp:backup/ct/9201/x", "nvme-scratch")
self.assertEqual(r.returncode, 0, r.stderr + r.stdout)
conf = pathlib.Path(tmp, "conf").read_text()
calls = pathlib.Path(tmp, "calls").read_text()
self.assertIn("onboot: 0", conf)
self.assertNotIn("onboot: 1", conf)
for line in conf.splitlines():
self.assertFalse(line.startswith("mp") and ": /" in line, f"host bind left: {line}")
if line.startswith("net"):
self.assertIn("link_down=1", line)
self.assertIn("mp0:", conf, "a storage volume must stay")
self.assertNotIn("\nstart", "\n" + calls, "the script started the guest")
self.assertIn("--onboot 0", calls.splitlines()[1], "onboot 0 must be set AT restore time")
def test_refuses_an_existing_vmid(self):
with tempfile.TemporaryDirectory() as tmp:
r = run(tmp, "9201", "tmp:backup/ct/9201/x", "nvme-scratch", exists=True)
self.assertNotEqual(r.returncode, 0)
self.assertIn("already exists", r.stderr)
self.assertNotIn("restore", pathlib.Path(tmp, "calls").read_text())
def test_refuses_the_agent_bands(self):
for v in ("990003", "9999", "abc"):
with tempfile.TemporaryDirectory() as tmp:
r = run(tmp, v, "tmp:backup/ct/9201/x", "s")
self.assertNotEqual(r.returncode, 0, v)
def test_readback_catches_a_bind_left_behind(self):
# Red-proof built in: the same script with the --delete line removed must FAIL its read-back.
with tempfile.TemporaryDirectory() as tmp:
broken = pathlib.Path(tmp, "broken.sh")
broken.write_text(SCRIPT.read_text().replace('\tpct set "$vmid" --delete "$binds"\n', "\t:\n"))
self.assertNotEqual(broken.read_text(), SCRIPT.read_text(), "mutation did not apply")
r = run(tmp, "9299", "tmp:backup/ct/9201/x", "s", script=broken)
self.assertEqual(r.returncode, 2, r.stdout + r.stderr)
self.assertIn("host-bind-left", r.stderr)
if __name__ == "__main__":
sys.exit(unittest.main())