2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-21 08:33:20 +00:00

Merge pull request #7818 from VersatusHPC/release/2.19-rc1

ci(xcat-core): Fixes to get CI running for all 2.19 targets
This commit is contained in:
Daniel Hilst
2026-09-17 18:18:48 -03:00
committed by GitHub
59 changed files with 2744 additions and 911 deletions
-711
View File
@@ -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
+2 -2
View File
@@ -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.
#
@@ -388,7 +388,7 @@ carry an architecture, and there the difference is packaging metadata rather tha
compiled output. Consequently this builder needs no C<sbuild> and no per-codename
chroot. (xcat-dep is different: its packages are compiled, so it builds per codename.)
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.
+3
View File
@@ -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.
+3 -7
View File
@@ -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";
+15 -2
View File
@@ -15,6 +15,20 @@ case "$BUILDARCH" in
*) echo "ERROR: unsupported architecture: $BUILDARCH" >&2; exit 1 ;;
esac
# 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.
rewrite_control() {
local control=$1 arch=$2 superseded
case "$arch" in
ppc64el) superseded="xcat-genesis-ppc64, xcat-genesis-base-ppc64" ;;
*) superseded="xcat-genesis-$arch" ;;
esac
sed -i -e "s/xcat-genesis-base-amd64/xcat-genesis-base-$arch/g" \
-e "s/xcat-genesis-scripts-amd64/xcat-genesis-scripts-$arch/g" \
-e "s/xcat-genesis-amd64/$superseded/g" "$control"
}
VERSION=$(cat "$DIR/../Version" 2>/dev/null || echo "2.18.0")
RELEASE=$(cat "$DIR/../Release" 2>/dev/null || echo "snap$(date +%Y%m%d%H%M)")
CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME")
@@ -163,8 +177,7 @@ rm -rf "$DIR/opt"
cp -a "$GENESIS_TMPDIR/opt" "$DIR/"
# Adjust control file for target arch
sed -i "s/xcat-genesis-base-amd64/xcat-genesis-base-$BUILDARCH/g" "$DIR/debian/control"
sed -i "s/xcat-genesis-scripts-amd64/xcat-genesis-scripts-$BUILDARCH/g" "$DIR/debian/control"
rewrite_control "$DIR/debian/control" "$BUILDARCH"
PKG_VERSION="${VERSION}-${RELEASE}~${CODENAME}"
rm -f "$DIR/debian/changelog"
+31 -8
View File
@@ -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
@@ -53,7 +53,25 @@ install() {
dracut_install mount.nfs sshd vi reboot lspci parted screen mkfs mkfs.ext4 mkfs.btrfs
#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
# getdestiny makes its request file with mktemp.
dracut_install mktemp
dracut_install dhclient lldpad
# 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_opt "/lib/$TRIPLET/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 /usr/sbin/rsyslogd /etc/protocols umount /usr/bin/dpkg
@@ -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
# screen exits when the image carries no usable terminal. doxcat is the whole of Genesis, so
# it must run whether or not the multiplexer starts. Prints screen or direct.
xcat_console_mode() {
if screen -ln -d -m -S xcatprobe true >/dev/null 2>&1; then
screen -S xcatprobe -X quit >/dev/null 2>&1
echo screen
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" = "screen" ]; then
if grep -q console=ttyS /proc/cmdline; then
while :; do sleep 1; screen -S console -ln screen -x doxcat </dev/tty1 &>/dev/tty1; clear &>/dev/tty1 ; done &
fi
while :; do screen -ln < /dev/tty2 &> /dev/tty2 ; done &
fi
while :; do screen -ln < /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 screen -dr doxcat || screen -S doxcat -L -ln doxcat; done
if [ "$XCAT_CONSOLE_MODE" = "screen" ]; then
while :; do screen -dr doxcat || screen -S doxcat -L -ln doxcat; done
else
while :; do doxcat; sleep 5; done
fi
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
#
# verify-genesis-payload [--commands-from <module-setup.sh>] <payload-root> [required-path ...]
#
# dracut_install() reports a missing binary and returns, so the module install function keeps
# going and the image ships without it. Check the extracted payload before it is packaged.
#
# Paths given on the command line are relative to <payload-root>. --commands-from reads back
# what the dracut module installs: a bare command name is looked for in the four binary
# directories, an absolute path under <payload-root> itself. The caller adds what only it
# knows (the DHCP client is not the same package on every release); the rules below come from
# the payload itself.
set -u
commands_from=""
while [ $# -gt 0 ]; do
case "$1" in
--commands-from)
commands_from=${2:-}
shift 2 || true
;;
--commands-from=*)
commands_from=${1#*=}
shift
;;
*)
break
;;
esac
done
payload=${1:-}
if [ -z "$payload" ] || [ ! -d "$payload" ]; then
echo "verify-genesis-payload: not a payload directory: ${payload:-<empty>}" >&2
exit 2
fi
shift
missing=""
# have PATH: true when the payload carries PATH as a file, following the usr-merge symlinks
# the image ships (/sbin -> usr/sbin).
have() {
[ -e "$payload/$1" ]
}
require() {
local path=$1 why=$2
have "$path" || missing="$missing
$path ($why)"
}
for path in "$@"; do
require "$path" "required by the build"
done
# The dracut module names every command and every data file Genesis needs. A name the build
# root does not supply installs nothing and says nothing, so read the names back and check
# each one. Names under a condition are release-dependent, so only the top level of install()
# counts.
if [ -n "$commands_from" ]; then
if [ ! -r "$commands_from" ]; then
echo "verify-genesis-payload: cannot read $commands_from" >&2
exit 2
fi
commands=$(awk '
/^install\(\)/ { in_install = 1; next }
in_install && /^}/ { in_install = 0 }
in_install && /^ dracut_install / {
sub(/#.*/, "")
sub(/^ dracut_install /, "")
print
}' "$commands_from" | tr ' \t' '\n\n' | grep -v '^$' | grep -v '^-' | sort -u)
if [ -z "$commands" ]; then
echo "verify-genesis-payload: no command name read from $commands_from" >&2
exit 2
fi
for want in $commands; do
case "$want" in
# dracut_install installs an absolute path at that same path, so read it back
# under the payload root. Dropping these let an image with no /usr/bin/awk pass.
/*) have "${want#/}" || missing="$missing
$want (installed by $commands_from)"
;;
*) have "bin/$want" || have "sbin/$want" \
|| have "usr/bin/$want" || have "usr/sbin/$want" \
|| missing="$missing
$want (installed by $commands_from)"
;;
esac
done
fi
require usr/sbin/sshd "Genesis is reached over ssh"
require usr/bin/mktemp "getdestiny makes its request file with it"
# OpenSSH 9.8 split the per-connection work into sshd-session, which sshd execs by absolute
# path. EL9 carries OpenSSH 9.9, so an image with sshd alone refuses every connection.
if have usr/sbin/sshd && grep -qa 'sshd-session' "$payload/usr/sbin/sshd" 2>/dev/null; then
if ! have usr/libexec/openssh/sshd-session && ! have usr/lib/openssh/sshd-session; then
missing="$missing
usr/libexec/openssh/sshd-session (this sshd execs it for every connection)"
fi
fi
# tmux exits under the C locale. The hook falls back to running doxcat directly, so this is
# not fatal to booting, but a Genesis shell without tmux loses the console attach.
if have usr/bin/tmux && ! have usr/lib/locale/C.utf8/LC_CTYPE; then
missing="$missing
usr/lib/locale/C.utf8/LC_CTYPE (tmux refuses to start without a UTF-8 locale)"
fi
if [ -n "$missing" ]; then
echo "verify-genesis-payload: $payload is incomplete:$missing" >&2
exit 1
fi
echo "verify-genesis-payload: $payload is complete"
exit 0
+34 -4
View File
@@ -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}/"
+3 -1
View File
@@ -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
+4 -4
View File
@@ -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
+47 -6
View File
@@ -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
+19
View File
@@ -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}
+99 -32
View File
@@ -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};
+44 -13
View File
@@ -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;
+2 -2
View File
@@ -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 -4
View File
@@ -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;
}
+50 -5
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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
+93
View File
@@ -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)" ]]
}
+68
View File
@@ -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
}
+122
View File
@@ -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 ]
}
+89
View File
@@ -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
}
+10
View File
@@ -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();
+1 -3
View File
@@ -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,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();
@@ -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');
@@ -0,0 +1,186 @@
#!/usr/bin/env perl
# Drive verify-genesis-payload against payload trees that each leave out one thing the image
# needs.
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 $verifier = repo_path('xCAT-genesis-builder/verify-genesis-payload');
plan skip_all => 'verify-genesis-payload not found' unless -f $verifier;
plan tests => 22;
my $tmpdir = tempdir(CLEANUP => 1);
my $module_seq = 0;
# A complete payload: OpenSSH 9.9 sshd plus its session helper, tmux plus a UTF-8 locale.
my $good = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, dhclient => 1, mktemp => 1);
my ($rc, $err) = run($good, 'usr/sbin/dhclient');
is($rc, 0, 'a complete payload passes') or diag($err);
# doxcat calls dhclient with ISC flags. dhclient.conf and dhclient-script are not enough.
my $nodhcp = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, dhclient => 0, mktemp => 1);
($rc, $err) = run($nodhcp, 'usr/sbin/dhclient');
isnt($rc, 0, 'a payload without dhclient fails');
like($err, qr{usr/sbin/dhclient}, 'the missing dhclient is named');
# sshd 9.9 execs /usr/libexec/openssh/sshd-session for every connection.
my $nohelper = build_payload(sshd_execs_session => 1, session_helper => 0, tmux => 1, locale => 1, dhclient => 1, mktemp => 1);
($rc, $err) = run($nohelper, 'usr/sbin/dhclient');
isnt($rc, 0, 'a payload whose sshd execs sshd-session but does not ship it fails');
like($err, qr{sshd-session}, 'the missing sshd-session is named');
# OpenSSH 8 does not use the helper, so el8 must still pass without it.
my $openssh8 = build_payload(sshd_execs_session => 0, session_helper => 0, tmux => 1, locale => 1, dhclient => 1, mktemp => 1);
($rc, $err) = run($openssh8, 'usr/sbin/dhclient');
is($rc, 0, 'an OpenSSH 8 payload passes without sshd-session') or diag($err);
# tmux without a UTF-8 locale is what stopped doxcat from ever running.
my $nolocale = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 0, dhclient => 1, mktemp => 1);
($rc, $err) = run($nolocale, 'usr/sbin/dhclient');
isnt($rc, 0, 'a payload with tmux and no UTF-8 locale fails');
like($err, qr{C\.utf8}, 'the missing locale is named');
# getdestiny makes its request file with mktemp.
my $nomktemp = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1, dhclient => 1, mktemp => 0);
($rc, $err) = run($nomktemp, 'usr/sbin/dhclient');
isnt($rc, 0, 'a payload without mktemp fails');
like($err, qr{usr/bin/mktemp}, 'the missing mktemp is named');
# dracut_install reports a missing binary and returns, so every name the dracut module
# installs has to be checked against the payload.
my $module = write_module_setup([qw(openssl wget tar)]);
my $full = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1,
dhclient => 1, mktemp => 1, commands => [qw(openssl wget tar)]);
($rc, $err) = run_with_commands($module, $full);
is($rc, 0, 'a payload carrying every command the module names passes') or diag($err);
my $noopenssl = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1,
dhclient => 1, mktemp => 1, commands => [qw(wget tar)]);
($rc, $err) = run_with_commands($module, $noopenssl);
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']);
my $nodhclient = build_payload(sshd_execs_session => 1, session_helper => 1, tmux => 1, locale => 1,
dhclient => 0, mktemp => 1, commands => ['wget']);
($rc, $err) = run_with_commands($conditional, $nodhclient);
is($rc, 0, 'a name installed under a condition is not required') or diag($err);
# A module the verifier cannot read names for covers nothing, so say so instead of passing.
my $unparsable = "$tmpdir/module-setup-unparsable.sh";
write_text($unparsable, "#!/bin/bash\nsetup() {\n dracut_install wget\n}\n");
($rc, $err) = run_with_commands($unparsable, $full);
is($rc, 2, 'a module the verifier finds no command names in is a usage error');
like($err, qr/command name/, 'the empty command list is named');
($rc, $err) = run_with_commands("$tmpdir/no-such-module", $full);
is($rc, 2, 'a module file that cannot be read is a usage error');
($rc, $err) = run("$tmpdir/does-not-exist");
is($rc >> 0, 2, 'a missing payload directory is a usage error');
#---
# build_payload: make a payload tree with the pieces the verifier reasons about.
#---
sub build_payload {
my (%opt) = @_;
my $root = tempdir(DIR => $tmpdir, CLEANUP => 1);
make_path("$root/usr/sbin", "$root/usr/bin", "$root/usr/libexec/openssh");
write_text("$root/usr/sbin/sshd",
$opt{sshd_execs_session}
? "OpenSSH_9.9p1\n/usr/libexec/openssh/sshd-session\n"
: "OpenSSH_8.0p1\n");
write_text("$root/usr/libexec/openssh/sshd-session", "helper\n") if $opt{session_helper};
write_text("$root/usr/bin/tmux", "tmux\n") if $opt{tmux};
if ($opt{locale}) {
make_path("$root/usr/lib/locale/C.utf8");
write_text("$root/usr/lib/locale/C.utf8/LC_CTYPE", "ctype\n");
}
write_text("$root/usr/sbin/dhclient", "dhclient\n") if $opt{dhclient};
write_text("$root/usr/bin/mktemp", "mktemp\n") if $opt{mktemp};
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;
}
#---
# run: run the verifier and return its exit status and stderr.
#---
sub run {
my ($root, @required) = @_;
my $errfile = "$tmpdir/err.$$";
my $cmd = join ' ', map { "'$_'" } ($verifier, $root, @required);
system("/bin/bash $cmd >/dev/null 2>$errfile");
my $status = $? >> 8;
my $err = -f $errfile ? read_text($errfile) : '';
unlink $errfile;
return ($status, $err);
}
#---
# write_module_setup: a dracut module whose install() names commands at the top level, and
# optionally more inside a conditional.
#---
sub write_module_setup {
my ($top, $conditional) = @_;
my $path = "$tmpdir/module-setup." . ++$module_seq . ".sh";
my $text = "#!/bin/bash\n\ninstall() {\n";
$text .= " dracut_install " . join(' ', @$top) . " # a trailing comment\n";
$text .= " dracut_install /usr/bin/awk /etc/services\n";
if ($conditional) {
$text .= " if command -v " . $conditional->[0] . " >/dev/null 2>&1; then\n";
$text .= " dracut_install " . join(' ', @$conditional) . "\n";
$text .= " fi\n";
}
$text .= "}\n";
write_text($path, $text);
return $path;
}
#---
# run_with_commands: run the verifier with the command list read back from a dracut module.
#---
sub run_with_commands {
my ($module, $root) = @_;
my $errfile = "$tmpdir/err.commands.$$";
my $cmd = join ' ', map { "'$_'" } ($verifier, '--commands-from', $module, $root);
system("/bin/bash $cmd >/dev/null 2>$errfile");
my $status = $? >> 8;
my $err = -f $errfile ? read_text($errfile) : '';
unlink $errfile;
return ($status, $err);
}
+46
View File
@@ -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();
+210
View File
@@ -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;
}
+123
View File
@@ -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();
+90
View File
@@ -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();
+122
View File
@@ -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();
+62
View File
@@ -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();
+10 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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