Skip to content

ci(xcat-core): Fixes to get CI running for all 2.19 targets - #7818

Open
dhilst wants to merge 38 commits into
xcat2:masterfrom
VersatusHPC:release/2.19-rc1
Open

dhilst wants to merge 38 commits into
xcat2:masterfrom
VersatusHPC:release/2.19-rc1

Conversation

@dhilst

@dhilst dhilst commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes for the problems regarding RC1 of 2.19 captured by the CI

A riscv64 build of xcat-core fails at xCAT-genesis-scripts:

  ERROR: Cannot find/open srpm: dist/rocky-10-riscv64-xcat/rpms/SRPMS/
  xCAT-genesis-scripts-riscv64-2.19.0-snap202609020458.src.rpm

The srpm on disk is named xCAT-genesis-scripts-%{tarch}-2.19.0-....src.rpm.
xCAT-genesis-scripts.spec and xCAT-genesis-base.spec take the package name from
%{tarch}, which an %ifarch ladder sets. That ladder has no riscv64 branch, so
%{tarch} stays literal and rpm builds a package with a macro in its name.

The test expands both specs with rpmspec for every arch xCAT supports and
asserts the Name carries that arch. It fails on riscv64 for both specs.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 07a0e79)
… stays literal

A riscv64 build of xcat-core dies at xCAT-genesis-scripts with "Cannot find/open
srpm: ...xCAT-genesis-scripts-riscv64-2.19.0-<release>.src.rpm", because the srpm
rpm produced is named xCAT-genesis-scripts-%{tarch}-2.19.0-<release>.src.rpm.

xCAT-genesis-scripts.spec and xCAT-genesis-base.spec take their package name from
%{tarch}, which an %ifarch ladder sets for x86, x86_64, ppc64 and aarch64. riscv64
is absent, so rpm leaves the macro unexpanded and builds a package whose NAME
contains it. buildrpms.pl then looks for the name it asked for and cannot find it.

Add the riscv64 branch to both specs. An arch that is still missing from the
ladder now stops the build with %{error:} instead of naming a package after a
macro.

xCAT-test/unit/genesis_spec_target_arch.t covers this: it fails on riscv64
without this change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit c9de89c)
Installing xCAT on a riscv64 Ubuntu management node fails before it starts:

  E: Unable to locate package xcat
  E: Unable to locate package xcat-test

xCAT/debian/control and xCATsn/debian/control name their architectures
explicitly, as "amd64 ppc64el". riscv64 is absent, so no riscv64 deb is ever
produced and apt has nothing to install -- while the rest of the tree already
carries riscv64 install templates, DHCP boot policy, mknb support and a Genesis
machine configuration.

The test reads both control files and asserts the explicit list covers every
Debian architecture xCAT ships. It fails on both files today.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 059c234)
apt on a riscv64 Ubuntu management node cannot find xCAT at all:

  E: Unable to locate package xcat

xCAT/debian/control and xCATsn/debian/control list "Architecture: amd64 ppc64el",
so the build produces no riscv64 deb and the published apt repository serves only
those two architectures. Everything else riscv64 needs is already in the tree --
the rocky10/rhels10 riscv64 install templates, the grub2 boot policy, mknb, the
OpenEmbedded Genesis machine -- and the xcat-dep riscv64 repository is built,
signed and complete; only the core packages are missing.

Add riscv64 to both lists.

xCAT-test/unit/debian_control_arch_coverage.t covers this: it fails on both files
without the change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 13492f5)
builddebs.pl replaced build-ubunturepo as the Ubuntu builder, and the arch support
riscv64 has in build-ubunturepo did not come with it. The pipeline prefers builddebs.pl
whenever the ref carries it, so on this branch the switch silently stops producing riscv64
debs: no xcat_*_riscv64.deb in the pool, and a published Release that says
'Architectures: amd64 ppc64el'. apt on a riscv64 management node then reports 'Unable to
locate package xcat', which is the same failure build-ubunturepo was fixed for.

builddebs.pl reads its architectures from BuildUtils, so unlike build-ubunturepo -- which
hardcoded the pair in three places -- riscv64 goes in one: @DEB_ARCHES.

xcat-genesis-scripts is the exception and needs its own rule. Its per-arch deb Depends on
xcat-genesis-base-<arch>, and no riscv64 genesis-base deb exists, because riscv64 takes the
OpenEmbedded Genesis image from the shared xcat-dep pool. Built for riscv64 it would be
uninstallable, so deb_package_arches excludes it.

build_utils.t covers both: removing riscv64 from @DEB_ARCHES fails four assertions,
including the reprepro Architectures line, and removing the genesis-scripts exclusion fails
its own.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 90f9156)
mknb stages the Genesis payload before building a netboot image, and those copies are the
only point at which it learns that an installed Genesis image is unusable. The legacy branch
runs two of them and keeps only the second exit status, so an unreadable root tree is
invisible: mknb exits 0 having built an initramfs from nothing, and the node never boots
with no error naming the cause. When the kernel copy is the one that fails, the message
blames the root tree instead.

