mirror of
https://git.proxmox.com/git/qemu-server
synced 2025-08-15 02:34:59 +00:00
add config to command tests
To have a better safety net for not introducing regressions and also some functional checks as the QEMU command defines the layout behavior of the VM. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com> Acked-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
89caf77b87
commit
7b963e5762
@ -1,6 +1,6 @@
|
|||||||
all: test
|
all: test
|
||||||
|
|
||||||
test: test_snapshot test_ovf
|
test: test_snapshot test_ovf test_cfg_to_cmd
|
||||||
|
|
||||||
test_snapshot: run_snapshot_tests.pl
|
test_snapshot: run_snapshot_tests.pl
|
||||||
./run_snapshot_tests.pl
|
./run_snapshot_tests.pl
|
||||||
@ -8,3 +8,6 @@ test_snapshot: run_snapshot_tests.pl
|
|||||||
|
|
||||||
test_ovf: run_ovf_tests.pl
|
test_ovf: run_ovf_tests.pl
|
||||||
./run_ovf_tests.pl
|
./run_ovf_tests.pl
|
||||||
|
|
||||||
|
test_cfg_to_cmd: run_config2command_tests.pl cfg2cmd/*.conf
|
||||||
|
perl -I../ ./run_config2command_tests.pl
|
||||||
|
85
test/cfg2cmd/README.adoc
Normal file
85
test/cfg2cmd/README.adoc
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
QemuServer Config 2 Command Test
|
||||||
|
================================
|
||||||
|
Thomas Lamprecht <t.lamprecht@proxmox.com>
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------
|
||||||
|
|
||||||
|
This is a relatively simple configuration to command test program.
|
||||||
|
It's main goals are to better enforce stability of commands, thus reducing
|
||||||
|
the likelihood that, for example, a migration breaking change which forgot to
|
||||||
|
bump/check the KVM/QEMU version, slips through
|
||||||
|
|
||||||
|
Further you get a certain regression and functional test coverage. You get a
|
||||||
|
safety net against breaking older or not often (manual) tested setups and
|
||||||
|
features.
|
||||||
|
|
||||||
|
NOTE: The safety net is only as good as the test count *and* quality.
|
||||||
|
|
||||||
|
|
||||||
|
Test Specification
|
||||||
|
------------------
|
||||||
|
|
||||||
|
A single test consists of two files, the input VM config `FILE.conf` and the
|
||||||
|
expected output command `FILE.conf.cmd`
|
||||||
|
|
||||||
|
Input
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
The `FILE.conf` are standard Proxmox VE VM configuration files, so you can just
|
||||||
|
copy over a config file from `/etc/pve/qemu-server` to add a configuration you
|
||||||
|
want to have tested.
|
||||||
|
|
||||||
|
Output
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
For the expected output `FILE.conf.cmd` we check the KVM/QEMU command produced.
|
||||||
|
As a single long line would be pretty hard to check for (problematic) changes
|
||||||
|
by humans, we use a pretty format, i.e., where each key value pair is on it's
|
||||||
|
own line. With this approach we can just diff expected and actual command and
|
||||||
|
one can pin point pretty fast in which component (e.g., net, drives, CPU, ...)
|
||||||
|
the issue is, if any. Such an output would look like:
|
||||||
|
|
||||||
|
----
|
||||||
|
/usr/bin/kvm \
|
||||||
|
-id 101 \
|
||||||
|
-name vm101 \
|
||||||
|
...
|
||||||
|
----
|
||||||
|
|
||||||
|
TIP: If the expected output file does not exist we have nothing to check, but
|
||||||
|
for convenience we will write it out. This should happen from clean code, and
|
||||||
|
the result should not get applied blindly, but only after a (quick) sanity
|
||||||
|
check.
|
||||||
|
|
||||||
|
|
||||||
|
Environment
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
It makes sense to have a stable and controlled environment for tests, thus you
|
||||||
|
one can use the 'description' in VM configurations to control this. The
|
||||||
|
description consists of all lines beginning with a '#' as first non-whitespace
|
||||||
|
character. Any environment variable follows the following format:
|
||||||
|
|
||||||
|
----
|
||||||
|
# NAME: VALUE
|
||||||
|
... rest of config...
|
||||||
|
----
|
||||||
|
|
||||||
|
There are the following variables you can control:
|
||||||
|
|
||||||
|
* *TEST*: a one line description for your test, gets outputted one testing and
|
||||||
|
should described in a short way any specialty about this specific test,
|
||||||
|
i.e., what does this test wants to ensure.
|
||||||
|
|
||||||
|
* *QEMU_VERSION*: the version we fake for this test, if not set we use the
|
||||||
|
actual one installed on the host.
|
||||||
|
|
||||||
|
* *HOST_ARCH*: the architecture we should fake for the test (aarch64 or x86_64),
|
||||||
|
defaults to `x86_64` to allow making this optional and still guarantee
|
||||||
|
stable tests
|
||||||
|
|
||||||
|
The storage environment is currently hardcoded in the test code, you can
|
||||||
|
extend it there if it's needed.
|
||||||
|
|
||||||
|
// vim: noai:tw=78
|
2
test/cfg2cmd/minimal-defaults.conf
Normal file
2
test/cfg2cmd/minimal-defaults.conf
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# TEST: minimal configuration to detect default changes
|
||||||
|
smbios1: uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3
|
24
test/cfg2cmd/minimal-defaults.conf.cmd
Normal file
24
test/cfg2cmd/minimal-defaults.conf.cmd
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/usr/bin/kvm \
|
||||||
|
-id 8006 \
|
||||||
|
-name vm8006 \
|
||||||
|
-chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server,nowait' \
|
||||||
|
-mon 'chardev=qmp,mode=control' \
|
||||||
|
-chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5' \
|
||||||
|
-mon 'chardev=qmp-event,mode=control' \
|
||||||
|
-pidfile /var/run/qemu-server/8006.pid \
|
||||||
|
-daemonize \
|
||||||
|
-smbios 'type=1,uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3' \
|
||||||
|
-smp '1,sockets=1,cores=1,maxcpus=1' \
|
||||||
|
-nodefaults \
|
||||||
|
-boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
|
||||||
|
-vnc unix:/var/run/qemu-server/8006.vnc,x509,password \
|
||||||
|
-cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \
|
||||||
|
-m 512 \
|
||||||
|
-device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \
|
||||||
|
-device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \
|
||||||
|
-device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \
|
||||||
|
-device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \
|
||||||
|
-device 'VGA,id=vga,bus=pci.0,addr=0x2' \
|
||||||
|
-device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \
|
||||||
|
-iscsi 'initiator-name=iqn.1993-08.org.debian:01:a1d15f6610fd' \
|
||||||
|
-machine 'type=pc'
|
15
test/cfg2cmd/simple1.conf
Normal file
15
test/cfg2cmd/simple1.conf
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# TEST: Simple test for a basic configuration with no special things
|
||||||
|
# QEMU_VERSION: 2.12.1
|
||||||
|
bootdisk: scsi0
|
||||||
|
cores: 3
|
||||||
|
ide2: none,media=cdrom
|
||||||
|
memory: 768
|
||||||
|
name: simple
|
||||||
|
net0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0
|
||||||
|
numa: 0
|
||||||
|
ostype: l26
|
||||||
|
scsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K
|
||||||
|
scsihw: virtio-scsi-pci
|
||||||
|
smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
|
||||||
|
sockets: 1
|
||||||
|
vmgenid: c773c261-d800-4348-9f5d-167fadd53cf8
|
32
test/cfg2cmd/simple1.conf.cmd
Normal file
32
test/cfg2cmd/simple1.conf.cmd
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/usr/bin/kvm \
|
||||||
|
-id 8006 \
|
||||||
|
-name simple \
|
||||||
|
-chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server,nowait' \
|
||||||
|
-mon 'chardev=qmp,mode=control' \
|
||||||
|
-chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5' \
|
||||||
|
-mon 'chardev=qmp-event,mode=control' \
|
||||||
|
-pidfile /var/run/qemu-server/8006.pid \
|
||||||
|
-daemonize \
|
||||||
|
-smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
|
||||||
|
-smp '3,sockets=1,cores=3,maxcpus=3' \
|
||||||
|
-nodefaults \
|
||||||
|
-boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
|
||||||
|
-vnc unix:/var/run/qemu-server/8006.vnc,x509,password \
|
||||||
|
-cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \
|
||||||
|
-m 768 \
|
||||||
|
-device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \
|
||||||
|
-device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \
|
||||||
|
-device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \
|
||||||
|
-device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \
|
||||||
|
-device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \
|
||||||
|
-device 'VGA,id=vga,bus=pci.0,addr=0x2' \
|
||||||
|
-device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \
|
||||||
|
-iscsi 'initiator-name=iqn.1993-08.org.debian:01:a1d15f6610fd' \
|
||||||
|
-drive 'if=none,id=drive-ide2,media=cdrom,aio=threads' \
|
||||||
|
-device 'ide-cd,bus=ide.1,unit=0,drive=drive-ide2,id=ide2,bootindex=200' \
|
||||||
|
-device 'virtio-scsi-pci,id=scsihw0,bus=pci.0,addr=0x5' \
|
||||||
|
-drive 'file=/var/lib/vz/images/8006/vm-8006-disk-0.qcow2,if=none,id=drive-scsi0,discard=on,format=qcow2,cache=none,aio=native,detect-zeroes=unmap' \
|
||||||
|
-device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,bootindex=100' \
|
||||||
|
-netdev 'type=tap,id=net0,ifname=tap8006i0,script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown,vhost=on' \
|
||||||
|
-device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
|
||||||
|
-machine 'type=pc'
|
173
test/run_config2command_tests.pl
Executable file
173
test/run_config2command_tests.pl
Executable file
@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib qw(..);
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
use Test::MockModule;
|
||||||
|
|
||||||
|
use PVE::Tools qw(file_get_contents file_set_contents run_command);
|
||||||
|
use PVE::QemuConfig;
|
||||||
|
use PVE::QemuServer;
|
||||||
|
|
||||||
|
my $base_env = {
|
||||||
|
storage_config => {
|
||||||
|
ids => {
|
||||||
|
local => {
|
||||||
|
content => {
|
||||||
|
images => 1,
|
||||||
|
},
|
||||||
|
path => '/var/lib/vz',
|
||||||
|
type => 'dir',
|
||||||
|
shared => 0,
|
||||||
|
},
|
||||||
|
'cifs-store' => {
|
||||||
|
shared => 1,
|
||||||
|
path => '/mnt/pve/cifs-store',
|
||||||
|
username => 'guest',
|
||||||
|
server => '127.0.0.42',
|
||||||
|
type => 'cifs',
|
||||||
|
share => 'CIFShare',
|
||||||
|
content => {
|
||||||
|
images => 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'rbd-store' => {
|
||||||
|
monhost => '127.0.0.42,127.0.0.21,::1',
|
||||||
|
content => {
|
||||||
|
images => 1
|
||||||
|
},
|
||||||
|
type => 'rbd',
|
||||||
|
pool => 'cpool',
|
||||||
|
username => 'admin',
|
||||||
|
shared => 1
|
||||||
|
},
|
||||||
|
'local-lvm' => {
|
||||||
|
vgname => 'pve',
|
||||||
|
bwlimit => 'restore=1024',
|
||||||
|
type => 'lvmthin',
|
||||||
|
thinpool => 'data',
|
||||||
|
content => {
|
||||||
|
images => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vmid => 8006,
|
||||||
|
real_qemu_version => PVE::QemuServer::kvm_user_version(), # not yet mocked
|
||||||
|
};
|
||||||
|
|
||||||
|
my $current_test; # = {
|
||||||
|
# description => 'Test description', # if available
|
||||||
|
# qemu_version => '2.12',
|
||||||
|
# host_arch => 'HOST_ARCH',
|
||||||
|
# config => { config hash },
|
||||||
|
# expected => [ expected outcome cmd line array ],
|
||||||
|
# };
|
||||||
|
|
||||||
|
# use the config description to allow changing environment, fields are:
|
||||||
|
# TEST: A single line describing the test, gets outputted
|
||||||
|
# QEMU_VERSION: \d+\.\d+(\.\d+)? (defaults to current version)
|
||||||
|
# HOST_ARCH: x86_64 | aarch64 (default to x86_64, to make tests stable)
|
||||||
|
# all fields are optional
|
||||||
|
sub parse_test($) {
|
||||||
|
my ($config_fn) = @_;
|
||||||
|
|
||||||
|
$current_test = {}; # reset
|
||||||
|
|
||||||
|
my $fake_config_fn ="$config_fn/qemu-server/8006.conf";
|
||||||
|
my $config_raw = file_get_contents($config_fn);
|
||||||
|
my $config = PVE::QemuServer::parse_vm_config($fake_config_fn, $config_raw);
|
||||||
|
|
||||||
|
$current_test->{config} = $config;
|
||||||
|
|
||||||
|
my $description = $config->{description} // '';
|
||||||
|
|
||||||
|
while ($description =~ /^\h*(.*?)\h*$/gm) {
|
||||||
|
my $line = $1;
|
||||||
|
next if !$line || $line =~ /^#/;
|
||||||
|
$line =~ s/^\s+//;
|
||||||
|
$line =~ s/\s+$//;
|
||||||
|
|
||||||
|
if ($line =~ /^TEST:\s*(.*)\s*$/) {
|
||||||
|
$current_test->{description} = "$1";
|
||||||
|
} elsif ($line =~ /^QEMU_VERSION:\s*(.*)\s*$/) {
|
||||||
|
$current_test->{qemu_version} = "$1";
|
||||||
|
} elsif ($line =~ /^HOST_ARCH:\s*(.*)\s*$/) {
|
||||||
|
$current_test->{host_arch} = "$1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $qemu_server_module;
|
||||||
|
$qemu_server_module = Test::MockModule->new('PVE::QemuServer');
|
||||||
|
$qemu_server_module->mock(
|
||||||
|
kvm_user_version => sub {
|
||||||
|
return $current_test->{qemu_version} // $base_env->{real_qemu_version};
|
||||||
|
},
|
||||||
|
get_host_arch => sub() {
|
||||||
|
return $current_test->{host_arch} // 'x86_64';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
my $qemu_server_config;
|
||||||
|
$qemu_server_config = Test::MockModule->new('PVE::QemuConfig');
|
||||||
|
$qemu_server_config->mock(
|
||||||
|
load_config => sub {
|
||||||
|
my ($class, $vmid, $node) = @_;
|
||||||
|
|
||||||
|
return $current_test->{config};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
sub do_test($) {
|
||||||
|
my ($config_fn) = @_;
|
||||||
|
|
||||||
|
die "no such input test config: $config_fn\n" if ! -f $config_fn;
|
||||||
|
|
||||||
|
parse_test $config_fn;
|
||||||
|
|
||||||
|
$config_fn =~ /([^\/]+)$/;
|
||||||
|
my $testname = "$1";
|
||||||
|
if (my $desc = $current_test->{description}) {
|
||||||
|
$testname = "'$testname' - $desc";
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($vmid, $storecfg) = $base_env->@{qw(vmid storage_config)};
|
||||||
|
|
||||||
|
my $cmdline = PVE::QemuServer::vm_commandline($storecfg, $vmid);
|
||||||
|
|
||||||
|
$cmdline =~ s/ -/ \\\n -/g; # same as qm showcmd --pretty
|
||||||
|
$cmdline .= "\n";
|
||||||
|
|
||||||
|
my $cmd_fn = "$config_fn.cmd";
|
||||||
|
|
||||||
|
if (-f $cmd_fn) {
|
||||||
|
my $cmdline_expected = file_get_contents($cmd_fn);
|
||||||
|
|
||||||
|
my $cmd_expected = [ sort split /\s*\\?\n\s*/, $cmdline_expected ];
|
||||||
|
my $cmd = [ sort split /\s*\\?\n\s*/, $cmdline ];
|
||||||
|
|
||||||
|
# comment out for easier debugging
|
||||||
|
#file_set_contents("$cmd_fn.tmp", $cmdline);
|
||||||
|
|
||||||
|
is_deeply($cmd, $cmd_expected, "$testname")
|
||||||
|
} else {
|
||||||
|
file_set_contents($cmd_fn, $cmdline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print "testing config to command stabillity\n";
|
||||||
|
|
||||||
|
# exec tests
|
||||||
|
if (my $file = shift) {
|
||||||
|
do_test $file;
|
||||||
|
} else {
|
||||||
|
foreach my $file (<cfg2cmd/*.conf>) {
|
||||||
|
do_test $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing();
|
Loading…
Reference in New Issue
Block a user