Use 'QEMU version' -> '+pve-version' mapping for machine types

The previously introduced approach can fail for pinned versions when a
new QEMU release is introduced. The saner approach is to use a mapping
that gives one pve-version for each QEMU release.

Fortunately, the old system has not been bumped yet, so we can still
change it without too much effort.

QEMU versions without a mapping are assumed to be pve0, 4.1 is mapped to
pve1 since thats what we had as our default previously.

Pinned machine versions (i.e. pc-i440fx-4.1) are always assumed to be
pve0, for specific pve-versions they'd have to be pinned as well (i.e.
pc-i440fx-4.1+pve1).

The new logic also makes the pve-version dynamic, and starts VMs with
the lowest possible 'feature-level', i.e. if a feature is only available
with 4.1+pve2, but the VM isn't using it, we still start it with
4.1+pve0.

We die if we don't support a version that is requested from us. This
allows us to use the pve-version as live-migration blocks (i.e. bumping
the version and then live-migrating a VM which uses the new feature (so
is running with the bumped version) to an outdated node will present the
user with a helpful error message and fail instead of silently modifying
the config and only failing *after* the migration).

$version_guard is introduced in config_to_command to use for features
that need to check pve-version, it automatically handles selecting the
newest necessary pve-version for the VM.

Tests have to be adjusted, since all of them now resolve to pve0 instead
of pve1. EXPECT_ERROR matching is changed to use 'eq' instead of regex
to allow special characters in error messages.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
This commit is contained in:
Stefan Reiter 2020-02-10 16:05:35 +01:00 committed by Thomas Lamprecht
parent 8541f8cddb
commit ac0077cc33
12 changed files with 92 additions and 22 deletions

View File