Extract the staging decision as stage_genesis_payload, preserving today's behaviour exactly,
so the outcome can be driven with an injected runner instead of a real Genesis tree.

The test fails on this commit, 2 of 10: 'an unreadable root tree fails the step' and 'the
failure names the kernel, not the root tree'.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 989deaa)
The legacy branch ran both copies and kept only the second exit status, so an unreadable
Genesis root tree left mknb exiting 0 with an initramfs built from nothing -- the node then
never boots and nothing names the cause. A failing kernel copy was reported as a failure of
the root tree, because the message was chosen from a flag set before either copy ran.

Return on the first failing copy, carrying the name of the file that could not be read.

mknb_genesis_staging.t goes from 2 failures to green on this commit.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit d46ca3e)
…olving

nodepurge/cases0 asserted that testnode1 and testnode2 no longer resolve by running 'ping'
with no count and expecting a non-zero exit. When the name does not resolve the ping fails
immediately, which is the passing path -- but when it DOES resolve, which is the regression
the case exists to catch, the ping never returns. The cell stops there and is killed by the
pipeline timeout, taking the whole run's JUnit with it, so the one case that finds a real
defect is also the one that hides every other result.

Bound both with -c 1 -w 2. The assertion is unchanged: a name that does not resolve, or
resolves to something that does not answer, still exits non-zero.

autotest_ping_bounded.t goes green on this commit.

Closes xcat2#59.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 187daab)
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 8d149c8)
builddebs.pl replaced build-ubunturepo, and both CD pipelines prefer it: the
fallback to build-ubunturepo fires only for refs that predate builddebs.pl, and
such a ref carries its own copy. Nothing on this branch runs the script, so its
presence only invites edits that never reach a build. The developer guide said
it was kept as a differential oracle until the CD pipelines moved over. They
have.

Remove the script, and record the removal in the build guide beside the
buildcore.sh, makerpm and buildlocal.sh entries.

