mirror of
git://git.proxmox.com/git/qemu-server.git
synced 2025-03-09 08:58:25 +03: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
|
||||
|
||||
test: test_snapshot test_ovf
|
||||
test: test_snapshot test_ovf test_cfg_to_cmd
|
||||
|
||||
test_snapshot: run_snapshot_tests.pl
|
||||
./run_snapshot_tests.pl
|
||||
@ -8,3 +8,6 @@ test_snapshot: run_snapshot_tests.pl
|
||||
|
||||
test_ovf: 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…
x
Reference in New Issue
Block a user