tests: nbd - convert to python

Signed-off-by: Susant Sahani <ssahani@redhat.com>
This commit is contained in:
Susant Sahani
2025-12-03 15:02:02 +05:30
committed by rwmjones
parent fff03b7135
commit 1387e7f6a8
3 changed files with 233 additions and 133 deletions

View File

@@ -521,8 +521,8 @@ mount_local_test_parallel_mount_local_LDADD = \
endif
TESTS += nbd/test-nbd.pl
EXTRA_DIST += nbd/test-nbd.pl
TESTS += nbd/test-nbd.py
EXTRA_DIST += nbd/test-nbd.py
TESTS += network/test-network.sh
EXTRA_DIST += network/test-network.sh

View File

@@ -1,131 +0,0 @@
#!/usr/bin/env perl
# Copyright (C) 2013 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use strict;
use warnings;
use POSIX qw(getcwd);
use Sys::Guestfs;
my $pid = 0;
END { kill 15, $pid if $pid > 0 };
exit 77 if $ENV{SKIP_TEST_NBD_PL};
# Check we have qemu-nbd.
if (system ("qemu-nbd --help >/dev/null 2>&1") != 0) {
print "$0: test skipped because qemu-nbd program not found\n";
exit 77
}
# Make a local copy of the disk so we can open it for writes.
my $disk = "../test-data/phony-guests/fedora.img";
if (! -r $disk || -z $disk) {
print "$0: test skipped because $disk is not found\n";
exit 77
}
system ("cp $disk fedora-nbd.img") == 0 || die;
$disk = "fedora-nbd.img";
my $has_format_opt = system ("qemu-nbd --help | grep -q -- --format") == 0;
sub run_test {
my $readonly = shift;
my $tcp = shift;
my $cwd = getcwd ();
my $server;
my $socket;
my $pidfile = "$cwd/nbd/nbd.pid";
unlink "$pidfile";
my @qemu_nbd = ("qemu-nbd", $disk, "-t", "--pid-file", $pidfile);
if ($has_format_opt) {
push @qemu_nbd, "--format", "raw";
}
if ($tcp) {
# Choose a random port number. XXX Should check it is not in use.
my $port = int (60000 + rand (5000));
push @qemu_nbd, "-p", $port;
$server = "localhost:$port";
}
else {
# qemu-nbd insists the socket path is absolute.
$socket = "$cwd/nbd/unix.sock";
unlink "$socket";
push @qemu_nbd, "-k", "$socket";
$server = "unix:$socket";
}
# Run the NBD server.
print "Starting ", join (" ", @qemu_nbd), " ...\n";
$pid = fork ();
if ($pid == 0) {
exec (@qemu_nbd);
die "qemu-nbd: $!";
}
# Wait for the pid file to appear.
for (my $i = 0; $i < 60; ++$i) {
last if -f $pidfile;
sleep 1
}
die "qemu-nbd did not start up\n" if ! -f $pidfile;
# libvirt does not set selinux label on passed in server sockets.
# Try relabelling here but don't require it to succeed, maybe
# selinux is disabled etc.
if ($socket) {
system ("chcon -vt svirt_image_t $socket");
}
my $g = Sys::Guestfs->new ();
# Add an NBD drive.
$g->add_drive ("", readonly => $readonly, format => "raw",
protocol => "nbd", server => [$server]);
# This dies if qemu cannot connect to the NBD server.
$g->launch ();
# Inspection is quite a thorough test:
my @roots = $g->inspect_os ();
die "roots should be a 1-sized array" unless @roots == 1;
die "$roots[0] != /dev/VG/Root" unless $roots[0] eq "/dev/VG/Root";
# Note we have to close the handle (hence killing qemu), and we
# have to kill qemu-nbd.
$g->close ();
kill 15, $pid;
waitpid ($pid, 0) or die "waitpid: $pid: $!";
$pid = 0;
unlink $pidfile
}
# Since read-only and read-write paths are quite different, we have to
# test both separately.
for my $readonly (1, 0) {
run_test ($readonly, 1);
}
# Test Unix domain socket codepath.
run_test (0, 0);
unlink $disk;
exit 0

231
tests/nbd/test-nbd.py Executable file
View File