xcat_probe_package_payload.t asserted the Debian staging by matching a `cp -f`
line in build-ubunturepo. builddebs.pl stages the helpers through
XCAT::BuildUtils::stage_probe_helpers, so the test now calls that function and
checks the files it produced. Verified by making stage_probe_helpers skip a
helper: the assertion fails.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
@dhilst dhilst changed the title fix(xcat-core): xCAT does not build or provision on riscv64 ci(xcat-core): Fixes to get CI running for all 2.19 targets Sep 4, 2026
A node whose vmstorage is a libvirt storage pool (dir://, nfs:// or lvm://) got a <disk>
element with no bus attribute. libvirt then chose the controller from the name of the
device alone, so the disk of a riscv64 node worked only while its volume was named sd*.

build_diskstruct in xCAT-server/lib/xcat/plugins/kvm.pm matched the pool entry, a hash
reference, against /^vd/, /^hd/ and /^sd/. A reference in a match is its address as a
string, so no branch ran and the bus was never set. The name of the device is in the
device field of that entry.

The three tests now read that field. The bus each one sets is the bus libvirt gives an
hd*, sd* or vd* name, so no domain changes: a riscv64 node keeps the sd* name its volume
has, and keeps the scsi controller the riscv64 virt machine provides. libvirt stores the
domain built before this change with bus="scsi" on that disk, which is what the domain
built after it states.

kvm_diskstruct_bus.t drives build_diskstruct in a scratch package, with a stub storage
pool in place of the one routine that reaches libvirt, and asserts the bus of an hd*, an
sd* and a vd* volume. It also asserts that a riscv64 node keeps the sd* name of its
volume. Four of its seven assertions fail without this change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
The name of the volume of a node, and the bus of a file-backed disk, could come from a
match made by a routine on the call path. A riscv64 node breaks on it: a leaked value
that is neither scsi nor virtio gives the node an hd* volume, and the riscv64 virt
machine has no IDE controller for that disk.

createstorage and build_diskstruct in xCAT-server/lib/xcat/plugins/kvm.pm read the model
of the disk out of the vmstorage value with s/=(.*)//, then read $1. The substitution is
allowed to fail, because most vmstorage values state no model, and a failed match leaves
$1 as the last successful capture. dohyp gives every node the storage model scsi before
mkvm runs, and a captured value takes priority over it, so a leaked value can only
replace the default that keeps a riscv64 node on sd*.

The leak follows the call path, not the history of the process. Perl restores $1 when the
block that set it ends, so a match made in a routine that has returned cannot reach
createstorage; only a match still live in an enclosing block can, and a later successful
match without a group empties $1 again. A long-running xcatd is not what makes this
happen, and looking for one is a wrong turn.

Both routines now read $1 only when their own substitution matches. A vmstorage value
that states a model, and vmstoragemodel, name the volume as before.

The default itself moves into default_storagemodel, which dohyp calls, so a test can hold
it. It sat inline with a comment, and changing it to ide left every assertion passing.

kvm_createstorage_model.t runs each node twice, once with a capture left live in the
calling block, because a case that leaves $1 empty passes against the defect. Five of its
eleven assertions fail without this change, and a sixth fails if the default changes.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
A failing case named one failed check and no result at all for the checks after it,
although the commands of the case kept running. The first [Failed] line was read as
the cause of the failure three times this week, and each time the real fault was a
later check: a riscv64 cell reported a makedns check 160 lines before rpower could
not start the domain.

run_case in xCAT-test/xcattest used one variable, $failflag, for two facts: the
result of the case, and the result of the check being reported. Every branch read
$failflag to decide whether to print [Pass] or [Failed], so a check that ran after a
failed one always read as failed. The guard "last if ($failflag)" at the top of the
check loop hid that, and hid every later check with it.

The result of a check is now $checkfail, set and read inside one iteration. A
continue block carries it into $failflag, which keeps the result of the case. The
guard and the per-branch "last" statements are gone, so each check reports what it
found. The output ~~ branch no longer clears $failflag on a match, which without the
guard would have turned a failed case into a passing one.

xCAT-test/unit/xcattest_report_every_check.t runs the harness over a fixture case and
asserts on the CHECK lines it writes. Without this change it reports two of four
checks, and one of two failed checks. A case whose checks all pass logs the same text
before and after: no truncation could happen while $failflag stayed 0.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
Three case files ship a check line the harness cannot use. pscp/cases0 asks for
"$$CN: done" with no operator, and load_case drops a check whose content does not
start with a word character, so the two lines go without a message and the case
asserts less than it reads. rscan/cases0 writes a command as a check, and
ngpfb/cases0 compares rc against a pattern, which no operator accepts, so both cases
report "Unrecognized testcase syntax" and fail on every run.

Each line is repaired to what the case around it says it means. pscp prints
"<node>: done" for each node it copied, in xCAT-client/bin/pscp, so the two lines
become "output=~$$CN: done". rscan runs its check against the definitions that
"rscan -z -w" wrote, so "check:lsdef -l $$CN" becomes a cmd and keeps the two checks
that follow it. rmhwconn is asked for output without "state=LINE UP", which is what
the lshwconn checks in the same case assert the other way round.

xCAT-test/unit/autotest_check_lines_are_understood.t reads every check line under
xCAT-test/autotest/testcase and asserts the harness reports one result for each of
them, and that none uses an operator it does not know. Without this change it names
all five: two lines from ngpfb, one from rscan, and pscp reporting 37 results for
the 39 check lines it ships.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
@viniciusferrao viniciusferrao added this to the 2.19 milestone Sep 4, 2026
@viniciusferrao
viniciusferrao self-requested a review September 4, 2026 21:48
…node boots a shell

A compute node fetches the legacy Genesis kernel and initramfs, the kernel starts, and then
nothing else happens: doxcat never runs, the node acquires no address, sshd refuses every
connection and the node stays at status=powering-on. The five genesis test cases in
xCAT-test/autotest/testcase/genesis have never passed. VersatusHPC/xcat-internal#78.

Three holes in the image, each fatal on its own. dracut_105/el/xcat-cmdline.sh ends in
`while :; do tmux attach-session -t doxcat || tmux new-session -s doxcat doxcat; done`, and the
image carries no locale data, so tmux exits with "need UTF-8 locale" and the loop spins
without ever reaching doxcat. module-setup.sh does not install
/usr/libexec/openssh/sshd-session, which OpenSSH 9.8 and later exec for every connection and
which EL9 now ships. xCAT-genesis-base.spec does not BuildRequire dhcp-client, so dhclient is
absent from the build chroot; dracut_install reports the missing binary and returns, and the
module install function keeps going, so the image ships without it.

xcat-cmdline.sh now resolves xcat_console_mode() once and runs doxcat directly when the
terminal multiplexer cannot start a session; the same shape replaces the screen loop on
Ubuntu. module-setup.sh installs the OpenSSH session helpers and the C.utf8 locale where they
exist. The spec BuildRequires dhcp-client on the releases that package it, and runs the new
xCAT-genesis-builder/verify-genesis-payload over the extracted payload, which fails the build
when sshd needs a helper the image lacks, when tmux has no UTF-8 locale, or when a binary the
caller named is missing.

The same runs exposed four defects in the test cases themselves. test.sh defined its synthetic
node as ppc64le whatever the management node was, so nodeset could not find a genesis kernel on
x86_64. genesistest.pl get_os() matched neither AlmaLinux nor Rocky and reported the OS as
unsupported. The -g check read $? instead of check_genesis_file()'s return value, so it could
never fail. And testxdsh() met "REMOTE HOST IDENTIFICATION HAS CHANGED" from the second boot
on, because Genesis makes new host keys every boot and nothing dropped the stale known_hosts
entry. test.sh now derives the node arch from uname and takes the tftp root from TFTPDIR,
get_os() recognises the redhat family, report_genesis_files() carries the result to an exit
status, and forget_host_keys() runs makeknownhosts -r before each probe.

Tests: genesis_console_mode.t drives xcat_console_mode() with the multiplexer shadowed;
genesis_payload_verification.t drives the verifier over payload trees carrying each hole;
genesis_testcase_helpers.t drives get_os(), check_genesis_file(), report_genesis_files() and
testxdsh(); genesis_incorrectmasterip_check.t runs test.sh against a scratch tftp root. Each
fails on the parent commit. The verifier also reports all three holes against the released
xCAT-genesis-base-x86_64-2.19.0-snap202609021858 payload.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit cb6021e)
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…ps it

