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

test(mknb): capture a Genesis copy failing without failing mknb

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 989deaa31eb1f3979a8db030d3ae8c404abda2fb)
This commit is contained in:
Daniel Hilst
2026-09-04 10:15:57 -03:00
parent 97f481a570
commit 445b755d8e
2 changed files with 104 additions and 13 deletions
+40 -13
View File
@@ -327,6 +327,41 @@ sub genesis_lzma_command {
return;
}
#-------------------------------------------------------------------------------
=head3 stage_genesis_payload
Descriptions:
Copy the Genesis payload into place for mknb: for a legacy image the unpacked
root tree and then the kernel, for an exported image the nbroot tree.
Extracted so the outcome can be driven directly. The copies are the only
place mknb learns that an installed Genesis image is unusable, and a caller
cannot tell WHICH copy failed from a single exit status.
Arguments:
genesis_type, genesis_dir, tftpdir, arch, tempdir, and an optional run
coderef used in place of system() by the tests.
Returns:
(rc, source) -- rc is the exit status of the copy that failed, and source
names it, so the caller reports the file it could not read.
=cut
#-------------------------------------------------------------------------------
sub stage_genesis_payload {
my (%a) = @_;
my $run = $a{run} || sub { return system($_[0]); };
my $rc;
if (($a{genesis_type} // '') eq 'legacy') {
$rc = $run->("shopt -s dotglob; GLOBIGNORE=\".:..\" cp -a $a{genesis_dir}/fs/* $a{tempdir}");
$rc = $run->("cp -a $a{genesis_dir}/kernel $a{tftpdir}/xcat/genesis.kernel.$a{arch}");
return ($rc, "$a{genesis_dir}/fs");
}
$rc = $run->("cp -a $a{genesis_dir}/nbroot/* $a{tempdir}");
return ($rc, "$a{genesis_dir}/nbroot");
}
sub process_request {
my $request = shift;
my $callback = shift;
@@ -555,21 +590,13 @@ sub process_request {
unless (-e "$tftpdir/xcat") {
mkpath("$tftpdir/xcat");
}
my $rc;
if ($genesis_type eq 'legacy') {
$rc = system("shopt -s dotglob; GLOBIGNORE=\".:..\" cp -a $genesis_dir/fs/* $tempdir");
$rc = system("cp -a $genesis_dir/kernel $tftpdir/xcat/genesis.kernel.$arch");
$invisibletouch = 1;
} else {
$rc = system("cp -a $genesis_dir/nbroot/* $tempdir");
}
$invisibletouch = 1 if $genesis_type eq 'legacy';
my ($rc, $failed_src) = stage_genesis_payload(
genesis_type => $genesis_type, genesis_dir => $genesis_dir,
tftpdir => $tftpdir, arch => $arch, tempdir => $tempdir);
if ($rc) {
system("rm -rf $tempdir");
if ($invisibletouch) {
$callback->({ error => ["Failed to copy $genesis_dir/fs contents"], errorcode => [1] });
} else {
$callback->({ error => ["Failed to copy $genesis_dir/nbroot contents"], errorcode => [1] });
}
$callback->({ error => ["Failed to copy $failed_src contents"], errorcode => [1] });
return;
}
my $sshdir;
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env perl
# mknb stages the Genesis payload before it can build a netboot image. Those copies are the
# only point at which mknb learns that an installed Genesis image is unusable, so a copy that
# fails silently produces an initramfs built from nothing and an exit status of 0 -- the node
# then never boots, with no error anywhere naming the cause.
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../../perl-xCAT";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
use Test::More;
BEGIN { $INC{'xCAT/Utils.pm'} = 1; $INC{'xCAT/MsgUtils.pm'} = 1;
$INC{'xCAT/Table.pm'} = 1; $INC{'xCAT/NetworkUtils.pm'} = 1;
$INC{'xCAT/TableUtils.pm'} = 1; $INC{'xCAT_monitoring/monitorctrl.pm'} = 1; }
require "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/mknb.pm";
can_ok('xCAT_plugin::mknb', 'stage_genesis_payload')
or BAIL_OUT('mknb has no stage_genesis_payload to drive');
# Drive the routine with a runner that fails exactly one copy, so each assertion names the
# copy it is about rather than the pair.
sub stage {
my (%opt) = @_;
my @ran;
my ($rc, $src) = xCAT_plugin::mknb::stage_genesis_payload(
genesis_type => $opt{type} // 'legacy',
genesis_dir => '/opt/xcat/share/xcat/netboot/genesis/x86_64',
tftpdir => '/tftpboot',
arch => 'x86_64',
tempdir => '/tmp/scratch',
run => sub {
my ($cmd) = @_;
push @ran, $cmd;
return ($opt{fail} && $cmd =~ /$opt{fail}/) ? 256 : 0;
},
);
return { rc => $rc, src => $src, ran => \@ran };
}
# --- legacy: both copies must be able to fail the step -----------------------
my $ok = stage();
is($ok->{rc}, 0, 'a legacy image whose copies both succeed stages cleanly');
is(scalar @{ $ok->{ran} }, 2, 'the legacy path copies the root tree and the kernel');
my $nofs = stage(fail => qr{/fs/\*});
isnt($nofs->{rc}, 0, 'an unreadable root tree fails the step');
like($nofs->{src}, qr{/fs$}, 'and the failure names the root tree');
my $nokernel = stage(fail => qr{/kernel });
isnt($nokernel->{rc}, 0, 'a missing kernel fails the step');
like($nokernel->{src}, qr{/kernel$}, 'and the failure names the kernel, not the root tree');
# --- exported (OpenEmbedded) path -------------------------------------------
my $nonb = stage(type => 'exported', fail => qr{/nbroot/\*});
isnt($nonb->{rc}, 0, 'an unreadable nbroot fails the step');
like($nonb->{src}, qr{/nbroot$}, 'and the failure names nbroot');
my $oknb = stage(type => 'exported');
is($oknb->{rc}, 0, 'an exported image whose copy succeeds stages cleanly');
done_testing();