2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-21 16:39:30 +00:00

fix(xcat-core): mkvm builds a riscv64 node as an x86_64 domain

A node with arch=riscv64 got an x86_64 libvirt domain from mkvm. The node took a
DHCP lease, received the riscv64 GRUB binary that nodeset staged, and could not run
it. The firmware fell through to the empty disk and stopped, so both flat
provisioning cases of the riscv64 cell failed with a node that never installed.

build_xmldesc and build_diskstruct in xCAT-server/lib/xcat/plugins/kvm.pm read the
architecture from the hypervisor cpumodel. The arch of the node was never read while
the domain XML was built, so on an x86_64 hypervisor every guest was an x86_64
guest, whatever the node said.

guest_arch_profile now takes the arch of the node as well, and returns the domain
type, the <os> arch and machine, the firmware and the device settings that follow
from them. A riscv64 node becomes a qemu domain with the virt machine type and UEFI
firmware. It drops the parts the riscv64 virt machine has no controller for, or that
libvirt refuses there: the pae, acpi and apic features, the SeaBIOS serial option,
the ich6 sound card, the USB tablet, and the ide disk and hd* optical drive. libvirt
resolves the emulator and the UEFI firmware files itself. POWER and x86_64 domains
do not change.

kvm_guest_arch.t drives build_xmldesc and build_diskstruct in a scratch package,
stubbing only the routines that reach libvirt or the xCAT database, and asserts the
domain and the disks of each architecture. Ten of its twenty assertions fail without
this change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 8d149c856302c8016fb0ead31e9859fd5a1e9dff)
This commit is contained in:
Daniel Hilst
2026-09-04 12:09:17 -03:00
parent 2bed8fe9a1
commit 349658d3d7
2 changed files with 192 additions and 22 deletions
+70 -22
View File
@@ -498,6 +498,8 @@ sub build_diskstruct {
my @suffixes = ('a', 'b', 'd' .. 'zzz');
my $suffidx = 0;
my $storagemodel = $confdata->{vm}->{$node}->[0]->{storagemodel};
my $profile = guest_arch_profile($confdata->{nodetype}->{$node}->[0]->{arch},
$confdata->{ $confdata->{vm}->{$node}->[0]->{host} }->{cpumodel});
my $cachemethod = "none";
if ($confdata->{vm}->{$node}->[0]->{storagecache}) {
$cachemethod = $confdata->{vm}->{$node}->[0]->{storagecache};
@@ -517,7 +519,7 @@ sub build_diskstruct {
#if not defined, model will stay undefined like above
$model = $storagemodel;
unless ($model) { $model = 'ide'; } #if still not defined, ide
unless ($model) { $model = $profile->{disk_model}; }
}
my $prefix = 'hd';
if ($model eq 'virtio') {
@@ -586,7 +588,8 @@ sub build_diskstruct {
push @returns, $diskhash;
}
}
my $cdprefix = 'hd';
# The riscv64 virt machine has no IDE controller, so the optical drive is scsi there.
my $cdprefix = $profile->{cd_prefix};
# Normally for vmstoragemodel=virtio, we would set prefix of "vd", but device name vd*
# doesn't work for CDROM, so for now use the same prefix "sd" as for vmstoragemodel=scsi.
@@ -704,6 +707,54 @@ sub getUnits {
}
}
# guest_arch_profile: the libvirt domain type and <os> settings for one guest.
#
# The architecture of the guest comes from the node, not from the hypervisor. A node whose
# arch is not the arch of the hypervisor runs under emulation, which libvirt expresses as
# domain type "qemu". riscv64 has no BIOS: the virt machine boots UEFI, and pae/acpi/apic
# are x86 features that libvirt rejects there.
#
# POWER keeps reading the hypervisor cpumodel. ppc64le hypervisors report "ppc64le" (not
# "ppc64"); both are pseries guests whose libvirt <os> arch is "ppc64".
#
# arch and machine stay undef when libvirt is to use its own default for the hypervisor.
sub guest_arch_profile {
my ($guest_arch, $hyp_cpumodel) = @_;
my %profile = (
domtype => 'kvm',
arch => undef,
machine => undef,
firmware => undef,
x86_features => 1,
bios => 1,
sound => 1,
video => 'vga',
usb_input => 1,
disk_model => 'ide',
cd_prefix => 'hd',
);
if (defined($guest_arch) and $guest_arch eq 'riscv64') {
$profile{domtype} = 'qemu';
$profile{arch} = 'riscv64';
$profile{machine} = 'virt';
$profile{firmware} = 'efi';
$profile{x86_features} = 0;
$profile{bios} = 0;
$profile{sound} = 0;
$profile{video} = 'virtio';
$profile{usb_input} = 0;
$profile{disk_model} = 'scsi';
$profile{cd_prefix} = 'sd';
} elsif (defined($hyp_cpumodel) and ($hyp_cpumodel eq "ppc64" or $hyp_cpumodel eq "ppc64le")) {
$profile{arch} = 'ppc64';
$profile{machine} = 'pseries';
$profile{x86_features} = 0;
$profile{bios} = 0;
$profile{sound} = 0;
}
return \%profile;
}
sub build_xmldesc {
my $node = shift;
my %args = @_;
@@ -716,19 +767,16 @@ sub build_xmldesc {
$hypcputhreads = "1";
}
$xtree{type} = 'kvm';
my $profile = guest_arch_profile($confdata->{nodetype}->{$node}->[0]->{arch}, $hypcpumodel);
$xtree{type} = $profile->{domtype};
$xtree{name}->{content} = $node;
$xtree{uuid}->{content} = getNodeUUID($node);
$xtree{os} = build_oshash();
# ppc64le hypervisors report cpumodel "ppc64le" (not "ppc64"); both are pseries
# guests whose libvirt <os> arch is "ppc64". Without this the guest is emitted
# as an x86-style domain (no machine, plus the pae/acpi/apic below) which libvirt
# rejects on ppc64le hosts: "machine type 'pseries-*' does not support ACPI".
if (defined($hypcpumodel) and ($hypcpumodel eq "ppc64" or $hypcpumodel eq "ppc64le")) {
$xtree{os}->{type}->{arch} = "ppc64";
$xtree{os}->{type}->{machine} = "pseries";
delete $xtree{os}->{bios};
}
$xtree{os}->{type}->{arch} = $profile->{arch} if defined $profile->{arch};
$xtree{os}->{type}->{machine} = $profile->{machine} if defined $profile->{machine};
$xtree{os}->{firmware} = $profile->{firmware} if defined $profile->{firmware};
delete $xtree{os}->{bios} unless $profile->{bios};
if ($args{memory}) {
$xtree{memory}->{content} = getUnits($args{memory}, "M", 1024);
if ($confdata->{vm}->{$node}->[0]->{memory}) {
@@ -940,9 +988,7 @@ sub build_xmldesc {
}
}
# pae/acpi/apic are x86 features; pseries (ppc64/ppc64le) guests do not support
# them and libvirt rejects the domain if they are present.
unless (defined($hypcpumodel) and ($hypcpumodel eq "ppc64" or $hypcpumodel eq "ppc64le")) {
if ($profile->{x86_features}) {
$xtree{features}->{pae} = {};
$xtree{features}->{acpi} = {};
$xtree{features}->{apic} = {};
@@ -965,10 +1011,13 @@ sub build_xmldesc {
$vram = 65536; } #surprise, spice blows up with less vram than this after version 0.6 and up
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => $model, vram => $vram } } ];
} else {
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => 'vga', vram => 8192 } } ];
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => $profile->{video}, vram => 8192 } } ];
}
# The riscv64 virt machine has no USB controller, and libvirt refuses a USB device there.
if ($profile->{usb_input}) {
$xtree{devices}->{input}->{type} = 'tablet';
$xtree{devices}->{input}->{bus} = 'usb';
}
$xtree{devices}->{input}->{type} = 'tablet';
$xtree{devices}->{input}->{bus} = 'usb';
if (defined($confdata->{vm}->{$node}->[0]->{vidproto})) {
$xtree{devices}->{graphics}->{type} = $confdata->{vm}->{$node}->[0]->{vidproto};
} else {
@@ -983,10 +1032,9 @@ sub build_xmldesc {
}
if (defined($hypcpumodel) and $hypcpumodel eq 'ppc64') {
$xtree{devices}->{emulator}->{content} = "/usr/bin/qemu-system-ppc64";
} elsif (defined($hypcpumodel) and $hypcpumodel eq 'ppc64le') {
# do nothing for ppc64le, do not support sound at this time
;
} else {
}
# libvirt resolves the emulator for every other architecture from its own capabilities.
if ($profile->{sound}) {
$xtree{devices}->{sound}->{model} = 'ich6';
}
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use Test::More;
# The scratch package below declares these; the test names them once each.
no warnings 'once';
my $source = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/kvm.pm";
open(my $source_fh, '<', $source) or die "open $source: $!";
my $content = do { local $/; <$source_fh> };
close($source_fh) or die "close $source: $!";
my @routines;
for my $name (qw(build_xmldesc guest_arch_profile build_oshash build_diskstruct getUnits)) {
my ($routine) = $content =~ /^(sub \Q$name\E\s*\{.*?^\})/ms;
BAIL_OUT("could not extract $name from kvm.pm") unless $routine;
push(@routines, $routine);
}
# kvm.pm needs a management node to load, so the domain builder runs in a scratch package.
# Only the routines that reach libvirt or the xCAT database are replaced; the domain builder
# itself is the code under test.
my $harness = <<'PERL';
package KVMArch;
use XML::Simple qw(XMLout);
our ($node, $confdata, $updatetable, $hypconn);
sub getNodeUUID { return '00000000-0000-0000-0000-000000000001'; }
sub get_multiple_paths_by_url { return {}; }
sub build_nicstruct { return []; }
sub genpassword { return 'password'; }
PERL
eval $harness . join("\n", @routines) . "\n1;\n"; ## no critic (BuiltinFunctions::ProhibitStringyEval)
BAIL_OUT("could not load the kvm domain builder: $@") if $@;
# Build one domain for a node of $guest_arch on a hypervisor that reports $hyp_cpumodel.
sub domain_xml {
my ($guest_arch, $hyp_cpumodel) = @_;
local $KVMArch::node = 'cn1';
local $KVMArch::confdata = {
vm => { cn1 => [ { host => 'hyp1', memory => 8192, cpus => 4 } ] },
nodetype => { cn1 => [ { arch => $guest_arch, os => 'rocky10.2' } ] },
hyp1 => { cpumodel => $hyp_cpumodel },
};
local $KVMArch::updatetable = {};
my $xml = KVMArch::build_xmldesc('cn1');
BAIL_OUT("build_xmldesc returned no XML for $guest_arch on $hyp_cpumodel")
unless defined $xml and !ref $xml;
return $xml;
}
sub os_type_element {
my ($xml) = @_;
my ($attrs) = $xml =~ m{<type\b([^>]*)>hvm</type>}s;
return defined $attrs ? $attrs : '';
}
# A riscv64 node on an x86_64 hypervisor. The guest architecture is not the host
# architecture, so the domain runs under emulation and states its own machine type.
my $riscv = domain_xml('riscv64', 'x86_64');
like($riscv, qr/<domain\b[^>]*\btype="qemu"/,
'a riscv64 guest on an x86_64 hypervisor is a qemu domain, not kvm');
like(os_type_element($riscv), qr/\barch="riscv64"/,
'the domain arch is the arch of the node');
like(os_type_element($riscv), qr/\bmachine="virt"/,
'a riscv64 guest uses the virt machine type');
like($riscv, qr/<os\b[^>]*\bfirmware="efi"/,
'a riscv64 virt guest boots UEFI');
unlike($riscv, qr/<(?:pae|acpi|apic)\b/,
'pae, acpi and apic are x86 features and are left out of a riscv64 guest');
unlike($riscv, qr/<bios\b/,
'the SeaBIOS serial option is left out of a riscv64 guest');
unlike($riscv, qr/<input\b/,
'the riscv64 virt machine has no USB controller, so it gets no USB tablet');
# POWER is unchanged: the arch still comes from the hypervisor there.
my $power = domain_xml('ppc64le', 'ppc64le');
like($power, qr/<domain\b[^>]*\btype="kvm"/, 'a POWER guest stays a kvm domain');
like(os_type_element($power), qr/\barch="ppc64"/, 'ppc64le hypervisors keep arch ppc64');
like(os_type_element($power), qr/\bmachine="pseries"/, 'ppc64le hypervisors keep machine pseries');
# x86_64 on x86_64 is unchanged: libvirt picks the arch and the machine type.
my $x86 = domain_xml('x86_64', 'x86_64');
like($x86, qr/<domain\b[^>]*\btype="kvm"/, 'an x86_64 guest stays a kvm domain');
unlike(os_type_element($x86), qr/\barch=/, 'an x86_64 guest states no arch');
unlike(os_type_element($x86), qr/\bmachine=/, 'an x86_64 guest states no machine type');
like($x86, qr/<input\b[^>]*\bbus="usb"/, 'an x86_64 guest keeps the USB tablet');
# The disks of a riscv64 guest. The virt machine has no IDE controller, so an ide disk or an
# hd* optical drive makes libvirt refuse the domain.
sub disk_struct {
my ($guest_arch) = @_;
local $KVMArch::node = 'cn1';
local $KVMArch::confdata = {
vm => { cn1 => [ { host => 'hyp1', storage => '/var/lib/libvirt/images/cn1.img' } ] },
nodetype => { cn1 => [ { arch => $guest_arch } ] },
hyp1 => { cpumodel => 'x86_64' },
};
my $chatter = '';
my $disks;
{
open(my $capture, '>', \\$chatter) or die "capture stdout: $!";
local *STDOUT = $capture;
($disks) = KVMArch::build_diskstruct(undef);
}
return $disks;
}
my $riscv_disks = disk_struct('riscv64');
is($riscv_disks->[0]->{target}->{bus}, 'scsi', 'a riscv64 disk is scsi, not ide');
like($riscv_disks->[0]->{target}->{dev}, qr/^sd/, 'a riscv64 disk is named sd*');
is($riscv_disks->[1]->{device}, 'cdrom', 'the guest still gets an optical drive');
like($riscv_disks->[1]->{target}->{dev}, qr/^sd/, 'a riscv64 optical drive is named sd*, not hd*');
my $x86_disks = disk_struct('x86_64');
is($x86_disks->[0]->{target}->{bus}, 'ide', 'an x86_64 disk keeps the ide default');
like($x86_disks->[1]->{target}->{dev}, qr/^hd/, 'an x86_64 optical drive keeps the hd* name');
done_testing();