qemu-server/PVE/QemuServer/Machine.pm
Fiona Ebner 84b4bc9ab1 move helper to check running QEMU version out of the 'Machine' module
The version of the running QEMU binary is not related to the machine
version and so it's a bit confusing to have the helper in the
'Machine' module. It cannot live in the 'Helpers' module, because that
would lead to a cyclic inclusion Helpers <-> Monitor. Thus,
'QMPHelpers' is chosen as the new home.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2024-07-30 21:19:51 +02:00

178 lines
4.9 KiB
Perl

package PVE::QemuServer::Machine;
use strict;
use warnings;
use PVE::QemuServer::Helpers;
use PVE::QemuServer::Monitor;
use PVE::JSONSchema qw(get_standard_option parse_property_string print_property_string);
# Bump this for VM HW layout changes during a release (where the QEMU machine
# version stays the same)
our $PVE_MACHINE_VERSION = {
'4.1' => 2,
};
my $machine_fmt = {
type => {
default_key => 1,
description => "Specifies the QEMU machine type.",
type => 'string',
pattern => '(pc|pc(-i440fx)?-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|virt(?:-\d+(\.\d+)+)?(\+pve\d+)?)',
maxLength => 40,
format_description => 'machine type',
optional => 1,
},
viommu => {
type => 'string',
description => "Enable and set guest vIOMMU variant (Intel vIOMMU needs q35 to be set as"
." machine type).",
enum => ['intel', 'virtio'],
optional => 1,
},
};
PVE::JSONSchema::register_format('pve-qemu-machine-fmt', $machine_fmt);
PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
description => "Specify the QEMU machine.",
type => 'string',
optional => 1,
format => PVE::JSONSchema::get_format('pve-qemu-machine-fmt'),
});
sub parse_machine {
my ($value) = @_;
return if !$value;
my $res = parse_property_string($machine_fmt, $value);
return $res;
}
sub print_machine {
my ($machine_conf) = @_;
return print_property_string($machine_conf, $machine_fmt);
}
sub assert_valid_machine_property {
my ($conf, $machine_conf) = @_;
my $q35 = $machine_conf->{type} && ($machine_conf->{type} =~ m/q35/) ? 1 : 0;
if ($machine_conf->{viommu} && $machine_conf->{viommu} eq "intel" && !$q35) {
die "to use Intel vIOMMU please set the machine type to q35\n";
}
}
sub machine_type_is_q35 {
my ($conf) = @_;
my $machine_conf = parse_machine($conf->{machine});
return $machine_conf->{type} && ($machine_conf->{type} =~ m/q35/) ? 1 : 0;
}
# In list context, also returns whether the current machine is deprecated or not.
sub current_from_query_machines {
my ($machines) = @_;
my ($current, $default);
for my $machine ($machines->@*) {
$default = $machine->{name} if $machine->{'is-default'};
if ($machine->{'is-current'}) {
$current = $machine->{name};
# pve-version only exists for the current machine
$current .= "+$machine->{'pve-version'}" if $machine->{'pve-version'};
return wantarray ? ($current, $machine->{deprecated} ? 1 : 0) : $current;
}
}
# fallback to the default machine if current is not supported by qemu - assume never deprecated
my $fallback = $default || 'pc';
return wantarray ? ($fallback, 0) : $fallback;
}
# This only works if VM is running.
# In list context, also returns whether the current machine is deprecated or not.
sub get_current_qemu_machine {
my ($vmid) = @_;
my $res = PVE::QemuServer::Monitor::mon_cmd($vmid, 'query-machines');
return current_from_query_machines($res);
}
# returns a string with major.minor+pve<VERSION>, patch version-part is ignored
# as it's seldom ressembling a real QEMU machine type, so it would be '0' 99% of
# the time anyway.. This explicitly separates pveversion from the machine.
sub extract_version {
my ($machine_type, $kvmversion) = @_;
if (defined($machine_type) && $machine_type =~
m/^(?:pc(?:-i440fx|-q35)?|virt)-(\d+)\.(\d+)(?:\.(\d+))?(\+pve\d+)?(?:\.pxe)?/)
{
my $versionstr = "$1.$2";
$versionstr .= $4 if $4;
return $versionstr;
} elsif (defined($kvmversion)) {
if ($kvmversion =~ m/^(\d+)\.(\d+)/) {
my $pvever = get_pve_version($kvmversion);
return "$1.$2+pve$pvever";
}
}
return;
}
sub machine_version {
my ($machine_type, $major, $minor, $pve) = @_;
return PVE::QemuServer::Helpers::min_version(
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+))?(?:\.pxe)?$/;
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;
}
sub qemu_machine_pxe {
my ($vmid, $conf) = @_;
my $machine = get_current_qemu_machine($vmid);
my $machine_conf = parse_machine($conf->{machine});
if ($machine_conf->{type} && $machine_conf->{type} =~ m/\.pxe$/) {
$machine .= '.pxe';
}
return $machine;
}
1;