VMs: port vm_persistence test to python
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{ self, ... }: {
|
{ ... }: {
|
||||||
perSystem = { pkgs, lib, self', ... }: {
|
perSystem = { pkgs, lib, ... }: {
|
||||||
packages = rec {
|
packages = rec {
|
||||||
# a script that executes all other checks
|
# a script that executes all other checks
|
||||||
impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
||||||
@@ -13,86 +13,9 @@
|
|||||||
]}"
|
]}"
|
||||||
ROOT=$(git rev-parse --show-toplevel)
|
ROOT=$(git rev-parse --show-toplevel)
|
||||||
cd "$ROOT/pkgs/clan-cli"
|
cd "$ROOT/pkgs/clan-cli"
|
||||||
${self'.packages.vm-persistence}/bin/vm-persistence
|
|
||||||
nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -m impure ./tests $@"
|
nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -m impure ./tests $@"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# TODO: port this to python and make it pure
|
|
||||||
vm-persistence =
|
|
||||||
let
|
|
||||||
machineConfigFile = builtins.toFile "vm-config.json" (builtins.toJSON {
|
|
||||||
clanCore.state.my-state = {
|
|
||||||
folders = [ "/var/my-state" ];
|
|
||||||
};
|
|
||||||
# powers off the machine after the state is created
|
|
||||||
systemd.services.poweroff = {
|
|
||||||
description = "Poweroff the machine";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "my-state.service" ];
|
|
||||||
script = ''
|
|
||||||
echo "Powering off the machine"
|
|
||||||
poweroff
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
# creates a file in the state folder
|
|
||||||
systemd.services.my-state = {
|
|
||||||
description = "Create a file in the state folder";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
script = ''
|
|
||||||
echo "Creating a file in the state folder"
|
|
||||||
echo "dream2nix" > /var/my-state/test
|
|
||||||
'';
|
|
||||||
serviceConfig.Type = "oneshot";
|
|
||||||
};
|
|
||||||
clan.virtualisation.graphics = false;
|
|
||||||
users.users.root.password = "root";
|
|
||||||
});
|
|
||||||
in
|
|
||||||
pkgs.writeShellScriptBin "vm-persistence" ''
|
|
||||||
#!${pkgs.bash}/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export PATH="${lib.makeBinPath [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.gitMinimal
|
|
||||||
pkgs.jq
|
|
||||||
pkgs.nix
|
|
||||||
pkgs.gnused
|
|
||||||
self'.packages.clan-cli
|
|
||||||
]}"
|
|
||||||
|
|
||||||
clanName=_test_vm_persistence
|
|
||||||
testFile=~/".config/clan/vmstate/$clanName/my-machine/var/my-state/test"
|
|
||||||
|
|
||||||
export TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d)
|
|
||||||
trap "${pkgs.coreutils}/bin/chmod -R +w '$TMPDIR'; ${pkgs.coreutils}/bin/rm -rf '$TMPDIR'" EXIT
|
|
||||||
|
|
||||||
# clean up vmstate after test
|
|
||||||
trap "${pkgs.coreutils}/bin/rm -rf ~/.config/clan/vmstate/$clanName" EXIT
|
|
||||||
|
|
||||||
cd $TMPDIR
|
|
||||||
mkdir ./clan
|
|
||||||
cd ./clan
|
|
||||||
nix flake init -t ${self}#templates.new-clan
|
|
||||||
nix flake lock --override-input clan-core ${self}
|
|
||||||
sed -i "s/__CHANGE_ME__/$clanName/g" flake.nix
|
|
||||||
clan machines create my-machine
|
|
||||||
|
|
||||||
cat ${machineConfigFile} | jq > ./machines/my-machine/settings.json
|
|
||||||
|
|
||||||
# clear state from previous runs
|
|
||||||
rm -rf "$testFile"
|
|
||||||
|
|
||||||
# machine will automatically shutdown due to the shutdown service above
|
|
||||||
clan vms run my-machine
|
|
||||||
|
|
||||||
set -x
|
|
||||||
if ! test -e "$testFile"; then
|
|
||||||
echo "failed: file "$testFile" was not created"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
runMockApi = pkgs.writeShellScriptBin "run-mock-api" ''
|
runMockApi = pkgs.writeShellScriptBin "run-mock-api" ''
|
||||||
#!${pkgs.bash}/bin/bash
|
#!${pkgs.bash}/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import fileinput
|
import fileinput
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@@ -37,22 +38,88 @@ class FlakeForTest(NamedTuple):
|
|||||||
path: Path
|
path: Path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_flake(
|
||||||
|
temporary_home: Path,
|
||||||
|
flake_template: Path,
|
||||||
|
substitutions: dict[str, str] = {},
|
||||||
|
# define the machines directly including their config
|
||||||
|
machine_configs: dict[str, dict] = {},
|
||||||
|
) -> FlakeForTest:
|
||||||
|
"""
|
||||||
|
Creates a clan flake with the given name.
|
||||||
|
Machines are fully generated from the machine_configs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
machine_configs = dict(
|
||||||
|
my_machine=dict(
|
||||||
|
clanCore=dict(
|
||||||
|
backups=dict(
|
||||||
|
...
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# copy the template to a new temporary location
|
||||||
|
flake = temporary_home / "flake"
|
||||||
|
shutil.copytree(flake_template, flake)
|
||||||
|
|
||||||
|
# substitute `substitutions` in all files of the template
|
||||||
|
for file in flake.rglob("*"):
|
||||||
|
if file.is_file():
|
||||||
|
print(f"Final Content of {file}:")
|
||||||
|
for line in fileinput.input(file, inplace=True):
|
||||||
|
for key, value in substitutions.items():
|
||||||
|
line = line.replace(key, value)
|
||||||
|
print(line, end="")
|
||||||
|
|
||||||
|
# generate machines from machineConfigs
|
||||||
|
for machine_name, machine_config in machine_configs.items():
|
||||||
|
settings_path = flake / "machines" / machine_name / "settings.json"
|
||||||
|
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
settings_path.write_text(json.dumps(machine_config, indent=2))
|
||||||
|
|
||||||
|
if "/tmp" not in str(os.environ.get("HOME")):
|
||||||
|
log.warning(
|
||||||
|
f"!! $HOME does not point to a temp directory!! HOME={os.environ['HOME']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: Find out why test_vms_api.py fails in nix build
|
||||||
|
# but works in pytest when this bottom line is commented out
|
||||||
|
sp.run(
|
||||||
|
["git", "config", "--global", "init.defaultBranch", "main"],
|
||||||
|
cwd=flake,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
sp.run(["git", "init"], cwd=flake, check=True)
|
||||||
|
sp.run(["git", "add", "."], cwd=flake, check=True)
|
||||||
|
sp.run(["git", "config", "user.name", "clan-tool"], cwd=flake, check=True)
|
||||||
|
sp.run(["git", "config", "user.email", "clan@example.com"], cwd=flake, check=True)
|
||||||
|
sp.run(["git", "commit", "-a", "-m", "Initial commit"], cwd=flake, check=True)
|
||||||
|
|
||||||
|
return FlakeForTest(flake)
|
||||||
|
|
||||||
|
|
||||||
def create_flake(
|
def create_flake(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
flake_name: str,
|
flake_template_name: str,
|
||||||
clan_core_flake: Path | None = None,
|
clan_core_flake: Path | None = None,
|
||||||
|
# names referring to pre-defined machines from ../machines
|
||||||
machines: list[str] = [],
|
machines: list[str] = [],
|
||||||
|
# alternatively specify the machines directly including their config
|
||||||
|
machine_configs: dict[str, dict] = {},
|
||||||
remote: bool = False,
|
remote: bool = False,
|
||||||
) -> Iterator[FlakeForTest]:
|
) -> Iterator[FlakeForTest]:
|
||||||
"""
|
"""
|
||||||
Creates a flake with the given name and machines.
|
Creates a flake with the given name and machines.
|
||||||
The machine names map to the machines in ./test_machines
|
The machine names map to the machines in ./test_machines
|
||||||
"""
|
"""
|
||||||
template = Path(__file__).parent / flake_name
|
template = Path(__file__).parent / flake_template_name
|
||||||
|
|
||||||
# copy the template to a new temporary location
|
# copy the template to a new temporary location
|
||||||
flake = temporary_home / flake_name
|
flake = temporary_home / flake_template_name
|
||||||
shutil.copytree(template, flake)
|
shutil.copytree(template, flake)
|
||||||
|
|
||||||
# lookup the requested machines in ./test_machines and include them
|
# lookup the requested machines in ./test_machines and include them
|
||||||
@@ -62,6 +129,13 @@ def create_flake(
|
|||||||
machine_path = Path(__file__).parent / "machines" / machine_name
|
machine_path = Path(__file__).parent / "machines" / machine_name
|
||||||
shutil.copytree(machine_path, flake / "machines" / machine_name)
|
shutil.copytree(machine_path, flake / "machines" / machine_name)
|
||||||
substitute(flake / "machines" / machine_name / "default.nix", flake)
|
substitute(flake / "machines" / machine_name / "default.nix", flake)
|
||||||
|
|
||||||
|
# generate machines from machineConfigs
|
||||||
|
for machine_name, machine_config in machine_configs.items():
|
||||||
|
settings_path = flake / "machines" / machine_name / "settings.json"
|
||||||
|
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
settings_path.write_text(json.dumps(machine_config, indent=2))
|
||||||
|
|
||||||
# in the flake.nix file replace the string __CLAN_URL__ with the the clan flake
|
# in the flake.nix file replace the string __CLAN_URL__ with the the clan flake
|
||||||
# provided by get_test_flake_toplevel
|
# provided by get_test_flake_toplevel
|
||||||
flake_nix = flake / "flake.nix"
|
flake_nix = flake / "flake.nix"
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from cli import Cli
|
from cli import Cli
|
||||||
from fixtures_flakes import FlakeForTest
|
from fixtures_flakes import FlakeForTest, generate_flake
|
||||||
|
from root import CLAN_CORE
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from age_keys import KeyPair
|
from age_keys import KeyPair
|
||||||
@@ -41,3 +43,63 @@ 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_persistence(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
temporary_home: Path,
|
||||||
|
) -> None:
|
||||||
|
flake = generate_flake(
|
||||||
|
temporary_home,
|
||||||
|
flake_template=CLAN_CORE / "templates" / "new-clan",
|
||||||
|
substitutions=dict(
|
||||||
|
__CHANGE_ME__="_test_vm_persistence",
|
||||||
|
),
|
||||||
|
machine_configs=dict(
|
||||||
|
my_machine=dict(
|
||||||
|
clanCore=dict(state=dict(my_state=dict(folders=["/var/my-state"]))),
|
||||||
|
systemd=dict(
|
||||||
|
services=dict(
|
||||||
|
poweroff=dict(
|
||||||
|
description="Poweroff the machine",
|
||||||
|
wantedBy=["multi-user.target"],
|
||||||
|
after=["my-state.service"],
|
||||||
|
script="""
|
||||||
|
echo "Powering off the machine"
|
||||||
|
poweroff
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
my_state=dict(
|
||||||
|
description="Create a file in the state folder",
|
||||||
|
wantedBy=["multi-user.target"],
|
||||||
|
script="""
|
||||||
|
echo "Creating a file in the state folder"
|
||||||
|
echo "dream2nix" > /var/my-state/test
|
||||||
|
""",
|
||||||
|
serviceConfig=dict(Type="oneshot"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
clan=dict(virtualisation=dict(graphics=False)),
|
||||||
|
users=dict(users=dict(root=dict(password="root"))),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
monkeypatch.chdir(flake.path)
|
||||||
|
Cli().run(["vms", "run", "my_machine"])
|
||||||
|
|
||||||
|
test_file = (
|
||||||
|
temporary_home
|
||||||
|
/ ".config"
|
||||||
|
/ "clan"
|
||||||
|
/ "vmstate"
|
||||||
|
/ "_test_vm_persistence"
|
||||||
|
/ "my_machine"
|
||||||
|
/ "var"
|
||||||
|
/ "my-state"
|
||||||
|
/ "test"
|
||||||
|
)
|
||||||
|
assert test_file.exists()
|
||||||
|
assert test_file.read_text() == "dream2nix\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user