#!/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{]*)>hvm}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/]*\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/]*\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/]*\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/]*\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/]*\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();