doxcat names dhclient at six call sites. AlmaLinux 10 and EPEL 10 package no ISC
dhcp-client, so the legacy Genesis image for el10 carries no dhclient binary. A node
that boots that image reports "dhclient: command not found" on its console and never
acquires an address.

The test lifts the client selection out of doxcat and runs it with the clients shadowed
by stubs that record their own argv. doxcat cannot be sourced, so the routines are
extracted and driven on their own. It also reads the spec and the dracut module, which
decide what client reaches the image.

It fails on the current source for nine reasons: doxcat carries no selection routine and
no runner, it still starts command lines with dhclient and still chains into dhclient
from the secondary NIC loop, the spec build-requires no client on the releases that drop
the ISC one and does not check the payload for one, and the dracut module installs
dhclient alone.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 5d2801f)
A compute node that boots the legacy Genesis image on el10 x86_64 never acquires an
address. Its serial console reports "/usr/bin/doxcat: line 293: dhclient: command not
found" and then "It seems to be taking a while to acquire an IPv4 address". The DHCP
server side is sound: Kea leases the address and the node never asks for it.

AlmaLinux 10 and EPEL 10 package no ISC dhcp-client. dracut_install reports a missing
binary and returns, so module-setup.sh named dhclient, the build kept going and the image
shipped without a client. doxcat then named dhclient at six call sites with no
alternative.

doxcat now chooses its client at run time. genesis_dhcp_command() returns the command
line for one interface and one address family, and genesis_start_dhcp() runs it and
reports when the image carries none. The ISC client keeps its command line where a
release packages it, so el8 and el9 are unchanged. Where it is absent, dhcpcd stands in:
AlmaLinux 10 baseos packages it at 236 KB, and it carries its own resolv.conf, hostname
and ntp hooks, so it needs no dhclient-script. dhcpcd on a single interface exits when
its 30 second timeout expires and de-configures the interface as it goes, so it is asked
for -t 0 and -p.

The dracut module installs whichever client the build root carries, together with the
hooks dhcpcd runs on every lease. The spec build-requires dhcpcd from rhel 10 on, and
verify-genesis-payload now requires usr/sbin/dhcpcd there, so an image that ships with no
client fails the build instead of reaching a node.

genesis_dhcp_client.t drives both routines with the clients shadowed by recording stubs.
It fails on the parent commit, and deleting the dhcpcd branch turns five of its
assertions red.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit b932999)
The nodeset_shell_incorrectmasterip case ran "nodeset testnode shell", the command failed
with "/tftpboot/boot/grub2/grub2.x86_64 does not exits", and the case still passed.
check_destiny in xCAT-test/autotest/testcase/genesis/test.sh discards the return value of
runcmd and greps the boot configuration file, which grub2.pm writes before it stops on the
missing boot loader. The sub-case asserts nothing.

wait_for_boot in xCAT-test/autotest/testcase/genesis/genesistest.pl waits for
nodelist.status "booted". A Genesis node reports its destiny with getdestiny and xcatd
writes "shell", "configuring" or "booting" from it, never "booted". Every caller discards
the return value, so each case rests on its xdsh probes alone.

genesis_incorrectmasterip_check.t now runs the check with a nodeset that fails, and reads
whether the grub2 boot loader for the node arch is present when nodeset runs.
genesis_testcase_helpers.t drives the status wait with lsdef shadowed, and drives the shell
case with every command it runs shadowed. genesis_payload_verification.t reads a payload
without mktemp, which getdestiny needs to make its request file.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 3597760)
…nd a node that never booted

