Merge pull request 'Fix leaks in vm tests' (#2192) from fix-warning into main
This commit is contained in:
@@ -194,7 +194,7 @@ def generate_facts(
|
|||||||
prompt: Callable[[str, str], str] = prompt_func,
|
prompt: Callable[[str, str], str] = prompt_func,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
was_regenerated = False
|
was_regenerated = False
|
||||||
with TemporaryDirectory() as tmp:
|
with TemporaryDirectory(prefix="facts-generate-") as tmp:
|
||||||
tmpdir = Path(tmp)
|
tmpdir = Path(tmp)
|
||||||
|
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ def upload_secrets(machine: Machine) -> None:
|
|||||||
if secret_facts_store.update_check():
|
if secret_facts_store.update_check():
|
||||||
log.info("Secrets already up to date")
|
log.info("Secrets already up to date")
|
||||||
return
|
return
|
||||||
with TemporaryDirectory() as tempdir:
|
with TemporaryDirectory(prefix="facts-upload-") as tempdir:
|
||||||
secret_facts_store.upload(Path(tempdir))
|
secret_facts_store.upload(Path(tempdir))
|
||||||
host = machine.target_host
|
host = machine.target_host
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def create_machine(opts: CreateOptions) -> None:
|
|||||||
)
|
)
|
||||||
raise ClanError(msg, description=description)
|
raise ClanError(msg, description=description)
|
||||||
|
|
||||||
with TemporaryDirectory() as tmpdir:
|
with TemporaryDirectory(prefix="machine-template-") as tmpdir:
|
||||||
tmpdirp = Path(tmpdir)
|
tmpdirp = Path(tmpdir)
|
||||||
command = nix_command(
|
command = nix_command(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
from pathlib import Path
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
@@ -12,17 +11,8 @@ from clan_cli.errors import ClanError
|
|||||||
# - no need to initialize by asking for capabilities
|
# - no need to initialize by asking for capabilities
|
||||||
# - results need to be base64 decoded
|
# - results need to be base64 decoded
|
||||||
class QgaSession:
|
class QgaSession:
|
||||||
def __init__(self, socket_file: Path | str) -> None:
|
def __init__(self, sock: socket.socket) -> None:
|
||||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
self.sock = sock
|
||||||
# try to reconnect a couple of times if connection refused
|
|
||||||
for _ in range(100):
|
|
||||||
try:
|
|
||||||
self.sock.connect(str(socket_file))
|
|
||||||
except ConnectionRefusedError:
|
|
||||||
sleep(0.1)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
self.sock.connect(str(socket_file))
|
|
||||||
|
|
||||||
def get_response(self) -> dict:
|
def get_response(self) -> dict:
|
||||||
result = self.sock.recv(9999999)
|
result = self.sock.recv(9999999)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ def upload_secrets(machine: Machine) -> None:
|
|||||||
if secret_store.update_check():
|
if secret_store.update_check():
|
||||||
log.info("Secrets already up to date")
|
log.info("Secrets already up to date")
|
||||||
return
|
return
|
||||||
with TemporaryDirectory() as tempdir:
|
with TemporaryDirectory(prefix="vars-upload-") as tempdir:
|
||||||
secret_store.upload(Path(tempdir))
|
secret_store.upload(Path(tempdir))
|
||||||
host = machine.target_host
|
host = machine.target_host
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ python3.pkgs.buildPythonApplication {
|
|||||||
chmod +w -R ./src
|
chmod +w -R ./src
|
||||||
cd ./src
|
cd ./src
|
||||||
|
|
||||||
export NIX_STATE_DIR=$TMPDIR/nix IN_NIX_SANDBOX=1
|
export NIX_STATE_DIR=$TMPDIR/nix IN_NIX_SANDBOX=1 PYTHONWARNINGS=error
|
||||||
${pythonWithTestDeps}/bin/python -m pytest -m "not impure and not with_core" ./tests
|
${pythonWithTestDeps}/bin/python -m pytest -m "not impure and not with_core" ./tests
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
@@ -147,7 +147,7 @@ python3.pkgs.buildPythonApplication {
|
|||||||
cd ./src
|
cd ./src
|
||||||
|
|
||||||
export CLAN_CORE=${clan-core-path}
|
export CLAN_CORE=${clan-core-path}
|
||||||
export NIX_STATE_DIR=$TMPDIR/nix IN_NIX_SANDBOX=1
|
export NIX_STATE_DIR=$TMPDIR/nix IN_NIX_SANDBOX=1 PYTHONWARNINGS=error
|
||||||
${pythonWithTestDeps}/bin/python -m pytest -m "not impure and with_core" ./tests
|
${pythonWithTestDeps}/bin/python -m pytest -m "not impure and with_core" ./tests
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ mkShell {
|
|||||||
shellHook = ''
|
shellHook = ''
|
||||||
export GIT_ROOT="$(git rev-parse --show-toplevel)"
|
export GIT_ROOT="$(git rev-parse --show-toplevel)"
|
||||||
export PKG_ROOT="$GIT_ROOT/pkgs/clan-cli"
|
export PKG_ROOT="$GIT_ROOT/pkgs/clan-cli"
|
||||||
|
export PYTHONWARNINGS=error
|
||||||
|
|
||||||
# Add current package to PYTHONPATH
|
# Add current package to PYTHONPATH
|
||||||
export PYTHONPATH="$PKG_ROOT''${PYTHONPATH:+:$PYTHONPATH:}"
|
export PYTHONPATH="$PKG_ROOT''${PYTHONPATH:+:$PYTHONPATH:}"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import socket
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
@@ -89,26 +90,43 @@ def wait_vm_down(machine_name: str, vm: VmThread, flake_url: str | None = None)
|
|||||||
|
|
||||||
|
|
||||||
# wait for vm to be up then connect and return qmp instance
|
# wait for vm to be up then connect and return qmp instance
|
||||||
|
@contextlib.contextmanager
|
||||||
def qmp_connect(
|
def qmp_connect(
|
||||||
machine_name: str, vm: VmThread, flake_url: str | None = None
|
machine_name: str, vm: VmThread, flake_url: str | None = None
|
||||||
) -> QEMUMonitorProtocol:
|
) -> Iterator[QEMUMonitorProtocol]:
|
||||||
if flake_url is None:
|
if flake_url is None:
|
||||||
flake_url = str(Path.cwd())
|
flake_url = str(Path.cwd())
|
||||||
state_dir = vm_state_dir(flake_url, machine_name)
|
state_dir = vm_state_dir(flake_url, machine_name)
|
||||||
wait_vm_up(machine_name, vm, flake_url)
|
wait_vm_up(machine_name, vm, flake_url)
|
||||||
qmp = QEMUMonitorProtocol(
|
with QEMUMonitorProtocol(
|
||||||
address=str(os.path.realpath(state_dir / "qmp.sock")),
|
address=str(os.path.realpath(state_dir / "qmp.sock")),
|
||||||
)
|
) as qmp:
|
||||||
qmp.connect()
|
qmp.connect()
|
||||||
return qmp
|
yield qmp
|
||||||
|
|
||||||
|
|
||||||
# wait for vm to be up then connect and return qga instance
|
# wait for vm to be up then connect and return qga instance
|
||||||
|
@contextlib.contextmanager
|
||||||
def qga_connect(
|
def qga_connect(
|
||||||
machine_name: str, vm: VmThread, flake_url: str | None = None
|
machine_name: str, vm: VmThread, flake_url: str | None = None
|
||||||
) -> QgaSession:
|
) -> Iterator[QgaSession]:
|
||||||
if flake_url is None:
|
if flake_url is None:
|
||||||
flake_url = str(Path.cwd())
|
flake_url = str(Path.cwd())
|
||||||
state_dir = vm_state_dir(flake_url, machine_name)
|
state_dir = vm_state_dir(flake_url, machine_name)
|
||||||
wait_vm_up(machine_name, vm, flake_url)
|
wait_vm_up(machine_name, vm, flake_url)
|
||||||
return QgaSession(os.path.realpath(state_dir / "qga.sock"))
|
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
try:
|
||||||
|
# try to reconnect a couple of times if connection refused
|
||||||
|
socket_file = os.path.realpath(state_dir / "qga.sock")
|
||||||
|
for _ in range(100):
|
||||||
|
try:
|
||||||
|
sock.connect(str(socket_file))
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
sleep(0.1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
sock.connect(str(socket_file))
|
||||||
|
yield QgaSession(sock)
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temporary_home(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]:
|
def temporary_home(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]:
|
||||||
with tempfile.TemporaryDirectory(prefix="pytest-") as dirpath:
|
with tempfile.TemporaryDirectory(prefix="pytest-home-") as dirpath:
|
||||||
xdg_runtime_dir = os.getenv("XDG_RUNTIME_DIR")
|
xdg_runtime_dir = os.getenv("XDG_RUNTIME_DIR")
|
||||||
monkeypatch.setenv("HOME", str(dirpath))
|
monkeypatch.setenv("HOME", str(dirpath))
|
||||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(Path(dirpath) / ".config"))
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(Path(dirpath) / ".config"))
|
||||||
@@ -34,5 +34,10 @@ def temporary_home(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]:
|
|||||||
|
|
||||||
monkeypatch.setenv("XDG_RUNTIME_DIR", str(runtime_dir))
|
monkeypatch.setenv("XDG_RUNTIME_DIR", str(runtime_dir))
|
||||||
monkeypatch.chdir(str(dirpath))
|
monkeypatch.chdir(str(dirpath))
|
||||||
log.debug("Temp HOME directory: %s", str(dirpath))
|
yield Path(dirpath)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def temp_dir() -> Iterator[Path]:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="pytest-") as dirpath:
|
||||||
yield Path(dirpath)
|
yield Path(dirpath)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import subprocess
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from age_keys import SopsSetup
|
from age_keys import SopsSetup
|
||||||
@@ -24,7 +23,7 @@ from root import CLAN_CORE
|
|||||||
from stdout import CaptureOutput
|
from stdout import CaptureOutput
|
||||||
|
|
||||||
|
|
||||||
def test_dependencies_as_files() -> None:
|
def test_dependencies_as_files(temp_dir: Path) -> None:
|
||||||
from clan_cli.vars.generate import dependencies_as_dir
|
from clan_cli.vars.generate import dependencies_as_dir
|
||||||
|
|
||||||
decrypted_dependencies = {
|
decrypted_dependencies = {
|
||||||
@@ -37,19 +36,17 @@ def test_dependencies_as_files() -> None:
|
|||||||
"var_2b": b"var_2b",
|
"var_2b": b"var_2b",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
with TemporaryDirectory() as tmpdir:
|
dependencies_as_dir(decrypted_dependencies, temp_dir)
|
||||||
dep_tmpdir = Path(tmpdir)
|
assert temp_dir.is_dir()
|
||||||
dependencies_as_dir(decrypted_dependencies, dep_tmpdir)
|
assert (temp_dir / "gen_1" / "var_1a").read_bytes() == b"var_1a"
|
||||||
assert dep_tmpdir.is_dir()
|
assert (temp_dir / "gen_1" / "var_1b").read_bytes() == b"var_1b"
|
||||||
assert (dep_tmpdir / "gen_1" / "var_1a").read_bytes() == b"var_1a"
|
assert (temp_dir / "gen_2" / "var_2a").read_bytes() == b"var_2a"
|
||||||
assert (dep_tmpdir / "gen_1" / "var_1b").read_bytes() == b"var_1b"
|
assert (temp_dir / "gen_2" / "var_2b").read_bytes() == b"var_2b"
|
||||||
assert (dep_tmpdir / "gen_2" / "var_2a").read_bytes() == b"var_2a"
|
# ensure the files are not world readable
|
||||||
assert (dep_tmpdir / "gen_2" / "var_2b").read_bytes() == b"var_2b"
|
assert (temp_dir / "gen_1" / "var_1a").stat().st_mode & 0o777 == 0o600
|
||||||
# ensure the files are not world readable
|
assert (temp_dir / "gen_1" / "var_1b").stat().st_mode & 0o777 == 0o600
|
||||||
assert (dep_tmpdir / "gen_1" / "var_1a").stat().st_mode & 0o777 == 0o600
|
assert (temp_dir / "gen_2" / "var_2a").stat().st_mode & 0o777 == 0o600
|
||||||
assert (dep_tmpdir / "gen_1" / "var_1b").stat().st_mode & 0o777 == 0o600
|
assert (temp_dir / "gen_2" / "var_2b").stat().st_mode & 0o777 == 0o600
|
||||||
assert (dep_tmpdir / "gen_2" / "var_2a").stat().st_mode & 0o777 == 0o600
|
|
||||||
assert (dep_tmpdir / "gen_2" / "var_2b").stat().st_mode & 0o777 == 0o600
|
|
||||||
|
|
||||||
|
|
||||||
def test_required_generators() -> None:
|
def test_required_generators() -> None:
|
||||||
|
|||||||
@@ -98,27 +98,34 @@ def test_vm_deployment(
|
|||||||
cmd.run(["nix", "flake", "lock"])
|
cmd.run(["nix", "flake", "lock"])
|
||||||
vm_m1 = run_vm_in_thread("m1_machine")
|
vm_m1 = run_vm_in_thread("m1_machine")
|
||||||
vm_m2 = run_vm_in_thread("m2_machine")
|
vm_m2 = run_vm_in_thread("m2_machine")
|
||||||
qga_m1 = qga_connect("m1_machine", vm_m1)
|
with (
|
||||||
qga_m2 = qga_connect("m2_machine", vm_m2)
|
qga_connect("m1_machine", vm_m1) as qga_m1,
|
||||||
# check my_secret is deployed
|
qga_connect("m2_machine", vm_m2) as qga_m2,
|
||||||
_, out, _ = qga_m1.run("cat /run/secrets/vars/m1_generator/my_secret", check=True)
|
):
|
||||||
assert out == "hello\n"
|
# check my_secret is deployed
|
||||||
# check shared_secret is deployed on m1
|
_, out, _ = qga_m1.run(
|
||||||
_, out, _ = qga_m1.run(
|
"cat /run/secrets/vars/m1_generator/my_secret", check=True
|
||||||
"cat /run/secrets/vars/my_shared_generator/shared_secret", check=True
|
)
|
||||||
)
|
assert out == "hello\n"
|
||||||
assert out == "hello\n"
|
# check shared_secret is deployed on m1
|
||||||
# check shared_secret is deployed on m2
|
_, out, _ = qga_m1.run(
|
||||||
_, out, _ = qga_m2.run(
|
"cat /run/secrets/vars/my_shared_generator/shared_secret", check=True
|
||||||
"cat /run/secrets/vars/my_shared_generator/shared_secret", check=True
|
)
|
||||||
)
|
assert out == "hello\n"
|
||||||
assert out == "hello\n"
|
# check shared_secret is deployed on m2
|
||||||
# check no_deploy_secret is not deployed
|
_, out, _ = qga_m2.run(
|
||||||
returncode, out, _ = qga_m1.run(
|
"cat /run/secrets/vars/my_shared_generator/shared_secret", check=True
|
||||||
"test -e /run/secrets/vars/my_shared_generator/no_deploy_secret", check=False
|
)
|
||||||
)
|
assert out == "hello\n"
|
||||||
assert returncode != 0
|
# check no_deploy_secret is not deployed
|
||||||
qga_m1.exec_cmd("poweroff")
|
returncode, out, _ = qga_m1.run(
|
||||||
qga_m2.exec_cmd("poweroff")
|
"test -e /run/secrets/vars/my_shared_generator/no_deploy_secret",
|
||||||
wait_vm_down("m1_machine", vm_m1)
|
check=False,
|
||||||
wait_vm_down("m2_machine", vm_m2)
|
)
|
||||||
|
assert returncode != 0
|
||||||
|
qga_m1.exec_cmd("poweroff")
|
||||||
|
qga_m2.exec_cmd("poweroff")
|
||||||
|
wait_vm_down("m1_machine", vm_m1)
|
||||||
|
wait_vm_down("m2_machine", vm_m2)
|
||||||
|
vm_m1.join()
|
||||||
|
vm_m2.join()
|
||||||
|
|||||||
@@ -54,46 +54,6 @@ def test_run(
|
|||||||
cli.run(["vms", "run", "vm1"])
|
cli.run(["vms", "run", "vm1"])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(no_kvm, reason="Requires KVM")
|
|
||||||
@pytest.mark.impure
|
|
||||||
def test_vm_qmp(
|
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
|
||||||
temporary_home: Path,
|
|
||||||
) -> None:
|
|
||||||
# set up a simple clan flake
|
|
||||||
flake = generate_flake(
|
|
||||||
temporary_home,
|
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
|
||||||
machine_configs={
|
|
||||||
"my_machine": {
|
|
||||||
"clan": {
|
|
||||||
"virtualisation": {"graphics": False},
|
|
||||||
"networking": {"targetHost": "client"},
|
|
||||||
},
|
|
||||||
"services": {"getty": {"autologinUser": "root"}},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
monkeypatch=monkeypatch,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 'clan vms run' must be executed from within the flake
|
|
||||||
monkeypatch.chdir(flake.path)
|
|
||||||
|
|
||||||
# start the VM
|
|
||||||
vm = run_vm_in_thread("my_machine")
|
|
||||||
|
|
||||||
# connect with qmp
|
|
||||||
qmp = qmp_connect("my_machine", vm)
|
|
||||||
|
|
||||||
# verify that issuing a command works
|
|
||||||
# result = qmp.cmd_obj({"execute": "query-status"})
|
|
||||||
result = qmp.command("query-status")
|
|
||||||
assert result["status"] == "running", result
|
|
||||||
|
|
||||||
# shutdown machine (prevent zombie qemu processes)
|
|
||||||
qmp.command("system_powerdown")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(no_kvm, reason="Requires KVM")
|
@pytest.mark.skipif(no_kvm, reason="Requires KVM")
|
||||||
@pytest.mark.impure
|
@pytest.mark.impure
|
||||||
def test_vm_persistence(
|
def test_vm_persistence(
|
||||||
@@ -130,48 +90,49 @@ def test_vm_persistence(
|
|||||||
vm = run_vm_in_thread("my_machine")
|
vm = run_vm_in_thread("my_machine")
|
||||||
|
|
||||||
# wait for the VM to start and connect qga
|
# wait for the VM to start and connect qga
|
||||||
qga = qga_connect("my_machine", vm)
|
with qga_connect("my_machine", vm) as qga:
|
||||||
|
# create state via qmp command instead of systemd service
|
||||||
|
qga.run("echo 'dream2nix' > /var/my-state/root", check=True)
|
||||||
|
qga.run("echo 'dream2nix' > /var/my-state/test", check=True)
|
||||||
|
qga.run("chown test /var/my-state/test", check=True)
|
||||||
|
qga.run("chown test /var/user-state", check=True)
|
||||||
|
qga.run("touch /var/my-state/rebooting", check=True)
|
||||||
|
qga.exec_cmd("poweroff")
|
||||||
|
|
||||||
# create state via qmp command instead of systemd service
|
# wait for socket to be down (systemd service 'poweroff' rebooting machine)
|
||||||
qga.run("echo 'dream2nix' > /var/my-state/root", check=True)
|
wait_vm_down("my_machine", vm)
|
||||||
qga.run("echo 'dream2nix' > /var/my-state/test", check=True)
|
|
||||||
qga.run("chown test /var/my-state/test", check=True)
|
|
||||||
qga.run("chown test /var/user-state", check=True)
|
|
||||||
qga.run("touch /var/my-state/rebooting", check=True)
|
|
||||||
qga.exec_cmd("poweroff")
|
|
||||||
|
|
||||||
# wait for socket to be down (systemd service 'poweroff' rebooting machine)
|
vm.join()
|
||||||
wait_vm_down("my_machine", vm)
|
|
||||||
|
|
||||||
# start vm again
|
## start vm again
|
||||||
vm = run_vm_in_thread("my_machine")
|
vm = run_vm_in_thread("my_machine")
|
||||||
|
|
||||||
# connect second time
|
## connect second time
|
||||||
qga = qga_connect("my_machine", vm)
|
with qga_connect("my_machine", vm) as qga:
|
||||||
# check state exists
|
# check state exists
|
||||||
qga.run("cat /var/my-state/test", check=True)
|
qga.run("cat /var/my-state/test", check=True)
|
||||||
# ensure root file is owned by root
|
# ensure root file is owned by root
|
||||||
qga.run("stat -c '%U' /var/my-state/root", check=True)
|
qga.run("stat -c '%U' /var/my-state/root", check=True)
|
||||||
# ensure test file is owned by test
|
# ensure test file is owned by test
|
||||||
qga.run("stat -c '%U' /var/my-state/test", check=True)
|
qga.run("stat -c '%U' /var/my-state/test", check=True)
|
||||||
# ensure /var/user-state is owned by test
|
# ensure /var/user-state is owned by test
|
||||||
qga.run("stat -c '%U' /var/user-state", check=True)
|
qga.run("stat -c '%U' /var/user-state", check=True)
|
||||||
|
|
||||||
# ensure that the file created by the service is still there and has the expected content
|
# ensure that the file created by the service is still there and has the expected content
|
||||||
exitcode, out, err = qga.run("cat /var/my-state/test")
|
exitcode, out, err = qga.run("cat /var/my-state/test")
|
||||||
assert exitcode == 0, err
|
assert exitcode == 0, err
|
||||||
assert out == "dream2nix\n", out
|
assert out == "dream2nix\n", out
|
||||||
|
|
||||||
# check for errors
|
# check for errors
|
||||||
exitcode, out, err = qga.run("cat /var/my-state/error")
|
exitcode, out, err = qga.run("cat /var/my-state/error")
|
||||||
assert exitcode == 1, out
|
assert exitcode == 1, out
|
||||||
|
|
||||||
# check all systemd services are OK, or print details
|
# check all systemd services are OK, or print details
|
||||||
exitcode, out, err = qga.run(
|
exitcode, out, err = qga.run(
|
||||||
"systemctl --failed | tee /tmp/yolo | grep -q '0 loaded units listed' || ( cat /tmp/yolo && false )"
|
"systemctl --failed | tee /tmp/yolo | grep -q '0 loaded units listed' || ( cat /tmp/yolo && false )"
|
||||||
)
|
)
|
||||||
assert exitcode == 0, out
|
assert exitcode == 0, out
|
||||||
|
|
||||||
# use qmp to shutdown the machine (prevent zombie qemu processes)
|
with qmp_connect("my_machine", vm) as qmp:
|
||||||
qmp = qmp_connect("my_machine", vm)
|
qmp.command("system_powerdown")
|
||||||
qmp.command("system_powerdown")
|
vm.join()
|
||||||
|
|||||||
Reference in New Issue
Block a user