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:
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -346,6 +346,9 @@ sub buildsources_genesis_base($) {
|
||||
"Error copying dracut_105 sources");
|
||||
cp "xCAT-genesis-builder/80-net-name-slot.rules",
|
||||
"$staging_root/80-net-name-slot.rules";
|
||||
# %install runs this against the extracted payload before it becomes an rpm.
|
||||
cp "xCAT-genesis-builder/verify-genesis-payload",
|
||||
"$staging_root/verify-genesis-payload";
|
||||
|
||||
unlink $support_tarball if -f $support_tarball;
|
||||
sh_or_die(qq(tar --sort=name --owner=0 --group=0 --mtime="\@$SOURCE_DATE_EPOCH" -cjf "$support_tarball" -C "$staging_parent" xCAT-genesis-base-build-support),
|
||||
|
||||
@@ -36,10 +36,8 @@ emitted, because a source-only run has no binary packages to advertise.
|
||||
``buildrpms.pl`` replaces all three, and its ``--source-only`` replaces the
|
||||
old ``SRCONLY=1``.
|
||||
|
||||
``build-ubunturepo`` is superseded by ``builddebs.pl`` but is **still in the
|
||||
tree for now**, as a differential oracle: it is the reference the new builder
|
||||
is checked against, and it is removed once the CD pipelines have been moved
|
||||
over. Do not add features to it.
|
||||
``build-ubunturepo`` was removed in 2.19. ``builddebs.pl`` replaces it, and the
|
||||
CD pipelines build every Ubuntu target with it.
|
||||
|
||||
Debian and Ubuntu packages
|
||||
--------------------------
|
||||
|
||||
@@ -64,7 +64,7 @@ Remove xCAT Files
|
||||
|
||||
[Ubuntu] ::
|
||||
|
||||
apt-get remove conserver-xcat elilo-xcat goconserver grub2-xcat ipmitool-xcat perl-xcat syslinux-xcat xcat xcat-buildkit xcat-client xcat-confluent xcat-genesis-base-amd64 xcat-genesis-base-ppc64 xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64 xcat-probe xcat-server xcat-test xcat-vlan xcatsn xnba-undi
|
||||
apt-get remove conserver-xcat elilo-xcat goconserver grub2-xcat ipmitool-xcat perl-xcat syslinux-xcat xcat xcat-buildkit xcat-client xcat-confluent xcat-genesis-base-amd64 xcat-genesis-base-ppc64el xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64el xcat-probe xcat-server xcat-test xcat-vlan xcatsn xnba-undi
|
||||
|
||||
To do an even more thorough cleanup, use links below to get a list of RPMs installed by xCAT. Some RPMs may not to be installed in a specific environment.
|
||||
|
||||
|
||||
@@ -32,13 +32,9 @@ my $GITHUB_API = "https://api.github.com";
|
||||
# through FindBin, so they can only be run from a source tree. Take a copy
|
||||
# before building and run the unit tests out of the copy.
|
||||
#
|
||||
# This used to be mandatory rather than tidy: build-ubunturepo set
|
||||
# local_core_repo_path="$curdir/../../xcat-core"
|
||||
# which, under the work/<repo>/<repo> layout GitHub checks out into, resolved to
|
||||
# the checkout's own parent, and it rm -rf'd that path to make room for the apt
|
||||
# repository -- destroying the tree the tests need. builddebs.pl writes under
|
||||
# dist/debs INSIDE the checkout and restores every file it edits, so the copy is
|
||||
# now only isolating the tests from build residue.
|
||||
# The copy is tidiness, not a requirement: builddebs.pl writes under dist/debs
|
||||
# inside the checkout and restores every file it edits, so the copy only keeps
|
||||
# build residue away from the tests.
|
||||
my $srcdir = getcwd();
|
||||
my $unitsrc = ($ENV{'RUNNER_TEMP'} ? $ENV{'RUNNER_TEMP'} : "/tmp") . "/xcat-core-unitsrc";
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+120
@@ -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
|
||||
@@ -12,6 +12,14 @@ Release: %{?release:%{release}}%{!?release:%(cat Release)}
|
||||
%ifarch aarch64
|
||||
%define tarch aarch64
|
||||
%endif
|
||||
%ifarch riscv64
|
||||
%define tarch riscv64
|
||||
%endif
|
||||
# An arch missing from the ladder above leaves %{tarch} unexpanded, and rpm then builds a package
|
||||
# with a macro in its NAME instead of failing. Stop the build here instead.
|
||||
%if ! %{defined tarch}
|
||||
%{error:no genesis tarch for %{_target_cpu} -- add an %%ifarch branch above}
|
||||
%endif
|
||||
BuildArch: noarch
|
||||
%define name xCAT-genesis-base-%{tarch}
|
||||
%define __spec_install_post :
|
||||
@@ -38,13 +46,22 @@ BuildRequires: chrony
|
||||
BuildRequires: cpio
|
||||
BuildRequires: e2fsprogs
|
||||
BuildRequires: hostname
|
||||
%if "%{_target_cpu}" == "x86_64"
|
||||
%if "%{tarch}" == "x86_64"
|
||||
BuildRequires: dmidecode
|
||||
BuildRequires: efibootmgr
|
||||
%endif
|
||||
BuildRequires: dosfstools
|
||||
BuildRequires: dracut
|
||||
BuildRequires: dracut-network
|
||||
# doxcat chooses its DHCP client at run time. RHEL 10 packages no ISC dhcp-client; its
|
||||
# baseos packages dhcpcd, which carries its own resolv.conf, hostname and ntp hooks and so
|
||||
# needs no dhclient-script.
|
||||
%if 0%{?rhel} && 0%{?rhel} < 10
|
||||
BuildRequires: dhcp-client
|
||||
%endif
|
||||
%if 0%{?rhel} >= 10
|
||||
BuildRequires: dhcpcd
|
||||
%endif
|
||||
BuildRequires: ethtool
|
||||
BuildRequires: gawk
|
||||
BuildRequires: ipmitool
|
||||
@@ -62,6 +79,9 @@ BuildRequires: nfs-utils
|
||||
BuildRequires: nmap-ncat
|
||||
BuildRequires: openssh-clients
|
||||
BuildRequires: openssh-server
|
||||
# getcert, getdestiny, getipmi and getadapter run the openssl command. el8 and el9 hold it in
|
||||
# the build root as a dependency of another package; el10 does not.
|
||||
BuildRequires: openssl
|
||||
BuildRequires: parted
|
||||
BuildRequires: pciutils
|
||||
BuildRequires: perl
|
||||
@@ -117,9 +137,6 @@ rm -rf "$DRACUTMODDIR"
|
||||
mkdir -p "$DRACUTMODDIR"
|
||||
cp -a "%{_builddir}/xCAT-genesis-base-build-support/dracut_105/el/." "$DRACUTMODDIR/"
|
||||
chmod 0755 "$DRACUTMODDIR/module-setup.sh" "$DRACUTMODDIR/xcatroot" "$DRACUTMODDIR/dhclient-script"
|
||||
if [ "%{_target_cpu}" != "x86_64" ]; then
|
||||
sed -i '/efibootmgr dmidecode/d' "$DRACUTMODDIR/module-setup.sh"
|
||||
fi
|
||||
|
||||
KERNELVERSION=$(ls -1 /lib/modules | sort -V | tail -n 1)
|
||||
test -n "$KERNELVERSION"
|
||||
@@ -216,6 +233,19 @@ test -n "$KERNEL_IMAGE"
|
||||
test -e "$KERNEL_IMAGE"
|
||||
cp "$KERNEL_IMAGE" "$GENESIS_ROOT/kernel"
|
||||
|
||||
# dracut_install reports a missing binary and returns, so a hole in the image reaches the
|
||||
# rpm silently. Three of them did.
|
||||
GENESIS_REQUIRED=""
|
||||
%if 0%{?rhel} && 0%{?rhel} < 10
|
||||
GENESIS_REQUIRED="usr/sbin/dhclient"
|
||||
%endif
|
||||
%if 0%{?rhel} >= 10
|
||||
GENESIS_REQUIRED="usr/sbin/dhcpcd"
|
||||
%endif
|
||||
bash "%{_builddir}/xCAT-genesis-base-build-support/verify-genesis-payload" \
|
||||
--commands-from "$DRACUTMODDIR/module-setup.sh" \
|
||||
"$GENESIS_FS" $GENESIS_REQUIRED
|
||||
|
||||
find "$GENESIS_TMPDIR" -type c -delete
|
||||
cp -a "$GENESIS_TMPDIR/%{prefix}/." "$RPM_BUILD_ROOT/%{prefix}/"
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ echo PS1="'"'[xCAT Genesis running on \H \w]\$ '"'" > /.bash_profile
|
||||
mkdir -p /etc/ssh
|
||||
mkdir -p /var/tmp/
|
||||
mkdir -p /var/empty/sshd
|
||||
sed -i '/^root:x/d' /etc/passwd
|
||||
# dracut writes this entry itself, with an empty password field unless the image is
|
||||
# built --hostonly. Match the user name only.
|
||||
sed -i '/^root:/d' /etc/passwd
|
||||
cat >>/etc/passwd <<"__ENDL"
|
||||
root:x:0:0::/:/bin/bash
|
||||
sshd:x:30:30:SSH User:/var/empty/sshd:/sbin/nologin
|
||||
|
||||
@@ -5,11 +5,11 @@ Maintainer: xCAT <xcat-user@lists.sourceforge.net>
|
||||
Build-Depends: debhelper (>= 9)
|
||||
Standards-Version: 3.9.4
|
||||
|
||||
Package: xcat-genesis-scripts-ppc64
|
||||
Package: xcat-genesis-scripts-ppc64el
|
||||
Architecture: all
|
||||
Depends: xcat-genesis-base-ppc64 (>= 2.13.10)
|
||||
Conflicts: xcat-genesis-scripts
|
||||
Replaces: xcat-genesis-scripts
|
||||
Depends: xcat-genesis-base-ppc64el (>= 2.13.10)
|
||||
Conflicts: xcat-genesis-scripts, xcat-genesis-scripts-ppc64
|
||||
Replaces: xcat-genesis-scripts, xcat-genesis-scripts-ppc64
|
||||
Description: xCAT genesis
|
||||
(Genesis Enhanced Netboot Environment for System Information and Servicing)
|
||||
is a small, embedded-like environment for xCAT's use in discovery and
|
||||
|
||||
@@ -205,6 +205,47 @@ secondary_nic_needs_dhcp() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# RHEL 10 packages no ISC dhcp-client, so the image carries whichever client its release
|
||||
# ships. Print the command line for one interface and one address family, or nothing when
|
||||
# the image carries no client at all.
|
||||
genesis_dhcp_command() {
|
||||
local family=$1
|
||||
local nic=$2
|
||||
|
||||
if command -v dhclient >/dev/null 2>&1; then
|
||||
if [ "$family" = 6 ]; then
|
||||
echo "dhclient -6 -pf /var/run/dhclient6.$nic.pid $nic -lf /var/lib/dhclient/dhclient6.leases"
|
||||
else
|
||||
echo "dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.$nic.pid $nic"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v dhcpcd >/dev/null 2>&1; then
|
||||
# dhcpcd carries its own resolv.conf, hostname and ntp hooks, so it does not need
|
||||
# dhclient-script. On a single interface it exits when its timeout expires, and it
|
||||
# de-configures the interface as it goes; -t 0 and -p turn both off.
|
||||
echo "dhcpcd -$family -b -p -t 0 $nic"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Run the client genesis_dhcp_command chose. The caller backgrounds this.
|
||||
genesis_start_dhcp() {
|
||||
local family=$1
|
||||
local nic=$2
|
||||
local command
|
||||
|
||||
command=$(genesis_dhcp_command "$family" "$nic")
|
||||
if [ -z "$command" ]; then
|
||||
logger -s -t $log_label -p local4.err "The image carries no DHCP client, so $nic gets no IPv$family address."
|
||||
return 1
|
||||
fi
|
||||
$command
|
||||
}
|
||||
|
||||
# see if they specified static ip info, otherwise use dhcp
|
||||
XCATPORT=3001
|
||||
for parm in `cat /proc/cmdline`; do
|
||||
@@ -253,8 +294,8 @@ else
|
||||
while [ $tries -lt 100 ]; do
|
||||
ALLUP_NICS=`ip link show | grep -v "^ " | grep "state UP" | awk '{print $2}' | sed -e 's/:$//'|grep -v lo | sort -n -r`
|
||||
for tmp1 in $ALLUP_NICS; do
|
||||
dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.$tmp1.pid $tmp1 &
|
||||
dhclient -6 -pf /var/run/dhclient6.$tmp1.pid $tmp1 -lf /var/lib/dhclient/dhclient6.leases &
|
||||
genesis_start_dhcp 4 "$tmp1" &
|
||||
genesis_start_dhcp 6 "$tmp1" &
|
||||
#bootnic=$tmp1
|
||||
#break
|
||||
done
|
||||
@@ -290,11 +331,11 @@ else
|
||||
/bin/bash
|
||||
fi
|
||||
else
|
||||
dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.$bootnic.pid $bootnic &
|
||||
genesis_start_dhcp 4 "$bootnic" &
|
||||
#we'll kick of IPv6 and IPv4 on all nics, but not wait for them to come up unless doing discovery, to reduce
|
||||
#chances that we'll perform a partial discovery
|
||||
#in other scenarios where downed non-bootnics cause issues, will rely on retries to fix things up
|
||||
dhclient -6 -pf /var/run/dhclient6.$bootnic.pid $bootnic -lf /var/lib/dhclient/dhclient6.leases &
|
||||
genesis_start_dhcp 6 "$bootnic" &
|
||||
NICCANDIDATES=`ip link|grep mtu|grep -v LOOPBACK|grep -v $bootnic|grep -v usb|awk -F: '{print $2}'`
|
||||
TSMNIC=$(cat /tmp/tsmhostnic 2>/dev/null)
|
||||
NICSTOBRINGUP=
|
||||
@@ -305,8 +346,8 @@ else
|
||||
done
|
||||
export NICSTOBRINGUP
|
||||
for nic in $NICSTOBRINGUP; do
|
||||
(while ! ethtool $nic | grep Link\ detected|grep yes > /dev/null && [ ! -f /tmp/netinitted ]; do sleep 5; done; dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.$nic.pid $nic ) &
|
||||
(while ! ethtool $nic | grep Link\ detected|grep yes > /dev/null && [ ! -f /tmp/netinitted ]; do sleep 5; done; dhclient -cf /etc/dhclient.conf -6 -pf /var/run/dhclient6.$nic.pid -lf /var/lib/dhclient/dhclient6.leases $nic ) &
|
||||
(while ! ethtool $nic | grep Link\ detected|grep yes > /dev/null && [ ! -f /tmp/netinitted ]; do sleep 5; done; genesis_start_dhcp 4 "$nic" ) &
|
||||
(while ! ethtool $nic | grep Link\ detected|grep yes > /dev/null && [ ! -f /tmp/netinitted ]; do sleep 5; done; genesis_start_dhcp 6 "$nic" ) &
|
||||
done
|
||||
|
||||
gripeiter=101
|
||||
|
||||
@@ -4,8 +4,27 @@ CREDPID=$!
|
||||
if [ -z "$XCATDEST" ]; then
|
||||
XCATDEST=$1
|
||||
fi
|
||||
# doxcat runs getcert in the foreground and ignores its status, so a wait with no bound stops
|
||||
# the boot and prints nothing.
|
||||
give_up() {
|
||||
logger -s -t xcat -p local4.err "getcert: $1"
|
||||
kill $CREDPID
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ! command -v openssl > /dev/null 2>&1; then
|
||||
give_up "this Genesis image carries no openssl, so no certificate is requested"
|
||||
fi
|
||||
|
||||
#retry in case certkey.pem is not right, yet
|
||||
# doxcat writes /etc/xcat/certkey.pem in the background with a 4096 bit key, so the first
|
||||
# requests fail. An emulated node needs minutes for that key.
|
||||
CSR_TIMEOUT=${GETCERT_CSR_TIMEOUT:-600}
|
||||
CSR_DEADLINE=$((SECONDS + CSR_TIMEOUT))
|
||||
while ! openssl req -new -key /etc/xcat/certkey.pem -out /tmp/tls.csr -subj "/CN=$(hostname)" >& /dev/null; do
|
||||
if [ "$SECONDS" -ge "$CSR_DEADLINE" ]; then
|
||||
give_up "no certificate request after ${CSR_TIMEOUT}s; /etc/xcat/certkey.pem is not usable"
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "<xcatrequest>
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
%ifarch aarch64
|
||||
%define tarch aarch64
|
||||
%endif
|
||||
%ifarch riscv64
|
||||
%define tarch riscv64
|
||||
%endif
|
||||
# An arch missing from the ladder above leaves %{tarch} unexpanded, and rpm then builds a package
|
||||
# with a macro in its NAME instead of failing. Stop the build here instead.
|
||||
%if ! %{defined tarch}
|
||||
%{error:no genesis tarch for %{_target_cpu} -- add an %%ifarch branch above}
|
||||
%endif
|
||||
%define rpminstallroot /opt/xcat/share/xcat/netboot/genesis/%{tarch}/fs
|
||||
BuildArch: noarch
|
||||
%define name xCAT-genesis-scripts-%{tarch}
|
||||
|
||||
@@ -498,6 +498,8 @@ sub build_diskstruct {
|
||||
my @suffixes = ('a', 'b', 'd' .. 'zzz');
|
||||
my $suffidx = 0;
|
||||
my $storagemodel = $confdata->{vm}->{$node}->[0]->{storagemodel};
|
||||
my $profile = guest_arch_profile($confdata->{nodetype}->{$node}->[0]->{arch},
|
||||
$confdata->{ $confdata->{vm}->{$node}->[0]->{host} }->{cpumodel});
|
||||
my $cachemethod = "none";
|
||||
if ($confdata->{vm}->{$node}->[0]->{storagecache}) {
|
||||
$cachemethod = $confdata->{vm}->{$node}->[0]->{storagecache};
|
||||
@@ -511,13 +513,17 @@ sub build_diskstruct {
|
||||
|
||||
#Setting default values of a virtual disk backed by a file at hd*.
|
||||
my $diskhash;
|
||||
$disk =~ s/=(.*)//;
|
||||
my $model = $1;
|
||||
# A failed substitution leaves $1 as the last successful capture, which can come
|
||||
# from a match made by a caller. Read $1 only when this substitution matches.
|
||||
my $model;
|
||||
if ($disk =~ s/=(.*)//) {
|
||||
$model = $1;
|
||||
}
|
||||
unless ($model) {
|
||||
|
||||
#if not defined, model will stay undefined like above
|
||||
$model = $storagemodel;
|
||||
unless ($model) { $model = 'ide'; } #if still not defined, ide
|
||||
unless ($model) { $model = $profile->{disk_model}; }
|
||||
}
|
||||
my $prefix = 'hd';
|
||||
if ($model eq 'virtio') {
|
||||
@@ -549,13 +555,16 @@ sub build_diskstruct {
|
||||
$tdiskhash->{driver}->{type} = $disks{$_}->{format};
|
||||
$tdiskhash->{driver}->{cache} = $cachemethod;
|
||||
$tdiskhash->{source}->{file} = $_;
|
||||
$tdiskhash->{target}->{dev} = $disks{$_}->{device};
|
||||
my $device = $disks{$_}->{device};
|
||||
$tdiskhash->{target}->{dev} = $device;
|
||||
|
||||
if ($disks{$_} =~ /^vd/) {
|
||||
# libvirt reads the bus out of the device name when the disk states
|
||||
# none: hd* is ide, sd* is scsi, vd* is virtio. State the same bus.
|
||||
if ($device =~ /^vd/) {
|
||||
$tdiskhash->{target}->{bus} = 'virtio';
|
||||
} elsif ($disks{$_} =~ /^hd/) {
|
||||
} elsif ($device =~ /^hd/) {
|
||||
$tdiskhash->{target}->{bus} = 'ide';
|
||||
} elsif ($disks{$_} =~ /^sd/) {
|
||||
} elsif ($device =~ /^sd/) {
|
||||
$tdiskhash->{target}->{bus} = 'scsi';
|
||||
}
|
||||
push @returns, $tdiskhash;
|
||||
@@ -586,7 +595,8 @@ sub build_diskstruct {
|
||||
push @returns, $diskhash;
|
||||
}
|
||||
}
|
||||
my $cdprefix = 'hd';
|
||||
# The riscv64 virt machine has no IDE controller, so the optical drive is scsi there.
|
||||
my $cdprefix = $profile->{cd_prefix};
|
||||
|
||||
# Normally for vmstoragemodel=virtio, we would set prefix of "vd", but device name vd*
|
||||
# doesn't work for CDROM, so for now use the same prefix "sd" as for vmstoragemodel=scsi.
|
||||
@@ -704,6 +714,63 @@ sub getUnits {
|
||||
}
|
||||
}
|
||||
|
||||
# default_storagemodel: the storage model of a node whose vmstoragemodel is empty.
|
||||
#
|
||||
# The model names the volume of the node, createstorage builds that name, and libvirt reads
|
||||
# the bus of the disk out of it. scsi keeps every architecture on sd*, which is the only disk
|
||||
# controller the riscv64 virt machine has.
|
||||
sub default_storagemodel {
|
||||
return 'scsi';
|
||||
}
|
||||
|
||||
# guest_arch_profile: the libvirt domain type and <os> settings for one guest.
|
||||
#
|
||||
# The architecture of the guest comes from the node, not from the hypervisor. A node whose
|
||||
# arch is not the arch of the hypervisor runs under emulation, which libvirt expresses as
|
||||
# domain type "qemu". riscv64 has no BIOS: the virt machine boots UEFI, and pae/acpi/apic
|
||||
# are x86 features that libvirt rejects there.
|
||||
#
|
||||
# POWER keeps reading the hypervisor cpumodel. ppc64le hypervisors report "ppc64le" (not
|
||||
# "ppc64"); both are pseries guests whose libvirt <os> arch is "ppc64".
|
||||
#
|
||||
# arch and machine stay undef when libvirt is to use its own default for the hypervisor.
|
||||
sub guest_arch_profile {
|
||||
my ($guest_arch, $hyp_cpumodel) = @_;
|
||||
my %profile = (
|
||||
domtype => 'kvm',
|
||||
arch => undef,
|
||||
machine => undef,
|
||||
firmware => undef,
|
||||
x86_features => 1,
|
||||
bios => 1,
|
||||
sound => 1,
|
||||
video => 'vga',
|
||||
usb_input => 1,
|
||||
disk_model => 'ide',
|
||||
cd_prefix => 'hd',
|
||||
);
|
||||
if (defined($guest_arch) and $guest_arch eq 'riscv64') {
|
||||
$profile{domtype} = 'qemu';
|
||||
$profile{arch} = 'riscv64';
|
||||
$profile{machine} = 'virt';
|
||||
$profile{firmware} = 'efi';
|
||||
$profile{x86_features} = 0;
|
||||
$profile{bios} = 0;
|
||||
$profile{sound} = 0;
|
||||
$profile{video} = 'virtio';
|
||||
$profile{usb_input} = 0;
|
||||
$profile{disk_model} = 'scsi';
|
||||
$profile{cd_prefix} = 'sd';
|
||||
} elsif (defined($hyp_cpumodel) and ($hyp_cpumodel eq "ppc64" or $hyp_cpumodel eq "ppc64le")) {
|
||||
$profile{arch} = 'ppc64';
|
||||
$profile{machine} = 'pseries';
|
||||
$profile{x86_features} = 0;
|
||||
$profile{bios} = 0;
|
||||
$profile{sound} = 0;
|
||||
}
|
||||
return \%profile;
|
||||
}
|
||||
|
||||
sub build_xmldesc {
|
||||
my $node = shift;
|
||||
my %args = @_;
|
||||
@@ -716,19 +783,16 @@ sub build_xmldesc {
|
||||
$hypcputhreads = "1";
|
||||
}
|
||||
|
||||
$xtree{type} = 'kvm';
|
||||
my $profile = guest_arch_profile($confdata->{nodetype}->{$node}->[0]->{arch}, $hypcpumodel);
|
||||
|
||||
$xtree{type} = $profile->{domtype};
|
||||
$xtree{name}->{content} = $node;
|
||||
$xtree{uuid}->{content} = getNodeUUID($node);
|
||||
$xtree{os} = build_oshash();
|
||||
# ppc64le hypervisors report cpumodel "ppc64le" (not "ppc64"); both are pseries
|
||||
# guests whose libvirt <os> arch is "ppc64". Without this the guest is emitted
|
||||
# as an x86-style domain (no machine, plus the pae/acpi/apic below) which libvirt
|
||||
# rejects on ppc64le hosts: "machine type 'pseries-*' does not support ACPI".
|
||||
if (defined($hypcpumodel) and ($hypcpumodel eq "ppc64" or $hypcpumodel eq "ppc64le")) {
|
||||
$xtree{os}->{type}->{arch} = "ppc64";
|
||||
$xtree{os}->{type}->{machine} = "pseries";
|
||||
delete $xtree{os}->{bios};
|
||||
}
|
||||
$xtree{os}->{type}->{arch} = $profile->{arch} if defined $profile->{arch};
|
||||
$xtree{os}->{type}->{machine} = $profile->{machine} if defined $profile->{machine};
|
||||
$xtree{os}->{firmware} = $profile->{firmware} if defined $profile->{firmware};
|
||||
delete $xtree{os}->{bios} unless $profile->{bios};
|
||||
if ($args{memory}) {
|
||||
$xtree{memory}->{content} = getUnits($args{memory}, "M", 1024);
|
||||
if ($confdata->{vm}->{$node}->[0]->{memory}) {
|
||||
@@ -940,9 +1004,7 @@ sub build_xmldesc {
|
||||
}
|
||||
}
|
||||
|
||||
# pae/acpi/apic are x86 features; pseries (ppc64/ppc64le) guests do not support
|
||||
# them and libvirt rejects the domain if they are present.
|
||||
unless (defined($hypcpumodel) and ($hypcpumodel eq "ppc64" or $hypcpumodel eq "ppc64le")) {
|
||||
if ($profile->{x86_features}) {
|
||||
$xtree{features}->{pae} = {};
|
||||
$xtree{features}->{acpi} = {};
|
||||
$xtree{features}->{apic} = {};
|
||||
@@ -965,10 +1027,13 @@ sub build_xmldesc {
|
||||
$vram = 65536; } #surprise, spice blows up with less vram than this after version 0.6 and up
|
||||
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => $model, vram => $vram } } ];
|
||||
} else {
|
||||
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => 'vga', vram => 8192 } } ];
|
||||
$xtree{devices}->{video} = [ { 'content' => '', 'model' => { type => $profile->{video}, vram => 8192 } } ];
|
||||
}
|
||||
# The riscv64 virt machine has no USB controller, and libvirt refuses a USB device there.
|
||||
if ($profile->{usb_input}) {
|
||||
$xtree{devices}->{input}->{type} = 'tablet';
|
||||
$xtree{devices}->{input}->{bus} = 'usb';
|
||||
}
|
||||
$xtree{devices}->{input}->{type} = 'tablet';
|
||||
$xtree{devices}->{input}->{bus} = 'usb';
|
||||
if (defined($confdata->{vm}->{$node}->[0]->{vidproto})) {
|
||||
$xtree{devices}->{graphics}->{type} = $confdata->{vm}->{$node}->[0]->{vidproto};
|
||||
} else {
|
||||
@@ -983,10 +1048,9 @@ sub build_xmldesc {
|
||||
}
|
||||
if (defined($hypcpumodel) and $hypcpumodel eq 'ppc64') {
|
||||
$xtree{devices}->{emulator}->{content} = "/usr/bin/qemu-system-ppc64";
|
||||
} elsif (defined($hypcpumodel) and $hypcpumodel eq 'ppc64le') {
|
||||
# do nothing for ppc64le, do not support sound at this time
|
||||
;
|
||||
} else {
|
||||
}
|
||||
# libvirt resolves the emulator for every other architecture from its own capabilities.
|
||||
if ($profile->{sound}) {
|
||||
$xtree{devices}->{sound}->{model} = 'ich6';
|
||||
}
|
||||
|
||||
@@ -1531,8 +1595,12 @@ sub createstorage {
|
||||
if ($mastername and $size) {
|
||||
return 1, "Can not specify both a master to clone and size(s)";
|
||||
}
|
||||
$filename =~ s/=(.*)//;
|
||||
my $model = $1;
|
||||
# A failed substitution leaves $1 as the last successful capture, which can come from a
|
||||
# match made by a caller. Read $1 only when this substitution matches.
|
||||
my $model;
|
||||
if ($filename =~ s/=(.*)//) {
|
||||
$model = $1;
|
||||
}
|
||||
unless ($model) {
|
||||
|
||||
#if not defined, model will stay undefined like above
|
||||
@@ -4251,8 +4319,7 @@ sub dohyp {
|
||||
|
||||
foreach $node (sort (keys %{ $hyphash{$hyp}->{nodes} })) {
|
||||
unless ($confdata->{vm}->{$node}->[0]->{storagemodel}) {
|
||||
# Storage model is not set, default to scsi for all architectures
|
||||
$confdata->{vm}->{$node}->[0]->{storagemodel} = "scsi";
|
||||
$confdata->{vm}->{$node}->[0]->{storagemodel} = default_storagemodel();
|
||||
}
|
||||
if ($confdata->{$hyp}->{cpu_thread}) {
|
||||
$confdata->{vm}->{$node}->[0]->{cpu_thread} = $confdata->{$hyp}->{cpu_thread};
|
||||
|
||||
@@ -356,6 +356,45 @@ sub genesis_lzma_command {
|
||||
return;
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
=head3 stage_genesis_payload
|
||||
|
||||
Descriptions:
|
||||
Copy the Genesis payload into place for mknb: for a legacy image the unpacked
|
||||
root tree and then the kernel, for an exported image the nbroot tree.
|
||||
|
||||
Extracted so the outcome can be driven directly. The copies are the only
|
||||
place mknb learns that an installed Genesis image is unusable, and a caller
|
||||
cannot tell WHICH copy failed from a single exit status.
|
||||
|
||||
Arguments:
|
||||
genesis_type, genesis_dir, tftpdir, arch, tempdir, and an optional run
|
||||
coderef used in place of system() by the tests.
|
||||
Returns:
|
||||
(rc, source) -- rc is the exit status of the copy that failed, and source
|
||||
names it, so the caller reports the file it could not read.
|
||||
|
||||
=cut
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
sub stage_genesis_payload {
|
||||
my (%a) = @_;
|
||||
my $run = $a{run} || sub { return system($_[0]); };
|
||||
my $rc;
|
||||
if (($a{genesis_type} // '') eq 'legacy') {
|
||||
# Two copies, each able to fail on its own. Return on the first, so neither the exit
|
||||
# status nor the name of the unreadable file is lost to the one that follows it.
|
||||
$rc = $run->("shopt -s dotglob; GLOBIGNORE=\".:..\" cp -a $a{genesis_dir}/fs/* $a{tempdir}");
|
||||
return ($rc, "$a{genesis_dir}/fs") if $rc;
|
||||
$rc = $run->("cp -a $a{genesis_dir}/kernel $a{tftpdir}/xcat/genesis.kernel.$a{arch}");
|
||||
return ($rc, "$a{genesis_dir}/kernel") if $rc;
|
||||
return (0, undef);
|
||||
}
|
||||
$rc = $run->("cp -a $a{genesis_dir}/nbroot/* $a{tempdir}");
|
||||
return ($rc, "$a{genesis_dir}/nbroot");
|
||||
}
|
||||
|
||||
sub process_request {
|
||||
my $request = shift;
|
||||
my $callback = shift;
|
||||
@@ -584,21 +623,13 @@ sub process_request {
|
||||
unless (-e "$tftpdir/xcat") {
|
||||
mkpath("$tftpdir/xcat");
|
||||
}
|
||||
my $rc;
|
||||
if ($genesis_type eq 'legacy') {
|
||||
$rc = system("shopt -s dotglob; GLOBIGNORE=\".:..\" cp -a $genesis_dir/fs/* $tempdir");
|
||||
$rc = system("cp -a $genesis_dir/kernel $tftpdir/xcat/genesis.kernel.$arch");
|
||||
$invisibletouch = 1;
|
||||
} else {
|
||||
$rc = system("cp -a $genesis_dir/nbroot/* $tempdir");
|
||||
}
|
||||
$invisibletouch = 1 if $genesis_type eq 'legacy';
|
||||
my ($rc, $failed_src) = stage_genesis_payload(
|
||||
genesis_type => $genesis_type, genesis_dir => $genesis_dir,
|
||||
tftpdir => $tftpdir, arch => $arch, tempdir => $tempdir);
|
||||
if ($rc) {
|
||||
system("rm -rf $tempdir");
|
||||
if ($invisibletouch) {
|
||||
$callback->({ error => ["Failed to copy $genesis_dir/fs contents"], errorcode => [1] });
|
||||
} else {
|
||||
$callback->({ error => ["Failed to copy $genesis_dir/nbroot contents"], errorcode => [1] });
|
||||
}
|
||||
$callback->({ error => ["Failed to copy $failed_src contents"], errorcode => [1] });
|
||||
return;
|
||||
}
|
||||
my $sshdir;
|
||||
|
||||
@@ -199,9 +199,9 @@ GO_XCAT_INSTALL_LIST=(perl-xCAT xCAT-client xCAT xCAT-buildkit
|
||||
# For Debian/Ubuntu, it will need a slightly different package list
|
||||
type dpkg >/dev/null 2>&1 &&
|
||||
GO_XCAT_INSTALL_LIST=(perl-xcat xcat-client xcat xcat-buildkit
|
||||
xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64 xcat-server
|
||||
xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64el xcat-server
|
||||
elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat
|
||||
xcat-genesis-base-amd64 xcat-genesis-base-ppc64 xnba-undi)
|
||||
xcat-genesis-base-amd64 xcat-genesis-base-ppc64el xnba-undi)
|
||||
# The package list of all the packages should be uninstalled
|
||||
GO_XCAT_UNINSTALL_LIST=("${GO_XCAT_INSTALL_LIST[@]}"
|
||||
goconserver xCAT-SoftLayer xCAT-confluent xCAT-csm xCAT-genesis-builder
|
||||
|
||||
@@ -2,14 +2,13 @@ start:nodeset_shell_lzma
|
||||
os:rhels8
|
||||
label:others,genesis
|
||||
description: verify could log in genesis shell lzma compression
|
||||
cmd:if [[ "__GETNODEATTR($$CN,os)__" =~ "rhel" ]]; then yum install -y https://rpmfind.net/linux/centos/8-stream/PowerTools/__GETNODEATTR($$CN,arch)__/os/Packages/xz-lzma-compat-5.2.4-3.el8.__GETNODEATTR($$CN,arch)__.rpm; elif rpm -q xz; then yum download https://rpmfind.net/linux/centos/8-stream/PowerTools/__GETNODEATTR($$CN,arch)__/os/Packages/xz-lzma-compat-5.2.4-3.el8.__GETNODEATTR($$CN,arch)__.rpm; rpm -ivh --nodeps xz-lzma-compat-5.2.4-3.el8.__GETNODEATTR($$CN,arch)__.rpm; fi
|
||||
#Generate genesis network boot with lzma compression
|
||||
cmd:mknb __GETNODEATTR($$CN,arch)__
|
||||
check:rc==0
|
||||
cmd:nodeset $$CN shell
|
||||
check:rc==0
|
||||
cmd:ls -l /tftpboot/xcat/genesis.fs.*.lzma
|
||||
check:output=~genesis
|
||||
check:rc==0
|
||||
cmd:find /tftpboot -type f -name $$CN | xargs grep "lzma"
|
||||
check:output=~genesis
|
||||
cmd:perl /opt/xcat/share/xcat/tools/autotest/testcase/genesis/genesistest.pl -n $$CN -g
|
||||
@@ -19,8 +18,7 @@ check:rc==0
|
||||
cmd:perl /opt/xcat/share/xcat/tools/autotest/testcase/genesis/genesistest.pl -n $$CN -c
|
||||
check:rc==0
|
||||
cmd:cat /tmp/genesistestlog/*
|
||||
#Remove lzma compression RPM, cleanup and generate default gz genesis network boot
|
||||
cmd:yum remove -y xz-lzma-compat
|
||||
#Cleanup and generate the default gz genesis network boot
|
||||
cmd:rm -f /tftpboot/xcat/genesis.fs.*.lzma
|
||||
cmd:mknb __GETNODEATTR($$CN,arch)__
|
||||
end
|
||||
|
||||
@@ -68,13 +68,7 @@ if (!defined($noderange)) {
|
||||
}
|
||||
my $os = &get_os;
|
||||
if ($check_genesis_file) {
|
||||
send_msg(2, "[$$]:Check if genesis packages are installed on mn...............");
|
||||
&check_genesis_file(&get_arch);
|
||||
if ($?) {
|
||||
send_msg(0, "genesis packages are not installed");
|
||||
} else {
|
||||
send_msg(2, "genesis packages are installed");
|
||||
}
|
||||
exit 1 if &report_genesis_files(&get_arch);
|
||||
}
|
||||
my $master=`lsdef -t site -i master -c 2>&1 | awk -F'=' '{print \$2}'`;
|
||||
if (!$master) { $master=hostname(); }
|
||||
@@ -89,29 +83,7 @@ if (!(-e $nodestanza)) {
|
||||
####nodesetshell test for genesis
|
||||
####################################
|
||||
if ($genesis_nodesetshell_test) {
|
||||
send_msg(2, "[$$]:Running nodeset NODE shell test...............");
|
||||
`nodeset $noderange shell`;
|
||||
if ($?) {
|
||||
send_msg(0, "[$$]:nodeset $noderange shell failed...............");
|
||||
exit 1;
|
||||
}
|
||||
`rpower $noderange boot`;
|
||||
if ($?) {
|
||||
send_msg(0, "[$$]:rpower $noderange failed...............");
|
||||
exit 1;
|
||||
}
|
||||
else {
|
||||
send_msg(2, "Installing with \"nodeset $noderange shell\" for shell test");
|
||||
sleep 120; # wait 2 min for install to finish
|
||||
wait_for_boot();
|
||||
}
|
||||
#run nodeshell test
|
||||
send_msg(2, "prepare for nodeshell script.");
|
||||
if ( &testxdsh(3)) {
|
||||
send_msg(0, "[$$]:Could not verify test results using xdsh...............");
|
||||
exit 1;
|
||||
}
|
||||
send_msg(2, "[$$]:Running nodesetshell test success...............");
|
||||
exit 1 if &run_nodeset_shell_test();
|
||||
}
|
||||
####################################
|
||||
####runcmd test for genesis
|
||||
@@ -148,6 +120,51 @@ if ($clear_env) {
|
||||
send_msg(2, "[$$]:Clear genesis test enviroment success...............");
|
||||
}
|
||||
##################################
|
||||
#run_nodeset_shell_test
|
||||
#################################
|
||||
sub run_nodeset_shell_test {
|
||||
send_msg(2, "[$$]:Running nodeset NODE shell test...............");
|
||||
`nodeset $noderange shell`;
|
||||
if ($?) {
|
||||
send_msg(0, "[$$]:nodeset $noderange shell failed...............");
|
||||
return 1;
|
||||
}
|
||||
`rpower $noderange boot`;
|
||||
if ($?) {
|
||||
send_msg(0, "[$$]:rpower $noderange failed...............");
|
||||
return 1;
|
||||
}
|
||||
send_msg(2, "Installing with \"nodeset $noderange shell\" for shell test");
|
||||
sleep 120; # wait 2 min for install to finish
|
||||
if (&wait_for_node_status("shell")) {
|
||||
send_msg(0, "[$$]:$noderange did not report the shell destiny...............");
|
||||
return 1;
|
||||
}
|
||||
#run nodeshell test
|
||||
send_msg(2, "prepare for nodeshell script.");
|
||||
if (&testxdsh(3)) {
|
||||
send_msg(0, "[$$]:Could not verify test results using xdsh...............");
|
||||
return 1;
|
||||
}
|
||||
send_msg(2, "[$$]:Running nodesetshell test success...............");
|
||||
return 0;
|
||||
}
|
||||
##################################
|
||||
#report_genesis_files
|
||||
#################################
|
||||
sub report_genesis_files {
|
||||
my ($arch) = @_;
|
||||
send_msg(2, "[$$]:Check if genesis packages are installed on mn...............");
|
||||
# The caller used to test $?, which holds the exit status of the last child process, not
|
||||
# this return value. A node with no genesis packages therefore reported success.
|
||||
if (&check_genesis_file($arch)) {
|
||||
send_msg(0, "genesis packages are not installed");
|
||||
return 1;
|
||||
}
|
||||
send_msg(2, "genesis packages are installed");
|
||||
return 0;
|
||||
}
|
||||
##################################
|
||||
#check_genesis_file
|
||||
#################################
|
||||
sub check_genesis_file {
|
||||
@@ -214,7 +231,7 @@ sub rungenesiscmd {
|
||||
else {
|
||||
send_msg(2, "Installing with \"$rinstall_cmd\" for runcmd test");
|
||||
sleep 120; # wait 2 min for install to finish
|
||||
wait_for_boot();
|
||||
$value = -1 if &wait_for_node_status("configuring");
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
@@ -257,13 +274,24 @@ sub rungenesisimg {
|
||||
} else {
|
||||
send_msg(2, "Installing with \"$rinstall_cmd\" for runimage test\n");
|
||||
sleep 120; # wait 2 min for install to finish
|
||||
wait_for_boot();
|
||||
$value = -1 if &wait_for_node_status("booting");
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
########################################
|
||||
####sleep while for xdsh $$CN could work
|
||||
#########################################
|
||||
##########################################
|
||||
####forget the node ssh host keys
|
||||
##########################################
|
||||
sub forget_host_keys {
|
||||
my ($noderange) = @_;
|
||||
# Genesis makes new host keys on every boot, and each case boots the node several times.
|
||||
# The stale known_hosts entry then makes ssh refuse the changed key, and xdsh cannot reach
|
||||
# the Genesis shell.
|
||||
system("makeknownhosts $noderange -r >/dev/null 2>&1");
|
||||
return 0;
|
||||
}
|
||||
sub testxdsh {
|
||||
my $value = shift;
|
||||
my $checkstring;
|
||||
@@ -285,6 +313,8 @@ sub testxdsh {
|
||||
return 1;
|
||||
}
|
||||
|
||||
&forget_host_keys($noderange);
|
||||
|
||||
# Check shell prompt on the node to verify it is running Genesis
|
||||
`xdsh $noderange -t 2 "echo \\\$PS1" | grep "Genesis"`;
|
||||
if ($?) {
|
||||
@@ -353,8 +383,9 @@ sub clearenv {
|
||||
`cat $nodestanza | chdef -z`;
|
||||
unlink("$nodestanza");
|
||||
}
|
||||
# "rinstall <node> boot" boots the node from its disk, which carries no operating system,
|
||||
# so the node reports no destiny and nodelist.status stays at powering-on. Only wait.
|
||||
sleep 120; # wait 2 min for reboot to finish
|
||||
wait_for_boot();
|
||||
return 0;
|
||||
}
|
||||
####################################
|
||||
@@ -365,7 +396,10 @@ sub get_os {
|
||||
my $output = `cat /etc/*release* 2>&1`;
|
||||
if ($output =~ /suse/i) {
|
||||
$os = "sles";
|
||||
} elsif ($output =~ /Red Hat/i) {
|
||||
} elsif ($output =~ /Red Hat/i
|
||||
or $output =~ /\b(?:almalinux|rocky|centos|fedora|oracle\s+linux)\b/i
|
||||
or $output =~ /^ID_LIKE=.*\brhel\b/mi) {
|
||||
# AlmaLinux and Rocky release files name neither Red Hat nor themselves as one.
|
||||
$os = "redhat";
|
||||
} elsif ($output =~ /ubuntu/i) {
|
||||
$os = "ubuntu";
|
||||
@@ -423,9 +457,10 @@ sub send_msg {
|
||||
|
||||
}
|
||||
#########################################
|
||||
### Wait for node to be in "booted" state
|
||||
### Wait for the node to report the status its destiny implies
|
||||
##########################################
|
||||
sub wait_for_boot {
|
||||
sub wait_for_node_status {
|
||||
my ($expected) = @_;
|
||||
my $iterations = 30; # Max wait 30x10 = 5 min
|
||||
my $sleep_interval = 10;
|
||||
my $boot_status;
|
||||
@@ -433,11 +468,11 @@ sub wait_for_boot {
|
||||
foreach my $i (1..$iterations) {
|
||||
$boot_status = `lsdef $noderange -i status -c | cut -d'=' -f2`;
|
||||
chop($boot_status);
|
||||
if ($boot_status eq "booted") {
|
||||
if ($boot_status eq $expected) {
|
||||
return 0;
|
||||
}
|
||||
sleep $sleep_interval;
|
||||
}
|
||||
print "After $iterations iterations node status: $boot_status \n";
|
||||
print "After $iterations iterations node status: $boot_status, expected $expected \n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -16,14 +16,49 @@ function runcmd(){
|
||||
# We should be using private networks
|
||||
TESTNODE=testnode
|
||||
TESTNODE_IP="192.168.3.1"
|
||||
# nodeset resolves the genesis kernel by the node arch, so the node takes this machine's.
|
||||
TESTNODE_ARCH="$(uname -m)"
|
||||
# The boot-loader configuration lives under the tftp root. Overridable so the check can run
|
||||
# against a scratch tree.
|
||||
TFTPDIR="${TFTPDIR:-/tftpboot}"
|
||||
|
||||
# grub2.pm names the boot loader grub2.<arch>, with every ppc64 flavour written as "ppc".
|
||||
TESTNODE_LOADER_ARCH="$TESTNODE_ARCH"
|
||||
[[ $TESTNODE_LOADER_ARCH =~ ^ppc64 ]] && TESTNODE_LOADER_ARCH="ppc"
|
||||
STAGED_BOOT_LOADER=""
|
||||
|
||||
MASTER_PRIVATE_IP="192.168.1.1"
|
||||
MASTER_PRIVATE_NETMASK="255.255.0.0"
|
||||
MASTER_PRIVATE_NETWORK="192_168_0_0-255_255_0_0"
|
||||
|
||||
|
||||
# xCAT builds no grub2 network boot loader for x86_64 or aarch64. The administrator installs
|
||||
# grub2.<arch> by hand -- docs/source/guides/install-guides/yum/grub2.rst. grub2.pm stops the
|
||||
# configuration when the file is absent, and this case reads the configuration only.
|
||||
function stage_boot_loader() {
|
||||
local loader="$TFTPDIR/boot/grub2/grub2.$TESTNODE_LOADER_ARCH";
|
||||
if [[ -e $loader ]];then
|
||||
return 0;
|
||||
fi
|
||||
mkdir -p "$TFTPDIR/boot/grub2" || return 1;
|
||||
: > "$loader" || return 1;
|
||||
STAGED_BOOT_LOADER="$loader";
|
||||
echo "Staged an empty boot loader at $loader for the check";
|
||||
return 0;
|
||||
}
|
||||
|
||||
function unstage_boot_loader() {
|
||||
if [[ -z $STAGED_BOOT_LOADER ]];then
|
||||
return 0;
|
||||
fi
|
||||
# grub2.pm links grub2-<node> to the loader. Remove the link with the file it points at.
|
||||
rm -f "$STAGED_BOOT_LOADER" "$TFTPDIR/boot/grub2/grub2-${TESTNODE}";
|
||||
STAGED_BOOT_LOADER="";
|
||||
return 0;
|
||||
}
|
||||
|
||||
function check_destiny() {
|
||||
cmd="chdef ${TESTNODE} arch=ppc64le cons=ipmi groups=all ip=${TESTNODE_IP} mac=4e:ee:ee:ee:ee:0e netboot=$NETBOOT tftpserver=$MASTER_PRIVATE_IP xcatmaster=$MASTER_PRIVATE_IP";
|
||||
cmd="chdef ${TESTNODE} arch=${TESTNODE_ARCH} cons=ipmi groups=all ip=${TESTNODE_IP} mac=4e:ee:ee:ee:ee:0e netboot=$NETBOOT tftpserver=$MASTER_PRIVATE_IP xcatmaster=$MASTER_PRIVATE_IP";
|
||||
runcmd $cmd;
|
||||
lsdef ${TESTNODE}
|
||||
|
||||
@@ -52,8 +87,15 @@ function check_destiny() {
|
||||
grep ${TESTNODE} /etc/hosts
|
||||
cmd="nodeset ${TESTNODE} shell";
|
||||
runcmd $cmd;
|
||||
# grub2.pm writes the boot configuration and only then stops on a missing boot loader,
|
||||
# so the file the check reads below exists even when nodeset failed.
|
||||
nodeset_rc=$?;
|
||||
cmd="ip addr del $MASTER_PRIVATE_IP/$MASTER_PRIVATE_NETMASK dev $NET2";
|
||||
runcmd $cmd;
|
||||
if [[ $nodeset_rc -ne 0 ]];then
|
||||
echo "'nodeset ${TESTNODE} shell' FAILED";
|
||||
return 1;
|
||||
fi
|
||||
echo "Check if 'nodeset ${TESTNODE} shell' is added to ${SHELLFOLDER}/${TESTNODE}"
|
||||
echo "==============================================="
|
||||
cat "${SHELLFOLDER}/${TESTNODE}"
|
||||
@@ -86,14 +128,17 @@ while [ "$#" -ge "0" ]; do
|
||||
"--check" )
|
||||
NETBOOT=$2;
|
||||
if [[ $NETBOOT =~ petitboot ]];then
|
||||
SHELLFOLDER="/tftpboot/petitboot";
|
||||
SHELLFOLDER="$TFTPDIR/petitboot";
|
||||
elif [[ $NETBOOT =~ xnba ]];then
|
||||
SHELLFOLDER="/tftpboot/xcat/xnba/nodes"
|
||||
SHELLFOLDER="$TFTPDIR/xcat/xnba/nodes"
|
||||
else
|
||||
SHELLFOLDER="/tftpboot/boot/grub2";
|
||||
SHELLFOLDER="$TFTPDIR/boot/grub2";
|
||||
stage_boot_loader || exit 1;
|
||||
fi
|
||||
check_destiny ;
|
||||
if [[ $? -eq 1 ]];then
|
||||
rc=$?;
|
||||
unstage_boot_loader;
|
||||
if [[ $rc -eq 1 ]];then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
|
||||
@@ -2,7 +2,7 @@ start:lsxcatd_null
|
||||
description:lsxcatd without any flag
|
||||
label:mn_only,ci_test,xcatd
|
||||
cmd:lsxcatd
|
||||
check:output=~lsxcatd
|
||||
check:output=~\[-v\|--version\]
|
||||
end
|
||||
|
||||
start:lsxcatd_h
|
||||
|
||||
@@ -78,7 +78,7 @@ description:for hwconn
|
||||
label:others,hctrl_fsp
|
||||
cmd:rmhwconn $$CN
|
||||
check:rc==0
|
||||
check:rc!~(state=LINE UP)
|
||||
check:output!~(state=LINE UP)
|
||||
cmd:mkhwconn $$CN -t
|
||||
check:rc==0
|
||||
cmd:sleep 40
|
||||
@@ -87,7 +87,7 @@ check:rc==0
|
||||
check:output=~(LINE UP)
|
||||
cmd:rmhwconn blade
|
||||
check:rc==0
|
||||
check:rc!~(state=LINE UP)
|
||||
check:output!~(state=LINE UP)
|
||||
cmd:mkhwconn blade -t
|
||||
check:rc==0
|
||||
cmd:sleep 50
|
||||
|
||||
@@ -21,9 +21,9 @@ cmd:ls /install/autoinst/testnode1*
|
||||
check:output=~No such file or directory
|
||||
cmd:ls /install/autoinst/testnode2*
|
||||
check:output=~No such file or directory
|
||||
cmd:ping testnode1
|
||||
cmd:ping -c 1 -w 2 testnode1
|
||||
check:rc!=0
|
||||
cmd:ping testnode2
|
||||
cmd:ping -c 1 -w 2 testnode2
|
||||
check:rc!=0
|
||||
end
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ cmd:echo "test" > /tmp/pscp.tmp
|
||||
check:rc==0
|
||||
cmd:pscp /tmp/pscp.tmp $$CN:/tmp/
|
||||
check:rc==0
|
||||
check:$$CN: done
|
||||
check:output=~$$CN: done
|
||||
cmd:xdsh $$CN "ls -l /tmp |grep pscp.tmp"
|
||||
check:rc==0
|
||||
check:output=~pscp.tmp
|
||||
@@ -41,7 +41,7 @@ cmd:echo "test" > /tmp/pscp/pscp.tmp
|
||||
check:rc==0
|
||||
cmd:pscp -r /tmp/pscp $$CN:/tmp/
|
||||
check:rc==0
|
||||
check:$$CN: done
|
||||
check:output=~$$CN: done
|
||||
cmd:xdsh $$CN "ls -l /tmp |grep pscp"
|
||||
check:rc==0
|
||||
check:output=~pscp
|
||||
|
||||
@@ -77,7 +77,7 @@ cmd:rmdef $$CN
|
||||
cmd:rscan __GETNODEATTR(testnode,hcp)__ -z -w
|
||||
check:rc==0
|
||||
check:output=~parent=[\w-]+
|
||||
check:lsdef -l $$CN
|
||||
cmd:lsdef -l $$CN
|
||||
check:rc==0
|
||||
check:output=~parent=[\w-]+
|
||||
cmd:rmdef all
|
||||
|
||||
@@ -83,9 +83,9 @@ start:updatenode_diskful_syncfiles_dir
|
||||
label:others,updatenode
|
||||
cmd:mkdir -p /tmp/sync/
|
||||
check:rc==0
|
||||
cmd:echo "test1" > /tmp/sync/test1.txt
|
||||
cmd:echo "syncdata1" > /tmp/sync/test1.txt
|
||||
check:rc==0
|
||||
cmd:echo "test2" > /tmp/sync/test2.txt
|
||||
cmd:echo "syncdata2" > /tmp/sync/test2.txt
|
||||
check:rc==0
|
||||
cmd:echo "/tmp/sync/* -> /tmp/" > /install/custom/install/__GETNODEATTR($$CN,os)__/compute.$$OS.synclist
|
||||
check:rc==0
|
||||
@@ -97,9 +97,9 @@ cmd:xdsh $$CN "ls -l /tmp"
|
||||
check:output=~test1.txt
|
||||
check:output=~test2.txt
|
||||
cmd:xdsh $$CN "cat /tmp/test1.txt"
|
||||
check:output=~test1
|
||||
check:output=~syncdata1
|
||||
cmd:xdsh $$CN "cat /tmp/test2.txt"
|
||||
check:output=~test2
|
||||
check:output=~syncdata2
|
||||
cmd:xdsh $$CN "rm -rf /tmp/test1.txt /tmp/test2.txt"
|
||||
check:rc==0
|
||||
cmd:chdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-install-compute synclists=
|
||||
|
||||
@@ -1040,8 +1040,8 @@ cmd:dir="/opt/inventory/site/osimage";if [ -e "${dir}" ];then mv ${dir} ${dir}".
|
||||
cmd:xcat-inventory export -t osimage -o test_myimage1,test_myimage2 --format json -d /opt/inventory/site/osimage
|
||||
check:rc==0
|
||||
check:output=~The osimage objects has been exported to directory /opt/inventory/site/osimage
|
||||
cmd: ls -R /opt/inventory/site/osimage
|
||||
check: output =~ site
|
||||
cmd:ls -R /opt/inventory/site/osimage
|
||||
check:output=~test_myimage1
|
||||
cmd:otherpkglist=`lsdef -t osimage -o test_myimage1 |grep otherpkglist|awk -F= '{print $2}'`;diff -y $otherpkglist /opt/inventory/site/osimage/test_myimage1$otherpkglist
|
||||
check:rc==0
|
||||
cmd:synclists=`lsdef -t osimage -o test_myimage1 |grep synclists|awk -F= '{print $2}'`;diff -y $synclists /opt/inventory/site/osimage/test_myimage1$synclists
|
||||
@@ -1073,8 +1073,8 @@ check:rc==0
|
||||
cmd: rmdef -t osimage -o test_myimage1,test_myimage2
|
||||
check:rc==0
|
||||
cmd:rm -rf /tmp/otherpkglist /tmp/synclists /tmp/postinstall /tmp/exlist /tmp/partitionfile /tmp/pkglist /tmp/template
|
||||
cmd: ls -R /opt/inventory/site
|
||||
check: output =~ site
|
||||
cmd:ls -R /opt/inventory/site
|
||||
check:output=~test_myimage1
|
||||
cmd:xcat-inventory import -t osimage -o test_myimage1,test_myimage2 -d /opt/inventory/site/osimage
|
||||
check:rc==0
|
||||
check:output=~The object test_myimage1 has been imported
|
||||
|
||||
@@ -42,11 +42,11 @@ start:xdcp_RP
|
||||
label:cn_os_ready,parallel_cmds
|
||||
cmd:xdsh $$CN "mkdir -p /tmp/xdcp/test1"
|
||||
check:rc==0
|
||||
cmd:xdsh $$CN "echo "test1" > /tmp/xdcp/test1/test1.txt"
|
||||
cmd:xdsh $$CN "echo "xdcpdata1" > /tmp/xdcp/test1/test1.txt"
|
||||
check:rc==0
|
||||
cmd:xdsh $$CN "mkdir -p /tmp/xdcp/test2"
|
||||
check:rc==0
|
||||
cmd:xdsh $$CN "echo "test2" > /tmp/xdcp/test2/test2.txt"
|
||||
cmd:xdsh $$CN "echo "xdcpdata2" > /tmp/xdcp/test2/test2.txt"
|
||||
check:rc==0
|
||||
cmd:xdcp $$CN -RP /tmp/xdcp /tmp
|
||||
check:rc==0
|
||||
@@ -58,9 +58,9 @@ check:output=~test1.txt
|
||||
cmd:ls -l /tmp/xdcp._$$CN/test2
|
||||
check:output=~test2.txt
|
||||
cmd:cat /tmp/xdcp._$$CN/test1/test1.txt
|
||||
check:output=~test1
|
||||
check:output=~xdcpdata1
|
||||
cmd:cat /tmp/xdcp._$$CN/test2/test2.txt
|
||||
check:output=~test2
|
||||
check:output=~xdcpdata2
|
||||
cmd:xdsh $$CN "rm -rf /tmp/xdcp"
|
||||
check:rc==0
|
||||
cmd:rm -rf /tmp/xdcp._$$CN
|
||||
@@ -71,11 +71,11 @@ start:xdcp_R
|
||||
label:cn_os_ready,parallel_cmds
|
||||
cmd:mkdir -p /tmp/xdcp/test1
|
||||
check:rc==0
|
||||
cmd:echo "test1" > /tmp/xdcp/test1/test1.txt
|
||||
cmd:echo "xdcpdata1" > /tmp/xdcp/test1/test1.txt
|
||||
check:rc==0
|
||||
cmd:mkdir -p /tmp/xdcp/test2
|
||||
check:rc==0
|
||||
cmd:echo "test2" > /tmp/xdcp/test2/test2.txt
|
||||
cmd:echo "xdcpdata2" > /tmp/xdcp/test2/test2.txt
|
||||
check:rc==0
|
||||
cmd:xdcp $$CN -R /tmp/xdcp /tmp
|
||||
check:rc==0
|
||||
@@ -89,9 +89,9 @@ check:output=~test1.txt
|
||||
cmd:xdsh $$CN "ls -l /tmp/xdcp/test2"
|
||||
check:output=~test2.txt
|
||||
cmd:xdsh $$CN "cat /tmp/xdcp/test1/test1.txt"
|
||||
check:output=~test1
|
||||
check:output=~xdcpdata1
|
||||
cmd:xdsh $$CN "cat /tmp/xdcp/test2/test2.txt"
|
||||
check:output=~test2
|
||||
check:output=~xdcpdata2
|
||||
cmd:xdsh $$CN "rm -rf /tmp/xdcp"
|
||||
check:rc==0
|
||||
cmd:rm -rf /tmp/xdcp
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# debuild-xcat-genesis-base converts the EL Genesis base rpm to a deb. The rpm name carries the
|
||||
# Genesis target architecture, and the deb must carry the Debian architecture: ppc64 becomes
|
||||
# ppc64el, x86_64 becomes amd64. An unmapped architecture leaves the deb named after the rpm and
|
||||
# makes it break a genesis-scripts package that no repository publishes. The rename also has to
|
||||
# name the deb it supersedes, or an upgraded ppc node keeps xcat-genesis-base-ppc64 as well.
|
||||
#
|
||||
# The script is driven here with alien shadowed by a shell function.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
SCRIPT="$(repo_path 'xCAT-genesis-builder/debuild-xcat-genesis-base')"
|
||||
# Fail rather than skip: a checkout without the converter has no deb rename to measure,
|
||||
# and a skip there covers nothing while reading green.
|
||||
[ -r "$SCRIPT" ]
|
||||
export SCRIPT
|
||||
}
|
||||
|
||||
# alien names the deb after the rpm: lower case, and "_" written as "-".
|
||||
shadow_alien()
|
||||
{
|
||||
alien()
|
||||
{
|
||||
local rpm="${!#}"
|
||||
local name="${rpm##*/}"
|
||||
name="${name%.rpm}"
|
||||
local dir="${name%%-snap*}"
|
||||
local package="${dir%-*}"
|
||||
package="${package,,}"
|
||||
package="${package//_/-}"
|
||||
|
||||
mkdir -p "${dir}/debian"
|
||||
cat >"${dir}/debian/control" <<CONTROL
|
||||
Source: ${package}
|
||||
Section: alien
|
||||
Priority: extra
|
||||
Maintainer: xCAT <xcat-user@lists.sourceforge.net>
|
||||
|
||||
Package: ${package}
|
||||
Architecture: all
|
||||
Description: xCAT genesis base
|
||||
CONTROL
|
||||
printf '%s (%s) unstable; urgency=low\n' "${package}" "1.0" \
|
||||
>"${dir}/debian/changelog"
|
||||
printf '#!/usr/bin/make -f\nbinary:\n\t@true\n' >"${dir}/debian/rules"
|
||||
chmod 0755 "${dir}/debian/rules"
|
||||
}
|
||||
}
|
||||
|
||||
# Convert one rpm name. Sets SOURCE_DIR to the produced source directory and CONTROL to its
|
||||
# control file.
|
||||
convert()
|
||||
{
|
||||
local rpm="$1"
|
||||
local work="${BATS_TEST_TMPDIR}/convert"
|
||||
|
||||
rm -rf "$work"
|
||||
mkdir -p "$work"
|
||||
(
|
||||
shadow_alien
|
||||
cd "$work" || exit 1
|
||||
: >"$rpm"
|
||||
source "$SCRIPT" "$rpm" >/dev/null 2>&1
|
||||
)
|
||||
|
||||
SOURCE_DIR="$(find "$work" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | head -1)"
|
||||
[ -n "$SOURCE_DIR" ] || return 1
|
||||
CONTROL="$work/$SOURCE_DIR/debian/control"
|
||||
[ -f "$CONTROL" ] || return 1
|
||||
}
|
||||
|
||||
@test "the x86_64 rpm becomes the amd64 deb, and replaces the package it supersedes" {
|
||||
convert 'xCAT-genesis-base-x86_64-2.13.10-snap202601010000.noarch.rpm'
|
||||
|
||||
[[ "$SOURCE_DIR" == *-amd64-* ]]
|
||||
grep -qx 'Package: xcat-genesis-base-amd64' "$CONTROL"
|
||||
grep -qE '^Breaks:.*\bxcat-genesis-scripts-amd64\b' "$CONTROL"
|
||||
grep -qx 'Replaces: xcat-genesis-amd64' "$CONTROL"
|
||||
grep -qE '^Breaks: xcat-genesis-amd64\b' "$CONTROL"
|
||||
}
|
||||
|
||||
@test "the ppc64 rpm becomes the ppc64el deb, and replaces the deb the rename leaves behind" {
|
||||
convert 'xCAT-genesis-base-ppc64-2.13.10-snap202601010000.noarch.rpm'
|
||||
|
||||
[[ "$SOURCE_DIR" == *-ppc64el-* ]]
|
||||
grep -qx 'Package: xcat-genesis-base-ppc64el' "$CONTROL"
|
||||
grep -qE '^Breaks:.*\bxcat-genesis-scripts-ppc64el\b' "$CONTROL"
|
||||
grep -qx 'Replaces: xcat-genesis-ppc64, xcat-genesis-base-ppc64' "$CONTROL"
|
||||
grep -qE '^Breaks: xcat-genesis-ppc64, xcat-genesis-base-ppc64\b' "$CONTROL"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# builddeb-genesis-base builds the Genesis base deb natively on Ubuntu. It writes the target
|
||||
# architecture into debian/control, which is held in the amd64 form in the tree. 2.19 renames
|
||||
# the ppc64 debs to ppc64el, so the ppc control must also name the deb it supersedes: without
|
||||
# the relation dpkg keeps xcat-genesis-base-ppc64 installed beside the new package, and that
|
||||
# old package owns the same files under /opt/xcat/share/xcat/netboot/genesis.
|
||||
#
|
||||
# The script needs dracut and root, so rewrite_control() is lifted out of it and run alone
|
||||
# against the control file the tree ships.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
SCRIPT="$(repo_path 'xCAT-genesis-builder/builddeb-genesis-base')"
|
||||
CONTROL="$(repo_path 'xCAT-genesis-builder/debian/control')"
|
||||
[ -r "$SCRIPT" ] || skip "$SCRIPT is required"
|
||||
[ -r "$CONTROL" ] || skip "$CONTROL is required"
|
||||
export SCRIPT CONTROL
|
||||
}
|
||||
|
||||
# Run the lifted rewrite_control() over a copy of the control file in the tree, and print it.
|
||||
rewrite()
|
||||
{
|
||||
local arch="$1" function copy="${BATS_TEST_TMPDIR}/control.$1"
|
||||
|
||||
function="$(extract_shell_function "$SCRIPT" rewrite_control)" ||
|
||||
{ echo 'rewrite_control() no longer matches in builddeb-genesis-base' >&2; return 99; }
|
||||
cp "$CONTROL" "$copy"
|
||||
(
|
||||
set -eu
|
||||
eval "$function"
|
||||
rewrite_control "$copy" "$arch"
|
||||
) || return 1
|
||||
cat "$copy"
|
||||
}
|
||||
|
||||
@test "the amd64 control names the package and the genesis deb it took over from" {
|
||||
run rewrite amd64
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
[[ "$output" =~ (^|$'\n')"Package: xcat-genesis-base-amd64"($'\n'|$) ]]
|
||||
[[ "$output" =~ (^|$'\n')"Replaces: xcat-genesis-amd64"($'\n'|$) ]]
|
||||
[[ "$output" =~ (^|$'\n')"Breaks: xcat-genesis-amd64, " ]]
|
||||
[[ "$output" =~ "xcat-genesis-scripts-amd64 (<< 2.13.10)" ]]
|
||||
}
|
||||
|
||||
@test "the ppc64el control also takes over from the ppc64 deb the rename leaves behind" {
|
||||
run rewrite ppc64el
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
[[ "$output" =~ (^|$'\n')"Package: xcat-genesis-base-ppc64el"($'\n'|$) ]]
|
||||
[[ "$output" =~ (^|$'\n')"Replaces: xcat-genesis-ppc64, xcat-genesis-base-ppc64"($'\n'|$) ]]
|
||||
[[ "$output" =~ (^|$'\n')"Breaks: xcat-genesis-ppc64, xcat-genesis-base-ppc64, " ]]
|
||||
[[ "$output" =~ "xcat-genesis-scripts-ppc64el (<< 2.13.10)" ]]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Drive xcat_console_mode() out of the Genesis dracut cmdline hook.
|
||||
#
|
||||
# The hook cannot be sourced: it mounts filesystems, starts udev and ends in an endless
|
||||
# loop. Extract the one function and run it with the terminal multiplexer shadowed.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
EL_HOOK="$(repo_path 'xCAT-genesis-builder/dracut_105/el/xcat-cmdline.sh')"
|
||||
UBUNTU_HOOK="$(repo_path 'xCAT-genesis-builder/dracut_105/ubuntu/xcat-cmdline.sh')"
|
||||
[ -r "$EL_HOOK" ] || skip "$EL_HOOK is required"
|
||||
[ -r "$UBUNTU_HOOK" ] || skip "$UBUNTU_HOOK is required"
|
||||
export EL_HOOK UBUNTU_HOOK
|
||||
}
|
||||
|
||||
# Run the extracted function with the multiplexer shadowed by a stub that either starts a
|
||||
# session or refuses, the way tmux refuses without a UTF-8 locale.
|
||||
run_mode()
|
||||
{
|
||||
local hook="$1" mux="$2" mux_works="$3" body
|
||||
body="$(extract_shell_function "$hook" xcat_console_mode)" ||
|
||||
{ echo "xcat_console_mode() not found in $hook" >&2; return 99; }
|
||||
(
|
||||
eval "$body"
|
||||
eval "$mux() {
|
||||
[ \"\$mux_works\" = 1 ] && return 0
|
||||
echo '$mux: need UTF-8 locale (LC_CTYPE) but have ANSI_X3.4-1968' >&2
|
||||
return 1
|
||||
}"
|
||||
xcat_console_mode
|
||||
) 2>/dev/null
|
||||
}
|
||||
|
||||
# The hook reads the mode once and guards the doxcat loop with it.
|
||||
assert_hook_guards_doxcat()
|
||||
{
|
||||
local hook="$1" mux="$2"
|
||||
grep -qx 'XCAT_CONSOLE_MODE="$(xcat_console_mode)"' "$hook"
|
||||
grep -qFx "if [ \"\$XCAT_CONSOLE_MODE\" = \"$mux\" ]; then" "$hook"
|
||||
grep -A1 '^else$' "$hook" | grep -qx ' while :; do doxcat; sleep 5; done'
|
||||
}
|
||||
|
||||
@test "the el hook leaves no unguarded tmux loop and exports a UTF-8 locale" {
|
||||
# tmux exits under the C locale, so an unguarded tmux loop never reaches doxcat.
|
||||
refute_grep -q '^while :; do tmux attach-session' "$EL_HOOK"
|
||||
grep -qx 'export LC_ALL=C.UTF-8' "$EL_HOOK"
|
||||
}
|
||||
|
||||
@test "el: xcat_console_mode reports the mode tmux can actually provide" {
|
||||
[ "$(run_mode "$EL_HOOK" tmux 0)" = direct ]
|
||||
[ "$(run_mode "$EL_HOOK" tmux 1)" = tmux ]
|
||||
}
|
||||
|
||||
@test "el: the hook resolves the console mode once and runs doxcat directly without tmux" {
|
||||
assert_hook_guards_doxcat "$EL_HOOK" tmux
|
||||
}
|
||||
|
||||
@test "ubuntu: xcat_console_mode reports the mode screen can actually provide" {
|
||||
[ "$(run_mode "$UBUNTU_HOOK" screen 0)" = direct ]
|
||||
[ "$(run_mode "$UBUNTU_HOOK" screen 1)" = screen ]
|
||||
}
|
||||
|
||||
@test "ubuntu: the hook resolves the console mode once and runs doxcat directly without screen" {
|
||||
assert_hook_guards_doxcat "$UBUNTU_HOOK" screen
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Drive the DHCP client selection out of doxcat.
|
||||
#
|
||||
# doxcat cannot be sourced: it restarts rsyslogd, reads /proc/cmdline and ends in a loop that
|
||||
# waits for an address. Extract the two routines and run them with the clients shadowed by
|
||||
# stubs that record their own argv.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
ISC4='dhclient -cf /etc/dhclient.conf -pf /var/run/dhclient.eth0.pid eth0'
|
||||
ISC6='dhclient -6 -pf /var/run/dhclient6.eth0.pid eth0 -lf /var/lib/dhclient/dhclient6.leases'
|
||||
|
||||
setup()
|
||||
{
|
||||
DOXCAT="$(repo_path 'xCAT-genesis-scripts/usr/bin/doxcat')"
|
||||
SPEC="$(repo_path 'xCAT-genesis-builder/xCAT-genesis-base.spec')"
|
||||
MODULE="$(repo_path 'xCAT-genesis-builder/dracut_105/el/module-setup.sh')"
|
||||
[ -r "$DOXCAT" ] || skip "$DOXCAT is required"
|
||||
[ -r "$SPEC" ] || skip "$SPEC is required"
|
||||
[ -r "$MODULE" ] || skip "$MODULE is required"
|
||||
export DOXCAT SPEC MODULE
|
||||
}
|
||||
|
||||
# Run the extracted routines with only the named clients on PATH. Sets OUT to the standard
|
||||
# output, RAN to the recorded argv of whatever ran, and STATUS to the exit status.
|
||||
probe()
|
||||
{
|
||||
local call="$1"
|
||||
shift
|
||||
local dir="${BATS_TEST_TMPDIR}/probe"
|
||||
local bin="$dir/bin" record="$dir/record" client selector runner
|
||||
|
||||
selector="$(extract_shell_function "$DOXCAT" genesis_dhcp_command)" ||
|
||||
{ echo 'doxcat carries no genesis_dhcp_command() to choose the client' >&2; return 99; }
|
||||
runner="$(extract_shell_function "$DOXCAT" genesis_start_dhcp)" ||
|
||||
{ echo 'doxcat carries no genesis_start_dhcp() to run the chosen client' >&2; return 99; }
|
||||
|
||||
rm -rf "$dir"
|
||||
mkdir -p "$bin"
|
||||
|
||||
# PATH holds the stubs alone, so each one names itself rather than calling basename.
|
||||
for client in "$@"; do
|
||||
printf '#!/bin/sh\necho "%s $*" >> "%s"\nexit 0\n' "$client" "$record" >"$bin/$client"
|
||||
chmod 0755 "$bin/$client"
|
||||
done
|
||||
|
||||
# logger writes to the console in the image and is not what these assertions measure.
|
||||
printf '#!/bin/sh\nexit 0\n' >"$bin/logger"
|
||||
chmod 0755 "$bin/logger"
|
||||
|
||||
printf 'log_label=test\n%s\n%s\n%s\n' "$selector" "$runner" "$call" >"$dir/probe.sh"
|
||||
OUT="$(PATH="$bin" /bin/bash "$dir/probe.sh" 2>/dev/null)" && STATUS=0 || STATUS=$?
|
||||
RAN="$(read_file_or_empty "$record")"
|
||||
return 0
|
||||
}
|
||||
|
||||
selected()
|
||||
{
|
||||
local family="$1"
|
||||
shift
|
||||
probe "genesis_dhcp_command $family eth0" "$@"
|
||||
printf '%s\n' "$OUT"
|
||||
}
|
||||
|
||||
started()
|
||||
{
|
||||
local family="$1"
|
||||
shift
|
||||
probe "genesis_start_dhcp $family eth0" "$@"
|
||||
printf '%s\n' "$RAN"
|
||||
}
|
||||
|
||||
@test "doxcat names no DHCP client directly" {
|
||||
# A release that packages no ISC client has no dhclient.
|
||||
refute_grep -qE '^[[:space:]]*dhclient[[:space:]]' "$DOXCAT"
|
||||
refute_grep -qE ';[[:space:]]*dhclient[[:space:]]' "$DOXCAT"
|
||||
}
|
||||
|
||||
@test "the build root and the payload check name the client the release ships" {
|
||||
# EL8 and EL9 package the ISC client; AlmaLinux 10 baseos packages dhcpcd. The payload
|
||||
# check has to name the client too, or the build passes with no client in the image again.
|
||||
grep -A1 '^%if 0%{?rhel} >= 10$' "$SPEC" | grep -qx 'BuildRequires: dhcpcd'
|
||||
grep -A1 '^%if 0%{?rhel} >= 10$' "$SPEC" | grep -qx 'GENESIS_REQUIRED="usr/sbin/dhcpcd"'
|
||||
}
|
||||
|
||||
@test "the dracut module installs the client the build root carries" {
|
||||
# dracut_install reports a missing binary and returns, so naming dhclient alone shipped an
|
||||
# image with no client at all.
|
||||
refute_grep -qE '^[[:space:]]*dracut_install dhclient lldpad$' "$MODULE"
|
||||
grep -qE '^[[:space:]]*dracut_install dhcpcd$' "$MODULE"
|
||||
grep -qE '^[[:space:]]*dracut_install /usr/libexec/dhcpcd-run-hooks$' "$MODULE"
|
||||
}
|
||||
|
||||
@test "the ISC client keeps its command lines and is preferred when both are present" {
|
||||
[ "$(selected 4 dhclient)" = "$ISC4" ]
|
||||
[ "$(selected 6 dhclient)" = "$ISC6" ]
|
||||
[ "$(selected 4 dhclient dhcpcd)" = "$ISC4" ]
|
||||
}
|
||||
|
||||
@test "dhcpcd stands in for dhclient, waiting for a lease and keeping the address" {
|
||||
# dhcpcd on a single interface exits when its timeout expires, and the default is 30
|
||||
# seconds; doxcat waits for the lease for as long as it takes. dhcpcd also de-configures
|
||||
# the interface when it exits unless it is persistent.
|
||||
[ "$(selected 4 dhcpcd)" = 'dhcpcd -4 -b -p -t 0 eth0' ]
|
||||
[ "$(selected 6 dhcpcd)" = 'dhcpcd -6 -b -p -t 0 eth0' ]
|
||||
[[ "$(selected 4 dhcpcd)" =~ (^|[[:space:]])-t\ 0([[:space:]]|$) ]]
|
||||
[[ "$(selected 4 dhcpcd)" =~ (^|[[:space:]])-p([[:space:]]|$) ]]
|
||||
}
|
||||
|
||||
@test "an image with no client chooses nothing, runs nothing and reports a failure" {
|
||||
[ "$(selected 4)" = '' ]
|
||||
[ "$(started 4)" = '' ]
|
||||
|
||||
probe 'genesis_start_dhcp 4 eth0'
|
||||
[ "$STATUS" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "genesis_start_dhcp runs the client it chose" {
|
||||
[ "$(started 4 dhcpcd)" = 'dhcpcd -4 -b -p -t 0 eth0' ]
|
||||
[ "$(started 4 dhclient)" = "$ISC4" ]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Drive getcert with openssl absent, and with a certificate key that is not ready yet.
|
||||
# doxcat runs getcert in the foreground and ignores its status, so a wait with no bound stops
|
||||
# the boot and prints nothing.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
GETCERT="$(repo_path 'xCAT-genesis-scripts/usr/bin/getcert')"
|
||||
[ -r "$GETCERT" ] || skip "$GETCERT is required"
|
||||
COUNTER="${BATS_TEST_TMPDIR}/req-count"
|
||||
export GETCERT COUNTER
|
||||
}
|
||||
|
||||
write_stub()
|
||||
{
|
||||
local dir="$1" name="$2" body="$3"
|
||||
printf '#!/bin/sh\n%s\n' "$body" >"$dir/$name"
|
||||
chmod 0755 "$dir/$name"
|
||||
}
|
||||
|
||||
# A PATH directory holding the commands getcert runs. openssl is absent unless it is asked for.
|
||||
stub_dir()
|
||||
{
|
||||
local with_openssl="${1:-0}" count=""
|
||||
local dir="${BATS_TEST_TMPDIR}/bin"
|
||||
|
||||
rm -rf "$dir"
|
||||
mkdir -p "$dir"
|
||||
write_stub "$dir" allowcred.awk 'exec sleep 3'
|
||||
write_stub "$dir" hostname 'echo node1'
|
||||
write_stub "$dir" logger 'echo "$@" >&2'
|
||||
write_stub "$dir" sleep 'exec /bin/sleep "$@"'
|
||||
if [ "$with_openssl" = 1 ]; then
|
||||
[ -n "${COUNT_REQUESTS:-}" ] && count="echo req >> '$COUNTER'"
|
||||
write_stub "$dir" openssl "[ \"\$1\" = req ] && { $count ; exit 1; }
|
||||
exit 0"
|
||||
fi
|
||||
printf '%s\n' "$dir"
|
||||
}
|
||||
|
||||
# Run getcert with only the stub directory on PATH. The timeout is the harness guard: a status
|
||||
# of 124 means getcert never stopped.
|
||||
run_getcert()
|
||||
{
|
||||
local bin="$1" limit="$2" csr_timeout="$3"
|
||||
timeout -k 2 "$limit" env PATH="$bin" GETCERT_CSR_TIMEOUT="$csr_timeout" \
|
||||
/bin/bash "$GETCERT" 192.0.2.1:3001 2>&1 </dev/null
|
||||
}
|
||||
|
||||
@test "getcert stops and names openssl when the image ships none" {
|
||||
# The el10 legacy image ships no openssl.
|
||||
run run_getcert "$(stub_dir 0)" 10 60
|
||||
[ "$status" -ne 124 ]
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *openssl* ]]
|
||||
}
|
||||
|
||||
@test "getcert retries the certificate request, then gives up and names the key" {
|
||||
# doxcat writes /etc/xcat/certkey.pem in the background, so the first requests can fail.
|
||||
export COUNT_REQUESTS=1
|
||||
run run_getcert "$(stub_dir 1)" 30 5
|
||||
[ "$status" -ne 124 ]
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *certkey.pem* ]]
|
||||
|
||||
tries=0
|
||||
[ -f "$COUNTER" ] && tries="$(grep -c req "$COUNTER")"
|
||||
[ "$tries" -gt 1 ]
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Run the nodeset_shell_incorrectmasterip check against a scratch tftp root, with the xCAT
|
||||
# commands and the net tools shadowed.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
SCRIPT="$(repo_path 'xCAT-test/autotest/testcase/genesis/test.sh')"
|
||||
[ -r "$SCRIPT" ] || skip "$SCRIPT is required"
|
||||
HOST_ARCH="$(uname -m)"
|
||||
# grub2.pm names the boot loader grub2.<arch>, with every ppc64 flavour written as "ppc".
|
||||
case "$HOST_ARCH" in
|
||||
ppc64*) LOADER_NAME=ppc ;;
|
||||
*) LOADER_NAME="$HOST_ARCH" ;;
|
||||
esac
|
||||
export SCRIPT HOST_ARCH LOADER_NAME
|
||||
}
|
||||
|
||||
# Run `test.sh --check <loader>` against a scratch tftp root. test.sh resets PATH, so the xCAT
|
||||
# commands are shadowed with shell functions, which bash resolves first. The fake nodeset writes
|
||||
# the boot file the check greps, so the assertion is on the check, not on xCAT.
|
||||
#
|
||||
# Sets STATUS, OUTPUT, CHDEF, LOADER_AT_NODESET and LOADER_LEFT.
|
||||
run_check()
|
||||
{
|
||||
local loader="$1" write_boot_file="$2" nodeset_status="${3:-0}"
|
||||
local root="${BATS_TEST_TMPDIR}/$loader-$write_boot_file-$nodeset_status"
|
||||
local tftp="$root/tftpboot"
|
||||
local boot_loader="$tftp/boot/grub2/grub2.$LOADER_NAME"
|
||||
local folder write
|
||||
|
||||
rm -rf "$root"
|
||||
mkdir -p "$tftp/xcat/xnba/nodes" "$tftp/boot/grub2" "$tftp/petitboot"
|
||||
|
||||
case "$loader" in
|
||||
xnba) folder="$tftp/xcat/xnba/nodes" ;;
|
||||
petitboot) folder="$tftp/petitboot" ;;
|
||||
*) folder="$tftp/boot/grub2" ;;
|
||||
esac
|
||||
if [ "$write_boot_file" = 1 ]; then
|
||||
write="printf 'xcatd=192.168.1.1:3001 destiny=shell\n' > '$folder/testnode'"
|
||||
else
|
||||
write=":"
|
||||
fi
|
||||
|
||||
cat >"$root/driver.sh" <<DRIVER
|
||||
chdef() { echo "\$@" >> '$root/chdef.log'; }
|
||||
lsdef() {
|
||||
if [ "\$1" = "-t" ] && [ "\$2" = "site" ]; then echo "clustersite: master=192.168.9.9"; return 0; fi
|
||||
echo "Object name: testnode"
|
||||
}
|
||||
ifconfig() { printf 'eth0: flags\n inet 192.168.9.9\n\n'; }
|
||||
netstat() { printf 'Kernel\nIface\neth0\neth1\nlo\n'; }
|
||||
ip() { return 0; }
|
||||
makenetworks() { return 0; }
|
||||
tabdump() { return 0; }
|
||||
makehosts() { return 0; }
|
||||
rmdef() { return 0; }
|
||||
nodeset() {
|
||||
if [ -e '$boot_loader' ]; then echo yes > '$root/loader.at.nodeset'; else echo no > '$root/loader.at.nodeset'; fi
|
||||
$write
|
||||
return $nodeset_status
|
||||
}
|
||||
export TFTPDIR='$tftp'
|
||||
. '$SCRIPT' --check $loader
|
||||
DRIVER
|
||||
|
||||
OUTPUT="$(/bin/bash "$root/driver.sh" 2>&1)" && STATUS=0 || STATUS=$?
|
||||
CHDEF="$(read_file_or_empty "$root/chdef.log")"
|
||||
LOADER_AT_NODESET="$(read_file_or_empty "$root/loader.at.nodeset")"
|
||||
LOADER_LEFT=0
|
||||
[ -e "$boot_loader" ] && LOADER_LEFT=1
|
||||
return 0
|
||||
}
|
||||
|
||||
@test "the xnba check passes and defines the node with the management node architecture" {
|
||||
# The case defined its node as ppc64le whatever the management node was, so nodeset could
|
||||
# not find a genesis kernel for it on x86_64 and the case could never pass there.
|
||||
run_check xnba 1
|
||||
[ "$STATUS" -eq 0 ] || { echo "$OUTPUT"; false; }
|
||||
[[ "$CHDEF" =~ (^|[[:space:]])arch=$HOST_ARCH([[:space:]]|$) ]]
|
||||
[ "$HOST_ARCH" = ppc64le ] ||
|
||||
[ "$(grep -cE '(^|[[:space:]])arch=ppc64le([[:space:]]|$)' <<<"$CHDEF")" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "the check fails when nodeset writes no boot file" {
|
||||
run_check xnba 0
|
||||
[ "$STATUS" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "the grub2 check reads the grub2 directory, and stages then removes the boot loader" {
|
||||
# grub2 and petitboot read their configuration from other directories under the tftp root.
|
||||
# xCAT builds no x86_64 or aarch64 grub2 network boot loader, so grub2.pm stops before it
|
||||
# configures anything. The check stages one for the node arch and removes it after.
|
||||
run_check grub2 1
|
||||
[ "$STATUS" -eq 0 ] || { echo "$OUTPUT"; false; }
|
||||
[ "$LOADER_AT_NODESET" = yes ]
|
||||
[ "$LOADER_LEFT" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "a nodeset that fails makes the check fail, whatever the boot file holds" {
|
||||
# grub2.pm writes the boot configuration and only then stops on a missing boot loader. The
|
||||
# check read the file that failed nodeset had already written, so it passed on the debris.
|
||||
run_check grub2 1 1
|
||||
[ "$STATUS" -ne 0 ]
|
||||
|
||||
run_check xnba 1 1
|
||||
[ "$STATUS" -ne 0 ]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# Drive the /etc/passwd rewrite out of the Genesis dracut cmdline hooks.
|
||||
#
|
||||
# mknb writes the management node key to /.ssh/authorized_keys for the legacy Genesis
|
||||
# image, so sshd finds it only while the home directory of root is /. The hook makes it /
|
||||
# by deleting the root entry the image ships and appending its own. Run that rewrite
|
||||
# against every root entry shape dracut writes and read back the result.
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
# dracut 99base writes the root entry itself. Up to dracut 057 the password field is always
|
||||
# x; from dracut 060 the x arrives only with --hostonly, and the Genesis image is built -N.
|
||||
DRACUT_049_057='root:x:0:0::/root:/bin/sh'
|
||||
DRACUT_107='root::0:0::/root:/bin/sh'
|
||||
|
||||
# A user name that starts with root but is not root. The delete must keep this line.
|
||||
DECOY='rootfsadm:x:501:501::/home/rootfsadm:/sbin/nologin'
|
||||
|
||||
# Lift the /etc/passwd rewrite out of a hook that cannot be sourced: the hook mounts
|
||||
# filesystems, starts udev and ends in an endless loop.
|
||||
extract_passwd_block()
|
||||
{
|
||||
local hook="$1"
|
||||
awk '
|
||||
/^sed .*\/etc\/passwd$/ { copy = 1 }
|
||||
copy { print }
|
||||
copy && /^__ENDL$/ { found = 1; exit }
|
||||
END { if (!found) exit 1 }
|
||||
' "$hook"
|
||||
}
|
||||
|
||||
# Run the extracted block against a scratch passwd file and print the result. The block names
|
||||
# /etc/passwd literally, so the path is redirected into the scratch tree first, and the run is
|
||||
# refused if any reference to the real file survives: CI runs this as root.
|
||||
run_rewrite()
|
||||
{
|
||||
local hook="$1" shipped="$2"
|
||||
local dir="${BATS_TEST_TMPDIR}/rewrite"
|
||||
local passwd="$dir/passwd" block script
|
||||
|
||||
rm -rf "$dir"
|
||||
mkdir -p "$dir"
|
||||
printf '%s\n%s\n' "$shipped" "$DECOY" >"$passwd"
|
||||
|
||||
block="$(extract_passwd_block "$(repo_path "$hook")")" ||
|
||||
{ echo "$hook: the /etc/passwd rewrite was not found" >&2; return 99; }
|
||||
|
||||
[ "$(grep -o -F '/etc/passwd' <<<"$block" | wc -l)" -eq 2 ] ||
|
||||
{ echo "$hook: expected 2 references to /etc/passwd" >&2; return 98; }
|
||||
script="${block//\/etc\/passwd/$passwd}"
|
||||
case "$script" in
|
||||
*/etc/passwd*) echo "$hook: a reference to the real /etc/passwd survived" >&2; return 97 ;;
|
||||
esac
|
||||
|
||||
bash -c "set -e
|
||||
$script" || return 1
|
||||
cat "$passwd"
|
||||
}
|
||||
|
||||
assert_root_home_is_slash()
|
||||
{
|
||||
local hook="$1" shipped="$2" passwd
|
||||
passwd="$(run_rewrite "$hook" "$shipped")"
|
||||
|
||||
[ "$(grep -c '^root:' <<<"$passwd")" -eq 1 ]
|
||||
[ "$(grep '^root:' <<<"$passwd")" = 'root:x:0:0::/:/bin/bash' ]
|
||||
grep -qxF "$DECOY" <<<"$passwd"
|
||||
}
|
||||
|
||||
assert_hook()
|
||||
{
|
||||
local hook="$1"
|
||||
[ -r "$(repo_path "$hook")" ] || skip "$hook is required"
|
||||
assert_root_home_is_slash "$hook" "$DRACUT_049_057"
|
||||
assert_root_home_is_slash "$hook" "$DRACUT_107"
|
||||
}
|
||||
|
||||
@test "the legacy hook gives root the home directory /" {
|
||||
assert_hook 'xCAT-genesis-builder/xcat-cmdline.sh'
|
||||
}
|
||||
|
||||
@test "the el dracut 105 hook gives root the home directory /" {
|
||||
assert_hook 'xCAT-genesis-builder/dracut_105/el/xcat-cmdline.sh'
|
||||
}
|
||||
|
||||
@test "the ubuntu dracut 105 hook gives root the home directory /" {
|
||||
assert_hook 'xCAT-genesis-builder/dracut_105/ubuntu/xcat-cmdline.sh'
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# go-xcat installs and uninstalls a fixed list of package names, and it keeps one list per
|
||||
# packaging format. The Genesis packages are named after the architecture, and the two formats
|
||||
# spell it differently: the rpm is xCAT-genesis-scripts-ppc64, the deb is
|
||||
# xcat-genesis-scripts-ppc64el.
|
||||
#
|
||||
# The lists are built by go-xcat itself here, not read as text: the deb list exists only when
|
||||
# "type dpkg" succeeds, so a shell function decides which branch each run takes.
|
||||
|
||||
load 'helpers/go_xcat'
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup()
|
||||
{
|
||||
go_xcat_require_source
|
||||
SCRIPTS_DEBIAN="$(repo_path 'xCAT-genesis-scripts/debian')"
|
||||
SPEC="$(repo_path 'xCAT-genesis-builder/xCAT-genesis-base.spec')"
|
||||
[ -d "$SCRIPTS_DEBIAN" ] || skip "$SCRIPTS_DEBIAN is required"
|
||||
[ -r "$SPEC" ] || skip "$SPEC is required"
|
||||
export SCRIPTS_DEBIAN SPEC
|
||||
}
|
||||
|
||||
# Run the array definitions of go-xcat and print the two lists it built, one per line.
|
||||
package_lists()
|
||||
{
|
||||
local want_dpkg="$1" list_body
|
||||
list_body="$(awk '
|
||||
/^GO_XCAT_INSTALL_LIST=\(/ { copy = 1 }
|
||||
/^PATH=/ { exit }
|
||||
copy { print }
|
||||
' "$GO_XCAT_SOURCE")"
|
||||
[ -n "$list_body" ] || { echo 'go-xcat package arrays not found' >&2; return 3; }
|
||||
(
|
||||
if [ "$want_dpkg" = 1 ]; then
|
||||
dpkg() { :; }
|
||||
fi
|
||||
# A real dpkg on the build host would select the deb branch on every run.
|
||||
PATH=""
|
||||
eval "$list_body"
|
||||
printf 'install %s\n' "${GO_XCAT_INSTALL_LIST[*]}"
|
||||
printf 'uninstall %s\n' "${GO_XCAT_UNINSTALL_LIST[*]}"
|
||||
)
|
||||
}
|
||||
|
||||
package_list()
|
||||
{
|
||||
package_lists "$1" | sed -n "s/^$2 //p"
|
||||
}
|
||||
|
||||
# The package names of a list, sorted, that start with a prefix.
|
||||
named()
|
||||
{
|
||||
local prefix="$1" word
|
||||
for word in $(cat); do
|
||||
case "$word" in
|
||||
"$prefix"*) printf '%s\n' "$word" ;;
|
||||
esac
|
||||
done | sort
|
||||
}
|
||||
|
||||
# The deb names come from the packaging: one control file per Debian architecture names the
|
||||
# genesis-scripts package, and its Depends names the genesis-base package that carries the
|
||||
# Genesis tree for that same architecture.
|
||||
control_scripts_packages()
|
||||
{
|
||||
grep -h '^Package:' "$SCRIPTS_DEBIAN"/control-* | awk '{ print $2 }' | sort
|
||||
}
|
||||
|
||||
control_base_packages()
|
||||
{
|
||||
grep -h '^Depends:' "$SCRIPTS_DEBIAN"/control-* |
|
||||
grep -o 'xcat-genesis-base-[a-z0-9]\+' | sort
|
||||
}
|
||||
|
||||
# The Genesis target architectures of the spec, which are not Debian architecture names.
|
||||
spec_target_arches()
|
||||
{
|
||||
awk '$1 == "%define" && $2 == "tarch" { print $3 }' "$SPEC" | sort -u
|
||||
}
|
||||
|
||||
# The names of a list that carry an architecture the spec does not define.
|
||||
unknown_target_arches()
|
||||
{
|
||||
local prefix="$1" name arch
|
||||
while read -r name; do
|
||||
arch="${name#"$prefix"}"
|
||||
spec_target_arches | grep -qx "$arch" || printf '%s\n' "$name"
|
||||
done
|
||||
}
|
||||
|
||||
@test "the package lists of go-xcat can be built for both packaging formats" {
|
||||
[ -n "$(control_scripts_packages)" ]
|
||||
[ -n "$(spec_target_arches)" ]
|
||||
|
||||
run package_list 1 install
|
||||
[ "$status" -eq 0 ]
|
||||
[[ " $output " == *' xcat-client '* ]]
|
||||
|
||||
run package_list 0 install
|
||||
[ "$status" -eq 0 ]
|
||||
[[ " $output " == *' xCAT-client '* ]]
|
||||
}
|
||||
|
||||
@test "the deb install list names the genesis packages the Debian control files declare" {
|
||||
list="$(package_list 1 install)"
|
||||
[ "$(printf '%s' "$list" | named 'xcat-genesis-scripts-')" = "$(control_scripts_packages)" ]
|
||||
[ "$(printf '%s' "$list" | named 'xcat-genesis-base-')" = "$(control_base_packages)" ]
|
||||
}
|
||||
|
||||
@test "the deb uninstall list names the genesis packages the Debian control files declare" {
|
||||
list="$(package_list 1 uninstall)"
|
||||
[ "$(printf '%s' "$list" | named 'xcat-genesis-scripts-')" = "$(control_scripts_packages)" ]
|
||||
[ "$(printf '%s' "$list" | named 'xcat-genesis-base-')" = "$(control_base_packages)" ]
|
||||
}
|
||||
|
||||
@test "the rpm install list names only Genesis target architectures" {
|
||||
list="$(package_list 0 install)"
|
||||
for prefix in xCAT-genesis-scripts- xCAT-genesis-base-; do
|
||||
[ -z "$(printf '%s' "$list" | named "$prefix" | unknown_target_arches "$prefix")" ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "the rpm uninstall list names only Genesis target architectures" {
|
||||
list="$(package_list 0 uninstall)"
|
||||
for prefix in xCAT-genesis-scripts- xCAT-genesis-base-; do
|
||||
[ -z "$(printf '%s' "$list" | named "$prefix" | unknown_target_arches "$prefix")" ]
|
||||
done
|
||||
}
|
||||
@@ -130,3 +130,13 @@ extract_first_matching_line()
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
# grep that fails when the pattern IS present.
|
||||
#
|
||||
# Do not write "! grep ..." for this. bash ignores errexit for a command inverted with "!",
|
||||
# so such a line never fails a test unless it is the last line of one.
|
||||
refute_grep()
|
||||
{
|
||||
! grep "$@"
|
||||
return $?
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Copy qw(copy);
|
||||
use File::Find qw(find);
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $program = "$FindBin::Bin/../xcattest";
|
||||
my $casedir = "$FindBin::Bin/../autotest/testcase";
|
||||
die("xcattest is not at $program") unless -f $program;
|
||||
die("no test cases under $casedir") unless -d $casedir;
|
||||
|
||||
# A check line xcattest does not understand costs the case the assertion it describes, and the
|
||||
# case says nothing about it: an unknown operator reports "Unrecognized testcase syntax", and a
|
||||
# line whose content does not start with a word character is dropped while the case is loaded.
|
||||
# Read the shipped check lines and let the harness report on them.
|
||||
my @files;
|
||||
find({ wanted => sub { push(@files, $File::Find::name) if -f $File::Find::name }, no_chdir => 1 }, $casedir);
|
||||
die("no case files under $casedir") unless @files;
|
||||
|
||||
my (%checks, %vars);
|
||||
for my $file (sort @files) {
|
||||
open(my $fh, '<', $file) or die("open $file: $!");
|
||||
while (my $line = <$fh>) {
|
||||
chomp($line);
|
||||
next unless $line =~ /^check\s*:\s*(\S.*)$/;
|
||||
my $check = $1;
|
||||
|
||||
# __GETNODEATTR(...)__ and its siblings read the xCAT database, one lsdef for each
|
||||
# check. The shape of the line is what this test reads, so a fixed value stands in.
|
||||
$check =~ s/__\w+\([^)]*\)__/placeholder/g;
|
||||
$vars{$1} = 1 while ($check =~ /\$\$(\w+)/g);
|
||||
push(@{ $checks{$file} }, $check);
|
||||
}
|
||||
close($fh) or die("close $file: $!");
|
||||
}
|
||||
die("no check lines under $casedir") unless keys %checks;
|
||||
|
||||
# One case per shipped file, so a check that reports nothing is attributed to its own file.
|
||||
my %case_of_file = map { $_ => 'syntax_' . do { my $n = $_; $n =~ s{^\Q$casedir\E/?}{}; $n =~ s/[^A-Za-z0-9_-]/_/g; $n } } keys %checks;
|
||||
|
||||
my $fixture = '';
|
||||
for my $file (sort keys %checks) {
|
||||
$fixture .= "start:$case_of_file{$file}\n";
|
||||
$fixture .= "cmd:true\n";
|
||||
$fixture .= "check:$_\n" for @{ $checks{$file} };
|
||||
$fixture .= "end\n";
|
||||
}
|
||||
|
||||
# xcattest derives its result directory from the location of the program, so the copy under the
|
||||
# scratch tree keeps every file the run writes inside that tree.
|
||||
my $root = tempdir(CLEANUP => 1);
|
||||
make_path("$root/bin", "$root/cases");
|
||||
copy($program, "$root/bin/xcattest") or die("copy xcattest: $!");
|
||||
chmod 0755, "$root/bin/xcattest";
|
||||
open(my $fixture_fh, '>', "$root/cases/fixture") or die("write the fixture case: $!");
|
||||
print $fixture_fh $fixture;
|
||||
close($fixture_fh) or die("close the fixture case: $!");
|
||||
|
||||
# Every variable a check line names has to resolve, or xcattest drops the whole case.
|
||||
# A "local" here would be undone at the end of its own statement, before the run.
|
||||
$ENV{"XCATTEST_$_"} = 'placeholder' for keys %vars;
|
||||
$ENV{XCATTEST_CASEDIR} = "$root/cases";
|
||||
# Some shipped patterns warn when perl compiles them, and the warnings say nothing about the
|
||||
# operator. The log file carries what this test reads, so the warnings go to the scratch tree.
|
||||
open(my $stderr_save, '>&', \*STDERR) or die("save STDERR: $!");
|
||||
open(STDERR, '>', "$root/stderr") or die("redirect STDERR: $!");
|
||||
system($^X, "$root/bin/xcattest", '-q', '-t', join(',', sort values %case_of_file));
|
||||
open(STDERR, '>&', $stderr_save) or die("restore STDERR: $!");
|
||||
|
||||
my ($logname) = glob("$root/share/xcat/tools/autotest/result/xcattest.log.*");
|
||||
die("the harness wrote no log under $root") unless $logname;
|
||||
open(my $log_fh, '<', $logname) or die("open $logname: $!");
|
||||
my @log = <$log_fh>;
|
||||
close($log_fh) or die("close $logname: $!");
|
||||
chomp(@log);
|
||||
|
||||
# Count what the harness reported for each case, and keep the lines it did not understand.
|
||||
my (%reported, @unrecognized, $current);
|
||||
for my $line (@log) {
|
||||
$current = $1 if ($line =~ /^------START::(\S+)::/);
|
||||
next unless defined $current;
|
||||
$reported{$current}++ if ($line =~ /^CHECK:/ or $line =~ /^Unrecognized testcase syntax:/);
|
||||
push(@unrecognized, "$current: $line") if ($line =~ /^Unrecognized testcase syntax:/);
|
||||
$current = undef if ($line =~ /^------END::/);
|
||||
}
|
||||
|
||||
is(join("\n", @unrecognized), '',
|
||||
'every check line in the shipped cases uses an operator xcattest understands');
|
||||
|
||||
my @silent;
|
||||
for my $file (sort keys %checks) {
|
||||
my $case = $case_of_file{$file};
|
||||
my $fed = scalar @{ $checks{$file} };
|
||||
my $got = $reported{$case} || 0;
|
||||
push(@silent, "$file: $fed check lines, $got reported") if ($got != $fed);
|
||||
}
|
||||
is(join("\n", @silent), '',
|
||||
'every check line in the shipped cases reports a result, so none is dropped while the case loads');
|
||||
|
||||
done_testing();
|
||||
@@ -7,9 +7,7 @@
|
||||
# builds of DIFFERENT checkouts share nothing and must run concurrently. The historic
|
||||
# host-global lock got that backwards and made the devel and stable CD lanes collide.
|
||||
#
|
||||
# This drives the real lock. The predecessor extracted a marked region out of
|
||||
# build-ubunturepo with a regex and ran that; now the lock is a function, so it is
|
||||
# called directly.
|
||||
# This drives the real lock. The lock is a function, so it is called directly.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
||||
@@ -0,0 +1,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);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env perl
|
||||
# The genesis specs name their package after the target arch: xCAT-genesis-scripts-<tarch> and
|
||||
# xCAT-genesis-base-<tarch>. %{tarch} comes from an %ifarch ladder, and an arch missing from that
|
||||
# ladder leaves the macro unexpanded instead of failing, so rpm builds a package whose Name
|
||||
# carries the macro.
|
||||
#
|
||||
# Expand each spec with rpmspec for every arch xCAT supports and assert the Name carries that arch.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $root = "$FindBin::Bin/../..";
|
||||
|
||||
sub command_exists { my ($c) = @_; return system("command -v $c >/dev/null 2>&1") == 0 }
|
||||
|
||||
plan skip_all => 'rpmspec is not installed' unless command_exists('rpmspec');
|
||||
|
||||
# arch under test => the tarch the spec must resolve it to (x86 and ppc64 are historical names
|
||||
# genesis keeps; see genesis_tarch_from_targetarch in buildrpms.pl).
|
||||
my %tarch = (
|
||||
x86_64 => 'x86_64',
|
||||
i686 => 'x86',
|
||||
ppc64le => 'ppc64',
|
||||
aarch64 => 'aarch64',
|
||||
riscv64 => 'riscv64',
|
||||
);
|
||||
|
||||
my %spec = (
|
||||
'xCAT-genesis-scripts' => "$root/xCAT-genesis-scripts/xCAT-genesis-scripts.spec",
|
||||
'xCAT-genesis-base' => "$root/xCAT-genesis-builder/xCAT-genesis-base.spec",
|
||||
);
|
||||
|
||||
for my $pkg (sort keys %spec) {
|
||||
my $spec = $spec{$pkg};
|
||||
ok(-f $spec, "$pkg spec is present") or next;
|
||||
for my $arch (sort keys %tarch) {
|
||||
my $name = `rpmspec --target $arch -q --qf '%{NAME}' --define 'version 2.19.0' --define 'release snap0' @{[quotemeta $spec]} 2>/dev/null`;
|
||||
chomp $name;
|
||||
is($name, "$pkg-$tarch{$arch}", "$pkg on $arch is named $pkg-$tarch{$arch}");
|
||||
unlike($name, qr/%\{/, "$pkg on $arch leaves no unexpanded macro in its name");
|
||||
}
|
||||
}
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env perl
|
||||
# Drive the genesis test case helpers. genesistest.pl needs a management node, so lift the
|
||||
# routines out and run them with rpm, dpkg and cat shadowed.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Slurper qw(read_text write_text);
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use Test::More;
|
||||
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
my $helper = repo_path('xCAT-test/autotest/testcase/genesis/genesistest.pl');
|
||||
my $shell = repo_path('xCAT-test/autotest/testcase/genesis/test.sh');
|
||||
plan skip_all => 'genesis testcase helpers not found' unless -f $helper && -f $shell;
|
||||
plan tests => 16;
|
||||
|
||||
my $tmpdir = tempdir(CLEANUP => 1);
|
||||
my $source = read_text($helper);
|
||||
|
||||
eval_subs($source, qw(get_os get_arch check_genesis_file));
|
||||
|
||||
# The destiny status check used to be wait_for_boot(), which waited for "booted" and ignored
|
||||
# its argument. Take whichever name the script carries, so this test fails on the status the
|
||||
# check waits for and not on a missing subroutine.
|
||||
my $waiter_name = waiter_name($source);
|
||||
eval_subs($source, $waiter_name);
|
||||
my $waiter = \&{"GenesisTest::$waiter_name"};
|
||||
|
||||
# get_os drives every later branch. AlmaLinux and Rocky release files say neither "Red Hat"
|
||||
# nor "suse" nor "ubuntu", so the management node read as unknown and the check was skipped.
|
||||
is(os_for("AlmaLinux release 9.8 (Olive Jaguar)\n"), 'redhat', 'AlmaLinux is a redhat family node');
|
||||
is(os_for("Rocky Linux release 9.5 (Blue Onyx)\n"), 'redhat', 'Rocky is a redhat family node');
|
||||
is(os_for("Red Hat Enterprise Linux release 9.5\n"), 'redhat', 'RHEL is still a redhat family node');
|
||||
is(os_for("SUSE Linux Enterprise Server 15 SP6\n"), 'sles', 'SLES is still detected');
|
||||
is(os_for("NAME=\"Ubuntu\"\nID=ubuntu\n"), 'ubuntu', 'Ubuntu is still detected');
|
||||
|
||||
# check_genesis_file answers with a return value. The caller used to read $? instead, so a
|
||||
# management node with no genesis packages reported success.
|
||||
{
|
||||
no warnings 'once';
|
||||
local $GenesisTest::os = 'redhat';
|
||||
is(rpm_check("xCAT-genesis-base-x86_64-2.19.0-snap1.noarch\nxCAT-genesis-scripts-x86_64-2.19.0-snap1.noarch\n"),
|
||||
0, 'both genesis packages installed reports success');
|
||||
is(rpm_check("xCAT-genesis-scripts-x86_64-2.19.0-snap1.noarch\n"),
|
||||
1, 'a missing genesis-base reports failure');
|
||||
eval_subs($source, qw(report_genesis_files));
|
||||
is(report_files("xCAT-genesis-scripts-x86_64-2.19.0-snap1.noarch\n"),
|
||||
1, 'report_genesis_files propagates the failure to its caller');
|
||||
}
|
||||
|
||||
# Genesis generates new host keys at every boot, and each case boots the node several times.
|
||||
{
|
||||
no warnings 'once';
|
||||
eval_subs($source, qw(forget_host_keys testxdsh));
|
||||
local $GenesisTest::noderange = 'xcat71-cn';
|
||||
my $run = run_testxdsh(3, genesis_prompt => 1, cmdline => 'destiny=shell');
|
||||
is($run->{status}, 0, 'testxdsh succeeds when the node answers in the Genesis shell');
|
||||
like($run->{makeknownhosts}, qr/\bxcat71-cn\b/, 'the node host keys are forgotten first');
|
||||
like($run->{makeknownhosts}, qr/-r/, 'makeknownhosts is asked to remove them');
|
||||
}
|
||||
|
||||
# xCAT sets nodelist.status from the destiny the node reports with getdestiny: "shell" for the
|
||||
# shell destiny, "configuring" for runcmd. "booted" belongs to an operating system install.
|
||||
{
|
||||
no warnings 'once';
|
||||
local $GenesisTest::noderange = 'xcat71-cn';
|
||||
is(wait_status('shell', 'shell'), 0,
|
||||
'a node that reports the shell destiny ends the wait');
|
||||
is(wait_status('configuring', 'configuring'), 0,
|
||||
'a node that reports the runcmd destiny ends the wait');
|
||||
isnt(wait_status('powering-on', 'shell'), 0,
|
||||
'a node that never reports its destiny fails the wait');
|
||||
}
|
||||
|
||||
# The shell case ignored the result of the wait, so it went on to xdsh whatever the node had
|
||||
# reported and rested entirely on the xdsh probes.
|
||||
{
|
||||
no warnings 'once';
|
||||
eval_subs($source, qw(run_nodeset_shell_test));
|
||||
local $GenesisTest::noderange = 'xcat71-cn';
|
||||
is(run_shell_test(status => 'shell'), 0,
|
||||
'the shell case passes when the node reports the shell destiny');
|
||||
isnt(run_shell_test(status => 'powering-on'), 0,
|
||||
'the shell case fails when the node never reports the shell destiny');
|
||||
}
|
||||
|
||||
#---
|
||||
# wait_status: drive the destiny status check with lsdef shadowed to report one status. The
|
||||
# extracted package neuters sleep, so the failure path does not wait five minutes.
|
||||
#---
|
||||
sub wait_status {
|
||||
my ($reported, $expected) = @_;
|
||||
local $ENV{PATH} = stub_bin(lsdef =>
|
||||
"#!/bin/sh\nprintf 'xcat71-cn: status=%s\\n' " . shell_quote($reported)) . ":$ENV{PATH}";
|
||||
return $waiter->($expected);
|
||||
}
|
||||
|
||||
#---
|
||||
# run_shell_test: drive the shell case with every command it runs shadowed. xdsh always answers
|
||||
# as a Genesis node, so the only thing under test is what the case does with the node status.
|
||||
#---
|
||||
sub run_shell_test {
|
||||
my (%opt) = @_;
|
||||
my $dir = tempdir(DIR => $tmpdir, CLEANUP => 1);
|
||||
write_text("$dir/nodeset", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/rpower", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/makeknownhosts", "#!/bin/sh\nexit 0\n");
|
||||
write_text("$dir/lsdef", "#!/bin/sh\nprintf 'xcat71-cn: status=%s\\n' " . shell_quote($opt{status}) . "\n");
|
||||
write_text("$dir/xdsh", "#!/bin/sh\nfor a in \"\$@\"; do\n case \"\$a\" in\n */cmdline|/proc/cmdline) printf '%s\\n' 'destiny=shell'; exit 0;;\n esac\ndone\nprintf '%s\\n' '[xCAT Genesis running on node]'\n");
|
||||
chmod 0755, map { "$dir/$_" } qw(nodeset rpower makeknownhosts lsdef xdsh);
|
||||
local $ENV{PATH} = "$dir:$ENV{PATH}";
|
||||
return GenesisTest::run_nodeset_shell_test();
|
||||
}
|
||||
|
||||
#---
|
||||
# run_testxdsh: drive testxdsh with makeknownhosts and xdsh shadowed. xdsh is asked twice --
|
||||
# once for the prompt, once for the file -- and the stub answers both from its arguments.
|
||||
#---
|
||||
sub run_testxdsh {
|
||||
my ($value, %opt) = @_;
|
||||
my $dir = tempdir(DIR => $tmpdir, CLEANUP => 1);
|
||||
my $log = "$dir/makeknownhosts.log";
|
||||
write_text("$dir/makeknownhosts", "#!/bin/sh\necho \"\$@\" >> '$log'\n");
|
||||
my $prompt = $opt{genesis_prompt} ? '[xCAT Genesis running on node]' : 'sh-5.1';
|
||||
write_text("$dir/xdsh", "#!/bin/sh\nfor a in \"\$@\"; do\n case \"\$a\" in\n */cmdline|/proc/cmdline) printf '%s\\n' '$opt{cmdline}'; exit 0;;\n esac\ndone\nprintf '%s\\n' '$prompt'\n");
|
||||
chmod 0755, "$dir/makeknownhosts", "$dir/xdsh";
|
||||
local $ENV{PATH} = "$dir:$ENV{PATH}";
|
||||
my $status = GenesisTest::testxdsh($value);
|
||||
return { status => $status, makeknownhosts => (-f $log ? read_text($log) : '') };
|
||||
}
|
||||
|
||||
#---
|
||||
# eval_subs: lift named subs out of the script and compile them into a scratch package, so
|
||||
# they can be run without a management node. Bails out when a sub stops being extractable.
|
||||
#---
|
||||
sub eval_subs {
|
||||
my ($text, @names) = @_;
|
||||
my $code = "package GenesisTest;\nno strict;\nno warnings;\nour \$os;\nour \$check_genesis_file;\nour \$noderange;\n";
|
||||
# The waits are minutes long. Neuter sleep so the extracted routines run at test speed.
|
||||
$code .= "use subs qw(sleep);\nsub sleep { \$GenesisTest::SLEPT += (\$_[0] || 0); return 1; }\n";
|
||||
$code .= "sub send_msg { push \@GenesisTest::MSG, \$_[1]; return 0; }\n";
|
||||
foreach my $name (@names) {
|
||||
my ($body) = $text =~ /^(sub \Q$name\E \{.*?^\})$/ms;
|
||||
die("sub $name() not found in $helper") unless defined $body;
|
||||
$code .= "$body\n";
|
||||
}
|
||||
$code .= "1;\n";
|
||||
eval $code or die("cannot compile the extracted helpers: $@");
|
||||
}
|
||||
|
||||
#---
|
||||
# waiter_name: the name the script gives its destiny status check.
|
||||
#---
|
||||
sub waiter_name {
|
||||
my ($text) = @_;
|
||||
foreach my $name (qw(wait_for_node_status wait_for_boot)) {
|
||||
return $name if $text =~ /^sub \Q$name\E \{/m;
|
||||
}
|
||||
die("no destiny status check found in $helper");
|
||||
}
|
||||
|
||||
#---
|
||||
# os_for: run get_os with `cat` shadowed so it reads the release text under test.
|
||||
#---
|
||||
sub os_for {
|
||||
my ($release) = @_;
|
||||
local $ENV{PATH} = stub_bin(cat => "#!/bin/sh\nprintf '%s' " . shell_quote($release)) . ":$ENV{PATH}";
|
||||
return GenesisTest::get_os();
|
||||
}
|
||||
|
||||
#---
|
||||
# rpm_check: run check_genesis_file with `rpm` shadowed so `rpm -qa` lists the given packages.
|
||||
#---
|
||||
sub rpm_check {
|
||||
my ($installed) = @_;
|
||||
local $ENV{PATH} = stub_bin(rpm => "#!/bin/sh\nprintf '%s' " . shell_quote($installed)) . ":$ENV{PATH}";
|
||||
return GenesisTest::check_genesis_file('x86_64');
|
||||
}
|
||||
|
||||
sub report_files {
|
||||
my ($installed) = @_;
|
||||
local $ENV{PATH} = stub_bin(rpm => "#!/bin/sh\nprintf '%s' " . shell_quote($installed)) . ":$ENV{PATH}";
|
||||
return GenesisTest::report_genesis_files('x86_64');
|
||||
}
|
||||
|
||||
#---
|
||||
# shell_quote: single-quote a string for /bin/sh.
|
||||
#---
|
||||
sub shell_quote {
|
||||
my ($v) = @_;
|
||||
$v =~ s/'/'\\''/g;
|
||||
return "'$v'";
|
||||
}
|
||||
|
||||
#---
|
||||
# stub_bin: a directory holding one shadow command, ahead of the real one on PATH.
|
||||
#---
|
||||
sub stub_bin {
|
||||
my (%cmd) = @_;
|
||||
my $dir = tempdir(DIR => $tmpdir, CLEANUP => 1);
|
||||
while (my ($name, $body) = each %cmd) {
|
||||
write_text("$dir/$name", $body);
|
||||
chmod 0755, "$dir/$name";
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# The scratch package below declares these; the test names them once each.
|
||||
no warnings 'once';
|
||||
|
||||
my $source = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/kvm.pm";
|
||||
open(my $source_fh, '<', $source) or die "open $source: $!";
|
||||
my $content = do { local $/; <$source_fh> };
|
||||
close($source_fh) or die "close $source: $!";
|
||||
|
||||
my @routines;
|
||||
for my $name (qw(createstorage build_diskstruct guest_arch_profile getUnits
|
||||
default_storagemodel)) {
|
||||
my ($routine) = $content =~ /^(sub \Q$name\E\s*\{.*?^\})/ms;
|
||||
die("could not extract $name from kvm.pm") unless $routine;
|
||||
push(@routines, $routine);
|
||||
}
|
||||
|
||||
# kvm.pm needs a management node to load, so createstorage runs in a scratch package.
|
||||
# get_filepath_by_url is the routine that reaches libvirt; it records the device name it is
|
||||
# asked for, which is the name createstorage gives the volume of the node.
|
||||
my $harness = <<'PERL';
|
||||
package KVMStore;
|
||||
our ($node, $confdata, $clonemethod, @asked);
|
||||
sub getstorageformat { my ($cfginfo) = @_; return $cfginfo->{storageformat}; }
|
||||
sub get_filepath_by_url { my %args = @_; push(@asked, $args{dev}); return $args{dev}; }
|
||||
sub oldCreateStorage { push(@asked, 'oldCreateStorage'); }
|
||||
sub get_multiple_paths_by_url { return {}; }
|
||||
PERL
|
||||
|
||||
eval $harness . join("\n", @routines) . "\n1;\n"; ## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
die("could not load the kvm storage routines: $@") if $@;
|
||||
|
||||
# The name createstorage gives the volume of one node. $stale is a capture left live in this
|
||||
# block by an earlier successful match, which is the state createstorage runs in when a
|
||||
# routine on the call path matched a pattern that has a group.
|
||||
sub volume_dev {
|
||||
my (%args) = @_;
|
||||
my $storage = $args{storage} // 'dir:///var/lib/libvirt/images/';
|
||||
my $cfginfo = {
|
||||
node => 'cn1',
|
||||
host => 'hyp1',
|
||||
storage => $storage,
|
||||
storagemodel => $args{storagemodel},
|
||||
};
|
||||
@KVMStore::asked = ();
|
||||
# The match must run in this block, and nothing may match after it: perl restores $1 when
|
||||
# the block that set it ends, and any later successful match replaces what it holds.
|
||||
my $subject = 'left by an earlier match: ' . ($args{stale} // '');
|
||||
$subject =~ /match: (.*)/ if defined $args{stale};
|
||||
# A match without a group empties $1, which is the clean state the other cases need.
|
||||
$subject =~ /^left/ unless defined $args{stale};
|
||||
KVMStore::createstorage($storage, undef, '30G', $cfginfo, 1);
|
||||
return $KVMStore::asked[0];
|
||||
}
|
||||
|
||||
# dohyp sets storagemodel to scsi for every node it dispatches, whatever the architecture,
|
||||
# before mkvm reaches createstorage. That default is what names the volume of a node whose
|
||||
# vmstoragemodel is empty, and a riscv64 node depends on it: the riscv64 virt machine has no
|
||||
# IDE controller, so its volume must be sd*.
|
||||
is(volume_dev(storagemodel => 'scsi'), 'sda',
|
||||
'the scsi storage model names an sd* volume');
|
||||
|
||||
# A capture from a match made elsewhere must not name the volume. These are the values a
|
||||
# routine on the mkvm call path can leave in $1.
|
||||
is(volume_dev(storagemodel => 'scsi', stale => '/var/lib/libvirt/images/'), 'sda',
|
||||
'a path left by an earlier match does not name the volume');
|
||||
is(volume_dev(storagemodel => 'scsi', stale => 'virtio'), 'sda',
|
||||
'a model name left by an earlier match does not name the volume');
|
||||
is(volume_dev(storagemodel => 'virtio', stale => 'scsi'), 'vda',
|
||||
'an earlier match does not override vmstoragemodel either');
|
||||
|
||||
# The model stated on the vmstorage value, and vmstoragemodel, still name the volume.
|
||||
is(volume_dev(storage => 'dir:///var/lib/libvirt/images/=scsi'), 'sda',
|
||||
'a model on the vmstorage value names an sd* volume');
|
||||
is(volume_dev(storagemodel => 'virtio'), 'vda',
|
||||
'vmstoragemodel=virtio names a vd* volume');
|
||||
|
||||
# createstorage on its own defaults to ide. Nothing in the product reaches this today: dohyp
|
||||
# gives every node the default storage model first.
|
||||
is(volume_dev(), 'hda', 'createstorage alone defaults to an hd* volume');
|
||||
|
||||
# A node with no vmstoragemodel takes its sd* name from that default, so the two are driven
|
||||
# together.
|
||||
is(volume_dev(storagemodel => KVMStore::default_storagemodel()), 'sda',
|
||||
'the default storage model names an sd* volume');
|
||||
|
||||
# build_diskstruct reads $1 the same way, for a disk backed by a plain file. The device name
|
||||
# and the bus of that disk must come from the node, not from a match made elsewhere.
|
||||
sub file_disk {
|
||||
my (%args) = @_;
|
||||
local $KVMStore::node = 'cn1';
|
||||
local $KVMStore::confdata = {
|
||||
vm => { cn1 => [ { host => 'hyp1', storage => '/var/lib/libvirt/images/cn1.img' } ] },
|
||||
nodetype => { cn1 => [ { arch => $args{arch} } ] },
|
||||
hyp1 => { cpumodel => 'x86_64' },
|
||||
};
|
||||
my $chatter = '';
|
||||
my $disks;
|
||||
my $subject = 'left by an earlier match: ' . ($args{stale} // '');
|
||||
$subject =~ /match: (.*)/ if defined $args{stale};
|
||||
$subject =~ /^left/ unless defined $args{stale};
|
||||
{
|
||||
open(my $capture, '>', \$chatter) or die "capture stdout: $!";
|
||||
local *STDOUT = $capture;
|
||||
($disks) = KVMStore::build_diskstruct(undef);
|
||||
}
|
||||
return $disks->[0];
|
||||
}
|
||||
|
||||
is(file_disk(arch => 'x86_64')->{target}->{bus}, 'ide',
|
||||
'a file-backed disk of an x86_64 node is ide');
|
||||
is(file_disk(arch => 'x86_64', stale => 'virtio')->{target}->{bus}, 'ide',
|
||||
'a model name left by an earlier match does not choose the bus of a file-backed disk');
|
||||
is(file_disk(arch => 'riscv64', stale => 'ide')->{target}->{dev}, 'sda',
|
||||
'a riscv64 file-backed disk keeps its sd* name whatever an earlier match left behind');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# The scratch package below declares these; the test names them once each.
|
||||
no warnings 'once';
|
||||
|
||||
my $source = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/kvm.pm";
|
||||
open(my $source_fh, '<', $source) or die "open $source: $!";
|
||||
my $content = do { local $/; <$source_fh> };
|
||||
close($source_fh) or die "close $source: $!";
|
||||
|
||||
my @routines;
|
||||
for my $name (qw(build_diskstruct guest_arch_profile getUnits)) {
|
||||
my ($routine) = $content =~ /^(sub \Q$name\E\s*\{.*?^\})/ms;
|
||||
die("could not extract $name from kvm.pm") unless $routine;
|
||||
push(@routines, $routine);
|
||||
}
|
||||
|
||||
# kvm.pm needs a management node to load, so the disk builder runs in a scratch package.
|
||||
# get_multiple_paths_by_url is the only routine it calls that reaches libvirt; it answers
|
||||
# from $pool, which holds what a storage pool reports for one node.
|
||||
my $harness = <<'PERL';
|
||||
package KVMDisk;
|
||||
our ($node, $confdata, $pool);
|
||||
sub get_multiple_paths_by_url { return $pool; }
|
||||
PERL
|
||||
|
||||
eval $harness . join("\n", @routines) . "\n1;\n"; ## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
die("could not load the kvm disk builder: $@") if $@;
|
||||
|
||||
# Build the disks of a node of $arch whose vmstorage is a libvirt pool holding the volumes
|
||||
# in $pool: a path => { device, format } map, the shape get_multiple_paths_by_url returns.
|
||||
sub pool_disks {
|
||||
my ($arch, $pool) = @_;
|
||||
local $KVMDisk::node = 'cn1';
|
||||
local $KVMDisk::confdata = {
|
||||
vm => { cn1 => [ {
|
||||
host => 'hyp1',
|
||||
storage => 'dir:///var/lib/libvirt/images/',
|
||||
storagecache => 'writeback',
|
||||
} ] },
|
||||
nodetype => { cn1 => [ { arch => $arch } ] },
|
||||
hyp1 => { cpumodel => 'x86_64' },
|
||||
};
|
||||
local $KVMDisk::pool = $pool;
|
||||
my $chatter = '';
|
||||
my $disks;
|
||||
{
|
||||
open(my $capture, '>', \$chatter) or die "capture stdout: $!";
|
||||
local *STDOUT = $capture;
|
||||
($disks) = KVMDisk::build_diskstruct(undef);
|
||||
}
|
||||
die('build_diskstruct returned no disks') unless ref $disks eq 'ARRAY';
|
||||
return $disks;
|
||||
}
|
||||
|
||||
# One volume in the pool, named <node>.<device>.<format>. The disk is the first element;
|
||||
# the optical drive build_diskstruct always appends is the second.
|
||||
sub pool_disk {
|
||||
my ($arch, $device) = @_;
|
||||
my $path = "/var/lib/libvirt/images/cn1.$device.qcow2";
|
||||
return pool_disks($arch, { $path => { device => $device, format => 'qcow2' } })->[0];
|
||||
}
|
||||
|
||||
# A disk on a libvirt storage pool states the bus of the device name it is given. libvirt
|
||||
# reads the same names the same way: hd* is ide, sd* is scsi, vd* is virtio.
|
||||
is(pool_disk('x86_64', 'hda')->{target}->{bus}, 'ide',
|
||||
'an hd* disk on a storage pool is ide');
|
||||
is(pool_disk('x86_64', 'sda')->{target}->{bus}, 'scsi',
|
||||
'an sd* disk on a storage pool is scsi');
|
||||
is(pool_disk('x86_64', 'vda')->{target}->{bus}, 'virtio',
|
||||
'a vd* disk on a storage pool is virtio');
|
||||
|
||||
# The device name is the name of the volume in the pool, and stays it. The riscv64 virt
|
||||
# machine has no IDE controller, so a riscv64 node depends on that name being sd*.
|
||||
my $riscv = pool_disk('riscv64', 'sda');
|
||||
is($riscv->{target}->{dev}, 'sda', 'a riscv64 pool disk keeps the sd* name of its volume');
|
||||
is($riscv->{target}->{bus}, 'scsi', 'a riscv64 pool disk is scsi, not ide');
|
||||
|
||||
my $riscv_all = pool_disks('riscv64',
|
||||
{ '/var/lib/libvirt/images/cn1.sda.qcow2' => { device => 'sda', format => 'qcow2' } });
|
||||
is($riscv_all->[1]->{device}, 'cdrom', 'the riscv64 guest still gets an optical drive');
|
||||
like($riscv_all->[1]->{target}->{dev}, qr/^sd/,
|
||||
'the riscv64 optical drive is named sd*, not hd*');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
# The scratch package below declares these; the test names them once each.
|
||||
no warnings 'once';
|
||||
|
||||
my $source = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/kvm.pm";
|
||||
open(my $source_fh, '<', $source) or die "open $source: $!";
|
||||
my $content = do { local $/; <$source_fh> };
|
||||
close($source_fh) or die "close $source: $!";
|
||||
|
||||
my @routines;
|
||||
for my $name (qw(build_xmldesc guest_arch_profile build_oshash build_diskstruct getUnits)) {
|
||||
my ($routine) = $content =~ /^(sub \Q$name\E\s*\{.*?^\})/ms;
|
||||
die("could not extract $name from kvm.pm") unless $routine;
|
||||
push(@routines, $routine);
|
||||
}
|
||||
|
||||
# kvm.pm needs a management node to load, so the domain builder runs in a scratch package.
|
||||
# Only the routines that reach libvirt or the xCAT database are replaced; the domain builder
|
||||
# itself is the code under test.
|
||||
my $harness = <<'PERL';
|
||||
package KVMArch;
|
||||
use XML::Simple qw(XMLout);
|
||||
our ($node, $confdata, $updatetable, $hypconn);
|
||||
sub getNodeUUID { return '00000000-0000-0000-0000-000000000001'; }
|
||||
sub get_multiple_paths_by_url { return {}; }
|
||||
sub build_nicstruct { return []; }
|
||||
sub genpassword { return 'password'; }
|
||||
PERL
|
||||
|
||||
eval $harness . join("\n", @routines) . "\n1;\n"; ## no critic (BuiltinFunctions::ProhibitStringyEval)
|
||||
die("could not load the kvm domain builder: $@") if $@;
|
||||
|
||||
# Build one domain for a node of $guest_arch on a hypervisor that reports $hyp_cpumodel.
|
||||
sub domain_xml {
|
||||
my ($guest_arch, $hyp_cpumodel) = @_;
|
||||
local $KVMArch::node = 'cn1';
|
||||
local $KVMArch::confdata = {
|
||||
vm => { cn1 => [ { host => 'hyp1', memory => 8192, cpus => 4 } ] },
|
||||
nodetype => { cn1 => [ { arch => $guest_arch, os => 'rocky10.2' } ] },
|
||||
hyp1 => { cpumodel => $hyp_cpumodel },
|
||||
};
|
||||
local $KVMArch::updatetable = {};
|
||||
my $xml = KVMArch::build_xmldesc('cn1');
|
||||
die("build_xmldesc returned no XML for $guest_arch on $hyp_cpumodel")
|
||||
unless defined $xml and !ref $xml;
|
||||
return $xml;
|
||||
}
|
||||
|
||||
sub os_type_element {
|
||||
my ($xml) = @_;
|
||||
my ($attrs) = $xml =~ m{<type\b([^>]*)>hvm</type>}s;
|
||||
return defined $attrs ? $attrs : '';
|
||||
}
|
||||
|
||||
# A riscv64 node on an x86_64 hypervisor. The guest architecture is not the host
|
||||
# architecture, so the domain runs under emulation and states its own machine type.
|
||||
my $riscv = domain_xml('riscv64', 'x86_64');
|
||||
like($riscv, qr/<domain\b[^>]*\btype="qemu"/,
|
||||
'a riscv64 guest on an x86_64 hypervisor is a qemu domain, not kvm');
|
||||
like(os_type_element($riscv), qr/\barch="riscv64"/,
|
||||
'the domain arch is the arch of the node');
|
||||
like(os_type_element($riscv), qr/\bmachine="virt"/,
|
||||
'a riscv64 guest uses the virt machine type');
|
||||
like($riscv, qr/<os\b[^>]*\bfirmware="efi"/,
|
||||
'a riscv64 virt guest boots UEFI');
|
||||
unlike($riscv, qr/<(?:pae|acpi|apic)\b/,
|
||||
'pae, acpi and apic are x86 features and are left out of a riscv64 guest');
|
||||
unlike($riscv, qr/<bios\b/,
|
||||
'the SeaBIOS serial option is left out of a riscv64 guest');
|
||||
unlike($riscv, qr/<input\b/,
|
||||
'the riscv64 virt machine has no USB controller, so it gets no USB tablet');
|
||||
|
||||
# POWER is unchanged: the arch still comes from the hypervisor there.
|
||||
my $power = domain_xml('ppc64le', 'ppc64le');
|
||||
like($power, qr/<domain\b[^>]*\btype="kvm"/, 'a POWER guest stays a kvm domain');
|
||||
like(os_type_element($power), qr/\barch="ppc64"/, 'ppc64le hypervisors keep arch ppc64');
|
||||
like(os_type_element($power), qr/\bmachine="pseries"/, 'ppc64le hypervisors keep machine pseries');
|
||||
|
||||
# x86_64 on x86_64 is unchanged: libvirt picks the arch and the machine type.
|
||||
my $x86 = domain_xml('x86_64', 'x86_64');
|
||||
like($x86, qr/<domain\b[^>]*\btype="kvm"/, 'an x86_64 guest stays a kvm domain');
|
||||
unlike(os_type_element($x86), qr/\barch=/, 'an x86_64 guest states no arch');
|
||||
unlike(os_type_element($x86), qr/\bmachine=/, 'an x86_64 guest states no machine type');
|
||||
like($x86, qr/<input\b[^>]*\bbus="usb"/, 'an x86_64 guest keeps the USB tablet');
|
||||
|
||||
# The disks of a riscv64 guest. The virt machine has no IDE controller, so an ide disk or an
|
||||
# hd* optical drive makes libvirt refuse the domain.
|
||||
sub disk_struct {
|
||||
my ($guest_arch) = @_;
|
||||
local $KVMArch::node = 'cn1';
|
||||
local $KVMArch::confdata = {
|
||||
vm => { cn1 => [ { host => 'hyp1', storage => '/var/lib/libvirt/images/cn1.img' } ] },
|
||||
nodetype => { cn1 => [ { arch => $guest_arch } ] },
|
||||
hyp1 => { cpumodel => 'x86_64' },
|
||||
};
|
||||
my $chatter = '';
|
||||
my $disks;
|
||||
{
|
||||
open(my $capture, '>', \\$chatter) or die "capture stdout: $!";
|
||||
local *STDOUT = $capture;
|
||||
($disks) = KVMArch::build_diskstruct(undef);
|
||||
}
|
||||
return $disks;
|
||||
}
|
||||
|
||||
my $riscv_disks = disk_struct('riscv64');
|
||||
is($riscv_disks->[0]->{target}->{bus}, 'scsi', 'a riscv64 disk is scsi, not ide');
|
||||
like($riscv_disks->[0]->{target}->{dev}, qr/^sd/, 'a riscv64 disk is named sd*');
|
||||
is($riscv_disks->[1]->{device}, 'cdrom', 'the guest still gets an optical drive');
|
||||
like($riscv_disks->[1]->{target}->{dev}, qr/^sd/, 'a riscv64 optical drive is named sd*, not hd*');
|
||||
|
||||
my $x86_disks = disk_struct('x86_64');
|
||||
is($x86_disks->[0]->{target}->{bus}, 'ide', 'an x86_64 disk keeps the ide default');
|
||||
like($x86_disks->[1]->{target}->{dev}, qr/^hd/, 'an x86_64 optical drive keeps the hd* name');
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env perl
|
||||
# mknb stages the Genesis payload before it can build a netboot image. Those copies are the only
|
||||
# point at which mknb learns that an installed Genesis image is unusable.
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../../perl-xCAT";
|
||||
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
|
||||
use Test::More;
|
||||
|
||||
BEGIN { $INC{'xCAT/Utils.pm'} = 1; $INC{'xCAT/MsgUtils.pm'} = 1;
|
||||
$INC{'xCAT/Table.pm'} = 1; $INC{'xCAT/NetworkUtils.pm'} = 1;
|
||||
$INC{'xCAT/TableUtils.pm'} = 1; $INC{'xCAT_monitoring/monitorctrl.pm'} = 1; }
|
||||
|
||||
require "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/mknb.pm";
|
||||
|
||||
can_ok('xCAT_plugin::mknb', 'stage_genesis_payload')
|
||||
or die('mknb has no stage_genesis_payload to drive');
|
||||
|
||||
# Drive the routine with a runner that fails exactly one copy, so each assertion names the
|
||||
# copy it is about rather than the pair.
|
||||
sub stage {
|
||||
my (%opt) = @_;
|
||||
my @ran;
|
||||
my ($rc, $src) = xCAT_plugin::mknb::stage_genesis_payload(
|
||||
genesis_type => $opt{type} // 'legacy',
|
||||
genesis_dir => '/opt/xcat/share/xcat/netboot/genesis/x86_64',
|
||||
tftpdir => '/tftpboot',
|
||||
arch => 'x86_64',
|
||||
tempdir => '/tmp/scratch',
|
||||
run => sub {
|
||||
my ($cmd) = @_;
|
||||
push @ran, $cmd;
|
||||
return ($opt{fail} && $cmd =~ /$opt{fail}/) ? 256 : 0;
|
||||
},
|
||||
);
|
||||
return { rc => $rc, src => $src, ran => \@ran };
|
||||
}
|
||||
|
||||
# --- legacy: both copies must be able to fail the step -----------------------
|
||||
my $ok = stage();
|
||||
is($ok->{rc}, 0, 'a legacy image whose copies both succeed stages cleanly');
|
||||
is(scalar @{ $ok->{ran} }, 2, 'the legacy path copies the root tree and the kernel');
|
||||
|
||||
my $nofs = stage(fail => qr{/fs/\*});
|
||||
isnt($nofs->{rc}, 0, 'an unreadable root tree fails the step');
|
||||
like($nofs->{src}, qr{/fs$}, 'and the failure names the root tree');
|
||||
|
||||
my $nokernel = stage(fail => qr{/kernel });
|
||||
isnt($nokernel->{rc}, 0, 'a missing kernel fails the step');
|
||||
like($nokernel->{src}, qr{/kernel$}, 'and the failure names the kernel, not the root tree');
|
||||
|
||||
# --- exported (OpenEmbedded) path -------------------------------------------
|
||||
my $nonb = stage(type => 'exported', fail => qr{/nbroot/\*});
|
||||
isnt($nonb->{rc}, 0, 'an unreadable nbroot fails the step');
|
||||
like($nonb->{src}, qr{/nbroot$}, 'and the failure names nbroot');
|
||||
|
||||
my $oknb = stage(type => 'exported');
|
||||
is($oknb->{rc}, 0, 'an exported image whose copy succeeds stages cleanly');
|
||||
|
||||
done_testing();
|
||||
@@ -12,7 +12,7 @@ use lib "$FindBin::Bin/../lib";
|
||||
use lib "$FindBin::Bin/../../build-utils/lib";
|
||||
use Test::More;
|
||||
|
||||
use XCAT::BuildUtils qw(XCAT_PROBE_HELPERS);
|
||||
use XCAT::BuildUtils qw(XCAT_PROBE_HELPERS stage_probe_helpers);
|
||||
use XCAT::Test::File qw(repo_path slurp_repo_file);
|
||||
|
||||
my @helpers = qw(
|
||||
@@ -29,7 +29,6 @@ my @affected_subcommands = qw(
|
||||
);
|
||||
|
||||
my $builder = slurp_repo_file('buildrpms.pl');
|
||||
my $debian_builder = slurp_repo_file('build-ubunturepo');
|
||||
my $installed_probe_test =
|
||||
slurp_repo_file('xCAT-test/autotest/testcase/probe/xcatproble_list');
|
||||
my $rpm_spec = slurp_repo_file('xCAT-probe/xCAT-probe.spec');
|
||||
@@ -67,6 +66,12 @@ like(
|
||||
'Debian package requires ss or the legacy netstat provider'
|
||||
);
|
||||
|
||||
# The Debian builder stages the helpers by calling stage_probe_helpers, so run it and
|
||||
# look at what it produced. The predecessor matched a `cp -f` line in build-ubunturepo,
|
||||
# which passed whenever that text was reformatted and failed whenever it moved.
|
||||
my $staged_probe_dir = File::Spec->catdir(tempdir(CLEANUP => 1), 'lib', 'perl', 'xCAT');
|
||||
stage_probe_helpers(repo_path(File::Spec->catdir('perl-xCAT', 'xCAT')), $staged_probe_dir);
|
||||
|
||||
for my $helper (@helpers) {
|
||||
my $source = repo_path(File::Spec->catfile('perl-xCAT', 'xCAT', $helper));
|
||||
ok(-f $source, "$helper source exists");
|
||||
@@ -75,9 +80,8 @@ for my $helper (@helpers) {
|
||||
scalar(grep { $_ eq $helper } XCAT_PROBE_HELPERS),
|
||||
"the shared builder helper list carries $helper"
|
||||
);
|
||||
like(
|
||||
$debian_builder,
|
||||
qr{cp -f [^\n]*/perl-xCAT/xCAT/\Q$helper\E\s+[^\n]*/lib/perl/xCAT/},
|
||||
ok(
|
||||
-f File::Spec->catfile($staged_probe_dir, $helper),
|
||||
"Debian builder stages $helper"
|
||||
);
|
||||
like(
|
||||
@@ -144,7 +148,7 @@ sub copy_tree {
|
||||
my ($source, $destination) = @_;
|
||||
my $rc = system('cp', '-R', $source, $destination);
|
||||
is($rc, 0, "copied $source into the package fixture")
|
||||
or BAIL_OUT("unable to create package fixture from $source");
|
||||
or die("unable to create package fixture from $source");
|
||||
}
|
||||
|
||||
sub run_command {
|
||||
|
||||
@@ -12,8 +12,8 @@ use Test::More;
|
||||
#
|
||||
# The deb side named xcat-genesis-scripts-amd64 in a plain Depends, and that package is
|
||||
# Architecture: all, so apt installed the x86 Genesis scripts (and, through them, the x86 Genesis
|
||||
# base) on a riscv64 management node. Restrict the dependency to the architectures that have a
|
||||
# legacy Genesis, and leave amd64 and ppc64el untouched.
|
||||
# base) on every management node that is not amd64. Name one scripts package per architecture that
|
||||
# has a legacy Genesis, so riscv64 gets none and ppc64el gets its own.
|
||||
|
||||
my $repo_root = File::Spec->rel2abs(
|
||||
File::Spec->catdir( $FindBin::Bin, '..', '..' )
|
||||
@@ -37,10 +37,12 @@ foreach my $pkg ( [ 'xCAT', 'xcat' ], [ 'xCATsn', 'xcatsn' ] ) {
|
||||
my ($recommends) = $control =~ /^Recommends:\s*(.*)$/m;
|
||||
ok( defined $recommends, "$name debian/control has a Recommends line" );
|
||||
|
||||
my ($entry) = grep { /xcat-genesis-scripts/ } split( /\s*,\s*/, $depends );
|
||||
ok( defined $entry, "$name depends on a legacy Genesis scripts package" );
|
||||
like( $entry, qr/\[!riscv64\]/,
|
||||
"$name excludes riscv64 from the legacy Genesis scripts dependency" );
|
||||
my @entries = grep { /xcat-genesis-scripts/ } split( /\s*,\s*/, $depends );
|
||||
ok( scalar(@entries), "$name depends on a legacy Genesis scripts package" );
|
||||
my @unqualified = grep { !/\[(?:amd64|ppc64el)\]\s*$/ } @entries;
|
||||
is_deeply( \@unqualified, [],
|
||||
"$name asks for the legacy Genesis scripts of an architecture that has them" )
|
||||
or diag( "unqualified: @unqualified" );
|
||||
|
||||
SKIP: {
|
||||
skip( "Dpkg::Deps is not available", 7 ) unless $have_dpkg_deps;
|
||||
@@ -56,8 +58,8 @@ foreach my $pkg ( [ 'xCAT', 'xcat' ], [ 'xCATsn', 'xcatsn' ] ) {
|
||||
"$name on riscv64 does not pull the legacy Genesis scripts" );
|
||||
like( $reduced{amd64}, qr/xcat-genesis-scripts-amd64/,
|
||||
"$name on amd64 still pulls them" );
|
||||
like( $reduced{ppc64el}, qr/xcat-genesis-scripts-amd64/,
|
||||
"$name on ppc64el still pulls them" );
|
||||
like( $reduced{ppc64el}, qr/xcat-genesis-scripts-ppc64el/,
|
||||
"$name on ppc64el pulls the ppc64el ones" );
|
||||
|
||||
# The restriction must not take anything else with it: every other dependency of the
|
||||
# amd64 package must survive on riscv64.
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use File::Copy qw(copy);
|
||||
use File::Path qw(make_path);
|
||||
use File::Temp qw(tempdir);
|
||||
use Test::More;
|
||||
|
||||
my $program = "$FindBin::Bin/../xcattest";
|
||||
die("xcattest is not at $program") unless -f $program;
|
||||
|
||||
#---
|
||||
=head3 run_harness
|
||||
|
||||
Descriptions: Run xcattest over one fixture case file and return its log lines.
|
||||
Arguments:
|
||||
$case_text - the content of the fixture case file
|
||||
@names - the case names to run
|
||||
Returns: a reference to the array of log lines, and the failed-cases report lines
|
||||
=cut
|
||||
|
||||
#---
|
||||
sub run_harness {
|
||||
my ($case_text, @names) = @_;
|
||||
|
||||
# xcattest derives its result directory from the location of the program, so the copy
|
||||
# under the scratch tree keeps every file the run writes inside that tree.
|
||||
my $root = tempdir(CLEANUP => 1);
|
||||
make_path("$root/bin", "$root/cases");
|
||||
copy($program, "$root/bin/xcattest") or die("copy xcattest: $!");
|
||||
chmod 0755, "$root/bin/xcattest";
|
||||
|
||||
open(my $case_fh, '>', "$root/cases/fixture") or die("write the fixture case: $!");
|
||||
print $case_fh $case_text;
|
||||
close($case_fh) or die("close the fixture case: $!");
|
||||
|
||||
local $ENV{XCATTEST_CASEDIR} = "$root/cases";
|
||||
system($^X, "$root/bin/xcattest", '-q', '-t', join(',', @names));
|
||||
|
||||
my $slurp = sub {
|
||||
my ($path) = @_;
|
||||
open(my $fh, '<', $path) or die("open $path: $!");
|
||||
my @lines = <$fh>;
|
||||
close($fh) or die("close $path: $!");
|
||||
chomp(@lines);
|
||||
return @lines;
|
||||
};
|
||||
|
||||
my ($log) = glob("$root/share/xcat/tools/autotest/result/xcattest.log.*");
|
||||
die("the harness wrote no running log under $root") unless $log;
|
||||
my ($failed) = glob("$root/share/xcat/tools/autotest/result/failedcases.*");
|
||||
die("the harness wrote no failed-cases report under $root") unless $failed;
|
||||
|
||||
return ([ $slurp->($log) ], [ $slurp->($failed) ]);
|
||||
}
|
||||
|
||||
#---
|
||||
=head3 reported_checks
|
||||
|
||||
Descriptions: Select the check results the harness reported.
|
||||
Arguments:
|
||||
$lines - a reference to the array of log lines
|
||||
Returns: a reference to the array of CHECK lines, in the order they were reported
|
||||
=cut
|
||||
|
||||
#---
|
||||
sub reported_checks {
|
||||
my ($lines) = @_;
|
||||
return [ grep { /^CHECK:/ } @{$lines} ];
|
||||
}
|
||||
|
||||
# The second command fails its first check. The check after it on the same command, and the
|
||||
# checks of every command after it, describe the same run and must report their own result.
|
||||
my $mixed = <<'CASE';
|
||||
start:mixedchecks
|
||||
description:a failed check between checks that pass
|
||||
cmd:echo alpha
|
||||
check:rc==0
|
||||
cmd:echo beta
|
||||
check:rc!=0
|
||||
check:output=~beta
|
||||
cmd:echo gamma
|
||||
check:output=~gamma
|
||||
end
|
||||
CASE
|
||||
|
||||
my ($log, $failed) = run_harness($mixed, 'mixedchecks');
|
||||
|
||||
is_deeply(reported_checks($log),
|
||||
[ "CHECK:rc == 0\t[Pass]",
|
||||
"CHECK:rc != 0\t[Failed]",
|
||||
"CHECK:output =~ beta\t[Pass]",
|
||||
"CHECK:output =~ gamma\t[Pass]" ],
|
||||
'every check reports its own result, and a failed check does not silence the checks after it');
|
||||
|
||||
is_deeply(reported_checks($failed), reported_checks($log),
|
||||
'the failed-cases report carries the same check results as the running log');
|
||||
|
||||
ok(scalar(grep { /^------END::mixedchecks::Failed::/ } @{$log}),
|
||||
'a check that passes after a failed check does not make the case pass');
|
||||
|
||||
# A case that fails more than one check names every one of them.
|
||||
my $twofails = <<'CASE';
|
||||
start:twofailedchecks
|
||||
description:two commands, each with a check that fails
|
||||
cmd:echo one
|
||||
check:rc!=0
|
||||
cmd:echo two
|
||||
check:rc!=0
|
||||
end
|
||||
CASE
|
||||
|
||||
($log, $failed) = run_harness($twofails, 'twofailedchecks');
|
||||
|
||||
is_deeply(reported_checks($log),
|
||||
[ "CHECK:rc != 0\t[Failed]", "CHECK:rc != 0\t[Failed]" ],
|
||||
'both failed checks are reported, not just the first');
|
||||
|
||||
# A case where every check passes is unchanged.
|
||||
my $allpass = <<'CASE';
|
||||
start:allcheckspass
|
||||
description:every check passes
|
||||
cmd:echo alpha
|
||||
check:rc==0
|
||||
check:output=~alpha
|
||||
cmd:echo beta
|
||||
check:output=~beta
|
||||
end
|
||||
CASE
|
||||
|
||||
($log, $failed) = run_harness($allpass, 'allcheckspass');
|
||||
|
||||
is_deeply(reported_checks($log),
|
||||
[ "CHECK:rc == 0\t[Pass]", "CHECK:output =~ alpha\t[Pass]", "CHECK:output =~ beta\t[Pass]" ],
|
||||
'a case whose checks all pass reports every check');
|
||||
|
||||
ok(scalar(grep { /^------END::allcheckspass::Passed::/ } @{$log}),
|
||||
'a case whose checks all pass still reports Passed');
|
||||
|
||||
done_testing();
|
||||
+16
-14
@@ -1417,8 +1417,11 @@ sub run_case {
|
||||
log_this($running_log_fd, ("ElapsedTime:$diffduration sec", "RETURN rc = $rc", "OUTPUT:", @output));
|
||||
push(@caselog, ("ElapsedTime:$diffduration sec", "RETURN rc = $rc", "OUTPUT:", @output));
|
||||
|
||||
# $checkfail is the result of this check, $failflag the result of the case. One
|
||||
# variable for both makes a failed check read as failing every later check.
|
||||
my $checkfail = 0;
|
||||
foreach my $check (@{ $cases_ref->[ $case_name_index_map_ref->{$case} ]->{check}->[$j] }) {
|
||||
last if ($failflag);
|
||||
$checkfail = 0;
|
||||
|
||||
if ($check =~ /rc\s*([=!]+)\s*(\d+)/) {
|
||||
my $lvalue = $rc;
|
||||
@@ -1426,12 +1429,11 @@ sub run_case {
|
||||
my $rvalue = $2;
|
||||
if ((($op eq '!=') && ($lvalue == $rvalue))
|
||||
|| (($op eq '==') && ($lvalue != $rvalue))) {
|
||||
$failflag = 1;
|
||||
$checkfail = 1;
|
||||
}
|
||||
if ($failflag) {
|
||||
if ($checkfail) {
|
||||
log_this($running_log_fd, "CHECK:rc $op $rvalue\t[Failed]");
|
||||
push(@caselog, "CHECK:rc $op $rvalue\t[Failed]");
|
||||
last;
|
||||
} else {
|
||||
log_this($running_log_fd, "CHECK:rc $op $rvalue\t[Pass]");
|
||||
push(@caselog, "CHECK:rc $op $rvalue\t[Pass]");
|
||||
@@ -1446,17 +1448,16 @@ sub run_case {
|
||||
|| (($op eq '!~') && ($lvalue =~ /$rvalue/))
|
||||
|| (($op eq '==') && ($lvalue ne $rvalue))
|
||||
|| (($op eq '!=') && ($lvalue eq $rvalue))) {
|
||||
$failflag = 1;
|
||||
$checkfail = 1;
|
||||
} elsif (($op ne '=~') && ($op ne '!~') && ($op ne '==') && ($op ne '!=')) {
|
||||
$failflag = 1;
|
||||
$checkfail = 1;
|
||||
log_this($running_log_fd, "CHECK:output unrecognized operator: $op\t[Failed]");
|
||||
push(@caselog, "CHECK:output unrecognized operator: $op\t[Failed]");
|
||||
last;
|
||||
next;
|
||||
}
|
||||
if ($failflag) {
|
||||
if ($checkfail) {
|
||||
log_this($running_log_fd, "CHECK:output $op $rvalue\t[Failed]");
|
||||
push(@caselog, "CHECK:output $op $rvalue\t[Failed]");
|
||||
last;
|
||||
} else {
|
||||
log_this($running_log_fd, "CHECK:output $op $rvalue\t[Pass]");
|
||||
push(@caselog, "CHECK:output $op $rvalue\t[Pass]");
|
||||
@@ -1464,7 +1465,7 @@ sub run_case {
|
||||
} elsif ($check =~ /output\s*~~\s*(\S.*)/) {
|
||||
my $op = "~~";
|
||||
|
||||
#my $failflag = 1;
|
||||
# This operator only sets $checkfail to 0, so the check always reports Pass.
|
||||
my $rvalue = $1;
|
||||
|
||||
$rvalue = getfunc($rvalue);
|
||||
@@ -1481,7 +1482,7 @@ sub run_case {
|
||||
my $min = $num * 0.9;
|
||||
$line =~ /.*:.*: (\d+) /;
|
||||
if ($1 < $max && $1 > $min) {
|
||||
$failflag = 0;
|
||||
$checkfail = 0;
|
||||
last;
|
||||
}
|
||||
} else {
|
||||
@@ -1489,19 +1490,20 @@ sub run_case {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($failflag) {
|
||||
if ($checkfail) {
|
||||
log_this($running_log_fd, "CHECK:output $op $rvalue\t[Failed]");
|
||||
push(@caselog, "CHECK:output $op $rvalue\t[Failed]");
|
||||
last;
|
||||
} else {
|
||||
log_this($running_log_fd, "CHECK:output $op $rvalue\t[Pass]");
|
||||
push(@caselog, "CHECK:output $op $rvalue\t[Pass]");
|
||||
}
|
||||
} else {
|
||||
$failflag = 1;
|
||||
$checkfail = 1;
|
||||
log_this($running_log_fd, "Unrecognized testcase syntax: CHECK:$check\t[Failed]");
|
||||
push(@caselog, "Unrecognized testcase syntax: CHECK:$check\t[Failed]");
|
||||
}
|
||||
} continue {
|
||||
$failflag = 1 if ($checkfail);
|
||||
}
|
||||
foreach my $cmdcheck (@{ $cases_ref->[ $case_name_index_map_ref->{$case} ]->{cmdcheck}->[$j] }) {
|
||||
if ($cmdcheck) {
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ Homepage: https://xcat.org/
|
||||
|
||||
Package: xcat
|
||||
Architecture: amd64 ppc64el riscv64
|
||||
Depends: ${perl:Depends}, goconserver(>= 0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, libxml-parser-perl, rsync, tftpd-hpa, libnet-telnet-perl, chrony | ntp, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [!riscv64]
|
||||
Depends: ${perl:Depends}, goconserver(>= 0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, libxml-parser-perl, rsync, tftpd-hpa, libnet-telnet-perl, chrony | ntp, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [amd64], xcat-genesis-scripts-ppc64el (>= 2.13-snap000000000000) [ppc64el]
|
||||
Recommends: net-tools, kea, tftp-hpa, syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, util-linux-extra, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64, xcat-genesis-openembedded-s390x
|
||||
Suggests: yaboot-xcat
|
||||
Description: Metapackage for a common, default xCAT setup
|
||||
|
||||
@@ -8,7 +8,7 @@ Homepage: https://xcat.org/
|
||||
|
||||
Package: xcatsn
|
||||
Architecture: amd64 ppc64el riscv64
|
||||
Depends: ${perl:Depends}, goconserver (>=0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, libxml-parser-perl, tftpd-hpa, libnet-telnet-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [!riscv64]
|
||||
Depends: ${perl:Depends}, goconserver (>=0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, libxml-parser-perl, tftpd-hpa, libnet-telnet-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, nmap, ipmitool-xcat (>= 1.8.18-4), xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) [amd64], xcat-genesis-scripts-ppc64el (>= 2.13-snap000000000000) [ppc64el]
|
||||
Recommends: net-tools, kea, tftp-hpa, syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64, xcat-genesis-openembedded-s390x
|
||||
Suggests: yaboot-xcat
|
||||
Description: Metapackage for a common, default xCAT service node setup
|
||||
|
||||
Reference in New Issue
Block a user