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:
@@ -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
@@ -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,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>`_
|
||||
|
||||
|
@@ -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>`_
|
||||
|
@@ -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
|
||||
-----------
|
||||
|
||||
|
||||
@@ -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};;
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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; }
|
||||
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user