2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-02 08:51:44 +00:00

Merge master into feature/openeuler-lts

This commit is contained in:
Vinícius Ferrão
2026-09-29 00:01:05 -03:00
14 changed files with 531 additions and 113 deletions
+5 -1
View File
@@ -6,7 +6,11 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- name: Refresh package indexes
run: sudo apt-get update
- name: Install dependencies
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests bats rpm build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests bats bubblewrap rpm build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common
- name: Enable postscript test namespaces
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- name: Run tests
run: perl github_action_xcat_test.pl
+1 -1
View File
@@ -60,7 +60,7 @@ author = u'IBM Corporation, xCAT Consortium'
# The short X.Y version.
version = '2'
# The full version, including alpha/beta/rc tags.
release = '2.17.0'
release = '2.20.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -106,8 +106,10 @@ The release is the last candidate that passed the tests.
xcat-dep/2.x_Linux/xcat-dep-X.Y.Z-linux.tar.bz2
xcat-dep/2.x_Ubuntu/xcat-dep-X.Y.Z-ubuntu.tar.bz2
#. Make sure that the ``.repo`` files in the published repositories point at the published
location, not at ``devel``.
#. Make sure that each ``.repo`` file under ``repos/yum/X.Y/`` points at ``repos/yum/X.Y/``, not at
``devel`` or ``latest``. ``latest`` is a link into the newest series, so a series path is right
through both. A file that names ``latest`` gives the next series to users of this one as soon as
that series ships.
#. If X.Y is the newest series, point ``latest`` at ``X.Y`` for yum and for apt.
@@ -146,9 +148,9 @@ Verify the Published Packages
``go-xcat`` installation does not prove the signatures.
On EL, use the three published ``.repo`` files, which set ``gpgcheck=1``. Enable the other
repositories that the installation guide requires first. The published files point at
``latest``. For an older series, replace ``latest`` with ``X.Y`` in the three files. Install the
Genesis image for the host architecture by name, because ``xCAT`` only recommends it. ::
repositories that the installation guide requires first. Use the files as published: each one
must point at ``repos/yum/X.Y/``. Install the Genesis image for the host architecture by name,
because ``xCAT`` only recommends it. ::
$ curl -fsSo /etc/yum.repos.d/xcat-core.repo \
https://xcat.org/files/xcat/repos/yum/X.Y/xcat-core/xcat-core.repo
+1
View File
@@ -1,2 +1,3 @@
Version,Release Date,New OS Supported,Release Notes
2.18.2,2026-08-01,,`2.18.2 Release Notes <https://github.com/xcat2/xcat-core/wiki/XCAT_2.18.2_Release_Notes>`_
2.18.0,2026-06-22,"RHEL 10,AlmaLinux 10",`2.18.0 Release Notes <https://github.com/xcat2/xcat-core/wiki/XCAT_2.18_Release_Notes>`_
1 Version Release Date New OS Supported Release Notes
2 2.18.2 2026-08-01 `2.18.2 Release Notes <https://github.com/xcat2/xcat-core/wiki/XCAT_2.18.2_Release_Notes>`_
3 2.18.0 2026-06-22 RHEL 10,AlmaLinux 10 `2.18.0 Release Notes <https://github.com/xcat2/xcat-core/wiki/XCAT_2.18_Release_Notes>`_
+3
View File
@@ -0,0 +1,3 @@
Version,Release Date,New OS Supported,Release Notes
2.19.1,2026-09-25,,`2.19.1 Release Notes <https://github.com/xcat2/xcat-core/wiki/XCAT_2.19.1_Release_Notes>`_
2.19.0,2026-09-18,"Ubuntu 26.04, riscv64 on EL10, Ubuntu 24.04 and Ubuntu 26.04",`2.19.0 Release Notes <https://github.com/xcat2/xcat-core/wiki/XCAT_2.19_Release_Notes>`_
1 Version Release Date New OS Supported Release Notes
2 2.19.1 2026-09-25 `2.19.1 Release Notes <https://github.com/xcat2/xcat-core/wiki/XCAT_2.19.1_Release_Notes>`_
3 2.19.0 2026-09-18 Ubuntu 26.04, riscv64 on EL10, Ubuntu 24.04 and Ubuntu 26.04 `2.19.0 Release Notes <https://github.com/xcat2/xcat-core/wiki/XCAT_2.19_Release_Notes>`_
+9
View File
@@ -6,6 +6,15 @@ The following tables documents the xCAT release versions and release dates. For
.. tabularcolumns:: |p{1cm}|p{4cm}|p{7cm}|p{7cm}
xCAT 2.19.x
-----------
.. csv-table:: 2.19.x Release Information
:file: _files/2.19.x.csv
:header-rows: 1
:class: longtable
:widths: 1 1 1 1
xCAT 2.18.x
-----------
+2 -2
View File
@@ -360,8 +360,8 @@ sub install_xcat{
my @cmds = ("sudo $repo/mklocalrepo.sh",
"sudo chmod 777 /etc/apt/sources.list",
"sudo echo \"deb [arch=amd64 allow-insecure=yes] http://xcat.org/files/xcat/repos/apt/latest/xcat-dep noble main\" >> /etc/apt/sources.list",
"sudo echo \"deb [arch=ppc64el allow-insecure=yes] http://xcat.org/files/xcat/repos/apt/latest/xcat-dep noble main\" >> /etc/apt/sources.list",
"sudo echo \"deb [arch=amd64 allow-insecure=yes] http://xcat.org/files/xcat/repos/apt/devel/xcat-dep noble main\" >> /etc/apt/sources.list",
"sudo echo \"deb [arch=ppc64el allow-insecure=yes] http://xcat.org/files/xcat/repos/apt/devel/xcat-dep noble main\" >> /etc/apt/sources.list",
"sudo timeout 600 apt-get -qq -o Acquire::Retries=3 -o Acquire::http::Timeout=30 --allow-insecure-repositories update");
chdir $ENV{RUNNER_WORKSPACE};;
+1 -1
View File
@@ -828,7 +828,7 @@ sub process_request {
open($cfg, ">", "$tftpdir/xcat/xnba/nets/$net");
print $cfg "#!gpxe\n";
if ($invisibletouch) {
print $cfg 'imgfetch -n kernel http://${next-server}'.$portsuffix.'/tftpboot/xcat/genesis.kernel.' . "$arch xcatd=" . $xcatd_address . ":$xcatdport $consolecmdline BOOTIF=01-" . '${netX/machyp}' . "\n";
print $cfg 'imgfetch -n kernel http://${next-server}'.$portsuffix.'/tftpboot/xcat/genesis.kernel.' . "$arch xcatd=" . $xcatd_address . ":$xcatdport $consolecmdline BOOTIF=01-" . '${netX/mac:hexhyp}' . "\n";
print $cfg 'imgfetch -n nbfs http://${next-server}'.$portsuffix . "$initrd_file\n";
} else {
print $cfg 'imgfetch -n kernel http://${next-server}'.$portsuffix.'/tftpboot/xcat/nbk.' . "$arch xcatd=" . $xcatd_address . ":$xcatdport $consolecmdline\n";
+11
View File
@@ -19,3 +19,14 @@ commands so tests cannot write to the host.
Extraction helpers in `helpers/shell_source.bash` are only for legacy code that
cannot safely be sourced yet. Do not add Perl `.t` tests that grep shell source
when the behavior can be tested with BATS.
## Postscript sandbox prerequisites
`otherpkgs_upgrade_scope.bats` runs the complete postscript in a Linux filesystem
sandbox because it writes to `/etc/yum.repos.d`. It requires Bubblewrap, Bash,
GNU core utilities, and permission to create user namespaces. The CI workflow
installs Bubblewrap and enables those namespaces.
Missing prerequisites fail this test without stopping unrelated test files.
Non-Linux hosts report a skip. Set `TMPDIR` to a writable, executable filesystem
if the default temporary directory is mounted with `noexec`.
+239
View File
@@ -0,0 +1,239 @@
#!/usr/bin/env bats
load 'helpers/shell_source'
setup()
{
[ "$(uname -s)" = Linux ] || skip 'otherpkgs filesystem isolation requires Linux'
local utility executable
local utilities=(bash sh basename dirname cat cp expr grep ls mkdir rm uname wc)
local bwrap
bwrap=$(PATH=/usr/bin:/bin type -P bwrap) || {
echo 'Install bubblewrap to run the otherpkgs test' >&2
return 1
}
postscripts=$(repo_path xCAT/postscripts)
fixture="$BATS_TEST_TMPDIR/fixture"
mkdir -p "$fixture/bin"
sandbox=(env -i PATH=/usr/bin:/bin LC_ALL=C "$bwrap"
--unshare-all --die-with-parent --new-session
--ro-bind / / --tmpfs /etc --tmpfs /usr/bin --tmpfs /tmp
--proc /proc --dev /dev --setenv PATH /usr/bin --setenv LC_ALL C)
if PATH=/usr/bin:/bin type -P coreutils >/dev/null; then
utilities+=(coreutils)
fi
for utility in "${utilities[@]}"; do
executable=$(PATH=/usr/bin:/bin type -P "$utility") || {
echo "Required utility is unavailable: $utility" >&2
return 1
}
sandbox+=(--ro-bind "$executable" "/usr/bin/$utility")
done
run "${sandbox[@]}" /usr/bin/sh -c 'test ! -e /etc/os-release'
if [ "$status" -ne 0 ]; then
echo "Cannot isolate otherpkgs: $output" >&2
return 1
fi
}
run_case()
{
local manager=$1 scenario=$2
local verbose='' remote='' mounted='' repoonly='' multiple='' empty=''
local upgrade_status=0 install_status=0
case "$scenario" in
http) verbose=1; remote=1 ;;
mounted) mounted=1 ;;
upgrade_failure) upgrade_status=17; verbose=1 ;;
install_failure) install_status=23; verbose=1 ;;
repoonly) repoonly=1; remote=1 ;;
multiple) multiple=1; verbose=1 ;;
empty) remote=1; empty=1 ;;
esac
printf '#!/usr/bin/sh\nexit 0\n' >"$fixture/bin/logger"
printf '#!/usr/bin/sh\nexit 1\n' >"$fixture/bin/dpkg"
printf '#!/usr/bin/sh\n[ "$*" = --version ]\n' >"$fixture/bin/rpm"
if [ "$mounted" ]; then
printf '#!/usr/bin/sh\nprintf "%%s\\n" "package-server:/install on /install type nfs (rw)"\n' >"$fixture/bin/mount"
else
printf '#!/usr/bin/sh\nexit 0\n' >"$fixture/bin/mount"
fi
cat >"$fixture/bin/$manager" <<'SH'
#!/usr/bin/sh
printf '%s\t' "${0##*/}" "SCOPE_ENV=${SCOPE_ENV:-}" "$@" >> /tmp/fixture/commands
printf '\n' >> /tmp/fixture/commands
sequence=$(wc -l < /tmp/fixture/commands)
mkdir "/tmp/fixture/repos.$sequence"
cp /etc/yum.repos.d/*.repo "/tmp/fixture/repos.$sequence/" 2>/dev/null || :
for argument do
case "$argument" in
upgrade) printf '%s\n' upgrade-result; exit "$UPGRADE_STATUS" ;;
install) printf '%s\n' install-result; exit "$INSTALL_STATUS" ;;
esac
done
exit 0
SH
chmod +x "$fixture/bin/"*
local otherpkgdir=/install/other packages=alpha/tool-one,beta/tool-two
local list_count=1
[ ! "$remote" ] || otherpkgdir=https://packages.example.invalid/extra,/install/other
[ ! "$empty" ] || packages=
[ ! "$multiple" ] || list_count=2
local command=("${sandbox[@]}"
--bind "$fixture" /tmp/fixture
--ro-bind "$postscripts" /tmp/postscripts --chdir /tmp/fixture)
local tool
for tool in logger dpkg rpm mount "$manager"; do
command+=(--ro-bind "$fixture/bin/$tool" "/usr/bin/$tool")
done
command+=(
--setenv OSVER rhel9 --setenv ARCH x86_64 --setenv UPDATENODE 1
--setenv NFSSERVER package-server --setenv HTTPPORT 80
--setenv INSTALLDIR /install --setenv OTHERPKGDIR "$otherpkgdir"
--setenv OTHERPKGS_INDEX "$list_count" --setenv OTHERPKGS1 "$packages"
--setenv ENVLIST1 SCOPE_ENV=first --setenv VERBOSE "$verbose"
--setenv UPGRADE_STATUS "$upgrade_status" --setenv INSTALL_STATUS "$install_status")
if [ "$multiple" ]; then
command+=(--setenv OTHERPKGS2 gamma/tool-three --setenv ENVLIST2 SCOPE_ENV=second)
fi
local runner
runner=$(cat <<'SH'
/usr/bin/bash /tmp/postscripts/otherpkgs "$@"
status=$?
mkdir /tmp/fixture/final-repos
cp /etc/yum.repos.d/*.repo /tmp/fixture/final-repos/ 2>/dev/null || :
exit "$status"
SH
)
command+=(/usr/bin/sh -c "$runner" otherpkgs-test)
[ ! "$repoonly" ] || command+=(--repoonly)
run "${command[@]}"
if [ "$status" -ne "$((upgrade_status + install_status))" ]; then
echo "$output" >&2
return 1
fi
[ -f "$fixture/commands" ]
local line operand sequence=0
local arguments=() paths=()
: >"$fixture/transactions"
while IFS= read -r line; do
sequence=$((sequence + 1))
IFS=$'\t' read -r -a arguments <<<"$line"
operand=
for operand in "${arguments[@]:2}"; do
[[ "$operand" = -* ]] || break
done
case "$operand" in clean|list) continue ;; esac
printf '%s\n' "$line" >>"$fixture/transactions"
paths=(alpha beta)
[ ! "$empty" ] || paths=()
if [ "$multiple" ] && [ "${arguments[1]}" = SCOPE_ENV=second ]; then
paths=(gamma)
fi
check_repositories "$fixture/repos.$sequence" "${paths[@]}"
done <"$fixture/commands"
: >"$fixture/expected-transactions"
: >"$fixture/expected-printed"
if [ ! "$repoonly" ]; then
expect_transaction first -y '--disablerepo=*' '--enablerepo=xcat-otherpkgs*' upgrade
if [ ! "$empty" ]; then
expect_transaction first -y install tool-one tool-two
fi
if [ "$multiple" ]; then
expect_transaction second -y '--disablerepo=*' '--enablerepo=xcat-otherpkgs*' upgrade
expect_transaction second -y install tool-three
fi
fi
diff -u "$fixture/expected-transactions" "$fixture/transactions"
: >"$fixture/printed"
while IFS= read -r line; do
[[ "$line" = SCOPE_ENV=* ]] || continue
read -r -a arguments <<<"$line"
printf '%s\t' "${arguments[@]}" >>"$fixture/printed"
printf '\n' >>"$fixture/printed"
done <<<"$output"
diff -u "$fixture/expected-printed" "$fixture/printed"
paths=(alpha beta)
[ ! "$empty" ] || paths=()
[ ! "$multiple" ] || paths=(gamma)
check_repositories "$fixture/final-repos" "${paths[@]}"
if [ "$verbose" ] && [ ! "$repoonly" ]; then
[[ $'\n'"$output"$'\n' = *$'\nupgrade-result\n'* ]]
if [ ! "$empty" ]; then
[[ $'\n'"$output"$'\n' = *$'\ninstall-result\n'* ]]
fi
fi
}
expect_transaction()
{
local label=$1
shift
printf '%s\t' "$manager" "SCOPE_ENV=$label" "$@" >>"$fixture/expected-transactions"
printf '\n' >>"$fixture/expected-transactions"
if [ "$verbose" ]; then
printf '%s\t' "SCOPE_ENV=$label" "$manager" "$@" >>"$fixture/expected-printed"
printf '\n' >>"$fixture/expected-printed"
fi
}
check_repositories()
{
local directory=$1
shift
local base=http://package-server:80 index=0 pkgpath file
[ ! "$mounted" ] || base=file://
{
printf '%s\t%s\t1\t0\n' xCAT-rhel9-path0 "$base/install/rhel9/x86_64/BaseOS"
printf '%s\t%s\t1\t0\n' xCAT-rhel9-path1 "$base/install/rhel9/x86_64/AppStream"
if [ "$remote" ]; then
printf '%s\t%s\t1\t0\n' xcat-otherpkgs0 https://packages.example.invalid/extra
index=1
fi
for pkgpath do
printf '%s\t%s\t1\t0\n' "xcat-otherpkgs$index" "$base/install/other/$pkgpath"
index=$((index + 1))
done
} | LC_ALL=C sort >"$fixture/expected-repos"
: >"$fixture/repos"
for file in "$directory/"*.repo; do
[ -f "$file" ] || continue
awk '
/^\[[^]]+\]$/ { sections = sections substr($0, 2, length($0) - 2) "\t" }
/^(baseurl|enabled|gpgcheck)=/ {
key = substr($0, 1, index($0, "=") - 1)
value = substr($0, index($0, "=") + 1)
sub(/[[:space:]]+$/, "", value)
values[key] = values[key] value "\t"
}
END {
record = sections values["baseurl"] values["enabled"] values["gpgcheck"]
sub(/\t$/, "", record)
print record
}
' "$file" >>"$fixture/repos"
done
LC_ALL=C sort "$fixture/repos" >"$fixture/sorted-repos"
diff -u "$fixture/expected-repos" "$fixture/sorted-repos"
}
@test "dnf: HTTP and local repositories" { run_case dnf http; }
@test "dnf: mounted repositories" { run_case dnf mounted; }
@test "dnf: upgrade failure" { run_case dnf upgrade_failure; }
@test "dnf: install failure" { run_case dnf install_failure; }
@test "dnf: repository-only mode" { run_case dnf repoonly; }
@test "dnf: separate package lists" { run_case dnf multiple; }
@test "dnf: remote repository without installs" { run_case dnf empty; }
@test "yum: HTTP and local repositories" { run_case yum http; }
@test "yum: mounted repositories" { run_case yum mounted; }
@test "yum: upgrade failure" { run_case yum upgrade_failure; }
@test "yum: install failure" { run_case yum install_failure; }
@test "yum: repository-only mode" { run_case yum repoonly; }
@test "yum: separate package lists" { run_case yum multiple; }
@test "yum: remote repository without installs" { run_case yum empty; }
+28
View File
@@ -553,4 +553,32 @@ my $unsafe = xCAT_plugin::mknb::_select_genesis_source(
);
is($unsafe, undef, 'unsupported architecture names cannot escape the image root');
$::XCATROOT = "$tmpdir/openembedded-x86_64-xcatroot";
prepare_export(
"$::XCATROOT/share/xcat/netboot/genesis-openembedded/x86_64",
'openembedded x86_64 kernel',
'openembedded x86_64 initramfs',
'x86_64',
);
$xCAT::TableUtils::tftpdir = "$tmpdir/openembedded-x86_64-tftpboot";
@responses = ();
xCAT_plugin::mknb::process_request(
{ arg => ['x86_64'] },
sub { push(@responses, @_); },
);
ok(
!grep({ ref($_) eq 'HASH' && $_->{error} } @responses),
'mknb installs an x86_64 OpenEmbedded export',
);
is(
read_file("$xCAT::TableUtils::tftpdir/xcat/genesis.kernel.x86_64"),
'openembedded x86_64 kernel',
'mknb publishes the OpenEmbedded x86_64 kernel',
);
like(
read_file("$xCAT::TableUtils::tftpdir/xcat/xnba/nets/192.0.2.0_24"),
qr{^imgfetch -n kernel \S+/xcat/genesis\.kernel\.x86_64 .* BOOTIF=01-\$\{netX/mac:hexhyp\}$}m,
'the OpenEmbedded BIOS Genesis script takes BOOTIF from mac:hexhyp',
);
done_testing();
+14
View File
@@ -232,6 +232,20 @@ foreach my $relative_path (
);
}
my $bios_genesis = read_config(
"$xCAT::TableUtils::tftpdir/xcat/xnba/nets/192.168.144.0_20"
);
like(
$bios_genesis,
qr{^imgfetch -n kernel \S+/xcat/genesis\.kernel\.x86_64 .* BOOTIF=01-\$\{netX/mac:hexhyp\}$}m,
'the BIOS Genesis script takes BOOTIF from mac:hexhyp',
);
unlike(
$bios_genesis,
qr/machyp/,
'the BIOS Genesis script does not use the xNBA-only machyp setting',
);
use_reporter_address_maps();
prepare_tftpdir($tmpdir, 'tftpboot-x86-legacy', 'x86_64', 'legacy');
$responses = run_mknb('x86_64');
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use File::Spec;
use Test::More;
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
my $script_path = File::Spec->catfile( $repo_root, 'xCAT/postscripts/otherpkgs' );
plan skip_all => "$script_path not found" unless -r $script_path;
open( my $fh, '<', $script_path ) or die "Unable to read $script_path: $!";
my $script = do { local $/; <$fh> };
close($fh);
# The postscript writes its repositories as [xcat-otherpkgs<index>]. The upgrade
# below is scoped with --enablerepo=xcat-otherpkgs*, so the two have to agree or
# the upgrade silently matches no repository at all.
like(
$script,
qr/echo\s+"\[xcat-otherpkgs\$urlrepoindex\]"/,
'remote repositories are still defined as xcat-otherpkgs<index>'
);
like(
$script,
qr/echo\s+"\[xcat-otherpkgs\$localrepoindex\]"/,
'local repositories are still defined as xcat-otherpkgs<index>'
);
# Both the verbose echo and the command actually executed must carry the same
# scoping, otherwise verbose output reports a command that was never run.
my @scoped = $script =~ /\$yumcmd\s+-y\s+--disablerepo=\*\s+--enablerepo=xcat-otherpkgs\*\s+upgrade/g;
is(
scalar(@scoped),
2,
'the yum/dnf upgrade is scoped to the xcat-otherpkgs repositories in both the verbose echo and the executed command'
);
# Counted rather than matched with unlike(), so that a failure reports the count
# instead of dumping the whole postscript into the test output.
my @unscoped = $script =~ /(\$yumcmd\s+-y\s+upgrade)/g;
is(
scalar(@unscoped),
0,
'no unscoped yum/dnf upgrade remains, which would also apply unrelated distribution updates'
);
# The install path must keep every repository enabled so that dependencies of
# the otherpkgs packages can still be resolved from the distribution.
like(
$script,
qr/\$yumcmd\s+-y\s+install\s+\$repo_pkgs/,
'the package install path is left unscoped so dependencies still resolve'
);
done_testing();
+210 -45
View File
@@ -3,62 +3,227 @@ use strict;
use warnings;
use FindBin;
use File::Spec;
use lib "$FindBin::Bin/../lib";
use Storable qw(dclone);
use Test::More;
use XCAT::Test::File qw(repo_path);
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
our $RCP;
sub slurp {
my ($rel) = @_;
my $path = File::Spec->catfile( $repo_root, $rel );
return unless -r $path;
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
my $c = do { local $/; <$fh> };
close($fh);
return $c;
BEGIN {
package xCAT::Utils;
$INC{'xCAT/Utils.pm'} = __FILE__;
package xCAT::Postage;
$INC{'xCAT/Postage.pm'} = __FILE__;
package xCAT::SvrUtils;
our $synclist;
sub getsynclistfile {
my ( $class, $nodes ) = @_;
return unless defined $synclist;
return { map { $_ => $synclist->{$_} } @$nodes };
}
$INC{'xCAT/SvrUtils.pm'} = __FILE__;
package xCAT::MsgUtils;
our @messages;
sub message {
my ( $class, @message ) = @_;
push @messages, \@message;
return;
}
$INC{'xCAT/MsgUtils.pm'} = __FILE__;
package xCAT::NodeRange;
use Exporter qw(import);
our @EXPORT = qw(noderange);
our %nodes;
sub noderange { return $nodes{ $_[0] }; }
$INC{'xCAT/NodeRange.pm'} = __FILE__;
}
my $syncfiles = slurp('xCAT-server/lib/xcat/plugins/syncfiles.pm');
my $updatenode = slurp('xCAT-server/lib/xcat/plugins/updatenode.pm');
my $xdsh = slurp('xCAT-server/lib/xcat/plugins/xdsh.pm');
my $plugin = repo_path('xCAT-server/lib/xcat/plugins/syncfiles.pm');
require $plugin;
plan skip_all => 'plugins not found'
unless defined($syncfiles) && defined($updatenode) && defined($xdsh);
sub run_syncfiles {
my ($case) = @_;
local %xCAT::NodeRange::nodes = (
'node1.example.test' => 'node1',
'node2.example.test' => 'node2',
);
local $xCAT::SvrUtils::synclist = $case->{synclist};
local @xCAT::MsgUtils::messages;
local @ARGV;
my @sent;
my $callback = sub { return; };
my $request = dclone($case->{request} || {
command => ['syncfiles'],
username => ['operator'],
arg => $case->{args},
_xcat_clienthost => [ $case->{client} ],
});
my $original = dclone($request);
my @warnings;
# The xdcp subrequest has to state the identity the sync runs as.
my ($call) = $syncfiles =~ /(\$subreq->\(\{[^}]*command\s*=>\s*\['xdcp'\][^}]*\})/s;
ok( $call, 'the xdcp subrequest was located in syncfiles' )
or BAIL_OUT('syncfiles.pm no longer matches the expected subrequest shape');
{
local $SIG{__WARN__} = sub { push @warnings, @_ } if $case->{warnings};
xCAT_plugin::syncfiles::process_request(
$request,
$callback,
sub {
my ( $outgoing, $response_callback ) = @_;
push @sent, [ dclone($outgoing), $response_callback ];
return;
},
);
}
like( $call, qr/username\s*=>/, 'the xdcp subrequest names a username' );
is_deeply( \@warnings, $case->{warnings}, 'the option diagnostics match' ) if $case->{warnings};
is_deeply( $request, $original, 'the caller request is unchanged' );
is( scalar @sent, scalar @{ $case->{expected} }, 'the request count matches' );
for my $index ( 0 .. $#{ $case->{expected} } ) {
my ( $node, $file, $copy_args ) = @{ $case->{expected}->[$index] };
my $sent = $sent[$index] || [];
is_deeply(
$sent->[0],
{
command => ['xdcp'],
username => ['root'],
node => [$node],
arg => [ '-F', $file, @$copy_args ],
env => ["DSH_RSYNC_FILE=$file"],
},
"request $index carries the root identity and copy parameters",
);
is( $sent->[1], $callback, "request $index retains the response callback" );
}
is( scalar @xCAT::MsgUtils::messages, scalar @{ $case->{messages} }, 'the diagnostic count matches' );
for my $index ( 0 .. $#{ $case->{messages} } ) {
my $message = $xCAT::MsgUtils::messages[$index] || [];
is( $message->[0], 'S', 'the diagnostic goes to the system log' );
like( $message->[1], $case->{messages}->[$index], 'the diagnostic identifies the failure' );
}
return;
}
# It must be an array reference. A bare string was the original form of this
# change and broke the non-hierarchical path, because the consumers index it as
# $request->{username}->[0].
like(
$call,
qr/username\s*=>\s*\['root'\]/,
'the username is the arrayref form the consumers index into'
);
unlike(
$call,
qr/username\s*=>\s*'root'\s*,/,
'the username is not a bare string, which would not survive ->[0]'
my @cases = (
{
name => 'daemon request without arguments or username',
request => { command => ['syncfiles'], _xcat_clienthost => ['node1.example.test'] },
synclist => { node1 => '/install/custom/sync-a' },
expected => [ [ 'node1', '/install/custom/sync-a', [] ] ],
messages => [],
},
{
name => 'multiple sync files retain order and identity',
client => 'node2.example.test',
args => [],
synclist => { node2 => '/install/custom/sync-b,/install/custom/sync-a,/install/custom/sync-c' },
expected => [
[ 'node2', '/install/custom/sync-b', [] ],
[ 'node2', '/install/custom/sync-a', [] ],
[ 'node2', '/install/custom/sync-c', [] ],
],
messages => [],
},
);
# The consumers this has to satisfy, pinned so the shape cannot drift apart.
like(
$xdsh,
qr/\$ENV\{DSH_FROM_USERID\}\s*=\s*\$request->\{username\}->\[0\]/,
'xdsh still derives DSH_FROM_USERID from the request username'
);
for my $option ( '-r', '-c', '--node-rcp' ) {
push @cases, {
name => "copy override $option retains identity on every request",
client => 'node1.example.test',
args => [ $option, '/usr/bin/scp' ],
synclist => { node1 => '/install/custom/sync-a,/install/custom/sync-b' },
expected => [
[ 'node1', '/install/custom/sync-a', [ '-r', '/usr/bin/scp' ] ],
[ 'node1', '/install/custom/sync-b', [ '-r', '/usr/bin/scp' ] ],
],
messages => [],
};
}
# updatenode makes the same xdcp call and already passes a username. The two
# should not diverge again.
like(
$updatenode,
qr/command\s*=>\s*\["xdcp"\][^;]*username\s*=>/s,
'updatenode still passes a username on its own xdcp call'
);
push @cases,
{
name => 'one sync file',
client => 'node1.example.test',
args => [],
synclist => { node1 => '/install/custom/sync-a' },
expected => [ [ 'node1', '/install/custom/sync-a', [] ] ],
messages => [],
},
{
name => 'invalid option sends no request',
client => 'node1.example.test',
args => ['--bogus'],
synclist => { node1 => '/install/custom/sync-a' },
expected => [],
messages => [ qr/Received syncfiles from node1\.example\.test, with invalid options\b/ ],
warnings => ["Unknown option: bogus\n"],
},
{
name => 'unavailable synclist lookup sends no request',
client => 'node1.example.test',
args => [],
synclist => undef,
expected => [],
messages => [ qr/\ACannot find synclist file for the node1\z/ ],
},
{
name => 'node without a synclist sends no request',
client => 'node1.example.test',
args => [],
synclist => { node1 => undef },
expected => [],
messages => [],
},
{
name => 'unresolved client sends no request',
client => 'unknown.example.test',
args => [],
synclist => { node1 => '/install/custom/sync-a' },
expected => [],
messages => [ qr/couldn't be correlated to a node/ ],
};
for my $case (@cases) {
subtest $case->{name} => sub {
local $RCP;
run_syncfiles($case);
};
}
# Direct same-process calls share the existing override; daemon process isolation is outside this test.
for my $option ( '-r', '-c', '--node-rcp' ) {
subtest "same-process copy override $option" => sub {
local $RCP;
for my $step (
[ 'clean sequence state', [], 'node1', 'sync-default', undef ],
[ 'initial override', [ $option, '/usr/bin/scp' ], 'node1', 'sync-a', '/usr/bin/scp' ],
[ 'rejected override', [ $option, '/usr/bin/false', '--bogus' ], 'node1', 'sync-rejected', undef, 1 ],
[ 'request without override', [], 'node2', 'sync-b', '/usr/bin/scp' ],
[ 'replacement override', [ $option, '/usr/bin/rsync' ], 'node1', 'sync-c', '/usr/bin/rsync' ],
[ 'request after replacement', [], 'node2', 'sync-d', '/usr/bin/rsync' ],
)
{
my ( $name, $args, $node, $list, $copy_command, $invalid ) = @$step;
subtest $name => sub {
my $file = "/install/custom/$list";
my $copy_args = defined $copy_command ? [ '-r', $copy_command ] : [];
my %diagnostics = $invalid ? (
messages => [ qr/Received syncfiles from \Q$node.example.test\E, with invalid options\b/ ],
warnings => ["Unknown option: bogus\n"],
) : ( messages => [] );
run_syncfiles({
client => "$node.example.test",
args => $args,
synclist => { $node => $file },
expected => $invalid ? [] : [ [ $node, $file, $copy_args ] ],
%diagnostics,
});
};
}
};
}
done_testing();