@ -3241,14 +3241,24 @@ my $default_machines = {
}; };
sub get_vm_machine { sub get_vm_machine {
my ($conf, $forcemachine, $arch, $add_pve_version) = @_; my ($conf, $forcemachine, $arch, $add_pve_version, $kvmversion) = @_;
my $machine = $forcemachine || $conf->{machine}; my $machine = $forcemachine || $conf->{machine};
if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) { if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
$arch //= 'x86_64'; $arch //= 'x86_64';
$machine ||= $default_machines->{$arch}; $machine ||= $default_machines->{$arch};
$machine .= "+pve$PVE::QemuServer::Machine::PVE_MACHINE_VERSION" if $add_pve_version; if ($add_pve_version) {
$kvmversion //= kvm_user_version();
my $pvever = PVE::QemuServer::Machine::get_pve_version($kvmversion);
$machine .= "+pve$pvever";
}
}
if ($add_pve_version && $machine !~ m/\+pve\d+$/) {
# for version-pinned machines that do not include a pve-version (e.g.
# pc-q35-4.1), we assume 0 to keep them stable in case we bump
$machine .= '+pve0';
} }
return $machine; return $machine;
@ -3422,8 +3432,26 @@ sub config_to_command {
$kvm //= 1 if is_native($arch); $kvm //= 1 if is_native($arch);
$machine_version =~ m/(\d+)\.(\d+)/; $machine_version =~ m/(\d+)\.(\d+)/;
my ($machine_major, $machine_minor) = ($1, $2);
die "Installed QEMU version '$kvmver' is too old to run machine type '$machine_type', please upgrade node '$nodename'\n" die "Installed QEMU version '$kvmver' is too old to run machine type '$machine_type', please upgrade node '$nodename'\n"
if !PVE::QemuServer::min_version($kvmver, $1, $2); if !PVE::QemuServer::min_version($kvmver, $machine_major, $machine_minor);
if (!PVE::QemuServer::Machine::can_run_pve_machine_version($machine_version, $kvmver)) {
my $max_pve_version = PVE::QemuServer::Machine::get_pve_version($machine_version);
die "Installed qemu-server (max feature level for $machine_major.$machine_minor is pve$max_pve_version)"
. " is too old to run machine type '$machine_type', please upgrade node '$nodename'\n";
}
# if a specific +pve version is required for a feature, use $version_guard
# instead of min_version to allow machines to be run with the minimum
# required version
my $required_pve_version = 0;
my $version_guard = sub {
my ($major, $minor, $pve) = @_;
return 0 if !min_version($machine_version, $major, $minor, $pve);
$required_pve_version = $pve if $pve && $pve > $required_pve_version;
return 1;
};
if ($kvm) { if ($kvm) {
die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n" die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n"
@ -3774,14 +3802,6 @@ sub config_to_command {
push @$rtcFlags, 'driftfix=slew' if $tdf; push @$rtcFlags, 'driftfix=slew' if $tdf;
if (!$kvm) {
push @$machineFlags, 'accel=tcg';
}
if ($machine_type) {
push @$machineFlags, "type=${machine_type}";
}
if (($conf->{startdate}) && ($conf->{startdate} ne 'now')) { if (($conf->{startdate}) && ($conf->{startdate} ne 'now')) {
push @$rtcFlags, "base=$conf->{startdate}"; push @$rtcFlags, "base=$conf->{startdate}";
} elsif ($useLocaltime) { } elsif ($useLocaltime) {
@ -4005,6 +4025,17 @@ sub config_to_command {
} }
} }
if (!$kvm) {
push @$machineFlags, 'accel=tcg';
}
my $machine_type_min = $machine_type;
if ($add_pve_version) {
$machine_type_min =~ s/\+pve\d+$//;
$machine_type_min .= "+pve$required_pve_version";
}
push @$machineFlags, "type=${machine_type_min}";
push @$cmd, @$devices; push @$cmd, @$devices;
push @$cmd, '-rtc', join(',', @$rtcFlags) push @$cmd, '-rtc', join(',', @$rtcFlags)
if scalar(@$rtcFlags); if scalar(@$rtcFlags);

View File

@ -8,7 +8,9 @@ use PVE::QemuServer::Monitor;
# Bump this for VM HW layout changes during a release (where the QEMU machine # Bump this for VM HW layout changes during a release (where the QEMU machine
# version stays the same) # version stays the same)
our $PVE_MACHINE_VERSION = 1; our $PVE_MACHINE_VERSION = {
'4.1' => 1,
};
sub machine_type_is_q35 { sub machine_type_is_q35 {
my ($conf) = @_; my ($conf) = @_;
@ -47,7 +49,8 @@ sub extract_version {
return $versionstr; return $versionstr;
} elsif (defined($kvmversion)) { } elsif (defined($kvmversion)) {
if ($kvmversion =~ m/^(\d+)\.(\d+)/) { if ($kvmversion =~ m/^(\d+)\.(\d+)/) {
return "$1.$2+pve$PVE_MACHINE_VERSION"; my $pvever = get_pve_version($kvmversion);
return "$1.$2+pve$pvever";
} }
} }
@ -61,6 +64,37 @@ sub machine_version {
extract_version($machine_type), $major, $minor, $pve); extract_version($machine_type), $major, $minor, $pve);
} }
sub get_pve_version {
my ($verstr) = @_;
if ($verstr =~ m/^(\d+\.\d+)/) {
return $PVE_MACHINE_VERSION->{$1} // 0;
}
die "internal error: cannot get pve version for invalid string '$verstr'";
}
sub can_run_pve_machine_version {
my ($machine_version, $kvmversion) = @_;
$machine_version =~ m/^(\d+)\.(\d+)(?:\+pve(\d+))$/;
my $major = $1;
my $minor = $2;
my $pvever = $3;
$kvmversion =~ m/(\d+)\.(\d+)/;
return 0 if PVE::QemuServer::Helpers::version_cmp($1, $major, $2, $minor) < 0;
# if $pvever is missing or 0, we definitely support it as long as we didn't
# fail the QEMU version check above
return 1 if !$pvever;
my $max_supported = get_pve_version("$major.$minor");
return 1 if $max_supported >= $pvever;
return 0;
}
# dies if a) VM not running or not exisiting b) Version query failed # dies if a) VM not running or not exisiting b) Version query failed
# So, any defined return value is valid, any invalid state can be caught by eval # So, any defined return value is valid, any invalid state can be caught by eval
sub runs_at_least_qemu_version { sub runs_at_least_qemu_version {

View File

@ -33,5 +33,5 @@
-netdev 'type=tap,id=net0,ifname=tap8006i0,script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown,vhost=on' \ -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=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \ -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-rtc 'driftfix=slew,base=localtime' \ -rtc 'driftfix=slew,base=localtime' \
-machine 'type=pc+pve1' \ -machine 'type=pc+pve0' \
-global 'kvm-pit.lost_tick_policy=discard' -global 'kvm-pit.lost_tick_policy=discard'

View File

@ -1,5 +1,5 @@
# TEST: newer machine verison than QEMU version installed on node # TEST: newer machine verison than QEMU version installed on node
# QEMU_VERSION: 4.1.1 # QEMU_VERSION: 4.1.1
# EXPECT_ERROR: Installed QEMU version '4.1.1' is too old to run machine type 'pc-q35-42.9', please upgrade node 'localhost' # EXPECT_ERROR: Installed QEMU version '4.1.1' is too old to run machine type 'pc-q35-42.9+pve0', please upgrade node 'localhost'
smbios1: uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3 smbios1: uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3
machine: pc-q35-42.9 machine: pc-q35-42.9

View File

@ -0,0 +1,5 @@
# TEST: newer machine verison than QEMU version installed on node
# QEMU_VERSION: 4.1.1
# EXPECT_ERROR: Installed qemu-server (max feature level for 4.0 is pve0) is too old to run machine type 'pc-q35-4.0+pve77', please upgrade node 'localhost'
smbios1: uuid=6cf17dc3-8341-4ecc-aebd-7503f2583fb3
machine: pc-q35-4.0+pve77

View File

@ -21,4 +21,4 @@
-device 'VGA,id=vga,bus=pci.0,addr=0x2' \ -device 'VGA,id=vga,bus=pci.0,addr=0x2' \
-device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \ -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \
-iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \ -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
-machine 'type=pc+pve1' -machine 'type=pc+pve0'

View File

@ -27,4 +27,4 @@
-device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,bootindex=100' \ -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' \ -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:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \ -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-machine 'type=pc-q35-3.1' -machine 'type=pc-q35-3.1+pve0'

View File

@ -31,4 +31,4 @@
-iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \ -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
-netdev 'type=tap,id=net0,ifname=tap8006i0,script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown,vhost=on' \ -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=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \ -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-machine 'type=q35+pve1' -machine 'type=q35+pve0'

View File

@ -36,4 +36,4 @@
-iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \ -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
-netdev 'type=tap,id=net0,ifname=tap8006i0,script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown,vhost=on' \ -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=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \ -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-machine 'type=q35+pve1' -machine 'type=q35+pve0'

View File

@ -34,5 +34,5 @@
-netdev 'type=tap,id=net0,ifname=tap8006i0,script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown,vhost=on' \ -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=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \ -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-rtc 'driftfix=slew,base=localtime' \ -rtc 'driftfix=slew,base=localtime' \
-machine 'type=q35+pve1' \ -machine 'type=q35+pve0' \
-global 'kvm-pit.lost_tick_policy=discard' -global 'kvm-pit.lost_tick_policy=discard'

View File

@ -27,4 +27,4 @@
-iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \ -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
-netdev 'type=tap,id=net0,ifname=tap8006i0,script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown,vhost=on' \ -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:67:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \ -device 'virtio-net-pci,mac=A2:C0:43:67:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-machine 'type=pc+pve1' -machine 'type=pc+pve0'

View File

@ -292,7 +292,7 @@ sub do_test($) {
return; return;
} }
chomp $err; chomp $err;
if ($err !~ /^\s*$err_expect\s*$/) { if ($err ne $err_expect) {
fail("$testname"); fail("$testname");
note("error does not match expected error: '$err' !~ '$err_expect'"); note("error does not match expected error: '$err' !~ '$err_expect'");
} else { } else {