The nodeset_shell_incorrectmasterip case passed while "nodeset testnode shell" failed with
"/tftpboot/boot/grub2/grub2.x86_64 does not exits". The grub2 sub-case asserted nothing. Every
genesis case reported "After 30 iterations node status: powering-on" and passed anyway.

check_destiny in xCAT-test/autotest/testcase/genesis/test.sh discarded the return value of
runcmd and read the boot configuration file, which grub2.pm writes before it stops on the
missing boot loader. xCAT builds no grub2 boot loader for x86_64, so the file is absent on a
correctly built management node. wait_for_boot in genesistest.pl waited for nodelist.status
"booted"; a Genesis node reports its destiny with getdestiny and xcatd writes "shell",
"configuring" or "booting" from it. Every caller discarded the return value. The node did not
reach even those statuses, because getdestiny makes its request file with mktemp and the
dracut module never installed it.

check_destiny now returns the status of nodeset, and the grub2 check stages an empty
grub2.<arch> when the management node has none and removes it after. wait_for_node_status
takes the status the destiny implies and each caller fails when the node does not reach it;
the shell case moved into run_nodeset_shell_test so its result can be read. clearenv no longer
waits, because "rinstall <node> boot" boots a disk with no operating system and reports
nothing. The dracut modules install mktemp and verify-genesis-payload requires it.

Tests: genesis_incorrectmasterip_check.t runs the check with a failing nodeset and reads
whether the boot loader is present when nodeset runs; genesis_testcase_helpers.t drives the
status wait and the shell case; genesis_payload_verification.t reads a payload without mktemp.
Each fails on the parent commit.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>

(cherry picked from commit 8ecf0a8)

The Release hunk of the original commit is dropped. buildrpms.pl writes Release from
SOURCE_DATE_EPOCH at build time, so the committed snap stamp is build debris.
…rchitecture

The ppc64el and riscv64 xcat debs depend on xcat-genesis-scripts-amd64, and
xcat-genesis-scripts-ppc64 depends on xcat-genesis-base-ppc64, a package no
repository publishes. Nothing reports either one: the amd64 scripts package is
Architecture: all, so it installs on any architecture, and the broken ppc64
dependency is never reached because nothing pulls that package.

Extend debian_control_arch_coverage.t. It now reads the Depends field of
xCAT/debian/control and xCATsn/debian/control, applies each architecture
restriction the way dpkg-gencontrol does, and asserts that the genesis scripts
a given architecture receives are that architecture's own. It also asserts that
xCAT-genesis-scripts/debian/control-<arch> builds xcat-genesis-scripts-<arch>
and depends on xcat-genesis-base-<arch>.

Eight of the eighteen assertions fail on this tree.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…ecture

A ppc64el or riscv64 management node installs the amd64 Genesis. xCAT and
xCATsn declare Architecture: amd64 ppc64el riscv64 and one unrestricted
Depends: xcat-genesis-scripts-amd64, so every architecture gets it. That
package is Architecture: all, so apt reports no error. It lays down
/opt/xcat/share/xcat/netboot/genesis/x86_64 and pulls the 128 MB
xcat-genesis-base-amd64, and the node receives no Genesis for its own
architecture. xcat-genesis-scripts-ppc64, the package that would carry it, is
uninstallable: it depends on xcat-genesis-base-ppc64, and builddeb-genesis-base
names the ppc64el base deb xcat-genesis-base-ppc64el.

xCAT/debian/control and xCATsn/debian/control now restrict the dependency by
architecture, the way xCAT.spec does with
%{?genesistarch:Requires: xCAT-genesis-scripts-%{genesistarch}}. amd64 gets
xcat-genesis-scripts-amd64, ppc64el gets xcat-genesis-scripts-ppc64el, and
riscv64 gets neither, because its Genesis is the OpenEmbedded image.
xCAT-genesis-scripts/debian/control-ppc64el builds xcat-genesis-scripts-ppc64el
and depends on xcat-genesis-base-ppc64el. It conflicts with and replaces the
old name, which shares the same files.

debian_control_arch_coverage.t asserts the genesis scripts an architecture
receives are that architecture's own, and that control-<arch> builds
xcat-genesis-scripts-<arch> against xcat-genesis-base-<arch>. Eight of its
assertions fail without this change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…ry has

go-xcat keeps one package list per packaging format. Its dpkg list names
xcat-genesis-scripts-ppc64 and xcat-genesis-base-ppc64. The Debian
architecture is ppc64el, so apt cannot find either package and both
"go-xcat install" and "go-xcat uninstall" stop on ppc64el.

The test evaluates the two arrays of go-xcat, one run per branch, and
compares them with the packaging: the deb names against the Package and
Depends fields of the xCAT-genesis-scripts control files, the rpm names
against the Genesis target architectures of xCAT-genesis-base.spec.

