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

test(xcat-core): Introduce BATS & convert shell scripting tests to it

The go-xcat shell behavior tests were written as Perl harnesses, which made the shell assertions harder to read and kept shell-specific setup outside a native shell test framework.

Add BATS to the GitHub Actions dependency set, run BATS tests from the same preserved source tree as the Perl unit suite, and move the go-xcat repository checks into xCAT-test/autotest/bats.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-09-08 12:11:42 -03:00
parent 796103c300
commit 3e302e7f19
30 changed files with 1113 additions and 645 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install dependencies
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests 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 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: Run tests
run: perl github_action_xcat_test.pl
+47 -2
View File
@@ -311,8 +311,11 @@ sub preserve_source_tree{
return 1;
}
@output = runcmd("ls $unitsrc/xCAT-test/unit/*.t | wc -l");
print "[preserve_source_tree] preserved $srcdir in $unitsrc ($output[0] unit tests)\n";
@output = runcmd("find $unitsrc/xCAT-test/unit -name '*.t' | wc -l");
my $perl_count = $output[0];
@output = runcmd("find $unitsrc/xCAT-test/autotest/bats -name '*.bats' 2>/dev/null | wc -l");
my $bats_count = $output[0];
print "[preserve_source_tree] preserved $srcdir in $unitsrc ($perl_count Perl unit tests, $bats_count BATS tests)\n";
return 0;
}
@@ -466,6 +469,39 @@ sub run_unit_tests{
return 0;
}
#--------------------------------------------------------
# Fuction name: run_bats_tests
# Description: Run shell-script unit tests under xCAT-test/autotest/bats.
# Runs against the pre-build copy of the source tree taken by
# preserve_source_tree(), like the Perl unit tests.
# Attributes:
# Return code: 0 all tests passed, 1 otherwise
#--------------------------------------------------------
sub run_bats_tests{
my $testdir = "$unitsrc/xCAT-test/autotest/bats";
my @output = runcmd("find $testdir -name '*.bats' -print -quit 2>/dev/null");
if (!@output) {
print "[run_bats_tests] no BATS tests found under $testdir\n";
return 0;
}
my $cmd = "cd $unitsrc && bats -r xCAT-test/autotest/bats";
print "[run_bats_tests] running $cmd\n";
@output = runcmd("$cmd");
print Dumper \@output;
if($::RUNCMD_RC){
print RED "[run_bats_tests] $cmd ....[Failed]\n";
$check_result_str .= "> **BATS TESTS Failed** : Please click ``Details`` label in ``Merge pull request`` box for detailed information\n";
print $check_result_str;
return 1;
}
print "[run_bats_tests] $cmd ....[Pass]\n";
$check_result_str .= "> **BATS TESTS Successful**\n";
print $check_result_str;
return 0;
}
#--------------------------------------------------------
# Fuction name: check_syntax
# Description:
@@ -689,6 +725,15 @@ if($rst){
}
mark_time("run_unit_tests");
#Run shell-script unit tests.
print GREEN "\n------Running xCAT-test BATS tests ------\n";
$rst = run_bats_tests();
if($rst){
print RED "Run of xCAT-test BATS tests failed\n";
exit $rst;
}
mark_time("run_bats_tests");
#Check the syntax of changing code
print GREEN "\n------ Checking the syntax of changed code------\n";
$rst = check_syntax();
@@ -9,14 +9,15 @@ depends() {
}
installkernel() {
local modules_dep modfile modname
local modules_dep modules_root modfile modname
if [[ -n "${kernel:-}" && -r "/lib/modules/$kernel/modules.dep" ]]; then
modules_dep="/lib/modules/$kernel/modules.dep"
elif [[ -n "${KERNELVERSION:-}" && -r "/lib/modules/$KERNELVERSION/modules.dep" ]]; then
modules_dep="/lib/modules/$KERNELVERSION/modules.dep"
modules_root="${DRACUT_MODULES_ROOT:-/lib/modules}"
if [[ -n "${kernel:-}" && -r "$modules_root/$kernel/modules.dep" ]]; then
modules_dep="$modules_root/$kernel/modules.dep"
elif [[ -n "${KERNELVERSION:-}" && -r "$modules_root/$KERNELVERSION/modules.dep" ]]; then
modules_dep="$modules_root/$KERNELVERSION/modules.dep"
else
modules_dep=$(ls -1 /lib/modules/*/modules.dep 2>/dev/null | head -n 1)
modules_dep=$(ls -1 "$modules_root"/*/modules.dep 2>/dev/null | head -n 1)
fi
[[ -r "$modules_dep" ]] || return 0
@@ -9,14 +9,15 @@ depends() {
}
installkernel() {
local modules_dep modfile modname
local modules_dep modules_root modfile modname
if [[ -n "${kernel:-}" && -r "/lib/modules/$kernel/modules.dep" ]]; then
modules_dep="/lib/modules/$kernel/modules.dep"
elif [[ -n "${KERNELVERSION:-}" && -r "/lib/modules/$KERNELVERSION/modules.dep" ]]; then
modules_dep="/lib/modules/$KERNELVERSION/modules.dep"
modules_root="${DRACUT_MODULES_ROOT:-/lib/modules}"
if [[ -n "${kernel:-}" && -r "$modules_root/$kernel/modules.dep" ]]; then
modules_dep="$modules_root/$kernel/modules.dep"
elif [[ -n "${KERNELVERSION:-}" && -r "$modules_root/$KERNELVERSION/modules.dep" ]]; then
modules_dep="$modules_root/$KERNELVERSION/modules.dep"
else
modules_dep=$(ls -1 /lib/modules/*/modules.dep 2>/dev/null | head -n 1)
modules_dep=$(ls -1 "$modules_root"/*/modules.dep 2>/dev/null | head -n 1)
fi
[[ -r "$modules_dep" ]] || return 0
@@ -100,10 +100,7 @@ if [ ! -x /usr/bin/wget ]; then
sleep 36500d
fi
# These dispatcher scripts are not needed by the legacy post.xcat path. Newer
# wget parses HTML-looking regex strings inside downloaded scripts and fails the
# whole recursive download on bogus URLs.
wget -l inf -N -r --waitretry=10 --random-wait --retry-connrefused -e robots=off -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent -t 20 -T 60 http://${MASTER_IP}:${HTTPPORT}${INSTALLDIR}/postscripts/ -P /xcatpost 2> /tmp/wget.log
xcat_download_postscripts "${MASTER_IP}:${HTTPPORT}" "$INSTALLDIR" "/xcatpost" "/tmp/wget.log"
if [ "$?" != "0" ]; then
msgutil_r "$MASTER_IP" "error" "failed to download postscripts from http://$MASTER_IP$INSTALLDIR/postscripts/,check /tmp/wget.log on the node, halt ..." "/var/log/xcat/xcat.log" "$log_label"
/tmp/updateflag $MASTER $XCATIPORT "installstatus failed"
@@ -191,17 +191,6 @@ if [ -e "/tmp/xcat.install_disk" ]; then
fi
msgutil_r "$MASTER_IP" "info" "Found $instdisk, generate partition file..." "/var/log/xcat/xcat.log" "$log_label"
set_sles11_uefi_bootloader()
{
if grep -E 'install=.*sles11' /proc/cmdline >/dev/null 2>&1; then
# SLES 11 AutoYaST keeps the template's legacy MBR bootloader
# location unless the UEFI path explicitly selects elilo.
sed -i -e '/<lba_support /d' \
-e '/<linear /d' \
-e 's!<location>mbr</location>!<loader_type>elilo</loader_type>!' \
/tmp/profile/modified.xml
fi
}
if [ -d /sys/firmware/efi ]; then
sed -e 's!<device>XCATPARTITIONHOOK</device>!<device>'$instdisk'</device><partitions config:type="list"><partition><filesystem config:type="symbol">vfat</filesystem><mount>/boot/efi</mount><size>128mb</size></partition><partition><mount>swap</mount><size>auto</size></partition><partition><mount>/</mount><size>auto</size></partition></partitions>!' /tmp/profile/autoinst.xml > /tmp/profile/modified.xml
@@ -63,3 +63,27 @@ declare -F xcat_enable_active_nm_autoconnect &>/dev/null || function xcat_enable
nmcli con mod "$con_name" connection.autoconnect yes
done
}
declare -F xcat_download_postscripts &>/dev/null || function xcat_download_postscripts {
local server="$1"
local install_dir="${2:-/install}"
local postroot="${3:-/xcatpost}"
local log_file="${4:-/tmp/wget.log}"
export LANG=C
wget -l inf -N -r --waitretry=10 --random-wait --retry-connrefused -e robots=off -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent -t 20 -T 60 "http://${server}${install_dir}/postscripts/" -P "$postroot" 2> "$log_file"
}
declare -F set_sles11_uefi_bootloader &>/dev/null || function set_sles11_uefi_bootloader {
local cmdline="${1:-/proc/cmdline}"
local profile="${2:-/tmp/profile/modified.xml}"
if grep -E 'install=.*sles11' "$cmdline" >/dev/null 2>&1; then
# SLES 11 AutoYaST keeps the template's legacy MBR bootloader
# location unless the UEFI path explicitly selects elilo.
sed -i -e '/<lba_support /d' \
-e '/<linear /d' \
-e 's!<location>mbr</location>!<loader_type>elilo</loader_type>!' \
"$profile"
fi
}
+20
View File
@@ -0,0 +1,20 @@
# xCAT-test
Unit tests that run from the source checkout are split by implementation
language:
| Test type | Location | Runner |
| --------- | -------- | ------ |
| Perl unit tests | `xCAT-test/unit/*.t` | `prove -r xCAT-test/unit` |
| Shell unit tests | `xCAT-test/autotest/bats/*.bats` | `bats -r xCAT-test/autotest/bats` |
Use Perl `.t` tests for Perl modules, Perl scripts, templates, and repository
artifacts. Use BATS tests for shell-script behavior that can be exercised from
the checkout by sourcing a shell library or script and shadowing external
commands.
Shell behavior should not be tested by Perl tests that grep shell source. Put
those tests under `xCAT-test/autotest/bats` instead.
See `unit/README.md` and `autotest/bats/README.md` for the detailed rules for
each unit-test suite.
+21
View File
@@ -0,0 +1,21 @@
# xCAT-test/autotest/bats
Shell-script unit tests live here and run with:
```bash
bats -r xCAT-test/autotest/bats
```
The GitHub Actions `xcat_test` workflow runs this command after the Perl `.t`
unit tests. Use BATS for shell behavior that can be exercised from the source
tree without an installed xCAT, a live management node, or real services.
Prefer sourcing an existing shell library or sourceable script and calling the
function under test. Keep reusable install-template helpers in
`xCAT-server/share/xcat/install/scripts/scriptlib`, and reusable postscript
helpers in `xCAT/postscripts/xcatlib.sh`. Use scratch directories and shadowed
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.
@@ -0,0 +1,118 @@
#!/usr/bin/env bats
load 'helpers/shell_source'
setup()
{
GENESIS_SPEC="$(repo_path 'xCAT-genesis-builder/xCAT-genesis-base.spec')"
DRACUT_MODULE="$(repo_path 'xCAT-genesis-builder/dracut_105/el/module-setup.sh')"
DOXCAT="$(repo_path 'xCAT-genesis-scripts/usr/bin/doxcat')"
[ -r "$GENESIS_SPEC" ] || skip "$GENESIS_SPEC is required"
[ -r "$DRACUT_MODULE" ] || skip "$DRACUT_MODULE is required"
[ -r "$DOXCAT" ] || skip "$DOXCAT is required"
export GENESIS_SPEC DRACUT_MODULE DOXCAT
}
run_installkernel()
{
local modules_root="$1"
local instmods_log="$2"
kernel=5.14.0-test
DRACUT_MODULES_ROOT="$modules_root"
instmods()
{
printf '%s\n' "$1" >>"$instmods_log"
}
source "$DRACUT_MODULE"
installkernel
}
run_doxcat_modprobe_preamble()
{
local preamble="$1"
local modprobe_log="$2"
modprobe()
{
printf '%s\n' "$*" >>"$modprobe_log"
}
eval "$preamble"
}
run_doxcat_bootif_block()
{
local block="$1"
BOOTIF=01-aa-bb-cc-dd-ee-ff
bootnic=
log_label=test
gripeiter=2
logger() { :; }
sleep() { :; }
ip()
{
printf '%s\n' "$*" >>"$IP_LOG"
if [ "$*" = "link show" ]; then
cat <<'EOF'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff
3: ib0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65520 qdisc mq state UP mode DEFAULT group default qlen 256
link/infiniband 00:bb:cc:dd:ee:ff brd 00:ff:ff:ff:ff:ff
EOF
fi
}
eval "$block"
printf '%s\n' "$bootnic"
}
@test "genesis build requires kernel module packages" {
grep -Fxq 'BuildRequires: kernel-core' "$GENESIS_SPEC"
grep -Fxq 'BuildRequires: kernel-modules' "$GENESIS_SPEC"
grep -Fxq 'BuildRequires: kernel-modules-extra' "$GENESIS_SPEC"
}
@test "dracut genesis module installs every module from modules.dep" {
local modules_root="${BATS_TEST_TMPDIR}/modules"
local modules_dep="${modules_root}/5.14.0-test/modules.dep"
local instmods_log="${BATS_TEST_TMPDIR}/instmods.log"
mkdir -p "${modules_root}/5.14.0-test"
cat >"$modules_dep" <<'EOF'
kernel/drivers/infiniband/ulp/ipoib/ib_ipoib.ko.xz:
kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko.xz:
EOF
run run_installkernel "$modules_root" "$instmods_log"
[ "$status" -eq 0 ]
grep -Fxq 'ib_ipoib' "$instmods_log"
grep -Fxq 'e1000e' "$instmods_log"
}
@test "doxcat loads IP over InfiniBand support during startup" {
local preamble
local modprobe_log="${BATS_TEST_TMPDIR}/modprobe.log"
preamble="$(extract_line_range "$DOXCAT" '^modprobe acpi_cpufreq' '^modprobe ib_ipoib$')" || return 1
run run_doxcat_modprobe_preamble "$preamble" "$modprobe_log"
[ "$status" -eq 0 ]
grep -Fxq 'ib_ipoib' "$modprobe_log"
}
@test "doxcat falls back to InfiniBand BOOTIF lookup after Ethernet lookup misses" {
local block
IP_LOG="${BATS_TEST_TMPDIR}/ip.log"
export IP_LOG
block="$(extract_shell_if_block "$DOXCAT" 'if [ ! -z "$BOOTIF" ]; then')" || return 1
run run_doxcat_bootif_block "$block"
[ "$status" -eq 0 ]
[ "$output" = "ib0" ]
[ "$(grep -c '^link show$' "$IP_LOG")" -eq 2 ]
}
@@ -0,0 +1,122 @@
#!/usr/bin/env bats
load 'helpers/go_xcat'
setup()
{
go_xcat_require_source
}
run_common_repository_case()
{
local case_dir="$1"
local common_present="$2"
shift 2
mkdir -p "$case_dir"
export ADD_LOG="${case_dir}/add.log"
export COMMON_PRESENT="$common_present"
export DOWNLOAD_LOG="${case_dir}/download.log"
export ID_LOG="${case_dir}/id.log"
export TEST_TMP="$case_dir"
go_xcat_load_functions \
add_xcat_dep_common_repo_yum_or_zypper \
xcat_dep_common_repo_configured \
refresh_xcat_dep_repository_ids
TMP_DIR="$TEST_TMP"
GO_XCAT_DEFAULT_BASE_URL=https://repo.example.invalid
GO_XCAT_DEP_REPOSITORY_IDS=(xcat-dep)
yum() { :; }
download_file()
{
printf '%s\n' "$1" >>"$DOWNLOAD_LOG"
[[ ${COMMON_PRESENT:-0} == 1 ]] || return 1
: >"$2"
}
add_repo_by_url_yum_or_zypper()
{
printf '%s %s\n' "$1" "$2" >>"$ADD_LOG"
}
xcat_dep_common_repo_configured()
{
[[ -s "$ADD_LOG" ]]
}
( add_xcat_dep_common_repo_yum_or_zypper "$@" )
refresh_xcat_dep_repository_ids
printf '%s\n' "${GO_XCAT_DEP_REPOSITORY_IDS[*]}" >"$ID_LOG"
}
run_template_generation()
{
local tmp_dir="$1"
local repo_log="$2"
mkdir -p "$tmp_dir"
export REPO_LOG="$repo_log"
export TEST_TMP="$tmp_dir"
go_xcat_load_functions add_repo_by_url_yum_or_zypper
TMP_DIR="$TEST_TMP"
GO_XCAT_DEFAULT_INSTALL_PATH=/install/xcat
yum() { :; }
add_repo_by_file() { cp "$1" "$REPO_LOG"; }
add_repo_by_url_yum_or_zypper \
https://repo.example.invalid/xcat-dep/common xcat-dep-common optional
}
@test "an available remote common repository is enabled" {
local case_dir="${BATS_TEST_TMPDIR}/remote-present"
run run_common_repository_case "$case_dir" 1 "" latest
[ "$status" -eq 0 ]
[ "$(read_file_or_empty "${case_dir}/download.log")" = "https://repo.example.invalid/yum/latest/xcat-dep/common/repodata/repomd.xml" ]
[ "$(read_file_or_empty "${case_dir}/add.log")" = "https://repo.example.invalid/yum/latest/xcat-dep/common xcat-dep-common" ]
[ "$(read_file_or_empty "${case_dir}/id.log")" = "xcat-dep xcat-dep-common" ]
}
@test "a release without the remote common repository remains usable" {
local case_dir="${BATS_TEST_TMPDIR}/remote-missing"
run run_common_repository_case "$case_dir" 0 "" 2.18
[ "$status" -eq 0 ]
[ "$(read_file_or_empty "${case_dir}/add.log")" = "" ]
[ "$(read_file_or_empty "${case_dir}/id.log")" = "xcat-dep" ]
}
@test "a custom repository file does not guess an unrelated common repository" {
local case_dir="${BATS_TEST_TMPDIR}/repo-file"
run run_common_repository_case "$case_dir" 1 https://repo.example.invalid/custom/xcat-dep.repo latest
[ "$status" -eq 0 ]
[ "$(read_file_or_empty "${case_dir}/download.log")" = "" ]
[ "$(read_file_or_empty "${case_dir}/add.log")" = "" ]
}
@test "a local common repository is enabled beside the distribution repository" {
local local_root="${BATS_TEST_TMPDIR}/local-repository"
local case_dir="${BATS_TEST_TMPDIR}/local-present"
mkdir -p "${local_root}/common/repodata"
: >"${local_root}/common/repodata/repomd.xml"
run run_common_repository_case "$case_dir" 0 "$local_root" latest
[ "$status" -eq 0 ]
[ "$(read_file_or_empty "${case_dir}/add.log")" = "${local_root}/common xcat-dep-common" ]
}
@test "the optional common repository template tolerates outages and verifies metadata" {
local template_log="${BATS_TEST_TMPDIR}/generated-common.repo"
run run_template_generation "$BATS_TEST_TMPDIR" "$template_log"
[ "$status" -eq 0 ]
grep -Fxq 'skip_if_unavailable=1' "$template_log"
grep -Fxq 'repo_gpgcheck=1' "$template_log"
}
@@ -0,0 +1,206 @@
#!/usr/bin/env bats
load 'helpers/go_xcat'
setup()
{
go_xcat_require_source
export CALLS="${BATS_TEST_TMPDIR}/calls"
export GO_XCAT_ARCH=x86_64
export GO_XCAT_LINUX_DISTRO=rocky
export GO_XCAT_LINUX_VERSION=10.2
export EPEL_HAS=1
export CRB_HAS=1
export QUERY_FAIL=0
export QUERY_WARNS=0
export SOURCE_ONLY=0
export ENTRY=check
}
run_el_repo_check()
{
rm -f "$CALLS"
go_xcat_load_functions \
repo_carries \
el_epel_and_crb_check \
install_packages_dnf \
install_packages_yum
EL_EPEL_TEST_RPM=perl-Crypt-CBC
EL_CRB_TEST_RPM=perl-IO-Tty
dnf()
{
echo "$*" >>"$CALLS"
if [[ ${QUERY_FAIL:-0} == 1 ]]; then
echo "Error: Failed to download metadata for repo 'epel'" >&2
return 1
fi
[[ ${QUERY_WARNS:-0} == 1 ]] && echo "Warning: repository 'extras' metadata is stale" >&2
local has=0
case "$*" in
*perl-Crypt-CBC*) has="$EPEL_HAS" ;;
*perl-IO-Tty*) has="$CRB_HAS" ;;
esac
[[ ${SOURCE_ONLY:-0} == 1 && "$*" != *"--arch"* ]] && has=1
case "$1" in
repoquery) [[ $has == 1 ]] && { echo "${@: -1}"; echo "${@: -1}"; }; return 0 ;;
list) [[ $has == 1 ]] && return 0; return 1 ;;
esac
return 0
}
yum()
{
dnf "$@"
}
case "${ENTRY:-check}" in
dnf) install_packages_dnf -y xCAT ;;
yum) install_packages_yum -y xCAT ;;
*) el_epel_and_crb_check dnf ;;
esac
}
@test "EL9 with EPEL and CRB passes after probing binary repositories" {
export GO_XCAT_LINUX_VERSION=9.5
run run_el_repo_check
[ "$status" -eq 0 ]
probes="$(joined_file_lines "$CALLS")"
[[ "$probes" =~ perl-Crypt-CBC.*\;.*perl-IO-Tty ]]
[[ "$probes" =~ ^repoquery\ ]]
[[ "$probes" =~ --arch\ x86_64,noarch ]]
}
@test "EL9 without EPEL stops and names the EL9 release package" {
export GO_XCAT_LINUX_VERSION=9.5
export EPEL_HAS=0
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ epel-release-latest-9\.noarch ]]
}
@test "EL10 with EPEL and CRB passes after probing both repositories" {
run run_el_repo_check
[ "$status" -eq 0 ]
probes="$(joined_file_lines "$CALLS")"
[[ "$probes" =~ perl-Crypt-CBC.*\;.*perl-IO-Tty ]]
}
@test "EL10 without EPEL stops with the EL10 EPEL release package" {
export EPEL_HAS=0
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ requires\ EPEL\ repository ]]
[[ "$output" =~ epel-release-latest-10\.noarch ]]
}
@test "EL10 without CRB stops with current CRB guidance" {
export GO_XCAT_LINUX_DISTRO=rhel
export CRB_HAS=0
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ requires\ CRB\ repository ]]
[[ "$output" =~ "'dnf update epel-release' and then 'crb enable'" ]]
[[ ! "$output" =~ gpgcheck=0|centos-crb|subscription-manager ]]
}
@test "a source repository does not stand in for the binary one" {
export EPEL_HAS=0
export SOURCE_ONLY=1
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ requires\ EPEL\ repository ]]
}
@test "Oracle Linux 10 without CRB names its CodeReady Builder command" {
export GO_XCAT_LINUX_DISTRO=ol
export GO_XCAT_LINUX_VERSION=10.1
export CRB_HAS=0
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ dnf\ config-manager\ --enable\ ol10_codeready_builder ]]
}
@test "a failed repository query stops with the package manager error" {
export QUERY_FAIL=1
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ Failed\ to\ download\ metadata ]]
[[ ! "$output" =~ requires\ EPEL\ repository ]]
}
@test "a warning on stderr does not stand in for a package" {
export EPEL_HAS=0
export QUERY_WARNS=1
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ requires\ EPEL\ repository ]]
}
@test "a warning beside a real match does not fail the check" {
export QUERY_WARNS=1
run run_el_repo_check
[ "$status" -eq 0 ]
}
@test "CentOS Stream 10, which reports the major version alone, is checked" {
export GO_XCAT_LINUX_DISTRO=centos
export GO_XCAT_LINUX_VERSION=10
export EPEL_HAS=0
export CRB_HAS=0
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ requires\ EPEL\ repository ]]
}
@test "EL8 and Fedora are not checked" {
export GO_XCAT_LINUX_VERSION=8.10
export EPEL_HAS=0
export CRB_HAS=0
run run_el_repo_check
[ "$status" -eq 0 ]
[ "$(joined_file_lines "$CALLS")" = "" ]
export GO_XCAT_LINUX_DISTRO=fedora
export GO_XCAT_LINUX_VERSION=42
run run_el_repo_check
[ "$status" -eq 0 ]
}
@test "dnf and yum installer paths run the check before installing" {
for entry in dnf yum; do
export ENTRY="$entry"
export EPEL_HAS=0
export CRB_HAS=1
run run_el_repo_check
[ "$status" -eq 1 ]
[[ "$output" =~ requires\ EPEL\ repository ]]
probes="$(joined_file_lines "$CALLS")"
[[ ! "$probes" =~ install ]]
export EPEL_HAS=1
export CRB_HAS=1
run run_el_repo_check
[ "$status" -eq 0 ]
probes="$(joined_file_lines "$CALLS")"
[[ "$probes" =~ perl-IO-Tty.*\;.*install\ initscripts.*\;.*install\ xCAT ]]
done
}
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
go_xcat_default_source()
{
printf '%s\n' "${BATS_TEST_DIRNAME}/../../../xCAT-server/share/xcat/tools/go-xcat"
}
go_xcat_require_source()
{
GO_XCAT_SOURCE="${XCAT_TEST_GO_XCAT:-$(go_xcat_default_source)}"
export GO_XCAT_SOURCE
[ -r "$GO_XCAT_SOURCE" ] || skip "$GO_XCAT_SOURCE is required"
}
go_xcat_extract_functions()
{
local function_name
for function_name in "$@"; do
awk -v name="$function_name" '
$0 == "function " name "()" { copy = 1 }
copy { print }
copy && /^}$/ { exit }
' "$GO_XCAT_SOURCE"
done
}
go_xcat_load_functions()
{
local function_body function_name
function_body="$(go_xcat_extract_functions "$@")" || return 1
eval "$function_body"
for function_name in "$@"; do
declare -F "$function_name" >/dev/null || {
printf 'missing %s\n' "$function_name" >&2
return 70
}
done
}
read_file_or_empty()
{
local path="$1"
[ -f "$path" ] || return 0
cat "$path"
}
joined_file_lines()
{
local path="$1"
local line separator=""
[ -f "$path" ] || return 0
while IFS= read -r line; do
printf '%s%s' "$separator" "$line"
separator=";"
done <"$path"
}
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
repo_root()
{
printf '%s\n' "${BATS_TEST_DIRNAME}/../../.."
}
repo_path()
{
printf '%s/%s\n' "$(repo_root)" "$1"
}
require_repo_file()
{
local path
path="$(repo_path "$1")"
[ -r "$path" ] || skip "$path is required"
printf '%s\n' "$path"
}
read_file_or_empty()
{
local path="$1"
[ -f "$path" ] || return 0
cat "$path"
}
extract_shell_function()
{
local file="$1"
local name="$2"
awk -v name="$name" '
BEGIN {
signature = "^[[:space:]]*(function[[:space:]]+)?" name "([[:space:]]*\\(\\))?[[:space:]]*$"
inline_signature = "^[[:space:]]*(function[[:space:]]+)?" name "([[:space:]]*\\(\\))?[[:space:]]*\\{"
}
$0 ~ signature || $0 ~ inline_signature {
copy = 1
}
copy {
print
opened += gsub(/\{/, "{")
closed += gsub(/\}/, "}")
if (opened > 0 && opened == closed) {
found = 1
exit
}
}
END {
if (!found) {
exit 1
}
}
' "$file"
}
extract_shell_if_block()
{
local file="$1"
local start="$2"
awk -v start="$start" '
index($0, start) {
copy = 1
}
copy {
print
if ($0 ~ /^[[:space:]]*if[[:space:]\[]/) {
depth++
}
line = $0
while (line ~ /(^|[;[:space:]])fi([;[:space:]]|$)/) {
depth--
sub(/(^|[;[:space:]])fi([;[:space:]]|$)/, " ", line)
}
if (depth == 0) {
found = 1
exit
}
}
END {
if (!found) {
exit 1
}
}
' "$file"
}
extract_line_range()
{
local file="$1"
local start="$2"
local end="$3"
awk -v start="$start" -v end="$end" '
$0 ~ start {
copy = 1
}
copy {
print
if ($0 ~ end) {
found = 1
exit
}
}
END {
if (!found) {
exit 1
}
}
' "$file"
}
extract_first_matching_line()
{
local file="$1"
local pattern="$2"
awk -v pattern="$pattern" '
$0 ~ pattern {
print
found = 1
exit
}
END {
if (!found) {
exit 1
}
}
' "$file"
}
@@ -0,0 +1,76 @@
#!/usr/bin/env bats
load 'helpers/shell_source'
setup()
{
SCRIPT_LIB="$(repo_path 'xCAT-server/share/xcat/install/scripts/scriptlib')"
XCATLIB="$(repo_path 'xCAT/postscripts/xcatlib.sh')"
[ -r "$SCRIPT_LIB" ] || skip "$SCRIPT_LIB is required"
[ -r "$XCATLIB" ] || skip "$XCATLIB is required"
export SCRIPT_LIB XCATLIB
}
capture_install_scriptlib_wget()
{
local wget_log="$1"
wget()
{
printf '%s\n' "$*" >"$wget_log"
return 0
}
source "$SCRIPT_LIB"
xcat_download_postscripts "192.0.2.10:80" "/install" "/xcatpost" "$wget_log"
}
capture_xcatlib_wget()
{
local wget_log="$1"
xcatpost="${BATS_TEST_TMPDIR}/xcatpost"
INSTALLDIR=/install
echolog() { :; }
sleep() { :; }
grep()
{
[ "${*: -1}" = "/tmp/wget.log" ] && return 1
command grep "$@"
}
wget()
{
printf '%s\n' "$*" >"$wget_log"
return 0
}
source "$XCATLIB"
download_postscripts 192.0.2.10:80
}
assert_download_policy()
{
local args="$1"
[[ "$args" == *'--reject index.html*,post.xcat.ng,post.xcat.rhels10'* ]]
[[ "$args" == *'--no-parent'* ]]
[[ "$args" == *'postscripts/'* ]]
[[ "$args" != *'<a href='* ]]
}
@test "install scriptlib recursive download rejects dispatcher scripts" {
local wget_log="${BATS_TEST_TMPDIR}/post-xcat-wget.log"
run capture_install_scriptlib_wget "$wget_log"
[ "$status" -eq 0 ]
assert_download_policy "$(read_file_or_empty "$wget_log")"
}
@test "postscript xcatlib recursive download rejects dispatcher scripts" {
local wget_log="${BATS_TEST_TMPDIR}/xcatdsklspost-wget.log"
run capture_xcatlib_wget "$wget_log"
[ "$status" -eq 0 ]
assert_download_policy "$(read_file_or_empty "$wget_log")"
}
@@ -0,0 +1,77 @@
#!/usr/bin/env bats
load 'helpers/shell_source'
setup()
{
XCATLIB="$(repo_path 'xCAT/postscripts/xcatlib.sh')"
[ -r "$XCATLIB" ] || skip "$XCATLIB is required"
export XCATLIB
}
run_restart_fallback()
{
ps()
{
cat <<'EOF'
root 4321 0.0 0.0 15432 2048 ? Ss 10:00 0:00 /usr/sbin/sshd
EOF
}
kill()
{
printf '%s\n' "$*" >>"$KILL_LOG"
[ "$1" = "-0" ] && return 1
return 0
}
sleep() { :; }
sshd()
{
printf '%s\n' start >>"$SSHD_LOG"
}
source "$XCATLIB"
xcat_restart_sshd_after_failed_service_restart sshd
}
run_wait_for_processes()
{
local rc
source "$XCATLIB"
xcat_wait_for_processes_to_exit "$*" 3
rc=$?
printf '%s\n' "$rc"
return 0
}
@test "remoteshell restart fallback sends an uncatchable signal before starting sshd" {
KILL_LOG="${BATS_TEST_TMPDIR}/kill.log"
SSHD_LOG="${BATS_TEST_TMPDIR}/sshd.log"
export KILL_LOG SSHD_LOG
run run_restart_fallback
[ "$status" -eq 0 ]
grep -Fxq -- '-9 4321' "$KILL_LOG"
! grep -Eq '^9( |$)' "$KILL_LOG"
[ "$(read_file_or_empty "$SSHD_LOG")" = "start" ]
}
@test "remoteshell wait loop reports a still-running process and gives up" {
local child
sleep 30 &
child=$!
run run_wait_for_processes "$child"
kill -KILL "$child" 2>/dev/null || true
wait "$child" 2>/dev/null || true
[ "$status" -eq 0 ]
[ "$output" = "1" ]
}
@test "remoteshell wait loop returns as soon as killed processes are gone" {
run run_wait_for_processes 999999
[ "$status" -eq 0 ]
[ "$output" = "0" ]
}
@@ -0,0 +1,45 @@
#!/usr/bin/env bats
load 'helpers/shell_source'
setup()
{
SCRIPT_LIB="$(repo_path 'xCAT-server/share/xcat/install/scripts/scriptlib')"
[ -r "$SCRIPT_LIB" ] || skip "$SCRIPT_LIB is required"
export SCRIPT_LIB
}
@test "SLES 11 UEFI install changes the AutoYaST bootloader to elilo" {
local cmdline="${BATS_TEST_TMPDIR}/cmdline"
local profile="${BATS_TEST_TMPDIR}/modified.xml"
printf '%s\n' 'BOOT_IMAGE=/linux install=http://192.0.2.10/install/sles11/ppc64le' >"$cmdline"
cat >"$profile" <<'EOF'
<bootloader>
<lba_support config:type="boolean">true</lba_support>
<linear config:type="boolean">true</linear>
<location>mbr</location>
</bootloader>
EOF
source "$SCRIPT_LIB"
run set_sles11_uefi_bootloader "$cmdline" "$profile"
[ "$status" -eq 0 ]
grep -Fxq '<loader_type>elilo</loader_type>' "$profile"
! grep -q '<location>mbr</location>' "$profile"
! grep -q '<lba_support ' "$profile"
! grep -q '<linear ' "$profile"
}
@test "non-SLES 11 install media keeps the legacy bootloader template value" {
local cmdline="${BATS_TEST_TMPDIR}/cmdline"
local profile="${BATS_TEST_TMPDIR}/modified.xml"
printf '%s\n' 'BOOT_IMAGE=/linux install=http://192.0.2.10/install/sles15/ppc64le' >"$cmdline"
printf '%s\n' '<location>mbr</location>' >"$profile"
source "$SCRIPT_LIB"
run set_sles11_uefi_bootloader "$cmdline" "$profile"
[ "$status" -eq 0 ]
grep -Fxq '<location>mbr</location>' "$profile"
}
@@ -0,0 +1,40 @@
#!/usr/bin/env bats
load 'helpers/shell_source'
setup()
{
ADD_SSH="$(repo_path 'xCAT-server/share/xcat/netboot/add-on/statelite/add_ssh')"
[ -r "$ADD_SSH" ] || skip "$ADD_SSH is required"
export ADD_SSH
}
run_sshd_config_block()
{
local root="$1"
local block
block="$(extract_shell_if_block "$ADD_SSH" 'if [ -r $ROOTDIR/etc/ssh/sshd_config ]')" || return 1
ROOTDIR="$root"
eval "$block"
}
@test "statelite add_ssh writes sshd settings below systemd's open-file limit" {
local root="${BATS_TEST_TMPDIR}/rootimg"
local sshd_config="${root}/etc/ssh/sshd_config"
mkdir -p "${root}/etc/ssh"
cat >"$sshd_config" <<'EOF'
X11Forwarding no
KeyRegenerationInterval 3600
MaxStartups 1024
EOF
run run_sshd_config_block "$root"
[ "$status" -eq 0 ]
grep -Fxq 'X11Forwarding yes' "$sshd_config"
grep -Fxq 'KeyRegenerationInterval 0' "$sshd_config"
grep -Fxq '#MaxStartups 1024' "$sshd_config"
grep -Fxq 'MaxStartups 100:30:200' "$sshd_config"
! grep -Fxq 'MaxStartups 1024' "$sshd_config"
}
+5
View File
@@ -46,6 +46,11 @@ so putting a test in `integration/` does not cost it CI coverage. What differs i
each suite is allowed to depend on, and that unit tests also run standalone from a
bare checkout with no xCAT at all.
Shell-script unit tests belong in [`../autotest/bats`](../autotest/bats/README.md)
and run with BATS. Do not add Perl `.t` tests that grep shell source when the
behavior can be exercised by sourcing a shell library or script and shadowing the
external commands it calls.
The distinction matters because a test that needs an absent environment does not fail
-- it calls `plan skip_all` and reports as skipped. A handful of those in a suite of
several hundred assertions is easy to stop reading. Keeping the two kinds in separate
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use FindBin;
use Test::More;
my $repo_root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..'));
my $spec = read_file('xCAT-genesis-builder/xCAT-genesis-base.spec');
like($spec, qr/^BuildRequires:\s+kernel-core$/m, 'genesis build installs the kernel core');
like($spec, qr/^BuildRequires:\s+kernel-modules$/m, 'genesis build installs the standard kernel modules');
like($spec, qr/^BuildRequires:\s+kernel-modules-extra$/m, 'genesis build installs the extra kernel modules');
my $dracut_module = read_file('xCAT-genesis-builder/dracut_105/el/module-setup.sh');
like(
$dracut_module,
qr/installkernel\(\) \{.*?modules_dep=.*?while IFS= read -r modfile;.*?instmods "\$modname".*?done < "\$modules_dep"/s,
'genesis dracut module installs every module available to the build'
);
my $doxcat = read_file('xCAT-genesis-scripts/usr/bin/doxcat');
like($doxcat, qr/^modprobe ib_ipoib$/m, 'genesis loads the IP over InfiniBand module');
like(
$doxcat,
qr/if \[ -z "\$bootnic" \]; then\s+bootnic=`ip link show\|grep -B1 infiniband/s,
'genesis checks InfiniBand addresses only after the Ethernet lookup misses'
);
done_testing();
sub read_file {
my ($file) = @_;
my $path = File::Spec->catfile($repo_root, split m{/}, $file);
open(my $fh, '<', $path) or die "open $path: $!";
my $contents = do { local $/; <$fh> };
close($fh) or die "close $path: $!";
return $contents;
}
-165
View File
@@ -1,165 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Path qw(make_path);
use File::Temp qw(tempdir);
use FindBin;
use Test::More;
my $go_xcat = "$FindBin::Bin/../../xCAT-server/share/xcat/tools/go-xcat";
my $tmpdir = tempdir(CLEANUP => 1);
my $driver = "$tmpdir/driver.sh";
open(my $driver_fh, '>', $driver) or die "open $driver: $!";
print {$driver_fh} <<'DRIVER';
#!/bin/bash
set -euo pipefail
function_body=$(
for function_name in \
add_xcat_dep_common_repo_yum_or_zypper \
xcat_dep_common_repo_configured \
refresh_xcat_dep_repository_ids
do
awk -v name="$function_name" '
$0 == "function " name "()" { copy = 1 }
copy { print }
copy && /^}$/ { exit }
' "$GO_XCAT_SOURCE"
done
)
eval "$function_body"
TMP_DIR=$TEST_TMP
GO_XCAT_DEFAULT_BASE_URL=https://repo.example.invalid
GO_XCAT_DEP_REPOSITORY_IDS=(xcat-dep)
yum() { :; }
download_file() {
printf '%s\n' "$1" >>"$DOWNLOAD_LOG"
[[ ${COMMON_PRESENT:-0} == 1 ]] || return 1
: >"$2"
}
add_repo_by_url_yum_or_zypper() {
printf '%s %s\n' "$1" "$2" >>"$ADD_LOG"
}
xcat_dep_common_repo_configured() { [[ -s "$ADD_LOG" ]]; }
( add_xcat_dep_common_repo_yum_or_zypper "$@" )
refresh_xcat_dep_repository_ids
printf '%s\n' "${GO_XCAT_DEP_REPOSITORY_IDS[*]}" >"$ID_LOG"
DRIVER
close($driver_fh) or die "close $driver: $!";
chmod(0755, $driver) or die "chmod $driver: $!";
sub run_case {
my ($name, $present, @arguments) = @_;
my $case_dir = "$tmpdir/$name";
make_path($case_dir);
local %ENV = (
%ENV,
ADD_LOG => "$case_dir/add.log",
COMMON_PRESENT => $present,
DOWNLOAD_LOG => "$case_dir/download.log",
GO_XCAT_SOURCE => $go_xcat,
ID_LOG => "$case_dir/id.log",
TEST_TMP => $case_dir,
);
my $status = system('bash', $driver, @arguments);
return ($status >> 8, $case_dir);
}
sub read_file {
my ($path) = @_;
return '' unless -f $path;
open(my $fh, '<', $path) or die "open $path: $!";
my $content = do { local $/; <$fh> };
close($fh) or die "close $path: $!";
return $content;
}
my ($status, $case_dir) = run_case('remote-present', 1, '', 'latest');
is($status, 0, 'an available common repository is accepted');
is(
read_file("$case_dir/download.log"),
"https://repo.example.invalid/yum/latest/xcat-dep/common/repodata/repomd.xml\n",
'the default repository is probed before it is enabled',
);
is(
read_file("$case_dir/add.log"),
"https://repo.example.invalid/yum/latest/xcat-dep/common xcat-dep-common\n",
'the common repository uses its own repository ID',
);
is(read_file("$case_dir/id.log"), "xcat-dep xcat-dep-common\n",
'common packages are included in repository listings');
($status, $case_dir) = run_case('remote-missing', 0, '', '2.18');
is($status, 0, 'a release without the common repository remains usable');
is(read_file("$case_dir/add.log"), '', 'a missing common repository is not enabled');
is(read_file("$case_dir/id.log"), "xcat-dep\n",
'legacy package listings remain unchanged when common is absent');
($status, $case_dir) = run_case(
'repo-file', 1, 'https://repo.example.invalid/custom/xcat-dep.repo', 'latest'
);
is($status, 0, 'a custom repository file remains supported');
is(read_file("$case_dir/download.log"), '',
'the common URL is not guessed from a custom repository file');
is(read_file("$case_dir/add.log"), '',
'a custom repository file does not enable an unrelated repository');
my $local_root = "$tmpdir/local-repository";
make_path("$local_root/common/repodata");
open(my $repomd_fh, '>', "$local_root/common/repodata/repomd.xml") or die $!;
close($repomd_fh) or die $!;
($status, $case_dir) = run_case('local-present', 0, $local_root, 'latest');
is($status, 0, 'a local common repository is accepted');
is(
read_file("$case_dir/add.log"),
"$local_root/common xcat-dep-common\n",
'the local common repository is enabled beside the distribution repository',
);
my $template_driver = "$tmpdir/template-driver.sh";
open(my $template_fh, '>', $template_driver) or die "open $template_driver: $!";
print {$template_fh} <<'DRIVER';
#!/bin/bash
set -euo pipefail
function_body=$(
awk '
/^function add_repo_by_url_yum_or_zypper\(\)/ { copy = 1 }
copy { print }
copy && /^}$/ { exit }
' "$GO_XCAT_SOURCE"
)
eval "$function_body"
TMP_DIR=$TEST_TMP
GO_XCAT_DEFAULT_INSTALL_PATH=/install/xcat
yum() { :; }
add_repo_by_file() { cp "$1" "$REPO_LOG"; }
add_repo_by_url_yum_or_zypper \
https://repo.example.invalid/xcat-dep/common xcat-dep-common optional
DRIVER
close($template_fh) or die "close $template_driver: $!";
chmod(0755, $template_driver) or die "chmod $template_driver: $!";
my $template_log = "$tmpdir/generated-common.repo";
local %ENV = (
%ENV,
GO_XCAT_SOURCE => $go_xcat,
REPO_LOG => $template_log,
TEST_TMP => $tmpdir,
);
$status = system('bash', $template_driver);
is($status >> 8, 0, 'go-xcat can generate the optional common repository');
like(read_file($template_log), qr/^skip_if_unavailable=1$/m,
'the generated common repository stays optional during outages');
like(read_file($template_log), qr/^repo_gpgcheck=1$/m,
'the generated common repository verifies signed metadata');
done_testing();
-161
View File
@@ -1,161 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw(tempdir);
use FindBin;
use Test::More;
# go-xcat checked for the EPEL and CRB repositories on EL9 only, and only when the version
# carried a minor number, so CentOS Stream was never checked. On EL10 a management node without
# them failed inside dnf install with a dependency error instead of the message that names the
# missing repository, and the CRB message proposed a CentOS Stream repository file with
# signature checks disabled. The probe used dnf list, which an installed copy of the probe
# package satisfies, and which reports a failed query as a missing repository. The check
# functions are taken from the shipped script and run against a dnf stand-in that answers for
# each probe and records what was asked.
my $go_xcat = "$FindBin::Bin/../../xCAT-server/share/xcat/tools/go-xcat";
plan skip_all => 'go-xcat not found' unless -r $go_xcat;
my $tmpdir = tempdir( CLEANUP => 1 );
my $driver = "$tmpdir/driver.sh";
my $calls = "$tmpdir/calls";
open( my $fh, '>', $driver ) or die "open $driver: $!";
print {$fh} <<'DRIVER';
#!/bin/bash
set -uo pipefail
eval "$(awk '
/^function (repo_carries|el9?_epel_and_crb_check|install_packages_(dnf|yum))\(\)/ { copy = 1 }
copy { print }
copy && /^}$/ { copy = 0 }
' "$GO_XCAT_SOURCE")"
EL9_EPEL_TEST_RPM="perl-Crypt-CBC"; EL_EPEL_TEST_RPM="perl-Crypt-CBC"
EL9_CRB_TEST_RPM="perl-IO-Tty"; EL_CRB_TEST_RPM="perl-IO-Tty"
# repoquery prints the name when an enabled repository carries the package and nothing
# otherwise, and fails with a message when the repositories cannot be read. list -q exits 1
# when neither a repository nor the installed set has the package.
dnf() {
echo "$*" >> "$CALLS"
if [[ ${QUERY_FAIL:-0} == 1 ]]; then
echo "Error: Failed to download metadata for repo 'epel'" >&2
return 1
fi
[[ ${QUERY_WARNS:-0} == 1 ]] && echo "Warning: repository 'extras' metadata is stale" >&2
local has=0
case "$*" in
*perl-Crypt-CBC*) has="$EPEL_HAS" ;;
*perl-IO-Tty*) has="$CRB_HAS" ;;
esac
# A source repository answers for the name unless the query is limited to binary architectures.
[[ ${SOURCE_ONLY:-0} == 1 && "$*" != *"--arch"* ]] && has=1
case "$1" in
repoquery) [[ $has == 1 ]] && { echo "${@: -1}"; echo "${@: -1}"; }; return 0 ;;
list) [[ $has == 1 ]] && return 0; return 1 ;;
esac
return 0
}
yum() { dnf "$@"; }
# ENTRY=dnf or yum runs the installer function go-xcat dispatches to, which owns the check.
case "${ENTRY:-check}" in
dnf) install_packages_dnf -y xCAT ;;
yum) install_packages_yum -y xCAT ;;
*) if declare -F el_epel_and_crb_check >/dev/null; then el_epel_and_crb_check dnf; else el9_epel_and_crb_check dnf; fi ;;
esac
DRIVER
close($fh);
# Runs the check as go-xcat would on a host of this distro and version. Returns the exit status,
# the output, and the probes the dnf stand-in saw.
sub check {
my (%host) = @_;
unlink $calls;
local $ENV{GO_XCAT_SOURCE} = $go_xcat;
local $ENV{CALLS} = $calls;
local $ENV{GO_XCAT_LINUX_DISTRO} = $host{distro} || 'rocky';
local $ENV{GO_XCAT_LINUX_VERSION} = $host{version};
local $ENV{GO_XCAT_ARCH} = 'x86_64';
local $ENV{EPEL_HAS} = $host{epel} ? 1 : 0;
local $ENV{CRB_HAS} = $host{crb} ? 1 : 0;
local $ENV{QUERY_FAIL} = $host{query_fail} ? 1 : 0;
local $ENV{QUERY_WARNS} = $host{query_warns} ? 1 : 0;
local $ENV{SOURCE_ONLY} = $host{source_only} ? 1 : 0;
local $ENV{ENTRY} = $host{entry} || 'check';
my $out = `bash '$driver' 2>&1`;
my $rc = $? >> 8;
my @probes;
if ( open( my $cfh, '<', $calls ) ) { chomp( @probes = <$cfh> ); close($cfh); }
return ( $rc, $out, join( ';', @probes ) );
}
my ( $rc, $out, $probes );
( $rc, $out, $probes ) = check( version => '9.5', epel => 1, crb => 1 );
is( $rc, 0, 'EL9 with EPEL and CRB passes' );
like( $probes, qr/perl-Crypt-CBC.*;.*perl-IO-Tty/, '... after probing both repositories' );
like( $probes, qr/^repoquery /, '... through repoquery, which an installed copy does not satisfy' );
like( $probes, qr/--arch x86_64,noarch/, '... limited to the binary architectures' );
( $rc, $out, $probes ) = check( version => '9.5', epel => 0, crb => 1 );
is( $rc, 1, 'EL9 without EPEL stops' );
like( $out, qr/epel-release-latest-9\.noarch/, '... and names the EL9 EPEL release package' );
( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1 );
is( $rc, 0, 'EL10 with EPEL and CRB passes' );
like( $probes, qr/perl-Crypt-CBC.*;.*perl-IO-Tty/, '... after probing both repositories' );
( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1 );
is( $rc, 1, 'EL10 without EPEL stops' );
like( $out, qr/requires EPEL repository/, '... and names the missing repository' );
like( $out, qr/epel-release-latest-10\.noarch/, '... and the EL10 EPEL release package' );
( $rc, $out, $probes ) = check( distro => 'rhel', version => '10.2', epel => 1, crb => 0 );
is( $rc, 1, 'EL10 without CRB stops' );
like( $out, qr/requires CRB repository/, '... and names the missing repository' );
like( $out, qr/'dnf update epel-release' and then 'crb enable'/, '... with a current crb helper, which covers RHEL under RHSM and RHUI' );
unlike( $out, qr/gpgcheck=0|centos-crb|subscription-manager/, '... and no repository file or subscription-only command' );
( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1, source_only => 1 );
is( $rc, 1, 'a source repository does not stand in for the binary one' );
like( $out, qr/requires EPEL repository/, '... so the missing repository is still reported' );
( $rc, $out, $probes ) = check( distro => 'ol', version => '10.1', epel => 1, crb => 0 );
is( $rc, 1, 'Oracle Linux 10 without CRB stops' );
like( $out, qr/dnf config-manager --enable ol10_codeready_builder/, '... with the command that enables its CodeReady Builder' );
( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1, query_fail => 1 );
is( $rc, 1, 'a failed repository query stops' );
like( $out, qr/Failed to download metadata/, '... with the package manager error' );
unlike( $out, qr/requires EPEL repository/, '... and not as a missing repository' );
( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1, query_warns => 1 );
is( $rc, 1, 'a warning on stderr does not stand in for a package' );
like( $out, qr/requires EPEL repository/, '... so the missing repository is still reported' );
( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1, query_warns => 1 );
is( $rc, 0, 'a warning beside a real match does not fail the check' );
( $rc, $out, $probes ) = check( distro => 'centos', version => '10', epel => 0, crb => 0 );
is( $rc, 1, 'CentOS Stream 10, which reports the major version alone, is checked' );
like( $out, qr/requires EPEL repository/, '... and told about EPEL' );
( $rc, $out, $probes ) = check( version => '8.10', epel => 0, crb => 0 );
is( $rc, 0, 'EL8 is not checked' );
is( $probes, '', '... and nothing is probed' );
( $rc, $out, $probes ) = check( distro => 'fedora', version => '42', epel => 0, crb => 0 );
is( $rc, 0, 'Fedora is not checked' );
# The installer functions go-xcat dispatches to run the check before they install anything.
foreach my $entry (qw(dnf yum)) {
( $rc, $out, $probes ) = check( entry => $entry, version => '10.2', epel => 0, crb => 1 );
is( $rc, 1, "install_packages_$entry on EL10 without EPEL stops" );
like( $out, qr/requires EPEL repository/, '... with the EPEL message' );
unlike( $probes, qr/install/, '... before anything is installed' );
( $rc, $out, $probes ) = check( entry => $entry, version => '10.2', epel => 1, crb => 1 );
is( $rc, 0, "install_packages_$entry on EL10 with EPEL and CRB installs" );
like( $probes, qr/perl-IO-Tty.*;.*install initscripts.*;.*install xCAT/, '... after the check passed' );
}
done_testing();
@@ -1,48 +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 %scripts = (
'legacy install post.xcat' => File::Spec->catfile(
$repo_root,
'xCAT-server/share/xcat/install/scripts/post.xcat'
),
'legacy netboot xcatdsklspost' => File::Spec->catfile(
$repo_root,
'xCAT/postscripts/xcatdsklspost'
),
);
foreach my $path ( values %scripts ) {
plan skip_all => "$path not found" unless -r $path;
}
foreach my $name ( sort keys %scripts ) {
my $path = $scripts{$name};
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
my $script = do { local $/; <$fh> };
close($fh);
like(
$script,
qr/--reject\s+"index\.html\*,post\.xcat\.ng,post\.xcat\.rhels10"/,
"$name recursive wget ignores dispatcher scripts that contain literal HTML-link regexes"
);
like(
$script,
qr/Newer\s+(?:#\s+)?wget\s+parses\s+HTML-looking\s+regex\s+strings/s,
"$name download policy documents why dispatcher scripts are excluded"
);
unlike(
$script,
qr/<a\s+href=/i,
"$name comments do not contain HTML links that wget can recurse into"
);
}
done_testing();
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use FindBin;
use Test::More;
my $script = File::Spec->catfile( $FindBin::Bin, '..', '..',
'xCAT', 'postscripts', 'remoteshell' );
plan skip_all => 'remoteshell not found' unless -r $script;
open( my $fh, '<', $script ) or die "Unable to read $script: $!";
my $source = do { local $/; <$fh> };
close($fh);
# A kill without the hyphen reads the signal number as one more process id, so
# the target gets the default signal, which a process can catch, and the
# process with that id is signalled as well.
my @bare = ( $source =~ /^[^#\n]*\bkill\s+\d/gm );
is( scalar(@bare), 0, 'no kill gives the signal number without a hyphen' );
like( $source, qr/kill -9 \$PIDLIST/,
'the ssh daemon gets the signal that a process cannot catch' );
done_testing();
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use FindBin;
use Test::More;
my $script = File::Spec->catfile( $FindBin::Bin, '..', '..',
'xCAT', 'postscripts', 'remoteshell' );
plan skip_all => 'remoteshell not found' unless -r $script;
plan skip_all => 'no bash' unless -x '/bin/bash';
open( my $fh, '<', $script ) or die "Unable to read $script: $!";
my $source = do { local $/; <$fh> };
close($fh);
my ($loop) = $source =~ /(waited=0\n.*?\n done)/s;
ok( defined($loop), 'the wait loop was found in the postscript' );
# Run the loop of the postscript against real processes, with a shorter bound
# so that the test does not spend the whole timeout of the postscript.
sub waits_for {
my (@pids) = @_;
( my $body = $loop ) =~ s/\$waited -lt 10/\$waited -lt 3/;
my $list = join( ' ', @pids );
my $out = `/bin/bash -c 'PIDLIST="$list"\n$body\necho \$alive' 2>/dev/null`;
chomp $out;
return $out;
}
# A process that is still running must be reported as alive, and the loop must
# give up rather than run for ever.
my $child = fork();
if ( !defined $child ) { plan skip_all => 'cannot fork' }
if ( $child == 0 ) { exec( 'sleep', '30' ); exit 1 }
my $start = time;
is( waits_for($child), '1', 'a process that is still running is reported alive' );
cmp_ok( time - $start, '<', 10, 'the loop gives up instead of waiting for ever' );
kill 'KILL', $child;
waitpid( $child, 0 );
# A process that has ended must be reported as gone, without waiting.
my $gone = fork();
if ( $gone == 0 ) { exit 0 }
waitpid( $gone, 0 );
$start = time;
is( waits_for($gone), '0', 'a process that has ended is reported gone' );
cmp_ok( time - $start, '<', 3, 'no time is spent once the process is gone' );
# The bound of the postscript itself, and the shape of the test.
like( $source, qr/while \[ \$waited -lt 10 \]/, 'the postscript waits at most ten seconds' );
like( $source, qr/kill -0 \$pid/, 'the check for a running process sends no signal' );
like( $source, qr/kill -9 \$PIDLIST/, 'the daemon still gets the signal it cannot catch' );
# The wait has to happen before the new daemon starts.
my ($block) = $source =~ /(PIDLIST=.*?\/usr\/sbin\/sshd)/s;
ok( defined($block), 'the fallback block was found' );
cmp_ok( index( $block, 'waited=0' ), '<', index( $block, '/usr/sbin/sshd' ),
'the wait comes before the new daemon starts' );
done_testing();
-20
View File
@@ -1,20 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $pre_sles_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/install/scripts/pre.sles" : '';
$pre_sles_path = "xCAT-server/share/xcat/install/scripts/pre.sles"
unless -f $pre_sles_path;
plan skip_all => "pre.sles not found" unless -f $pre_sles_path;
my $src = do { local $/; open my $fh, '<', $pre_sles_path or die $!; <$fh> };
like($src, qr/sub set_sles11_uefi_bootloader\b|set_sles11_uefi_bootloader\(\)/, 'SLES UEFI bootloader helper exists');
like($src, qr/install=\.\*sles11/, 'SLES 11 UEFI bootloader change is scoped to SLES 11 install media');
like($src, qr/<loader_type>elilo<\/loader_type>/, 'SLES 11 UEFI install selects elilo');
like($src, qr/<location>mbr<\/location>/, 'legacy MBR template value is replaced at install time');
like($src, qr/if \[ -d \/sys\/firmware\/efi \]; then\s+sed .*?set_sles11_uefi_bootloader/s, 'UEFI default partitioning applies bootloader helper');
done_testing();
-17
View File
@@ -1,17 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $script_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/netboot/add-on/statelite/add_ssh" : '';
$script_path = "xCAT-server/share/xcat/netboot/add-on/statelite/add_ssh"
unless -f $script_path;
plan skip_all => "add_ssh not found" unless -f $script_path;
my $script = do { local $/; open my $fh, '<', $script_path or die $!; <$fh> };
like($script, qr/MaxStartups 100:30:200/, 'add_ssh caps MaxStartups below systemd soft nofile limit');
unlike($script, qr/echo\s+"MaxStartups 1024"/, 'add_ssh does not force MaxStartups 1024');
done_testing();
+1 -15
View File
@@ -631,20 +631,6 @@ fi
#if the service restart with "service/systemctl" failed
#try to kill the process and start
if [ "$?" != "0" ];then
PIDLIST=`ps aux | grep -v grep | grep "/usr/sbin/sshd"|awk -F" " '{print $2}'|xargs`
if [ -n "$PIDLIST" ]; then
kill -9 $PIDLIST
waited=0
while [ $waited -lt 10 ]; do
alive=0
for pid in $PIDLIST; do
if kill -0 $pid 2>/dev/null; then alive=1; fi
done
if [ $alive -eq 0 ]; then break; fi
sleep 1
waited=`expr $waited + 1`
done
fi
/usr/sbin/sshd
xcat_restart_sshd_after_failed_service_restart
fi
kill -9 $CREDPID
+6 -61
View File
@@ -19,7 +19,12 @@
#
#####################################################
[ -f "/xcatpost/xcatlib.sh" ] && . /xcatpost/xcatlib.sh
if [ -f "/xcatpost/xcatlib.sh" ]; then
. /xcatpost/xcatlib.sh
else
str_dir_name=`dirname $0`
[ -f "$str_dir_name/xcatlib.sh" ] && . "$str_dir_name/xcatlib.sh"
fi
if [ -f /xcatpost/mypostscript.post ]; then
XCATDEBUGMODE=`grep 'XCATDEBUGMODE=' /xcatpost/mypostscript.post | cut -d= -f2 | tr -d \'\" | tr A-Z a-z`
@@ -83,66 +88,6 @@ echolog()
}
download_postscripts()
{
server=$1
if [ -z $server ]; then
return 1;
fi
# Do not override the parameter --installdir
if [ -z "$INSTALLDIR" ]; then
if [ -f /opt/xcat/xcatinfo ]; then
INSTALLDIR=`grep 'INSTALLDIR' /opt/xcat/xcatinfo |cut -d= -f2`
fi
if [ -z "$INSTALLDIR" ]; then
INSTALLDIR="/install"
fi
fi
echolog "debug" "trying to download postscripts from http://$server$INSTALLDIR/postscripts/"
max_retries=5
retry=0
rc=1 # this is a fail return
while [ 0 -eq 0 ]; do
if [ -e "$xcatpost" ]; then
rm -rf "$xcatpost"
fi
# These dispatcher scripts are not needed by the legacy netboot post
# path. Newer wget parses HTML-looking regex strings inside downloaded
# scripts and fails the whole recursive download on bogus URLs.
export LANG=C; wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent http://$server$INSTALLDIR/postscripts/ -P /$xcatpost 2> /tmp/wget.log
rc=$?
if [ $rc -eq 0 ]; then
# return from wget was 0 but some OS do not return errors, so we
# have additional checks for
# failed: Connection httpd not running
# 404: Not Found - if directory does not exist
grep -i -E "... failed: Connection refused.$" /tmp/wget.log
rc1=$?
grep -i -E "ERROR 404: Not Found.$" /tmp/wget.log
rc2=$?
# check to see no errors at all, grep returns 1
if [ $rc1 -eq 1 ] && [ $rc2 -eq 1 ]; then
echolog "debug" "postscripts are downloaded from $server successfully."
return 0
fi
fi
retry=$(($retry+1))
echolog "debug" "download_postscripts retry $retry"
if [ $retry -eq $max_retries ]; then
echolog "debug" "failed to download postscripts from http://$server$INSTALLDIR/postscripts/ after several retries."
break
fi
SLI=$(awk 'BEGIN{srand(); printf("%d\n",rand()*20)}')
sleep $SLI
done
return $rc
}
download_mypostscript()
{
server=$1
+101
View File
@@ -833,6 +833,107 @@ function msgutil {
msgutil_r "" "$@"
}
function xcat_download_postscripts {
local server="$1"
local install_dir="${2:-${INSTALLDIR:-/install}}"
local postroot="${3:-/$xcatpost}"
local log_file="${4:-/tmp/wget.log}"
export LANG=C
wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent "http://$server$install_dir/postscripts/" -P "$postroot" 2> "$log_file"
}
function download_postscripts {
server=$1
if [ -z $server ]; then
return 1;
fi
# Do not override the parameter --installdir
if [ -z "$INSTALLDIR" ]; then
if [ -f /opt/xcat/xcatinfo ]; then
INSTALLDIR=`grep 'INSTALLDIR' /opt/xcat/xcatinfo |cut -d= -f2`
fi
if [ -z "$INSTALLDIR" ]; then
INSTALLDIR="/install"
fi
fi
echolog "debug" "trying to download postscripts from http://$server$INSTALLDIR/postscripts/"
max_retries=5
retry=0
rc=1 # this is a fail return
while [ 0 -eq 0 ]; do
if [ -e "$xcatpost" ]; then
rm -rf "$xcatpost"
fi
xcat_download_postscripts "$server" "$INSTALLDIR" "/$xcatpost" "/tmp/wget.log"
rc=$?
if [ $rc -eq 0 ]; then
# return from wget was 0 but some OS do not return errors, so we
# have additional checks for
# failed: Connection httpd not running
# 404: Not Found - if directory does not exist
grep -i -E "... failed: Connection refused.$" /tmp/wget.log
rc1=$?
grep -i -E "ERROR 404: Not Found.$" /tmp/wget.log
rc2=$?
# check to see no errors at all, grep returns 1
if [ $rc1 -eq 1 ] && [ $rc2 -eq 1 ]; then
echolog "debug" "postscripts are downloaded from $server successfully."
return 0
fi
fi
retry=$(($retry+1))
echolog "debug" "download_postscripts retry $retry"
if [ $retry -eq $max_retries ]; then
echolog "debug" "failed to download postscripts from http://$server$INSTALLDIR/postscripts/ after several retries."
break
fi
SLI=$(awk 'BEGIN{srand(); printf("%d\n",rand()*20)}')
sleep $SLI
done
return $rc
}
function xcat_wait_for_processes_to_exit {
local pidlist="$1"
local max_wait="${2:-10}"
local waited=0
local alive
local pid
while [ $waited -lt $max_wait ]; do
alive=0
for pid in $pidlist; do
if kill -0 $pid 2>/dev/null; then
alive=1
fi
done
if [ $alive -eq 0 ]; then
return 0
fi
sleep 1
waited=`expr $waited + 1`
done
return 1
}
function xcat_restart_sshd_after_failed_service_restart {
local sshd_cmd="${1:-/usr/sbin/sshd}"
local PIDLIST
PIDLIST=`ps aux | grep -v grep | grep "/usr/sbin/sshd"|awk -F" " '{print $2}'|xargs`
if [ -n "$PIDLIST" ]; then
kill -9 $PIDLIST
xcat_wait_for_processes_to_exit "$PIDLIST"
fi
$sshd_cmd
}
function fetch_mypostscript {
local postroot
postroot=$1