Merge pull request #94858 from liff/virtualbox-python-test

nixosTests.virtualbox: Port to python
This commit is contained in:
Florian Klink 2020-08-26 10:00:04 +02:00 committed by GitHub
commit df2f22daa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 202 additions and 190 deletions

View File

@ -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 {})