It fails on the four deb assertions today.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
On ppc64el "go-xcat install" and "go-xcat uninstall" stop with

  E: Unable to locate package xcat-genesis-scripts-ppc64

apt refuses the whole transaction, so no package of the list is installed
or removed.

The dpkg branch of GO_XCAT_INSTALL_LIST in
xCAT-server/share/xcat/tools/go-xcat names xcat-genesis-scripts-ppc64 and
xcat-genesis-base-ppc64. Those are rpm architecture names.
GO_XCAT_UNINSTALL_LIST is derived from the same array, so both actions
carry the wrong names. The Debian architecture is ppc64el.

This change renames the two entries of the dpkg branch. The rpm branch
keeps ppc64, which is the Genesis target architecture of the spec.

go_xcat_genesis_package_names.t fails the four deb assertions without
this change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
debuild-xcat-genesis-base maps only x86_64 to amd64. For the ppc64 rpm it
leaves every name at ppc64, so it builds xcat-genesis-base-ppc64 and
writes "Breaks: xcat-genesis-scripts-ppc64". The Debian architecture is
ppc64el, and the packages xCAT-genesis-scripts builds for it are named
ppc64el, so the Breaks names a package no repository publishes.

This change replaces the single x86_64 test with an architecture map:
x86_64 to amd64, ppc64 and ppc64le to ppc64el. The map now drives the
source tree rename, the control and changelog rewrite, and the Breaks
field. PACKAGE_ARCH keeps the rpm value, because the preinst removes the
Genesis tree under that name.

genesis_base_deb_arch.t drives the script with alien shadowed. It fails
the three ppc64el assertions without this change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
The legacy Genesis image built on el10 carries no openssl command. getcert waits for one
with no bound, so the node reports no destiny and never boots. Nothing in the build or in
the suite sees the hole.

genesis_base_spec_buildrequires.t reads the spec and requires an unconditional
BuildRequires on openssl, and requires that no %{_target_cpu} is read after
BuildArch: noarch, where rpm has already set it to noarch.

genesis_payload_verification.t drives verify-genesis-payload with a dracut module and a
payload, and requires every command the module installs at the top level of install() to be
present. A name installed under a condition is not required, and a module the verifier
reads no names from is a usage error.

genesis_getcert_missing_openssl.t runs getcert with a PATH that holds stubs and no openssl,
under a harness timeout. Ten assertions fail on this commit: getcert never stops, and the
verifier and the spec do not know about openssl.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
… never returns

A compute node that boots the legacy Genesis image built on el10 stops after
"Getting initial certificate --> <mn>:3001". /etc/xcat is empty, getdestiny never runs and
the node reports no destiny, so xcatd leaves nodelist.status at powering-on. On the node,
openssl reports "command not found" and the getcert process stays alive.

xCAT-genesis-base.spec never build-requires openssl. el8 and el9 hold /usr/bin/openssl in
the build root as a dependency of another package, and el10 does not, so dracut_install in
dracut_105/el/module-setup.sh installed nothing and reported nothing. getcert line 8 waits
for openssl with no bound, which turns the missing command into a wait instead of an error.

The spec now build-requires openssl on every release. verify-genesis-payload reads the
command names back from the dracut module with --commands-from and requires each one in the
payload, so the next name the build root does not supply fails the build. getcert reports a
missing openssl and stops, and bounds the wait for /etc/xcat/certkey.pem at 600 seconds,
which is far longer than the background 4096 bit key needs.

The spec read %{_target_cpu} after BuildArch: noarch, where rpm has already set it to
noarch, so the dmidecode and efibootmgr build-requires never applied and a sed deleted their
dracut_install line on every architecture. Every shipped image lacks both. The spec now
reads %{tarch}, and the dracut module installs whichever of the two the build root carries,
because ppc64le packages neither.

xCAT-test/unit/genesis_getcert_missing_openssl.t, genesis_payload_verification.t and
genesis_base_spec_buildrequires.t fail on the test commit before this one.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
mknb writes the management node key to /.ssh/authorized_keys for the legacy
Genesis image. sshd reads that file only while the home directory of root is
/. The dracut cmdline hook makes it / by deleting the root entry the image
ships and appending its own.

The test runs that rewrite against both root entry shapes dracut writes, for
all three hooks, and reads back /etc/passwd. It also keeps a user name that
starts with root in the file, so a wider delete cannot pass unnoticed.

Six of the eighteen assertions fail today.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…/root

Every case that boots a node into the el10 legacy Genesis image fails with
"root@<cn>: Permission denied (publickey,password,keyboard-interactive)".
mknb writes the management node key to /.ssh/authorized_keys, and sshd looks
for it under the home directory of root.