@@ -0,0 +1,231 @@
#!/usr/bin/env python3
# Copyright (C) 2025 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Test NBD support by attaching a guest via qemu-nbd and running inspection.
import os
import sys
import shutil
import time
import random
import atexit
import subprocess
import guestfs
prog = os.path.basename(sys.argv[0])
# Track the qemu-nbd process so we can clean it up on exit.
server_proc = None
def _cleanup_server() -> None:
"""Ensure any qemu-nbd process is terminated when the test exits."""
global server_proc
if server_proc is not None:
try:
server_proc.terminate()
server_proc.wait(timeout=10)
except Exception:
# Last resort: kill -9 if terminate didn't work or timed out.
try:
server_proc.kill()
except Exception:
pass
finally:
server_proc = None
atexit.register(_cleanup_server)
# Allow skipping the test via environment variable (mirrors SKIP_TEST_NBD_PL).
if os.environ.get("SKIP_TEST_NBD_PY"):
sys.exit(77)
# Check that qemu-nbd is available and callable.
try:
result = subprocess.run(
["qemu-nbd", "--help"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
if result.returncode != 0:
print(f"{prog}: test skipped because qemu-nbd program not found")
sys.exit(77)
except FileNotFoundError:
print(f"{prog}: test skipped because qemu-nbd program not found")
sys.exit(77)
# Make a local copy of the disk so we can safely open it for writes.
disk = "../test-data/phony-guests/fedora.img"
if not os.path.isfile(disk) or os.path.getsize(disk) == 0:
print(f"{prog}: test skipped because {disk} is not found")
sys.exit(77)
local_disk = "fedora-nbd.img"
shutil.copyfile(disk, local_disk)
disk = local_disk
# Check if qemu-nbd supports the --format option (like the Perl grep).
has_format_opt = False
try:
help_out = subprocess.run(
["qemu-nbd", "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
if help_out.returncode == 0 and "--format" in help_out.stdout:
has_format_opt = True
except Exception:
# If this check fails for some reason, just assume no --format option.
has_format_opt = False
def run_test(readonly: bool, tcp: bool) -> None:
"""Run a single NBD test.
:param readonly: If True, attach the NBD drive read-only.
:param tcp: If True, connect using TCP; otherwise use Unix domain socket.
"""
global server_proc
cwd = os.getcwd()
pidfile = os.path.join(cwd, "nbd", "nbd.pid")
# Ensure the nbd/ directory exists so we can create pidfile and socket.
os.makedirs(os.path.dirname(pidfile), exist_ok=True)
# Base qemu-nbd command.
qemu_nbd_cmd = [
"qemu-nbd",
disk,
"-t", # persistent, multiple connections allowed
"--pid-file",
pidfile,
]
# Add '--format raw' if supported.
if has_format_opt:
qemu_nbd_cmd.extend(["--format", "raw"])
socket_path = None
if tcp:
# Choose a random port number. The original Perl test doesn't
# check if it is already in use, so we don't either.
port = random.randint(60000, 64999)
qemu_nbd_cmd.extend(["-p", str(port)])
server = f"localhost:{port}"
else:
# Unix domain socket: qemu-nbd insists on an absolute path.
socket_path = os.path.join(cwd, "nbd", "unix.sock")
try:
os.unlink(socket_path)
except FileNotFoundError:
pass
qemu_nbd_cmd.extend(["-k", socket_path])
server = f"unix:{socket_path}"
print("Starting", " ".join(qemu_nbd_cmd), "...")
# Start qemu-nbd in the background.
server_proc = subprocess.Popen(qemu_nbd_cmd)
# Wait for the pid file to appear, up to ~60 seconds (1s intervals).
for _ in range(60):
if os.path.isfile(pidfile):
break
# If qemu-nbd exited early, bail out immediately.
if server_proc.poll() is not None:
_cleanup_server()
raise RuntimeError("qemu-nbd exited unexpectedly while starting")
time.sleep(1)
else:
_cleanup_server()
raise RuntimeError("qemu-nbd did not start up")
# If using a Unix domain socket, try relabeling for SELinux.
# Failure is not fatal (maybe SELinux is disabled).
if socket_path is not None:
try:
subprocess.run(
["chcon", "-vt", "svirt_image_t", socket_path],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except FileNotFoundError:
# chcon not available, ignore.
pass
g = guestfs.GuestFS()
try:
# Add an NBD drive via protocol=nbd.
# ``server`` expects a list; we pass the full "localhost:port" or "unix:/path".
g.add_drive(
"",
readonly=bool(readonly),
format="raw",
protocol="nbd",
server=[server],
)
# This fails if qemu can't connect to the NBD server.
g.launch()
# Inspection is a fairly thorough test of the guest.
roots = g.inspect_os()
if len(roots) != 1:
raise RuntimeError("roots should be a 1-sized array")
if roots[0] != "/dev/VG/Root":
raise RuntimeError(f"{roots[0]} != /dev/VG/Root")
finally:
# Close guestfs handle (which will kill its qemu instance).
try:
g.close()
except Exception:
pass
# Terminate qemu-nbd and wait for it.
_cleanup_server()
try:
os.unlink(pidfile)
except FileNotFoundError:
pass
def main() -> int:
# Since read-only and read-write paths are quite different,
# test both via TCP.
for readonly in (True, False):
run_test(readonly, tcp=True)
# Test Unix domain socket codepath (read-write).
run_test(readonly=False, tcp=False)
# Cleanup the copied disk image.
try:
os.unlink(disk)
except FileNotFoundError:
pass
return 0
if __name__ == "__main__":
sys.exit(main())