fix(l4d2-host): make stop_instance idempotent on the unmount step

systemctl stop is already a no-op on a stopped unit, but stop_instance
was unconditionally running fusermount3 -u and bubbling up the EINVAL
when the overlay wasn't currently mounted (e.g. server already stopped).
Mirror the established delete_instance pattern: always attempt the
unmount, swallow CalledProcessError, and label the step "(if mounted)".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-08 11:24:04 +02:00
parent 38548ab0d7
commit d5d710afa7
No known key found for this signature in database
2 changed files with 32 additions and 8 deletions

View file

@ -135,14 +135,17 @@ def stop_instance(
passthrough=passthrough,
should_cancel=should_cancel,
)
emit_step("unmounting runtime overlay...", on_stdout, passthrough)
run_command(
["fusermount3", "-u", str(root / "runtime" / name / "merged")],
on_stdout=on_stdout,
on_stderr=on_stderr,
passthrough=passthrough,
should_cancel=should_cancel,
)
emit_step("unmounting runtime overlay (if mounted)...", on_stdout, passthrough)
try:
run_command(
["fusermount3", "-u", str(root / "runtime" / name / "merged")],
on_stdout=on_stdout,
on_stderr=on_stderr,
passthrough=passthrough,
should_cancel=should_cancel,
)
except subprocess.CalledProcessError:
pass
emit_step("stop complete.", on_stdout, passthrough)

View file

@ -96,6 +96,27 @@ def test_delete_stopped_instance_removes_dirs(tmp_path: Path, monkeypatch: pytes
assert ["sudo", "-n", "/usr/local/libexec/left4me/left4me-systemctl", "stop", "alpha"] in calls
def test_stop_succeeds_when_unmount_fails(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
fusermount_calls: list[list[str]] = []
def fake_run_command(cmd, **kwargs):
del kwargs
if cmd and cmd[0] == "fusermount3":
fusermount_calls.append(list(cmd))
raise subprocess.CalledProcessError(
returncode=1,
cmd=list(cmd),
stderr="fusermount3: failed to unmount /var/lib/left4me/runtime/alpha/merged: Invalid argument",
)
monkeypatch.setattr("l4d2host.instances.run_command", fake_run_command)
monkeypatch.setattr("l4d2host.service_control.run_command", fake_run_command)
stop_instance("alpha", root=tmp_path)
assert fusermount_calls, "stop must always attempt fusermount3 -u (no preflight)"
def test_delete_succeeds_when_unmount_fails(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
fusermount_calls: list[list[str]] = []