The dracut cmdline hook deletes the root entry the image ships with
"sed -i '/^root:x/d'" and appends its own entry with the home directory /.
dracut 99base writes that entry, and from dracut 060 the password field holds
x only when the image is built --hostonly. The Genesis image is built with -N,
so el10 (dracut 107) ships "root::0:0::/root:/bin/sh". The delete does not
match, two root entries survive, and getpwnam returns the first one. el8
(dracut 049) and el9 (dracut 057) always ship the x and are not affected.

The delete now matches the user name only. All three cmdline hooks carry the
same statement and all three change.

genesis_root_home.t runs the rewrite against both entry shapes and fails on
the empty password field without this change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
… image

nodeset_shell_lzma asserts "output=~genesis" on "ls -l
/tftpboot/xcat/genesis.fs.*.lzma". The path in the command holds the string
the check looks for, so the error "ls: cannot access
'/tftpboot/xcat/genesis.fs.*.lzma'" matches too. The check is green whether
mknb wrote the lzma image or not. Removing the "xz --format=lzma" fallback
from genesis_lzma_command in mknb.pm makes mknb write only the gz image, and
this check still passes.

The same case starts with a "yum install" of xz-lzma-compat from a
rpmfind.net CentOS 8-Stream PowerTools URL. No check follows it. The URL
returns 404 since CentOS 8-Stream went end of life, so the step has done
nothing for a long time.

The check is now "rc==0". ls returns 2 when the glob matches no file, so the
check is red exactly when mknb wrote no lzma image. The dead install and its
paired "yum remove" are deleted rather than gated, because
genesis_lzma_command falls back to "xz --format=lzma" and xz is present on
every EL management node: the lzma image the case tests is produced without
the package.

Verified with the runcmd and check-evaluation code lifted out of
xCAT-test/xcattest. With the lzma image present both checks are green. With
only the gz image present the old check stays green and "rc==0" is red.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 942729a)
A sweep of xCAT-test/autotest/testcase for the shape found in
nodeset_shell_lzma -- a check whose expected pattern also appears in its own
command -- returns 274 matches. In six of them the self-matching check is the
only proof of the property the case exists to test, so the case is green when
the property is absent.

xdcp_RP and xdcp_R write "test1" into /tmp/xdcp/test1/test1.txt, then read it
back and assert "output=~test1". The path repeats the content, so "cat:
/tmp/xdcp/test1/test1.txt: No such file or directory" matches. These are the
only checks that read the copied bytes; the earlier ls checks prove the name
arrived, not the content. updatenode_diskful_syncfiles_dir has the same shape
for the files updatenode -F syncs.

lsxcatd_null asserts "output=~lsxcatd" on "lsxcatd", which the shell's
"lsxcatd: command not found" satisfies.
export_import_multiple_osimages_by_dir asserts "output=~site" on "ls -R
/opt/inventory/site", which "ls: cannot access '/opt/inventory/site'"
satisfies.

The copied and synced files now carry a content marker that the path does not
repeat, so the check reads the bytes and not the file name. lsxcatd_null
asserts a line of the usage text, and the two inventory listings assert the
exported osimage name.

Verified with the runcmd and check-evaluation code lifted out of
xCAT-test/xcattest. Every replaced check is green both when the behaviour is
present and when it is removed. Every new check is green with the behaviour
present and red without it.

Seven further self-matching checks in xdcp/cases0 ("ls -l /tmp/test1.txt"
with "output=~test1") measure nothing but sit behind a sound "ls -l /tmp"
check, so a real failure is already caught. They are left for a decision.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit 194dd4e)

@viniciusferrao viniciusferrao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @dhilst, two points here:

  1. The ppc64el naming cleanup makes sense given xCAT’s historical
    Power naming. Could we also add replacement/conflict metadata for
    the old xcat-genesis-base-ppc64 package, as we do for the scripts
    package, so existing installations can upgrade cleanly? Keeping
    the legacy genesis/ppc64/ directory is fine; this is only about
    the package-name transition.

  2. In verify-genesis-payload, grep -v '^[/-]' also drops absolute
    command paths, so missing binaries such as /usr/bin/awk can pass
    verification. Could we check required absolute paths relative to
    the payload root as well, and add a test for a missing one?

Thanks!

@dhilst
dhilst marked this pull request as ready for review September 11, 2026 18:54
…ase/2.19-rc1

master moved 149 commits ahead of the branch point and four files needed a
decision.

xCAT/debian/control and xCATsn/debian/control: master moved nmap and
ipmitool-xcat into Depends, raised the ipmitool version and added the s390x
OpenEmbedded Genesis recommendation. The branch made the genesis-scripts
dependency per architecture. Both are kept, so the ppc64el metapackage depends
on xcat-genesis-scripts-ppc64el and no longer on the amd64 package.

build-utils/lib/XCAT/BuildUtils.pm and xCAT-test/unit/build_utils.t: master
replaced @DEB_ARCHES plus the branch's %NO_RISCV64 exception list with
%ARCH_PACKAGES, which carries the architecture list per package.
deb_package_arches returns the same answer for every package, so master's form
is kept and %NO_RISCV64 is dropped.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…tion

