From b3ed5e6d24ca35fcec1c1578a50f1246569f1e2f Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Fri, 11 Sep 2026 16:00:32 -0300 Subject: [PATCH] test(xcat-core): a Genesis image with no /usr/bin/awk passes verification 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> --- xCAT-test/unit/genesis_base_deb_arch.t | 16 ++++- .../unit/genesis_base_deb_control_rewrite.t | 64 +++++++++++++++++++ xCAT-test/unit/genesis_payload_verification.t | 28 +++++++- 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 xCAT-test/unit/genesis_base_deb_control_rewrite.t diff --git a/xCAT-test/unit/genesis_base_deb_arch.t b/xCAT-test/unit/genesis_base_deb_arch.t index 6f7e0b701..5bcb83227 100644 --- a/xCAT-test/unit/genesis_base_deb_arch.t +++ b/xCAT-test/unit/genesis_base_deb_arch.t @@ -2,7 +2,8 @@ # debuild-xcat-genesis-base converts the EL Genesis base rpm to a deb. The rpm name carries the # Genesis target architecture, and the deb must carry the Debian architecture: ppc64 becomes # ppc64el, x86_64 becomes amd64. An unmapped architecture leaves the deb named after the rpm and -# makes it break a genesis-scripts package that no repository publishes. +# makes it break a genesis-scripts package that no repository publishes. The rename also has to +# name the deb it supersedes, or an upgraded ppc node keeps xcat-genesis-base-ppc64 as well. # # The script is driven here with alien and dpkg-buildpackage shadowed by shell functions. use strict; @@ -75,6 +76,13 @@ my %expected = ( 'xCAT-genesis-base-ppc64-2.13.10-snap202601010000.noarch.rpm' => 'ppc64el', ); +# The package the new deb takes over from. On ppc that is the deb this rename leaves behind: +# without the relation dpkg keeps xcat-genesis-base-ppc64 and its copy of the same files. +my %superseded = ( + 'amd64' => 'xcat-genesis-amd64', + 'ppc64el' => 'xcat-genesis-ppc64, xcat-genesis-base-ppc64', +); + for my $rpm (sort keys %expected) { my $arch = $expected{$rpm}; my ($dir, $control) = convert($arch, $rpm); @@ -84,8 +92,12 @@ for my $rpm (sort keys %expected) { like($dir, qr/\Q-$arch-\E/, "$rpm builds in a $arch source tree"); like($control, qr/^Package:\s*xcat-genesis-base-\Q$arch\E$/m, "$rpm builds the package xcat-genesis-base-$arch"); - like($control, qr/^Breaks:\s*xcat-genesis-scripts-\Q$arch\E\b/m, + like($control, qr/^Breaks:.*\bxcat-genesis-scripts-\Q$arch\E\b/m, "xcat-genesis-base-$arch breaks the genesis scripts of its own architecture"); + like($control, qr/^Replaces:\s*\Q$superseded{$arch}\E\s*$/m, + "xcat-genesis-base-$arch replaces $superseded{$arch}"); + like($control, qr/^Breaks:\s*\Q$superseded{$arch}\E\b/m, + "xcat-genesis-base-$arch breaks $superseded{$arch}"); } done_testing(); diff --git a/xCAT-test/unit/genesis_base_deb_control_rewrite.t b/xCAT-test/unit/genesis_base_deb_control_rewrite.t new file mode 100644 index 000000000..44fb2979d --- /dev/null +++ b/xCAT-test/unit/genesis_base_deb_control_rewrite.t @@ -0,0 +1,64 @@ +#!/usr/bin/env perl +# builddeb-genesis-base builds the Genesis base deb natively on Ubuntu. It writes the target +# architecture into debian/control, which is held in the amd64 form in the tree. 2.19 renames +# the ppc64 debs to ppc64el, so the ppc control must also name the deb it supersedes: without +# the relation dpkg keeps xcat-genesis-base-ppc64 installed beside the new package, and that +# old package owns the same files under /opt/xcat/share/xcat/netboot/genesis. +# +# The script needs dracut and root, so rewrite_control() is lifted out of it and run alone +# against the control file the tree ships. +use strict; +use warnings; + +use File::Slurper qw(read_text write_text); +use File::Temp qw(tempdir); +use FindBin; +use lib "$FindBin::Bin/../lib"; +use Test::More; + +use XCAT::Test::File qw(repo_path slurp_repo_file); + +my $script = repo_path('xCAT-genesis-builder/builddeb-genesis-base'); +my $control = repo_path('xCAT-genesis-builder/debian/control'); +plan skip_all => 'builddeb-genesis-base not found' unless -f $script; +plan tests => 8; + +my $text = slurp_repo_file('xCAT-genesis-builder/builddeb-genesis-base'); +my ($function) = $text =~ /^(rewrite_control\(\)\s*\{.*?^\})/ms; +BAIL_OUT('rewrite_control() no longer matches in builddeb-genesis-base') + unless defined $function; + +my $tmpdir = tempdir(CLEANUP => 1); + +# What the ppc64el package has to take over from, and what amd64 already took over from. +my %superseded = ( + 'amd64' => 'xcat-genesis-amd64', + 'ppc64el' => 'xcat-genesis-ppc64, xcat-genesis-base-ppc64', +); + +for my $arch (sort keys %superseded) { + my $out = rewrite($arch); + + like($out, qr/^Package:\s*xcat-genesis-base-\Q$arch\E$/m, + "$arch control names the package xcat-genesis-base-$arch"); + like($out, qr/^Replaces:\s*\Q$superseded{$arch}\E\s*$/m, + "$arch control replaces $superseded{$arch}"); + like($out, qr/^Breaks:\s*\Q$superseded{$arch}\E\b/m, + "$arch control breaks $superseded{$arch}"); + like($out, qr/^Breaks:.*\bxcat-genesis-scripts-\Q$arch\E \(<< 2\.13\.10\)/m, + "$arch control breaks the genesis scripts of its own architecture"); +} + +#--- +# rewrite: run the lifted rewrite_control() over a copy of the control file in the tree. +#--- +sub rewrite { + my ($arch) = @_; + my $copy = "$tmpdir/control.$arch"; + write_text($copy, read_text($control)); + my $driver = "$tmpdir/driver.$arch.sh"; + write_text($driver, "#!/bin/bash\nset -eu\n$function\nrewrite_control \"\$1\" \"\$2\"\n"); + system('bash', $driver, $copy, $arch) == 0 + or BAIL_OUT("rewrite_control failed for $arch"); + return read_text($copy); +} diff --git a/xCAT-test/unit/genesis_payload_verification.t b/xCAT-test/unit/genesis_payload_verification.t index c845b3cd9..7e4be6ba6 100644 --- a/xCAT-test/unit/genesis_payload_verification.t +++ b/xCAT-test/unit/genesis_payload_verification.t @@ -15,7 +15,7 @@ use XCAT::Test::File qw(repo_path); my $verifier = repo_path('xCAT-genesis-builder/verify-genesis-payload'); plan skip_all => 'verify-genesis-payload not found' unless -f $verifier; -plan tests => 18; +plan tests => 22; my $tmpdir = tempdir(CLEANUP => 1); my $module_seq = 0; @@ -71,6 +71,23 @@ my $noopenssl = build_payload(sshd_execs_session => 1, session_helper => 1, tmux isnt($rc, 0, 'a payload without openssl fails'); like($err, qr/openssl/, 'the missing openssl is named'); +# dracut_install installs an absolute path at that same path. The verifier used to drop every +# name that started with "/", so an image with no /usr/bin/awk passed. doxcat, getdestiny and +# the firmware wrappers all run awk. +my $noawk = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, + dhclient => 1, mktemp => 1, commands => [qw(openssl wget tar)], absent => ['usr/bin/awk']); +($rc, $err) = run_with_commands($module, $noawk); +isnt($rc, 0, 'a payload without the absolute path /usr/bin/awk fails'); +like($err, qr{/usr/bin/awk}, 'the missing /usr/bin/awk is named'); + +# The module names data files by absolute path too. Genesis resolves service names with +# /etc/services. +my $noservices = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, + dhclient => 1, mktemp => 1, commands => [qw(openssl wget tar)], absent => ['etc/services']); +($rc, $err) = run_with_commands($module, $noservices); +isnt($rc, 0, 'a payload without the absolute path /etc/services fails'); +like($err, qr{/etc/services}, 'the missing /etc/services is named'); + # The DHCP client is release-dependent, so the module installs it inside a conditional. Those # names are not the contract; the spec passes the one it wants as a required path. my $conditional = write_module_setup(['wget'], ['dhclient']); @@ -112,6 +129,15 @@ sub build_payload { write_text("$root/usr/sbin/dhclient", "dhclient\n") if $opt{dhclient}; write_text("$root/usr/bin/mktemp", "mktemp\n") if $opt{mktemp}; write_text("$root/usr/bin/$_", "$_\n") for @{ $opt{commands} || [] }; + + # The module written by write_module_setup names these two by absolute path. + my %absent = map { $_ => 1 } @{ $opt{absent} || [] }; + for my $path (qw(usr/bin/awk etc/services)) { + next if $absent{$path}; + my ($dir) = $path =~ m{^(.*)/}; + make_path("$root/$dir"); + write_text("$root/$path", "$path\n"); + } return $root; }