mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-21 16:39:30 +00:00
test(xcat-core): capture the genesis harness passing on a failed nodeset
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 3597760645ddf38f110457934982e2cc9a113f84)
This commit is contained in:
@@ -15,11 +15,14 @@ use XCAT::Test::File qw(repo_path);
|
||||
|
||||
my $script = repo_path('xCAT-test/autotest/testcase/genesis/test.sh');
|
||||
plan skip_all => 'genesis test.sh not found' unless -f $script;
|
||||
plan tests => 5;
|
||||
plan tests => 9;
|
||||
|
||||
my $host_arch = `uname -m`;
|
||||
chomp $host_arch;
|
||||
|
||||
# grub2.pm names the boot loader grub2.<arch>, with every ppc64 flavour written as "ppc".
|
||||
my $loader_name = $host_arch =~ /^ppc64/ ? 'ppc' : $host_arch;
|
||||
|
||||
# The case defined its node as ppc64le whatever the management node was, so nodeset could not
|
||||
# find a genesis kernel for it on x86_64 and the case could never pass there.
|
||||
my $run = run_check('xnba', write_boot_file => 1);
|
||||
@@ -39,6 +42,20 @@ my $grub = run_check('grub2', write_boot_file => 1);
|
||||
is($grub->{status}, 0, 'the grub2 check reads the grub2 directory')
|
||||
or diag($grub->{output});
|
||||
|
||||
# grub2.pm writes the boot configuration and only then stops on a missing boot loader. The
|
||||
# check read the file that failed nodeset had already written, so it passed on the debris.
|
||||
my $refused = run_check('grub2', write_boot_file => 1, nodeset_status => 1);
|
||||
isnt($refused->{status}, 0, 'a nodeset that fails makes the check fail');
|
||||
|
||||
my $refused_xnba = run_check('xnba', write_boot_file => 1, nodeset_status => 1);
|
||||
isnt($refused_xnba->{status}, 0, 'a nodeset that fails makes the xnba check fail too');
|
||||
|
||||
# xCAT builds no x86_64 or aarch64 grub2 network boot loader, so grub2.pm stops before it
|
||||
# configures anything. The check stages one for the node arch and removes it after.
|
||||
is($grub->{loader_at_nodeset}, "yes\n",
|
||||
'the grub2 boot loader for the node arch is in place when nodeset runs');
|
||||
ok(!$grub->{loader_left}, 'the staged boot loader is removed again');
|
||||
|
||||
#---
|
||||
# run_check: run `test.sh --check <loader>` against a scratch tftp root. test.sh resets PATH,
|
||||
# so the xCAT commands are shadowed with shell functions, which bash resolves first. The fake
|
||||
@@ -49,6 +66,7 @@ sub run_check {
|
||||
my $root = tempdir(CLEANUP => 1);
|
||||
my $tftp = "$root/tftpboot";
|
||||
make_path("$tftp/xcat/xnba/nodes", "$tftp/boot/grub2", "$tftp/petitboot");
|
||||
my $boot_loader = "$tftp/boot/grub2/grub2.$loader_name";
|
||||
|
||||
my $folder = $loader eq 'xnba' ? "$tftp/xcat/xnba/nodes"
|
||||
: $loader eq 'petitboot' ? "$tftp/petitboot"
|
||||
@@ -71,7 +89,11 @@ makenetworks() { return 0; }
|
||||
tabdump() { return 0; }
|
||||
makehosts() { return 0; }
|
||||
rmdef() { return 0; }
|
||||
nodeset() { $write; return 0; }
|
||||
nodeset() {
|
||||
if [ -e '$boot_loader' ]; then echo yes > '$root/loader.at.nodeset'; else echo no > '$root/loader.at.nodeset'; fi
|
||||
$write
|
||||
return @{[ $opt{nodeset_status} || 0 ]};
|
||||
}
|
||||
export TFTPDIR='$tftp'
|
||||
. '$script' --check $loader
|
||||
DRIVER
|
||||
@@ -79,6 +101,12 @@ DRIVER
|
||||
my $out = `/bin/bash "$driver" 2>&1`;
|
||||
my $status = $? >> 8;
|
||||
my $chdef = -f "$root/chdef.log" ? read_text("$root/chdef.log") : '';
|
||||
return { status => $status, output => $out, chdef => $chdef };
|
||||
return {
|
||||
status => $status,
|
||||
output => $out,
|
||||
chdef => $chdef,
|
||||
loader_at_nodeset => (-f "$root/loader.at.nodeset" ? read_text("$root/loader.at.nodeset") : ''),
|
||||
loader_left => (-e $boot_loader ? 1 : 0),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,39 +15,46 @@ 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 => 9;
|
||||
plan tests => 11;
|
||||
|
||||
my $tmpdir = tempdir(CLEANUP => 1);
|
||||
|
||||
# A complete payload: OpenSSH 9.9 sshd plus its session helper, tmux plus a UTF-8 locale.
|
||||
my $good = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, dhclient => 1);
|
||||
my $good = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, dhclient => 1, mktemp => 1);
|
||||
my ($rc, $err) = run($good, 'usr/sbin/dhclient');
|
||||
is($rc, 0, 'a complete payload passes') or diag($err);
|
||||
|
||||
# doxcat calls dhclient with ISC flags. The released el9 image carried dhclient.conf and
|
||||
# dhclient-script but no dhclient, so Genesis never acquired an address.
|
||||
my $nodhcp = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, dhclient => 0);
|
||||
my $nodhcp = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, dhclient => 0, mktemp => 1);
|
||||
($rc, $err) = run($nodhcp, 'usr/sbin/dhclient');
|
||||
isnt($rc, 0, 'a payload without dhclient fails');
|
||||
like($err, qr{usr/sbin/dhclient}, 'the missing dhclient is named');
|
||||
|
||||
# sshd 9.9 execs /usr/libexec/openssh/sshd-session for every connection.
|
||||
my $nohelper = build_payload(sshd_execs_session => 1, session_helper => 0, tmux => 1, locale => 1, dhclient => 1);
|
||||
my $nohelper = build_payload(sshd_execs_session => 1, session_helper => 0, tmux => 1, locale => 1, dhclient => 1, mktemp => 1);
|
||||
($rc, $err) = run($nohelper, 'usr/sbin/dhclient');
|
||||
isnt($rc, 0, 'a payload whose sshd execs sshd-session but does not ship it fails');
|
||||
like($err, qr{sshd-session}, 'the missing sshd-session is named');
|
||||
|
||||
# OpenSSH 8 does not use the helper, so el8 must still pass without it.
|
||||
my $openssh8 = build_payload(sshd_execs_session => 0, session_helper => 0, tmux => 1, locale => 1, dhclient => 1);
|
||||
my $openssh8 = build_payload(sshd_execs_session => 0, session_helper => 0, tmux => 1, locale => 1, dhclient => 1, mktemp => 1);
|
||||
($rc, $err) = run($openssh8, 'usr/sbin/dhclient');
|
||||
is($rc, 0, 'an OpenSSH 8 payload passes without sshd-session') or diag($err);
|
||||
|
||||
# tmux without a UTF-8 locale is what stopped doxcat from ever running.
|
||||
my $nolocale = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 0, dhclient => 1);
|
||||
my $nolocale = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 0, dhclient => 1, mktemp => 1);
|
||||
($rc, $err) = run($nolocale, 'usr/sbin/dhclient');
|
||||
isnt($rc, 0, 'a payload with tmux and no UTF-8 locale fails');
|
||||
like($err, qr{C\.utf8}, 'the missing locale is named');
|
||||
|
||||
# getdestiny makes its request file with mktemp. Without it the node never reports its destiny,
|
||||
# so xcatd never sets nodelist.status and the node stays at powering-on.
|
||||
my $nomktemp = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, dhclient => 1, mktemp => 0);
|
||||
($rc, $err) = run($nomktemp, 'usr/sbin/dhclient');
|
||||
isnt($rc, 0, 'a payload without mktemp fails');
|
||||
like($err, qr{usr/bin/mktemp}, 'the missing mktemp is named');
|
||||
|
||||
($rc, $err) = run("$tmpdir/does-not-exist");
|
||||
is($rc >> 0, 2, 'a missing payload directory is a usage error');
|
||||
|
||||
@@ -69,6 +76,7 @@ sub build_payload {
|
||||
write_text("$root/usr/lib/locale/C.utf8/LC_CTYPE", "ctype\n");
|
||||
}
|
||||
write_text("$root/usr/sbin/dhclient", "dhclient\n") if $opt{dhclient};
|
||||
write_text("$root/usr/bin/mktemp", "mktemp\n") if $opt{mktemp};
|
||||
return $root;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,20 @@ use XCAT::Test::File qw(repo_path);
|
||||
my $helper = repo_path('xCAT-test/autotest/testcase/genesis/genesistest.pl');
|
||||
my $shell = repo_path('xCAT-test/autotest/testcase/genesis/test.sh');
|
||||
plan skip_all => 'genesis testcase helpers not found' unless -f $helper && -f $shell;
|
||||
plan tests => 11;
|
||||
plan tests => 16;
|
||||
|
||||
my $tmpdir = tempdir(CLEANUP => 1);
|
||||
my $source = read_text($helper);
|
||||
|
||||
eval_subs($source, qw(get_os get_arch check_genesis_file));
|
||||
|
||||
# The destiny status check used to be wait_for_boot(), which waited for "booted" and ignored
|
||||
# its argument. Take whichever name the script carries, so this test fails on the status the
|
||||
# check waits for and not on a missing subroutine.
|
||||
my $waiter_name = waiter_name($source);
|
||||
eval_subs($source, $waiter_name);
|
||||
my $waiter = \&{"GenesisTest::$waiter_name"};
|
||||
|
||||
# get_os drives every later branch. AlmaLinux and Rocky release files say neither "Red Hat"
|
||||
# nor "suse" nor "ubuntu", so the management node read as unknown and the check was skipped.
|
||||
is(os_for("AlmaLinux release 9.8 (Olive Jaguar)\n"), 'redhat', 'AlmaLinux is a redhat family node');
|
||||
@@ -57,6 +64,60 @@ is(os_for("NAME=\"Ubuntu\"\nID=ubuntu\n"), 'ubuntu', 'Ubuntu is still
|
||||
like($run->{makeknownhosts}, qr/-r/, 'makeknownhosts is asked to remove them');
|
||||
}
|
||||
|
||||
# xCAT sets nodelist.status from the destiny the node reports with getdestiny: "shell" for the
|
||||
# shell destiny, "configuring" for runcmd. A Genesis node never reaches "booted" -- that status
|
||||
# belongs to an operating system install reporting through updateflag.
|
||||
{
|
||||
no warnings 'once';
|
||||
local $GenesisTest::noderange = 'xcat71-cn';
|
||||
is(wait_status('shell', 'shell'), 0,
|
||||
'a node that reports the shell destiny ends the wait');
|
||||
is(wait_status('configuring', 'configuring'), 0,
|
||||
'a node that reports the runcmd destiny ends the wait');
|
||||
isnt(wait_status('powering-on', 'shell'), 0,
|
||||
'a node that never reports its destiny fails the wait');
|
||||
}
|
||||
|
||||
# The shell case ignored the result of the wait, so it went on to xdsh whatever the node had
|
||||
# reported and rested entirely on the xdsh probes.
|
||||
{
|
||||
no warnings 'once';
|
||||
eval_subs($source, qw(run_nodeset_shell_test));
|
||||
local $GenesisTest::noderange = 'xcat71-cn';
|
||||
is(run_shell_test(status => 'shell'), 0,
|
||||
'the shell case passes when the node reports the shell destiny');
|
||||
isnt(run_shell_test(status => 'powering-on'), 0,
|
||||
'the shell case fails when the node never reports the shell destiny');
|
||||
}
|
||||
|
||||
#---
|
||||
# wait_status: drive the destiny status check with lsdef shadowed to report one status. The
|
||||
# extracted package neuters sleep, so the failure path does not wait five minutes.
|
||||
#---
|
||||
sub wait_status {
|
||||
my ($reported, $expected) = @_;
|
||||
local $ENV{PATH} = stub_bin(lsdef =>
|
||||
"#!/bin/sh\nprintf 'xcat71-cn: status=%s\\n' " . shell_quote($reported)) . ":$ENV{PATH}";
|
||||
return $waiter->($expected);
|
||||
}
|
||||
|
||||
#---
|
||||
# run_shell_test: drive the shell case with every command it runs shadowed. xdsh always answers
|
||||
# as a Genesis node, so the only thing under test is what the case does with the node status.
|
||||
#---
|
||||
sub run_shell_test {
|
||||
my (%opt) = @_;
|
||||
my $dir = tempdir(DIR => $tmpdir, CLEANUP => 1);
|
||||
write_text("$dir/nodeset", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/rpower", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/makeknownhosts", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/lsdef", "#!/bin/sh\nprintf 'xcat71-cn: status=%s\\n' " . shell_quote($opt{status}) . "\n");
|
||||
write_text("$dir/xdsh", "#!/bin/sh\nfor a in \"\$@\"; do\n case \"\$a\" in\n */cmdline|/proc/cmdline) printf '%s\\n' 'destiny=shell'; exit 0;;\n esac\ndone\nprintf '%s\\n' '[xCAT Genesis running on node]'\n");
|
||||
chmod 0755, map { "$dir/$_" } qw(nodeset rpower makeknownhosts lsdef xdsh);
|
||||
local $ENV{PATH} = "$dir:$ENV{PATH}";
|
||||
return GenesisTest::run_nodeset_shell_test();
|
||||
}
|
||||
|
||||
#---
|
||||
# run_testxdsh: drive testxdsh with makeknownhosts and xdsh shadowed. xdsh is asked twice --
|
||||
# once for the prompt, once for the file -- and the stub answers both from its arguments.
|
||||
@@ -81,6 +142,8 @@ sub run_testxdsh {
|
||||
sub eval_subs {
|
||||
my ($text, @names) = @_;
|
||||
my $code = "package GenesisTest;\nno strict;\nno warnings;\nour \$os;\nour \$check_genesis_file;\nour \$noderange;\n";
|
||||
# The waits are minutes long. Neuter sleep so the extracted routines run at test speed.
|
||||
$code .= "use subs qw(sleep);\nsub sleep { \$GenesisTest::SLEPT += (\$_[0] || 0); return 1; }\n";
|
||||
$code .= "sub send_msg { push \@GenesisTest::MSG, \$_[1]; return 0; }\n";
|
||||
foreach my $name (@names) {
|
||||
my ($body) = $text =~ /^(sub \Q$name\E \{.*?^\})$/ms;
|
||||
@@ -91,6 +154,17 @@ sub eval_subs {
|
||||
eval $code or BAIL_OUT("cannot compile the extracted helpers: $@");
|
||||
}
|
||||
|
||||
#---
|
||||
# waiter_name: the name the script gives its destiny status check.
|
||||
#---
|
||||
sub waiter_name {
|
||||
my ($text) = @_;
|
||||
foreach my $name (qw(wait_for_node_status wait_for_boot)) {
|
||||
return $name if $text =~ /^sub \Q$name\E \{/m;
|
||||
}
|
||||
BAIL_OUT("no destiny status check found in $helper");
|
||||
}
|
||||
|
||||
#---
|
||||
# os_for: run get_os with `cat` shadowed so it reads the release text under test.
|
||||
#---
|
||||
|
||||
Reference in New Issue
Block a user