verify-genesis-payload reads the names the dracut module installs back out of
module-setup.sh and checks each one against the extracted payload. It drops
every name that starts with "/", so the 609 absolute paths the EL module names
are checked by nothing. An image built without /usr/bin/awk, /etc/services or
/lib64/libnss_dns.so.2 passes.

The Genesis debs carry the architecture in the package name. 2.19 renames the
ppc64 debs to ppc64el, and neither builddeb-genesis-base nor
debuild-xcat-genesis-base names the deb the new package supersedes. dpkg keeps
xcat-genesis-base-ppc64 installed beside xcat-genesis-base-ppc64el, and the old
package owns the same files under /opt/xcat/share/xcat/netboot/genesis.

genesis_payload_verification.t drives the verifier against a payload missing
/usr/bin/awk and one missing /etc/services. genesis_base_deb_arch.t asserts the
Replaces and Breaks the alien path writes. genesis_base_deb_control_rewrite.t
lifts rewrite_control() out of builddeb-genesis-base and runs it over the
control file in the tree.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
… and name the deb the rename supersedes

verify-genesis-payload dropped every name that started with "/" before checking
the payload, so the 609 absolute paths the EL dracut module installs were
checked by nothing. An image with no /usr/bin/awk passed. The filter now drops
only option words, and an absolute name is read back under the payload root.

The ppc64 to ppc64el rename left the new Genesis base deb without a relation to
the deb it replaces, so dpkg kept xcat-genesis-base-ppc64 installed beside it
with its own copy of the files under /opt/xcat/share/xcat/netboot/genesis.
builddeb-genesis-base and debuild-xcat-genesis-base now write Replaces and
Breaks for the superseded package, as the genesis-scripts control file already
does. The native path takes the architecture rewrite into rewrite_control() so
the superseded name is derived in one place.

genesis_payload_verification.t, genesis_base_deb_arch.t and
genesis_base_deb_control_rewrite.t fail without this change.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
xcat_riscv64_genesis_dependency.t pins the deb dependency on the legacy Genesis
scripts as a single xcat-genesis-scripts-amd64 entry qualified [!riscv64]. That
entry gives a ppc64el management node the amd64 scripts package, which pulls the
amd64 Genesis base with it, and this branch replaces it with one entry per
architecture.

The test now asserts that every xcat-genesis-scripts entry names an architecture
that has a legacy Genesis, and that the ppc64el reduction asks for
xcat-genesis-scripts-ppc64el. The riscv64 assertions are unchanged: no entry
applies there.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
… whole suite

Thirteen test files this branch adds call BAIL_OUT at fifty-one places:
an extraction that stopped matching, a fixture that is not there, a
harness that wrote no log. prove stops every remaining file on a
bail-out, not only the file that called it, so one of them hides the
results of every test that would have run after it. die is just as loud
and costs only its own file.

Fifteen comments the branch added also carried the incident rather than
the constraint. Three pasted an error transcript, five traced a failure
from a macro or a missing file out to a node that never boots, and the
rest counted call sites, package sizes or dracut build numbers. Each now
states the one fact the reader cannot re-derive from the code.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…t was replaced

The comment above the /usr/bin/awk case in genesis_payload_verification.t
said what the verifier used to do with an absolute path. The rule the
reader needs is what dracut_install does with one.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
Eight unit tests measure shell code: the Genesis dracut cmdline hooks, doxcat,
getcert, the two Genesis deb builders, go-xcat and the genesis test case. The
Perl in each one is scaffolding. It reads the script, lifts a block out with a
regular expression, writes a wrapper, shells out and reads the files back. A
reader follows two languages to reach one assertion, and the scaffolding is
longer than the assertion.

xCAT-test/bats already states this kind of assertion in the language of the
thing under test, and the xcat_test workflow runs it. The eight files move
there. Each one keeps what it proved: the rpm architecture becomes the Debian
architecture and names the deb it supersedes, the dracut hook picks the console
mode the multiplexer can provide, the hook gives root the home directory /,
getcert stops when the image ships no openssl, the genesis case defines its
node with the architecture of the management node and fails when nodeset fails,
doxcat picks dhcpcd where the release drops the ISC client, and go-xcat names
the Genesis packages the packaging builds.

helpers/shell_source.bash gains refute_grep. bash ignores errexit for a command
inverted with "!", so "! grep" anywhere but the last line of a test can never
fail it.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
…t fail

genesis_base_deb_arch.t stopped with a die when debuild-xcat-genesis-base was
absent, because a checkout without the converter has no deb rename to measure.
The BATS file skipped there instead, and a skip reads green.

setup() now asserts the script is readable.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants