[pve-devel] [PATCH qemu-server 1/3] add config to command tests

Wolfgang Bumiller w.bumiller at proxmox.com
Fri Dec 7 10:31:17 CET 2018


From: Thomas Lamprecht <t.lamprecht at proxmox.com>

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 at proxmox.com>
Acked-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
 test/Makefile                          |   5 +-
 test/cfg2cmd/README.adoc               |  85 ++++++++++++++++
 test/cfg2cmd/minimal-defaults.conf     |   2 +
 test/cfg2cmd/minimal-defaults.conf.cmd |  24 +++++
 test/cfg2cmd/simple1.conf              |  15 +++
 test/cfg2cmd/simple1.conf.cmd          |  32 ++++++
 test/run_config2command_tests.pl       | 173 +++++++++++++++++++++++++++++++++
 7 files changed, 335 insertions(+), 1 deletion(-)
 create mode 100644 test/cfg2cmd/README.adoc
 create mode 100644 test/cfg2cmd/minimal-defaults.conf
 create mode 100644 test/cfg2cmd/minimal-defaults.conf.cmd
 create mode 100644 test/cfg2cmd/simple1.conf
 create mode 100644 test/cfg2cmd/simple1.conf.cmd
 create mode 100755 test/run_config2command_tests.pl

diff --git a/test/Makefile b/test/Makefile
index 6777f4a..48ea0a4 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -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
diff --git a/test/cfg2cmd/README.adoc b/test/cfg2cmd/README.adoc
new file mode 100644
index 0000000..fd9923a
--- /dev/null
+++ b/test/cfg2cmd/README.adoc
@@ -0,0 +1,85 @@
+QemuServer Config 2 Command Test
+================================
+Thomas Lamprecht <t.lamprecht at 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
diff --git a/test/cfg2cmd/minimal-defaults.conf b/test/cfg2cmd/minimal-defaults.conf
new file mode 100644
index 0000000..bb22d05
--- /dev/null
+++ b/test/cfg2cmd/minimal-defaults.conf
@@ -0,0 +1,2 @@
+# TEST: minimal configuration to detect default changes
+smbios1: uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3
diff --git a/test/cfg2cmd/minimal-defaults.conf.cmd b/test/cfg2cmd/minimal-defaults.conf.cmd
new file mode 100644
index 0000000..6c1f9d6
--- /dev/null
+++ b/test/cfg2cmd/minimal-defaults.conf.cmd
@@ -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'
diff --git a/test/cfg2cmd/simple1.conf b/test/cfg2cmd/simple1.conf
new file mode 100644
index 0000000..10a4c31
--- /dev/null
+++ b/test/cfg2cmd/simple1.conf
@@ -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
diff --git a/test/cfg2cmd/simple1.conf.cmd b/test/cfg2cmd/simple1.conf.cmd
new file mode 100644
index 0000000..f202ea8
--- /dev/null
+++ b/test/cfg2cmd/simple1.conf.cmd
@@ -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'
diff --git a/test/run_config2command_tests.pl b/test/run_config2command_tests.pl
new file mode 100755
index 0000000..00dbcbb
--- /dev/null
+++ b/test/run_config2command_tests.pl
@@ -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();
-- 
2.11.0





More information about the pve-devel mailing list