2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-22 00:49:30 +00:00
Files
xcat-core/xCAT-test/unit/kvm_guest_arch.t
T
Daniel Hilst 349658d3d7 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)
2026-09-04 12:20:34 -03:00

123 lines
5.2 KiB
Perl

#!/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();