2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-25 09:14:05 +00:00

Merge pull request #7851 from VersatusHPC/fix/updatenode-xcatpost-cleanup

fix(xcatdsklspost): updatenode leaves /xcatpost populated when site.cleanupdiskfullxcatpost is set
This commit is contained in:
Daniel Hilst
2026-09-24 10:21:49 -03:00
committed by GitHub
5 changed files with 313 additions and 8 deletions
@@ -288,7 +288,9 @@ site Attributes:
cleanupdiskfullxcatpost: (yes/1 or no/0). Set to 'yes' or '1' to clean up the /xcatpost
directory on the diskfull nodes after the
postscripts are run with no errors. Default is no.
postscripts are run with no errors: at deployment, after each
updatenode run, and after a reboot. updateflag.awk stays, so
the node can still report its status. Default is no.
db2installloc: The location which the service nodes should mount for
the db2 code to install. Format is hostname:/path. If hostname is
+3 -1
View File
@@ -1198,7 +1198,9 @@ passed as argument rather than by table value',
" postscripts are run. Default is no.\n\n" .
" cleanupdiskfullxcatpost: (yes/1 or no/0). Set to 'yes' or '1' to clean up the /xcatpost\n" .
" directory on the diskfull nodes after the\n" .
" postscripts are run with no errors. Default is no.\n\n" .
" postscripts are run with no errors: at deployment, after each\n" .
" updatenode run, and after a reboot. updateflag.awk stays, so\n" .
" the node can still report its status. Default is no.\n\n" .
" db2installloc: The location which the service nodes should mount for\n" .
" the db2 code to install. Format is hostname:/path. If hostname is\n" .
" omitted, it defaults to the management node. Default is /mntdb2.\n\n" .
@@ -0,0 +1,170 @@
#!/usr/bin/env bats
load 'helpers/shell_source'
setup()
{
DSKLSPOST="$(repo_path 'xCAT/postscripts/xcatdsklspost')"
[ -r "$DSKLSPOST" ] || skip "$DSKLSPOST is required"
XCATPOST="${BATS_TEST_TMPDIR}/xcatpost"
MYPS="${BATS_TEST_TMPDIR}/mypostscript"
MSGLOG="${BATS_TEST_TMPDIR}/msgutil.log"
export DSKLSPOST XCATPOST MYPS MSGLOG
}
# A scratch /xcatpost with what an updatenode run downloads into it.
make_xcatpost()
{
mkdir -p "$XCATPOST/_xcat"
printf 'updateflag\n' >"$XCATPOST/updateflag.awk"
printf 'setroute\n' >"$XCATPOST/setroute"
printf 'xcatlib\n' >"$XCATPOST/xcatlib.sh"
printf 'credentials\n' >"$XCATPOST/_xcat/postscript.cfg"
}
# A scratch mypostscript that carries the site values and the run result.
# $1 is the value of return_value, the rest are the site table lines.
make_mypostscript()
{
local result=$1
shift
{
printf '%s\n' '#!/bin/bash'
printf 'msgutil_r() { printf "%%s\\n" "$3" >>"%s"; }\n' "$MSGLOG"
printf 'MASTER_IP=192.0.2.1\n'
printf 'NODE=node1\n'
printf 'log_label=xcat.updatenode\n'
printf 'return_value=%s\n' "$result"
printf '%s\n' "$@"
} >"$MYPS"
}
append_cleanup()
{
(
XCATDSKLSPOST_SOURCE_ONLY=1
export XCATDSKLSPOST_SOURCE_ONLY
# shellcheck disable=SC1090
. "$DSKLSPOST"
type -t append_xcatpost_cleanup >/dev/null \
|| { echo "xcatdsklspost defines no append_xcatpost_cleanup" >&2; exit 99; }
append_xcatpost_cleanup "$MYPS" "$XCATPOST"
)
}
@test "an updatenode run that succeeds removes the postscripts when site.cleanupdiskfullxcatpost is set" {
make_xcatpost
make_mypostscript 0 "CLEANUPXCATPOST='no'" "CLEANUPDISKFULLXCATPOST='yes'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ ! -e "$XCATPOST/setroute" ]
[ ! -e "$XCATPOST/xcatlib.sh" ]
[ ! -e "$XCATPOST/_xcat" ]
}
@test "the cleanup keeps updateflag.awk so the node can still report its status" {
make_xcatpost
make_mypostscript 0 "CLEANUPXCATPOST='no'" "CLEANUPDISKFULLXCATPOST='yes'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ -f "$XCATPOST/updateflag.awk" ]
[ -d "$XCATPOST" ]
grep -q 'cleanup of .* completed' "$MSGLOG"
}
@test "an updatenode run that fails keeps the postscripts for diagnosis" {
make_xcatpost
make_mypostscript 1 "CLEANUPXCATPOST='no'" "CLEANUPDISKFULLXCATPOST='yes'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ -f "$XCATPOST/setroute" ]
[ -f "$XCATPOST/_xcat/postscript.cfg" ]
refute_grep -q 'cleanup of .* completed' "$MSGLOG"
}
@test "site.cleanupdiskfullxcatpost accepts the other true values of the site table" {
make_xcatpost
make_mypostscript 0 "CLEANUPDISKFULLXCATPOST='1'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ ! -e "$XCATPOST/setroute" ]
}
# cleanupdiskfullxcatpost names the node type it applies to. updatenode calls this script for a
# diskless or statelite node as well, and the site value is one row that reaches all of them, so
# without a guard a diskless node loses its postscripts to a setting that does not name it.
@test "a netboot node keeps its postscripts when site.cleanupdiskfullxcatpost is set" {
make_xcatpost
make_mypostscript 0 "NODESETSTATE='netboot'" "CLEANUPDISKFULLXCATPOST='yes'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ -f "$XCATPOST/setroute" ]
[ -f "$XCATPOST/_xcat/postscript.cfg" ]
# Nothing was appended, so the run cannot report a cleanup it did not do.
refute_grep -q 'cleanup of .* completed' "$MSGLOG"
}
@test "a statelite node keeps its postscripts when site.cleanupdiskfullxcatpost is set" {
make_xcatpost
make_mypostscript 0 "NODESETSTATE='statelite'" "CLEANUPDISKFULLXCATPOST='yes'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ -f "$XCATPOST/setroute" ]
}
@test "a diskful node is still cleaned when site.cleanupdiskfullxcatpost is set" {
make_xcatpost
make_mypostscript 0 "NODESETSTATE='install'" "CLEANUPDISKFULLXCATPOST='yes'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ ! -e "$XCATPOST/setroute" ]
[ -f "$XCATPOST/updateflag.awk" ]
}
# site.cleanupxcatpost names no node type, so it keeps applying to every one. A guard added to the
# wrong branch would show up here.
@test "site.cleanupxcatpost still applies to a netboot node" {
make_xcatpost
make_mypostscript 0 "NODESETSTATE='netboot'" "CLEANUPXCATPOST='yes'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ -z "$(ls -A "$XCATPOST")" ]
}
@test "site.cleanupxcatpost removes every file including updateflag.awk" {
make_xcatpost
make_mypostscript 0 "CLEANUPXCATPOST='yes'" "CLEANUPDISKFULLXCATPOST='no'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ -z "$(ls -A "$XCATPOST")" ]
}
@test "neither setting leaves the downloaded postscripts in place" {
make_xcatpost
make_mypostscript 0 "CLEANUPXCATPOST='no'" "CLEANUPDISKFULLXCATPOST='no'"
append_cleanup
run bash "$MYPS"
[ "$status" -eq 0 ]
[ -f "$XCATPOST/setroute" ]
[ -f "$XCATPOST/updateflag.awk" ]
refute_grep -q -E 'rm -rf|-delete' "$MYPS"
}
@@ -0,0 +1,82 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use File::Temp qw(tempdir);
use Test::More;
# The install-time post script embeds whole postscripts with "#INCLUDE:<path>#" inside a
# here-document. The template copies the file verbatim, so a line in the postscript that
# equals the here-document delimiter ends that here-document early. The generated install
# script then holds the rest of the postscript as shell code.
#
# That happened: a "cat >> ... <<EOF" added to xcatdsklspost put a line "EOF" in the file,
# post.xcat embeds it with "cat >/opt/xcat/xcatdsklspost << 'EOF'", and every diskfull
# install ran a post script that bash refused to parse. No postscript ran, so remoteshell
# never installed the root key and the node answered "Permission denied (publickey)".
my $root = "$FindBin::Bin/../..";
my $scriptdir = "$root/xCAT-server/share/xcat/install/scripts";
die "$scriptdir not found\n" unless -d $scriptdir;
# Return every (post script, delimiter, embedded file) the install scripts declare.
sub embeddings {
my @found;
opendir(my $dh, $scriptdir) or die "cannot read $scriptdir: $!";
my @posts = sort grep { /^post\./ and -f "$scriptdir/$_" } readdir($dh);
closedir $dh;
for my $post (@posts) {
open(my $fh, '<', "$scriptdir/$post") or die "cannot read $post: $!";
my @lines = <$fh>;
close $fh;
for my $i (0 .. $#lines - 1) {
# cat >file << 'EOF' / (cat << 'EOF'
my ($delim) = $lines[$i] =~ /<<-?\s*'([A-Za-z_][A-Za-z0-9_]*)'\s*$/;
next unless defined $delim;
my ($include) = $lines[$i + 1] =~ /^#INCLUDE:(.+)#\s*$/;
next unless defined $include;
# The include path is a template expression; only its last element names the file.
next unless $include =~ m{/postscripts/([^/#]+)$};
my $file = "$root/xCAT/postscripts/$1";
next unless -f $file;
push @found, { post => $post, delim => $delim, file => $file, name => $1 };
}
}
return @found;
}
my @embeddings = embeddings();
die 'no "#INCLUDE:" inside a here-document was found; the scan no longer matches the install scripts'
unless @embeddings;
my $scratch = tempdir(CLEANUP => 1);
for my $e (@embeddings) {
my $label = "$e->{post} embeds $e->{name} in a <<'$e->{delim}' here-document";
open(my $fh, '<', $e->{file}) or die "cannot read $e->{file}: $!";
my @body = <$fh>;
close $fh;
my @collisions = grep { $body[$_] =~ /^\Q$e->{delim}\E\s*$/ } 0 .. $#body;
is(scalar @collisions, 0,
"$label, and no line of $e->{name} is \"$e->{delim}\"")
or diag("$e->{name} line(s) " . join(', ', map { $_ + 1 } @collisions)
. " end the here-document early");
# Assemble the embedding the way the template does and let bash parse it. bash reads the
# here-document up to the first delimiter line, so an early end leaves the rest of the
# postscript as commands. -n parses and runs nothing.
my $script = "$scratch/$e->{post}.$e->{name}.sh";
open(my $out, '>', $script) or die "cannot write $script: $!";
print $out "cat >\"\$1\" <<'$e->{delim}'\n", @body, "$e->{delim}\n";
close $out;
my $errors = qx{bash -n \Q$script\E 2>&1};
is($?, 0, "$label, and bash parses the result")
or diag($errors);
}
done_testing();
+55 -6
View File
@@ -224,6 +224,60 @@ parsehttpserver ()
fi
}
#####################################################
# Append to the generated mypostscript the /xcatpost cleanup the site table asks for.
#
# The append happens on every path this script serves: node deployment, and each
# updatenode, moncfg or reboot run.
#
# The two site attributes do not ask for the same thing. cleanupxcatpost removes
# every file and ignores the result of the run. cleanupdiskfullxcatpost keeps
# updateflag.awk, which the node needs to report its status, and removes nothing
# after a failed run, so the postscripts stay on the node for diagnosis.
# xcatinstallpost applies the same rule at install time.
#
# cleanupdiskfullxcatpost names the node type it applies to, and this script serves
# every type: updatenode calls it for a diskless or statelite node as well. The site
# value is one row and reaches all of them, so the diskful branch reads NODESETSTATE
# and does nothing for netboot or statelite. cleanupxcatpost carries no such word and
# keeps applying everywhere.
#
# $1 - path of the generated mypostscript
# $2 - the xcatpost directory to remove the postscripts from
#####################################################
append_xcatpost_cleanup()
{
local mypostscript=$1
local postdir=$2
local cleanupxcatpost
local cleanupdiskfullxcatpost
cleanupxcatpost=`grep CLEANUPXCATPOST= "$mypostscript" |awk -F = '{print $2}' | tr -d \'\" | tr A-Z a-z`
if [[ "$cleanupxcatpost" =~ ^(1|yes|y)$ ]]; then
echo "cd /" >> "$mypostscript"
# /xcatpost might be read-only for statelite nodes
echo "rm -rf $postdir/*" >> "$mypostscript"
fi
local nodesetstate
nodesetstate=`grep '^NODESETSTATE=' "$mypostscript" |awk -F = '{print $2}' | tr -d \'\" | tr A-Z a-z`
cleanupdiskfullxcatpost=`grep CLEANUPDISKFULLXCATPOST= "$mypostscript" |awk -F = '{print $2}' | tr -d \'\" | tr A-Z a-z`
if [[ "$cleanupdiskfullxcatpost" =~ ^(1|yes|y)$ ]] \
&& [ "$nodesetstate" != "netboot" ] && [ "$nodesetstate" != "statelite" ]; then
# post.xcat and the other install scripts embed this whole file in a here-document
# that ends at a line "EOF". A line "EOF" here ends that here-document early and
# leaves the rest of this file as shell code in the generated install script.
cat >> "$mypostscript" <<XCATPOST_CLEANUP
if [ "\$return_value" -eq "0" ]; then
cd /
find $postdir/ -type f -not -name 'updateflag.awk' -delete
find $postdir/ -type d -empty -delete
msgutil_r \$MASTER_IP "info" "cleanup of $postdir completed.(\$NODE)" "/var/log/xcat/xcat.log" "\$log_label"
fi
XCATPOST_CLEANUP
fi
}
if [ "$XCATDSKLSPOST_SOURCE_ONLY" = "1" ]; then
return 0 2>/dev/null || exit 0
fi
@@ -1086,12 +1140,7 @@ fi
DHCP_TMP=`sed 's/\(DHCPINTERFACES=\)\(.*\)$/\1"\2"/' /$xcatpost/mypostscript`
echo "$DHCP_TMP" > /$xcatpost/mypostscript
CLEANUPXCATPOST=`grep CLEANUPXCATPOST= /$xcatpost/mypostscript |awk -F = '{print $2}' | tr -d \'\" | tr A-Z a-z`
if [[ "$CLEANUPXCATPOST" =~ ^(1|yes|y)$ ]]; then
echo "cd /" >> /$xcatpost/mypostscript
# /xcatpost might be read-only for statelite nodes
echo "rm -rf /$xcatpost/*" >> /$xcatpost/mypostscript
fi
append_xcatpost_cleanup /$xcatpost/mypostscript /$xcatpost