clan-cli: Fix tmpdir leak and fix tests/temporary_dir inconsistencies
All checks were successful
checks / check-links (pull_request) Successful in 14s
checks / checks-impure (pull_request) Successful in 1m50s
checks / checks (pull_request) Successful in 3m35s

This commit is contained in:
Luis Hebendanz 2024-03-27 15:51:52 +01:00
parent 0676bf7283
commit e6ad0cfbc1
2 changed files with 73 additions and 70 deletions

View File

@ -3,6 +3,7 @@ import importlib
import json import json
import logging import logging
import os import os
from contextlib import ExitStack
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
@ -108,86 +109,87 @@ def run_vm(
socketdir: Path | None = None, socketdir: Path | None = None,
nix_options: list[str] = [], nix_options: list[str] = [],
) -> None: ) -> None:
machine = Machine(vm.machine_name, vm.flake_url) with ExitStack() as stack:
log.debug(f"Creating VM for {machine}") machine = Machine(vm.machine_name, vm.flake_url)
log.debug(f"Creating VM for {machine}")
# store the temporary rootfs inside XDG_CACHE_HOME on the host # store the temporary rootfs inside XDG_CACHE_HOME on the host
# otherwise, when using /tmp, we risk running out of memory # otherwise, when using /tmp, we risk running out of memory
cache = user_cache_dir() / "clan" cache = user_cache_dir() / "clan"
cache.mkdir(exist_ok=True) cache.mkdir(exist_ok=True)
if cachedir is None: if cachedir is None:
cache_tmp = TemporaryDirectory(dir=cache) cache_tmp = stack.enter_context(TemporaryDirectory(dir=cache))
cachedir = Path(cache_tmp.name) cachedir = Path(cache_tmp)
if socketdir is None: if socketdir is None:
socket_tmp = TemporaryDirectory() socket_tmp = stack.enter_context(TemporaryDirectory())
socketdir = Path(socket_tmp.name) socketdir = Path(socket_tmp)
# TODO: We should get this from the vm argument # TODO: We should get this from the vm argument
nixos_config = build_vm(machine, cachedir, nix_options) nixos_config = build_vm(machine, cachedir, nix_options)
state_dir = vm_state_dir(str(vm.flake_url), machine.name) state_dir = vm_state_dir(str(vm.flake_url), machine.name)
state_dir.mkdir(parents=True, exist_ok=True) state_dir.mkdir(parents=True, exist_ok=True)
# specify socket files for qmp and qga # specify socket files for qmp and qga
qmp_socket_file = socketdir / "qmp.sock" qmp_socket_file = socketdir / "qmp.sock"
qga_socket_file = socketdir / "qga.sock" qga_socket_file = socketdir / "qga.sock"
# Create symlinks to the qmp/qga sockets to be able to find them later. # Create symlinks to the qmp/qga sockets to be able to find them later.
# This indirection is needed because we cannot put the sockets directly # This indirection is needed because we cannot put the sockets directly
# in the state_dir. # in the state_dir.
# The reason is, qemu has a length limit of 108 bytes for the qmp socket # The reason is, qemu has a length limit of 108 bytes for the qmp socket
# path which is violated easily. # path which is violated easily.
qmp_link = state_dir / "qmp.sock" qmp_link = state_dir / "qmp.sock"
if os.path.lexists(qmp_link): if os.path.lexists(qmp_link):
qmp_link.unlink() qmp_link.unlink()
qmp_link.symlink_to(qmp_socket_file) qmp_link.symlink_to(qmp_socket_file)
qga_link = state_dir / "qga.sock" qga_link = state_dir / "qga.sock"
if os.path.lexists(qga_link): if os.path.lexists(qga_link):
qga_link.unlink() qga_link.unlink()
qga_link.symlink_to(qga_socket_file) qga_link.symlink_to(qga_socket_file)
rootfs_img = prepare_disk(cachedir) rootfs_img = prepare_disk(cachedir)
state_img = state_dir / "state.qcow2" state_img = state_dir / "state.qcow2"
if not state_img.exists(): if not state_img.exists():
state_img = prepare_disk( state_img = prepare_disk(
directory=state_dir, directory=state_dir,
file_name="state.qcow2", file_name="state.qcow2",
size="50G", size="50G",
) )
virtiofsd_socket = socketdir / "virtiofsd.sock" virtiofsd_socket = socketdir / "virtiofsd.sock"
qemu_cmd = qemu_command( qemu_cmd = qemu_command(
vm, vm,
nixos_config, nixos_config,
secrets_dir=Path(nixos_config["secrets_dir"]), secrets_dir=Path(nixos_config["secrets_dir"]),
rootfs_img=rootfs_img, rootfs_img=rootfs_img,
state_img=state_img, state_img=state_img,
virtiofsd_socket=virtiofsd_socket, virtiofsd_socket=virtiofsd_socket,
qmp_socket_file=qmp_socket_file, qmp_socket_file=qmp_socket_file,
qga_socket_file=qga_socket_file, qga_socket_file=qga_socket_file,
)
packages = ["nixpkgs#qemu"]
env = os.environ.copy()
if vm.graphics and not vm.waypipe:
packages.append("nixpkgs#virt-viewer")
remote_viewer_mimetypes = module_root() / "vms" / "mimetypes"
env["XDG_DATA_DIRS"] = (
f"{remote_viewer_mimetypes}:{env.get('XDG_DATA_DIRS', '')}"
) )
with ( packages = ["nixpkgs#qemu"]
start_waypipe(qemu_cmd.vsock_cid, f"[{vm.machine_name}] "),
start_virtiofsd(virtiofsd_socket), env = os.environ.copy()
): if vm.graphics and not vm.waypipe:
run( packages.append("nixpkgs#virt-viewer")
nix_shell(packages, qemu_cmd.args), remote_viewer_mimetypes = module_root() / "vms" / "mimetypes"
env=env, env["XDG_DATA_DIRS"] = (
log=Log.BOTH, f"{remote_viewer_mimetypes}:{env.get('XDG_DATA_DIRS', '')}"
error_msg=f"Could not start vm {machine}", )
)
with (
start_waypipe(qemu_cmd.vsock_cid, f"[{vm.machine_name}] "),
start_virtiofsd(virtiofsd_socket),
):
run(
nix_shell(packages, qemu_cmd.args),
env=env,
log=Log.BOTH,
error_msg=f"Could not start vm {machine}",
)
@dataclass @dataclass

View File

@ -16,6 +16,7 @@ def temporary_home(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]:
path = Path(env_dir).resolve() path = Path(env_dir).resolve()
log.debug("Temp HOME directory: %s", str(path)) log.debug("Temp HOME directory: %s", str(path))
monkeypatch.setenv("HOME", str(path)) monkeypatch.setenv("HOME", str(path))
monkeypatch.setenv("XDG_CONFIG_HOME", str(path / ".config"))
monkeypatch.chdir(str(path)) monkeypatch.chdir(str(path))
yield path yield path
else: else: