mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-25 17:24:07 +00:00
Merge master into fix/ubuntu-genesis-native-sbuild
master changed the same three files this branch changes, so the pull request could not merge. Conflicts and how each was resolved: xCAT-genesis-builder/builddeb-genesis-base. Both sides teach the build root to cope with a package whose name moved between releases. master added optional_packages(), which keeps a package only where apt has a candidate, and called it for util-linux-extra. This branch added add_first_available(), which takes the first name apt carries and fails when it carries none, and calls it for bind9-dnsutils/dnsutils and util-linux-extra/util-linux, plus add_if_available() for tzdata-legacy. The branch covers master's case and two more, so its helpers are kept and optional_packages() goes with its only caller. Every other master change to this file, including the DHCP client fix, is preserved. xCAT-test/unit/genesis_payload_verification.t. master has four assertions this branch does not: two payloads missing an absolute path. Its version is kept. The branch replaced plan skip_all with a fail(), because skipping covers nothing when the file under test is the gate itself, and that change is applied to master's version. xCAT-test/unit/genesis_ubuntu_build_root.t. master's added assertions drive optional_packages() directly, which the resolved builder no longer has. This branch's version matches the implementation that survives, and it already dies rather than skipping when the builder is missing, so it is kept whole. prove -j4 -r xCAT-test/unit passes: 212 files, 5892 tests. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
@@ -1,711 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Update GSA Ubuntu Repositories or create a local repository
|
||||
#
|
||||
# Author: Leonardo Tonetto (tonetto@linux.vnet.ibm.com)
|
||||
# Revisor: Arif Ali (aali@ocf.co.uk)
|
||||
#
|
||||
#
|
||||
# Getting Started:
|
||||
# - Clone the xcat-core git repository under a directory named "xcat-core/src"
|
||||
# - make sure reprepro is installed on the build machine
|
||||
# - Run this script from the local git repository you just created.
|
||||
# ./build-ubunturepo -c BUILDALL=1
|
||||
|
||||
# Usage: attr=value attr=value ... ./build-ubunturepo { -c | -d }
|
||||
# PROMOTE=1 - if the attribute "PROMOTE" is specified, means an official dot release. This does not
|
||||
# actually build xcat, just uploads the most recent snap build to http://xcat.org/files/xcat/ .
|
||||
# If not specified, a snap build is assumed, which uploads to https://xcat.org/files/xcat/
|
||||
# PREGA=1 - use this option with PROMOTE=1 on a branch that already has a released dot release, but this
|
||||
# build is a GA candidate build, not to be released yet. This will result in the tarball
|
||||
# being uploaded to http://xcat.org/files/xcat/repos/apt
|
||||
# (but the tarball file name will be like a released tarball, not a snap build).
|
||||
# When you are ready to release this build, use PROMOTE=1 without PREGA
|
||||
# BUILDALL=1 - build all rpms, whether they changed or not. Should be used for snap builds that are in
|
||||
# prep for a release.
|
||||
# GPGSIGN=0 - Do not sign the repo in the end of the build. The repo will be signed by default
|
||||
#
|
||||
# LOCAL_KEY=1 Use local keys to sign repo instead of WGET from GSA. By default use GSA.
|
||||
#
|
||||
# GPG_HOME=<path> - Use the specified directory as GNUPGHOME for signing (no passphrase assumed).
|
||||
# Bypasses GSA download and LOCAL_KEY.
|
||||
#
|
||||
# SETUP=1 Setup environment for build. By default do not setup environment.
|
||||
#
|
||||
# LOG=<filename> - provide an LOG file option to redirect some output into log file
|
||||
#
|
||||
# DEST=<directory> - provide a directory to contains the build result
|
||||
#
|
||||
# Running builds in parallel on one host: the build lock is scoped to the source checkout (see
|
||||
# the "build-lock" block below), so two builds from DIFFERENT
|
||||
# checkouts -- e.g. the devel and stable Ubuntu CD lanes -- run
|
||||
# concurrently, while two builds of the SAME checkout still fail-fast
|
||||
# (they build in-place and would corrupt each other). For parallel
|
||||
# builds give each a separate checkout and a separate output tree
|
||||
# (a distinct DEST). GPG_HOME may be SHARED between parallel builds
|
||||
# -- it is used read-only for signing.
|
||||
#
|
||||
# For the dependency packages 1. All the xcat dependency deb packages should be uploaded to
|
||||
# "pokgsa/projects/x/xcat/build/ubuntu/xcat-dep/debs/" on GSA
|
||||
# 2. run ./build-ubunturepo -d
|
||||
#
|
||||
# 3. the built xcat-dep deb packages tarball can be found in "../../xcat-dep"
|
||||
# related to the path of this script
|
||||
############################
|
||||
printusage()
|
||||
{
|
||||
printf "Usage: %s {-c | -d} \n" $(basename $0) >&2
|
||||
echo " -c : Build the xcat-core packages and create the repo"
|
||||
echo " -d : Create the xcat-dep repo."
|
||||
}
|
||||
# For the purpose of getting the distribution name
|
||||
if [[ ! -f /etc/lsb-release ]]; then
|
||||
echo "ERROR: Could not find /etc/lsb-release, is this script executed on a Ubuntu machine?"
|
||||
exit 1
|
||||
fi
|
||||
. /etc/lsb-release
|
||||
|
||||
export HOME=/root
|
||||
|
||||
|
||||
# Process cmd line variable assignments, assigning each attr=val pair to a variable of same name
|
||||
for i in $*; do
|
||||
echo $i | grep '=' -q
|
||||
if [ $? != 0 ];then
|
||||
continue
|
||||
fi
|
||||
# upper case the variable name
|
||||
varstring=`echo "$i"|cut -d '=' -f 1|tr '[a-z]' '[A-Z]'`=`echo "$i"|cut -d '=' -f 2`
|
||||
export $varstring
|
||||
done
|
||||
|
||||
#Setup environment so the xcat-deps can be built on a FVT test machine
|
||||
if [ "$SETUP" = "1" ];then
|
||||
#Mount GSA
|
||||
POKGSA="/gsa/pokgsa"
|
||||
POKGSA2="/gsa/pokgsa-p2"
|
||||
POKGSAIBM="pokgsa.ibm.com"
|
||||
if [ ! -d $POKGSA ];then
|
||||
mkdir -p $POKGSA
|
||||
mount ${POKGSAIBM}:${POKGSA} ${POKGSA}
|
||||
fi
|
||||
if [ ! -d $POKGSA2 ];then
|
||||
mkdir -p $POKGSA2
|
||||
mount ${POKGSAIBM}:${POKGSA2} ${POKGSA2}
|
||||
fi
|
||||
|
||||
# Verify needed packages installed
|
||||
REPREPO="reprepro"
|
||||
DEVSCRIPTS="devscripts"
|
||||
DEBHELPER="debhelper"
|
||||
QUILT="quilt"
|
||||
|
||||
apt-get -y install $REPREPO $DEVSCRIPTS $DEBHELPER $QUILT
|
||||
|
||||
echo "Finished setup for xcat-dep build. Rerun this script with SETUP=0 LOCAL_KEY=1 flags"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check the necessary packages before starting the build
|
||||
declare -a packages=( "reprepro" "devscripts" "debhelper" "libsoap-lite-perl" "libdbi-perl" "quilt" "git")
|
||||
|
||||
for package in ${packages[@]}; do
|
||||
RC=`dpkg -l | grep $package >> /dev/null 2>&1; echo $?`
|
||||
if [[ ${RC} != 0 ]]; then
|
||||
echo "ERROR: Could not find $package, install using 'apt-get install $package' to continue"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Supported distributions. Set DISTS="jammy noble resolute" to limit local validation builds.
|
||||
dists="${DISTS:-saucy trusty utopic xenial bionic focal jammy noble resolute}"
|
||||
|
||||
# GPG key used to sign the apt repo (reprepro SignWith). Defaults to the historic
|
||||
# name. Override with GPG_KEY_ID=<keyid|email> (space-free, since it is passed via
|
||||
# the attr=value parser above), e.g. GPG_KEY_ID=xcat-build@xcat.org
|
||||
GPG_KEY_ID="${GPG_KEY_ID:-xCAT Automatic Signing Key}"
|
||||
|
||||
c_flag= # xcat-core (trunk-delvel) path
|
||||
d_flag= # xcat-dep (trunk) path
|
||||
r_flag= #genesis base rpm package path
|
||||
|
||||
while getopts 'cdr:' OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
c) c_flag=1
|
||||
;;
|
||||
d) d_flag=1
|
||||
;;
|
||||
r) r_flag=1
|
||||
genesis_rpm_path="$OPTARG"
|
||||
;;
|
||||
?) printusage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
if [ -z "$c_flag" -a -z "$d_flag" ];then
|
||||
printusage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ "$c_flag" -a "$d_flag" ];then
|
||||
printusage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ -z "$BUILDALL" ]; then
|
||||
BUILDALL=1
|
||||
fi
|
||||
|
||||
# Find where this script is located to set some build variables
|
||||
old_pwd=`pwd`
|
||||
cd `dirname $0`
|
||||
curdir=`pwd`
|
||||
|
||||
# Scope the build lock to THIS checkout. build-ubunturepo builds the packages in-place
|
||||
# in its own source tree (it rewrites debian/changelog and debian/control, drops
|
||||
# *.orig.tar.gz at the checkout root and runs dpkg-buildpackage inside the package
|
||||
# dirs), so the resource two builds actually contend for is the checkout -- not the
|
||||
# host. The historic single /var/lock/xcatbld.lock was host-global and fail-fast, so
|
||||
# two builds from *different* checkouts (e.g. the devel and stable Ubuntu CD lanes on
|
||||
# one build host) collided and the loser failed the pipeline even though they share
|
||||
# nothing. Key the lock on the checkout path instead: builds of the SAME checkout
|
||||
# still fail-fast (they would corrupt each other in-place), while builds of DISTINCT
|
||||
# checkouts get distinct locks and run in parallel. The lock file stays on the local
|
||||
# /var/lock (reliable flock; the checkout may live on NFS/virtiofs where flock is not)
|
||||
# and the source tree is left byte-pristine.
|
||||
#
|
||||
# NOTE: the two marked regions below are extracted verbatim and exercised by the unit
|
||||
# test xCAT-test/unit/build_ubunturepo_lock.t (which runs them with a chosen $curdir)
|
||||
# -- keep the markers, and keep each region self-contained.
|
||||
# BEGIN build-lock-id
|
||||
lock_id_for() { printf '%s' "$1" | md5sum | cut -c1-12; }
|
||||
LOCKFILE="/var/lock/xcatbld-$(lock_id_for "$curdir").lock"
|
||||
# END build-lock-id
|
||||
# BEGIN build-lock-acquire
|
||||
exec 8>"$LOCKFILE"
|
||||
if ! flock -n 8; then
|
||||
echo "ERROR: Can't get lock $LOCKFILE for checkout $curdir. Another build is already using this checkout. Exiting...."
|
||||
exit 1
|
||||
fi
|
||||
# END build-lock-acquire
|
||||
|
||||
# for the git case, query the current branch and set REL (changing master to devel if necessary)
|
||||
function setbranch {
|
||||
# Get the current branch name. safe.directory='*' so this still works when the
|
||||
# build runs as root against a repo owned by another user (otherwise git errors
|
||||
# with "dubious ownership", returns empty, and REL collapses to an unstable value).
|
||||
branch=`git -c safe.directory='*' rev-parse --abbrev-ref HEAD 2>/dev/null`
|
||||
if [ "$branch" = "master" ]; then
|
||||
REL="devel"
|
||||
elif [ "$branch" = "HEAD" ] || [ -z "$branch" ]; then
|
||||
# Special handling when in a 'detached HEAD' state
|
||||
branch=`git -c safe.directory='*' describe --abbrev=0 HEAD 2>/dev/null`
|
||||
[[ -n "$branch" ]] && REL=`echo $branch|cut -d. -f 1,2`
|
||||
else
|
||||
REL=$branch
|
||||
fi
|
||||
}
|
||||
|
||||
WGET_CMD="wget"
|
||||
if [ ! -z ${LOG} ]; then
|
||||
WGET_CMD="wget -o ${LOG}"
|
||||
fi
|
||||
|
||||
if [ "$GPGSIGN" = "0" ];then
|
||||
echo "GPGSIGN=$GPGSIGN specified, skip gnupg key downloading"
|
||||
elif [ -n "$GPG_HOME" ];then
|
||||
echo "GPG_HOME=$GPG_HOME specified, using provided GNUPGHOME"
|
||||
export GNUPGHOME="$GPG_HOME"
|
||||
else
|
||||
#sync the gpg key to the build machine local
|
||||
gsa_url=http://pokgsa.ibm.com/projects/x/xcat/build/linux
|
||||
mkdir -p $HOME/.gnupg
|
||||
for key_name in pubring.gpg secring.gpg trustdb.gpg; do
|
||||
if [ "$LOCAL_KEY" = "1" ];then
|
||||
# Keys are already in the local $HOME/.gnupg directory
|
||||
chmod 600 $HOME/.gnupg/$key_name
|
||||
else
|
||||
# Need to download keys from GSA
|
||||
if [ ! -f $HOME/.gnupg/$key_name ] || [ `wc -c $HOME/.gnupg/$key_name|cut -f 1 -d' '` == 0 ]; then
|
||||
rm -f $HOME/.gnupg/$key_name
|
||||
${WGET_CMD} -P $HOME/.gnupg $gsa_url/keys/$key_name
|
||||
chmod 600 $HOME/.gnupg/$key_name
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
REL=xcat-core
|
||||
if [ "$c_flag" ]
|
||||
then
|
||||
setbranch
|
||||
# Sanitize REL into a stable, filesystem-safe token: replace any character that
|
||||
# isn't [A-Za-z0-9._-] (e.g. the '/' in a branch like feat/ubuntu-e2e, which would
|
||||
# otherwise create nested dirs) with '-', and never let it be empty.
|
||||
REL=${REL//[^A-Za-z0-9._-]/-}
|
||||
[ -z "$REL" ] && REL="local"
|
||||
package_dir_name=debs$REL
|
||||
|
||||
#define the dep source code path, core build target path and dep build target path
|
||||
if [ -z "$DEST" ]; then
|
||||
local_core_repo_path="$curdir/../../xcat-core"
|
||||
PKGDIR="../../$package_dir_name"
|
||||
else
|
||||
local_core_repo_path="$DEST/$package_dir_name/xcat-core"
|
||||
PKGDIR="$DEST/$package_dir_name/$package_dir_name"
|
||||
fi
|
||||
if [ ! -d "$PKGDIR" ];then
|
||||
mkdir -p "$PKGDIR"
|
||||
fi
|
||||
|
||||
echo "#############################################################"
|
||||
echo "Building xcat-core on branch ($REL) to $local_core_repo_path"
|
||||
echo "#############################################################"
|
||||
if [ "$PROMOTE" != 1 ]; then
|
||||
code_change=0
|
||||
update_log=''
|
||||
|
||||
if [ -z "$GITUP" ];then
|
||||
update_log=../coregitup
|
||||
echo "git pull > $update_log"
|
||||
git pull > $update_log
|
||||
else
|
||||
update_log=$GITUP
|
||||
fi
|
||||
|
||||
if ! grep -q 'Already up-to-date' $update_log; then
|
||||
code_change=1
|
||||
fi
|
||||
ver=`cat Version`
|
||||
short_ver=`cat Version|cut -d. -f 1,2`
|
||||
short_short_ver=`cat Version|cut -d. -f 1`
|
||||
commit_id_long=`git rev-parse HEAD`
|
||||
commit_id="${commit_id_long:0:7}"
|
||||
if [ -f Gitepoch ]; then
|
||||
source_date_epoch=$(cat Gitepoch)
|
||||
else
|
||||
source_date_epoch=$(git log -1 --format=%ct HEAD 2>/dev/null || date +%s)
|
||||
fi
|
||||
export SOURCE_DATE_EPOCH="$source_date_epoch"
|
||||
export DEBEMAIL="xcat-build@xcat.org"
|
||||
export DEBFULLNAME="xCAT Build"
|
||||
build_time=$(date -d "@$source_date_epoch" --utc '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u)
|
||||
build_machine=`hostname`
|
||||
|
||||
if [ $code_change == 0 -a "$UP" != 1 -a "$BUILDALL" != 1 ]; then
|
||||
echo "Nothing new detected. Exiting...."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "###############################"
|
||||
echo "# Building xcat-core packages #"
|
||||
echo "###############################"
|
||||
|
||||
#the package type: local | snap | alpha
|
||||
#the build introduce string
|
||||
build_string="Snap_Build"
|
||||
if [ -f Release ]; then
|
||||
xcat_release=$(cat Release)
|
||||
else
|
||||
xcat_release="snap$(date -d "@$source_date_epoch" --utc '+%Y%m%d%H%M')"
|
||||
fi
|
||||
pkg_version="${ver}-${xcat_release}"
|
||||
|
||||
packages="xCAT-client xCAT-genesis-scripts perl-xCAT xCAT-server xCAT xCATsn xCAT-test xCAT-buildkit xCAT-vlan xCAT-confluent xCAT-probe"
|
||||
if [ -n "$PACKAGE" ]; then
|
||||
match=""
|
||||
for p in $packages; do
|
||||
p_low=$(echo "$p" | tr '[A-Z]' '[a-z]')
|
||||
pkg_low=$(echo "$PACKAGE" | tr '[A-Z]' '[a-z]')
|
||||
if [ "$p_low" = "$pkg_low" ]; then
|
||||
match="$p"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$match" ]; then
|
||||
echo "ERROR: Package '$PACKAGE' not found. Valid packages: $packages"
|
||||
exit 1
|
||||
fi
|
||||
packages="$match"
|
||||
fi
|
||||
target_archs=(amd64 ppc64el)
|
||||
for file in $packages
|
||||
do
|
||||
file_low=`echo $file | tr '[A-Z]' '[a-z]'`
|
||||
if [ "$file" = "xCAT" -o "$file" = "xCAT-genesis-scripts" -o "$file" = "xCATsn" ]; then
|
||||
target_archs="amd64 ppc64el"
|
||||
else
|
||||
target_archs="all"
|
||||
fi
|
||||
for target_arch in $target_archs
|
||||
do
|
||||
tar_orig="${file_low}_${ver}.orig.tar.gz"
|
||||
if grep -q "3.0 (quilt)" "${file}/debian/source/format" && [ ! -f "$tar_orig" ]; then
|
||||
tar czf "$tar_orig" --exclude debian -C "$file" .
|
||||
fi
|
||||
|
||||
if grep -q $file $update_log || [ "$BUILDALL" == 1 -o "$file" = "perl-xCAT" ]; then
|
||||
rm -f $PKGDIR/${file_low}_*.$target_arch.deb
|
||||
cd $file
|
||||
CURDIR=$(pwd)
|
||||
|
||||
find . -name '*.dch' -delete
|
||||
deterministic_date=$(date -R -d "@$SOURCE_DATE_EPOCH" --utc 2>/dev/null || date -R --utc)
|
||||
sed -i "1s/(.*)/(${pkg_version})/" debian/changelog
|
||||
sed -i "s/^ -- .*/ -- $DEBFULLNAME <$DEBEMAIL> $deterministic_date/" debian/changelog
|
||||
if [ "$target_arch" = "all" ]; then
|
||||
#xcat probe use some functions shipped by xCAT, for below reasons we need to copy files to xCAT-probe directory
|
||||
#1 make xcat probe code to be self-contained
|
||||
#2 don't maintain two files for each script
|
||||
#3 symbolic link can't work during package
|
||||
if [ $file_low = "xcat-probe" ]; then
|
||||
mkdir -p ${CURDIR}/lib/perl/xCAT/
|
||||
cp -f ${CURDIR}/../perl-xCAT/xCAT/CommandUtils.pm ${CURDIR}/lib/perl/xCAT/
|
||||
cp -f ${CURDIR}/../perl-xCAT/xCAT/NetworkUtils.pm ${CURDIR}/lib/perl/xCAT/
|
||||
cp -f ${CURDIR}/../perl-xCAT/xCAT/GlobalDef.pm ${CURDIR}/lib/perl/xCAT/
|
||||
cp -f ${CURDIR}/../perl-xCAT/xCAT/ServiceNodeUtils.pm ${CURDIR}/lib/perl/xCAT/
|
||||
fi
|
||||
CURDIR=$(pwd)
|
||||
cp ${CURDIR}/debian/control ${CURDIR}/debian/control.save.998
|
||||
# Magic string used here
|
||||
sed -i -e "s#>= 2.13-snap000000000000#= ${pkg_version}#g" ${CURDIR}/debian/control
|
||||
dpkg-buildpackage -rfakeroot -uc -us
|
||||
mv ${CURDIR}/debian/control.save.998 ${CURDIR}/debian/control
|
||||
else
|
||||
if [ "$file" = "xCAT-genesis-scripts" ]; then
|
||||
echo "Rename control file to build pkg: mv ${CURDIR}/debian/control-${target_arch} ${CURDIR}/debian/control"
|
||||
cp ${CURDIR}/debian/control-${target_arch} ${CURDIR}/debian/control
|
||||
elif [ "$file" = "xCAT" ]; then
|
||||
# shipping bmcsetup and getipmi scripts as part of postscripts
|
||||
files=("bmcsetup" "getipmi")
|
||||
for f in "${files[@]}"; do
|
||||
cp ${CURDIR}/../xCAT-genesis-scripts/usr/bin/$f ${CURDIR}/postscripts/$f
|
||||
sed -i "s/xcat.genesis.$f/$f/g" ${CURDIR}/postscripts/$f
|
||||
done
|
||||
fi
|
||||
CURDIR=$(pwd)
|
||||
cp ${CURDIR}/debian/control ${CURDIR}/debian/control.save.998
|
||||
# Magic string used here
|
||||
sed -i -e "s#>= 2.13-snap000000000000#= ${pkg_version}#g" ${CURDIR}/debian/control
|
||||
dpkg-buildpackage -rfakeroot -uc -us -a$target_arch
|
||||
mv ${CURDIR}/debian/control.save.998 ${CURDIR}/debian/control
|
||||
if [ "$file" = "xCAT-genesis-scripts" ]; then
|
||||
echo "Move control file back: mv ${CURDIR}/debian/control ${CURDIR}/debian/control-${target_arch}"
|
||||
rm ${CURDIR}/debian/control
|
||||
elif [ "$file" = "xCAT" ]; then
|
||||
files=("bmcsetup" "getipmi")
|
||||
for f in "${files[@]}"; do
|
||||
rm -f ${CURDIR}/postscripts/$f
|
||||
done
|
||||
fi
|
||||
fi
|
||||
rc=$?
|
||||
if [ $rc -gt 0 ]; then
|
||||
echo "Error: $file build package failed exit code $rc"
|
||||
exit $rc
|
||||
fi
|
||||
cd -
|
||||
find $file -maxdepth 3 -type d -name "${file_low}*" | grep debian | xargs rm -rf
|
||||
find $file -maxdepth 3 -type f -name "files" | grep debian | xargs rm -rf
|
||||
mv ${file_low}* $PKGDIR/
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
find $PKGDIR/* ! -name '*.deb' | xargs rm -f
|
||||
fi
|
||||
|
||||
if [ "$PROMOTE" = 1 ]; then
|
||||
upload_dir="xcat-core"
|
||||
tar_name="xcat-core-$ver.tar.bz2"
|
||||
else
|
||||
upload_dir="core-snap"
|
||||
tar_name="core-debs-snap.tar.bz2"
|
||||
fi
|
||||
|
||||
echo "#################################"
|
||||
echo "# Creating xcat-core repository #"
|
||||
echo "#################################"
|
||||
|
||||
#clean the repo directory
|
||||
if [ -e $local_core_repo_path ]; then
|
||||
rm -rf $local_core_repo_path
|
||||
fi
|
||||
mkdir -p $local_core_repo_path
|
||||
cd $local_core_repo_path
|
||||
mkdir conf
|
||||
|
||||
for dist in $dists; do
|
||||
# for all releases moving forward, support amd64 and ppc64el
|
||||
tmp_out_arch="amd64 ppc64el"
|
||||
if [ "$dist" = "saucy" ]; then
|
||||
# for older releases of Ubuntu that does not support ppc64el
|
||||
tmp_out_arch="amd64"
|
||||
fi
|
||||
cat << __EOF__ >> conf/distributions
|
||||
Origin: xCAT internal repository
|
||||
Label: xcat-core bazaar repository
|
||||
Codename: $dist
|
||||
Architectures: $tmp_out_arch
|
||||
Components: main
|
||||
Description: Repository automatically genereted conf
|
||||
__EOF__
|
||||
|
||||
if [ "$GPGSIGN" = "0" ];then
|
||||
#echo "GPGSIGN=$GPGSIGN specified, the repo will not be signed"
|
||||
echo "" >> conf/distributions
|
||||
else
|
||||
keyid=$(gpg --list-keys --keyid-format long "$GPG_KEY_ID" | grep '^pub' | sed -e 's/.*\///' -e 's/ .*//')
|
||||
echo "SignWith: $keyid" >> conf/distributions
|
||||
echo "" >> conf/distributions
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$GPG_HOME" ]; then
|
||||
cat << __EOF__ > conf/options
|
||||
verbose
|
||||
basedir .
|
||||
__EOF__
|
||||
else
|
||||
cat << __EOF__ > conf/options
|
||||
verbose
|
||||
ask-passphrase
|
||||
basedir .
|
||||
__EOF__
|
||||
fi
|
||||
|
||||
#import the deb packages into the repo
|
||||
amd_files=`ls ../$package_dir_name/*.deb | grep -v "ppc64el"`
|
||||
all_files=`ls ../$package_dir_name/*.deb`
|
||||
for dist in $dists; do
|
||||
deb_files=$all_files
|
||||
if [ "$dist" = "saucy" ]; then
|
||||
# for older releases of Ubuntu that does not support ppc64el
|
||||
deb_files=$amd_files
|
||||
fi
|
||||
for file in $deb_files; do
|
||||
reprepro -b ./ includedeb $dist $file;
|
||||
done
|
||||
done
|
||||
#create the mklocalrepo script
|
||||
cat << '__EOF__' > mklocalrepo.sh
|
||||
. /etc/lsb-release
|
||||
cd `dirname $0`
|
||||
host_arch=`uname -m`
|
||||
if [ "$host_arch" != "ppc64le" ];then
|
||||
host_arch="amd64"
|
||||
else
|
||||
host_arch="ppc64el"
|
||||
fi
|
||||
echo deb [arch=$host_arch] file://"`pwd`" $DISTRIB_CODENAME main > /etc/apt/sources.list.d/xcat-core.list
|
||||
__EOF__
|
||||
|
||||
chmod 775 mklocalrepo.sh
|
||||
|
||||
#
|
||||
# Add a buildinfo file into the tar.bz2 file to track information about the build
|
||||
#
|
||||
BUILDINFO=$local_core_repo_path/buildinfo
|
||||
echo "VERSION=$ver" > $BUILDINFO
|
||||
echo "RELEASE=$xcat_release" >> $BUILDINFO
|
||||
echo "BUILD_TIME=$build_time" >> $BUILDINFO
|
||||
echo "BUILD_MACHINE=$build_machine" >> $BUILDINFO
|
||||
echo "COMMIT_ID=$commit_id" >> $BUILDINFO
|
||||
echo "COMMIT_ID_LONG=$commit_id_long" >> $BUILDINFO
|
||||
|
||||
#create the xcat-core.list file
|
||||
|
||||
cd ../
|
||||
if ! grep xcat /etc/group ; then
|
||||
groupadd xcat
|
||||
fi
|
||||
|
||||
chgrp -R root xcat-core
|
||||
chmod -R g+w xcat-core
|
||||
|
||||
#build the tar ball
|
||||
echo "Creating `pwd`/$tar_name ..."
|
||||
tar -hjcf $tar_name xcat-core
|
||||
chgrp root $tar_name
|
||||
chmod g+w $tar_name
|
||||
|
||||
if [ -n "$DEST" ]; then
|
||||
ln -sf $(basename `pwd`)/$tar_name ../$tar_name
|
||||
if [ $? != 0 ]; then
|
||||
echo "ERROR: Failed to make symbol link $DEST/$tar_name"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -e core-snap ]; then
|
||||
ln -s xcat-core core-snap
|
||||
fi
|
||||
|
||||
cd $old_pwd
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$d_flag" ]
|
||||
then
|
||||
echo "################################"
|
||||
echo "# Creating xcat-dep repository #"
|
||||
echo "################################"
|
||||
|
||||
#the path of ubuntu xcat-dep deb packages on GSA
|
||||
GSA="/gsa/pokgsa/projects/x/xcat/build/ubuntu/xcat-dep"
|
||||
if [ ! -d $GSA ]; then
|
||||
echo "build-ubunturepo: It appears that you do not have GSA to access the xcat-dep pkgs."
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
#define the dep source code path, core build target path and dep build target path
|
||||
if [ -z "$DEST" ]; then
|
||||
local_dep_repo_path="$curdir/../../xcat-dep/xcat-dep"
|
||||
else
|
||||
local_dep_repo_path="$DEST/xcat-dep/xcat-dep"
|
||||
fi
|
||||
|
||||
# Sync from the GSA master copy of the dep rpms
|
||||
echo "Creating directory $local_dep_repo_path"
|
||||
mkdir -p $local_dep_repo_path/
|
||||
|
||||
echo "Syncing RPMs from $GSA/ to $local_dep_repo_path/../ ..."
|
||||
rsync -ilrtpu --delete $GSA/ $local_dep_repo_path/../
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error from rsync, cannot continue!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#clean all old files
|
||||
if [ -e $local_dep_repo_path ];then
|
||||
rm -rf $local_dep_repo_path
|
||||
fi
|
||||
mkdir -p $local_dep_repo_path
|
||||
cd $local_dep_repo_path
|
||||
mkdir conf
|
||||
|
||||
|
||||
#create the conf/distributions file
|
||||
for dist in $dists; do
|
||||
tmp_out_arch="amd64 ppc64el"
|
||||
if [ "$dist" = "saucy" ]; then
|
||||
# for older releases of Ubuntu that does not support ppc64el
|
||||
tmp_out_arch="amd64"
|
||||
fi
|
||||
cat << __EOF__ >> conf/distributions
|
||||
Origin: xCAT internal repository
|
||||
Label: xcat-dep bazaar repository
|
||||
Codename: $dist
|
||||
Architectures: $tmp_out_arch
|
||||
Components: main
|
||||
Description: Repository automatically genereted conf
|
||||
__EOF__
|
||||
|
||||
if [ "$GPGSIGN" = "0" ];then
|
||||
echo "GPGSIGN=$GPGSIGN specified, the repo will not be signed"
|
||||
echo "" >> conf/distributions
|
||||
else
|
||||
keyid=$(gpg --list-keys --keyid-format long "$GPG_KEY_ID" | grep '^pub' | sed -e 's/.*\///' -e 's/ .*//')
|
||||
echo "SignWith: $keyid" >> conf/distributions
|
||||
echo "" >> conf/distributions
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
|
||||
|
||||
if [ -n "$GPG_HOME" ]; then
|
||||
cat << __EOF__ > conf/options
|
||||
verbose
|
||||
basedir .
|
||||
__EOF__
|
||||
else
|
||||
cat << __EOF__ > conf/options
|
||||
verbose
|
||||
ask-passphrase
|
||||
basedir .
|
||||
__EOF__
|
||||
fi
|
||||
|
||||
#import the deb packages into the repo
|
||||
amd_files=`ls ../debs/*.deb | grep -v "ppc64el"`
|
||||
all_files=`ls ../debs/*.deb`
|
||||
for dist in $dists; do
|
||||
deb_files=$all_files
|
||||
if [ "$dist" = "saucy" ]; then
|
||||
# for older releases of Ubuntu that does not support ppc64el
|
||||
deb_files=$amd_files
|
||||
fi
|
||||
for file in $deb_files; do
|
||||
reprepro -b ./ includedeb $dist $file;
|
||||
done
|
||||
done
|
||||
|
||||
cat << '__EOF__' > mklocalrepo.sh
|
||||
. /etc/lsb-release
|
||||
cd `dirname $0`
|
||||
host_arch=`uname -m`
|
||||
if [ "$host_arch" != "ppc64le" ];then
|
||||
host_arch="amd64"
|
||||
else
|
||||
host_arch="ppc64el"
|
||||
fi
|
||||
echo deb [arch=$host_arch] file://"`pwd`" $DISTRIB_CODENAME main > /etc/apt/sources.list.d/xcat-dep.list
|
||||
__EOF__
|
||||
|
||||
chmod 775 mklocalrepo.sh
|
||||
|
||||
cd ..
|
||||
if ! grep xcat /etc/group ; then
|
||||
groupadd xcat
|
||||
fi
|
||||
|
||||
chgrp -R root xcat-dep
|
||||
chmod -R g+w xcat-dep
|
||||
|
||||
#create the tar ball
|
||||
dep_tar_name=xcat-dep-ubuntu-`date +%Y%m%d%H%M`.tar.bz2
|
||||
tar -hjcf $dep_tar_name xcat-dep
|
||||
chgrp root $dep_tar_name
|
||||
chmod g+w $dep_tar_name
|
||||
|
||||
|
||||
USER="xcat"
|
||||
SERVER="xcat.org"
|
||||
FILES_PATH="files"
|
||||
FRS="/var/www/${SERVER}/${FILES_PATH}"
|
||||
APT_DIR="${FRS}/xcat"
|
||||
APT_REPO_DIR="${APT_DIR}/repos/apt/devel"
|
||||
|
||||
# Decide whether to upload the xcat-dep package or NOT (default is to NOT upload xcat-dep
|
||||
if [ "$UP" != "1" ]; then
|
||||
echo "Upload not specified, Done! (rerun with UP=1, to upload)"
|
||||
cd $old_pwd
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#upload the dep packages
|
||||
i=0
|
||||
echo "Uploading debs from xcat-dep to ${APT_REPO_DIR}/xcat-dep/ ..."
|
||||
while [ $((i+=1)) -le 5 ] && ! rsync -urLv --delete xcat-dep $USER@${SERVER}:${APT_REPO_DIR}/
|
||||
do : ; done
|
||||
|
||||
#upload the tarball
|
||||
i=0
|
||||
echo "Uploading $dep_tar_name to ${APT_DIR}/xcat-dep/2.x_Ubuntu/ ..."
|
||||
while [ $((i+=1)) -le 5 ] && ! rsync -v --force $dep_tar_name $USER@${SERVER}:${APT_DIR}/xcat-dep/2.x_Ubuntu/
|
||||
do : ; done
|
||||
|
||||
#upload the README file
|
||||
cd debs
|
||||
i=0
|
||||
echo "Uploading README to ${APT_DIR}/xcat-dep/2.x_Ubuntu/ ..."
|
||||
while [ $((i+=1)) -le 5 ] && ! rsync -v --force README $USER@${SERVER}:${APT_DIR}/xcat-dep/2.x_Ubuntu/
|
||||
do : ; done
|
||||
|
||||
fi
|
||||
|
||||
cd $old_pwd
|
||||
exit 0
|
||||
+6
-3
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/perl
|
||||
# Build the xcat-core Debian packages and assemble a signed apt repository.
|
||||
#
|
||||
# Replaces build-ubunturepo. The shape mirrors buildrpms.pl -- Getopt::Long options,
|
||||
# Builds every xCAT deb and the apt repository. The shape mirrors buildrpms.pl -- Getopt::Long options,
|
||||
# one package list, build then index then sign -- so the two builders read the same way
|
||||
# and share XCAT::BuildUtils.
|
||||
#
|
||||
@@ -117,9 +117,12 @@ my $VERSION = read_line("$ROOT/Version") // die "Cannot read $ROOT/Version\n";
|
||||
my $EPOCH = source_date_epoch();
|
||||
# A Release file, when present, is authoritative: buildrpms.pl writes one, and a
|
||||
# pipeline that builds both must stamp the rpms and the debs with the same release.
|
||||
# The tracked file holds snap000000000000, which no build writes -- snap_release()
|
||||
# renders a real time. A tree where buildrpms.pl has not run still carries it, so
|
||||
# treat the placeholder as an unstamped tree and derive the release from the commit.
|
||||
my $FILE_RELEASE = do {
|
||||
my $r = read_line("$ROOT/Release");
|
||||
($r && $r =~ /\S/) ? $r : undef;
|
||||
($r && $r =~ /\S/ && $r !~ /\Asnap0+\z/) ? $r : undef;
|
||||
};
|
||||
my $RELEASE = $opts{release} || $FILE_RELEASE || snap_release($EPOCH);
|
||||
my $PKGVER = deb_version($VERSION, $RELEASE);
|
||||
@@ -554,7 +557,7 @@ xcat-dep's C<sbuild-all.pl> creates on the Ubuntu build host. The build refuses
|
||||
in a root of another release, and it reads its own log: dracut reports a command it
|
||||
cannot install with a C<FAILED:> line and still exits 0.
|
||||
|
||||
Replaces C<build-ubunturepo>. The GSA upload paths, the C<PROMOTE>/C<PREGA> release
|
||||
Replaced C<build-ubunturepo>, removed in 2.19. The GSA upload paths, the C<PROMOTE>/C<PREGA> release
|
||||
flows and the C<-d> xcat-dep repository mode were not carried over: publishing is done
|
||||
by the CD pipeline's own deploy step, and xcat-dep is built from its own repository.
|
||||
|
||||
|
||||
@@ -346,6 +346,9 @@ sub buildsources_genesis_base($) {
|
||||
"Error copying dracut_105 sources");
|
||||
cp "xCAT-genesis-builder/80-net-name-slot.rules",
|
||||
"$staging_root/80-net-name-slot.rules";
|
||||
# %install runs this against the extracted payload before it becomes an rpm.
|
||||
cp "xCAT-genesis-builder/verify-genesis-payload",
|
||||
"$staging_root/verify-genesis-payload";
|
||||
|
||||
unlink $support_tarball if -f $support_tarball;
|
||||
sh_or_die(qq(tar --sort=name --owner=0 --group=0 --mtime="\@$SOURCE_DATE_EPOCH" -cjf "$support_tarball" -C "$staging_parent" xCAT-genesis-base-build-support),
|
||||
|
||||
@@ -36,10 +36,8 @@ emitted, because a source-only run has no binary packages to advertise.
|
||||
``buildrpms.pl`` replaces all three, and its ``--source-only`` replaces the
|
||||
old ``SRCONLY=1``.
|
||||
|
||||
``build-ubunturepo`` is superseded by ``builddebs.pl`` but is **still in the
|
||||
tree for now**, as a differential oracle: it is the reference the new builder
|
||||
is checked against, and it is removed once the CD pipelines have been moved
|
||||
over. Do not add features to it.
|
||||
``build-ubunturepo`` was removed in 2.19. ``builddebs.pl`` replaces it, and the
|
||||
CD pipelines build every Ubuntu target with it.
|
||||
|
||||
Debian and Ubuntu packages
|
||||
--------------------------
|
||||
|
||||
@@ -64,7 +64,7 @@ Remove xCAT Files
|
||||
|
||||
[Ubuntu] ::
|
||||
|
||||
apt-get remove conserver-xcat elilo-xcat goconserver grub2-xcat ipmitool-xcat perl-xcat syslinux-xcat xcat xcat-buildkit xcat-client xcat-confluent xcat-genesis-base-amd64 xcat-genesis-base-ppc64 xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64 xcat-probe xcat-server xcat-test xcat-vlan xcatsn xnba-undi
|
||||
apt-get remove conserver-xcat elilo-xcat goconserver grub2-xcat ipmitool-xcat perl-xcat syslinux-xcat xcat xcat-buildkit xcat-client xcat-confluent xcat-genesis-base-amd64 xcat-genesis-base-ppc64el xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64el xcat-probe xcat-server xcat-test xcat-vlan xcatsn xnba-undi
|
||||
|
||||
To do an even more thorough cleanup, use links below to get a list of RPMs installed by xCAT. Some RPMs may not to be installed in a specific environment.
|
||||
|
||||
|
||||
@@ -32,13 +32,9 @@ my $GITHUB_API = "https://api.github.com";
|
||||
# through FindBin, so they can only be run from a source tree. Take a copy
|
||||
# before building and run the unit tests out of the copy.
|
||||
#
|
||||
# This used to be mandatory rather than tidy: build-ubunturepo set
|
||||
# local_core_repo_path="$curdir/../../xcat-core"
|
||||
# which, under the work/<repo>/<repo> layout GitHub checks out into, resolved to
|
||||
# the checkout's own parent, and it rm -rf'd that path to make room for the apt
|
||||
# repository -- destroying the tree the tests need. builddebs.pl writes under
|
||||
# dist/debs INSIDE the checkout and restores every file it edits, so the copy is
|
||||
# now only isolating the tests from build residue.
|
||||
# The copy is tidiness, not a requirement: builddebs.pl writes under dist/debs
|
||||
# inside the checkout and restores every file it edits, so the copy only keeps
|
||||
# build residue away from the tests.
|
||||
my $srcdir = getcwd();
|
||||
my $unitsrc = ($ENV{'RUNNER_TEMP'} ? $ENV{'RUNNER_TEMP'} : "/tmp") . "/xcat-core-unitsrc";
|
||||
|
||||
|
||||
@@ -66,6 +66,13 @@ sub normalize_algorithm {
|
||||
return;
|
||||
}
|
||||
|
||||
sub algorithm_rr_type {
|
||||
my ( $class, $algorithm ) = @_;
|
||||
|
||||
$algorithm = $class->normalize_algorithm($algorithm) or return;
|
||||
return $ALGORITHMS{$algorithm};
|
||||
}
|
||||
|
||||
sub new_install_default_algorithm {
|
||||
my ( $class, %args ) = @_;
|
||||
|
||||
|
||||
+66
-22
@@ -3909,36 +3909,80 @@ sub fullpathbin
|
||||
#--------------------------------------------------------------------------------
|
||||
sub gettimezone
|
||||
{
|
||||
my ($class) = @_;
|
||||
my ($class, %opt) = @_;
|
||||
|
||||
my $tz;
|
||||
if (xCAT::Utils->isAIX()) {
|
||||
$tz = $ENV{'TZ'};
|
||||
} else { # all linux
|
||||
my $localtime = "/etc/localtime";
|
||||
my $zoneinfo = "/usr/share/zoneinfo";
|
||||
return $ENV{'TZ'};
|
||||
}
|
||||
|
||||
# all linux. %opt{root} prefixes every path, so a test drives this against a scratch tree.
|
||||
my $root = defined($opt{root}) ? $opt{root} : '';
|
||||
my $localtime = "$root/etc/localtime";
|
||||
my $zoneinfo = "$root/usr/share/zoneinfo";
|
||||
|
||||
# On every current distribution /etc/localtime is a symlink into the zoneinfo tree and its
|
||||
# target is the name. Read it before the scan: the scan compares /etc/localtime against every
|
||||
# file in the tree, and a cloud image running on UTC may ship no /etc/localtime at all.
|
||||
if (-l $localtime) {
|
||||
my $zone = _zone_from_path(readlink($localtime));
|
||||
return $zone if defined $zone;
|
||||
}
|
||||
|
||||
if (-e $localtime) {
|
||||
my $cmd = "find $zoneinfo -xtype f -exec cmp -s $localtime {} \\; -print | grep -v posix | grep -v SystemV | grep -v right | grep -v localtime ";
|
||||
my $zone_result = xCAT::Utils->runcmd("$cmd", 0);
|
||||
if ($::RUNCMD_RC != 0)
|
||||
{
|
||||
$tz = "Could not determine timezone checksum";
|
||||
return $tz;
|
||||
if ($::RUNCMD_RC == 0) {
|
||||
my @zones = split /\n/, $zone_result;
|
||||
my $zone = _zone_from_path($zones[0]);
|
||||
return $zone if defined $zone;
|
||||
}
|
||||
my @zones = split /\n/, $zone_result;
|
||||
|
||||
$zones[0] =~ s/$zoneinfo\///;
|
||||
if (!$zones[0]) { # if we still did not get one, then default
|
||||
$tz = `cat /etc/timezone`;
|
||||
chomp $tz;
|
||||
} else {
|
||||
$tz = $zones[0];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return $tz;
|
||||
|
||||
if (open(my $tz_fh, '<', "$root/etc/timezone")) {
|
||||
my $zone = <$tz_fh>;
|
||||
close($tz_fh);
|
||||
if (defined $zone) {
|
||||
chomp $zone;
|
||||
return $zone if length($zone) and $zone !~ /\s/;
|
||||
}
|
||||
}
|
||||
|
||||
# The caller writes this value into a kickstart or an autoyast profile, where it must be one
|
||||
# token. Name the zone the host is actually on rather than a sentence that stops the installer.
|
||||
return 'UTC';
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
=head3 _zone_from_path
|
||||
Returns the timezone name inside a path under a zoneinfo tree, for a symlink target or a
|
||||
line of the zoneinfo scan. Both absolute and relative targets carry "/zoneinfo/", so the
|
||||
name is whatever follows it.
|
||||
Arguments:
|
||||
A path, or undef
|
||||
Returns:
|
||||
The timezone name, or undef when the path names no zone
|
||||
Globals:
|
||||
none
|
||||
Error:
|
||||
None
|
||||
Example:
|
||||
my $zone = _zone_from_path("../usr/share/zoneinfo/America/Sao_Paulo");
|
||||
Comments:
|
||||
none
|
||||
=cut
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
sub _zone_from_path
|
||||
{
|
||||
my ($path) = @_;
|
||||
|
||||
return undef unless defined($path) and length($path);
|
||||
return undef unless $path =~ m{(?:^|/)zoneinfo/(.+)\z};
|
||||
my $zone = $1;
|
||||
$zone =~ s{^posix/}{};
|
||||
return undef if $zone eq '' or $zone eq 'localtime' or $zone =~ /\s/;
|
||||
return $zone;
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
@@ -30,17 +30,40 @@ alien -d -g -c -k "${RPM_PACKAGE}" || exit 1
|
||||
PACKAGE_ARCH="${EXTRACT_DIR%-*}"
|
||||
PACKAGE_ARCH="${PACKAGE_ARCH##*-}"
|
||||
|
||||
if [[ ${EXTRACT_DIR} =~ -x86_64- ]]
|
||||
then
|
||||
rm -rf "${EXTRACT_DIR//x86_64/amd64}"
|
||||
mv "${EXTRACT_DIR}" "${EXTRACT_DIR//x86_64/amd64}"
|
||||
EXTRACT_DIR="${EXTRACT_DIR//x86_64/amd64}"
|
||||
# The rpm carries the Genesis target architecture, the deb must carry the Debian architecture.
|
||||
# alien copies the rpm name into the deb and writes "_" as "-", so x86_64 arrives as x86-64.
|
||||
case "${PACKAGE_ARCH}" in
|
||||
x86_64)
|
||||
ALIEN_ARCH="x86-64" ; DEB_ARCH="amd64" ;;
|
||||
ppc64|ppc64le)
|
||||
ALIEN_ARCH="${PACKAGE_ARCH}" ; DEB_ARCH="ppc64el" ;;
|
||||
*)
|
||||
ALIEN_ARCH="${PACKAGE_ARCH}" ; DEB_ARCH="${PACKAGE_ARCH}" ;;
|
||||
esac
|
||||
|
||||
sed -i -e 's/x86-64/amd64/g' "${EXTRACT_DIR}/debian/control"
|
||||
sed -i -e 's/x86-64/amd64/g' "${EXTRACT_DIR}/debian/changelog"
|
||||
if [[ "${DEB_ARCH}" != "${PACKAGE_ARCH}" ]]
|
||||
then
|
||||
rm -rf "${EXTRACT_DIR//${PACKAGE_ARCH}/${DEB_ARCH}}"
|
||||
mv "${EXTRACT_DIR}" "${EXTRACT_DIR//${PACKAGE_ARCH}/${DEB_ARCH}}"
|
||||
EXTRACT_DIR="${EXTRACT_DIR//${PACKAGE_ARCH}/${DEB_ARCH}}"
|
||||
|
||||
sed -i -e "s/${ALIEN_ARCH}/${DEB_ARCH}/g" "${EXTRACT_DIR}/debian/control"
|
||||
sed -i -e "s/${ALIEN_ARCH}/${DEB_ARCH}/g" "${EXTRACT_DIR}/debian/changelog"
|
||||
fi
|
||||
|
||||
sed -i -e "/^Description:/i Breaks: xcat-genesis-scripts-${PACKAGE_ARCH//x86_64/amd64} (<< 2.13.10)" "${EXTRACT_DIR}/debian/control"
|
||||
# The Genesis debs carry the architecture in the package name, so the packages an upgrade has
|
||||
# to displace carry it too. 2.19 renames the ppc64 debs to ppc64el; dpkg keeps the old package,
|
||||
# and its copy of the same files, unless the new one replaces it by name.
|
||||
case "${DEB_ARCH}" in
|
||||
ppc64el)
|
||||
SUPERSEDED="xcat-genesis-ppc64, xcat-genesis-base-ppc64" ;;
|
||||
*)
|
||||
SUPERSEDED="xcat-genesis-${DEB_ARCH}" ;;
|
||||
esac
|
||||
|
||||
sed -i -e "/^Description:/i Replaces: ${SUPERSEDED}" \
|
||||
-e "/^Description:/i Breaks: ${SUPERSEDED}, xcat-genesis-scripts-${DEB_ARCH} (<< 2.13.10)" \
|
||||
"${EXTRACT_DIR}/debian/control"
|
||||
|
||||
cat >"${EXTRACT_DIR}/debian/preinst" <<EOF
|
||||
#!/bin/bash
|
||||
|
||||
@@ -48,13 +48,50 @@ install() {
|
||||
dracut_install mount.nfs sshd vi reboot lspci parted tmux mkfs mkfs.ext4 mkfs.xfs xfs_db
|
||||
#dracut_install libvirtd /usr/share/libvirt/cpu_map.xml /usr/bin/qemu-img /usr/libexec/qemu-kvm
|
||||
dracut_install mkswap df ifenslave ssh-keygen scp clear
|
||||
dracut_install dhclient lldpad
|
||||
# getdestiny makes its request file with mktemp.
|
||||
dracut_install mktemp
|
||||
dracut_install lldpad
|
||||
|
||||
# RHEL 10 packages no ISC dhcp-client. Install whichever client the build root carries;
|
||||
# doxcat chooses between them at run time.
|
||||
if command -v dhclient >/dev/null 2>&1; then
|
||||
dracut_install dhclient
|
||||
elif command -v dhcpcd >/dev/null 2>&1; then
|
||||
dracut_install dhcpcd
|
||||
# dhcpcd runs these on every lease. They write resolv.conf, the hostname and
|
||||
# ntp.conf, which is the work dhclient-script does for the ISC client.
|
||||
dracut_install /usr/libexec/dhcpcd-run-hooks
|
||||
for _dhcpcd_hook in /usr/libexec/dhcpcd-hooks/*; do
|
||||
_dracut_install_opt "$_dhcpcd_hook"
|
||||
done
|
||||
_dracut_install_opt /etc/dhcpcd.conf
|
||||
fi
|
||||
|
||||
# OpenSSH 9.8 moved the per-connection work into sshd-session, which sshd execs by
|
||||
# absolute path.
|
||||
for _sshd_helper in \
|
||||
/usr/libexec/openssh/sshd-session \
|
||||
/usr/libexec/openssh/sshd-auth \
|
||||
/usr/lib/openssh/sshd-session \
|
||||
/usr/lib/openssh/sshd-auth
|
||||
do
|
||||
_dracut_install_opt "$_sshd_helper"
|
||||
done
|
||||
|
||||
# tmux exits under the C locale, and the image carries no locale data of its own.
|
||||
for _lc_file in /usr/lib/locale/C.utf8/LC_*; do
|
||||
_dracut_install_opt "$_lc_file"
|
||||
done
|
||||
dracut_install /lib64/libnss_dns.so.2
|
||||
dracut_install poweroff hwclock date /usr/share/terminfo/x/xterm /usr/share/terminfo/s/screen /etc/nsswitch.conf /etc/services
|
||||
dracut_install /sbin/rsyslogd /etc/protocols umount /bin/rpm /usr/lib/rpm/rpmrc
|
||||
#dracut_install chmod /sbin/route /sbin/ifconfig /usr/bin/whoami /usr/bin/head /usr/bin/tail basename /etc/redhat-release ping tr lsusb /usr/share/hwdata/usb.ids #ibm fw wrapper requirements
|
||||
dracut_install chmod ip /usr/bin/whoami /usr/bin/head /usr/bin/tail basename /etc/redhat-release ping tr lsusb /usr/share/hwdata/usb.ids #ibm fw wrapper requirements
|
||||
dracut_install efibootmgr dmidecode #uxspi prereqs, but will use dmidecode to improve decision on loading ipmi_si
|
||||
# uxspi prereqs. dmidecode also improves the decision on loading ipmi_si. Neither is
|
||||
# packaged for ppc64le, so install whichever the build root carries.
|
||||
for _fw_tool in efibootmgr dmidecode; do
|
||||
command -v "$_fw_tool" >/dev/null 2>&1 && dracut_install "$_fw_tool"
|
||||
done
|
||||
dracut_install lldptool
|
||||
dracut_install /usr/share/zoneinfo/posix/Zulu
|
||||
dracut_install /usr/share/zoneinfo/posix/GMT-0
|
||||
|
||||
@@ -2,13 +2,29 @@
|
||||
root=1
|
||||
rootok=1
|
||||
netroot=xcat
|
||||
|
||||
# The image ships the C.UTF-8 locale only. tmux refuses to start under the C locale.
|
||||
export LC_ALL=C.UTF-8
|
||||
|
||||
# tmux exits when the image carries no UTF-8 locale. doxcat is the whole of Genesis, so it
|
||||
# must run whether or not the multiplexer starts. Prints tmux or direct.
|
||||
xcat_console_mode() {
|
||||
if tmux -f /dev/null new-session -d -s xcatprobe true >/dev/null 2>&1; then
|
||||
tmux kill-session -t xcatprobe >/dev/null 2>&1
|
||||
echo tmux
|
||||
else
|
||||
echo direct
|
||||
fi
|
||||
}
|
||||
clear
|
||||
echo PS1="'"'[xCAT Genesis running on \H \w]\$ '"'" > /.bashrc
|
||||
echo PS1="'"'[xCAT Genesis running on \H \w]\$ '"'" > /.bash_profile
|
||||
mkdir -p /etc/ssh
|
||||
mkdir -p /var/tmp/
|
||||
mkdir -p /var/empty/sshd
|
||||
sed -i '/^root:x/d' /etc/passwd
|
||||
# dracut writes this entry itself, with an empty password field unless the image is
|
||||
# built --hostonly. Match the user name only.
|
||||
sed -i '/^root:/d' /etc/passwd
|
||||
cat >>/etc/passwd <<"__ENDL"
|
||||
root:x:0:0::/:/bin/bash
|
||||
sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin
|
||||
@@ -39,10 +55,13 @@ mkdir -p /var/lib/dhclient/
|
||||
mkdir -p /var/log
|
||||
ip link set lo up
|
||||
echo '127.0.0.1 localhost' >> /etc/hosts
|
||||
if grep -q console=ttyS /proc/cmdline; then
|
||||
XCAT_CONSOLE_MODE="$(xcat_console_mode)"
|
||||
if [ "$XCAT_CONSOLE_MODE" = "tmux" ]; then
|
||||
if grep -q console=ttyS /proc/cmdline; then
|
||||
while :; do sleep 1; tmux attach-session -t doxcat </dev/tty1 &>/dev/tty1; clear &>/dev/tty1 ; done &
|
||||
fi
|
||||
while :; do tmux new-session < /dev/tty2 &> /dev/tty2 ; done &
|
||||
fi
|
||||
while :; do tmux new-session < /dev/tty2 &> /dev/tty2 ; done &
|
||||
|
||||
# The section below is just for System P LE hardware discovery
|
||||
|
||||
@@ -87,4 +106,8 @@ elif [[ ${ARCH} =~ x86_64 ]]; then
|
||||
done
|
||||
fi
|
||||
|
||||
while :; do tmux attach-session -t doxcat || tmux new-session -s doxcat doxcat; done
|
||||
if [ "$XCAT_CONSOLE_MODE" = "tmux" ]; then
|
||||
while :; do tmux attach-session -t doxcat || tmux new-session -s doxcat doxcat; done
|
||||
else
|
||||
while :; do doxcat; sleep 5; done
|
||||
fi
|
||||
|
||||
@@ -12,6 +12,14 @@ Release: %{?release:%{release}}%{!?release:%(cat Release)}
|
||||
%ifarch aarch64
|
||||
%define tarch aarch64
|
||||
%endif
|
||||
%ifarch riscv64
|
||||
%define tarch riscv64
|
||||
%endif
|
||||
# An arch missing from the ladder above leaves %{tarch} unexpanded, and rpm then builds a package
|
||||
# with a macro in its NAME instead of failing. Stop the build here instead.
|
||||
%if ! %{defined tarch}
|
||||
%{error:no genesis tarch for %{_target_cpu} -- add an %%ifarch branch above}
|
||||
%endif
|
||||
BuildArch: noarch
|
||||
%define name xCAT-genesis-base-%{tarch}
|
||||
%define __spec_install_post :
|
||||
@@ -38,13 +46,22 @@ BuildRequires: chrony
|
||||
BuildRequires: cpio
|
||||
BuildRequires: e2fsprogs
|
||||
BuildRequires: hostname
|
||||
%if "%{_target_cpu}" == "x86_64"
|
||||
%if "%{tarch}" == "x86_64"
|
||||
BuildRequires: dmidecode
|
||||
BuildRequires: efibootmgr
|
||||
%endif
|
||||
BuildRequires: dosfstools
|
||||
BuildRequires: dracut
|
||||
BuildRequires: dracut-network
|
||||
# doxcat chooses its DHCP client at run time. RHEL 10 packages no ISC dhcp-client; its
|
||||
# baseos packages dhcpcd, which carries its own resolv.conf, hostname and ntp hooks and so
|
||||
# needs no dhclient-script.
|
||||
%if 0%{?rhel} && 0%{?rhel} < 10
|
||||
BuildRequires: dhcp-client
|
||||
%endif
|
||||
%if 0%{?rhel} >= 10
|
||||
BuildRequires: dhcpcd
|
||||
%endif
|
||||
BuildRequires: ethtool
|
||||
BuildRequires: gawk
|
||||
BuildRequires: ipmitool
|
||||
@@ -62,6 +79,9 @@ BuildRequires: nfs-utils
|
||||
BuildRequires: nmap-ncat
|
||||
BuildRequires: openssh-clients
|
||||
BuildRequires: openssh-server
|
||||
# getcert, getdestiny, getipmi and getadapter run the openssl command. el8 and el9 hold it in
|
||||
# the build root as a dependency of another package; el10 does not.
|
||||
BuildRequires: openssl
|
||||
BuildRequires: parted
|
||||
BuildRequires: pciutils
|
||||
BuildRequires: perl
|
||||
@@ -117,9 +137,6 @@ rm -rf "$DRACUTMODDIR"
|
||||
mkdir -p "$DRACUTMODDIR"
|
||||
cp -a "%{_builddir}/xCAT-genesis-base-build-support/dracut_105/el/." "$DRACUTMODDIR/"
|
||||
chmod 0755 "$DRACUTMODDIR/module-setup.sh" "$DRACUTMODDIR/xcatroot" "$DRACUTMODDIR/dhclient-script"
|
||||
if [ "%{_target_cpu}" != "x86_64" ]; then
|
||||
sed -i '/efibootmgr dmidecode/d' "$DRACUTMODDIR/module-setup.sh"
|
||||
fi
|
||||
|
||||
KERNELVERSION=$(ls -1 /lib/modules | sort -V | tail -n 1)
|
||||
test -n "$KERNELVERSION"
|
||||
@@ -216,6 +233,19 @@ test -n "$KERNEL_IMAGE"
|
||||
test -e "$KERNEL_IMAGE"
|
||||
cp "$KERNEL_IMAGE" "$GENESIS_ROOT/kernel"
|
||||
|
||||
# dracut_install reports a missing binary and returns, so a hole in the image reaches the
|
||||
# rpm silently. Three of them did.
|
||||
GENESIS_REQUIRED=""
|
||||
%if 0%{?rhel} && 0%{?rhel} < 10
|
||||
GENESIS_REQUIRED="usr/sbin/dhclient"
|
||||
%endif
|
||||
%if 0%{?rhel} >= 10
|
||||
GENESIS_REQUIRED="usr/sbin/dhcpcd"
|
||||
%endif
|
||||
bash "%{_builddir}/xCAT-genesis-base-build-support/verify-genesis-payload" \
|
||||
--commands-from "$DRACUTMODDIR/module-setup.sh" \
|
||||
"$GENESIS_FS" $GENESIS_REQUIRED
|
||||
|
||||
find "$GENESIS_TMPDIR" -type c -delete
|
||||
cp -a "$GENESIS_TMPDIR/%{prefix}/." "$RPM_BUILD_ROOT/%{prefix}/"
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ echo PS1="'"'[xCAT Genesis running on \H \w]\$ '"'" > /.bash_profile
|
||||
mkdir -p /etc/ssh
|
||||
mkdir -p /var/tmp/
|
||||
mkdir -p /var/empty/sshd
|
||||
sed -i '/^root:x/d' /etc/passwd
|
||||
# dracut writes this entry itself, with an empty password field unless the image is
|
||||
# built --hostonly. Match the user name only.
|
||||
sed -i '/^root:/d' /etc/passwd
|
||||
cat >>/etc/passwd <<"__ENDL"
|
||||
root:x:0:0::/:/bin/bash
|
||||
sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin
|
||||
|
||||
@@ -5,11 +5,11 @@ Maintainer: xCAT <xcat-user@lists.sourceforge.net>
|
||||
Build-Depends: debhelper (>= 9)
|
||||
Standards-Version: 3.9.4
|
||||
|
||||
Package: xcat-genesis-scripts-ppc64
|
||||
Package: xcat-genesis-scripts-ppc64el
|
||||
Architecture: all
|
||||
Depends: xcat-genesis-base-ppc64 (>= 2.13.10)
|
||||
Conflicts: xcat-genesis-scripts
|
||||
Replaces: xcat-genesis-scripts
|
||||
Depends: xcat-genesis-base-ppc64el (>= 2.13.10)
|
||||
Conflicts: xcat-genesis-scripts, xcat-genesis-scripts-ppc64
|
||||
Replaces: xcat-genesis-scripts, xcat-genesis-scripts-ppc64
|
||||
Description: xCAT genesis
|
||||
(Genesis Enhanced Netboot Environment for System Information and Servicing)
|
||||
is a small, embedded-like environment for xCAT's use in discovery and
|
||||
|
||||
@@ -205,6 +205,47 @@ secondary_nic_needs_dhcp() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# RHEL 10 packages no ISC dhcp-client, so the image carries whichever client its release
|
||||
# ships. Print the command line for one interface and one address family, or nothing when
|
||||
# the image carries no client at all.
|
||||
genesis_dhcp_command() {
|
||||
local family=$1
|
||||
local nic=$2
|
||||
|
||||
if command -v dhclient >/dev/null 2>&1; then
|
||||
if [ "$family" = 6 ]; then
|
||||
echo "dhclient -6 -pf /var/run/dhclient6.$nic.pid $nic -lf /var/lib/dhclient/dhclient6.leases"
|
||||
else
|
||||
echo "dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.$nic.pid $nic"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v dhcpcd >/dev/null 2>&1; then
|
||||
# dhcpcd carries its own resolv.conf, hostname and ntp hooks, so it does not need
|
||||
# dhclient-script. On a single interface it exits when its timeout expires, and it
|
||||
# de-configures the interface as it goes; -t 0 and -p turn both off.
|
||||
echo "dhcpcd -$family -b -p -t 0 $nic"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Run the client genesis_dhcp_command chose. The caller backgrounds this.
|
||||
genesis_start_dhcp() {
|
||||
local family=$1
|
||||
local nic=$2
|
||||
local command
|
||||
|
||||
command=$(genesis_dhcp_command "$family" "$nic")
|
||||
if [ -z "$command" ]; then
|
||||
logger -s -t $log_label -p local4.err "The image carries no DHCP client, so $nic gets no IPv$family address."
|
||||
return 1
|
||||
fi
|
||||
$command
|
||||
}
|
||||
|
||||
# see if they specified static ip info, otherwise use dhcp
|
||||
XCATPORT=3001
|
||||
for parm in `cat /proc/cmdline`; do
|
||||
@@ -253,8 +294,8 @@ else
|
||||
while [ $tries -lt 100 ]; do
|
||||
ALLUP_NICS=`ip link show | grep -v "^ " | grep "state UP" | awk '{print $2}' | sed -e 's/:$//'|grep -v lo | sort -n -r`
|
||||
for tmp1 in $ALLUP_NICS; do
|
||||
dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.$tmp1.pid $tmp1 &
|
||||
dhclient -6 -pf /var/run/dhclient6.$tmp1.pid $tmp1 -lf /var/lib/dhclient/dhclient6.leases &
|
||||
genesis_start_dhcp 4 "$tmp1" &
|
||||
genesis_start_dhcp 6 "$tmp1" &
|
||||
#bootnic=$tmp1
|
||||
#break
|
||||
done
|
||||
@@ -290,11 +331,11 @@ else
|
||||
/bin/bash
|
||||
fi
|
||||
else
|
||||
dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.$bootnic.pid $bootnic &
|
||||
genesis_start_dhcp 4 "$bootnic" &
|
||||
#we'll kick of IPv6 and IPv4 on all nics, but not wait for them to come up unless doing discovery, to reduce
|
||||
#chances that we'll perform a partial discovery
|
||||
#in other scenarios where downed non-bootnics cause issues, will rely on retries to fix things up
|
||||
dhclient -6 -pf /var/run/dhclient6.$bootnic.pid $bootnic -lf /var/lib/dhclient/dhclient6.leases &
|
||||
genesis_start_dhcp 6 "$bootnic" &
|
||||
NICCANDIDATES=`ip link|grep mtu|grep -v LOOPBACK|grep -v $bootnic|grep -v usb|awk -F: '{print $2}'`
|
||||
TSMNIC=$(cat /tmp/tsmhostnic 2>/dev/null)
|
||||
NICSTOBRINGUP=
|
||||
@@ -305,8 +346,8 @@ else
|
||||
done
|
||||
export NICSTOBRINGUP
|
||||
for nic in $NICSTOBRINGUP; do
|
||||
(while ! ethtool $nic | grep Link\ detected|grep yes > /dev/null && [ ! -f /tmp/netinitted ]; do sleep 5; done; dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.$nic.pid $nic ) &
|
||||
(while ! ethtool $nic | grep Link\ detected|grep yes > /dev/null && [ ! -f /tmp/netinitted ]; do sleep 5; done; dhclient -cf /etc/dhclient.conf -6 -pf /var/run/dhclient6.$nic.pid -lf /var/lib/dhclient/dhclient6.leases $nic ) &
|
||||
(while ! ethtool $nic | grep Link\ detected|grep yes > /dev/null && [ ! -f /tmp/netinitted ]; do sleep 5; done; genesis_start_dhcp 4 "$nic" ) &
|
||||
(while ! ethtool $nic | grep Link\ detected|grep yes > /dev/null && [ ! -f /tmp/netinitted ]; do sleep 5; done; genesis_start_dhcp 6 "$nic" ) &
|
||||
done
|
||||
|
||||
gripeiter=101
|
||||
|
||||
@@ -4,8 +4,27 @@ CREDPID=$!
|
||||
if [ -z "$XCATDEST" ]; then
|
||||
XCATDEST=$1
|
||||
fi
|
||||
# doxcat runs getcert in the foreground and ignores its status, so a wait with no bound stops
|
||||
# the boot and prints nothing.
|
||||
give_up() {
|
||||
logger -s -t xcat -p local4.err "getcert: $1"
|
||||
kill $CREDPID
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ! command -v openssl > /dev/null 2>&1; then
|
||||
give_up "this Genesis image carries no openssl, so no certificate is requested"
|
||||
fi
|
||||
|
||||
#retry in case certkey.pem is not right, yet
|
||||
# doxcat writes /etc/xcat/certkey.pem in the background with a 4096 bit key, so the first
|
||||
# requests fail. An emulated node needs minutes for that key.
|
||||
CSR_TIMEOUT=${GETCERT_CSR_TIMEOUT:-600}
|
||||
CSR_DEADLINE=$((SECONDS + CSR_TIMEOUT))
|
||||
while ! openssl req -new -key /etc/xcat/certkey.pem -out /tmp/tls.csr -subj "/CN=$(hostname)" >& /dev/null; do
|
||||
if [ "$SECONDS" -ge "$CSR_DEADLINE" ]; then
|
||||
give_up "no certificate request after ${CSR_TIMEOUT}s; /etc/xcat/certkey.pem is not usable"
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "<xcatrequest>
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
%ifarch aarch64
|
||||
%define tarch aarch64
|
||||
%endif
|
||||
%ifarch riscv64
|
||||
%define tarch riscv64
|
||||
%endif
|
||||
# An arch missing from the ladder above leaves %{tarch} unexpanded, and rpm then builds a package
|
||||
# with a macro in its NAME instead of failing. Stop the build here instead.
|
||||
%if ! %{defined tarch}
|
||||
%{error:no genesis tarch for %{_target_cpu} -- add an %%ifarch branch above}
|
||||
%endif
|
||||
%define rpminstallroot /opt/xcat/share/xcat/netboot/genesis/%{tarch}/fs
|
||||
BuildArch: noarch
|
||||
%define name xCAT-genesis-scripts-%{tarch}
|
||||
|
||||
@@ -37,10 +37,10 @@ sub ddns_tsig_algorithm {
|
||||
|
||||
my $settings = $ctx->{omapi_settings} || xCAT::DHCP::OmapiPolicy->settings();
|
||||
|
||||
# Keep old Net::DNS on MD5 unless the administrator explicitly selects a
|
||||
# different OMAPI algorithm. Old Net::DNS can sign non-MD5 updates only
|
||||
# through a KEY RR, which ddns_sign_update builds below.
|
||||
return "hmac-md5" if (!net_dns_uses_keyfile() && !$settings->{algorithm_explicit});
|
||||
# $ctx->{tsig_algorithm} is the algorithm the named.conf key stanza already declares.
|
||||
# The Net::DNS version does not select the algorithm: old Net::DNS signs every algorithm
|
||||
# except MD5 through a KEY RR, which ddns_sign_update builds.
|
||||
return $settings->{algorithm} if $settings->{algorithm_explicit};
|
||||
return $ctx->{tsig_algorithm} || $settings->{algorithm};
|
||||
}
|
||||
|
||||
@@ -65,13 +65,17 @@ sub ddns_sign_update {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($settings->{algorithm} eq 'hmac-md5') {
|
||||
# named matches the key by name and by algorithm. Sign with the algorithm the key stanza
|
||||
# declares, not with the OMAPI default.
|
||||
my $algorithm = ddns_tsig_algorithm($ctx);
|
||||
if ($algorithm eq 'hmac-md5') {
|
||||
$update->sign_tsig($settings->{key_name}, $ctx->{privkey});
|
||||
return;
|
||||
}
|
||||
|
||||
my $owner = xCAT::DHCP::OmapiPolicy->key_owner($settings);
|
||||
my $keyrr = Net::DNS::RR->new("$owner IN KEY 512 3 $settings->{key_rr_type} $ctx->{privkey}");
|
||||
my $owner = xCAT::DHCP::OmapiPolicy->key_owner($settings);
|
||||
my $rr_type = xCAT::DHCP::OmapiPolicy->algorithm_rr_type($algorithm);
|
||||
my $keyrr = Net::DNS::RR->new("$owner IN KEY 512 3 $rr_type $ctx->{privkey}");
|
||||
$update->sign_tsig($keyrr);
|
||||
}
|
||||
|
||||
@@ -1345,10 +1349,6 @@ sub update_namedconf {
|
||||
$ctx->{tsig_algorithm} = $omapi_settings->{algorithm};
|
||||
push @newnamed, ddns_key_contents($ctx);
|
||||
$ctx->{restartneeded} = 1;
|
||||
} elsif ($algorithmnow && !net_dns_uses_keyfile() && lc($algorithmnow) ne "hmac-md5") {
|
||||
$ctx->{tsig_algorithm} = "hmac-md5";
|
||||
push @newnamed, ddns_key_contents($ctx);
|
||||
$ctx->{restartneeded} = 1;
|
||||
} else {
|
||||
push @newnamed, @keyblock;
|
||||
}
|
||||
@@ -1579,6 +1579,26 @@ sub update_namedconf {
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
=head3 ddns_update_request
|
||||
|
||||
Descriptions: Copy the records of one dynamic DNS update into a new, unsigned request.
|
||||
Arguments: the update the caller built, the zone name
|
||||
Returns: a Net::DNS::Update holding the same prerequisite and update records
|
||||
|
||||
=cut
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
sub ddns_update_request {
|
||||
my ($update, $zone) = @_;
|
||||
|
||||
my $request = Net::DNS::Update->new($zone);
|
||||
$request->push(pre => $_) for $update->answer;
|
||||
$request->push(update => $_) for $update->authority;
|
||||
return $request;
|
||||
}
|
||||
|
||||
# Send a signed dynamic DNS update, retrying transient rejections. Right after a zone (re)load named
|
||||
# can reply NOTAUTH, or SERVFAIL before the zone is ready to accept dynamic updates; both are
|
||||
# recoverable, so retry a few times (pausing on SERVFAIL). Returns 0 only when the update was
|
||||
@@ -1589,8 +1609,11 @@ sub send_ddns_update {
|
||||
my ($ctx, $resolver, $update, $zone, $entry) = @_;
|
||||
|
||||
for my $attempt (1 .. 3) {
|
||||
ddns_sign_update($ctx, $update);
|
||||
my $reply = $resolver->send($update);
|
||||
# sign_tsig appends the TSIG to the additional section. A second signature on the same
|
||||
# packet sends two TSIG records, and named answers FORMERR. Sign a copy for each attempt.
|
||||
my $request = ddns_update_request($update, $zone);
|
||||
ddns_sign_update($ctx, $request);
|
||||
my $reply = $resolver->send($request);
|
||||
if (!$reply) {
|
||||
xCAT::SvrUtils::sendmsg([ 1, "No reply received when sending DNS update to zone $zone" ], $callback);
|
||||
return 1;
|
||||
|
||||
@@ -191,6 +191,8 @@ my %INSTALL_BOOT_FILES = (
|
||||
'ppc64' => [
|
||||
[ 'install/netboot/ubuntu-installer/{darch}/vmlinux', 'install/netboot/ubuntu-installer/{darch}/initrd.gz' ],
|
||||
[ 'install/vmlinux', 'install/netboot/initrd.gz' ],
|
||||
[ 'casper/hwe-vmlinux', 'casper/hwe-initrd' ],
|
||||
[ 'casper/vmlinux', 'casper/initrd' ],
|
||||
],
|
||||
'riscv64' => [
|
||||
[ 'casper/vmlinux', 'casper/initrd' ],
|
||||
@@ -379,6 +381,53 @@ sub _no_grub2_loader {
|
||||
return;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------
|
||||
|
||||
=head3 install_prescript
|
||||
|
||||
Descriptions: Return the pre-install script an Ubuntu or Debian install runs.
|
||||
Arguments:
|
||||
$platform - the distribution family, for example ubuntu
|
||||
$arch - the architecture of the node
|
||||
$subiquity - true when the osimage template is a Subiquity autoinstall
|
||||
Returns: the full path of the pre-install script
|
||||
|
||||
=cut
|
||||
|
||||
#-------------------------------------------------------
|
||||
sub install_prescript
|
||||
{
|
||||
my ($platform, $arch, $subiquity) = @_;
|
||||
my $base = "$::XCATROOT/share/xcat/install/scripts/pre.$platform";
|
||||
|
||||
# pre.ubuntu.ppc64 writes a partman recipe, which only the debian-installer
|
||||
# reads. Subiquity gets its POWER partitioning from pre.ubuntu.subiquity.
|
||||
return "$base.subiquity" if ($subiquity);
|
||||
return "$base.ppc64" if (defined($arch) and $arch =~ /ppc64/i and $platform eq "ubuntu");
|
||||
return $base;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------
|
||||
|
||||
=head3 install_media_is_bootable
|
||||
|
||||
Descriptions: Report whether copied media carries an install kernel and initrd.
|
||||
Arguments:
|
||||
$arch - the xCAT architecture of the node
|
||||
$darch - the dpkg architecture
|
||||
$pkgdir - the directory copycds wrote the media to
|
||||
Returns: 1 when the media can boot a network install, 0 when it cannot
|
||||
|
||||
=cut
|
||||
|
||||
#-------------------------------------------------------
|
||||
sub install_media_is_bootable
|
||||
{
|
||||
my ($arch, $darch, $pkgdir) = @_;
|
||||
|
||||
return install_boot_files($arch, $darch, $pkgdir) ? 1 : 0;
|
||||
}
|
||||
|
||||
sub is_ubuntu_live_media
|
||||
{
|
||||
my $media_path = shift;
|
||||
@@ -1132,18 +1181,10 @@ sub mkinstall {
|
||||
);
|
||||
}
|
||||
|
||||
# maybe Debian will decide to use subiquity at some point?
|
||||
my $prescript = "$::XCATROOT/share/xcat/install/scripts/pre.$platform";
|
||||
if (using_subiquity($os,$tmplfile)) {
|
||||
$prescript = $prescript . ".subiquity";
|
||||
}
|
||||
my $prescript =
|
||||
install_prescript($platform, $arch, using_subiquity($os, $tmplfile));
|
||||
my $postscript = "$::XCATROOT/share/xcat/install/scripts/post.$platform";
|
||||
|
||||
# for powerkvm VM ubuntu LE#
|
||||
if ($arch =~ /ppc64/i and $platform eq "ubuntu") {
|
||||
$prescript = "$::XCATROOT/share/xcat/install/scripts/pre.$platform.ppc64";
|
||||
}
|
||||
|
||||
|
||||
if (-r "$prescript") {
|
||||
$preerr = xCAT::Template->subvars($prescript,
|
||||
@@ -1176,10 +1217,11 @@ sub mkinstall {
|
||||
next;
|
||||
}
|
||||
|
||||
if ($arch =~ /ppc64/i and !(-e "$pkgdir/install/netboot/initrd.gz") and
|
||||
!(-e "$pkgdir/install/netboot/ubuntu-installer/$darch/initrd.gz")) {
|
||||
xCAT::MsgUtils->report_node_error($callback, $node,
|
||||
"The network boot initrd.gz is not found in $pkgdir/install/netboot. This is provided by Ubuntu, please download and retry."
|
||||
# The POWER live-server ISO keeps its installer under casper and ships no netboot
|
||||
# tree, so the media that can boot it is the media install_boot_files resolves.
|
||||
unless (install_media_is_bootable($arch, $darch, $pkgdir)) {
|
||||
xCAT::MsgUtils->report_node_error($callback, $node,
|
||||
"No install kernel and initrd were found on the media in $pkgdir."
|
||||
);
|
||||
next;
|
||||
}
|
||||
|
||||
@@ -498,6 +498,8 @@ sub build_diskstruct {
|
||||
my @suffixes = ('a', 'b', 'd' .. 'zzz');
|
||||
my $suffidx = 0;
|
||||
my $storagemodel = $confdata->{vm}->{$node}->[0]->{storagemodel};
|
||||
my $profile = guest_arch_profile($confdata->{nodetype}->{$node}->[0]->{arch},
|
||||
$confdata->{ $confdata->{vm}->{$node}->[0]->{host} }->{cpumodel});
|
||||
my $cachemethod = "none";
|
||||
if ($confdata->{vm}->{$node}->[0]->{storagecache}) {
|
||||
$cachemethod = $confdata->{vm}->{$node}->[0]->{storagecache};
|
||||
@@ -511,13 +513,17 @@ sub build_diskstruct {
|
||||
|
||||
#Setting default values of a virtual disk backed by a file at hd*.
|
||||
my $diskhash;
|
||||
$disk =~ s/=(.*)//;
|
||||
my $model = $1;
|
||||
# A failed substitution leaves $1 as the last successful capture, which can come
|
||||
# from a match made by a caller. Read $1 only when this substitution matches.
|
||||
my $model;
|
||||
if ($disk =~ s/=(.*)//) {
|
||||
$model = $1;
|
||||
}
|
||||
unless ($model) {
|
||||
|
||||
#if not defined, model will stay undefined like above
|
||||
$model = $storagemodel;
|
||||
unless ($model) { $model = 'ide'; } #if still not defined, ide
|
||||
unless ($model) { $model = $profile->{disk_model}; }
|
||||
}
|
||||
my $prefix = 'hd';
|
||||
if ($model eq 'virtio') {
|
||||
@@ -549,13 +555,16 @@ sub build_diskstruct {
|
||||
$tdiskhash->{driver}->{type} = $disks{$_}->{format};
|
||||
$tdiskhash->{driver}->{cache} = $cachemethod;
|
||||
$tdiskhash->{source}->{file} = $_;
|
||||
$tdiskhash->{target}->{dev} = $disks{$_}->{device};
|
||||
my $device = $disks{$_}->{device};
|
||||
$tdiskhash->{target}->{dev} = $device;
|
||||
|
||||
if ($disks{$_} =~ /^vd/) {
|
||||
# libvirt reads the bus out of the device name when the disk states
|
||||
# none: hd* is ide, sd* is scsi, vd* is virtio. State the same bus.
|
||||
if ($device =~ /^vd/) {
|
||||
$tdiskhash->{target}->{bus} = 'virtio';
|
||||
} elsif ($disks{$_} =~ /^hd/) {
|
||||
} elsif ($device =~ /^hd/) {
|
||||
$tdiskhash->{target}->{bus} = 'ide';
|
||||
} elsif ($disks{$_} =~ /^sd/) {
|
||||
} elsif ($device =~ /^sd/) {
|
||||
$tdiskhash->{target}->{bus} = 'scsi';
|
||||
}
|
||||
push @returns, $tdiskhash;
|
||||
@@ -586,7 +595,8 @@ sub build_diskstruct {
|
||||
push @returns, $diskhash;
|
||||
}
|
||||
}
|
||||
my $cdprefix = 'hd';
|
||||
# The riscv64 virt machine has no IDE controller, so the optical drive is scsi there.
|
||||
my $cdprefix = $profile->{cd_prefix};
|
||||
|
||||
# Normally for vmstoragemodel=virtio, we would set prefix of "vd", but device name vd*
|
||||
# doesn't work for CDROM, so for now use the same prefix "sd" as for vmstoragemodel=scsi.
|
||||
@@ -704,6 +714,63 @@ sub getUnits {
|
||||
}
|
||||
}
|
||||
|
||||
# default_storagemodel: the storage model of a node whose vmstoragemodel is empty.
|
||||
#
|
||||
# The model names the volume of the node, createstorage builds that name, and libvirt reads
|
||||
# the bus of the disk out of it. scsi keeps every architecture on sd*, which is the only disk
|
||||
# controller the riscv64 virt machine has.
|
||||
sub default_storagemodel {
|
||||
return 'scsi';
|
||||
}
|
||||
|
||||
# guest_arch_profile: the libvirt domain type and <os> settings for one guest.
|
||||
#
|
||||
# The architecture of the guest comes from the node, not from the hypervisor. A node whose
|
||||
# arch is not the arch of the hypervisor runs under emulation, which libvirt expresses as
|
||||
# domain type "qemu". riscv64 has no BIOS: the virt machine boots UEFI, and pae/acpi/apic
|
||||
# are x86 features that libvirt rejects there.
|
||||
#
|
||||
# POWER keeps reading the hypervisor cpumodel. ppc64le hypervisors report "ppc64le" (not
|
||||
# "ppc64"); both are pseries guests whose libvirt <os> arch is "ppc64".
|
||||
#
|
||||
# arch and machine stay undef when libvirt is to use its own default for the hypervisor.
|
||||
sub guest_arch_profile {
|
||||
my ($guest_arch, $hyp_cpumodel) = @_;
|
||||
my %profile = (
|
||||
domtype => 'kvm',
|
||||
arch => undef,
|
||||
machine => undef,
|
||||
firmware => undef,
|
||||
x86_features => 1,
|
||||
bios => 1,
|
||||
sound => 1,
|
||||
video => 'vga',
|
||||
usb_input => 1,
|
||||
disk_model => 'ide',
|
||||
cd_prefix => 'hd',
|
||||
);
|
||||
if (defined($guest_arch) and $guest_arch eq 'riscv64') {
|
||||
$profile{domtype} = 'qemu';
|
||||
$profile{arch} = 'riscv64';
|
||||
$profile{machine} = 'virt';
|
||||
$profile{firmware} = 'efi';
|
||||
$profile{x86_features} = 0;
|
||||
$profile{bios} = 0;
|
||||
$profile{sound} = 0;
|
||||
$profile{video} = 'virtio';
|
||||
$profile{usb_input} = 0;
|
||||
$profile{disk_model} = 'scsi';
|
||||
$profile{cd_prefix} = 'sd';
|
||||
} elsif (defined($hyp_cpumodel) and ($hyp_cpumodel eq "ppc64" or $hyp_cpumodel eq "ppc64le")) {
|
||||
$profile{arch} = 'ppc64';
|
||||
$profile{machine} = 'pseries';
|
||||
$profile{x86_features} = 0;
|
||||
$profile{bios} = 0;
|
||||
$profile{sound} = 0;
|
||||
}
|
||||
return \%profile;
|
||||
}
|
||||
|
||||
sub build_xmldesc {
|
||||
my $node = shift;
|
||||
my %args = @_;
|
||||
@@ -716,19 +783,16 @@ sub build_xmldesc {
|
||||
$hypcputhreads = "1";
|
||||
}
|
||||
|
||||
$xtree{type} = 'kvm';
|
||||
my $profile = guest_arch_profile($confdata->{nodetype}->{$node}->[0]->{arch}, $hypcpumodel);
|
||||
|
||||
$xtree{type} = $profile->{domtype};
|
||||
$xtree{name}->{content} = $node;
|
||||
$xtree{uuid}->{content} = getNodeUUID($node);
|
||||
$xtree{os} = build_oshash();
|
||||
# ppc64le hypervisors report cpumodel "ppc64le" (not "ppc64"); both are pseries
|
||||
# guests whose libvirt <os> arch is "ppc64". Without this the guest is emitted
|
||||
# as an x86-style domain (no machine, plus the pae/acpi/apic below) which libvirt
|
||||
# rejects on ppc64le hosts: "machine type 'pseries-*' does not support ACPI".
|
||||
if (defined($hypcpumodel) and ($hypcpumodel eq "ppc64" or $hypcpumodel eq "ppc64le")) {
|
||||
$xtree{os}->{type}->{arch} = "ppc64";
|
||||
$xtree{os}->{type}->{machine} = "pseries";
|
||||
delete $xtree{os}->{bios};
|
||||
}
|
||||
$xtree{os}->{type}->{arch} = $profile->{arch} if defined $profile->{arch};
|
||||
$xtree{os}->{type}->{machine} = $profile->{machine} if defined $profile->{machine};
|
||||
$xtree{os}->{firmware} = $profile->{firmware} if defined $profile->{firmware};
|
||||
delete $xtree{os}->{bios} unless $profile->{bios};
|
||||
if ($args{memory}) {
|
||||
$xtree{memory}->{content} = getUnits($args{memory}, "M", 1024);
|
||||
if ($confdata->{vm}->{$node}->[0]->{memory}) {
|
||||
@@ -940,9 +1004,7 @@ sub build_xmldesc {
|
||||
}
|
||||
}
|
||||
|
||||
# pae/acpi/apic are x86 features; pseries (ppc64/ppc64le) guests do not support
|
||||
# them and libvirt rejects the domain if they are present.
|
||||
unless (defined($hypcpumodel) and ($hypcpumodel eq "ppc64" or $hypcpumodel eq "ppc64le")) {
|
||||
if ($profile->{x86_features}) {
|
||||
$xtree{features}->{pae} = {};
|
||||
$xtree{features}->{acpi} = {};
|
||||
$xtree{features}->{apic} = {};
|
||||
@@ -965,10 +1027,13 @@ sub build_xmldesc {
|
||||
$vram = 65536; } #surprise, spice blows up with less vram than this after version 0.6 and up
|
||||
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => $model, vram => $vram } } ];
|
||||
} else {
|
||||
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => 'vga', vram => 8192 } } ];
|
||||
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => $profile->{video}, vram => 8192 } } ];
|
||||
}
|
||||
# The riscv64 virt machine has no USB controller, and libvirt refuses a USB device there.
|
||||
if ($profile->{usb_input}) {
|
||||
$xtree{devices}->{input}->{type} = 'tablet';
|
||||
$xtree{devices}->{input}->{bus} = 'usb';
|
||||
}
|
||||
$xtree{devices}->{input}->{type} = 'tablet';
|
||||
$xtree{devices}->{input}->{bus} = 'usb';
|
||||
if (defined($confdata->{vm}->{$node}->[0]->{vidproto})) {
|
||||
$xtree{devices}->{graphics}->{type} = $confdata->{vm}->{$node}->[0]->{vidproto};
|
||||
} else {
|
||||
@@ -983,10 +1048,9 @@ sub build_xmldesc {
|
||||
}
|
||||
if (defined($hypcpumodel) and $hypcpumodel eq 'ppc64') {
|
||||
$xtree{devices}->{emulator}->{content} = "/usr/bin/qemu-system-ppc64";
|
||||
} elsif (defined($hypcpumodel) and $hypcpumodel eq 'ppc64le') {
|
||||
# do nothing for ppc64le, do not support sound at this time
|
||||
;
|
||||
} else {
|
||||
}
|
||||
# libvirt resolves the emulator for every other architecture from its own capabilities.
|
||||
if ($profile->{sound}) {
|
||||
$xtree{devices}->{sound}->{model} = 'ich6';
|
||||
}
|
||||
|
||||
@@ -1531,8 +1595,12 @@ sub createstorage {
|
||||
if ($mastername and $size) {
|
||||
return 1, "Can not specify both a master to clone and size(s)";
|
||||
}
|
||||
$filename =~ s/=(.*)//;
|
||||
my $model = $1;
|
||||
# A failed substitution leaves $1 as the last successful capture, which can come from a
|
||||
# match made by a caller. Read $1 only when this substitution matches.
|
||||
my $model;
|
||||
if ($filename =~ s/=(.*)//) {
|
||||
$model = $1;
|
||||
}
|
||||
unless ($model) {
|
||||
|
||||
#if not defined, model will stay undefined like above
|
||||
@@ -4251,8 +4319,7 @@ sub dohyp {
|
||||
|
||||
foreach $node (sort (keys %{ $hyphash{$hyp}->{nodes} })) {
|
||||
unless ($confdata->{vm}->{$node}->[0]->{storagemodel}) {
|
||||
# Storage model is not set, default to scsi for all architectures
|
||||
$confdata->{vm}->{$node}->[0]->{storagemodel} = "scsi";
|
||||
$confdata->{vm}->{$node}->[0]->{storagemodel} = default_storagemodel();
|
||||
}
|
||||
if ($confdata->{$hyp}->{cpu_thread}) {
|
||||
$confdata->{vm}->{$node}->[0]->{cpu_thread} = $confdata->{$hyp}->{cpu_thread};
|
||||
|
||||
@@ -356,6 +356,45 @@ sub genesis_lzma_command {
|
||||
return;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
=head3 stage_genesis_payload
|
||||
|
||||
Descriptions:
|
||||
Copy the Genesis payload into place for mknb: for a legacy image the unpacked
|
||||
root tree and then the kernel, for an exported image the nbroot tree.
|
||||
|
||||
Extracted so the outcome can be driven directly. The copies are the only
|
||||
place mknb learns that an installed Genesis image is unusable, and a caller
|
||||
cannot tell WHICH copy failed from a single exit status.
|
||||
|
||||
Arguments:
|
||||
genesis_type, genesis_dir, tftpdir, arch, tempdir, and an optional run
|
||||
coderef used in place of system() by the tests.
|
||||
Returns:
|
||||
(rc, source) -- rc is the exit status of the copy that failed, and source
|
||||
names it, so the caller reports the file it could not read.
|
||||
|
||||
=cut
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
sub stage_genesis_payload {
|
||||
my (%a) = @_;
|
||||
my $run = $a{run} || sub { return system($_[0]); };
|
||||
my $rc;
|
||||
if (($a{genesis_type} // '') eq 'legacy') {
|
||||
# Two copies, each able to fail on its own. Return on the first, so neither the exit
|
||||
# status nor the name of the unreadable file is lost to the one that follows it.
|
||||
$rc = $run->("shopt -s dotglob; GLOBIGNORE=\".:..\" cp -a $a{genesis_dir}/fs/* $a{tempdir}");
|
||||
return ($rc, "$a{genesis_dir}/fs") if $rc;
|
||||
$rc = $run->("cp -a $a{genesis_dir}/kernel $a{tftpdir}/xcat/genesis.kernel.$a{arch}");
|
||||
return ($rc, "$a{genesis_dir}/kernel") if $rc;
|
||||
return (0, undef);
|
||||
}
|
||||
$rc = $run->("cp -a $a{genesis_dir}/nbroot/* $a{tempdir}");
|
||||
return ($rc, "$a{genesis_dir}/nbroot");
|
||||
}
|
||||
|
||||
sub process_request {
|
||||
my $request = shift;
|
||||
my $callback = shift;
|
||||
@@ -584,21 +623,13 @@ sub process_request {
|
||||
unless (-e "$tftpdir/xcat") {
|
||||
mkpath("$tftpdir/xcat");
|
||||
}
|
||||
my $rc;
|
||||
if ($genesis_type eq 'legacy') {
|
||||
$rc = system("shopt -s dotglob; GLOBIGNORE=\".:..\" cp -a $genesis_dir/fs/* $tempdir");
|
||||
$rc = system("cp -a $genesis_dir/kernel $tftpdir/xcat/genesis.kernel.$arch");
|
||||
$invisibletouch = 1;
|
||||
} else {
|
||||
$rc = system("cp -a $genesis_dir/nbroot/* $tempdir");
|
||||
}
|
||||
$invisibletouch = 1 if $genesis_type eq 'legacy';
|
||||
my ($rc, $failed_src) = stage_genesis_payload(
|
||||
genesis_type => $genesis_type, genesis_dir => $genesis_dir,
|
||||
tftpdir => $tftpdir, arch => $arch, tempdir => $tempdir);
|
||||
if ($rc) {
|
||||
system("rm -rf $tempdir");
|
||||
if ($invisibletouch) {
|
||||
$callback->({ error => ["Failed to copy $genesis_dir/fs contents"], errorcode => [1] });
|
||||
} else {
|
||||
$callback->({ error => ["Failed to copy $genesis_dir/nbroot contents"], errorcode => [1] });
|
||||
}
|
||||
$callback->({ error => ["Failed to copy $failed_src contents"], errorcode => [1] });
|
||||
return;
|
||||
}
|
||||
my $sshdir;
|
||||
|
||||
@@ -278,6 +278,60 @@ storage:
|
||||
path: /
|
||||
device: root-part-fs
|
||||
EOF
|
||||
elif [ "$(uname -m)" = "ppc64le" ] || [ "$(uname -m)" = "ppc64" ]; then
|
||||
# POWER firmware reads neither an ESP nor a bios_grub partition. It loads the
|
||||
# boot loader from a PReP partition, and curtin writes grub to the partition
|
||||
# flagged prep, not to the disk.
|
||||
cat <<EOF >/tmp/partitionfile
|
||||
storage:
|
||||
version: 1
|
||||
config:
|
||||
- id: disk-detected
|
||||
type: disk
|
||||
ptable: gpt
|
||||
path: $INSTALL_DISK
|
||||
wipe: superblock-recursive
|
||||
preserve: false
|
||||
- id: prep-part
|
||||
type: partition
|
||||
device: disk-detected
|
||||
size: 8M
|
||||
flag: prep
|
||||
number: 1
|
||||
preserve: false
|
||||
grub_device: true
|
||||
- id: swap-part
|
||||
type: partition
|
||||
device: disk-detected
|
||||
size: 2G
|
||||
number: 2
|
||||
preserve: false
|
||||
- id: root-part
|
||||
type: partition
|
||||
device: disk-detected
|
||||
size: -1
|
||||
wipe: superblock
|
||||
number: 3
|
||||
preserve: false
|
||||
- id: swap-part-fs
|
||||
type: format
|
||||
fstype: swap
|
||||
volume: swap-part
|
||||
preserve: false
|
||||
- id: root-part-fs
|
||||
type: format
|
||||
fstype: ext4
|
||||
volume: root-part
|
||||
preserve: false
|
||||
- id: swap-part-mount
|
||||
type: mount
|
||||
path: none
|
||||
device: swap-part-fs
|
||||
- id: root-part-mount
|
||||
type: mount
|
||||
path: /
|
||||
device: root-part-fs
|
||||
EOF
|
||||
else
|
||||
cat <<EOF >/tmp/partitionfile
|
||||
storage:
|
||||
|
||||
@@ -90,9 +90,9 @@ autoinstall:
|
||||
installmac="#SUBIQUITYINSTALLMAC#";
|
||||
mkdir -p /target/etc/netplan;
|
||||
if [ -z "${installnic}" ]; then
|
||||
printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " dhcp4: true" >/target/etc/netplan/00-xcat-install.yaml;
|
||||
printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " dhcp4: true" " dhcp4-overrides:" " use-domains: true" >/target/etc/netplan/00-xcat-install.yaml;
|
||||
else
|
||||
printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " set-name: ${installnic}" " dhcp4: true" >/target/etc/netplan/00-xcat-install.yaml;
|
||||
printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " set-name: ${installnic}" " dhcp4: true" " dhcp4-overrides:" " use-domains: true" >/target/etc/netplan/00-xcat-install.yaml;
|
||||
fi;
|
||||
chmod 600 /target/etc/netplan/00-xcat-install.yaml;
|
||||
printf ''%s\n'' ''#HOSTNAME#'' >/target/etc/hostname;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
bash
|
||||
ifupdown
|
||||
nfs-common
|
||||
openssl
|
||||
isc-dhcp-client
|
||||
libc-bin
|
||||
linux-image-generic
|
||||
openssh-server
|
||||
openssh-client
|
||||
wget
|
||||
vim
|
||||
rsync
|
||||
busybox-static
|
||||
gawk
|
||||
bind9-dnsutils
|
||||
tar
|
||||
gzip
|
||||
xz-utils
|
||||
cpio
|
||||
chrony
|
||||
@@ -0,0 +1,20 @@
|
||||
bash
|
||||
ifupdown
|
||||
nfs-common
|
||||
openssl
|
||||
isc-dhcp-client
|
||||
libc-bin
|
||||
linux-image-generic
|
||||
openssh-server
|
||||
openssh-client
|
||||
wget
|
||||
vim
|
||||
rsync
|
||||
busybox-static
|
||||
gawk
|
||||
bind9-dnsutils
|
||||
tar
|
||||
gzip
|
||||
xz-utils
|
||||
cpio
|
||||
chrony
|
||||
@@ -0,0 +1,20 @@
|
||||
bash
|
||||
ifupdown
|
||||
nfs-common
|
||||
openssl
|
||||
isc-dhcp-client
|
||||
libc-bin
|
||||
linux-image-generic
|
||||
openssh-server
|
||||
openssh-client
|
||||
wget
|
||||
vim
|
||||
rsync
|
||||
busybox-static
|
||||
gawk
|
||||
bind9-dnsutils
|
||||
tar
|
||||
gzip
|
||||
xz-utils
|
||||
cpio
|
||||
chrony
|
||||
@@ -0,0 +1,20 @@
|
||||
bash
|
||||
ifupdown
|
||||
nfs-common
|
||||
openssl
|
||||
isc-dhcp-client
|
||||
libc-bin
|
||||
linux-image-generic
|
||||
openssh-server
|
||||
openssh-client
|
||||
wget
|
||||
vim
|
||||
rsync
|
||||
busybox-static
|
||||
gawk
|
||||
bind9-dnsutils
|
||||
tar
|
||||
gzip
|
||||
xz-utils
|
||||
cpio
|
||||
chrony
|
||||
@@ -199,9 +199,9 @@ GO_XCAT_INSTALL_LIST=(perl-xCAT xCAT-client xCAT xCAT-buildkit
|
||||
# For Debian/Ubuntu, it will need a slightly different package list
|
||||
type dpkg >/dev/null 2>&1 &&
|
||||
GO_XCAT_INSTALL_LIST=(perl-xcat xcat-client xcat xcat-buildkit
|
||||
xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64 xcat-server
|
||||
xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64el xcat-server
|
||||
elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat
|
||||
xcat-genesis-base-amd64 xcat-genesis-base-ppc64 xnba-undi)
|
||||
xcat-genesis-base-amd64 xcat-genesis-base-ppc64el xnba-undi)
|
||||
# The package list of all the packages should be uninstalled
|
||||
GO_XCAT_UNINSTALL_LIST=("${GO_XCAT_INSTALL_LIST[@]}"
|
||||
goconserver xCAT-SoftLayer xCAT-confluent xCAT-csm xCAT-genesis-builder
|
||||
|
||||
@@ -2,14 +2,13 @@ start:nodeset_shell_lzma
|
||||
os:rhels8
|
||||
label:others,genesis
|
||||
description: verify could log in genesis shell lzma compression
|
||||
cmd:if [[ "__GETNODEATTR($$CN,os)__" =~ "rhel" ]]; then yum install -y https://rpmfind.net/linux/centos/8-stream/PowerTools/__GETNODEATTR($$CN,arch)__/os/Packages/xz-lzma-compat-5.2.4-3.el8.__GETNODEATTR($$CN,arch)__.rpm; elif rpm -q xz; then yum download https://rpmfind.net/linux/centos/8-stream/PowerTools/__GETNODEATTR($$CN,arch)__/os/Packages/xz-lzma-compat-5.2.4-3.el8.__GETNODEATTR($$CN,arch)__.rpm; rpm -ivh --nodeps xz-lzma-compat-5.2.4-3.el8.__GETNODEATTR($$CN,arch)__.rpm; fi
|
||||
#Generate genesis network boot with lzma compression
|
||||
cmd:mknb __GETNODEATTR($$CN,arch)__
|
||||
check:rc==0
|
||||
cmd:nodeset $$CN shell
|
||||
check:rc==0
|
||||
cmd:ls -l /tftpboot/xcat/genesis.fs.*.lzma
|
||||
check:output=~genesis
|
||||
check:rc==0
|
||||
cmd:find /tftpboot -type f -name $$CN | xargs grep "lzma"
|
||||
check:output=~genesis
|
||||
cmd:perl /opt/xcat/share/xcat/tools/autotest/testcase/genesis/genesistest.pl -n $$CN -g
|
||||
@@ -19,8 +18,7 @@ check:rc==0
|
||||
cmd:perl /opt/xcat/share/xcat/tools/autotest/testcase/genesis/genesistest.pl -n $$CN -c
|
||||
check:rc==0
|
||||
cmd:cat /tmp/genesistestlog/*
|
||||
#Remove lzma compression RPM, cleanup and generate default gz genesis network boot
|
||||
cmd:yum remove -y xz-lzma-compat
|
||||
#Cleanup and generate the default gz genesis network boot
|
||||
cmd:rm -f /tftpboot/xcat/genesis.fs.*.lzma
|
||||
cmd:mknb __GETNODEATTR($$CN,arch)__
|
||||
end
|
||||
|
||||
@@ -68,13 +68,7 @@ if (!defined($noderange)) {
|
||||
}
|
||||
my $os = &get_os;
|
||||
if ($check_genesis_file) {
|
||||
send_msg(2, "[$$]:Check if genesis packages are installed on mn...............");
|
||||
&check_genesis_file(&get_arch);
|
||||
if ($?) {
|
||||
send_msg(0, "genesis packages are not installed");
|
||||
} else {
|
||||
send_msg(2, "genesis packages are installed");
|
||||
}
|
||||
exit 1 if &report_genesis_files(&get_arch);
|
||||
}
|
||||
my $master=`lsdef -t site -i master -c 2>&1 | awk -F'=' '{print \$2}'`;
|
||||
if (!$master) { $master=hostname(); }
|
||||
@@ -89,29 +83,7 @@ if (!(-e $nodestanza)) {
|
||||
####nodesetshell test for genesis
|
||||
####################################
|
||||
if ($genesis_nodesetshell_test) {
|
||||
send_msg(2, "[$$]:Running nodeset NODE shell test...............");
|
||||
`nodeset $noderange shell`;
|
||||
if ($?) {
|
||||
send_msg(0, "[$$]:nodeset $noderange shell failed...............");
|
||||
exit 1;
|
||||
}
|
||||
`rpower $noderange boot`;
|
||||
if ($?) {
|
||||
send_msg(0, "[$$]:rpower $noderange failed...............");
|
||||
exit 1;
|
||||
}
|
||||
else {
|
||||
send_msg(2, "Installing with \"nodeset $noderange shell\" for shell test");
|
||||
sleep 120; # wait 2 min for install to finish
|
||||
wait_for_boot();
|
||||
}
|
||||
#run nodeshell test
|
||||
send_msg(2, "prepare for nodeshell script.");
|
||||
if ( &testxdsh(3)) {
|
||||
send_msg(0, "[$$]:Could not verify test results using xdsh...............");
|
||||
exit 1;
|
||||
}
|
||||
send_msg(2, "[$$]:Running nodesetshell test success...............");
|
||||
exit 1 if &run_nodeset_shell_test();
|
||||
}
|
||||
####################################
|
||||
####runcmd test for genesis
|
||||
@@ -148,6 +120,51 @@ if ($clear_env) {
|
||||
send_msg(2, "[$$]:Clear genesis test enviroment success...............");
|
||||
}
|
||||
##################################
|
||||
#run_nodeset_shell_test
|
||||
#################################
|
||||
sub run_nodeset_shell_test {
|
||||
send_msg(2, "[$$]:Running nodeset NODE shell test...............");
|
||||
`nodeset $noderange shell`;
|
||||
if ($?) {
|
||||
send_msg(0, "[$$]:nodeset $noderange shell failed...............");
|
||||
return 1;
|
||||
}
|
||||
`rpower $noderange boot`;
|
||||
if ($?) {
|
||||
send_msg(0, "[$$]:rpower $noderange failed...............");
|
||||
return 1;
|
||||
}
|
||||
send_msg(2, "Installing with \"nodeset $noderange shell\" for shell test");
|
||||
sleep 120; # wait 2 min for install to finish
|
||||
if (&wait_for_node_status("shell")) {
|
||||
send_msg(0, "[$$]:$noderange did not report the shell destiny...............");
|
||||
return 1;
|
||||
}
|
||||
#run nodeshell test
|
||||
send_msg(2, "prepare for nodeshell script.");
|
||||
if (&testxdsh(3)) {
|
||||
send_msg(0, "[$$]:Could not verify test results using xdsh...............");
|
||||
return 1;
|
||||
}
|
||||
send_msg(2, "[$$]:Running nodesetshell test success...............");
|
||||
return 0;
|
||||
}
|
||||
##################################
|
||||
#report_genesis_files
|
||||
#################################
|
||||
sub report_genesis_files {
|
||||
my ($arch) = @_;
|
||||
send_msg(2, "[$$]:Check if genesis packages are installed on mn...............");
|
||||
# The caller used to test $?, which holds the exit status of the last child process, not
|
||||
# this return value. A node with no genesis packages therefore reported success.
|
||||
if (&check_genesis_file($arch)) {
|
||||
send_msg(0, "genesis packages are not installed");
|
||||
return 1;
|
||||
}
|
||||
send_msg(2, "genesis packages are installed");
|
||||
return 0;
|
||||
}
|
||||
##################################
|
||||
#check_genesis_file
|
||||
#################################
|
||||
sub check_genesis_file {
|
||||
@@ -214,7 +231,7 @@ sub rungenesiscmd {
|
||||
else {
|
||||
send_msg(2, "Installing with \"$rinstall_cmd\" for runcmd test");
|
||||
sleep 120; # wait 2 min for install to finish
|
||||
wait_for_boot();
|
||||
$value = -1 if &wait_for_node_status("configuring");
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
@@ -257,13 +274,24 @@ sub rungenesisimg {
|
||||
} else {
|
||||
send_msg(2, "Installing with \"$rinstall_cmd\" for runimage test\n");
|
||||
sleep 120; # wait 2 min for install to finish
|
||||
wait_for_boot();
|
||||
$value = -1 if &wait_for_node_status("booting");
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
########################################
|
||||
####sleep while for xdsh $$CN could work
|
||||
#########################################
|
||||
##########################################
|
||||
####forget the node ssh host keys
|
||||
##########################################
|
||||
sub forget_host_keys {
|
||||
my ($noderange) = @_;
|
||||
# Genesis makes new host keys on every boot, and each case boots the node several times.
|
||||
# The stale known_hosts entry then makes ssh refuse the changed key, and xdsh cannot reach
|
||||
# the Genesis shell.
|
||||
system("makeknownhosts $noderange -r >/dev/null 2>&1");
|
||||
return 0;
|
||||
}
|
||||
sub testxdsh {
|
||||
my $value = shift;
|
||||
my $checkstring;
|
||||
@@ -285,6 +313,8 @@ sub testxdsh {
|
||||
return 1;
|
||||
}
|
||||
|
||||
&forget_host_keys($noderange);
|
||||
|
||||
# Check shell prompt on the node to verify it is running Genesis
|
||||
`xdsh $noderange -t 2 "echo \\\$PS1" | grep "Genesis"`;
|
||||
if ($?) {
|
||||
@@ -353,8 +383,9 @@ sub clearenv {
|
||||
`cat $nodestanza | chdef -z`;
|
||||
unlink("$nodestanza");
|
||||
}
|
||||
# "rinstall <node> boot" boots the node from its disk, which carries no operating system,
|
||||
# so the node reports no destiny and nodelist.status stays at powering-on. Only wait.
|
||||
sleep 120; # wait 2 min for reboot to finish
|
||||
wait_for_boot();
|
||||
return 0;
|
||||
}
|
||||
####################################
|
||||
@@ -365,7 +396,10 @@ sub get_os {
|
||||
my $output = `cat /etc/*release* 2>&1`;
|
||||
if ($output =~ /suse/i) {
|
||||
$os = "sles";
|
||||
} elsif ($output =~ /Red Hat/i) {
|
||||
} elsif ($output =~ /Red Hat/i
|
||||
or $output =~ /\b(?:almalinux|rocky|centos|fedora|oracle\s+linux)\b/i
|
||||
or $output =~ /^ID_LIKE=.*\brhel\b/mi) {
|
||||
# AlmaLinux and Rocky release files name neither Red Hat nor themselves as one.
|
||||
$os = "redhat";
|
||||
} elsif ($output =~ /ubuntu/i) {
|
||||
$os = "ubuntu";
|
||||
@@ -423,9 +457,10 @@ sub send_msg {
|
||||
|
||||
}
|
||||
#########################################
|
||||
### Wait for node to be in "booted" state
|
||||
### Wait for the node to report the status its destiny implies
|
||||
##########################################
|
||||
sub wait_for_boot {
|
||||
sub wait_for_node_status {
|
||||
my ($expected) = @_;
|
||||
my $iterations = 30; # Max wait 30x10 = 5 min
|
||||
my $sleep_interval = 10;
|
||||
my $boot_status;
|
||||
@@ -433,11 +468,11 @@ sub wait_for_boot {
|
||||
foreach my $i (1..$iterations) {
|
||||
$boot_status = `lsdef $noderange -i status -c | cut -d'=' -f2`;
|
||||
chop($boot_status);
|
||||
if ($boot_status eq "booted") {
|
||||
if ($boot_status eq $expected) {
|
||||
return 0;
|
||||
}
|
||||
sleep $sleep_interval;
|
||||
}
|
||||
print "After $iterations iterations node status: $boot_status \n";
|
||||
print "After $iterations iterations node status: $boot_status, expected $expected \n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -16,14 +16,49 @@ function runcmd(){
|
||||
# We should be using private networks
|
||||
TESTNODE=testnode
|
||||
TESTNODE_IP="192.168.3.1"
|
||||
# nodeset resolves the genesis kernel by the node arch, so the node takes this machine's.
|
||||
TESTNODE_ARCH="$(uname -m)"
|
||||
# The boot-loader configuration lives under the tftp root. Overridable so the check can run
|
||||
# against a scratch tree.
|
||||
TFTPDIR="${TFTPDIR:-/tftpboot}"
|
||||
|
||||
# grub2.pm names the boot loader grub2.<arch>, with every ppc64 flavour written as "ppc".
|
||||
TESTNODE_LOADER_ARCH="$TESTNODE_ARCH"
|
||||
[[ $TESTNODE_LOADER_ARCH =~ ^ppc64 ]] && TESTNODE_LOADER_ARCH="ppc"
|
||||
STAGED_BOOT_LOADER=""
|
||||
|
||||
MASTER_PRIVATE_IP="192.168.1.1"
|
||||
MASTER_PRIVATE_NETMASK="255.255.0.0"
|
||||
MASTER_PRIVATE_NETWORK="192_168_0_0-255_255_0_0"
|
||||
|
||||
|
||||
# xCAT builds no grub2 network boot loader for x86_64 or aarch64. The administrator installs
|
||||
# grub2.<arch> by hand -- docs/source/guides/install-guides/yum/grub2.rst. grub2.pm stops the
|
||||
# configuration when the file is absent, and this case reads the configuration only.
|
||||
function stage_boot_loader() {
|
||||
local loader="$TFTPDIR/boot/grub2/grub2.$TESTNODE_LOADER_ARCH";
|
||||
if [[ -e $loader ]];then
|
||||
return 0;
|
||||
fi
|
||||
mkdir -p "$TFTPDIR/boot/grub2" || return 1;
|
||||
: > "$loader" || return 1;
|
||||
STAGED_BOOT_LOADER="$loader";
|
||||
echo "Staged an empty boot loader at $loader for the check";
|
||||
return 0;
|
||||
}
|
||||
|
||||
function unstage_boot_loader() {
|
||||
if [[ -z $STAGED_BOOT_LOADER ]];then
|
||||
return 0;
|
||||
fi
|
||||
# grub2.pm links grub2-<node> to the loader. Remove the link with the file it points at.
|
||||
rm -f "$STAGED_BOOT_LOADER" "$TFTPDIR/boot/grub2/grub2-${TESTNODE}";
|
||||
STAGED_BOOT_LOADER="";
|
||||
return 0;
|
||||
}
|
||||
|
||||
function check_destiny() {
|
||||
cmd="chdef ${TESTNODE} arch=ppc64le cons=ipmi groups=all ip=${TESTNODE_IP} mac=4e:ee:ee:ee:ee:0e netboot=$NETBOOT tftpserver=$MASTER_PRIVATE_IP xcatmaster=$MASTER_PRIVATE_IP";
|
||||
cmd="chdef ${TESTNODE} arch=${TESTNODE_ARCH} cons=ipmi groups=all ip=${TESTNODE_IP} mac=4e:ee:ee:ee:ee:0e netboot=$NETBOOT tftpserver=$MASTER_PRIVATE_IP xcatmaster=$MASTER_PRIVATE_IP";
|
||||
runcmd $cmd;
|
||||
lsdef ${TESTNODE}
|
||||
|
||||
@@ -52,8 +87,15 @@ function check_destiny() {
|
||||
grep ${TESTNODE} /etc/hosts
|
||||
cmd="nodeset ${TESTNODE} shell";
|
||||
runcmd $cmd;
|
||||
# grub2.pm writes the boot configuration and only then stops on a missing boot loader,
|
||||
# so the file the check reads below exists even when nodeset failed.
|
||||
nodeset_rc=$?;
|
||||
cmd="ip addr del $MASTER_PRIVATE_IP/$MASTER_PRIVATE_NETMASK dev $NET2";
|
||||
runcmd $cmd;
|
||||
if [[ $nodeset_rc -ne 0 ]];then
|
||||
echo "'nodeset ${TESTNODE} shell' FAILED";
|
||||
return 1;
|
||||
fi
|
||||
echo "Check if 'nodeset ${TESTNODE} shell' is added to ${SHELLFOLDER}/${TESTNODE}"
|
||||
echo "==============================================="
|
||||
cat "${SHELLFOLDER}/${TESTNODE}"
|
||||
@@ -86,14 +128,17 @@ while [ "$#" -ge "0" ]; do
|
||||
"--check" )
|
||||
NETBOOT=$2;
|
||||
if [[ $NETBOOT =~ petitboot ]];then
|
||||
SHELLFOLDER="/tftpboot/petitboot";
|
||||
SHELLFOLDER="$TFTPDIR/petitboot";
|
||||
elif [[ $NETBOOT =~ xnba ]];then
|
||||
SHELLFOLDER="/tftpboot/xcat/xnba/nodes"
|
||||
SHELLFOLDER="$TFTPDIR/xcat/xnba/nodes"
|
||||
else
|
||||
SHELLFOLDER="/tftpboot/boot/grub2";
|
||||
SHELLFOLDER="$TFTPDIR/boot/grub2";
|
||||
stage_boot_loader || exit 1;
|
||||
fi
|
||||
check_destiny ;
|
||||
if [[ $? -eq 1 ]];then
|
||||
rc=$?;
|
||||
unstage_boot_loader;
|
||||
if [[ $rc -eq 1 ]];then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
|
||||
@@ -57,7 +57,7 @@ check:rc==0
|
||||
cmd:if [ "__GETNODEATTR($$CN,mgt)__" == "kvm" ]; then str=`/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/retry_install.sh $$CN __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute 1`; if [[ $str =~ "failed" ]]; then exit 0; else exit 1; fi; fi
|
||||
check:rc==0
|
||||
|
||||
cmd:if [ "__GETNODEATTR($$CN,mgt)__" == "kvm" ]; then str1=`lsdef $$CN | grep vmothersetting | cut -d '=' -f 2`;str2="machine:invalid"; if [[ "__GETNODEATTR($$CN,arch)__" =~ "ppc64" ]]; then str3="machine:pseries-rhel7.6.0"; elif [[ "__GETNODEATTR($$CN,arch)__" =~ "x86_64" ]]; then str3="machine:pc";fi; if [ $str1 == $str2 ]; then str5=$str3; else str4=`echo $str1 | sed -e "s/$str2//"`;str5=$str4$str3;fi; chdef $$CN vmothersetting=$str5; fi
|
||||
cmd:if [ "__GETNODEATTR($$CN,mgt)__" == "kvm" ]; then str1=`lsdef $$CN | grep vmothersetting | cut -d '=' -f 2`;str2="machine:invalid"; if [[ "__GETNODEATTR($$CN,arch)__" =~ "ppc64" ]]; then str3="machine:pseries-rhel7.6.0"; elif [[ "__GETNODEATTR($$CN,arch)__" =~ "x86_64" ]]; then str3="machine:pc"; elif [[ "__GETNODEATTR($$CN,arch)__" =~ "riscv64" ]]; then str3="machine:virt";fi; if [ "$str1" == "$str2" ]; then str5=$str3; else str4=`echo $str1 | sed -e "s/$str2//"`;str5=$str4$str3;fi; chdef $$CN vmothersetting=$str5; fi
|
||||
check:rc==0
|
||||
cmd:if [ "__GETNODEATTR($$CN,mgt)__" == "kvm" ]; then str=`lsdef $$CN | grep vmothersetting`; if [[ $str =~ "machine" ]]; then exit 0; else exit 1; fi; fi
|
||||
check:rc==0
|
||||
@@ -68,7 +68,7 @@ check:rc==0
|
||||
cmd:if [ "__GETNODEATTR($$CN,mgt)__" == "kvm" ]; then str=`/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/retry_install.sh $$CN __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute`; echo $str; if [[ $str =~ "The provision succeeded" ]]; then exit 0; else exit 1; fi; fi
|
||||
check:rc==0
|
||||
|
||||
cmd:if [ "__GETNODEATTR($$CN,mgt)__" == "kvm" ]; then str1=`lsdef $$CN | grep vmothersetting | cut -d '=' -f 2`;str2=";"; if [[ "__GETNODEATTR($$CN,arch)__" =~ "ppc64" ]]; then str3="machine:pseries-7.6.0"; elif [[ "__GETNODEATTR($$CN,arch)__" =~ "x86_64" ]]; then str3="machine:pc";fi; if [ $str1 == $str3 ]; then chdef $$CN vmothersetting=; else str4=`echo $str1 | sed -e "s/$str2$str3//"`; chdef $$CN vmothersetting=$str4;fi; fi
|
||||
cmd:if [ "__GETNODEATTR($$CN,mgt)__" == "kvm" ]; then str1=`lsdef $$CN | grep vmothersetting | cut -d '=' -f 2`;str2=";"; if [[ "__GETNODEATTR($$CN,arch)__" =~ "ppc64" ]]; then str3="machine:pseries-rhel7.6.0"; elif [[ "__GETNODEATTR($$CN,arch)__" =~ "x86_64" ]]; then str3="machine:pc"; elif [[ "__GETNODEATTR($$CN,arch)__" =~ "riscv64" ]]; then str3="machine:virt";fi; if [ "$str1" == "$str3" ]; then chdef $$CN vmothersetting=; else str4=`echo $str1 | sed -e "s/$str2$str3//"`; chdef $$CN vmothersetting=$str4;fi; fi
|
||||
check:rc==0
|
||||
cmd:if [ "__GETNODEATTR($$CN,arch)__" != "ppc64" -a "__GETNODEATTR($$CN,mgt)__" != "ipmi" -a "__GETNODEATTR($$CN,mgt)__" != "openbmc" ];then echo "CN node $$CN is a VM, mgt is __GETNODEATTR($$CN,mgt)__, starting to recreate the vm"; echo "rpower $$CN off"; rpower $$CN off; sleep 3; echo "rpower $$CN stat"; rpower $$CN stat; var=`expr substr "__GETNODEATTR($$CN,vmstorage)__" 1 3`; echo "The disk type of $$CN is $var"; if [ "$var" = "phy" ]; then echo "mkvm $$CN"; mkvm $$CN; echo "rmvm $$CN -f -p"; rmvm $$CN -f -p; echo "mkvm $$CN"; mkvm $$CN; exit $?; elif [ "$var" = "dir" ]; then echo "mkvm $$CN -s 30G -f"; mkvm $$CN -s 30G -f; echo "rmvm $$CN -f -p"; rmvm $$CN -f -p; echo "mkvm $$CN -s 30G -f"; mkvm $$CN -s 30G -f; exit $?; elif ["$var" = "nfs" -o "$var" = "lvm" ];then echo "Need to fix disk type $var"; exit 2; else echo "Unsupported disk type $var"; exit 3;fi;else echo "CN node $$CN is not a VM; do not need to recreate it";fi
|
||||
check:rc==0
|
||||
|
||||
@@ -2,7 +2,7 @@ start:lsxcatd_null
|
||||
description:lsxcatd without any flag
|
||||
label:mn_only,ci_test,xcatd
|
||||
cmd:lsxcatd
|
||||
check:output=~lsxcatd
|
||||
check:output=~\[-v\|--version\]
|
||||
end
|
||||
|
||||
start:lsxcatd_h
|
||||
|
||||
@@ -78,7 +78,7 @@ description:for hwconn
|
||||
label:others,hctrl_fsp
|
||||
cmd:rmhwconn $$CN
|
||||
check:rc==0
|
||||
check:rc!~(state=LINE UP)
|
||||
check:output!~(state=LINE UP)
|
||||
cmd:mkhwconn $$CN -t
|
||||
check:rc==0
|
||||
cmd:sleep 40
|
||||
@@ -87,7 +87,7 @@ check:rc==0
|
||||
check:output=~(LINE UP)
|
||||
cmd:rmhwconn blade
|
||||
check:rc==0
|
||||
check:rc!~(state=LINE UP)
|
||||
check:output!~(state=LINE UP)
|
||||
cmd:mkhwconn blade -t
|
||||
check:rc==0
|
||||
cmd:sleep 50
|
||||
|
||||
@@ -21,9 +21,9 @@ cmd:ls /install/autoinst/testnode1*
|
||||
check:output=~No such file or directory
|
||||
cmd:ls /install/autoinst/testnode2*
|
||||
check:output=~No such file or directory
|
||||
cmd:ping testnode1
|
||||
cmd:ping -c 1 -w 2 testnode1
|
||||
check:rc!=0
|
||||
cmd:ping testnode2
|
||||
cmd:ping -c 1 -w 2 testnode2
|
||||
check:rc!=0
|
||||
end
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ cmd:echo "test" > /tmp/pscp.tmp
|
||||
check:rc==0
|
||||
cmd:pscp /tmp/pscp.tmp $$CN:/tmp/
|
||||
check:rc==0
|
||||
check:$$CN: done
|
||||
check:output=~$$CN: done
|
||||
cmd:xdsh $$CN "ls -l /tmp |grep pscp.tmp"
|
||||
check:rc==0
|
||||
check:output=~pscp.tmp
|
||||
@@ -41,7 +41,7 @@ cmd:echo "test" > /tmp/pscp/pscp.tmp
|
||||
check:rc==0
|
||||
cmd:pscp -r /tmp/pscp $$CN:/tmp/
|
||||
check:rc==0
|
||||
check:$$CN: done
|
||||
check:output=~$$CN: done
|
||||
cmd:xdsh $$CN "ls -l /tmp |grep pscp"
|
||||
check:rc==0
|
||||
check:output=~pscp
|
||||
|
||||
@@ -77,7 +77,7 @@ cmd:rmdef $$CN
|
||||
cmd:rscan __GETNODEATTR(testnode,hcp)__ -z -w
|
||||
check:rc==0
|
||||
check:output=~parent=[\w-]+
|
||||
check:lsdef -l $$CN
|
||||
cmd:lsdef -l $$CN
|
||||
check:rc==0
|
||||
check:output=~parent=[\w-]+
|
||||
cmd:rmdef all
|
||||
|
||||
@@ -83,9 +83,9 @@ start:updatenode_diskful_syncfiles_dir
|
||||
label:others,updatenode
|
||||
cmd:mkdir -p /tmp/sync/
|
||||
check:rc==0
|
||||
cmd:echo "test1" > /tmp/sync/test1.txt
|
||||
cmd:echo "syncdata1" > /tmp/sync/test1.txt
|
||||
check:rc==0
|
||||
cmd:echo "test2" > /tmp/sync/test2.txt
|
||||
cmd:echo "syncdata2" > /tmp/sync/test2.txt
|
||||
check:rc==0
|
||||
cmd:echo "/tmp/sync/* -> /tmp/" > /install/custom/install/__GETNODEATTR($$CN,os)__/compute.$$OS.synclist
|
||||
check:rc==0
|
||||
@@ -97,9 +97,9 @@ cmd:xdsh $$CN "ls -l /tmp"
|
||||
check:output=~test1.txt
|
||||
check:output=~test2.txt
|
||||
cmd:xdsh $$CN "cat /tmp/test1.txt"
|
||||
check:output=~test1
|
||||
check:output=~syncdata1
|
||||
cmd:xdsh $$CN "cat /tmp/test2.txt"
|
||||
check:output=~test2
|
||||
check:output=~syncdata2
|
||||
cmd:xdsh $$CN "rm -rf /tmp/test1.txt /tmp/test2.txt"
|
||||
check:rc==0
|
||||
cmd:chdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-install-compute synclists=
|
||||
|
||||
@@ -1040,8 +1040,8 @@ cmd:dir="/opt/inventory/site/osimage";if [ -e "${dir}" ];then mv ${dir} ${dir}".
|
||||
cmd:xcat-inventory export -t osimage -o test_myimage1,test_myimage2 --format json -d /opt/inventory/site/osimage
|
||||
check:rc==0
|
||||
check:output=~The osimage objects has been exported to directory /opt/inventory/site/osimage
|
||||
cmd: ls -R /opt/inventory/site/osimage
|
||||
check: output =~ site
|
||||
cmd:ls -R /opt/inventory/site/osimage
|
||||
check:output=~test_myimage1
|
||||
cmd:otherpkglist=`lsdef -t osimage -o test_myimage1 |grep otherpkglist|awk -F= '{print $2}'`;diff -y $otherpkglist /opt/inventory/site/osimage/test_myimage1$otherpkglist
|
||||
check:rc==0
|
||||
cmd:synclists=`lsdef -t osimage -o test_myimage1 |grep synclists|awk -F= '{print $2}'`;diff -y $synclists /opt/inventory/site/osimage/test_myimage1$synclists
|
||||
@@ -1073,8 +1073,8 @@ check:rc==0
|
||||
cmd: rmdef -t osimage -o test_myimage1,test_myimage2
|
||||
check:rc==0
|
||||
cmd:rm -rf /tmp/otherpkglist /tmp/synclists /tmp/postinstall /tmp/exlist /tmp/partitionfile /tmp/pkglist /tmp/template
|
||||
cmd: ls -R /opt/inventory/site
|
||||
check: output =~ site
|
||||
cmd:ls -R /opt/inventory/site
|
||||
check:output=~test_myimage1
|
||||
cmd:xcat-inventory import -t osimage -o test_myimage1,test_myimage2 -d /opt/inventory/site/osimage
|
||||
check:rc==0
|
||||
check:output=~The object test_myimage1 has been imported
|
||||
|
||||
@@ -42,11 +42,11 @@ start:xdcp_RP
|
||||
label:cn_os_ready,parallel_cmds
|
||||
cmd:xdsh $$CN "mkdir -p /tmp/xdcp/test1"
|
||||
check:rc==0
|
||||
cmd:xdsh $$CN "echo "test1" > /tmp/xdcp/test1/test1.txt"
|
||||
cmd:xdsh $$CN "echo "xdcpdata1" > /tmp/xdcp/test1/test1.txt"
|
||||
check:rc==0
|
||||
cmd:xdsh $$CN "mkdir -p /tmp/xdcp/test2"
|
||||
check:rc==0
|
||||
cmd:xdsh $$CN "echo "test2" > /tmp/xdcp/test2/test2.txt"
|
||||
cmd:xdsh $$CN "echo "xdcpdata2" > /tmp/xdcp/test2/test2.txt"
|
||||
check:rc==0
|
||||
cmd:xdcp $$CN -RP /tmp/xdcp /tmp
|
||||
check:rc==0
|
||||
@@ -58,9 +58,9 @@ check:output=~test1.txt
|
||||
cmd:ls -l /tmp/xdcp._$$CN/test2
|
||||
check:output=~test2.txt
|
||||
cmd:cat /tmp/xdcp._$$CN/test1/test1.txt
|
||||
check:output=~test1
|
||||
check:output=~xdcpdata1
|
||||
cmd:cat /tmp/xdcp._$$CN/test2/test2.txt
|
||||
check:output=~test2
|
||||
check:output=~xdcpdata2
|
||||
cmd:xdsh $$CN "rm -rf /tmp/xdcp"
|
||||
check:rc==0
|
||||
cmd:rm -rf /tmp/xdcp._$$CN
|
||||
@@ -71,11 +71,11 @@ start:xdcp_R
|
||||
label:cn_os_ready,parallel_cmds
|
||||
cmd:mkdir -p /tmp/xdcp/test1
|
||||
check:rc==0
|
||||
cmd:echo "test1" > /tmp/xdcp/test1/test1.txt
|
||||
cmd:echo "xdcpdata1" > /tmp/xdcp/test1/test1.txt
|
||||
check:rc==0
|
||||
cmd:mkdir -p /tmp/xdcp/test2
|
||||
check:rc==0
|
||||
cmd:echo "test2" > /tmp/xdcp/test2/test2.txt
|
||||
cmd:echo "xdcpdata2" > /tmp/xdcp/test2/test2.txt
|
||||
check:rc==0
|
||||
cmd:xdcp $$CN -R /tmp/xdcp /tmp
|
||||
check:rc==0
|
||||
@@ -89,9 +89,9 @@ check:output=~test1.txt
|
||||
cmd:xdsh $$CN "ls -l /tmp/xdcp/test2"
|
||||
check:output=~test2.txt
|
||||
cmd:xdsh $$CN "cat /tmp/xdcp/test1/test1.txt"
|
||||
check:output=~test1
|
||||
check:output=~xdcpdata1
|
||||
cmd:xdsh $$CN "cat /tmp/xdcp/test2/test2.txt"
|
||||
check:output=~test2
|
||||
check:output=~xdcpdata2
|
||||
cmd:xdsh $$CN "rm -rf /tmp/xdcp"
|
||||
check:rc==0
|
||||
cmd:rm -rf /tmp/xdcp
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# reg_linux_diskless_installation_flat corrupts the KVM machine type of the compute node, checks
|
||||
# that the node fails to boot, and then restores it. The restore reads a machine type from a
|
||||
# ladder that names ppc64 and x86_64 only, so on any other architecture it writes an empty
|
||||
# vmothersetting and the check after it fails.
|
||||
#
|
||||
# The two commands are lifted out of the case file and RUN, with lsdef and chdef shadowed, so
|
||||
# the assertions read the value the case would write. The extraction fails the test when it
|
||||
# stops matching, so a rewrite fails loudly instead of covering nothing.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
CASE="$(repo_path 'xCAT-test/autotest/testcase/installation/reg_linux_diskless_installation_flat')"
|
||||
[ -r "$CASE" ] || skip "$CASE is required"
|
||||
export CASE
|
||||
}
|
||||
|
||||
# The command that restores the machine type, and the one that removes it again afterwards.
|
||||
restore_command()
|
||||
{
|
||||
extract_first_matching_line "$CASE" \
|
||||
'^cmd:.*str2="machine:invalid".*chdef [$][$]CN vmothersetting=[$]str5'
|
||||
}
|
||||
|
||||
remove_command()
|
||||
{
|
||||
extract_first_matching_line "$CASE" \
|
||||
'^cmd:.*str2=";".*~ "ppc64".*chdef [$][$]CN vmothersetting='
|
||||
}
|
||||
|
||||
# Render one command the way xcattest does, then run it with lsdef and chdef shadowed. bash
|
||||
# resolves a function ahead of PATH, so the case's own backticks read the stub.
|
||||
#
|
||||
# Sets OUT to everything the command printed and WRITTEN to the value it gave chdef.
|
||||
run_case_command()
|
||||
{
|
||||
local cmd="$1" arch="$2" lsdef_value="$3"
|
||||
|
||||
cmd="${cmd#cmd:}"
|
||||
cmd="${cmd//__GETNODEATTR(\$\$CN,arch)__/$arch}"
|
||||
cmd="${cmd//__GETNODEATTR(\$\$CN,mgt)__/kvm}"
|
||||
cmd="${cmd//\$\$CN/cn1}"
|
||||
|
||||
OUT="$(bash -c "lsdef() { echo ' vmothersetting=$lsdef_value'; }
|
||||
chdef() { echo \"CHDEF:[\$*]\"; }
|
||||
$cmd" 2>&1)" || true
|
||||
# The brackets keep an empty value apart from no call at all: the cleanup is meant to write
|
||||
# an empty vmothersetting, and a command that never reaches chdef must not read as that.
|
||||
CHDEF_CALLS="$(grep -c '^CHDEF:' <<<"$OUT" || true)"
|
||||
WRITTEN="$(sed -n 's/^CHDEF:\[cn1 vmothersetting=\(.*\)\]$/\1/p' <<<"$OUT")"
|
||||
}
|
||||
|
||||
# The machine type each architecture must end up with. riscv64 guests run the qemu "virt"
|
||||
# machine; kvm.pm sets it in guest_arch_profile.
|
||||
#
|
||||
# The restore writes the machine type; the cleanup after it takes the same machine type away
|
||||
# again and leaves every other setting. Both read the same ladder, so both are checked against
|
||||
# the same value.
|
||||
assert_arch()
|
||||
{
|
||||
local arch="$1" machine="$2" restore remove
|
||||
|
||||
restore="$(restore_command)"
|
||||
remove="$(remove_command)"
|
||||
|
||||
# The node carries the corrupt value only.
|
||||
run_case_command "$restore" "$arch" 'machine:invalid'
|
||||
[ "$CHDEF_CALLS" -eq 1 ]
|
||||
[ "$WRITTEN" = "machine:$machine" ]
|
||||
|
||||
# The node carries a setting of its own beside the corrupt value.
|
||||
run_case_command "$restore" "$arch" 'cpumode:host-passthrough;machine:invalid'
|
||||
[ "$CHDEF_CALLS" -eq 1 ]
|
||||
[ "$WRITTEN" = "cpumode:host-passthrough;machine:$machine" ]
|
||||
|
||||
# The cleanup, with nothing but the machine type to remove.
|
||||
run_case_command "$remove" "$arch" "machine:$machine"
|
||||
[ "$(grep -c 'unary operator expected' <<<"$OUT")" -eq 0 ]
|
||||
[ "$CHDEF_CALLS" -eq 1 ]
|
||||
[ "$WRITTEN" = "" ]
|
||||
|
||||
# The cleanup, with a setting of its own that must survive it.
|
||||
run_case_command "$remove" "$arch" "cpumode:host-passthrough;machine:$machine"
|
||||
[ "$(grep -c 'unary operator expected' <<<"$OUT")" -eq 0 ]
|
||||
[ "$CHDEF_CALLS" -eq 1 ]
|
||||
[ "$WRITTEN" = "cpumode:host-passthrough" ]
|
||||
}
|
||||
|
||||
@test "ppc64le: the restore writes the machine type and the cleanup takes it away" {
|
||||
assert_arch ppc64le pseries-rhel7.6.0
|
||||
}
|
||||
|
||||
@test "x86_64: the restore writes the machine type and the cleanup takes it away" {
|
||||
assert_arch x86_64 pc
|
||||
}
|
||||
|
||||
@test "riscv64: the restore writes the machine type and the cleanup takes it away" {
|
||||
assert_arch riscv64 virt
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# debuild-xcat-genesis-base converts the EL Genesis base rpm to a deb. The rpm name carries the
|
||||
# Genesis target architecture, and the deb must carry the Debian architecture: ppc64 becomes
|
||||
# ppc64el, x86_64 becomes amd64. An unmapped architecture leaves the deb named after the rpm and
|
||||
# makes it break a genesis-scripts package that no repository publishes. The rename also has to
|
||||
# name the deb it supersedes, or an upgraded ppc node keeps xcat-genesis-base-ppc64 as well.
|
||||
#
|
||||
# The script is driven here with alien shadowed by a shell function.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
SCRIPT="$(repo_path 'xCAT-genesis-builder/debuild-xcat-genesis-base')"
|
||||
# Fail rather than skip: a checkout without the converter has no deb rename to measure,
|
||||
# and a skip there covers nothing while reading green.
|
||||
[ -r "$SCRIPT" ]
|
||||
export SCRIPT
|
||||
}
|
||||
|
||||
# alien names the deb after the rpm: lower case, and "_" written as "-".
|
||||
shadow_alien()
|
||||
{
|
||||
alien()
|
||||
{
|
||||
local rpm="${!#}"
|
||||
local name="${rpm##*/}"
|
||||
name="${name%.rpm}"
|
||||
local dir="${name%%-snap*}"
|
||||
local package="${dir%-*}"
|
||||
package="${package,,}"
|
||||
package="${package//_/-}"
|
||||
|
||||
mkdir -p "${dir}/debian"
|
||||
cat >"${dir}/debian/control" <<CONTROL
|
||||
Source: ${package}
|
||||
Section: alien
|
||||
Priority: extra
|
||||
Maintainer: xCAT <xcat-user@lists.sourceforge.net>
|
||||
|
||||
Package: ${package}
|
||||
Architecture: all
|
||||
Description: xCAT genesis base
|
||||
CONTROL
|
||||
printf '%s (%s) unstable; urgency=low\n' "${package}" "1.0" \
|
||||
>"${dir}/debian/changelog"
|
||||
printf '#!/usr/bin/make -f\nbinary:\n\t@true\n' >"${dir}/debian/rules"
|
||||
chmod 0755 "${dir}/debian/rules"
|
||||
}
|
||||
}
|
||||
|
||||
# Convert one rpm name. Sets SOURCE_DIR to the produced source directory and CONTROL to its
|
||||
# control file.
|
||||
convert()
|
||||
{
|
||||
local rpm="$1"
|
||||
local work="${BATS_TEST_TMPDIR}/convert"
|
||||
|
||||
rm -rf "$work"
|
||||
mkdir -p "$work"
|
||||
(
|
||||
shadow_alien
|
||||
cd "$work" || exit 1
|
||||
: >"$rpm"
|
||||
source "$SCRIPT" "$rpm" >/dev/null 2>&1
|
||||
)
|
||||
|
||||
SOURCE_DIR="$(find "$work" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | head -1)"
|
||||
[ -n "$SOURCE_DIR" ] || return 1
|
||||
CONTROL="$work/$SOURCE_DIR/debian/control"
|
||||
[ -f "$CONTROL" ] || return 1
|
||||
}
|
||||
|
||||
@test "the x86_64 rpm becomes the amd64 deb, and replaces the package it supersedes" {
|
||||
convert 'xCAT-genesis-base-x86_64-2.13.10-snap202601010000.noarch.rpm'
|
||||
|
||||
[[ "$SOURCE_DIR" == *-amd64-* ]]
|
||||
grep -qx 'Package: xcat-genesis-base-amd64' "$CONTROL"
|
||||
grep -qE '^Breaks:.*\bxcat-genesis-scripts-amd64\b' "$CONTROL"
|
||||
grep -qx 'Replaces: xcat-genesis-amd64' "$CONTROL"
|
||||
grep -qE '^Breaks: xcat-genesis-amd64\b' "$CONTROL"
|
||||
}
|
||||
|
||||
@test "the ppc64 rpm becomes the ppc64el deb, and replaces the deb the rename leaves behind" {
|
||||
convert 'xCAT-genesis-base-ppc64-2.13.10-snap202601010000.noarch.rpm'
|
||||
|
||||
[[ "$SOURCE_DIR" == *-ppc64el-* ]]
|
||||
grep -qx 'Package: xcat-genesis-base-ppc64el' "$CONTROL"
|
||||
grep -qE '^Breaks:.*\bxcat-genesis-scripts-ppc64el\b' "$CONTROL"
|
||||
grep -qx 'Replaces: xcat-genesis-ppc64, xcat-genesis-base-ppc64' "$CONTROL"
|
||||
grep -qE '^Breaks: xcat-genesis-ppc64, xcat-genesis-base-ppc64\b' "$CONTROL"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# builddeb-genesis-base builds the Genesis base deb natively on Ubuntu. It writes the target
|
||||
# architecture into debian/control, which is held in the amd64 form in the tree. 2.19 renames
|
||||
# the ppc64 debs to ppc64el, so the ppc control must also name the deb it supersedes: without
|
||||
# the relation dpkg keeps xcat-genesis-base-ppc64 installed beside the new package, and that
|
||||
# old package owns the same files under /opt/xcat/share/xcat/netboot/genesis.
|
||||
#
|
||||
# The script needs dracut and root, so rewrite_control() is lifted out of it and run alone
|
||||
# against the control file the tree ships.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
SCRIPT="$(repo_path 'xCAT-genesis-builder/builddeb-genesis-base')"
|
||||
CONTROL="$(repo_path 'xCAT-genesis-builder/debian/control')"
|
||||
[ -r "$SCRIPT" ] || skip "$SCRIPT is required"
|
||||
[ -r "$CONTROL" ] || skip "$CONTROL is required"
|
||||
export SCRIPT CONTROL
|
||||
}
|
||||
|
||||
# Run the lifted rewrite_control() over a copy of the control file in the tree, and print it.
|
||||
rewrite()
|
||||
{
|
||||
local arch="$1" function copy="${BATS_TEST_TMPDIR}/control.$1"
|
||||
|
||||
function="$(extract_shell_function "$SCRIPT" rewrite_control)" ||
|
||||
{ echo 'rewrite_control() no longer matches in builddeb-genesis-base' >&2; return 99; }
|
||||
cp "$CONTROL" "$copy"
|
||||
(
|
||||
set -eu
|
||||
eval "$function"
|
||||
rewrite_control "$copy" "$arch"
|
||||
) || return 1
|
||||
cat "$copy"
|
||||
}
|
||||
|
||||
@test "the amd64 control names the package and the genesis deb it took over from" {
|
||||
run rewrite amd64
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
[[ "$output" =~ (^|$'\n')"Package: xcat-genesis-base-amd64"($'\n'|$) ]]
|
||||
[[ "$output" =~ (^|$'\n')"Replaces: xcat-genesis-amd64"($'\n'|$) ]]
|
||||
[[ "$output" =~ (^|$'\n')"Breaks: xcat-genesis-amd64, " ]]
|
||||
[[ "$output" =~ "xcat-genesis-scripts-amd64 (<< 2.13.10)" ]]
|
||||
}
|
||||
|
||||
@test "the ppc64el control also takes over from the ppc64 deb the rename leaves behind" {
|
||||
run rewrite ppc64el
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
[[ "$output" =~ (^|$'\n')"Package: xcat-genesis-base-ppc64el"($'\n'|$) ]]
|
||||
[[ "$output" =~ (^|$'\n')"Replaces: xcat-genesis-ppc64, xcat-genesis-base-ppc64"($'\n'|$) ]]
|
||||
[[ "$output" =~ (^|$'\n')"Breaks: xcat-genesis-ppc64, xcat-genesis-base-ppc64, " ]]
|
||||
[[ "$output" =~ "xcat-genesis-scripts-ppc64el (<< 2.13.10)" ]]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Drive xcat_console_mode() out of the Genesis dracut cmdline hook.
|
||||
#
|
||||
# The hook cannot be sourced: it mounts filesystems, starts udev and ends in an endless
|
||||
# loop. Extract the one function and run it with the terminal multiplexer shadowed.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
EL_HOOK="$(repo_path 'xCAT-genesis-builder/dracut_105/el/xcat-cmdline.sh')"
|
||||
UBUNTU_HOOK="$(repo_path 'xCAT-genesis-builder/dracut_105/ubuntu/xcat-cmdline.sh')"
|
||||
[ -r "$EL_HOOK" ] || skip "$EL_HOOK is required"
|
||||
[ -r "$UBUNTU_HOOK" ] || skip "$UBUNTU_HOOK is required"
|
||||
export EL_HOOK UBUNTU_HOOK
|
||||
}
|
||||
|
||||
# Run the extracted function with the multiplexer shadowed by a stub that either starts a
|
||||
# session or refuses, the way tmux refuses without a UTF-8 locale.
|
||||
run_mode()
|
||||
{
|
||||
local hook="$1" mux="$2" mux_works="$3" body
|
||||
body="$(extract_shell_function "$hook" xcat_console_mode)" ||
|
||||
{ echo "xcat_console_mode() not found in $hook" >&2; return 99; }
|
||||
(
|
||||
eval "$body"
|
||||
eval "$mux() {
|
||||
[ \"\$mux_works\" = 1 ] && return 0
|
||||
echo '$mux: need UTF-8 locale (LC_CTYPE) but have ANSI_X3.4-1968' >&2
|
||||
return 1
|
||||
}"
|
||||
xcat_console_mode
|
||||
) 2>/dev/null
|
||||
}
|
||||
|
||||
# The hook reads the mode once and guards the doxcat loop with it.
|
||||
assert_hook_guards_doxcat()
|
||||
{
|
||||
local hook="$1" mux="$2"
|
||||
grep -qx 'XCAT_CONSOLE_MODE="$(xcat_console_mode)"' "$hook"
|
||||
grep -qFx "if [ \"\$XCAT_CONSOLE_MODE\" = \"$mux\" ]; then" "$hook"
|
||||
grep -A1 '^else$' "$hook" | grep -qx ' while :; do doxcat; sleep 5; done'
|
||||
}
|
||||
|
||||
@test "the el hook leaves no unguarded tmux loop and exports a UTF-8 locale" {
|
||||
# tmux exits under the C locale, so an unguarded tmux loop never reaches doxcat.
|
||||
refute_grep -q '^while :; do tmux attach-session' "$EL_HOOK"
|
||||
grep -qx 'export LC_ALL=C.UTF-8' "$EL_HOOK"
|
||||
}
|
||||
|
||||
@test "el: xcat_console_mode reports the mode tmux can actually provide" {
|
||||
[ "$(run_mode "$EL_HOOK" tmux 0)" = direct ]
|
||||
[ "$(run_mode "$EL_HOOK" tmux 1)" = tmux ]
|
||||
}
|
||||
|
||||
@test "el: the hook resolves the console mode once and runs doxcat directly without tmux" {
|
||||
assert_hook_guards_doxcat "$EL_HOOK" tmux
|
||||
}
|
||||
|
||||
@test "ubuntu: xcat_console_mode reports the mode screen can actually provide" {
|
||||
[ "$(run_mode "$UBUNTU_HOOK" screen 0)" = direct ]
|
||||
[ "$(run_mode "$UBUNTU_HOOK" screen 1)" = screen ]
|
||||
}
|
||||
|
||||
@test "ubuntu: the hook resolves the console mode once and runs doxcat directly without screen" {
|
||||
assert_hook_guards_doxcat "$UBUNTU_HOOK" screen
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Drive the DHCP client selection out of doxcat.
|
||||
#
|
||||
# doxcat cannot be sourced: it restarts rsyslogd, reads /proc/cmdline and ends in a loop that
|
||||
# waits for an address. Extract the two routines and run them with the clients shadowed by
|
||||
# stubs that record their own argv.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
ISC4='dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.eth0.pid eth0'
|
||||
ISC6='dhclient -6 -pf /var/run/dhclient6.eth0.pid eth0 -lf /var/lib/dhclient/dhclient6.leases'
|
||||
|
||||
setup()
|
||||
{
|
||||
DOXCAT="$(repo_path 'xCAT-genesis-scripts/usr/bin/doxcat')"
|
||||
SPEC="$(repo_path 'xCAT-genesis-builder/xCAT-genesis-base.spec')"
|
||||
MODULE="$(repo_path 'xCAT-genesis-builder/dracut_105/el/module-setup.sh')"
|
||||
[ -r "$DOXCAT" ] || skip "$DOXCAT is required"
|
||||
[ -r "$SPEC" ] || skip "$SPEC is required"
|
||||
[ -r "$MODULE" ] || skip "$MODULE is required"
|
||||
export DOXCAT SPEC MODULE
|
||||
}
|
||||
|
||||
# Run the extracted routines with only the named clients on PATH. Sets OUT to the standard
|
||||
# output, RAN to the recorded argv of whatever ran, and STATUS to the exit status.
|
||||
probe()
|
||||
{
|
||||
local call="$1"
|
||||
shift
|
||||
local dir="${BATS_TEST_TMPDIR}/probe"
|
||||
local bin="$dir/bin" record="$dir/record" client selector runner
|
||||
|
||||
selector="$(extract_shell_function "$DOXCAT" genesis_dhcp_command)" ||
|
||||
{ echo 'doxcat carries no genesis_dhcp_command() to choose the client' >&2; return 99; }
|
||||
runner="$(extract_shell_function "$DOXCAT" genesis_start_dhcp)" ||
|
||||
{ echo 'doxcat carries no genesis_start_dhcp() to run the chosen client' >&2; return 99; }
|
||||
|
||||
rm -rf "$dir"
|
||||
mkdir -p "$bin"
|
||||
|
||||
# PATH holds the stubs alone, so each one names itself rather than calling basename.
|
||||
for client in "$@"; do
|
||||
printf '#!/bin/sh\necho "%s $*" >> "%s"\nexit 0\n' "$client" "$record" >"$bin/$client"
|
||||
chmod 0755 "$bin/$client"
|
||||
done
|
||||
|
||||
# logger writes to the console in the image and is not what these assertions measure.
|
||||
printf '#!/bin/sh\nexit 0\n' >"$bin/logger"
|
||||
chmod 0755 "$bin/logger"
|
||||
|
||||
printf 'log_label=test\n%s\n%s\n%s\n' "$selector" "$runner" "$call" >"$dir/probe.sh"
|
||||
OUT="$(PATH="$bin" /bin/bash "$dir/probe.sh" 2>/dev/null)" && STATUS=0 || STATUS=$?
|
||||
RAN="$(read_file_or_empty "$record")"
|
||||
return 0
|
||||
}
|
||||
|
||||
selected()
|
||||
{
|
||||
local family="$1"
|
||||
shift
|
||||
probe "genesis_dhcp_command $family eth0" "$@"
|
||||
printf '%s\n' "$OUT"
|
||||
}
|
||||
|
||||
started()
|
||||
{
|
||||
local family="$1"
|
||||
shift
|
||||
probe "genesis_start_dhcp $family eth0" "$@"
|
||||
printf '%s\n' "$RAN"
|
||||
}
|
||||
|
||||
@test "doxcat names no DHCP client directly" {
|
||||
# A release that packages no ISC client has no dhclient.
|
||||
refute_grep -qE '^[[:space:]]*dhclient[[:space:]]' "$DOXCAT"
|
||||
refute_grep -qE ';[[:space:]]*dhclient[[:space:]]' "$DOXCAT"
|
||||
}
|
||||
|
||||
@test "the build root and the payload check name the client the release ships" {
|
||||
# EL8 and EL9 package the ISC client; AlmaLinux 10 baseos packages dhcpcd. The payload
|
||||
# check has to name the client too, or the build passes with no client in the image again.
|
||||
grep -A1 '^%if 0%{?rhel} >= 10$' "$SPEC" | grep -qx 'BuildRequires: dhcpcd'
|
||||
grep -A1 '^%if 0%{?rhel} >= 10$' "$SPEC" | grep -qx 'GENESIS_REQUIRED="usr/sbin/dhcpcd"'
|
||||
}
|
||||
|
||||
@test "the dracut module installs the client the build root carries" {
|
||||
# dracut_install reports a missing binary and returns, so naming dhclient alone shipped an
|
||||
# image with no client at all.
|
||||
refute_grep -qE '^[[:space:]]*dracut_install dhclient lldpad$' "$MODULE"
|
||||
grep -qE '^[[:space:]]*dracut_install dhcpcd$' "$MODULE"
|
||||
grep -qE '^[[:space:]]*dracut_install /usr/libexec/dhcpcd-run-hooks$' "$MODULE"
|
||||
}
|
||||
|
||||
@test "the ISC client keeps its command lines and is preferred when both are present" {
|
||||
[ "$(selected 4 dhclient)" = "$ISC4" ]
|
||||
[ "$(selected 6 dhclient)" = "$ISC6" ]
|
||||
[ "$(selected 4 dhclient dhcpcd)" = "$ISC4" ]
|
||||
}
|
||||
|
||||
@test "dhcpcd stands in for dhclient, waiting for a lease and keeping the address" {
|
||||
# dhcpcd on a single interface exits when its timeout expires, and the default is 30
|
||||
# seconds; doxcat waits for the lease for as long as it takes. dhcpcd also de-configures
|
||||
# the interface when it exits unless it is persistent.
|
||||
[ "$(selected 4 dhcpcd)" = 'dhcpcd -4 -b -p -t 0 eth0' ]
|
||||
[ "$(selected 6 dhcpcd)" = 'dhcpcd -6 -b -p -t 0 eth0' ]
|
||||
[[ "$(selected 4 dhcpcd)" =~ (^|[[:space:]])-t\ 0([[:space:]]|$) ]]
|
||||
[[ "$(selected 4 dhcpcd)" =~ (^|[[:space:]])-p([[:space:]]|$) ]]
|
||||
}
|
||||
|
||||
@test "an image with no client chooses nothing, runs nothing and reports a failure" {
|
||||
[ "$(selected 4)" = '' ]
|
||||
[ "$(started 4)" = '' ]
|
||||
|
||||
probe 'genesis_start_dhcp 4 eth0'
|
||||
[ "$STATUS" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "genesis_start_dhcp runs the client it chose" {
|
||||
[ "$(started 4 dhcpcd)" = 'dhcpcd -4 -b -p -t 0 eth0' ]
|
||||
[ "$(started 4 dhclient)" = "$ISC4" ]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Drive getcert with openssl absent, and with a certificate key that is not ready yet.
|
||||
# doxcat runs getcert in the foreground and ignores its status, so a wait with no bound stops
|
||||
# the boot and prints nothing.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
GETCERT="$(repo_path 'xCAT-genesis-scripts/usr/bin/getcert')"
|
||||
[ -r "$GETCERT" ] || skip "$GETCERT is required"
|
||||
COUNTER="${BATS_TEST_TMPDIR}/req-count"
|
||||
export GETCERT COUNTER
|
||||
}
|
||||
|
||||
write_stub()
|
||||
{
|
||||
local dir="$1" name="$2" body="$3"
|
||||
printf '#!/bin/sh\n%s\n' "$body" >"$dir/$name"
|
||||
chmod 0755 "$dir/$name"
|
||||
}
|
||||
|
||||
# A PATH directory holding the commands getcert runs. openssl is absent unless it is asked for.
|
||||
stub_dir()
|
||||
{
|
||||
local with_openssl="${1:-0}" count=""
|
||||
local dir="${BATS_TEST_TMPDIR}/bin"
|
||||
|
||||
rm -rf "$dir"
|
||||
mkdir -p "$dir"
|
||||
write_stub "$dir" allowcred.awk 'exec sleep 3'
|
||||
write_stub "$dir" hostname 'echo node1'
|
||||
write_stub "$dir" logger 'echo "$@" >&2'
|
||||
write_stub "$dir" sleep 'exec /bin/sleep "$@"'
|
||||
if [ "$with_openssl" = 1 ]; then
|
||||
[ -n "${COUNT_REQUESTS:-}" ] && count="echo req >> '$COUNTER'"
|
||||
write_stub "$dir" openssl "[ \"\$1\" = req ] && { $count ; exit 1; }
|
||||
exit 0"
|
||||
fi
|
||||
printf '%s\n' "$dir"
|
||||
}
|
||||
|
||||
# Run getcert with only the stub directory on PATH. The timeout is the harness guard: a status
|
||||
# of 124 means getcert never stopped.
|
||||
run_getcert()
|
||||
{
|
||||
local bin="$1" limit="$2" csr_timeout="$3"
|
||||
timeout -k 2 "$limit" env PATH="$bin" GETCERT_CSR_TIMEOUT="$csr_timeout" \
|
||||
/bin/bash "$GETCERT" 192.0.2.1:3001 2>&1 </dev/null
|
||||
}
|
||||
|
||||
@test "getcert stops and names openssl when the image ships none" {
|
||||
# The el10 legacy image ships no openssl.
|
||||
run run_getcert "$(stub_dir 0)" 10 60
|
||||
[ "$status" -ne 124 ]
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *openssl* ]]
|
||||
}
|
||||
|
||||
@test "getcert retries the certificate request, then gives up and names the key" {
|
||||
# doxcat writes /etc/xcat/certkey.pem in the background, so the first requests can fail.
|
||||
export COUNT_REQUESTS=1
|
||||
run run_getcert "$(stub_dir 1)" 30 5
|
||||
[ "$status" -ne 124 ]
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *certkey.pem* ]]
|
||||
|
||||
tries=0
|
||||
[ -f "$COUNTER" ] && tries="$(grep -c req "$COUNTER")"
|
||||
[ "$tries" -gt 1 ]
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Run the nodeset_shell_incorrectmasterip check against a scratch tftp root, with the xCAT
|
||||
# commands and the net tools shadowed.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
SCRIPT="$(repo_path 'xCAT-test/autotest/testcase/genesis/test.sh')"
|
||||
[ -r "$SCRIPT" ] || skip "$SCRIPT is required"
|
||||
HOST_ARCH="$(uname -m)"
|
||||
# grub2.pm names the boot loader grub2.<arch>, with every ppc64 flavour written as "ppc".
|
||||
case "$HOST_ARCH" in
|
||||
ppc64*) LOADER_NAME=ppc ;;
|
||||
*) LOADER_NAME="$HOST_ARCH" ;;
|
||||
esac
|
||||
export SCRIPT HOST_ARCH LOADER_NAME
|
||||
}
|
||||
|
||||
# Run `test.sh --check <loader>` against a scratch tftp root. test.sh resets PATH, so the xCAT
|
||||
# commands are shadowed with shell functions, which bash resolves first. The fake nodeset writes
|
||||
# the boot file the check greps, so the assertion is on the check, not on xCAT.
|
||||
#
|
||||
# Sets STATUS, OUTPUT, CHDEF, LOADER_AT_NODESET and LOADER_LEFT.
|
||||
run_check()
|
||||
{
|
||||
local loader="$1" write_boot_file="$2" nodeset_status="${3:-0}"
|
||||
local root="${BATS_TEST_TMPDIR}/$loader-$write_boot_file-$nodeset_status"
|
||||
local tftp="$root/tftpboot"
|
||||
local boot_loader="$tftp/boot/grub2/grub2.$LOADER_NAME"
|
||||
local folder write
|
||||
|
||||
rm -rf "$root"
|
||||
mkdir -p "$tftp/xcat/xnba/nodes" "$tftp/boot/grub2" "$tftp/petitboot"
|
||||
|
||||
case "$loader" in
|
||||
xnba) folder="$tftp/xcat/xnba/nodes" ;;
|
||||
petitboot) folder="$tftp/petitboot" ;;
|
||||
*) folder="$tftp/boot/grub2" ;;
|
||||
esac
|
||||
if [ "$write_boot_file" = 1 ]; then
|
||||
write="printf 'xcatd=192.168.1.1:3001 destiny=shell\n' > '$folder/testnode'"
|
||||
else
|
||||
write=":"
|
||||
fi
|
||||
|
||||
cat >"$root/driver.sh" <<DRIVER
|
||||
chdef() { echo "\$@" >> '$root/chdef.log'; }
|
||||
lsdef() {
|
||||
if [ "\$1" = "-t" ] && [ "\$2" = "site" ]; then echo "clustersite: master=192.168.9.9"; return 0; fi
|
||||
echo "Object name: testnode"
|
||||
}
|
||||
ifconfig() { printf 'eth0: flags\n inet 192.168.9.9\n\n'; }
|
||||
netstat() { printf 'Kernel\nIface\neth0\neth1\nlo\n'; }
|
||||
ip() { return 0; }
|
||||
makenetworks() { return 0; }
|
||||
tabdump() { return 0; }
|
||||
makehosts() { return 0; }
|
||||
rmdef() { return 0; }
|
||||
nodeset() {
|
||||
if [ -e '$boot_loader' ]; then echo yes > '$root/loader.at.nodeset'; else echo no > '$root/loader.at.nodeset'; fi
|
||||
$write
|
||||
return $nodeset_status
|
||||
}
|
||||
export TFTPDIR='$tftp'
|
||||
. '$SCRIPT' --check $loader
|
||||
DRIVER
|
||||
|
||||
OUTPUT="$(/bin/bash "$root/driver.sh" 2>&1)" && STATUS=0 || STATUS=$?
|
||||
CHDEF="$(read_file_or_empty "$root/chdef.log")"
|
||||
LOADER_AT_NODESET="$(read_file_or_empty "$root/loader.at.nodeset")"
|
||||
LOADER_LEFT=0
|
||||
[ -e "$boot_loader" ] && LOADER_LEFT=1
|
||||
return 0
|
||||
}
|
||||
|
||||
@test "the xnba check passes and defines the node with the management node architecture" {
|
||||
# The case defined its node as ppc64le whatever the management node was, so nodeset could
|
||||
# not find a genesis kernel for it on x86_64 and the case could never pass there.
|
||||
run_check xnba 1
|
||||
[ "$STATUS" -eq 0 ] || { echo "$OUTPUT"; false; }
|
||||
[[ "$CHDEF" =~ (^|[[:space:]])arch=$HOST_ARCH([[:space:]]|$) ]]
|
||||
[ "$HOST_ARCH" = ppc64le ] ||
|
||||
[ "$(grep -cE '(^|[[:space:]])arch=ppc64le([[:space:]]|$)' <<<"$CHDEF")" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "the check fails when nodeset writes no boot file" {
|
||||
run_check xnba 0
|
||||
[ "$STATUS" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "the grub2 check reads the grub2 directory, and stages then removes the boot loader" {
|
||||
# grub2 and petitboot read their configuration from other directories under the tftp root.
|
||||
# xCAT builds no x86_64 or aarch64 grub2 network boot loader, so grub2.pm stops before it
|
||||
# configures anything. The check stages one for the node arch and removes it after.
|
||||
run_check grub2 1
|
||||
[ "$STATUS" -eq 0 ] || { echo "$OUTPUT"; false; }
|
||||
[ "$LOADER_AT_NODESET" = yes ]
|
||||
[ "$LOADER_LEFT" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "a nodeset that fails makes the check fail, whatever the boot file holds" {
|
||||
# grub2.pm writes the boot configuration and only then stops on a missing boot loader. The
|
||||
# check read the file that failed nodeset had already written, so it passed on the debris.
|
||||
run_check grub2 1 1
|
||||
[ "$STATUS" -ne 0 ]
|
||||
|
||||
run_check xnba 1 1
|
||||
[ "$STATUS" -ne 0 ]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Drive the /etc/passwd rewrite out of the Genesis dracut cmdline hooks.
|
||||
#
|
||||
# mknb writes the management node key to /.ssh/authorized_keys for the legacy Genesis
|
||||
# image, so sshd finds it only while the home directory of root is /. The hook makes it /
|
||||
# by deleting the root entry the image ships and appending its own. Run that rewrite
|
||||
# against every root entry shape dracut writes and read back the result.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
# dracut 99base writes the root entry itself. Up to dracut 057 the password field is always
|
||||
# x; from dracut 060 the x arrives only with --hostonly, and the Genesis image is built -N.
|
||||
DRACUT_049_057='root:x:0:0::/root:/bin/sh'
|
||||
DRACUT_107='root::0:0::/root:/bin/sh'
|
||||
|
||||
# A user name that starts with root but is not root. The delete must keep this line.
|
||||
DECOY='rootfsadm:x:501:501::/home/rootfsadm:/sbin/nologin'
|
||||
|
||||
# Lift the /etc/passwd rewrite out of a hook that cannot be sourced: the hook mounts
|
||||
# filesystems, starts udev and ends in an endless loop.
|
||||
extract_passwd_block()
|
||||
{
|
||||
local hook="$1"
|
||||
awk '
|
||||
/^sed .*\/etc\/passwd$/ { copy = 1 }
|
||||
copy { print }
|
||||
copy && /^__ENDL$/ { found = 1; exit }
|
||||
END { if (!found) exit 1 }
|
||||
' "$hook"
|
||||
}
|
||||
|
||||
# Run the extracted block against a scratch passwd file and print the result. The block names
|
||||
# /etc/passwd literally, so the path is redirected into the scratch tree first, and the run is
|
||||
# refused if any reference to the real file survives: CI runs this as root.
|
||||
run_rewrite()
|
||||
{
|
||||
local hook="$1" shipped="$2"
|
||||
local dir="${BATS_TEST_TMPDIR}/rewrite"
|
||||
local passwd="$dir/passwd" block script
|
||||
|
||||
rm -rf "$dir"
|
||||
mkdir -p "$dir"
|
||||
printf '%s\n%s\n' "$shipped" "$DECOY" >"$passwd"
|
||||
|
||||
block="$(extract_passwd_block "$(repo_path "$hook")")" ||
|
||||
{ echo "$hook: the /etc/passwd rewrite was not found" >&2; return 99; }
|
||||
|
||||
[ "$(grep -o -F '/etc/passwd' <<<"$block" | wc -l)" -eq 2 ] ||
|
||||
{ echo "$hook: expected 2 references to /etc/passwd" >&2; return 98; }
|
||||
script="${block//\/etc\/passwd/$passwd}"
|
||||
case "$script" in
|
||||
*/etc/passwd*) echo "$hook: a reference to the real /etc/passwd survived" >&2; return 97 ;;
|
||||
esac
|
||||
|
||||
bash -c "set -e
|
||||
$script" || return 1
|
||||
cat "$passwd"
|
||||
}
|
||||
|
||||
assert_root_home_is_slash()
|
||||
{
|
||||
local hook="$1" shipped="$2" passwd
|
||||
passwd="$(run_rewrite "$hook" "$shipped")"
|
||||
|
||||
[ "$(grep -c '^root:' <<<"$passwd")" -eq 1 ]
|
||||
[ "$(grep '^root:' <<<"$passwd")" = 'root:x:0:0::/:/bin/bash' ]
|
||||
grep -qxF "$DECOY" <<<"$passwd"
|
||||
}
|
||||
|
||||
assert_hook()
|
||||
{
|
||||
local hook="$1"
|
||||
[ -r "$(repo_path "$hook")" ] || skip "$hook is required"
|
||||
assert_root_home_is_slash "$hook" "$DRACUT_049_057"
|
||||
assert_root_home_is_slash "$hook" "$DRACUT_107"
|
||||
}
|
||||
|
||||
@test "the legacy hook gives root the home directory /" {
|
||||
assert_hook 'xCAT-genesis-builder/xcat-cmdline.sh'
|
||||
}
|
||||
|
||||
@test "the el dracut 105 hook gives root the home directory /" {
|
||||
assert_hook 'xCAT-genesis-builder/dracut_105/el/xcat-cmdline.sh'
|
||||
}
|
||||
|
||||
@test "the ubuntu dracut 105 hook gives root the home directory /" {
|
||||
assert_hook 'xCAT-genesis-builder/dracut_105/ubuntu/xcat-cmdline.sh'
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# go-xcat installs and uninstalls a fixed list of package names, and it keeps one list per
|
||||
# packaging format. The Genesis packages are named after the architecture, and the two formats
|
||||
# spell it differently: the rpm is xCAT-genesis-scripts-ppc64, the deb is
|
||||
# xcat-genesis-scripts-ppc64el.
|
||||
#
|
||||
# The lists are built by go-xcat itself here, not read as text: the deb list exists only when
|
||||
# "type dpkg" succeeds, so a shell function decides which branch each run takes.
|
||||
|
||||
load 'helpers/go_xcat'
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
go_xcat_require_source
|
||||
SCRIPTS_DEBIAN="$(repo_path 'xCAT-genesis-scripts/debian')"
|
||||
SPEC="$(repo_path 'xCAT-genesis-builder/xCAT-genesis-base.spec')"
|
||||
[ -d "$SCRIPTS_DEBIAN" ] || skip "$SCRIPTS_DEBIAN is required"
|
||||
[ -r "$SPEC" ] || skip "$SPEC is required"
|
||||
export SCRIPTS_DEBIAN SPEC
|
||||
}
|
||||
|
||||
# Run the array definitions of go-xcat and print the two lists it built, one per line.
|
||||
package_lists()
|
||||
{
|
||||
local want_dpkg="$1" list_body
|
||||
list_body="$(awk '
|
||||
/^GO_XCAT_INSTALL_LIST=\(/ { copy = 1 }
|
||||
/^PATH=/ { exit }
|
||||
copy { print }
|
||||
' "$GO_XCAT_SOURCE")"
|
||||
[ -n "$list_body" ] || { echo 'go-xcat package arrays not found' >&2; return 3; }
|
||||
(
|
||||
if [ "$want_dpkg" = 1 ]; then
|
||||
dpkg() { :; }
|
||||
fi
|
||||
# A real dpkg on the build host would select the deb branch on every run.
|
||||
PATH=""
|
||||
eval "$list_body"
|
||||
printf 'install %s\n' "${GO_XCAT_INSTALL_LIST[*]}"
|
||||
printf 'uninstall %s\n' "${GO_XCAT_UNINSTALL_LIST[*]}"
|
||||
)
|
||||
}
|
||||
|
||||
package_list()
|
||||
{
|
||||
package_lists "$1" | sed -n "s/^$2 //p"
|
||||
}
|
||||
|
||||
# The package names of a list, sorted, that start with a prefix.
|
||||
named()
|
||||
{
|
||||
local prefix="$1" word
|
||||
for word in $(cat); do
|
||||
case "$word" in
|
||||
"$prefix"*) printf '%s\n' "$word" ;;
|
||||
esac
|
||||
done | sort
|
||||
}
|
||||
|
||||
# The deb names come from the packaging: one control file per Debian architecture names the
|
||||
# genesis-scripts package, and its Depends names the genesis-base package that carries the
|
||||
# Genesis tree for that same architecture.
|
||||
control_scripts_packages()
|
||||
{
|
||||
grep -h '^Package:' "$SCRIPTS_DEBIAN"/control-* | awk '{ print $2 }' | sort
|
||||
}
|
||||
|
||||
control_base_packages()
|
||||
{
|
||||
grep -h '^Depends:' "$SCRIPTS_DEBIAN"/control-* |
|
||||
grep -o 'xcat-genesis-base-[a-z0-9]\+' | sort
|
||||
}
|
||||
|
||||
# The Genesis target architectures of the spec, which are not Debian architecture names.
|
||||
spec_target_arches()
|
||||
{
|
||||
awk '$1 == "%define" && $2 == "tarch" { print $3 }' "$SPEC" | sort -u
|
||||
}
|
||||
|
||||
# The names of a list that carry an architecture the spec does not define.
|
||||
unknown_target_arches()
|
||||
{
|
||||
local prefix="$1" name arch
|
||||
while read -r name; do
|
||||
arch="${name#"$prefix"}"
|
||||
spec_target_arches | grep -qx "$arch" || printf '%s\n' "$name"
|
||||
done
|
||||
}
|
||||
|
||||
@test "the package lists of go-xcat can be built for both packaging formats" {
|
||||
[ -n "$(control_scripts_packages)" ]
|
||||
[ -n "$(spec_target_arches)" ]
|
||||
|
||||
run package_list 1 install
|
||||
[ "$status" -eq 0 ]
|
||||
[[ " $output " == *' xcat-client '* ]]
|
||||
|
||||
run package_list 0 install
|
||||
[ "$status" -eq 0 ]
|
||||
[[ " $output " == *' xCAT-client '* ]]
|
||||
}
|
||||
|
||||
@test "the deb install list names the genesis packages the Debian control files declare" {
|
||||
list="$(package_list 1 install)"
|
||||
[ "$(printf '%s' "$list" | named 'xcat-genesis-scripts-')" = "$(control_scripts_packages)" ]
|
||||
[ "$(printf '%s' "$list" | named 'xcat-genesis-base-')" = "$(control_base_packages)" ]
|
||||
}
|
||||
|
||||
@test "the deb uninstall list names the genesis packages the Debian control files declare" {
|
||||
list="$(package_list 1 uninstall)"
|
||||
[ "$(printf '%s' "$list" | named 'xcat-genesis-scripts-')" = "$(control_scripts_packages)" ]
|
||||
[ "$(printf '%s' "$list" | named 'xcat-genesis-base-')" = "$(control_base_packages)" ]
|
||||
}
|
||||
|
||||
@test "the rpm install list names only Genesis target architectures" {
|
||||
list="$(package_list 0 install)"
|
||||
for prefix in xCAT-genesis-scripts- xCAT-genesis-base-; do
|
||||
[ -z "$(printf '%s' "$list" | named "$prefix" | unknown_target_arches "$prefix")" ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "the rpm uninstall list names only Genesis target architectures" {
|
||||
list="$(package_list 0 uninstall)"
|
||||
for prefix in xCAT-genesis-scripts- xCAT-genesis-base-; do
|
||||
[ -z "$(printf '%s' "$list" | named "$prefix" | unknown_target_arches "$prefix")" ]
|
||||
done
|
||||
}
|
||||
@@ -130,3 +130,13 @@ extract_first_matching_line()
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
# grep that fails when the pattern IS present.
|
||||
#
|
||||
# Do not write "! grep ..." for this. bash ignores errexit for a command inverted with "!",
|
||||
# so such a line never fails a test unless it is the last line of one.
|
||||
refute_grep()
|
||||
{
|
||||
! grep "$@"
|
||||
return $?
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Copy qw(copy);
|
||||
use File::Find qw(find);
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $program = "$FindBin::Bin/../xcattest";
|
||||
my $casedir = "$FindBin::Bin/../autotest/testcase";
|
||||
die("xcattest is not at $program") unless -f $program;
|
||||
die("no test cases under $casedir") unless -d $casedir;
|
||||
|
||||
# A check line xcattest does not understand costs the case the assertion it describes, and the
|
||||
# case says nothing about it: an unknown operator reports "Unrecognized testcase syntax", and a
|
||||
# line whose content does not start with a word character is dropped while the case is loaded.
|
||||
# Read the shipped check lines and let the harness report on them.
|
||||
my @files;
|
||||
find({ wanted => sub { push(@files, $File::Find::name) if -f $File::Find::name }, no_chdir => 1 }, $casedir);
|
||||
die("no case files under $casedir") unless @files;
|
||||
|
||||
my (%checks, %vars);
|
||||
for my $file (sort @files) {
|
||||
open(my $fh, '<', $file) or die("open $file: $!");
|
||||
while (my $line = <$fh>) {
|
||||
chomp($line);
|
||||
next unless $line =~ /^check\s*:\s*(\S.*)$/;
|
||||
my $check = $1;
|
||||
|
||||
# __GETNODEATTR(...)__ and its siblings read the xCAT database, one lsdef for each
|
||||
# check. The shape of the line is what this test reads, so a fixed value stands in.
|
||||
$check =~ s/__\w+\([^)]*\)__/placeholder/g;
|
||||
$vars{$1} = 1 while ($check =~ /\$\$(\w+)/g);
|
||||
push(@{ $checks{$file} }, $check);
|
||||
}
|
||||
close($fh) or die("close $file: $!");
|
||||
}
|
||||
die("no check lines under $casedir") unless keys %checks;
|
||||
|
||||
# One case per shipped file, so a check that reports nothing is attributed to its own file.
|
||||
my %case_of_file = map { $_ => 'syntax_' . do { my $n = $_; $n =~ s{^\Q$casedir\E/?}{}; $n =~ s/[^A-Za-z0-9_-]/_/g; $n } } keys %checks;
|
||||
|
||||
my $fixture = '';
|
||||
for my $file (sort keys %checks) {
|
||||
$fixture .= "start:$case_of_file{$file}\n";
|
||||
$fixture .= "cmd:true\n";
|
||||
$fixture .= "check:$_\n" for @{ $checks{$file} };
|
||||
$fixture .= "end\n";
|
||||
}
|
||||
|
||||
# xcattest derives its result directory from the location of the program, so the copy under the
|
||||
# scratch tree keeps every file the run writes inside that tree.
|
||||
my $root = tempdir(CLEANUP => 1);
|
||||
make_path("$root/bin", "$root/cases");
|
||||
copy($program, "$root/bin/xcattest") or die("copy xcattest: $!");
|
||||
chmod 0755, "$root/bin/xcattest";
|
||||
open(my $fixture_fh, '>', "$root/cases/fixture") or die("write the fixture case: $!");
|
||||
print $fixture_fh $fixture;
|
||||
close($fixture_fh) or die("close the fixture case: $!");
|
||||
|
||||
# Every variable a check line names has to resolve, or xcattest drops the whole case.
|
||||
# A "local" here would be undone at the end of its own statement, before the run.
|
||||
$ENV{"XCATTEST_$_"} = 'placeholder' for keys %vars;
|
||||
$ENV{XCATTEST_CASEDIR} = "$root/cases";
|
||||
# Some shipped patterns warn when perl compiles them, and the warnings say nothing about the
|
||||
# operator. The log file carries what this test reads, so the warnings go to the scratch tree.
|
||||
open(my $stderr_save, '>&', \*STDERR) or die("save STDERR: $!");
|
||||
open(STDERR, '>', "$root/stderr") or die("redirect STDERR: $!");
|
||||
system($^X, "$root/bin/xcattest", '-q', '-t', join(',', sort values %case_of_file));
|
||||
open(STDERR, '>&', $stderr_save) or die("restore STDERR: $!");
|
||||
|
||||
my ($logname) = glob("$root/share/xcat/tools/autotest/result/xcattest.log.*");
|
||||
die("the harness wrote no log under $root") unless $logname;
|
||||
open(my $log_fh, '<', $logname) or die("open $logname: $!");
|
||||
my @log = <$log_fh>;
|
||||
close($log_fh) or die("close $logname: $!");
|
||||
chomp(@log);
|
||||
|
||||
# Count what the harness reported for each case, and keep the lines it did not understand.
|
||||
my (%reported, @unrecognized, $current);
|
||||
for my $line (@log) {
|
||||
$current = $1 if ($line =~ /^------START::(\S+)::/);
|
||||
next unless defined $current;
|
||||
$reported{$current}++ if ($line =~ /^CHECK:/ or $line =~ /^Unrecognized testcase syntax:/);
|
||||
push(@unrecognized, "$current: $line") if ($line =~ /^Unrecognized testcase syntax:/);
|
||||
$current = undef if ($line =~ /^------END::/);
|
||||
}
|
||||
|
||||
is(join("\n", @unrecognized), '',
|
||||
'every check line in the shipped cases uses an operator xcattest understands');
|
||||
|
||||
my @silent;
|
||||
for my $file (sort keys %checks) {
|
||||
my $case = $case_of_file{$file};
|
||||
my $fed = scalar @{ $checks{$file} };
|
||||
my $got = $reported{$case} || 0;
|
||||
push(@silent, "$file: $fed check lines, $got reported") if ($got != $fed);
|
||||
}
|
||||
is(join("\n", @silent), '',
|
||||
'every check line in the shipped cases reports a result, so none is dropped while the case loads');
|
||||
|
||||
done_testing();
|
||||
@@ -7,9 +7,7 @@
|
||||
# builds of DIFFERENT checkouts share nothing and must run concurrently. The historic
|
||||
# host-global lock got that backwards and made the devel and stable CD lanes collide.
|
||||
#
|
||||
# This drives the real lock. The predecessor extracted a marked region out of
|
||||
# build-ubunturepo with a regex and ran that; now the lock is a function, so it is
|
||||
# called directly.
|
||||
# This drives the real lock. The lock is a function, so it is called directly.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../../build-utils/lib";
|
||||
use Test::More;
|
||||
use XCAT::BuildUtils qw(read_line snap_release);
|
||||
|
||||
# builddebs.pl takes the Release file as authoritative. The tracked file holds the
|
||||
# placeholder snap000000000000, and only buildrpms.pl overwrites it with the commit
|
||||
# time. A pipeline that does not run buildrpms.pl keeps the placeholder.
|
||||
#
|
||||
# The release decision is extracted from builddebs.pl and run here, so the assertions
|
||||
# measure the shipped code rather than a copy of it.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs( File::Spec->catdir( $FindBin::Bin, '..', '..' ) );
|
||||
my $builder = File::Spec->catfile( $repo_root, 'builddebs.pl' );
|
||||
die "builddebs.pl not found\n" unless -f $builder;
|
||||
|
||||
my $src = do { local $/; open my $fh, '<', $builder or die $!; <$fh> };
|
||||
|
||||
# die rather than skip: a rewrite that stops this matching must fail loudly
|
||||
# instead of silently covering nothing.
|
||||
my ($block) = $src =~ /^(my \$FILE_RELEASE\b.*?^my \$RELEASE\s*=.*?;\n)/ms;
|
||||
die('could not extract the release decision from builddebs.pl')
|
||||
unless defined $block;
|
||||
|
||||
my $dir = tempdir( CLEANUP => 1 );
|
||||
my $run = 0;
|
||||
|
||||
# Resolve the release the way builddebs.pl does, for one Release file content and one
|
||||
# --release option. Returns the release string.
|
||||
sub release_for {
|
||||
my ( $file_content, $opt_release ) = @_;
|
||||
$run++;
|
||||
my $ROOT = File::Spec->catdir( $dir, "run$run" );
|
||||
mkdir $ROOT or die $!;
|
||||
if ( defined $file_content ) {
|
||||
open( my $fh, '>', File::Spec->catfile( $ROOT, 'Release' ) ) or die $!;
|
||||
print {$fh} $file_content;
|
||||
close($fh);
|
||||
}
|
||||
my $EPOCH = 1756000000;
|
||||
my %opts;
|
||||
$opts{release} = $opt_release if defined $opt_release;
|
||||
my $got = eval "$block\n\$RELEASE";
|
||||
die $@ if $@;
|
||||
return $got;
|
||||
}
|
||||
|
||||
my $from_epoch = snap_release(1756000000);
|
||||
|
||||
is( release_for("snap000000000000\n"), $from_epoch,
|
||||
'the tracked placeholder is not a release, so the commit time is used' );
|
||||
|
||||
is( release_for("snap202608240826\n"), 'snap202608240826',
|
||||
'a release buildrpms.pl wrote is still authoritative' );
|
||||
|
||||
is( release_for(undef), $from_epoch,
|
||||
'no Release file gives the commit time' );
|
||||
|
||||
is( release_for( "snap000000000000\n", 'snap209901010000' ), 'snap209901010000',
|
||||
'--release still wins over the placeholder' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,251 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
# makedns must sign every update with the algorithm the named.conf key stanza declares.
|
||||
# named matches a TSIG key by name AND algorithm, so a stanza that does not agree with the
|
||||
# signature makes named reject every update and makedns exit 1.
|
||||
#
|
||||
# Run this test with XCATROOT set to the tree under test. xCAT::Table does
|
||||
# "use lib $::XCATROOT/lib/perl", so an installed /opt/xcat shadows the modules under test:
|
||||
# XCATROOT=$PWD/xCAT-server prove xCAT-test/unit/ddns_named_key_algorithm.t
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
|
||||
use File::Temp qw(tempfile);
|
||||
use Test::More;
|
||||
|
||||
$ENV{XCATCFG} ||= 'SQLite:/tmp';
|
||||
$ENV{XCATROOT} ||= "$FindBin::Bin/../../xCAT-server";
|
||||
|
||||
my $ddns_plugin_path =
|
||||
"$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/ddns.pm";
|
||||
if ( -f $ddns_plugin_path ) {
|
||||
require $ddns_plugin_path;
|
||||
}
|
||||
else {
|
||||
require xCAT_plugin::ddns;
|
||||
}
|
||||
|
||||
# KEY RR algorithm numbers, as ddns_sign_update writes them for old Net::DNS.
|
||||
my %ALGORITHM_OF_RR_TYPE = (
|
||||
157 => 'hmac-md5',
|
||||
161 => 'hmac-sha1',
|
||||
162 => 'hmac-sha224',
|
||||
163 => 'hmac-sha256',
|
||||
164 => 'hmac-sha384',
|
||||
165 => 'hmac-sha512',
|
||||
);
|
||||
|
||||
my $SECRET = 'c2VjcmV0LXNlY3JldC1zZWNyZXQtc2VjcmV0LXNlY3I=';
|
||||
|
||||
my $with_key = <<"NAMED";
|
||||
options {
|
||||
};
|
||||
key "xcat_key" {
|
||||
\talgorithm hmac-sha256;
|
||||
\tsecret "$SECRET";
|
||||
};
|
||||
NAMED
|
||||
|
||||
my $without_key = "options {\n};\n";
|
||||
|
||||
subtest 'old Net::DNS keeps the configured algorithm in named.conf' => sub {
|
||||
my $result = run_makedns(
|
||||
named_conf => $with_key,
|
||||
site_algorithm => 'hmac-sha256',
|
||||
net_dns => '1.25',
|
||||
);
|
||||
|
||||
is( $result->{named_algorithm}, 'hmac-sha256',
|
||||
'named.conf keeps the algorithm the key was generated with' );
|
||||
is( $result->{signing_algorithm}, 'hmac-sha256',
|
||||
'the update is signed with that algorithm' );
|
||||
is( $result->{restartneeded}, 0, 'named is not restarted' );
|
||||
};
|
||||
|
||||
subtest 'old Net::DNS does not downgrade an unconfigured site' => sub {
|
||||
my $result = run_makedns(
|
||||
named_conf => $with_key,
|
||||
site_algorithm => undef,
|
||||
net_dns => '1.25',
|
||||
);
|
||||
|
||||
is( $result->{named_algorithm}, 'hmac-sha256',
|
||||
'named.conf keeps hmac-sha256 when the site table names no algorithm' );
|
||||
is( $result->{signing_algorithm}, 'hmac-sha256',
|
||||
'the update is signed with hmac-sha256' );
|
||||
is( $result->{restartneeded}, 0, 'named is not restarted' );
|
||||
};
|
||||
|
||||
subtest 'new Net::DNS signs through the key file' => sub {
|
||||
my $result = run_makedns(
|
||||
named_conf => $with_key,
|
||||
site_algorithm => 'hmac-sha256',
|
||||
net_dns => '1.47',
|
||||
);
|
||||
|
||||
is( $result->{named_algorithm}, 'hmac-sha256',
|
||||
'named.conf keeps hmac-sha256' );
|
||||
is( $result->{signing_algorithm}, 'keyfile',
|
||||
'the update is signed with /etc/xcat/ddns.key' );
|
||||
};
|
||||
|
||||
subtest 'an explicit site algorithm replaces the stanza' => sub {
|
||||
my $result = run_makedns(
|
||||
named_conf => $with_key,
|
||||
site_algorithm => 'hmac-sha512',
|
||||
net_dns => '1.25',
|
||||
);
|
||||
|
||||
is( $result->{named_algorithm}, 'hmac-sha512',
|
||||
'named.conf takes the algorithm the administrator selected' );
|
||||
is( $result->{signing_algorithm}, 'hmac-sha512',
|
||||
'the update is signed with the selected algorithm' );
|
||||
is( $result->{restartneeded}, 1, 'named is restarted for the new stanza' );
|
||||
};
|
||||
|
||||
subtest 'a generated key stays on hmac-md5 for old Net::DNS' => sub {
|
||||
my $result = run_makedns(
|
||||
named_conf => $without_key,
|
||||
site_algorithm => undef,
|
||||
net_dns => '1.25',
|
||||
);
|
||||
|
||||
is( $result->{named_algorithm}, 'hmac-md5',
|
||||
'a key created for old Net::DNS uses hmac-md5' );
|
||||
is( $result->{signing_algorithm}, 'hmac-md5',
|
||||
'the update is signed with hmac-md5' );
|
||||
};
|
||||
|
||||
done_testing();
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
=head3 run_makedns
|
||||
|
||||
Description: Run update_namedconf over a scratch named.conf, then sign one update
|
||||
with the context that run produced.
|
||||
Arguments: named_conf (the file content), site_algorithm (undef for none),
|
||||
net_dns (the Net::DNS version to report)
|
||||
Returns: hash reference with named_algorithm, signing_algorithm, restartneeded
|
||||
|
||||
=cut
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
sub run_makedns {
|
||||
my (%args) = @_;
|
||||
|
||||
my ( $named_fh, $named_path ) = tempfile( UNLINK => 1 );
|
||||
print {$named_fh} $args{named_conf};
|
||||
close($named_fh) or die "Unable to close $named_path: $!";
|
||||
|
||||
my $settings = xCAT::DHCP::OmapiPolicy->settings(
|
||||
site_values => {
|
||||
dhcpomapialgorithm => $args{site_algorithm},
|
||||
dhcpomapikeyname => undef,
|
||||
dhcpomshellpath => undef,
|
||||
}
|
||||
);
|
||||
die "Unusable OMAPI settings: $settings->{error}" if $settings->{error};
|
||||
|
||||
my $ctx = {
|
||||
omapi_settings => $settings,
|
||||
privkey => $SECRET,
|
||||
zonesdir => '/tmp',
|
||||
dbdir => '/tmp',
|
||||
zonestotouch => {},
|
||||
adzones => {},
|
||||
dnsupdaters => [],
|
||||
adservers => [],
|
||||
restartneeded => 0,
|
||||
};
|
||||
|
||||
no warnings qw(redefine once);
|
||||
local *xCAT_plugin::ddns::get_conf = sub { return $named_path; };
|
||||
local *xCAT_plugin::ddns::ensure_ddns_key_file = sub { return; };
|
||||
local *xCAT::TableUtils::get_site_attribute = sub { return; };
|
||||
local *xCAT::Utils::runcmd = sub { return (); };
|
||||
local *xCAT::Utils::isAIX = sub { return 0; };
|
||||
local *xCAT::Utils::isLinux = sub { return 1; };
|
||||
local *xCAT::Table::new = sub { return bless {}, 'Local::DDNS::PasswdTable'; };
|
||||
|
||||
my $update = Local::DDNS::Update->new();
|
||||
{
|
||||
local $Net::DNS::VERSION = $args{net_dns};
|
||||
xCAT_plugin::ddns::update_namedconf( $ctx, 0 );
|
||||
xCAT_plugin::ddns::ddns_sign_update( $ctx, $update );
|
||||
}
|
||||
|
||||
open( my $result_fh, '<', $named_path )
|
||||
or die "Unable to read $named_path: $!";
|
||||
local $/;
|
||||
my $contents = <$result_fh>;
|
||||
close($result_fh) or die "Unable to close $named_path: $!";
|
||||
|
||||
my ($named_algorithm) =
|
||||
( $contents =~ /key\s+"?xcat_key"?[^{]*\{[^}]*?algorithm\s+([^;\s]+)\s*;/s );
|
||||
|
||||
return {
|
||||
named_algorithm => defined($named_algorithm) ? lc($named_algorithm) : undef,
|
||||
signing_algorithm => signing_algorithm($update),
|
||||
restartneeded => $ctx->{restartneeded} ? 1 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
=head3 signing_algorithm
|
||||
|
||||
Description: Name the TSIG algorithm one recorded sign_tsig call selects.
|
||||
Arguments: the recording update object
|
||||
Returns: the algorithm name, or "keyfile" for the key-file interface
|
||||
|
||||
=cut
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
sub signing_algorithm {
|
||||
my ($update) = @_;
|
||||
|
||||
my $calls = $update->{sign_tsig_calls};
|
||||
is( scalar( @{$calls} ), 1, 'the update is signed once' );
|
||||
my $args = $calls->[0];
|
||||
|
||||
if ( @{$args} == 1 && !ref( $args->[0] ) ) {
|
||||
return 'keyfile';
|
||||
}
|
||||
if ( @{$args} == 2 ) {
|
||||
return 'hmac-md5';
|
||||
}
|
||||
|
||||
my $rr_type = $args->[0]->algorithm;
|
||||
return $ALGORITHM_OF_RR_TYPE{$rr_type} || "KEY RR algorithm $rr_type";
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
package Local::DDNS::Update;
|
||||
|
||||
sub new {
|
||||
return bless { sign_tsig_calls => [] }, shift;
|
||||
}
|
||||
|
||||
sub sign_tsig {
|
||||
my ( $self, @args ) = @_;
|
||||
push @{ $self->{sign_tsig_calls} }, \@args;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
package Local::DDNS::PasswdTable;
|
||||
|
||||
sub setAttribs {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -104,8 +104,8 @@ subtest 'all Net::DNS thresholds share the dotted version policy' => sub {
|
||||
'no Net::DNS threshold uses Perl numeric comparison' );
|
||||
|
||||
my @policy_calls = ( $source =~ /net_dns_uses_keyfile\(\)/g );
|
||||
is( scalar(@policy_calls), 4,
|
||||
'all four Net::DNS threshold sites use the shared policy' );
|
||||
is( scalar(@policy_calls), 2,
|
||||
'both Net::DNS threshold sites use the shared policy' );
|
||||
};
|
||||
|
||||
subtest 'Net::DNS threshold controls DDNS policy and signing' => sub {
|
||||
@@ -116,7 +116,6 @@ subtest 'Net::DNS threshold controls DDNS policy and signing' => sub {
|
||||
|
||||
foreach my $case (@net_dns_versions) {
|
||||
my ( $version, $uses_keyfile ) = @{$case};
|
||||
my $expected_algorithm = $uses_keyfile ? 'hmac-sha256' : 'hmac-md5';
|
||||
is(
|
||||
with_net_dns_version(
|
||||
$version,
|
||||
@@ -126,8 +125,8 @@ subtest 'Net::DNS threshold controls DDNS policy and signing' => sub {
|
||||
);
|
||||
}
|
||||
),
|
||||
$expected_algorithm,
|
||||
"Net::DNS $version selects the expected implicit algorithm"
|
||||
'hmac-sha256',
|
||||
"Net::DNS $version keeps the algorithm the site table implies"
|
||||
);
|
||||
|
||||
my $update = Local::DDNS::Update->new();
|
||||
@@ -172,17 +171,16 @@ subtest 'Net::DNS threshold controls named key reconciliation' => sub {
|
||||
my ( $version, $uses_keyfile ) = @{$case};
|
||||
my ( $named_contents, $restartneeded ) =
|
||||
reconcile_named_key($version);
|
||||
my $expected_algorithm = $uses_keyfile ? 'hmac-sha256' : 'hmac-md5';
|
||||
|
||||
like(
|
||||
$named_contents,
|
||||
qr/^\s*algorithm\s+\Q$expected_algorithm\E\s*;/m,
|
||||
"Net::DNS $version keeps the expected named key algorithm"
|
||||
qr/^\s*algorithm\s+hmac-sha256\s*;/m,
|
||||
"Net::DNS $version keeps the named key algorithm"
|
||||
);
|
||||
is(
|
||||
$restartneeded ? 1 : 0,
|
||||
$uses_keyfile ? 0 : 1,
|
||||
"Net::DNS $version records the expected named restart state"
|
||||
0,
|
||||
"Net::DNS $version leaves named alone"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
# send_ddns_update retries a rejected dynamic DNS update. Net::DNS appends the TSIG to the
|
||||
# additional section, so every attempt must sign its own request: named answers FORMERR to a
|
||||
# message that carries two TSIG records (measured on BIND 9.18.33).
|
||||
#
|
||||
# Run this test with XCATROOT set to the tree under test. xCAT::Table does
|
||||
# "use lib $::XCATROOT/lib/perl", so an installed /opt/xcat shadows the modules under test:
|
||||
# XCATROOT=$PWD/xCAT-server prove xCAT-test/unit/ddns_update_retry.t
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
|
||||
use Test::More;
|
||||
|
||||
$ENV{XCATCFG} ||= 'SQLite:/tmp';
|
||||
$ENV{XCATROOT} ||= "$FindBin::Bin/../../xCAT-server";
|
||||
|
||||
my $ddns_plugin_path =
|
||||
"$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/ddns.pm";
|
||||
if ( -f $ddns_plugin_path ) {
|
||||
require $ddns_plugin_path;
|
||||
}
|
||||
else {
|
||||
require xCAT_plugin::ddns;
|
||||
}
|
||||
|
||||
my $SECRET = 'c2VjcmV0LXNlY3JldC1zZWNyZXQtc2VjcmV0LXNlY3I=';
|
||||
my $ZONE = 'test.lab';
|
||||
|
||||
subtest 'every attempt of a rejected update carries one TSIG' => sub {
|
||||
my $resolver = Local::DDNS::Resolver->new( replies => [ 'NOTAUTH', 'NOTAUTH', 'NOTAUTH' ] );
|
||||
my $rc = send_update($resolver);
|
||||
|
||||
is( $rc, 1, 'a persistently rejected update reports failure' );
|
||||
is( scalar( @{ $resolver->{sent} } ), 3, 'the update is sent three times' );
|
||||
is_deeply(
|
||||
[ map { $_->{tsig_count} } @{ $resolver->{sent} } ],
|
||||
[ 1, 1, 1 ],
|
||||
'each attempt carries exactly one TSIG record'
|
||||
);
|
||||
is_deeply(
|
||||
[ map { $_->{updates} } @{ $resolver->{sent} } ],
|
||||
[ ( ['n1.test.lab. 300 IN A 10.0.0.1'] ) x 3 ],
|
||||
'each attempt carries the same update records'
|
||||
);
|
||||
};
|
||||
|
||||
subtest 'a retry can be accepted' => sub {
|
||||
my $resolver = Local::DDNS::Resolver->new( replies => [ 'NOTAUTH', 'NOERROR' ] );
|
||||
my $rc = send_update($resolver);
|
||||
|
||||
is( $rc, 0, 'the accepted retry reports success' );
|
||||
is( scalar( @{ $resolver->{sent} } ), 2, 'the update is sent twice' );
|
||||
};
|
||||
|
||||
done_testing();
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
=head3 send_update
|
||||
|
||||
Description: Send one dynamic DNS update through send_ddns_update.
|
||||
Arguments: the recording resolver
|
||||
Returns: the send_ddns_update return code
|
||||
|
||||
=cut
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
sub send_update {
|
||||
my ($resolver) = @_;
|
||||
|
||||
my $settings = xCAT::DHCP::OmapiPolicy->settings(
|
||||
site_values => {
|
||||
dhcpomapialgorithm => 'hmac-sha256',
|
||||
dhcpomapikeyname => undef,
|
||||
dhcpomshellpath => undef,
|
||||
}
|
||||
);
|
||||
die "Unusable OMAPI settings: $settings->{error}" if $settings->{error};
|
||||
|
||||
my $ctx = {
|
||||
omapi_settings => $settings,
|
||||
privkey => $SECRET,
|
||||
};
|
||||
|
||||
my $update = Net::DNS::Update->new($ZONE);
|
||||
$update->push( update => Net::DNS::RR->new('n1.test.lab. 300 IN A 10.0.0.1') );
|
||||
|
||||
no warnings qw(redefine once);
|
||||
local *xCAT::SvrUtils::sendmsg = sub { return; };
|
||||
|
||||
# Net::DNS 1.36 removed sign_tsig($name, $secret) and signs from a key file. Report the
|
||||
# version that signs from a KEY RR, so the test needs no key file.
|
||||
local $Net::DNS::VERSION = '1.25';
|
||||
return xCAT_plugin::ddns::send_ddns_update( $ctx, $resolver, $update, $ZONE, 'n1.test.lab' );
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
package Local::DDNS::Resolver;
|
||||
|
||||
# Answer FORMERR to a message with more than one TSIG record, as named does, and otherwise
|
||||
# answer the next scripted rcode.
|
||||
sub new {
|
||||
my ( $class, %args ) = @_;
|
||||
return bless { replies => $args{replies}, sent => [] }, $class;
|
||||
}
|
||||
|
||||
sub send {
|
||||
my ( $self, $packet ) = @_;
|
||||
|
||||
my @tsig = grep { $_->type eq 'TSIG' } $packet->additional;
|
||||
push @{ $self->{sent} },
|
||||
{
|
||||
tsig_count => scalar(@tsig),
|
||||
updates => [ map { $_->plain } $packet->authority ],
|
||||
};
|
||||
|
||||
my $rcode = shift @{ $self->{replies} };
|
||||
$rcode = 'SERVFAIL' unless defined $rcode;
|
||||
$rcode = 'FORMERR' if @tsig > 1;
|
||||
return Local::DDNS::Reply->new($rcode);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
package Local::DDNS::Reply;
|
||||
|
||||
sub new {
|
||||
my ( $class, $rcode ) = @_;
|
||||
return bless { rcode => $rcode }, $class;
|
||||
}
|
||||
|
||||
sub header { return $_[0]; }
|
||||
|
||||
sub rcode { return $_[0]->{rcode}; }
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env perl
|
||||
# xCAT and xCATsn name their Debian architectures explicitly. An architecture missing from that
|
||||
# list is not a build failure: it is a package apt cannot find at all.
|
||||
#
|
||||
# The list is compared against the architectures the DEB build itself supports, taken from
|
||||
# build-utils/lib/XCAT/BuildUtils or, failing that, the documented set.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $root = "$FindBin::Bin/../..";
|
||||
|
||||
# The Debian architectures xCAT ships. dpkg names, not rpm ones.
|
||||
my @arches = qw(amd64 ppc64el riscv64);
|
||||
|
||||
my @controls = grep { -f } ("$root/xCAT/debian/control", "$root/xCATsn/debian/control");
|
||||
plan skip_all => 'no Debian control files in this tree' unless @controls;
|
||||
|
||||
for my $ctl (@controls) {
|
||||
open my $fh, '<', $ctl or die "read $ctl: $!";
|
||||
local $/; my $text = <$fh>; close $fh;
|
||||
(my $short = $ctl) =~ s{^\Q$root\E/}{};
|
||||
my @lines = ($text =~ /^Architecture:\s*(.+)$/mg);
|
||||
my @explicit = grep { !/^(?:any|all)$/ } map { s/^\s+|\s+$//gr } @lines;
|
||||
ok(scalar(@explicit), "$short names architectures explicitly") or next;
|
||||
for my $line (@explicit) {
|
||||
my %have = map { $_ => 1 } split /\s+/, $line;
|
||||
my @missing = grep { !$have{$_} } @arches;
|
||||
is_deeply(\@missing, [], "$short covers @arches");
|
||||
}
|
||||
}
|
||||
|
||||
# The genesis dependency must follow the architecture. xCAT and xCATsn are built once per
|
||||
# architecture from one control file, and xcat-genesis-scripts-amd64 is Architecture: all, so an
|
||||
# unrestricted Depends on it installs the x86_64 Genesis tree on every architecture.
|
||||
#
|
||||
# xCAT-genesis-scripts keeps one control file per Debian architecture, named for it. Its package
|
||||
# name and its genesis-base dependency must carry that same architecture: the base deb
|
||||
# builddeb-genesis-base builds for ppc64el is xcat-genesis-base-ppc64el, not -ppc64.
|
||||
|
||||
# Return the folded value of a control field, or undef.
|
||||
sub control_field {
|
||||
my ($text, $name) = @_;
|
||||
return $1 if $text =~ /^\Q$name\E:[ \t]*(.*(?:\n[ \t]+.*)*)/m;
|
||||
return;
|
||||
}
|
||||
|
||||
# Split a dependency field into [package name, architecture restriction] pairs. Alternatives
|
||||
# separated by "|" are returned one by one, because a restriction binds to one alternative.
|
||||
sub dependency_terms {
|
||||
my ($field) = @_;
|
||||
my @terms;
|
||||
return @terms unless defined $field;
|
||||
$field =~ s/\n/ /g;
|
||||
for my $dep (split /,/, $field) {
|
||||
for my $alt (split /\|/, $dep) {
|
||||
next unless $alt =~ /^\s*([A-Za-z0-9][A-Za-z0-9+.-]*)\s*(?:\([^)]*\))?\s*(?:\[([^\]]*)\])?/;
|
||||
push @terms, [ $1, $2 ];
|
||||
}
|
||||
}
|
||||
return @terms;
|
||||
}
|
||||
|
||||
# dpkg-gencontrol drops a dependency whose architecture restriction excludes the build
|
||||
# architecture. No restriction means the dependency reaches every architecture.
|
||||
sub term_applies {
|
||||
my ($restriction, $arch) = @_;
|
||||
return 1 unless defined $restriction;
|
||||
my @tokens = grep { length } split /\s+/, $restriction;
|
||||
return 1 unless @tokens;
|
||||
my $negated = ($tokens[0] =~ /^!/) ? 1 : 0;
|
||||
my %named = map { my $t = $_; $t =~ s/^!//; $t =~ s/^any-//; ($t => 1) } @tokens;
|
||||
return $negated ? (exists $named{$arch} ? 0 : 1) : (exists $named{$arch} ? 1 : 0);
|
||||
}
|
||||
|
||||
# The architectures xCAT-genesis-scripts is packaged for, taken from its per-architecture control
|
||||
# files. riscv64 has none on purpose: its Genesis is the OpenEmbedded image.
|
||||
my $scripts_debian = "$root/xCAT-genesis-scripts/debian";
|
||||
my @scripts_arches = sort map { m{/control-(.+)$} ? $1 : () } glob("$scripts_debian/control-*");
|
||||
|
||||
SKIP: {
|
||||
skip 'xCAT-genesis-scripts has no per-architecture control files', 1 unless @scripts_arches;
|
||||
|
||||
for my $arch (@scripts_arches) {
|
||||
my $ctl = "$scripts_debian/control-$arch";
|
||||
open my $fh, '<', $ctl or die "read $ctl: $!";
|
||||
local $/; my $text = <$fh>; close $fh;
|
||||
|
||||
my ($package) = ($text =~ /^Package:\s*(\S+)/m);
|
||||
is($package, "xcat-genesis-scripts-$arch",
|
||||
"control-$arch builds xcat-genesis-scripts-$arch");
|
||||
|
||||
my @bases = grep { /^xcat-genesis-base-/ }
|
||||
map { $_->[0] } dependency_terms(control_field($text, 'Depends'));
|
||||
is_deeply(\@bases, ["xcat-genesis-base-$arch"],
|
||||
"xcat-genesis-scripts-$arch depends on xcat-genesis-base-$arch");
|
||||
}
|
||||
|
||||
for my $ctl (@controls) {
|
||||
open my $fh, '<', $ctl or die "read $ctl: $!";
|
||||
local $/; my $text = <$fh>; close $fh;
|
||||
(my $short = $ctl) =~ s{^\Q$root\E/}{};
|
||||
|
||||
my ($arch_line) = ($text =~ /^Architecture:\s*(.+)$/m);
|
||||
next unless defined $arch_line;
|
||||
my @built = grep { !/^(?:any|all)$/ } split /\s+/, $arch_line;
|
||||
|
||||
my @genesis = grep { $_->[0] =~ /^xcat-genesis-scripts-/ }
|
||||
dependency_terms(control_field($text, 'Depends'));
|
||||
|
||||
for my $arch (@built) {
|
||||
my @reaching = map { $_->[0] }
|
||||
grep { term_applies($_->[1], $arch) } @genesis;
|
||||
my @foreign = grep { $_ ne "xcat-genesis-scripts-$arch" } @reaching;
|
||||
is_deeply(\@foreign, [],
|
||||
"$short on $arch depends on no other architecture's genesis scripts");
|
||||
|
||||
my %packaged = map { $_ => 1 } @scripts_arches;
|
||||
next unless $packaged{$arch};
|
||||
ok(scalar(grep { $_ eq "xcat-genesis-scripts-$arch" } @reaching),
|
||||
"$short on $arch depends on xcat-genesis-scripts-$arch");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
done_testing();
|
||||
@@ -7,10 +7,9 @@ use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# The installer kernel and initrd sit in a different place on every Ubuntu media layout:
|
||||
# netboot trees name them after the Debian architecture, live images keep them under
|
||||
# casper, and a hardware-enablement kernel ships beside the release one. Build each layout
|
||||
# on disk and ask the resolver, rather than reading the table that describes them.
|
||||
# The installer kernel and initrd sit in a different place on every Ubuntu media layout.
|
||||
# Build each layout on disk and ask the resolver, rather than read the table that
|
||||
# describes them.
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
@@ -103,6 +102,56 @@ is(
|
||||
'riscv64 does not accept the kernel name the other live images use',
|
||||
);
|
||||
|
||||
# The Ubuntu ppc64el live-server ISO carries no netboot tree. 22.04 and 24.04 ship the
|
||||
# hardware-enablement pair under casper beside the release pair; 26.04 ships the release
|
||||
# pair only.
|
||||
is(
|
||||
resolved('ppc64le', 'ppc64el', media('casper/vmlinux', 'casper/initrd')),
|
||||
'casper/vmlinux|casper/initrd',
|
||||
'the POWER live image keeps its kernel under casper',
|
||||
);
|
||||
is(
|
||||
resolved('ppc64le', 'ppc64el',
|
||||
media('casper/hwe-vmlinux', 'casper/hwe-initrd', 'casper/vmlinux', 'casper/initrd')),
|
||||
'casper/hwe-vmlinux|casper/hwe-initrd',
|
||||
'the POWER hardware-enablement kernel wins over the release kernel',
|
||||
);
|
||||
is(
|
||||
resolved('ppc64le', 'ppc64el',
|
||||
media('install/netboot/ubuntu-installer/ppc64el/vmlinux',
|
||||
'install/netboot/ubuntu-installer/ppc64el/initrd.gz',
|
||||
'casper/vmlinux', 'casper/initrd')),
|
||||
'install/netboot/ubuntu-installer/ppc64el/vmlinux|install/netboot/ubuntu-installer/ppc64el/initrd.gz',
|
||||
'a POWER netboot tree still wins over a live image on the same media',
|
||||
);
|
||||
|
||||
# mkinstall asks this routine, so it accepts every media install_boot_files resolves.
|
||||
can_ok('xCAT_plugin::debian', 'install_media_is_bootable');
|
||||
is(
|
||||
xCAT_plugin::debian::install_media_is_bootable('ppc64le', 'ppc64el',
|
||||
media('casper/vmlinux', 'casper/initrd')),
|
||||
1,
|
||||
'a POWER live image is bootable media',
|
||||
);
|
||||
is(
|
||||
xCAT_plugin::debian::install_media_is_bootable('ppc64le', 'ppc64el',
|
||||
media('install/netboot/ubuntu-installer/ppc64el/vmlinux',
|
||||
'install/netboot/ubuntu-installer/ppc64el/initrd.gz')),
|
||||
1,
|
||||
'a POWER netboot tree is bootable media',
|
||||
);
|
||||
is(
|
||||
xCAT_plugin::debian::install_media_is_bootable('ppc64le', 'ppc64el', media('README')),
|
||||
0,
|
||||
'media with no installer is not bootable media',
|
||||
);
|
||||
is(
|
||||
xCAT_plugin::debian::install_media_is_bootable('x86_64', 'amd64',
|
||||
media('casper/vmlinuz', 'casper/initrd')),
|
||||
1,
|
||||
'an x86 live image is bootable media',
|
||||
);
|
||||
|
||||
# --- nothing to boot -------------------------------------------------------
|
||||
is(resolved('x86_64', 'amd64', media('casper/vmlinuz')), undef,
|
||||
'a kernel without its initrd is not a match');
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# Ubuntu has two installers and two pre-install scripts that are not interchangeable.
|
||||
# pre.ubuntu.subiquity writes a curtin "storage:" document that the autoinstall
|
||||
# early-commands append to /autoinstall.yaml. pre.ubuntu.ppc64 writes a partman recipe
|
||||
# for the debian-installer, which is not YAML at all.
|
||||
#
|
||||
# mkinstall chose the subiquity script, then overwrote that choice for every ppc64 node,
|
||||
# so a ppc64el Subiquity install appended a partman recipe to its autoinstall.yaml. The
|
||||
# ppc64 script belongs to the debian-installer path only.
|
||||
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
my $plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/debian.pm";
|
||||
die "debian.pm not found\n" unless -r $plugin;
|
||||
eval { require $plugin; 1 } or plan skip_all => "could not load debian.pm: $@";
|
||||
|
||||
can_ok('xCAT_plugin::debian', 'install_prescript')
|
||||
or die('mkinstall still chooses the pre-install script inline, so nothing can drive it');
|
||||
|
||||
sub chosen {
|
||||
my ($platform, $arch, $subiquity) = @_;
|
||||
my $path = xCAT_plugin::debian::install_prescript($platform, $arch, $subiquity);
|
||||
$path =~ s{.*/}{};
|
||||
return $path;
|
||||
}
|
||||
|
||||
# --- subiquity: the arch never changes the script -------------------------
|
||||
is(chosen('ubuntu', 'x86_64', 1), 'pre.ubuntu.subiquity',
|
||||
'an x86_64 subiquity install gets the subiquity pre-install script');
|
||||
is(chosen('ubuntu', 'ppc64el', 1), 'pre.ubuntu.subiquity',
|
||||
'a ppc64el subiquity install gets the subiquity pre-install script, not the partman one');
|
||||
is(chosen('ubuntu', 'ppc64le', 1), 'pre.ubuntu.subiquity',
|
||||
'the ppc64le spelling reaches the same script');
|
||||
is(chosen('ubuntu', 'ppc64', 1), 'pre.ubuntu.subiquity',
|
||||
'so does the bare ppc64 spelling');
|
||||
|
||||
# --- debian-installer: ppc64 keeps its own script -------------------------
|
||||
is(chosen('ubuntu', 'ppc64el', 0), 'pre.ubuntu.ppc64',
|
||||
'a ppc64el debian-installer install keeps the partman pre-install script');
|
||||
is(chosen('ubuntu', 'ppc64', 0), 'pre.ubuntu.ppc64',
|
||||
'and so does the bare ppc64 spelling');
|
||||
is(chosen('ubuntu', 'x86_64', 0), 'pre.ubuntu',
|
||||
'an x86_64 debian-installer install gets the plain script');
|
||||
|
||||
# --- the override is Ubuntu only -----------------------------------------
|
||||
is(chosen('debian', 'ppc64el', 0), 'pre.debian',
|
||||
'Debian on POWER has no ppc64 pre-install script to select');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env perl
|
||||
# The genesis spec is the build root manifest: what it does not build-require, the buildroot
|
||||
# only holds by accident, and dracut_install then installs nothing.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use Test::More;
|
||||
|
||||
use XCAT::Test::File qw(repo_path slurp_repo_file);
|
||||
|
||||
my $relative = 'xCAT-genesis-builder/xCAT-genesis-base.spec';
|
||||
plan skip_all => "$relative not found" unless -f repo_path($relative);
|
||||
plan tests => 4;
|
||||
|
||||
my @lines = split /\n/, slurp_repo_file($relative);
|
||||
|
||||
# getcert, getdestiny, getipmi and getadapter all run the openssl command. el8 and el9
|
||||
# held it in the buildroot as a dependency of something else; el10 does not.
|
||||
my @openssl = grep { /^BuildRequires:\s*openssl\s*$/ } @lines;
|
||||
is(scalar(@openssl), 1, 'the spec build-requires openssl');
|
||||
|
||||
my ($buildarch) = grep { $lines[$_] =~ /^BuildArch:\s*noarch/ } 0 .. $#lines;
|
||||
ok(defined $buildarch, 'the spec sets BuildArch: noarch');
|
||||
|
||||
# rpm reads the spec a second time with the target set to noarch, so %{_target_cpu} is
|
||||
# "noarch" from BuildArch onwards. %{tarch} keeps the real architecture.
|
||||
my @late_target_cpu = grep { $lines[$_] =~ /_target_cpu/ } ($buildarch + 1) .. $#lines;
|
||||
is(scalar(@late_target_cpu), 0,
|
||||
'%{_target_cpu} is not read after BuildArch: noarch')
|
||||
or diag(join "\n", map { ($_ + 1) . ": $lines[$_]" } @late_target_cpu);
|
||||
|
||||
my ($openssl_line) = grep { $lines[$_] =~ /^BuildRequires:\s*openssl\s*$/ } 0 .. $#lines;
|
||||
my $guarded = 0;
|
||||
if (defined $openssl_line) {
|
||||
for my $i (reverse 0 .. $openssl_line - 1) {
|
||||
last if $lines[$i] =~ /^%endif/;
|
||||
$guarded = 1, last if $lines[$i] =~ /^%if/;
|
||||
}
|
||||
}
|
||||
is($guarded, 0, 'openssl is build-required on every release');
|
||||
@@ -21,7 +21,7 @@ if (!-f $verifier) {
|
||||
done_testing();
|
||||
exit;
|
||||
}
|
||||
plan tests => 18;
|
||||
plan tests => 22;
|
||||
|
||||
my $tmpdir = tempdir(CLEANUP => 1);
|
||||
my $module_seq = 0;
|
||||
@@ -74,6 +74,22 @@ my $noopenssl = build_payload(sshd_execs_session => 1, session_helper => 1, tmux
|
||||
isnt($rc, 0, 'a payload without openssl fails');
|
||||
like($err, qr/openssl/, 'the missing openssl is named');
|
||||
|
||||
# dracut_install installs an absolute path at that same path, so a name starting with "/" is a
|
||||
# command the payload must carry. doxcat, getdestiny and the firmware wrappers all run awk.
|
||||
my $noawk = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1,
|
||||
dhclient => 1, mktemp => 1, commands => [qw(openssl wget tar)], absent => ['usr/bin/awk']);
|
||||
($rc, $err) = run_with_commands($module, $noawk);
|
||||
isnt($rc, 0, 'a payload without the absolute path /usr/bin/awk fails');
|
||||
like($err, qr{/usr/bin/awk}, 'the missing /usr/bin/awk is named');
|
||||
|
||||
# The module names data files by absolute path too. Genesis resolves service names with
|
||||
# /etc/services.
|
||||
my $noservices = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1,
|
||||
dhclient => 1, mktemp => 1, commands => [qw(openssl wget tar)], absent => ['etc/services']);
|
||||
($rc, $err) = run_with_commands($module, $noservices);
|
||||
isnt($rc, 0, 'a payload without the absolute path /etc/services fails');
|
||||
like($err, qr{/etc/services}, 'the missing /etc/services is named');
|
||||
|
||||
# The DHCP client is release-dependent, so the module installs it inside a conditional. Those
|
||||
# names are not the contract; the spec passes the one it wants as a required path.
|
||||
my $conditional = write_module_setup(['wget'], ['dhclient']);
|
||||
@@ -114,12 +130,16 @@ sub build_payload {
|
||||
}
|
||||
write_text("$root/usr/sbin/dhclient", "dhclient\n") if $opt{dhclient};
|
||||
write_text("$root/usr/bin/mktemp", "mktemp\n") if $opt{mktemp};
|
||||
# The module also names two absolute paths, and dracut_install installs an absolute
|
||||
# path at that same path. They are data files every payload carries.
|
||||
make_path("$root/etc");
|
||||
write_text("$root/usr/bin/awk", "awk\n");
|
||||
write_text("$root/etc/services", "services\n");
|
||||
write_text("$root/usr/bin/$_", "$_\n") for @{ $opt{commands} || [] };
|
||||
|
||||
# The module written by write_module_setup names these two by absolute path.
|
||||
my %absent = map { $_ => 1 } @{ $opt{absent} || [] };
|
||||
for my $path (qw(usr/bin/awk etc/services)) {
|
||||
next if $absent{$path};
|
||||
my ($dir) = $path =~ m{^(.*)/};
|
||||
make_path("$root/$dir");
|
||||
write_text("$root/$path", "$path\n");
|
||||
}
|
||||
return $root;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env perl
|
||||
# The genesis specs name their package after the target arch: xCAT-genesis-scripts-<tarch> and
|
||||
# xCAT-genesis-base-<tarch>. %{tarch} comes from an %ifarch ladder, and an arch missing from that
|
||||
# ladder leaves the macro unexpanded instead of failing, so rpm builds a package whose Name
|
||||
# carries the macro.
|
||||
#
|
||||
# Expand each spec with rpmspec for every arch xCAT supports and assert the Name carries that arch.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $root = "$FindBin::Bin/../..";
|
||||
|
||||
sub command_exists { my ($c) = @_; return system("command -v $c >/dev/null 2>&1") == 0 }
|
||||
|
||||
plan skip_all => 'rpmspec is not installed' unless command_exists('rpmspec');
|
||||
|
||||
# arch under test => the tarch the spec must resolve it to (x86 and ppc64 are historical names
|
||||
# genesis keeps; see genesis_tarch_from_targetarch in buildrpms.pl).
|
||||
my %tarch = (
|
||||
x86_64 => 'x86_64',
|
||||
i686 => 'x86',
|
||||
ppc64le => 'ppc64',
|
||||
aarch64 => 'aarch64',
|
||||
riscv64 => 'riscv64',
|
||||
);
|
||||
|
||||
my %spec = (
|
||||
'xCAT-genesis-scripts' => "$root/xCAT-genesis-scripts/xCAT-genesis-scripts.spec",
|
||||
'xCAT-genesis-base' => "$root/xCAT-genesis-builder/xCAT-genesis-base.spec",
|
||||
);
|
||||
|
||||
for my $pkg (sort keys %spec) {
|
||||
my $spec = $spec{$pkg};
|
||||
ok(-f $spec, "$pkg spec is present") or next;
|
||||
for my $arch (sort keys %tarch) {
|
||||
my $name = `rpmspec --target $arch -q --qf '%{NAME}' --define 'version 2.19.0' --define 'release snap0' @{[quotemeta $spec]} 2>/dev/null`;
|
||||
chomp $name;
|
||||
is($name, "$pkg-$tarch{$arch}", "$pkg on $arch is named $pkg-$tarch{$arch}");
|
||||
unlike($name, qr/%\{/, "$pkg on $arch leaves no unexpanded macro in its name");
|
||||
}
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env perl
|
||||
# Drive the genesis test case helpers. genesistest.pl needs a management node, so lift the
|
||||
# routines out and run them with rpm, dpkg and cat shadowed.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Slurper qw(read_text write_text);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use Test::More;
|
||||
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
my $helper = repo_path('xCAT-test/autotest/testcase/genesis/genesistest.pl');
|
||||
my $shell = repo_path('xCAT-test/autotest/testcase/genesis/test.sh');
|
||||
plan skip_all => 'genesis testcase helpers not found' unless -f $helper && -f $shell;
|
||||
plan tests => 16;
|
||||
|
||||
my $tmpdir = tempdir(CLEANUP => 1);
|
||||
my $source = read_text($helper);
|
||||
|
||||
eval_subs($source, qw(get_os get_arch check_genesis_file));
|
||||
|
||||
# The destiny status check used to be wait_for_boot(), which waited for "booted" and ignored
|
||||
# its argument. Take whichever name the script carries, so this test fails on the status the
|
||||
# check waits for and not on a missing subroutine.
|
||||
my $waiter_name = waiter_name($source);
|
||||
eval_subs($source, $waiter_name);
|
||||
my $waiter = \&{"GenesisTest::$waiter_name"};
|
||||
|
||||
# get_os drives every later branch. AlmaLinux and Rocky release files say neither "Red Hat"
|
||||
# nor "suse" nor "ubuntu", so the management node read as unknown and the check was skipped.
|
||||
is(os_for("AlmaLinux release 9.8 (Olive Jaguar)\n"), 'redhat', 'AlmaLinux is a redhat family node');
|
||||
is(os_for("Rocky Linux release 9.5 (Blue Onyx)\n"), 'redhat', 'Rocky is a redhat family node');
|
||||
is(os_for("Red Hat Enterprise Linux release 9.5\n"), 'redhat', 'RHEL is still a redhat family node');
|
||||
is(os_for("SUSE Linux Enterprise Server 15 SP6\n"), 'sles', 'SLES is still detected');
|
||||
is(os_for("NAME=\"Ubuntu\"\nID=ubuntu\n"), 'ubuntu', 'Ubuntu is still detected');
|
||||
|
||||
# check_genesis_file answers with a return value. The caller used to read $? instead, so a
|
||||
# management node with no genesis packages reported success.
|
||||
{
|
||||
no warnings 'once';
|
||||
local $GenesisTest::os = 'redhat';
|
||||
is(rpm_check("xCAT-genesis-base-x86_64-2.19.0-snap1.noarch\nxCAT-genesis-scripts-x86_64-2.19.0-snap1.noarch\n"),
|
||||
0, 'both genesis packages installed reports success');
|
||||
is(rpm_check("xCAT-genesis-scripts-x86_64-2.19.0-snap1.noarch\n"),
|
||||
1, 'a missing genesis-base reports failure');
|
||||
eval_subs($source, qw(report_genesis_files));
|
||||
is(report_files("xCAT-genesis-scripts-x86_64-2.19.0-snap1.noarch\n"),
|
||||
1, 'report_genesis_files propagates the failure to its caller');
|
||||
}
|
||||
|
||||
# Genesis generates new host keys at every boot, and each case boots the node several times.
|
||||
{
|
||||
no warnings 'once';
|
||||
eval_subs($source, qw(forget_host_keys testxdsh));
|
||||
local $GenesisTest::noderange = 'xcat71-cn';
|
||||
my $run = run_testxdsh(3, genesis_prompt => 1, cmdline => 'destiny=shell');
|
||||
is($run->{status}, 0, 'testxdsh succeeds when the node answers in the Genesis shell');
|
||||
like($run->{makeknownhosts}, qr/\bxcat71-cn\b/, 'the node host keys are forgotten first');
|
||||
like($run->{makeknownhosts}, qr/-r/, 'makeknownhosts is asked to remove them');
|
||||
}
|
||||
|
||||
# xCAT sets nodelist.status from the destiny the node reports with getdestiny: "shell" for the
|
||||
# shell destiny, "configuring" for runcmd. "booted" belongs to an operating system install.
|
||||
{
|
||||
no warnings 'once';
|
||||
local $GenesisTest::noderange = 'xcat71-cn';
|
||||
is(wait_status('shell', 'shell'), 0,
|
||||
'a node that reports the shell destiny ends the wait');
|
||||
is(wait_status('configuring', 'configuring'), 0,
|
||||
'a node that reports the runcmd destiny ends the wait');
|
||||
isnt(wait_status('powering-on', 'shell'), 0,
|
||||
'a node that never reports its destiny fails the wait');
|
||||
}
|
||||
|
||||
# The shell case ignored the result of the wait, so it went on to xdsh whatever the node had
|
||||
# reported and rested entirely on the xdsh probes.
|
||||
{
|
||||
no warnings 'once';
|
||||
eval_subs($source, qw(run_nodeset_shell_test));
|
||||
local $GenesisTest::noderange = 'xcat71-cn';
|
||||
is(run_shell_test(status => 'shell'), 0,
|
||||
'the shell case passes when the node reports the shell destiny');
|
||||
isnt(run_shell_test(status => 'powering-on'), 0,
|
||||
'the shell case fails when the node never reports the shell destiny');
|
||||
}
|
||||
|
||||
#---
|
||||
# wait_status: drive the destiny status check with lsdef shadowed to report one status. The
|
||||
# extracted package neuters sleep, so the failure path does not wait five minutes.
|
||||
#---
|
||||
sub wait_status {
|
||||
my ($reported, $expected) = @_;
|
||||
local $ENV{PATH} = stub_bin(lsdef =>
|
||||
"#!/bin/sh\nprintf 'xcat71-cn: status=%s\\n' " . shell_quote($reported)) . ":$ENV{PATH}";
|
||||
return $waiter->($expected);
|
||||
}
|
||||
|
||||
#---
|
||||
# run_shell_test: drive the shell case with every command it runs shadowed. xdsh always answers
|
||||
# as a Genesis node, so the only thing under test is what the case does with the node status.
|
||||
#---
|
||||
sub run_shell_test {
|
||||
my (%opt) = @_;
|
||||
my $dir = tempdir(DIR => $tmpdir, CLEANUP => 1);
|
||||
write_text("$dir/nodeset", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/rpower", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/makeknownhosts", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/lsdef", "#!/bin/sh\nprintf 'xcat71-cn: status=%s\\n' " . shell_quote($opt{status}) . "\n");
|
||||
write_text("$dir/xdsh", "#!/bin/sh\nfor a in \"\$@\"; do\n case \"\$a\" in\n */cmdline|/proc/cmdline) printf '%s\\n' 'destiny=shell'; exit 0;;\n esac\ndone\nprintf '%s\\n' '[xCAT Genesis running on node]'\n");
|
||||
chmod 0755, map { "$dir/$_" } qw(nodeset rpower makeknownhosts lsdef xdsh);
|
||||
local $ENV{PATH} = "$dir:$ENV{PATH}";
|
||||
return GenesisTest::run_nodeset_shell_test();
|
||||
}
|
||||
|
||||
#---
|
||||
# run_testxdsh: drive testxdsh with makeknownhosts and xdsh shadowed. xdsh is asked twice --
|
||||
# once for the prompt, once for the file -- and the stub answers both from its arguments.
|
||||
#---
|
||||
sub run_testxdsh {
|
||||
my ($value, %opt) = @_;
|
||||
my $dir = tempdir(DIR => $tmpdir, CLEANUP => 1);
|
||||
my $log = "$dir/makeknownhosts.log";
|
||||
write_text("$dir/makeknownhosts", "#!/bin/sh\necho \"\$@\" >> '$log'\n");
|
||||
my $prompt = $opt{genesis_prompt} ? '[xCAT Genesis running on node]' : 'sh-5.1';
|
||||
write_text("$dir/xdsh", "#!/bin/sh\nfor a in \"\$@\"; do\n case \"\$a\" in\n */cmdline|/proc/cmdline) printf '%s\\n' '$opt{cmdline}'; exit 0;;\n esac\ndone\nprintf '%s\\n' '$prompt'\n");
|
||||
chmod 0755, "$dir/makeknownhosts", "$dir/xdsh";
|
||||
local $ENV{PATH} = "$dir:$ENV{PATH}";
|
||||
my $status = GenesisTest::testxdsh($value);
|
||||
return { status => $status, makeknownhosts => (-f $log ? read_text($log) : '') };
|
||||
}
|
||||
|
||||
#---
|
||||
# eval_subs: lift named subs out of the script and compile them into a scratch package, so
|
||||
# they can be run without a management node. Bails out when a sub stops being extractable.
|
||||
#---
|
||||
sub eval_subs {
|
||||
my ($text, @names) = @_;
|
||||
my $code = "package GenesisTest;\nno strict;\nno warnings;\nour \$os;\nour \$check_genesis_file;\nour \$noderange;\n";
|
||||
# The waits are minutes long. Neuter sleep so the extracted routines run at test speed.
|
||||
$code .= "use subs qw(sleep);\nsub sleep { \$GenesisTest::SLEPT += (\$_[0] || 0); return 1; }\n";
|
||||
$code .= "sub send_msg { push \@GenesisTest::MSG, \$_[1]; return 0; }\n";
|
||||
foreach my $name (@names) {
|
||||
my ($body) = $text =~ /^(sub \Q$name\E \{.*?^\})$/ms;
|
||||
die("sub $name() not found in $helper") unless defined $body;
|
||||
$code .= "$body\n";
|
||||
}
|
||||
$code .= "1;\n";
|
||||
eval $code or die("cannot compile the extracted helpers: $@");
|
||||
}
|
||||
|
||||
#---
|
||||
# waiter_name: the name the script gives its destiny status check.
|
||||
#---
|
||||
sub waiter_name {
|
||||
my ($text) = @_;
|
||||
foreach my $name (qw(wait_for_node_status wait_for_boot)) {
|
||||
return $name if $text =~ /^sub \Q$name\E \{/m;
|
||||
}
|
||||
die("no destiny status check found in $helper");
|
||||
}
|
||||
|
||||
#---
|
||||
# os_for: run get_os with `cat` shadowed so it reads the release text under test.
|
||||
#---
|
||||
sub os_for {
|
||||
my ($release) = @_;
|
||||
local $ENV{PATH} = stub_bin(cat => "#!/bin/sh\nprintf '%s' " . shell_quote($release)) . ":$ENV{PATH}";
|
||||
return GenesisTest::get_os();
|
||||
}
|
||||
|
||||
#---
|
||||
# rpm_check: run check_genesis_file with `rpm` shadowed so `rpm -qa` lists the given packages.
|
||||
#---
|
||||
sub rpm_check {
|
||||
my ($installed) = @_;
|
||||
local $ENV{PATH} = stub_bin(rpm => "#!/bin/sh\nprintf '%s' " . shell_quote($installed)) . ":$ENV{PATH}";
|
||||
return GenesisTest::check_genesis_file('x86_64');
|
||||
}
|
||||
|
||||
sub report_files {
|
||||
my ($installed) = @_;
|
||||
local $ENV{PATH} = stub_bin(rpm => "#!/bin/sh\nprintf '%s' " . shell_quote($installed)) . ":$ENV{PATH}";
|
||||
return GenesisTest::report_genesis_files('x86_64');
|
||||
}
|
||||
|
||||
#---
|
||||
# shell_quote: single-quote a string for /bin/sh.
|
||||
#---
|
||||
sub shell_quote {
|
||||
my ($v) = @_;
|
||||
$v =~ s/'/'\\''/g;
|
||||
return "'$v'";
|
||||
}
|
||||
|
||||
#---
|
||||
# stub_bin: a directory holding one shadow command, ahead of the real one on PATH.
|
||||
#---
|
||||
sub stub_bin {
|
||||
my (%cmd) = @_;
|
||||
my $dir = tempdir(DIR => $tmpdir, CLEANUP => 1);
|
||||
while (my ($name, $body) = each %cmd) {
|
||||
write_text("$dir/$name", $body);
|
||||
chmod 0755, "$dir/$name";
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# The scratch package below declares these; the test names them once each.
|
||||
no warnings 'once';
|
||||
|
||||
my $source = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/kvm.pm";
|
||||
open(my $source_fh, '<', $source) or die "open $source: $!";
|
||||
my $content = do { local $/; <$source_fh> };
|
||||
close($source_fh) or die "close $source: $!";
|
||||
|
||||
my @routines;
|
||||
for my $name (qw(createstorage build_diskstruct guest_arch_profile getUnits
|
||||
default_storagemodel)) {
|
||||
my ($routine) = $content =~ /^(sub \Q$name\E\s*\{.*?^\})/ms;
|
||||
die("could not extract $name from kvm.pm") unless $routine;
|
||||
push(@routines, $routine);
|
||||
}
|
||||
|
||||
# kvm.pm needs a management node to load, so createstorage runs in a scratch package.
|
||||
# get_filepath_by_url is the routine that reaches libvirt; it records the device name it is
|
||||
# asked for, which is the name createstorage gives the volume of the node.
|
||||
my $harness = <<'PERL';
|
||||
package KVMStore;
|
||||
our ($node, $confdata, $clonemethod, @asked);
|
||||
sub getstorageformat { my ($cfginfo) = @_; return $cfginfo->{storageformat}; }
|
||||
sub get_filepath_by_url { my %args = @_; push(@asked, $args{dev}); return $args{dev}; }
|
||||
sub oldCreateStorage { push(@asked, 'oldCreateStorage'); }
|
||||
sub get_multiple_paths_by_url { return {}; }
|
||||
PERL
|
||||
|
||||
eval $harness . join("\n", @routines) . "\n1;\n"; ## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
die("could not load the kvm storage routines: $@") if $@;
|
||||
|
||||
# The name createstorage gives the volume of one node. $stale is a capture left live in this
|
||||
# block by an earlier successful match, which is the state createstorage runs in when a
|
||||
# routine on the call path matched a pattern that has a group.
|
||||
sub volume_dev {
|
||||
my (%args) = @_;
|
||||
my $storage = $args{storage} // 'dir:///var/lib/libvirt/images/';
|
||||
my $cfginfo = {
|
||||
node => 'cn1',
|
||||
host => 'hyp1',
|
||||
storage => $storage,
|
||||
storagemodel => $args{storagemodel},
|
||||
};
|
||||
@KVMStore::asked = ();
|
||||
# The match must run in this block, and nothing may match after it: perl restores $1 when
|
||||
# the block that set it ends, and any later successful match replaces what it holds.
|
||||
my $subject = 'left by an earlier match: ' . ($args{stale} // '');
|
||||
$subject =~ /match: (.*)/ if defined $args{stale};
|
||||
# A match without a group empties $1, which is the clean state the other cases need.
|
||||
$subject =~ /^left/ unless defined $args{stale};
|
||||
KVMStore::createstorage($storage, undef, '30G', $cfginfo, 1);
|
||||
return $KVMStore::asked[0];
|
||||
}
|
||||
|
||||
# dohyp sets storagemodel to scsi for every node it dispatches, whatever the architecture,
|
||||
# before mkvm reaches createstorage. That default is what names the volume of a node whose
|
||||
# vmstoragemodel is empty, and a riscv64 node depends on it: the riscv64 virt machine has no
|
||||
# IDE controller, so its volume must be sd*.
|
||||
is(volume_dev(storagemodel => 'scsi'), 'sda',
|
||||
'the scsi storage model names an sd* volume');
|
||||
|
||||
# A capture from a match made elsewhere must not name the volume. These are the values a
|
||||
# routine on the mkvm call path can leave in $1.
|
||||
is(volume_dev(storagemodel => 'scsi', stale => '/var/lib/libvirt/images/'), 'sda',
|
||||
'a path left by an earlier match does not name the volume');
|
||||
is(volume_dev(storagemodel => 'scsi', stale => 'virtio'), 'sda',
|
||||
'a model name left by an earlier match does not name the volume');
|
||||
is(volume_dev(storagemodel => 'virtio', stale => 'scsi'), 'vda',
|
||||
'an earlier match does not override vmstoragemodel either');
|
||||
|
||||
# The model stated on the vmstorage value, and vmstoragemodel, still name the volume.
|
||||
is(volume_dev(storage => 'dir:///var/lib/libvirt/images/=scsi'), 'sda',
|
||||
'a model on the vmstorage value names an sd* volume');
|
||||
is(volume_dev(storagemodel => 'virtio'), 'vda',
|
||||
'vmstoragemodel=virtio names a vd* volume');
|
||||
|
||||
# createstorage on its own defaults to ide. Nothing in the product reaches this today: dohyp
|
||||
# gives every node the default storage model first.
|
||||
is(volume_dev(), 'hda', 'createstorage alone defaults to an hd* volume');
|
||||
|
||||
# A node with no vmstoragemodel takes its sd* name from that default, so the two are driven
|
||||
# together.
|
||||
is(volume_dev(storagemodel => KVMStore::default_storagemodel()), 'sda',
|
||||
'the default storage model names an sd* volume');
|
||||
|
||||
# build_diskstruct reads $1 the same way, for a disk backed by a plain file. The device name
|
||||
# and the bus of that disk must come from the node, not from a match made elsewhere.
|
||||
sub file_disk {
|
||||
my (%args) = @_;
|
||||
local $KVMStore::node = 'cn1';
|
||||
local $KVMStore::confdata = {
|
||||
vm => { cn1 => [ { host => 'hyp1', storage => '/var/lib/libvirt/images/cn1.img' } ] },
|
||||
nodetype => { cn1 => [ { arch => $args{arch} } ] },
|
||||
hyp1 => { cpumodel => 'x86_64' },
|
||||
};
|
||||
my $chatter = '';
|
||||
my $disks;
|
||||
my $subject = 'left by an earlier match: ' . ($args{stale} // '');
|
||||
$subject =~ /match: (.*)/ if defined $args{stale};
|
||||
$subject =~ /^left/ unless defined $args{stale};
|
||||
{
|
||||
open(my $capture, '>', \$chatter) or die "capture stdout: $!";
|
||||
local *STDOUT = $capture;
|
||||
($disks) = KVMStore::build_diskstruct(undef);
|
||||
}
|
||||
return $disks->[0];
|
||||
}
|
||||
|
||||
is(file_disk(arch => 'x86_64')->{target}->{bus}, 'ide',
|
||||
'a file-backed disk of an x86_64 node is ide');
|
||||
is(file_disk(arch => 'x86_64', stale => 'virtio')->{target}->{bus}, 'ide',
|
||||
'a model name left by an earlier match does not choose the bus of a file-backed disk');
|
||||
is(file_disk(arch => 'riscv64', stale => 'ide')->{target}->{dev}, 'sda',
|
||||
'a riscv64 file-backed disk keeps its sd* name whatever an earlier match left behind');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# The scratch package below declares these; the test names them once each.
|
||||
no warnings 'once';
|
||||
|
||||
my $source = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/kvm.pm";
|
||||
open(my $source_fh, '<', $source) or die "open $source: $!";
|
||||
my $content = do { local $/; <$source_fh> };
|
||||
close($source_fh) or die "close $source: $!";
|
||||
|
||||
my @routines;
|
||||
for my $name (qw(build_diskstruct guest_arch_profile getUnits)) {
|
||||
my ($routine) = $content =~ /^(sub \Q$name\E\s*\{.*?^\})/ms;
|
||||
die("could not extract $name from kvm.pm") unless $routine;
|
||||
push(@routines, $routine);
|
||||
}
|
||||
|
||||
# kvm.pm needs a management node to load, so the disk builder runs in a scratch package.
|
||||
# get_multiple_paths_by_url is the only routine it calls that reaches libvirt; it answers
|
||||
# from $pool, which holds what a storage pool reports for one node.
|
||||
my $harness = <<'PERL';
|
||||
package KVMDisk;
|
||||
our ($node, $confdata, $pool);
|
||||
sub get_multiple_paths_by_url { return $pool; }
|
||||
PERL
|
||||
|
||||
eval $harness . join("\n", @routines) . "\n1;\n"; ## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
die("could not load the kvm disk builder: $@") if $@;
|
||||
|
||||
# Build the disks of a node of $arch whose vmstorage is a libvirt pool holding the volumes
|
||||
# in $pool: a path => { device, format } map, the shape get_multiple_paths_by_url returns.
|
||||
sub pool_disks {
|
||||
my ($arch, $pool) = @_;
|
||||
local $KVMDisk::node = 'cn1';
|
||||
local $KVMDisk::confdata = {
|
||||
vm => { cn1 => [ {
|
||||
host => 'hyp1',
|
||||
storage => 'dir:///var/lib/libvirt/images/',
|
||||
storagecache => 'writeback',
|
||||
} ] },
|
||||
nodetype => { cn1 => [ { arch => $arch } ] },
|
||||
hyp1 => { cpumodel => 'x86_64' },
|
||||
};
|
||||
local $KVMDisk::pool = $pool;
|
||||
my $chatter = '';
|
||||
my $disks;
|
||||
{
|
||||
open(my $capture, '>', \$chatter) or die "capture stdout: $!";
|
||||
local *STDOUT = $capture;
|
||||
($disks) = KVMDisk::build_diskstruct(undef);
|
||||
}
|
||||
die('build_diskstruct returned no disks') unless ref $disks eq 'ARRAY';
|
||||
return $disks;
|
||||
}
|
||||
|
||||
# One volume in the pool, named <node>.<device>.<format>. The disk is the first element;
|
||||
# the optical drive build_diskstruct always appends is the second.
|
||||
sub pool_disk {
|
||||
my ($arch, $device) = @_;
|
||||
my $path = "/var/lib/libvirt/images/cn1.$device.qcow2";
|
||||
return pool_disks($arch, { $path => { device => $device, format => 'qcow2' } })->[0];
|
||||
}
|
||||
|
||||
# A disk on a libvirt storage pool states the bus of the device name it is given. libvirt
|
||||
# reads the same names the same way: hd* is ide, sd* is scsi, vd* is virtio.
|
||||
is(pool_disk('x86_64', 'hda')->{target}->{bus}, 'ide',
|
||||
'an hd* disk on a storage pool is ide');
|
||||
is(pool_disk('x86_64', 'sda')->{target}->{bus}, 'scsi',
|
||||
'an sd* disk on a storage pool is scsi');
|
||||
is(pool_disk('x86_64', 'vda')->{target}->{bus}, 'virtio',
|
||||
'a vd* disk on a storage pool is virtio');
|
||||
|
||||
# The device name is the name of the volume in the pool, and stays it. The riscv64 virt
|
||||
# machine has no IDE controller, so a riscv64 node depends on that name being sd*.
|
||||
my $riscv = pool_disk('riscv64', 'sda');
|
||||
is($riscv->{target}->{dev}, 'sda', 'a riscv64 pool disk keeps the sd* name of its volume');
|
||||
is($riscv->{target}->{bus}, 'scsi', 'a riscv64 pool disk is scsi, not ide');
|
||||
|
||||
my $riscv_all = pool_disks('riscv64',
|
||||
{ '/var/lib/libvirt/images/cn1.sda.qcow2' => { device => 'sda', format => 'qcow2' } });
|
||||
is($riscv_all->[1]->{device}, 'cdrom', 'the riscv64 guest still gets an optical drive');
|
||||
like($riscv_all->[1]->{target}->{dev}, qr/^sd/,
|
||||
'the riscv64 optical drive is named sd*, not hd*');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# The scratch package below declares these; the test names them once each.
|
||||
no warnings 'once';
|
||||
|
||||
my $source = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/kvm.pm";
|
||||
open(my $source_fh, '<', $source) or die "open $source: $!";
|
||||
my $content = do { local $/; <$source_fh> };
|
||||
close($source_fh) or die "close $source: $!";
|
||||
|
||||
my @routines;
|
||||
for my $name (qw(build_xmldesc guest_arch_profile build_oshash build_diskstruct getUnits)) {
|
||||
my ($routine) = $content =~ /^(sub \Q$name\E\s*\{.*?^\})/ms;
|
||||
die("could not extract $name from kvm.pm") unless $routine;
|
||||
push(@routines, $routine);
|
||||
}
|
||||
|
||||
# kvm.pm needs a management node to load, so the domain builder runs in a scratch package.
|
||||
# Only the routines that reach libvirt or the xCAT database are replaced; the domain builder
|
||||
# itself is the code under test.
|
||||
my $harness = <<'PERL';
|
||||
package KVMArch;
|
||||
use XML::Simple qw(XMLout);
|
||||
our ($node, $confdata, $updatetable, $hypconn);
|
||||
sub getNodeUUID { return '00000000-0000-0000-0000-000000000001'; }
|
||||
sub get_multiple_paths_by_url { return {}; }
|
||||
sub build_nicstruct { return []; }
|
||||
sub genpassword { return 'password'; }
|
||||
PERL
|
||||
|
||||
eval $harness . join("\n", @routines) . "\n1;\n"; ## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
die("could not load the kvm domain builder: $@") if $@;
|
||||
|
||||
# Build one domain for a node of $guest_arch on a hypervisor that reports $hyp_cpumodel.
|
||||
sub domain_xml {
|
||||
my ($guest_arch, $hyp_cpumodel) = @_;
|
||||
local $KVMArch::node = 'cn1';
|
||||
local $KVMArch::confdata = {
|
||||
vm => { cn1 => [ { host => 'hyp1', memory => 8192, cpus => 4 } ] },
|
||||
nodetype => { cn1 => [ { arch => $guest_arch, os => 'rocky10.2' } ] },
|
||||
hyp1 => { cpumodel => $hyp_cpumodel },
|
||||
};
|
||||
local $KVMArch::updatetable = {};
|
||||
my $xml = KVMArch::build_xmldesc('cn1');
|
||||
die("build_xmldesc returned no XML for $guest_arch on $hyp_cpumodel")
|
||||
unless defined $xml and !ref $xml;
|
||||
return $xml;
|
||||
}
|
||||
|
||||
sub os_type_element {
|
||||
my ($xml) = @_;
|
||||
my ($attrs) = $xml =~ m{<type\b([^>]*)>hvm</type>}s;
|
||||
return defined $attrs ? $attrs : '';
|
||||
}
|
||||
|
||||
# A riscv64 node on an x86_64 hypervisor. The guest architecture is not the host
|
||||
# architecture, so the domain runs under emulation and states its own machine type.
|
||||
my $riscv = domain_xml('riscv64', 'x86_64');
|
||||
like($riscv, qr/<domain\b[^>]*\btype="qemu"/,
|
||||
'a riscv64 guest on an x86_64 hypervisor is a qemu domain, not kvm');
|
||||
like(os_type_element($riscv), qr/\barch="riscv64"/,
|
||||
'the domain arch is the arch of the node');
|
||||
like(os_type_element($riscv), qr/\bmachine="virt"/,
|
||||
'a riscv64 guest uses the virt machine type');
|
||||
like($riscv, qr/<os\b[^>]*\bfirmware="efi"/,
|
||||
'a riscv64 virt guest boots UEFI');
|
||||
unlike($riscv, qr/<(?:pae|acpi|apic)\b/,
|
||||
'pae, acpi and apic are x86 features and are left out of a riscv64 guest');
|
||||
unlike($riscv, qr/<bios\b/,
|
||||
'the SeaBIOS serial option is left out of a riscv64 guest');
|
||||
unlike($riscv, qr/<input\b/,
|
||||
'the riscv64 virt machine has no USB controller, so it gets no USB tablet');
|
||||
|
||||
# POWER is unchanged: the arch still comes from the hypervisor there.
|
||||
my $power = domain_xml('ppc64le', 'ppc64le');
|
||||
like($power, qr/<domain\b[^>]*\btype="kvm"/, 'a POWER guest stays a kvm domain');
|
||||
like(os_type_element($power), qr/\barch="ppc64"/, 'ppc64le hypervisors keep arch ppc64');
|
||||
like(os_type_element($power), qr/\bmachine="pseries"/, 'ppc64le hypervisors keep machine pseries');
|
||||
|
||||
# x86_64 on x86_64 is unchanged: libvirt picks the arch and the machine type.
|
||||
my $x86 = domain_xml('x86_64', 'x86_64');
|
||||
like($x86, qr/<domain\b[^>]*\btype="kvm"/, 'an x86_64 guest stays a kvm domain');
|
||||
unlike(os_type_element($x86), qr/\barch=/, 'an x86_64 guest states no arch');
|
||||
unlike(os_type_element($x86), qr/\bmachine=/, 'an x86_64 guest states no machine type');
|
||||
like($x86, qr/<input\b[^>]*\bbus="usb"/, 'an x86_64 guest keeps the USB tablet');
|
||||
|
||||
# The disks of a riscv64 guest. The virt machine has no IDE controller, so an ide disk or an
|
||||
# hd* optical drive makes libvirt refuse the domain.
|
||||
sub disk_struct {
|
||||
my ($guest_arch) = @_;
|
||||
local $KVMArch::node = 'cn1';
|
||||
local $KVMArch::confdata = {
|
||||
vm => { cn1 => [ { host => 'hyp1', storage => '/var/lib/libvirt/images/cn1.img' } ] },
|
||||
nodetype => { cn1 => [ { arch => $guest_arch } ] },
|
||||
hyp1 => { cpumodel => 'x86_64' },
|
||||
};
|
||||
my $chatter = '';
|
||||
my $disks;
|
||||
{
|
||||
open(my $capture, '>', \\$chatter) or die "capture stdout: $!";
|
||||
local *STDOUT = $capture;
|
||||
($disks) = KVMArch::build_diskstruct(undef);
|
||||
}
|
||||
return $disks;
|
||||
}
|
||||
|
||||
my $riscv_disks = disk_struct('riscv64');
|
||||
is($riscv_disks->[0]->{target}->{bus}, 'scsi', 'a riscv64 disk is scsi, not ide');
|
||||
like($riscv_disks->[0]->{target}->{dev}, qr/^sd/, 'a riscv64 disk is named sd*');
|
||||
is($riscv_disks->[1]->{device}, 'cdrom', 'the guest still gets an optical drive');
|
||||
like($riscv_disks->[1]->{target}->{dev}, qr/^sd/, 'a riscv64 optical drive is named sd*, not hd*');
|
||||
|
||||
my $x86_disks = disk_struct('x86_64');
|
||||
is($x86_disks->[0]->{target}->{bus}, 'ide', 'an x86_64 disk keeps the ide default');
|
||||
like($x86_disks->[1]->{target}->{dev}, qr/^hd/, 'an x86_64 optical drive keeps the hd* name');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env perl
|
||||
# mknb stages the Genesis payload before it can build a netboot image. Those copies are the only
|
||||
# point at which mknb learns that an installed Genesis image is unusable.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
use Test::More;
|
||||
|
||||
BEGIN { $INC{'xCAT/Utils.pm'} = 1; $INC{'xCAT/MsgUtils.pm'} = 1;
|
||||
$INC{'xCAT/Table.pm'} = 1; $INC{'xCAT/NetworkUtils.pm'} = 1;
|
||||
$INC{'xCAT/TableUtils.pm'} = 1; $INC{'xCAT_monitoring/monitorctrl.pm'} = 1; }
|
||||
|
||||
require "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/mknb.pm";
|
||||
|
||||
can_ok('xCAT_plugin::mknb', 'stage_genesis_payload')
|
||||
or die('mknb has no stage_genesis_payload to drive');
|
||||
|
||||
# Drive the routine with a runner that fails exactly one copy, so each assertion names the
|
||||
# copy it is about rather than the pair.
|
||||
sub stage {
|
||||
my (%opt) = @_;
|
||||
my @ran;
|
||||
my ($rc, $src) = xCAT_plugin::mknb::stage_genesis_payload(
|
||||
genesis_type => $opt{type} // 'legacy',
|
||||
genesis_dir => '/opt/xcat/share/xcat/netboot/genesis/x86_64',
|
||||
tftpdir => '/tftpboot',
|
||||
arch => 'x86_64',
|
||||
tempdir => '/tmp/scratch',
|
||||
run => sub {
|
||||
my ($cmd) = @_;
|
||||
push @ran, $cmd;
|
||||
return ($opt{fail} && $cmd =~ /$opt{fail}/) ? 256 : 0;
|
||||
},
|
||||
);
|
||||
return { rc => $rc, src => $src, ran => \@ran };
|
||||
}
|
||||
|
||||
# --- legacy: both copies must be able to fail the step -----------------------
|
||||
my $ok = stage();
|
||||
is($ok->{rc}, 0, 'a legacy image whose copies both succeed stages cleanly');
|
||||
is(scalar @{ $ok->{ran} }, 2, 'the legacy path copies the root tree and the kernel');
|
||||
|
||||
my $nofs = stage(fail => qr{/fs/\*});
|
||||
isnt($nofs->{rc}, 0, 'an unreadable root tree fails the step');
|
||||
like($nofs->{src}, qr{/fs$}, 'and the failure names the root tree');
|
||||
|
||||
my $nokernel = stage(fail => qr{/kernel });
|
||||
isnt($nokernel->{rc}, 0, 'a missing kernel fails the step');
|
||||
like($nokernel->{src}, qr{/kernel$}, 'and the failure names the kernel, not the root tree');
|
||||
|
||||
# --- exported (OpenEmbedded) path -------------------------------------------
|
||||
my $nonb = stage(type => 'exported', fail => qr{/nbroot/\*});
|
||||
isnt($nonb->{rc}, 0, 'an unreadable nbroot fails the step');
|
||||
like($nonb->{src}, qr{/nbroot$}, 'and the failure names nbroot');
|
||||
|
||||
my $oknb = stage(type => 'exported');
|
||||
is($oknb->{rc}, 0, 'an exported image whose copy succeeds stages cleanly');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# genimage builds the netboot initrd for ppc64el with mkinitrd, not dracut, and mkinitrd
|
||||
# copies usr/bin/dig out of the rootimg. A release with no ppc64el package list falls back
|
||||
# to compute.pkglist, which installs no dig, and genimage stops with
|
||||
# "Failed to find usr/bin/dig". The package that holds dig changed name in 22.04.
|
||||
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
my $netboot = 'xCAT-server/share/xcat/netboot/ubuntu';
|
||||
|
||||
sub packages {
|
||||
my ($path) = @_;
|
||||
my $full = repo_path($path);
|
||||
return unless -r $full;
|
||||
open(my $fh, '<', $full) or die "cannot read $full: $!";
|
||||
my @packages = grep { length && !/^#/ } map { my $l = $_; chomp $l; $l =~ s/\s+//g; $l } <$fh>;
|
||||
close($fh);
|
||||
return \@packages;
|
||||
}
|
||||
|
||||
foreach my $release (qw(20.04 22.04 24.04 26.04)) {
|
||||
my $list = packages("$netboot/compute.ubuntu$release.ppc64el.pkglist");
|
||||
ok($list, "$release has a ppc64el netboot package list");
|
||||
|
||||
SKIP: {
|
||||
skip "no $release ppc64el package list to inspect", 4 unless $list;
|
||||
|
||||
ok(scalar(grep { $_ eq 'dnsutils' || $_ eq 'bind9-dnsutils' } @{$list}),
|
||||
"the $release ppc64el image installs the dig that mkinitrd copies");
|
||||
ok(scalar(grep { $_ eq 'linux-image-generic' } @{$list}),
|
||||
"the $release ppc64el image installs a kernel");
|
||||
ok(scalar(grep { $_ eq 'nfs-common' } @{$list}),
|
||||
"the $release ppc64el image can mount its root over NFS");
|
||||
is_deeply(packages("$netboot/compute.ubuntu$release.ppc64le.pkglist"), $list,
|
||||
"the $release ppc64le list matches the ppc64el list");
|
||||
}
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -98,7 +98,7 @@ $tmpl_path = "$FindBin::Bin/../../xCAT-server/share/xcat/install/ubuntu/compute.
|
||||
unless -f $tmpl_path;
|
||||
|
||||
SKIP: {
|
||||
skip 'compute.subiquity.tmpl not found', 4 unless -f $tmpl_path;
|
||||
skip 'compute.subiquity.tmpl not found', 6 unless -f $tmpl_path;
|
||||
my $tmpl = do { local $/; open my $fh, '<', $tmpl_path or die $!; <$fh> };
|
||||
|
||||
# The netplan-writing part of late-commands, verbatim: from the resolved values down to the
|
||||
@@ -106,7 +106,7 @@ SKIP: {
|
||||
my ($snippet) = $tmpl =~ /(installnic="#SUBIQUITYINSTALLNIC#".*?fi;)/s;
|
||||
ok($snippet, 'the netplan late-command is rendered from the resolved values');
|
||||
|
||||
skip 'netplan late-command not found in the template', 3 unless $snippet;
|
||||
skip 'netplan late-command not found in the template', 5 unless $snippet;
|
||||
$snippet =~ s/''/'/g; # undo the YAML single-quote escaping
|
||||
|
||||
for my $case (
|
||||
@@ -132,9 +132,16 @@ SKIP: {
|
||||
' match:', qq( macaddress: "$case->{mac}"),
|
||||
($case->{setname} ne '' ? " set-name: $case->{setname}" : ()),
|
||||
' dhcp4: true',
|
||||
' dhcp4-overrides:',
|
||||
' use-domains: true',
|
||||
);
|
||||
is($netplan, join("\n", @expected) . "\n",
|
||||
"the netplan written for $case->{name} matches the resolved values");
|
||||
|
||||
# systemd-networkd defaults UseDomains to no, so a netplan carrying dhcp4: true alone
|
||||
# drops the search domain DHCP offers, and no single-label name resolves on the node.
|
||||
like($netplan, qr/^\s+use-domains: true$/m,
|
||||
"the netplan for $case->{name} asks networkd to use the DHCP search domain");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,18 +31,25 @@ is( system("bash -n $pre_path 2>/dev/null"), 0,
|
||||
# redirected into a scratch tree. Both substitutions are asserted: if either stops
|
||||
# matching, this bails out rather than silently covering nothing or writing to /tmp.
|
||||
my ($storage_block) = $script =~ /(^if \[ -d \/sys\/firmware\/efi \]; then\n.*?\n^fi$)/ms;
|
||||
BAIL_OUT('the firmware branch that writes the partition file no longer matches')
|
||||
die('the firmware branch that writes the partition file no longer matches')
|
||||
unless $storage_block;
|
||||
|
||||
my $brackets = () = $storage_block =~ /\[ /g;
|
||||
BAIL_OUT("the partitioning block now has $brackets bracket tests; the shadow below covers one")
|
||||
unless $brackets == 1;
|
||||
die('the firmware test the shadow below answers is gone')
|
||||
unless $storage_block =~ /\[ -d \/sys\/firmware\/efi \]/;
|
||||
die('the block no longer asks uname for the machine architecture')
|
||||
unless $storage_block =~ /uname -m/;
|
||||
|
||||
my $sandbox = File::Temp::tempdir( CLEANUP => 1 );
|
||||
my $partfile = File::Spec->catfile( $sandbox, 'partitionfile' );
|
||||
# One redirect per firmware branch. Counting the branches rather than naming a number
|
||||
# keeps the guard true when a branch is added, and still fails loudly if a redirect
|
||||
# escapes the sandbox.
|
||||
my $branches = () = $storage_block =~ /^\s*cat <<EOF >\/tmp\/partitionfile$/mg;
|
||||
my $rewrites = ( $storage_block =~ s{/tmp/partitionfile}{$partfile}g );
|
||||
BAIL_OUT("expected two partition-file redirects to sandbox, rewrote $rewrites")
|
||||
unless $rewrites == 2;
|
||||
die("the block writes the partition file in $branches places and $rewrites were rewritten")
|
||||
unless $branches >= 2 && $rewrites == $branches;
|
||||
die('a partition-file path escaped the sandbox')
|
||||
if $storage_block =~ m{/tmp/partitionfile};
|
||||
|
||||
my %YAML_FOR;
|
||||
|
||||
@@ -54,13 +61,16 @@ sub partition_config_for {
|
||||
open( my $fh, '>', $script ) or die "Unable to write $script: $!";
|
||||
# `[` is shadowed rather than the condition rewritten: bash resolves a function
|
||||
# ahead of the builtin, so the script's own test runs unmodified.
|
||||
my $machine = $firmware eq 'prep' ? 'ppc64le' : 'x86_64';
|
||||
print {$fh} <<"SHELL";
|
||||
INSTALL_DISK=/dev/sdz
|
||||
logger() { :; }
|
||||
uname() { builtin echo $machine; }
|
||||
# Only the firmware probe is answered here; every other test falls through to the builtin.
|
||||
[() {
|
||||
case "\$1 \$2" in
|
||||
"-d /sys/firmware/efi") return @{[ $firmware eq 'uefi' ? 0 : 1 ]} ;;
|
||||
*) builtin echo "unexpected bracket test: \$*" >&2; builtin return 2 ;;
|
||||
*) builtin [ "\$@" ;;
|
||||
esac
|
||||
}
|
||||
$storage_block
|
||||
@@ -69,9 +79,9 @@ SHELL
|
||||
|
||||
unlink $partfile;
|
||||
system( 'bash', $script ) == 0
|
||||
or BAIL_OUT("the extracted partitioning block failed to run for $firmware");
|
||||
or die("the extracted partitioning block failed to run for $firmware");
|
||||
open( my $out_fh, '<', $partfile )
|
||||
or BAIL_OUT("the partitioning block wrote no file for $firmware: $!");
|
||||
or die("the partitioning block wrote no file for $firmware: $!");
|
||||
my $yaml = do { local $/; <$out_fh> };
|
||||
close($out_fh);
|
||||
$YAML_FOR{$firmware} = $yaml;
|
||||
@@ -110,7 +120,22 @@ ok( !exists $bios->{'efi-part'}, 'BIOS installs get no EFI partition' );
|
||||
is( $bios->{'bios-grub'}{flag}, 'bios_grub', 'they get a bios_grub partition instead' );
|
||||
is( $bios->{'disk-detected'}{grub_device}, 'true', 'and grub is installed to the disk' );
|
||||
|
||||
foreach my $firmware ( [ UEFI => $uefi ], [ BIOS => $bios ] ) {
|
||||
# POWER firmware reads neither an ESP nor a bios_grub partition. It boots from a PReP
|
||||
# partition, and curtin installs grub to that partition rather than to the disk.
|
||||
my $prep = partition_config_for('prep');
|
||||
ok( !exists $prep->{'efi-part'}, 'POWER installs get no EFI partition' );
|
||||
ok( !exists $prep->{'bios-grub'}, 'POWER installs get no bios_grub partition' );
|
||||
is( $prep->{'prep-part'}{type}, 'partition', 'POWER installs get a PReP partition' );
|
||||
is( $prep->{'prep-part'}{device}, 'disk-detected', 'on the detected install disk' );
|
||||
is( $prep->{'prep-part'}{flag}, 'prep', 'flagged prep, which is what SLOF reads' );
|
||||
is( $prep->{'prep-part'}{number}, '1', 'as the first partition' );
|
||||
is( $prep->{'prep-part'}{grub_device}, 'true', 'and grub is installed to it' );
|
||||
isnt( $prep->{'disk-detected'}{grub_device}, 'true',
|
||||
'not to the disk, which leaves POWER with nothing to boot' );
|
||||
ok( !exists $prep->{'prep-part-fs'},
|
||||
'the PReP partition carries no filesystem' );
|
||||
|
||||
foreach my $firmware ( [ UEFI => $uefi ], [ BIOS => $bios ], [ PReP => $prep ] ) {
|
||||
my ( $name, $config ) = @{$firmware};
|
||||
is( $config->{'root-part-fs'}{fstype}, 'ext4', "$name root filesystem is ext4" );
|
||||
is( $config->{'root-part-mount'}{path}, '/', "$name mounts root at /" );
|
||||
@@ -122,7 +147,7 @@ foreach my $firmware ( [ UEFI => $uefi ], [ BIOS => $bios ] ) {
|
||||
|
||||
# Subiquity re-serializes autoinstall.yaml and appends this file, so the block has
|
||||
# to start at column 0 -- asserted on what was written, not on the heredoc.
|
||||
foreach my $firmware ( [ UEFI => 'uefi' ], [ BIOS => 'bios' ] ) {
|
||||
foreach my $firmware ( [ UEFI => 'uefi' ], [ BIOS => 'bios' ], [ PReP => 'prep' ] ) {
|
||||
my ( $name, $key ) = @{$firmware};
|
||||
like( partition_yaml_for($key), qr/\Astorage:\n version: 1\n/,
|
||||
"$name config starts at column 0 with storage: version: 1" );
|
||||
|
||||
@@ -59,10 +59,11 @@ unlike($tmpl, qr/tr ''A-F'' ''a-f''/,
|
||||
'the shell no longer normalizes the MAC (Template.pm resolves mac.mac entries)');
|
||||
unlike($tmpl, qr/cut -d''\|'' -f1/,
|
||||
'the shell no longer splits mac.mac entries (Template.pm resolves them for this node)');
|
||||
like($tmpl, qr/printf ''%s\\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \\"\$\{installmac\}\\"" " set-name: \$\{installnic\}" " dhcp4: true" >\/target\/etc\/netplan\/00-xcat-install\.yaml;/, 'target netplan printf stays on one shell line');
|
||||
like($tmpl, qr/printf ''%s\\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \\"\$\{installmac\}\\"" " set-name: \$\{installnic\}" " dhcp4: true" " dhcp4-overrides:" " use-domains: true" >\/target\/etc\/netplan\/00-xcat-install\.yaml;/, 'target netplan printf stays on one shell line');
|
||||
like($tmpl, qr/" macaddress: \\"\$\{installmac\}\\""/, 'target netplan matches by MAC address');
|
||||
like($tmpl, qr/" set-name: \$\{installnic\}"/, 'target netplan sets the expected installnic name');
|
||||
like($tmpl, qr/"\s+dhcp4: true"/, 'target netplan enables DHCPv4 on installnic');
|
||||
like($tmpl, qr/"\s+use-domains: true"/, 'target netplan takes the search domain DHCP offers, so short names resolve on the node');
|
||||
like($tmpl, qr/printf ''%s\\n'' ''#HOSTNAME#'' >\/target\/etc\/hostname/, 'template writes target hostname before disabling cloud-init');
|
||||
like($tmpl, qr/sed -i ''s\/\^127\\\.0\\\.1\\\.1\.\*\/127\.0\.1\.1 #HOSTNAME#\/'' \/target\/etc\/hosts/, 'template updates target hosts entry for hostname');
|
||||
like($tmpl, qr/touch \/target\/etc\/cloud\/cloud-init\.disabled/, 'target cloud-init is disabled after target netplan is written');
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env perl
|
||||
# gettimezone names the timezone that goes into a kickstart or an autoyast profile, where the
|
||||
# value must be one token. It found the name by comparing /etc/localtime against every file under
|
||||
# /usr/share/zoneinfo, and when that pipeline failed it returned the sentence "Could not determine
|
||||
# timezone checksum" as if it were a name.
|
||||
#
|
||||
# A cloud image that runs on UTC ships no /etc/localtime, so the scan finds nothing there, and
|
||||
# anaconda refuses a timezone command that carries more than one argument.
|
||||
#
|
||||
# Utils.pm cannot be loaded here, so the routine is extracted and driven against a scratch root
|
||||
# with the two collaborators it calls replaced.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $source = "$FindBin::Bin/../../perl-xCAT/xCAT/Utils.pm";
|
||||
open(my $source_fh, '<', $source) or die "open $source: $!";
|
||||
my $content = do { local $/; <$source_fh> };
|
||||
close($source_fh) or die "close $source: $!";
|
||||
|
||||
my @routines;
|
||||
for my $name (qw(gettimezone _zone_from_path)) {
|
||||
my ($routine) = $content =~ /^(sub \Q$name\E\s*\n?\{.*?^\})/ms;
|
||||
die("could not extract $name from Utils.pm") unless $routine;
|
||||
push(@routines, $routine);
|
||||
}
|
||||
# The routines call each other unqualified and the caller reaches them through the class, so
|
||||
# they go back into the package they came from.
|
||||
eval "package xCAT::Utils;\n" . join("\n", @routines); ## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
die("could not load the timezone routines: $@") if $@;
|
||||
|
||||
# The collaborators gettimezone calls. The scan runs `find`, which must never look at the host
|
||||
# this test runs on, so it answers from the scratch root instead.
|
||||
our $SCAN_OUT = '';
|
||||
our $SCAN_RC = 1;
|
||||
{
|
||||
no warnings 'once';
|
||||
*xCAT::Utils::isAIX = sub { return 0 };
|
||||
*xCAT::Utils::runcmd = sub { $::RUNCMD_RC = $SCAN_RC; return $SCAN_OUT };
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------------------------
|
||||
=head3 scratch_root
|
||||
|
||||
Descriptions:
|
||||
A root with a zoneinfo tree, and /etc/localtime as a symlink into it when a zone is named.
|
||||
Arguments:
|
||||
$zone - the zone to link /etc/localtime to, or undef for a root with no /etc/localtime
|
||||
Returns:
|
||||
The root directory.
|
||||
=cut
|
||||
#-----------------------------------------------------------------------------------------------
|
||||
sub scratch_root {
|
||||
my ($zone) = @_;
|
||||
my $root = tempdir(CLEANUP => 1);
|
||||
make_path("$root/etc", "$root/usr/share/zoneinfo/America");
|
||||
for my $name ('UTC', 'America/Sao_Paulo') {
|
||||
open(my $fh, '>', "$root/usr/share/zoneinfo/$name") or die "create $name: $!";
|
||||
print {$fh} "TZif";
|
||||
close($fh) or die "close $name: $!";
|
||||
}
|
||||
symlink("../usr/share/zoneinfo/$zone", "$root/etc/localtime") or die "symlink: $!"
|
||||
if defined $zone;
|
||||
return $root;
|
||||
}
|
||||
|
||||
is(xCAT::Utils->gettimezone(root => scratch_root('America/Sao_Paulo')), 'America/Sao_Paulo',
|
||||
'the /etc/localtime symlink names the zone');
|
||||
|
||||
# No /etc/localtime, so the scan reports nothing.
|
||||
my $none = xCAT::Utils->gettimezone(root => scratch_root(undef));
|
||||
is($none, 'UTC', 'a root with no /etc/localtime falls back to UTC');
|
||||
unlike($none, qr/\s/,
|
||||
'the value is one token, which is all the kickstart timezone command accepts');
|
||||
|
||||
# /etc/timezone is consulted before the fallback.
|
||||
my $root = scratch_root(undef);
|
||||
open(my $tz_fh, '>', "$root/etc/timezone") or die "create /etc/timezone: $!";
|
||||
print {$tz_fh} "America/Sao_Paulo\n";
|
||||
close($tz_fh) or die "close /etc/timezone: $!";
|
||||
is(xCAT::Utils->gettimezone(root => $root), 'America/Sao_Paulo',
|
||||
'/etc/timezone names the zone when there is no symlink');
|
||||
|
||||
# The scan still answers for a root whose /etc/localtime is a copy rather than a symlink.
|
||||
$root = scratch_root(undef);
|
||||
open(my $copy_fh, '>', "$root/etc/localtime") or die "create /etc/localtime: $!";
|
||||
print {$copy_fh} "TZif";
|
||||
close($copy_fh) or die "close /etc/localtime: $!";
|
||||
{
|
||||
local $SCAN_OUT = "$root/usr/share/zoneinfo/America/Sao_Paulo\n";
|
||||
local $SCAN_RC = 0;
|
||||
is(xCAT::Utils->gettimezone(root => $root), 'America/Sao_Paulo',
|
||||
'the zoneinfo scan names the zone when /etc/localtime is a copy');
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -12,7 +12,7 @@ use lib "$FindBin::Bin/../lib";
|
||||
use lib "$FindBin::Bin/../../build-utils/lib";
|
||||
use Test::More;
|
||||
|
||||
use XCAT::BuildUtils qw(XCAT_PROBE_HELPERS);
|
||||
use XCAT::BuildUtils qw(XCAT_PROBE_HELPERS stage_probe_helpers);
|
||||
use XCAT::Test::File qw(repo_path slurp_repo_file);
|
||||
|
||||
my @helpers = qw(
|
||||
@@ -29,7 +29,6 @@ my @affected_subcommands = qw(
|
||||
);
|
||||
|
||||
my $builder = slurp_repo_file('buildrpms.pl');
|
||||
my $debian_builder = slurp_repo_file('build-ubunturepo');
|
||||
my $installed_probe_test =
|
||||
slurp_repo_file('xCAT-test/autotest/testcase/probe/xcatproble_list');
|
||||
my $rpm_spec = slurp_repo_file('xCAT-probe/xCAT-probe.spec');
|
||||
@@ -67,6 +66,12 @@ like(
|
||||
'Debian package requires ss or the legacy netstat provider'
|
||||
);
|
||||
|
||||
# The Debian builder stages the helpers by calling stage_probe_helpers, so run it and
|
||||
# look at what it produced. The predecessor matched a `cp -f` line in build-ubunturepo,
|
||||
# which passed whenever that text was reformatted and failed whenever it moved.
|
||||
my $staged_probe_dir = File::Spec->catdir(tempdir(CLEANUP => 1), 'lib', 'perl', 'xCAT');
|
||||
stage_probe_helpers(repo_path(File::Spec->catdir('perl-xCAT', 'xCAT')), $staged_probe_dir);
|
||||
|
||||
for my $helper (@helpers) {
|
||||
my $source = repo_path(File::Spec->catfile('perl-xCAT', 'xCAT', $helper));
|
||||
ok(-f $source, "$helper source exists");
|
||||
@@ -75,9 +80,8 @@ for my $helper (@helpers) {
|
||||
scalar(grep { $_ eq $helper } XCAT_PROBE_HELPERS),
|
||||
"the shared builder helper list carries $helper"
|
||||
);
|
||||
like(
|
||||
$debian_builder,
|
||||
qr{cp -f [^\n]*/perl-xCAT/xCAT/\Q$helper\E\s+[^\n]*/lib/perl/xCAT/},
|
||||
ok(
|
||||
-f File::Spec->catfile($staged_probe_dir, $helper),
|
||||
"Debian builder stages $helper"
|
||||
);
|
||||
like(
|
||||
@@ -144,7 +148,7 @@ sub copy_tree {
|
||||
my ($source, $destination) = @_;
|
||||
my $rc = system('cp', '-R', $source, $destination);
|
||||
is($rc, 0, "copied $source into the package fixture")
|
||||
or BAIL_OUT("unable to create package fixture from $source");
|
||||
or die("unable to create package fixture from $source");
|
||||
}
|
||||
|
||||
sub run_command {
|
||||
|
||||
@@ -12,8 +12,8 @@ use Test::More;
|
||||
#
|
||||
# The deb side named xcat-genesis-scripts-amd64 in a plain Depends, and that package is
|
||||
# Architecture: all, so apt installed the x86 Genesis scripts (and, through them, the x86 Genesis
|
||||
# base) on a riscv64 management node. Restrict the dependency to the architectures that have a
|
||||
# legacy Genesis, and leave amd64 and ppc64el untouched.
|
||||
# base) on every management node that is not amd64. Name one scripts package per architecture that
|
||||
# has a legacy Genesis, so riscv64 gets none and ppc64el gets its own.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs(
|
||||
File::Spec->catdir( $FindBin::Bin, '..', '..' )
|
||||
@@ -37,10 +37,12 @@ foreach my $pkg ( [ 'xCAT', 'xcat' ], [ 'xCATsn', 'xcatsn' ] ) {
|
||||
my ($recommends) = $control =~ /^Recommends:\s*(.*)$/m;
|
||||
ok( defined $recommends, "$name debian/control has a Recommends line" );
|
||||
|
||||
my ($entry) = grep { /xcat-genesis-scripts/ } split( /\s*,\s*/, $depends );
|
||||
ok( defined $entry, "$name depends on a legacy Genesis scripts package" );
|
||||
like( $entry, qr/\[!riscv64\]/,
|
||||
"$name excludes riscv64 from the legacy Genesis scripts dependency" );
|
||||
my @entries = grep { /xcat-genesis-scripts/ } split( /\s*,\s*/, $depends );
|
||||
ok( scalar(@entries), "$name depends on a legacy Genesis scripts package" );
|
||||
my @unqualified = grep { !/\[(?:amd64|ppc64el)\]\s*$/ } @entries;
|
||||
is_deeply( \@unqualified, [],
|
||||
"$name asks for the legacy Genesis scripts of an architecture that has them" )
|
||||
or diag( "unqualified: @unqualified" );
|
||||
|
||||
SKIP: {
|
||||
skip( "Dpkg::Deps is not available", 7 ) unless $have_dpkg_deps;
|
||||
@@ -56,8 +58,8 @@ foreach my $pkg ( [ 'xCAT', 'xcat' ], [ 'xCATsn', 'xcatsn' ] ) {
|
||||
"$name on riscv64 does not pull the legacy Genesis scripts" );
|
||||
like( $reduced{amd64}, qr/xcat-genesis-scripts-amd64/,
|
||||
"$name on amd64 still pulls them" );
|
||||
like( $reduced{ppc64el}, qr/xcat-genesis-scripts-amd64/,
|
||||
"$name on ppc64el still pulls them" );
|
||||
like( $reduced{ppc64el}, qr/xcat-genesis-scripts-ppc64el/,
|
||||
"$name on ppc64el pulls the ppc64el ones" );
|
||||
|
||||
# The restriction must not take anything else with it: every other dependency of the
|
||||
# amd64 package must survive on riscv64.
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use File::Copy qw(copy);
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use Test::More;
|
||||
|
||||
my $program = "$FindBin::Bin/../xcattest";
|
||||
die("xcattest is not at $program") unless -f $program;
|
||||
|
||||
#---
|
||||
=head3 run_harness
|
||||
|
||||
Descriptions: Run xcattest over one fixture case file and return its log lines.
|
||||
Arguments:
|
||||
$case_text - the content of the fixture case file
|
||||
@names - the case names to run
|
||||
Returns: a reference to the array of log lines, and the failed-cases report lines
|
||||
=cut
|
||||
|
||||
#---
|
||||
sub run_harness {
|
||||
my ($case_text, @names) = @_;
|
||||
|
||||
# xcattest derives its result directory from the location of the program, so the copy
|
||||
# under the scratch tree keeps every file the run writes inside that tree.
|
||||
my $root = tempdir(CLEANUP => 1);
|
||||
make_path("$root/bin", "$root/cases");
|
||||
copy($program, "$root/bin/xcattest") or die("copy xcattest: $!");
|
||||
chmod 0755, "$root/bin/xcattest";
|
||||
|
||||
open(my $case_fh, '>', "$root/cases/fixture") or die("write the fixture case: $!");
|
||||
print $case_fh $case_text;
|
||||
close($case_fh) or die("close the fixture case: $!");
|
||||
|
||||
local $ENV{XCATTEST_CASEDIR} = "$root/cases";
|
||||
system($^X, "$root/bin/xcattest", '-q', '-t', join(',', @names));
|
||||
|
||||
my $slurp = sub {
|
||||
my ($path) = @_;
|
||||
open(my $fh, '<', $path) or die("open $path: $!");
|
||||
my @lines = <$fh>;
|
||||
close($fh) or die("close $path: $!");
|
||||
chomp(@lines);
|
||||
return @lines;
|
||||
};
|
||||
|
||||
my ($log) = glob("$root/share/xcat/tools/autotest/result/xcattest.log.*");
|
||||
die("the harness wrote no running log under $root") unless $log;
|
||||
my ($failed) = glob("$root/share/xcat/tools/autotest/result/failedcases.*");
|
||||
die("the harness wrote no failed-cases report under $root") unless $failed;
|
||||
|
||||
return ([ $slurp->($log) ], [ $slurp->($failed) ]);
|
||||
}
|
||||
|
||||
#---
|
||||
=head3 reported_checks
|
||||
|
||||
Descriptions: Select the check results the harness reported.
|
||||
Arguments:
|
||||
$lines - a reference to the array of log lines
|
||||
Returns: a reference to the array of CHECK lines, in the order they were reported
|
||||
=cut
|
||||
|
||||
#---
|
||||
sub reported_checks {
|
||||
my ($lines) = @_;
|
||||
return [ grep { /^CHECK:/ } @{$lines} ];
|
||||
}
|
||||
|
||||
# The second command fails its first check. The check after it on the same command, and the
|
||||
# checks of every command after it, describe the same run and must report their own result.
|
||||
my $mixed = <<'CASE';
|
||||
start:mixedchecks
|
||||
description:a failed check between checks that pass
|
||||
cmd:echo alpha
|
||||
check:rc==0
|
||||
cmd:echo beta
|
||||
check:rc!=0
|
||||
check:output=~beta
|
||||
cmd:echo gamma
|
||||
check:output=~gamma
|
||||
end
|
||||
CASE
|
||||
|
||||
my ($log, $failed) = run_harness($mixed, 'mixedchecks');
|
||||
|
||||
is_deeply(reported_checks($log),
|
||||
[ "CHECK:rc == 0\t[Pass]",
|
||||
"CHECK:rc != 0\t[Failed]",
|
||||
"CHECK:output =~ beta\t[Pass]",
|
||||
"CHECK:output =~ gamma\t[Pass]" ],
|
||||
'every check reports its own result, and a failed check does not silence the checks after it');
|
||||
|
||||
is_deeply(reported_checks($failed), reported_checks($log),
|
||||
'the failed-cases report carries the same check results as the running log');
|
||||
|
||||
ok(scalar(grep { /^------END::mixedchecks::Failed::/ } @{$log}),
|
||||
'a check that passes after a failed check does not make the case pass');
|
||||
|
||||
# A case that fails more than one check names every one of them.
|
||||
my $twofails = <<'CASE';
|
||||
start:twofailedchecks
|
||||
description:two commands, each with a check that fails
|
||||
cmd:echo one
|
||||
check:rc!=0
|
||||
cmd:echo two
|
||||
check:rc!=0
|
||||
end
|
||||
CASE
|
||||
|
||||
($log, $failed) = run_harness($twofails, 'twofailedchecks');
|
||||
|
||||
is_deeply(reported_checks($log),
|
||||
[ "CHECK:rc != 0\t[Failed]", "CHECK:rc != 0\t[Failed]" ],
|
||||
'both failed checks are reported, not just the first');
|
||||
|
||||
# A case where every check passes is unchanged.
|
||||
my $allpass = <<'CASE';
|
||||
start:allcheckspass
|
||||
description:every check passes
|
||||
cmd:echo alpha
|
||||
check:rc==0
|
||||
check:output=~alpha
|
||||
cmd:echo beta
|
||||
check:output=~beta
|
||||
end
|
||||
CASE
|
||||
|
||||
($log, $failed) = run_harness($allpass, 'allcheckspass');
|
||||
|
||||
is_deeply(reported_checks($log),
|
||||
[ "CHECK:rc == 0\t[Pass]", "CHECK:output =~ alpha\t[Pass]", "CHECK:output =~ beta\t[Pass]" ],
|
||||
'a case whose checks all pass reports every check');
|
||||
|
||||
ok(scalar(grep { /^------END::allcheckspass::Passed::/ } @{$log}),
|
||||
'a case whose checks all pass still reports Passed');
|
||||
|
||||
done_testing();
|
||||
+16
-14
@@ -1417,8 +1417,11 @@ sub run_case {
|
||||
log_this($running_log_fd, ("ElapsedTime:$diffduration sec", "RETURN rc = $rc", "OUTPUT:", @output));
|
||||
push(@caselog, ("ElapsedTime:$diffduration sec", "RETURN rc = $rc", "OUTPUT:", @output));
|
||||
|
||||
# $checkfail is the result of this check, $failflag the result of the case. One
|
||||
# variable for both makes a failed check read as failing every later check.
|
||||
my $checkfail = 0;
|
||||
foreach my $check (@{ $cases_ref->[ $case_name_index_map_ref->{$case} ]->{check}->[$j] }) {
|
||||
last if ($failflag);
|
||||
$checkfail = 0;
|
||||
|
||||
if ($check =~ /rc\s*([=!]+)\s*(\d+)/) {
|
||||
my $lvalue = $rc;
|
||||
@@ -1426,12 +1429,11 @@ sub run_case {
|
||||
my $rvalue = $2;
|
||||
if ((($op eq '!=') && ($lvalue == $rvalue))
|
||||
|| (($op eq '==') && ($lvalue != $rvalue))) {
|
||||
$failflag = 1;
|
||||
$checkfail = 1;
|
||||
}
|
||||
if ($failflag) {
|
||||
if ($checkfail) {
|
||||
log_this($running_log_fd, "CHECK:rc $op $rvalue\t[Failed]");
|
||||
push(@caselog, "CHECK:rc $op $rvalue\t[Failed]");
|
||||
last;
|
||||
} else {
|
||||
log_this($running_log_fd, "CHECK:rc $op $rvalue\t[Pass]");
|
||||
push(@caselog, "CHECK:rc $op $rvalue\t[Pass]");
|
||||
@@ -1446,17 +1448,16 @@ sub run_case {
|
||||
|| (($op eq '!~') && ($lvalue =~ /$rvalue/))
|
||||
|| (($op eq '==') && ($lvalue ne $rvalue))
|
||||
|| (($op eq '!=') && ($lvalue eq $rvalue))) {
|
||||
$failflag = 1;
|
||||
$checkfail = 1;
|
||||
} elsif (($op ne '=~') && ($op ne '!~') && ($op ne '==') && ($op ne '!=')) {
|
||||
$failflag = 1;
|
||||
$checkfail = 1;
|
||||
log_this($running_log_fd, "CHECK:output unrecognized operator: $op\t[Failed]");
|
||||
push(@caselog, "CHECK:output unrecognized operator: $op\t[Failed]");
|
||||
last;
|
||||
next;
|
||||
}
|
||||
if ($failflag) {
|
||||
if ($checkfail) {
|
||||
log_this($running_log_fd, "CHECK:output $op $rvalue\t[Failed]");
|
||||
push(@caselog, "CHECK:output $op $rvalue\t[Failed]");
|
||||
last;
|
||||
} else {
|
||||
log_this($running_log_fd, "CHECK:output $op $rvalue\t[Pass]");
|
||||
push(@caselog, "CHECK:output $op $rvalue\t[Pass]");
|
||||
@@ -1464,7 +1465,7 @@ sub run_case {
|
||||
} elsif ($check =~ /output\s*~~\s*(\S.*)/) {
|
||||
my $op = "~~";
|
||||
|
||||
#my $failflag = 1;
|
||||
# This operator only sets $checkfail to 0, so the check always reports Pass.
|
||||
my $rvalue = $1;
|
||||
|
||||
$rvalue = getfunc($rvalue);
|
||||
@@ -1481,7 +1482,7 @@ sub run_case {
|
||||
my $min = $num * 0.9;
|
||||
$line =~ /.*:.*: (\d+) /;
|
||||
if ($1 < $max && $1 > $min) {
|
||||
$failflag = 0;
|
||||
$checkfail = 0;
|
||||
last;
|
||||
}
|
||||
} else {
|
||||
@@ -1489,19 +1490,20 @@ sub run_case {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($failflag) {
|
||||
if ($checkfail) {
|
||||
log_this($running_log_fd, "CHECK:output $op $rvalue\t[Failed]");
|
||||
push(@caselog, "CHECK:output $op $rvalue\t[Failed]");
|
||||
last;
|
||||
} else {
|
||||
log_this($running_log_fd, "CHECK:output $op $rvalue\t[Pass]");
|
||||
push(@caselog, "CHECK:output $op $rvalue\t[Pass]");
|
||||
}
|
||||
} else {
|
||||
$failflag = 1;
|
||||
$checkfail = 1;
|
||||
log_this($running_log_fd, "Unrecognized testcase syntax: CHECK:$check\t[Failed]");
|
||||
push(@caselog, "Unrecognized testcase syntax: CHECK:$check\t[Failed]");
|
||||
}
|
||||
} continue {
|
||||
$failflag = 1 if ($checkfail);
|
||||
}
|
||||
foreach my $cmdcheck (@{ $cases_ref->[ $case_name_index_map_ref->{$case} ]->{cmdcheck}->[$j] }) {
|
||||
if ($cmdcheck) {
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ Homepage: https://xcat.org/
|
||||
|
||||
Package: xcat
|
||||
Architecture: amd64 ppc64el riscv64
|
||||
Depends: ${perl:Depends}, goconserver(>= 0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, libxml-parser-perl, rsync, tftpd-hpa, libnet-telnet-perl, chrony | ntp, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [!riscv64]
|
||||
Depends: ${perl:Depends}, goconserver(>= 0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, libxml-parser-perl, rsync, tftpd-hpa, libnet-telnet-perl, chrony | ntp, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [amd64], xcat-genesis-scripts-ppc64el (>= 2.13-snap000000000000) [ppc64el]
|
||||
Recommends: net-tools, kea, tftp-hpa, syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, util-linux-extra, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64, xcat-genesis-openembedded-s390x
|
||||
Suggests: yaboot-xcat
|
||||
Description: Metapackage for a common, default xCAT setup
|
||||
|
||||
@@ -8,7 +8,7 @@ Homepage: https://xcat.org/
|
||||
|
||||
Package: xcatsn
|
||||
Architecture: amd64 ppc64el riscv64
|
||||
Depends: ${perl:Depends}, goconserver (>=0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, libxml-parser-perl, tftpd-hpa, libnet-telnet-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [!riscv64]
|
||||
Depends: ${perl:Depends}, goconserver (>=0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, libxml-parser-perl, tftpd-hpa, libnet-telnet-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [amd64], xcat-genesis-scripts-ppc64el (>= 2.13-snap000000000000) [ppc64el]
|
||||
Recommends: net-tools, kea, tftp-hpa, syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64, xcat-genesis-openembedded-s390x
|
||||
Suggests: yaboot-xcat
|
||||
Description: Metapackage for a common, default xCAT service node setup
|
||||
|
||||
Reference in New Issue
Block a user