Merge pull request #94858 from liff/virtualbox-python-test
nixosTests.virtualbox: Port to python
This commit is contained in:
commit
df2f22daa8
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
assert use64bitGuest -> useKvmNestedVirt;
|
assert use64bitGuest -> useKvmNestedVirt;
|
||||||
|
|
||||||
with import ../lib/testing.nix { inherit system pkgs; };
|
with import ../lib/testing-python.nix { inherit system pkgs; };
|
||||||
with pkgs.lib;
|
with pkgs.lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
|
@ -91,13 +91,15 @@ let
|
||||||
(isYes "SERIAL_8250_CONSOLE")
|
(isYes "SERIAL_8250_CONSOLE")
|
||||||
(isYes "SERIAL_8250")
|
(isYes "SERIAL_8250")
|
||||||
];
|
];
|
||||||
|
|
||||||
|
networking.usePredictableInterfaceNames = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
mkLog = logfile: tag: let
|
mkLog = logfile: tag: let
|
||||||
rotated = map (i: "${logfile}.${toString i}") (range 1 9);
|
rotated = map (i: "${logfile}.${toString i}") (range 1 9);
|
||||||
all = concatMapStringsSep " " (f: "\"${f}\"") ([logfile] ++ rotated);
|
all = concatMapStringsSep " " (f: "\"${f}\"") ([logfile] ++ rotated);
|
||||||
logcmd = "tail -F ${all} 2> /dev/null | logger -t \"${tag}\"";
|
logcmd = "tail -F ${all} 2> /dev/null | logger -t \"${tag}\"";
|
||||||
in optionalString debug "$machine->execute(ru '${logcmd} & disown');";
|
in if debug then "machine.execute(ru('${logcmd} & disown'))" else "pass";
|
||||||
|
|
||||||
testVM = vmName: vmScript: let
|
testVM = vmName: vmScript: let
|
||||||
cfg = (import ../lib/eval-config.nix {
|
cfg = (import ../lib/eval-config.nix {
|
||||||
|
@ -204,96 +206,105 @@ let
|
||||||
};
|
};
|
||||||
|
|
||||||
testSubs = ''
|
testSubs = ''
|
||||||
my ${"$" + name}_sharepath = '${sharePath}';
|
|
||||||
|
|
||||||
sub checkRunning_${name} {
|
|
||||||
my $cmd = 'VBoxManage list runningvms | grep -q "^\"${name}\""';
|
|
||||||
my ($status, $out) = $machine->execute(ru $cmd);
|
|
||||||
return $status == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub cleanup_${name} {
|
${name}_sharepath = "${sharePath}"
|
||||||
$machine->execute(ru "VBoxManage controlvm ${name} poweroff")
|
|
||||||
if checkRunning_${name};
|
|
||||||
$machine->succeed("rm -rf ${sharePath}");
|
|
||||||
$machine->succeed("mkdir -p ${sharePath}");
|
|
||||||
$machine->succeed("chown alice.users ${sharePath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
sub createVM_${name} {
|
|
||||||
vbm("createvm --name ${name} ${createFlags}");
|
|
||||||
vbm("modifyvm ${name} ${vmFlags}");
|
|
||||||
vbm("setextradata ${name} VBoxInternal/PDM/HaltOnReset 1");
|
|
||||||
vbm("storagectl ${name} ${controllerFlags}");
|
|
||||||
vbm("storageattach ${name} ${diskFlags}");
|
|
||||||
vbm("sharedfolder add ${name} ${sharedFlags}");
|
|
||||||
vbm("sharedfolder add ${name} ${nixstoreFlags}");
|
|
||||||
cleanup_${name};
|
|
||||||
|
|
||||||
${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"}
|
def check_running_${name}():
|
||||||
}
|
cmd = "VBoxManage list runningvms | grep -q '^\"${name}\"'"
|
||||||
|
(status, _) = machine.execute(ru(cmd))
|
||||||
|
return status == 0
|
||||||
|
|
||||||
sub destroyVM_${name} {
|
|
||||||
cleanup_${name};
|
|
||||||
vbm("unregistervm ${name} --delete");
|
|
||||||
}
|
|
||||||
|
|
||||||
sub waitForVMBoot_${name} {
|
def cleanup_${name}():
|
||||||
$machine->execute(ru(
|
if check_running_${name}():
|
||||||
'set -e; i=0; '.
|
machine.execute(ru("VBoxManage controlvm ${name} poweroff"))
|
||||||
'while ! test -e ${sharePath}/boot-done; do '.
|
machine.succeed("rm -rf ${sharePath}")
|
||||||
'sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; '.
|
machine.succeed("mkdir -p ${sharePath}")
|
||||||
'VBoxManage list runningvms | grep -q "^\"${name}\""; '.
|
machine.succeed("chown alice.users ${sharePath}")
|
||||||
'done'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
sub waitForIP_${name} ($) {
|
|
||||||
my $property = "/VirtualBox/GuestInfo/Net/$_[0]/V4/IP";
|
|
||||||
my $getip = "VBoxManage guestproperty get ${name} $property | ".
|
|
||||||
"sed -n -e 's/^Value: //p'";
|
|
||||||
my $ip = $machine->succeed(ru(
|
|
||||||
'for i in $(seq 1000); do '.
|
|
||||||
'if ipaddr="$('.$getip.')" && [ -n "$ipaddr" ]; then '.
|
|
||||||
'echo "$ipaddr"; exit 0; '.
|
|
||||||
'fi; '.
|
|
||||||
'sleep 1; '.
|
|
||||||
'done; '.
|
|
||||||
'echo "Could not get IPv4 address for ${name}!" >&2; '.
|
|
||||||
'exit 1'
|
|
||||||
));
|
|
||||||
chomp $ip;
|
|
||||||
return $ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub waitForStartup_${name} {
|
def create_vm_${name}():
|
||||||
for (my $i = 0; $i <= 120; $i += 10) {
|
# fmt: off
|
||||||
$machine->sleep(10);
|
vbm(f"createvm --name ${name} ${createFlags}")
|
||||||
return if checkRunning_${name};
|
vbm(f"modifyvm ${name} ${vmFlags}")
|
||||||
eval { $_[0]->() } if defined $_[0];
|
vbm(f"setextradata ${name} VBoxInternal/PDM/HaltOnReset 1")
|
||||||
}
|
vbm(f"storagectl ${name} ${controllerFlags}")
|
||||||
die "VirtualBox VM didn't start up within 2 minutes";
|
vbm(f"storageattach ${name} ${diskFlags}")
|
||||||
}
|
vbm(f"sharedfolder add ${name} ${sharedFlags}")
|
||||||
|
vbm(f"sharedfolder add ${name} ${nixstoreFlags}")
|
||||||
|
cleanup_${name}()
|
||||||
|
|
||||||
sub waitForShutdown_${name} {
|
${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"}
|
||||||
for (my $i = 0; $i <= 120; $i += 10) {
|
# fmt: on
|
||||||
$machine->sleep(10);
|
|
||||||
return unless checkRunning_${name};
|
|
||||||
}
|
|
||||||
die "VirtualBox VM didn't shut down within 2 minutes";
|
|
||||||
}
|
|
||||||
|
|
||||||
sub shutdownVM_${name} {
|
|
||||||
$machine->succeed(ru "touch ${sharePath}/shutdown");
|
def destroy_vm_${name}():
|
||||||
$machine->execute(
|
cleanup_${name}()
|
||||||
'set -e; i=0; '.
|
vbm("unregistervm ${name} --delete")
|
||||||
'while test -e ${sharePath}/shutdown '.
|
|
||||||
' -o -e ${sharePath}/boot-done; do '.
|
|
||||||
'sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; '.
|
def wait_for_vm_boot_${name}():
|
||||||
'done'
|
machine.execute(
|
||||||
);
|
ru(
|
||||||
waitForShutdown_${name};
|
"set -e; i=0; "
|
||||||
}
|
"while ! test -e ${sharePath}/boot-done; do "
|
||||||
|
"sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; "
|
||||||
|
"VBoxManage list runningvms | grep -q '^\"${name}\"'; "
|
||||||
|
"done"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_ip_${name}(interface):
|
||||||
|
property = f"/VirtualBox/GuestInfo/Net/{interface}/V4/IP"
|
||||||
|
# fmt: off
|
||||||
|
getip = f"VBoxManage guestproperty get ${name} {property} | sed -n -e 's/^Value: //p'"
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
ip = machine.succeed(
|
||||||
|
ru(
|
||||||
|
"for i in $(seq 1000); do "
|
||||||
|
f'if ipaddr="$({getip})" && [ -n "$ipaddr" ]; then '
|
||||||
|
'echo "$ipaddr"; exit 0; '
|
||||||
|
"fi; "
|
||||||
|
"sleep 1; "
|
||||||
|
"done; "
|
||||||
|
"echo 'Could not get IPv4 address for ${name}!' >&2; "
|
||||||
|
"exit 1"
|
||||||
|
)
|
||||||
|
).strip()
|
||||||
|
return ip
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_startup_${name}(nudge=lambda: None):
|
||||||
|
for _ in range(0, 130, 10):
|
||||||
|
machine.sleep(10)
|
||||||
|
if check_running_${name}():
|
||||||
|
return
|
||||||
|
nudge()
|
||||||
|
raise Exception("VirtualBox VM didn't start up within 2 minutes")
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_shutdown_${name}():
|
||||||
|
for _ in range(0, 130, 10):
|
||||||
|
machine.sleep(10)
|
||||||
|
if not check_running_${name}():
|
||||||
|
return
|
||||||
|
raise Exception("VirtualBox VM didn't shut down within 2 minutes")
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown_vm_${name}():
|
||||||
|
machine.succeed(ru("touch ${sharePath}/shutdown"))
|
||||||
|
machine.execute(
|
||||||
|
"set -e; i=0; "
|
||||||
|
"while test -e ${sharePath}/shutdown "
|
||||||
|
" -o -e ${sharePath}/boot-done; do "
|
||||||
|
"sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; "
|
||||||
|
"done"
|
||||||
|
)
|
||||||
|
wait_for_shutdown_${name}()
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -364,26 +375,31 @@ let
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
sub ru ($) {
|
from shlex import quote
|
||||||
my $esc = $_[0] =~ s/'/'\\${"'"}'/gr;
|
|
||||||
return "su - alice -c '$esc'";
|
|
||||||
}
|
|
||||||
|
|
||||||
sub vbm {
|
|
||||||
$machine->succeed(ru("VBoxManage ".$_[0]));
|
|
||||||
};
|
|
||||||
|
|
||||||
sub removeUUIDs {
|
|
||||||
return join("\n", grep { $_ !~ /^UUID:/ } split(/\n/, $_[0]))."\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
${concatStrings (mapAttrsToList (_: getAttr "testSubs") vms)}
|
${concatStrings (mapAttrsToList (_: getAttr "testSubs") vms)}
|
||||||
|
|
||||||
$machine->waitForX;
|
def ru(cmd: str) -> str:
|
||||||
|
return f"su - alice -c {quote(cmd)}"
|
||||||
|
|
||||||
|
|
||||||
|
def vbm(cmd: str) -> str:
|
||||||
|
return machine.succeed(ru(f"VBoxManage {cmd}"))
|
||||||
|
|
||||||
|
|
||||||
|
def remove_uuids(output: str) -> str:
|
||||||
|
return "\n".join(
|
||||||
|
[line for line in (output or "").splitlines() if not line.startswith("UUID:")]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
machine.wait_for_x()
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"}
|
${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"}
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
${testScript}
|
${testScript}
|
||||||
|
# (keep black happy)
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
|
@ -393,133 +409,129 @@ let
|
||||||
|
|
||||||
unfreeTests = mapAttrs (mkVBoxTest true vboxVMsWithExtpack) {
|
unfreeTests = mapAttrs (mkVBoxTest true vboxVMsWithExtpack) {
|
||||||
enable-extension-pack = ''
|
enable-extension-pack = ''
|
||||||
createVM_testExtensionPack;
|
create_vm_testExtensionPack()
|
||||||
vbm("startvm testExtensionPack");
|
vbm("startvm testExtensionPack")
|
||||||
waitForStartup_testExtensionPack;
|
wait_for_startup_testExtensionPack()
|
||||||
$machine->screenshot("cli_started");
|
machine.screenshot("cli_started")
|
||||||
waitForVMBoot_testExtensionPack;
|
wait_for_vm_boot_testExtensionPack()
|
||||||
$machine->screenshot("cli_booted");
|
machine.screenshot("cli_booted")
|
||||||
|
|
||||||
$machine->nest("Checking for privilege escalation", sub {
|
with machine.nested("Checking for privilege escalation"):
|
||||||
$machine->fail("test -e '/root/VirtualBox VMs'");
|
machine.fail("test -e '/root/VirtualBox VMs'")
|
||||||
$machine->fail("test -e '/root/.config/VirtualBox'");
|
machine.fail("test -e '/root/.config/VirtualBox'")
|
||||||
$machine->succeed("test -e '/home/alice/VirtualBox VMs'");
|
machine.succeed("test -e '/home/alice/VirtualBox VMs'")
|
||||||
});
|
|
||||||
|
|
||||||
shutdownVM_testExtensionPack;
|
shutdown_vm_testExtensionPack()
|
||||||
destroyVM_testExtensionPack;
|
destroy_vm_testExtensionPack()
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
in mapAttrs (mkVBoxTest false vboxVMs) {
|
in mapAttrs (mkVBoxTest false vboxVMs) {
|
||||||
simple-gui = ''
|
simple-gui = ''
|
||||||
createVM_simple;
|
|
||||||
$machine->succeed(ru "VirtualBox &");
|
|
||||||
$machine->waitUntilSucceeds(
|
|
||||||
ru "xprop -name 'Oracle VM VirtualBox Manager'"
|
|
||||||
);
|
|
||||||
$machine->sleep(5);
|
|
||||||
$machine->screenshot("gui_manager_started");
|
|
||||||
# Home to select Tools, down to move to the VM, enter to start it.
|
# Home to select Tools, down to move to the VM, enter to start it.
|
||||||
$machine->sendKeys("home");
|
def send_vm_startup():
|
||||||
$machine->sendKeys("down");
|
machine.send_key("home")
|
||||||
$machine->sendKeys("ret");
|
machine.send_key("down")
|
||||||
$machine->screenshot("gui_manager_sent_startup");
|
machine.send_key("ret")
|
||||||
waitForStartup_simple (sub {
|
|
||||||
$machine->sendKeys("home");
|
|
||||||
$machine->sendKeys("down");
|
create_vm_simple()
|
||||||
$machine->sendKeys("ret");
|
machine.succeed(ru("VirtualBox &"))
|
||||||
});
|
machine.wait_until_succeeds(ru("xprop -name 'Oracle VM VirtualBox Manager'"))
|
||||||
$machine->screenshot("gui_started");
|
machine.sleep(5)
|
||||||
waitForVMBoot_simple;
|
machine.screenshot("gui_manager_started")
|
||||||
$machine->screenshot("gui_booted");
|
send_vm_startup()
|
||||||
shutdownVM_simple;
|
machine.screenshot("gui_manager_sent_startup")
|
||||||
$machine->sleep(5);
|
wait_for_startup_simple(send_vm_startup)
|
||||||
$machine->screenshot("gui_stopped");
|
machine.screenshot("gui_started")
|
||||||
$machine->sendKeys("ctrl-q");
|
wait_for_vm_boot_simple()
|
||||||
$machine->sleep(5);
|
machine.screenshot("gui_booted")
|
||||||
$machine->screenshot("gui_manager_stopped");
|
shutdown_vm_simple()
|
||||||
destroyVM_simple;
|
machine.sleep(5)
|
||||||
|
machine.screenshot("gui_stopped")
|
||||||
|
machine.send_key("ctrl-q")
|
||||||
|
machine.sleep(5)
|
||||||
|
machine.screenshot("gui_manager_stopped")
|
||||||
|
destroy_vm_simple()
|
||||||
'';
|
'';
|
||||||
|
|
||||||
simple-cli = ''
|
simple-cli = ''
|
||||||
createVM_simple;
|
create_vm_simple()
|
||||||
vbm("startvm simple");
|
vbm("startvm simple")
|
||||||
waitForStartup_simple;
|
wait_for_startup_simple()
|
||||||
$machine->screenshot("cli_started");
|
machine.screenshot("cli_started")
|
||||||
waitForVMBoot_simple;
|
wait_for_vm_boot_simple()
|
||||||
$machine->screenshot("cli_booted");
|
machine.screenshot("cli_booted")
|
||||||
|
|
||||||
$machine->nest("Checking for privilege escalation", sub {
|
with machine.nested("Checking for privilege escalation"):
|
||||||
$machine->fail("test -e '/root/VirtualBox VMs'");
|
machine.fail("test -e '/root/VirtualBox VMs'")
|
||||||
$machine->fail("test -e '/root/.config/VirtualBox'");
|
machine.fail("test -e '/root/.config/VirtualBox'")
|
||||||
$machine->succeed("test -e '/home/alice/VirtualBox VMs'");
|
machine.succeed("test -e '/home/alice/VirtualBox VMs'")
|
||||||
});
|
|
||||||
|
|
||||||
shutdownVM_simple;
|
shutdown_vm_simple()
|
||||||
destroyVM_simple;
|
destroy_vm_simple()
|
||||||
'';
|
'';
|
||||||
|
|
||||||
headless = ''
|
headless = ''
|
||||||
createVM_headless;
|
create_vm_headless()
|
||||||
$machine->succeed(ru("VBoxHeadless --startvm headless & disown %1"));
|
machine.succeed(ru("VBoxHeadless --startvm headless & disown %1"))
|
||||||
waitForStartup_headless;
|
wait_for_startup_headless()
|
||||||
waitForVMBoot_headless;
|
wait_for_vm_boot_headless()
|
||||||
shutdownVM_headless;
|
shutdown_vm_headless()
|
||||||
destroyVM_headless;
|
destroy_vm_headless()
|
||||||
'';
|
'';
|
||||||
|
|
||||||
host-usb-permissions = ''
|
host-usb-permissions = ''
|
||||||
my $userUSB = removeUUIDs vbm("list usbhost");
|
user_usb = remove_uuids(vbm("list usbhost"))
|
||||||
print STDERR $userUSB;
|
print(user_usb, file=sys.stderr)
|
||||||
my $rootUSB = removeUUIDs $machine->succeed("VBoxManage list usbhost");
|
root_usb = remove_uuids(machine.succeed("VBoxManage list usbhost"))
|
||||||
print STDERR $rootUSB;
|
print(root_usb, file=sys.stderr)
|
||||||
|
|
||||||
die "USB host devices differ for root and normal user"
|
if user_usb != root_usb:
|
||||||
if $userUSB ne $rootUSB;
|
raise Exception("USB host devices differ for root and normal user")
|
||||||
die "No USB host devices found" if $userUSB =~ /<none>/;
|
if "<none>" in user_usb:
|
||||||
|
raise Exception("No USB host devices found")
|
||||||
'';
|
'';
|
||||||
|
|
||||||
systemd-detect-virt = ''
|
systemd-detect-virt = ''
|
||||||
createVM_detectvirt;
|
create_vm_detectvirt()
|
||||||
vbm("startvm detectvirt");
|
vbm("startvm detectvirt")
|
||||||
waitForStartup_detectvirt;
|
wait_for_startup_detectvirt()
|
||||||
waitForVMBoot_detectvirt;
|
wait_for_vm_boot_detectvirt()
|
||||||
shutdownVM_detectvirt;
|
shutdown_vm_detectvirt()
|
||||||
my $result = $machine->succeed("cat '$detectvirt_sharepath/result'");
|
result = machine.succeed(f"cat '{detectvirt_sharepath}/result'").strip()
|
||||||
chomp $result;
|
destroy_vm_detectvirt()
|
||||||
destroyVM_detectvirt;
|
if result != "oracle":
|
||||||
die "systemd-detect-virt returned \"$result\" instead of \"oracle\""
|
raise Exception(f'systemd-detect-virt returned "{result}" instead of "oracle"')
|
||||||
if $result ne "oracle";
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
net-hostonlyif = ''
|
net-hostonlyif = ''
|
||||||
createVM_test1;
|
create_vm_test1()
|
||||||
createVM_test2;
|
create_vm_test2()
|
||||||
|
|
||||||
vbm("startvm test1");
|
vbm("startvm test1")
|
||||||
waitForStartup_test1;
|
wait_for_startup_test1()
|
||||||
waitForVMBoot_test1;
|
wait_for_vm_boot_test1()
|
||||||
|
|
||||||
vbm("startvm test2");
|
vbm("startvm test2")
|
||||||
waitForStartup_test2;
|
wait_for_startup_test2()
|
||||||
waitForVMBoot_test2;
|
wait_for_vm_boot_test2()
|
||||||
|
|
||||||
$machine->screenshot("net_booted");
|
machine.screenshot("net_booted")
|
||||||
|
|
||||||
my $test1IP = waitForIP_test1 1;
|
test1_ip = wait_for_ip_test1(1)
|
||||||
my $test2IP = waitForIP_test2 1;
|
test2_ip = wait_for_ip_test2(1)
|
||||||
|
|
||||||
$machine->succeed("echo '$test2IP' | nc -N '$test1IP' 1234");
|
machine.succeed(f"echo '{test2_ip}' | nc -N '{test1_ip}' 1234")
|
||||||
$machine->succeed("echo '$test1IP' | nc -N '$test2IP' 1234");
|
machine.succeed(f"echo '{test1_ip}' | nc -N '{test2_ip}' 1234")
|
||||||
|
|
||||||
$machine->waitUntilSucceeds("nc -N '$test1IP' 5678 < /dev/null >&2");
|
machine.wait_until_succeeds(f"nc -N '{test1_ip}' 5678 < /dev/null >&2")
|
||||||
$machine->waitUntilSucceeds("nc -N '$test2IP' 5678 < /dev/null >&2");
|
machine.wait_until_succeeds(f"nc -N '{test2_ip}' 5678 < /dev/null >&2")
|
||||||
|
|
||||||
shutdownVM_test1;
|
shutdown_vm_test1()
|
||||||
shutdownVM_test2;
|
shutdown_vm_test2()
|
||||||
|
|
||||||
destroyVM_test1;
|
destroy_vm_test1()
|
||||||
destroyVM_test2;
|
destroy_vm_test2()
|
||||||
'';
|
'';
|
||||||
} // (if enableUnfree then unfreeTests else {})
|
} // (if enableUnfree then unfreeTests else {})
|
||||||
|
|
Loading…
Reference in New Issue