#!/usr/bin/env python3 """A saved machine must be fast, must match its image, and must never be load-bearing. python3 tests/test_vm_warm_start.py WHY Resuming a saved machine turns a 90-second Windows boot into about 30 seconds of session setup, and the cold start of PowerShell - measured at over ninety seconds on a fresh boot - is inside the saved memory rather than in front of the agent. The risk is not that it is slow, it is that it is WRONG. A saved state describes memory that matches one particular disk, down to the page cache. Resume it against an image that has changed since and nothing reports an error: the guest simply runs with a filesystem in memory that disagrees with the one on disk. So the interlock - the checksum recorded when the image was frozen - is the part that matters, and these cases are mostly about it. The second rule is that this is an optimisation. An optimisation that can take the feature down is not one, so a saved machine that will not load must become a cold boot rather than a failed session. """ import json import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "vm", "vmd")) import session as S # noqa: E402 FAILS: list[str] = [] def check(label: str, condition: bool, detail: str = "") -> None: if condition: print(f"ok {label}") else: FAILS.append(f"{label}: {detail}") print(f"FAIL {label}\n {detail}") # --- the interlock ------------------------------------------------------ import tempfile # noqa: E402 work = tempfile.mkdtemp() S.WARM_ROOT = os.path.join(work, "warm") S.BASE_ROOT = os.path.join(work, "base") os.makedirs(os.path.join(S.WARM_ROOT, "windows")) os.makedirs(S.BASE_ROOT) warm = os.path.join(S.WARM_ROOT, "windows") for name in ("windows.qcow2", "state", "OVMF_VARS.fd"): open(os.path.join(warm, name), "wb").write(b"x") with open(os.path.join(S.BASE_ROOT, "windows.sha256"), "w") as fh: fh.write(f"{'a' * 64} {S.BASE_ROOT}/windows.qcow2\n" f"{'b' * 64} {S.BASE_ROOT}/windows-OVMF_VARS.fd\n") def manifest(sha: str) -> None: with open(os.path.join(warm, "manifest.json"), "w") as fh: json.dump({"os": "windows", "base_sha256": sha}, fh) manifest("a" * 64) check("a saved machine matching the image is usable", S.warm_set("windows") is not None) manifest("c" * 64) check("one taken from a different image is refused", S.warm_set("windows") is None, "a mismatched checksum was accepted - this is how a disk gets corrupted") manifest("a" * 64) os.rename(os.path.join(warm, "state"), os.path.join(warm, "state.away")) check("one with no state file is refused", S.warm_set("windows") is None) os.rename(os.path.join(warm, "state.away"), os.path.join(warm, "state")) os.rename(os.path.join(warm, "manifest.json"), os.path.join(warm, "m.away")) check("one with no manifest is refused", S.warm_set("windows") is None, "nothing could vouch for it and it was used anyway") os.rename(os.path.join(warm, "m.away"), os.path.join(warm, "manifest.json")) with open(os.path.join(warm, "manifest.json"), "w") as fh: fh.write("{not json") check("an unreadable manifest is refused", S.warm_set("windows") is None) manifest("a" * 64) # An image with no recorded checksum cannot be vouched for either. That is # the state an image is in before it has ever been frozen. os.rename(os.path.join(S.BASE_ROOT, "windows.sha256"), os.path.join(S.BASE_ROOT, "away")) check("an image with no recorded checksum is refused", S.warm_set("windows") is None, "there was nothing to compare against and it was used anyway") os.rename(os.path.join(S.BASE_ROOT, "away"), os.path.join(S.BASE_ROOT, "windows.sha256")) check("and it is usable again once everything lines up", S.warm_set("windows") is not None) check("an image with no saved machine at all is simply not warm", S.warm_set("ubuntu") is None) # --- the source, read as source ----------------------------------------- src = open(os.path.join(os.path.dirname(__file__), "..", "vm", "vmd", "session.py")).read() vmd_src = open(os.path.join(os.path.dirname(__file__), "..", "vm", "vmd", "vmd.py")).read() qmp_src = open(os.path.join(os.path.dirname(__file__), "..", "vm", "vmd", "qmp.py")).read() launch = open(os.path.join(os.path.dirname(__file__), "..", "vm", "host", "owuivm-launch")).read() # The share cannot be in a saved state - virtiofsd refuses migration with # "vhost-user backend lacks VHOST_USER_PROTOCOL_F_LOG_SHMFD" - so a warm # session must not be given one at boot and must plug it in afterwards. check("a warm start boots without the share attached", "'' if warm else (share or '')" in src, "a saved machine cannot carry one; qemu refuses to migrate with it") check("and the share is plugged in after the resume", "_attach_share" in src and "attach_share" in qmp_src) check("into the port the argv always carries", "pcie-root-port,id=vfsport" in launch and 'bus="vfsport"' in qmp_src, "q35's root complex is not hot-pluggable; without the port there is nowhere to put it") check("at the same address a cold boot uses", "bus=vfsport,addr=0x0" in launch and 'addr="0x0"' in qmp_src, "a drive letter that moves between sessions is worse than a slow boot") # The fallback is the whole safety argument. check("a warm start that fails becomes a cold boot", "allow_warm=False" in vmd_src and "warm_start_failed" in vmd_src, "an optimisation that can take the feature down is not one") check("and the caller is told it happened", "booted from cold" in vmd_src) # The clock: a resumed machine believes it is whenever it was saved. check("the clock is set on every session, not only resumed ones", "_sync_clock(session)" in src and "clock = _sync_clock" in src) check("and the set is verified rather than assumed", "info_time" in src and "within" in src, "the first version swallowed the error and ran 26s behind") # State is written through a descriptor vmd owns. check("machine state is written through a descriptor, not a path", "save_state_to_fd" in qmp_src and "send_fds" in qmp_src, "qemu writes 0600 as its own user; the alternatives are a 4 GB copy " "or letting the sandbox write the file a later session resumes from") check("and the resume path only accepts this host's own saved state", "/var/lib/owuivm/warm/(ubuntu|windows)/state$" in launch, "a migration stream sets the machine's registers, memory and devices") print() if FAILS: print(f"FAILED {len(FAILS)}") raise SystemExit(1) print("a saved machine is fast, is checked against its image, and cannot break a session")