2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-01 16:31:41 +00:00

Merge pull request #7889 from VersatusHPC/fix/service-node-ub

fix(xcat-server): an Ubuntu service node never installs, and never serves its compute node
This commit is contained in:
Daniel Hilst
2026-09-30 20:34:06 -03:00
committed by GitHub
22 changed files with 1640 additions and 14 deletions
+62
View File
@@ -1708,6 +1708,43 @@ sub nfs_export_exists {
return 0;
}
#-----------------------------------------------------------------------------
=head3 nfs_export_line
The /etc/exports line for a directory a service node serves to its nodes.
Arguments:
$dir the directory to export
reexport true when $dir is itself an NFS mount
Returns:
one export line, with no trailing newline.
exportfs refuses an NFS mount without an explicit fsid, and crossmnt lets a client cross into
the mount below. The fsid is derived from the path, so a restart does not make client mounts
stale.
=cut
#-----------------------------------------------------------------------------
sub nfs_export_line {
my ( $dir, %opts ) = _nfs_method_args(@_);
my $options = 'rw,no_root_squash,sync,no_subtree_check,insecure';
if ( $opts{reexport} ) {
$options .= ',crossmnt,fsid=' . _nfs_export_fsid($dir);
}
return "$dir *($options)";
}
# A stable, non-zero fsid for a path. 0 is reserved for the export root.
sub _nfs_export_fsid {
my ($dir) = @_;
my $sum = 0;
$sum = ( $sum * 31 + ord($_) ) % 2147483647 for split //, $dir;
return $sum || 1;
}
sub _ensure_nfs_exported {
my ($nfsserver, $nfsdirectory, $callback, %opts) = @_;
my $export_options = 'rw,no_root_squash,sync,no_subtree_check,insecure';
@@ -2512,4 +2549,29 @@ sub searchcompressedrootimg{
}
#-----------------------------------------------------------------------------
=head3 named_service_action
Which service action brings a freshly written named configuration into effect.
Linux gets a restart. On Debian the package already runs named, so a start is a no-op and
the daemon keeps serving the configuration it read at install time.
Arguments: the platform, 'aix' or 'linux'
Returns: 'start', 'restart', or '' for a platform with no action
=cut
#-----------------------------------------------------------------------------
sub named_service_action {
my ($platform) = @_;
$platform = '' unless defined $platform;
return 'start' if $platform eq 'aix';
return 'restart' if $platform eq 'linux';
return '';
}
1;
+30 -3
View File
@@ -788,6 +788,8 @@ sub setup_FTP
=cut
#-----------------------------------------------------------------------------
sub setup_DNS
{
my $srvclist = shift;
@@ -827,11 +829,15 @@ sub setup_DNS
#}
#my $rc = xCAT::Utils->startService($serv);
# Restart, not start. makenamed.conf has just rewritten named.conf, and a start is a no-op
# where the package already runs the daemon -- Debian does -- so the service keeps serving the
# configuration it read at install time.
my $rc = 0;
if (xCAT::Utils->isAIX()) {
my $action = xCAT::SvrUtils::named_service_action(xCAT::Utils->isAIX() ? 'aix' : 'linux');
if ($action eq 'start') {
$rc = xCAT::Utils->startService("named");
} elsif (xCAT::Utils->isLinux()) {
$rc = xCAT::Utils->startservice("named");
} elsif ($action eq 'restart') {
$rc = xCAT::Utils->restartservice("named");
}
if ($rc != 0)
@@ -919,6 +925,20 @@ sub setup_NFS
my $rc = 0;
if (xCAT::Utils->isLinux())
{
# nfsserver=1 means this node serves files. With site.installloc set the install directory
# is itself a mount here, and re-exporting one needs an fsid.
my $installdir = xCAT::TableUtils->getInstallDir() || "/install";
unless (xCAT::SvrUtils->nfs_export_exists($installdir))
{
my $line = xCAT::SvrUtils->nfs_export_line($installdir,
reexport => (xCAT::Utils->isMounted($installdir) ? 1 : 0));
xCAT::Utils->runcmd("/bin/echo '$line' >> /etc/exports", 0);
if ($::RUNCMD_RC != 0)
{
xCAT::MsgUtils->message('S', "Could not add $installdir to /etc/exports.");
}
}
#my $os = xCAT::Utils->osver();
#if ($os =~ /sles.*/)
#{
@@ -930,6 +950,13 @@ sub setup_NFS
# $rc = xCAT::Utils->startService("nfs");
#}
$rc = xCAT::Utils->startservice("nfs");
# After the daemon, so a fresh export is picked up.
xCAT::Utils->runcmd("/usr/sbin/exportfs -a", 0);
if ($::RUNCMD_RC != 0)
{
xCAT::MsgUtils->message('S', "Error with /usr/sbin/exportfs -a.");
}
}
else
{ #AIX
+51 -9
View File
@@ -19,7 +19,7 @@ is_lsb_ubuntu ()
exit 1 # Not Ubuntu
}
' /etc/lsb-release >/dev/null 2>&1
' "${1:-/etc/lsb-release}" >/dev/null 2>&1
# Routine exit status is exit status of the last command -- the awk script.
#
@@ -28,14 +28,56 @@ is_lsb_ubuntu ()
}
DIRECTORY=/var/named
# forwarder_addresses [<resolv.conf> [<systemd-resolved uplink>]]
#
# The addresses named must forward to, one per line.
#
# A loopback address is not a forwarder: under systemd-resolved /etc/resolv.conf holds the
# 127.0.0.53 stub, and that stub points back at this named. The real servers are in the
# systemd-resolved uplink file.
forwarder_addresses()
{
_fa_resolv=${1:-/etc/resolv.conf}
_fa_uplink=${2:-/run/systemd/resolve/resolv.conf}
_fa_addrs=$(_fa_usable "$_fa_resolv")
if [ -z "$_fa_addrs" ]; then
_fa_addrs=$(_fa_usable "$_fa_uplink")
fi
[ -n "$_fa_addrs" ] && printf '%s\n' "$_fa_addrs"
return 0
}
# check for SLES
grep -s -q sles /etc/os-release
IS_SLES=$?
if [ -f /etc/SuSE-release ] || [ $IS_SLES -eq 0 ]; then
DIRECTORY=/var/lib/named
fi
_fa_usable()
{
[ -r "$1" ] || return 0
awk '$1 == "nameserver" && $2 !~ /^127\./ && $2 != "::1" { print $2 }' "$1" 2>/dev/null
}
# named_directory [<lsb-release> [<os-release> [<SuSE-release>]]]
#
# The working directory for named. It must be writable by the user named drops to: Debian runs
# it as "bind" and ships /var/cache/bind, while /var/named is created root-owned.
named_directory()
{
_nd_lsb=${1:-/etc/lsb-release}
_nd_os=${2:-/etc/os-release}
_nd_suse=${3:-/etc/SuSE-release}
if is_lsb_ubuntu "$_nd_lsb"; then
echo /var/cache/bind
return 0
fi
if [ -f "$_nd_suse" ] || grep -s -q sles "$_nd_os"; then
echo /var/lib/named
return 0
fi
echo /var/named
}
# A test loads this file for the routines above, so the rest must not run on a require.
[ "${MAKENAMED_LIB:-}" = 1 ] && return 0
DIRECTORY=$(named_directory)
FILE=/etc/named.conf
if ( is_lsb_ubuntu ); then
@@ -58,7 +100,7 @@ echo "options {
forward only;
forwarders {" >$FILE
for i in $(grep "^nameserver" /etc/resolv.conf | awk '{print $2}')
forwarder_addresses | while read -r i
do
echo " $i;"
done >>$FILE
@@ -0,0 +1,137 @@
#cloud-config
autoinstall:
version: 1
keyboard: {layout: us, toggle: null, variant: ''}
locale: en_US
network:
version: 2
ethernets:
xcat-install:
match:
name: "e*"
dhcp4: true
ssh:
allow-pw: true
authorized-keys: []
install-server: true
identity:
realname: 'xCAT Admin'
username: xcatadm
hostname: #HOSTNAME#
password: "#CRYPTORLOCKED:passwd:key=system,username=root:password#"
#UBUNTU_SUBIQUITY_APT_CONFIG#
kernel:
package: linux-generic
user-data:
hostname: #HOSTNAME#
disable_root: false
package_update: false
package_upgrade: false
timezone: #TABLE:site:key=timezone:value#
chpasswd:
list:
- "root:#CRYPTORLOCKED:passwd:key=system,username=root:password#"
expire: false
packages:
- openssh-server
- openssh-client
- wget
- vim
- rsync
- busybox-static
- gawk
- bind9-dnsutils
- chrony
- gpg
- #INCLUDE_DEFAULT_PKGLIST_AUTOINSTALL#
early-commands:
- |
exec >/tmp/pre-install.log 2>&1
set -eux
echo "=== xCAT early-commands ==="
echo "=== network ==="
ip addr show
ip route show
echo "=== disk detection ==="
wget -T 30 -O /tmp/getinstdisk http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/getinstdisk
test -s /tmp/getinstdisk
chmod u+x /tmp/getinstdisk
/tmp/getinstdisk
echo "=== pre-install script ==="
wget -T 30 -O /tmp/pre.sh http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/#HOSTNAME#.pre
test -s /tmp/pre.sh
chmod u+x /tmp/pre.sh
/tmp/pre.sh
echo "=== storage injection ==="
test -s /tmp/partitionfile
sed -i '/^\.\.\.$/d' /autoinstall.yaml
cat /tmp/partitionfile >> /autoinstall.yaml
echo "=== DNS setup ==="
# glibc's resolver discards a nameserver line naming a host, so writing the xcatmaster
# *name* leaves the installer -- and the in-target apt that inherits this file -- with no
# DNS. Resolve it here, while the live installer's DHCP resolv.conf still works, and keep
# that file when the name does not resolve. A name written back is the case this step
# exists to prevent.
xcatmaster_host="#TABLE:noderes:$NODE:xcatmaster#"
xcatmaster_ip="$(getent ahostsv4 "$xcatmaster_host" | awk '{print $1; exit}')"
if [ -n "$xcatmaster_ip" ]; then
rm -f /etc/resolv.conf
echo "nameserver $xcatmaster_ip" >/etc/resolv.conf
else
echo "xcat: $xcatmaster_host has no IPv4 address; keeping the resolver DHCP supplied"
fi
echo "domain #TABLE:site:key=domain:value#" >>/etc/resolv.conf
echo "=== early-commands complete ==="
late-commands:
- mkdir -p /target/var/log/xcat/
- '{
cat /tmp/pre-install.log >> /target/var/log/xcat/xcat.log;
installnic="#SUBIQUITYINSTALLNIC#";
installmac="#SUBIQUITYINSTALLMAC#";
mkdir -p /target/etc/netplan;
if [ -z "${installnic}" ]; then
printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " dhcp4: true" " dhcp4-overrides:" " use-domains: true" >/target/etc/netplan/00-xcat-install.yaml;
else
printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " set-name: ${installnic}" " dhcp4: true" " dhcp4-overrides:" " use-domains: true" >/target/etc/netplan/00-xcat-install.yaml;
fi;
chmod 600 /target/etc/netplan/00-xcat-install.yaml;
printf ''%s\n'' ''#HOSTNAME#'' >/target/etc/hostname;
if grep -q ''^127\.0\.1\.1'' /target/etc/hosts; then
sed -i ''s/^127\.0\.1\.1.*/127.0.1.1 #HOSTNAME#/'' /target/etc/hosts;
else
printf ''%s\n'' ''127.0.1.1 #HOSTNAME#'' >>/target/etc/hosts;
fi;
mkdir -p /target/etc/cloud;
touch /target/etc/cloud/cloud-init.disabled;
echo "Updating kernel command line...";
printf ''%s\n'' ''GRUB_CMDLINE_LINUX="#TABLEBLANKOKAY:bootparams:$NODE:kcmdline#"'' >>/target/etc/default/grub;
curtin in-target --target /target update-grub2;
echo "Running late_command Installation script...";
wget -T 30 http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/#HOSTNAME#.post;
chmod u+x #HOSTNAME#.post;
cp ./#HOSTNAME#.post /target/root/post.script;
curtin in-target --target /target /root/post.script;
} >>/target/var/log/xcat/xcat.log 2>&1'
# The installer's sources for the otherpkgs repository and the pkgdir mirrors, and the apt
# configuration that kept recommended packages out, served the install; ospkgs and otherpkgs
# write their own after the first boot. A separate item, so the status of the post script above
# still decides whether the install goes on.
- rm -f /target/etc/apt/sources.list.d/xcat-otherpkgs-*.list /target/etc/apt/sources.list.d/xcat-otherpkgs-*.sources /target/etc/apt/sources.list.d/xcat-pkgdir-*.list /target/etc/apt/sources.list.d/xcat-pkgdir-*.sources /target/etc/apt/apt.conf.d/94curtin-config
# Flip the node to local-disk boot, or it PXE-loops back into the installer on reboot.
# xcatd's install monitor greets with "ready", then answers "next" with "done" and runs
# "nodeset <node> next". Require both tokens: another service on that port is not a flipped
# node. The monitor listens on site.xcatiport; TABLEBLANKOKAY because an absent key fails a
# plain TABLE lookup, and with it the whole template.
# Retry, and log a failure rather than reinstall silently. Both reads are bounded: a monitor
# that accepts and never answers would block a bare read and hang the install here. A
# monitor that died and never came back is #7759.
- ['bash', '-c', 'xm=#XCATVAR:XCATMASTER#; port="#TABLEBLANKOKAY:site:key=xcatiport:value#"; [ -n "$port" ] || port=3002; ok=0; for i in 1 2 3 4 5; do if exec 3<>/dev/tcp/$xm/$port; then if read -r -t 10 hello <&3 && [ "$hello" = "ready" ]; then printf "next\n" >&3; if read -r -t 10 ack <&3 && [ "$ack" = "done" ]; then ok=1; fi; fi; exec 3>&- 3<&-; [ "$ok" = 1 ] && break; fi; sleep 5; done; if [ "$ok" != 1 ]; then echo "xcat: FAILED to flip $(hostname) to local-disk boot via $xm:$port; the node will PXE back into the installer" >>/target/var/log/xcat/xcat.log; fi; exit 0']
error-commands:
# Subiquity waits for every error command to return. "nc -l 8080" waits for a collector, which
# an unattended install does not have. Keep the archive on the installer, and print the end of
# the curtin log to the console, which the management node records.
#
# Both destinations are read from the environment so a test can point them at its own
# directory. The installer sets neither, so an install writes where it always did.
- ['sh', '-c', 'tar -c --ignore-failed-read --transform="s/^/#HOSTNAME#-logs\//" /var/crash /var/log/installer /tmp/pre-install.log /autoinstall.yaml >"${XCAT_ERROR_ARCHIVE:-/run/#HOSTNAME#-logs.tar}" 2>/dev/null; exit 0']
- ['sh', '-c', 'tail -n 80 /var/log/installer/curtin-install.log >"${XCAT_ERROR_CONSOLE:-/dev/console}" 2>/dev/null; exit 0']
@@ -0,0 +1,240 @@
#!/bin/sh
#
# check_provisioning_source.sh --baseline <compute node> <service node>
# check_provisioning_source.sh <compute node> <service node>
# check_provisioning_source.sh --count <ip> [<baseline spec>]
#
# Answer which server sent the compute node its boot payload during THIS run.
#
# The hierarchy cases set noderes.servicenode and read SERVICEGROUP back out of the compute
# node's xcatinfo. That records what xCAT wrote, not where the node fetched from. The management
# node, the service node and the compute node share one subnet, both dhcpd instances hold a
# reservation for the compute node, and xCAT does not arbitrate between them. The compute node
# takes whichever server answers first, so a flat provision satisfies every other assertion the
# cases make.
#
# The httpd access logs settle it. The xNBA exchange hands out an http:// filename, so the
# kernel, the initrd, the root image and the install tree are HTTP requests logged against the
# compute node's address on the server that answered them.
#
# Two things bound what counts, and without either one the answer is wrong in both directions.
# Run --baseline before provisioning: an earlier flat run leaves management-node requests for the
# same address, and counting the whole log fails a later hierarchical run for them. And only a
# request under /install or /tftpboot is a boot payload: a 404 for /favicon.ico is a request from
# the compute node that carries no payload, and it must not stand for one. wget asks for the root
# image as //install/..., so the leading slash repeats.
#
# Scope: the PXE ROM exchange hands out xcat/xnba.kpxe over TFTP and httpd never sees it.
# xnba.kpxe is the same binary on both servers, so it decides nothing about the fetch source.
#
# Run this on the management node. It reads the service node's log with "xdsh -e", which copies
# this script to the service node and runs it there with --count.
set -u
TOKEN=XCAT_HTTPD_REQUESTS
STATE="${XCAT_PROV_SOURCE_STATE:-/var/tmp/xcat-provisioning-source.base}"
# Every readable candidate log. The combined format puts the client address in field 1, and the
# Debian per-vhost format puts the vhost there and the client in field 2.
local_logs()
{
# An explicit log REPLACES the search. Without this a test that points here still reads the
# host's own apache log, so it cannot control what the check counts.
if [ -n "${XCAT_HTTPD_ACCESS_LOG:-}" ]; then
[ -r "$XCAT_HTTPD_ACCESS_LOG" ] && echo "$XCAT_HTTPD_ACCESS_LOG"
return 0
fi
for f in /var/log/httpd/access_log \
/var/log/apache2/access.log \
/var/log/apache2/access_log \
/var/log/apache2/other_vhosts_access.log
do
[ -r "$f" ] || continue
echo "$f"
done
}
# Record how many lines each log holds now, as file:lines joined by commas.
baseline_local()
{
spec=""
for f in $(local_logs); do
n=$(wc -l <"$f" 2>/dev/null | tr -d ' ')
[ -n "$n" ] || n=0
spec="$spec,$f:$n"
done
echo "$TOKEN base $(echo "$spec" | sed 's/^,//')"
}
# Count the boot-payload requests this address made AFTER the baseline. A log shorter than its
# baseline was rotated, so its baseline no longer locates anything and the whole file is new.
count_local_requests()
{
ip="$1"
spec="${2:-}"
logs=$(local_logs | tr '\n' ' ')
if [ -z "$logs" ]; then
echo "$TOKEN nolog 0 0 0"
return 0
fi
checked=""
for f in $logs; do
base=$(echo "$spec" | tr ',' '\n' | sed -n "s|^$f:||p" | head -1)
[ -n "$base" ] || base=0
now=$(wc -l <"$f" 2>/dev/null | tr -d ' ')
[ -n "$now" ] || now=0
[ "$now" -lt "$base" ] && base=0
checked="$checked,$f:$base"
done
# shellcheck disable=SC2086
awk -v ip="$ip" -v token="$TOKEN" -v bases="$(echo "$checked" | sed 's/^,//')" '
BEGIN {
n = split(bases, a, ",")
for (i = 1; i <= n; i++) {
if (a[i] == "") continue
p = index(a[i], ":")
if (p) base[substr(a[i], 1, p - 1)] = substr(a[i], p + 1) + 0
}
}
{
total++
b = (FILENAME in base) ? base[FILENAME] : 0
if (FNR <= b) next
fresh++
if ($1 != ip && $2 != ip) next
path = ""
for (i = 1; i <= NF; i++) if ($i ~ /^"(GET|HEAD|POST)$/) { path = $(i + 1); break }
if (path ~ /^\/+(install|tftpboot)\//) payload++
}
END { print token, "ok", payload + 0, fresh + 0, total + 0 }
' $logs
}
# xdsh prefixes each line with the node name, so read the fields after the token.
read_counts()
{
awk -v token="$TOKEN" '
{ for (i = 1; i <= NF; i++) if ($i == token) { print $(i+1), $(i+2), $(i+3), $(i+4); exit } }
'
}
read_baseline()
{
awk -v token="$TOKEN" '
{ for (i = 1; i <= NF; i++) if ($i == token && $(i+1) == "base") { print $(i+2); exit } }
'
}
node_address()
{
node="$1"
addr=$(lsdef -t node -o "$node" -i ip 2>/dev/null | sed -n 's/^[[:space:]]*ip=//p' | head -1)
[ -n "$addr" ] || addr=$(getent ahostsv4 "$node" 2>/dev/null | awk '{ print $1; exit }')
echo "$addr"
}
if [ "${1:-}" = "--count" ]; then
count_local_requests "${2:-}" "${3:-}"
exit 0
fi
if [ "${1:-}" = "--baseline-local" ]; then
baseline_local
exit 0
fi
MODE=check
if [ "${1:-}" = "--baseline" ]; then
MODE=baseline
shift
fi
CN="${1:-}"
SN="${2:-}"
if [ -z "$CN" ] || [ -z "$SN" ]; then
echo "provisioning source error: usage: $0 [--baseline] <compute node> <service node>" >&2
exit 2
fi
SELF=$(readlink -f "$0")
MN=$(hostname)
if [ "$MODE" = baseline ]; then
MN_BASE=$(baseline_local | read_baseline)
SN_BASE=$(xdsh "$SN" -e "$SELF" --baseline-local 2>&1 | read_baseline)
if [ -z "$MN_BASE" ]; then
echo "provisioning source error: no access log to baseline on $MN" >&2
exit 1
fi
if [ -z "$SN_BASE" ]; then
echo "provisioning source error: no access log to baseline on $SN" >&2
exit 1
fi
printf 'MN %s\nSN %s\n' "$MN_BASE" "$SN_BASE" >"$STATE" || exit 1
echo "provisioning source baseline recorded in $STATE"
exit 0
fi
# Without a baseline this cannot tell a request from this run from one an earlier run left
# behind, so it refuses rather than answering from the whole log.
if [ ! -r "$STATE" ]; then
echo "provisioning source error: no baseline in $STATE. Run --baseline before provisioning" >&2
exit 1
fi
MN_BASE=$(sed -n 's/^MN //p' "$STATE" | head -1)
SN_BASE=$(sed -n 's/^SN //p' "$STATE" | head -1)
CN_IP=$(node_address "$CN")
if [ -z "$CN_IP" ]; then
echo "provisioning source error: $CN has no address, so no log can be read for it" >&2
exit 1
fi
MN_COUNTS=$(count_local_requests "$CN_IP" "$MN_BASE" | read_counts)
SN_COUNTS=$(xdsh "$SN" -e "$SELF" --count "$CN_IP" "$SN_BASE" 2>&1 | read_counts)
set -- $MN_COUNTS
MN_STATE="${1:-none}" MN_REQ="${2:-0}" MN_NEW="${3:-0}" MN_LINES="${4:-0}"
set -- $SN_COUNTS
SN_STATE="${1:-none}" SN_REQ="${2:-0}" SN_NEW="${3:-0}" SN_LINES="${4:-0}"
echo "$SN served $CN $SN_REQ boot-payload request(s) since the baseline (log $SN_STATE, $SN_NEW new of $SN_LINES lines)"
echo "$MN served $CN $MN_REQ boot-payload request(s) since the baseline (log $MN_STATE, $MN_NEW new of $MN_LINES lines)"
RC=0
if [ "$SN_STATE" != ok ]; then
echo "provisioning source error: no httpd access log could be read on $SN" >&2
RC=1
fi
# An empty log makes "the management node served nothing" a property of the file, not a
# measurement. New lines are NOT required: the service node is provisioned before the baseline, so
# after it a correct hierarchical run leaves the management node's log unchanged.
if [ "$MN_STATE" != ok ] || [ "$MN_LINES" -eq 0 ]; then
echo "provisioning source error: no httpd access log with entries could be read on $MN" >&2
RC=1
fi
if [ "$RC" -eq 0 ] && [ "$SN_REQ" -eq 0 ]; then
echo "provisioning source error: $SN served $CN no boot payload, so it did not provision it" >&2
RC=1
fi
# Count requests, not bytes. A 304 or a HEAD carries no body, so the management node can answer
# for the compute node and still log 0 bytes.
if [ "$RC" -eq 0 ] && [ "$MN_REQ" -gt 0 ]; then
echo "provisioning source error: $MN answered $MN_REQ boot-payload request(s) for $CN, so this provision was flat" >&2
RC=1
fi
if [ "$RC" -eq 0 ]; then
echo "provisioning source ok: $SN served $CN and $MN served it nothing"
fi
exit "$RC"
@@ -1,7 +1,10 @@
start:SN_setup_case
os:Linux
os:rhels,sles
label:sn_diskful,provision
#stop:yes
# createrepo, the xcat/ prefix in the otherpkgs pkglist paths, and yum or zypper for
# perl-Sys-Virt are all rpm. A Debian management node has none of them, so os: above
# names the rpm families only and SN_setup_case_ub covers Ubuntu.
cmd:fdisk -l
cmd:df -T
cmd:XCAT_DATABASE=$$XCAT_DATABASE /opt/xcat/share/xcat/tools/autotest/testcase/installation/pre_deploy_sn __GETNODEATTR($$SN,os)__ __GETNODEATTR($$SN,arch)__
@@ -0,0 +1,118 @@
start:SN_setup_case_ub
os:ubuntu
label:sn_diskful,provision
#stop:yes
# The Debian counterpart of SN_setup_case. Same order, same database setup and the same node
# state at the end, so reg_linux_diskless_installation_hierarchy and
# reg_linux_diskfull_installation_hierarchy run after either one without a change.
#
# Four things differ, all of them package format. A Debian management node has no createrepo,
# so the staged repositories are indexed with dpkg-scanpackages. service.ubuntu.otherpkgs.pkglist
# names its directories as xcat-core and xcat-dep, without the xcat/ prefix the EL pkglists use,
# so otherpkgdir goes one level deeper. perl-Sys-Virt is libsys-virt-perl. SN_install_openbmc_py.sh
# is not called: it acts on rhels7 ppc64 only.
cmd:fdisk -l
cmd:df -T
cmd:XCAT_DATABASE=$$XCAT_DATABASE /opt/xcat/share/xcat/tools/autotest/testcase/installation/pre_deploy_sn __GETNODEATTR($$SN,os)__ __GETNODEATTR($$SN,arch)__
check:rc==0
cmd:chtab key=nameservers site.value="<xcatmaster>"
check:rc==0
cmd:makedns -n
check:rc==0
cmd:if [ -x /usr/bin/goconserver ]; then makegocons $$SN; else makeconservercf $$SN;fi
check:rc==0
cmd:sleep 20
cmd:if [[ "__GETNODEATTR($$SN,arch)__" =~ "ppc64" ]]; then getmacs -D $$SN -V; fi
check:rc==0
cmd:makedhcp -n
check:rc==0
cmd:makedhcp -a
check:rc==0
cmd:sleep 2
cmd:if [ -f /etc/kea/kea-dhcp4.conf ]; then grep $$SN /etc/kea/kea-dhcp4.conf; elif [ -f /var/lib/dhcp/dhcpd.leases ]; then grep $$SN /var/lib/dhcp/dhcpd.leases; fi
check:output=~$$SN
cmd:chdef -t node $$SN groups=service,all
check:rc==0
cmd:chdef -t group -o service profile=service primarynic=mac installnic=mac
check:rc==0
cmd:chdef -t group -o service setupnfs=1 setupdhcp=1 setuptftp=1 setupnameserver=1 setupconserver=2 setupntp=1
check:rc==0
cmd:chdef -t group -o service nfsserver=$$MN tftpserver=$$MN xcatmaster=$$MN monserver=$$MN
check:rc==0
cmd:chtab node=service postscripts.postscripts="servicenode"
check:rc==0
cmd:copycds $$ISO
check:rc==0
cmd:chdef -t site clustersite installloc="/install"
check:rc==0
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/installation/stage_sn_apt_repo.sh /install/post/otherpkgs/__GETNODEATTR($$SN,os)__/__GETNODEATTR($$SN,arch)__/xcat/xcat-core
check:rc==0
check:output=~apt index ok
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/installation/stage_sn_apt_repo.sh /install/post/otherpkgs/__GETNODEATTR($$SN,os)__/__GETNODEATTR($$SN,arch)__/xcat/xcat-dep __GETNODEATTR($$SN,os)__
check:rc==0
check:output=~apt index ok
# service.ubuntu.otherpkgs.pkglist names mariadb-client with no directory, so the otherpkgs
# postscript writes a source line for otherpkgdir itself. apt-get update then fails on it, and
# the postscript deletes the source line it had just written for xcat-core, because the
# apt-cache show that follows returns non-zero. Index this level too.
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/installation/stage_sn_apt_repo.sh /install/post/otherpkgs/__GETNODEATTR($$SN,os)__/__GETNODEATTR($$SN,arch)__/xcat
check:rc==0
check:output=~apt index ok
cmd:chdef -t osimage __GETNODEATTR($$SN,os)__-__GETNODEATTR($$SN,arch)__-install-service otherpkgdir=/install/post/otherpkgs/__GETNODEATTR($$SN,os)__/__GETNODEATTR($$SN,arch)__/xcat
check:rc==0
cmd:chdef -t osimage __GETNODEATTR($$SN,os)__-__GETNODEATTR($$SN,arch)__-install-service otherpkglist=/opt/xcat/share/xcat/install/ubuntu/service.ubuntu.otherpkgs.pkglist
check:rc==0
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/retry_install.sh $$SN __GETNODEATTR($$SN,os)__-__GETNODEATTR($$SN,arch)__-install-service
check:rc==0
check:output=~Provision node\(s\)\: $$SN
cmd:if [[ -f /var/lib/kea/kea-leases4.csv ]]; then cat /var/lib/kea/kea-leases4.csv; elif [[ -f /var/lib/dhcp/dhcpd.leases ]]; then cat /var/lib/dhcp/dhcpd.leases; fi
cmd:ping $$SN -c 3
check:rc==0
check:output=~64 bytes from $$SN
cmd:lsdef -l $$SN | grep status
check:rc==0
check:output=~booted
cmd:xdsh $$SN date
check:rc==0
check:output=~\d\d:\d\d:\d\d
cmd:xdsh $$SN "ps -ef |grep xcatd"
check:rc==0
check:output=~xcatd:
# Ubuntu's /etc/bash.bashrc returns before /etc/profile.d on a non-interactive shell, so the
# xCAT commands are not on the PATH xdsh gets. The EL shell reads profile.d there and the EL
# case does not need this.
cmd:xdsh $$SN ". /etc/profile.d/xcat.sh; lsdef"
check:rc==0
check:output=~$$SN: $$SN
cmd:xdsh $$SN ". /etc/profile.d/xcat.sh; tabdump site"
check:rc==0
check:output=~tftpdir
cmd:xdsh $$SN "mount"
check:rc==0
check:output=~/install on /install
cmd:xdsh $$SN "mount"
check:rc==0
check:output=~/tftpboot on /tftpboot
cmd:xdsh $$SN cat /opt/xcat/xcatinfo
check:rc==0
check:output=~NODE=$$SN
check:output=~IMAGENAME=__GETNODEATTR($$SN,os)__-__GETNODEATTR($$SN,arch)__-install-service
cmd:xdsh $$SN "cat /var/log/xcat/xcat.log"
cmd:xdsh $$SN "DEBIAN_FRONTEND=noninteractive apt-get install -y libsys-virt-perl"
check:rc==0
cmd:makentp -a
check:rc==0
cmd:if [ -x /usr/bin/goconserver ]; then makegocons -d $$SN; else makeconservercf -d $$SN;fi
end
@@ -47,6 +47,10 @@ check:rc==0
cmd:if [[ -f /test.synclist ]] ;then mv -f /test.synclist /test.synclist.bak;fi;
cmd:echo "/test.synclist -> /test.synclist" > /test.synclist;chdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-install-compute synclists=/test.synclist
check:rc==0
# The logs carry requests from earlier runs, so record where both of them end
# before this provision. The check below reads only what comes after.
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/check_provisioning_source.sh --baseline $$CN $$SN
check:rc==0
cmd:nodeset $$CN osimage=__GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-install-compute
check:rc==0
cmd:updatenode $$CN -f
@@ -74,6 +78,13 @@ check:rc==0
check:output=~NODE=$$CN
check:output=~IMAGENAME=__GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-install-compute
check:output=~SERVICEGROUP=$$SN
# SERVICEGROUP above is a copy of the noderes.servicenode value this case wrote, so it says
# what xCAT set and not which server sent the boot payload. Both dhcpd instances answer for
# $$CN, so the management node can win the xNBA exchange and serve it. The httpd access logs
# are what separates the two topologies.
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/check_provisioning_source.sh $$CN $$SN
check:rc==0
check:output=~provisioning source ok
cmd:xdsh $$CN "cat /var/log/xcat/xcat.log"
cmd:xdsh $$CN "cat /test.synclist"
check:rc==0
@@ -53,6 +53,10 @@ check:rc==0
cmd:packimage __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute
check:rc==0
# The logs carry requests from earlier runs, so record where both of them end
# before this provision. The check below reads only what comes after.
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/check_provisioning_source.sh --baseline $$CN $$SN
check:rc==0
cmd:nodeset $$CN osimage=__GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute
check:rc==0
@@ -82,6 +86,13 @@ check:output=~NODE=$$CN
check:output=~IMAGENAME='__GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute'
check:output=~IMAGEUUID='\w+-\w+-\w+-\w+-\w+'
check:output=~SERVICEGROUP=$$SN
# SERVICEGROUP above is a copy of the noderes.servicenode value this case wrote, so it says
# what xCAT set and not which server sent the boot payload. Both dhcpd instances answer for
# $$CN, so the management node can win the xNBA exchange and serve it. The httpd access logs
# are what separates the two topologies.
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/check_provisioning_source.sh $$CN $$SN
check:rc==0
check:output=~provisioning source ok
cmd:xdsh $$CN "cat /var/log/xcat/xcat.log"
cmd:rootimgdir=`lsdef -t osimage __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute|grep rootimgdir|awk -F'=' '{print $2}'`; if [ -d $rootimgdir.regbak ]; then rm -rf $rootimgdir; mv $rootimgdir.regbak $rootimgdir; fi
check:rc==0
@@ -171,6 +182,10 @@ cmd:packimage -m squashfs __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-ne
check:rc==0
check:output=~archive method:squashfs
# The logs carry requests from earlier runs, so record where both of them end
# before this provision. The check below reads only what comes after.
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/check_provisioning_source.sh --baseline $$CN $$SN
check:rc==0
cmd:nodeset $$CN osimage=__GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute
check:rc==0
@@ -200,6 +215,13 @@ check:output=~NODE=$$CN
check:output=~IMAGENAME='__GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute'
check:output=~IMAGEUUID='\w+-\w+-\w+-\w+-\w+'
check:output=~SERVICEGROUP=$$SN
# SERVICEGROUP above is a copy of the noderes.servicenode value this case wrote, so it says
# what xCAT set and not which server sent the boot payload. Both dhcpd instances answer for
# $$CN, so the management node can win the xNBA exchange and serve it. The httpd access logs
# are what separates the two topologies.
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/check_provisioning_source.sh $$CN $$SN
check:rc==0
check:output=~provisioning source ok
cmd:xdsh $$CN "cat /var/log/xcat/xcat.log"
cmd:rootimgdir=`lsdef -t osimage __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-netboot-compute|grep rootimgdir|awk -F'=' '{print $2}'`; if [ -d $rootimgdir.regbak ]; then rm -rf $rootimgdir; mv $rootimgdir.regbak $rootimgdir; fi
check:rc==0
@@ -0,0 +1,63 @@
#!/bin/sh
#
# stage_sn_apt_repo.sh <repository directory> [<os version>]
#
# Write a flat apt index at <repository directory>, so the otherpkgs postscript on a Debian
# service node can install from it.
#
# The postscript builds one source line per directory named in the osimage otherpkglist, in the
# form "deb <type>://<otherpkgdir>/<directory> ./". That trailing "./" is apt's flat layout: it
# reads <directory>/Packages and takes each Filename relative to it. A reprepro tree with
# dists/ and pool/ has no such file, and neither has a directory of loose debs.
#
# <os version> selects one subdirectory when the tree holds one per release, which is how
# xcat-dep stages its debs. Without it, or with no subdirectory whose name starts the os
# version, the whole tree is indexed.
#
# Run this on the management node. The EL cases call createrepo here, which no Debian host has.
set -u
DIR="${1:-}"
OSVER="${2:-}"
if [ -z "$DIR" ]; then
echo "usage: $0 <repository directory> [<os version>]" >&2
exit 2
fi
if [ ! -d "$DIR" ]; then
echo "apt index error: $DIR is not a directory" >&2
exit 1
fi
scan=.
for sub in "$DIR"/*/; do
[ -d "$sub" ] || continue
name=$(basename "$sub")
case "$OSVER" in
"$name"*) scan=$name ;;
esac
done
cd "$DIR" || exit 1
err=$(mktemp) || exit 1
if ! dpkg-scanpackages -m "$scan" > Packages.new 2>"$err"; then
echo "apt index error: dpkg-scanpackages failed under $DIR/$scan" >&2
cat "$err" >&2
rm -f Packages.new "$err"
exit 1
fi
rm -f "$err"
# An empty index is the failure this guard exists for: apt reports no candidate, and the
# service node install then fails somewhere else entirely.
count=$(grep -c '^Package: ' Packages.new || true)
if [ "$count" -eq 0 ]; then
echo "apt index error: no deb package under $DIR/$scan" >&2
rm -f Packages.new
exit 1
fi
mv -f Packages.new Packages
gzip -9cf Packages > Packages.gz
echo "apt index ok: $count package(s) indexed at $DIR from $scan"
@@ -0,0 +1,291 @@
#!/usr/bin/env bats
#
# Drive check_provisioning_source.sh, which is what tells a hierarchical provision from a flat
# one. The management node, the service node and the compute node share one subnet and both
# dhcpd instances answer for the compute node, so the management node can win the xNBA exchange
# and serve the boot payload itself. The httpd access logs are the only record of that.
#
# The check takes a baseline of both logs before provisioning and reads only what came after, so
# these tests write the old lines, take the baseline, then append the lines of the run.
#
# lsdef, xdsh and hostname are stubbed. XCAT_HTTPD_ACCESS_LOG is the management node's log.
load 'helpers/shell_source'
CN=cn01
SN=sn01
CN_IP=192.0.2.10
setup()
{
SCRIPT="$(require_repo_file 'xCAT-test/autotest/testcase/commoncmd/check_provisioning_source.sh')"
BIN="${BATS_TEST_TMPDIR}/bin"
MN_LOG="${BATS_TEST_TMPDIR}/mn-access_log"
SN_LOG="${BATS_TEST_TMPDIR}/sn-access_log"
STATE="${BATS_TEST_TMPDIR}/baseline"
mkdir -p "$BIN"
: >"$MN_LOG"
: >"$SN_LOG"
printf '#!/bin/sh\nprintf "Object name: %s\\n ip=%s\\n" "$3" "%s"\n' "%s" "%s" "$CN_IP" >"$BIN/lsdef"
printf '#!/bin/sh\necho mn01\n' >"$BIN/hostname"
# A test that reaches the network measures the lab, not this script.
printf '#!/bin/sh\necho "unexpected getent $*" >&2\nexit 1\n' >"$BIN/getent"
# xdsh -e copies the script to the service node and runs it there. Run it here instead, with
# the service node log in place of the management node one, and prefix the node name as xdsh
# does.
printf '#!/bin/sh\nnode=$1\nshift\n[ "$1" = "-e" ] && shift\nscript=$1\nshift\nXCAT_HTTPD_ACCESS_LOG=%s "$script" "$@" | sed "s/^/$node: /"\n' \
"$SN_LOG" >"$BIN/xdsh"
chmod 0755 "$BIN"/*
export PATH="$BIN:$PATH"
}
# One access-log line in the combined format: $1 client, $2 bytes, $3 path.
access_line()
{
printf '%s - - [01/Jan/2026:00:00:00 +0000] "GET %s HTTP/1.1" 200 %s "-" "iPXE"\n' "$1" "$3" "$2"
}
take_baseline()
{
env XCAT_HTTPD_ACCESS_LOG="$MN_LOG" XCAT_PROV_SOURCE_STATE="$STATE" \
"$SCRIPT" --baseline "$CN" "$SN" >/dev/null
}
run_check()
{
run env XCAT_HTTPD_ACCESS_LOG="$MN_LOG" XCAT_PROV_SOURCE_STATE="$STATE" "$SCRIPT" "$CN" "$SN"
}
# A log that has to hold something at baseline time, so the baseline is not trivially zero.
seed_logs()
{
access_line 192.0.2.30 512 /install/rh/x86_64/ >"$SN_LOG"
access_line 192.0.2.31 512 /install/rh/x86_64/ >"$MN_LOG"
}
@test "a service node that served the compute node and a silent management node pass" {
seed_logs
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -eq 0 ]
[[ "$output" == *"provisioning source ok"* ]]
}
@test "a management node request from BEFORE the baseline does not fail this run" {
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >"$MN_LOG"
access_line 192.0.2.30 512 /install/rh/x86_64/ >"$SN_LOG"
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -eq 0 ]
[[ "$output" == *"provisioning source ok"* ]]
}
@test "a service node request from BEFORE the baseline does not satisfy the check" {
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >"$SN_LOG"
access_line 192.0.2.31 512 /install/rh/x86_64/ >"$MN_LOG"
take_baseline
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"no boot payload"* ]]
}
@test "a request that carries no boot payload does not satisfy the check" {
seed_logs
take_baseline
printf '%s - - [01/Jan/2026:00:00:00 +0000] "GET /favicon.ico HTTP/1.1" 404 209 "-" "iPXE"\n' \
"$CN_IP" >>"$SN_LOG"
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"no boot payload"* ]]
}
@test "a management node request that carries no boot payload does not read as a flat provision" {
seed_logs
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
printf '%s - - [01/Jan/2026:00:00:00 +0000] "GET /favicon.ico HTTP/1.1" 404 209 "-" "iPXE"\n' \
"$CN_IP" >>"$MN_LOG"
run_check
[ "$status" -eq 0 ]
[[ "$output" == *"provisioning source ok"* ]]
}
@test "the check refuses to answer with no baseline" {
seed_logs
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"no baseline"* ]]
[[ "$output" != *"provisioning source ok"* ]]
}
@test "a log rotated below its baseline is read from its first line" {
for i in 1 2 3 4 5; do access_line 192.0.2.30 512 /install/rh/x86_64/; done >"$SN_LOG"
access_line 192.0.2.31 512 /install/rh/x86_64/ >"$MN_LOG"
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >"$SN_LOG"
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -eq 0 ]
[[ "$output" == *"provisioning source ok"* ]]
}
@test "the management node answering for the compute node fails the check" {
seed_logs
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
{
access_line 192.0.2.21 4096 /install/rh/x86_64/
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel
} >>"$MN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"this provision was flat"* ]]
[[ "$output" != *"provisioning source ok"* ]]
}
@test "a management node answering only a bodyless request still fails the check" {
seed_logs
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
{
access_line 192.0.2.21 4096 /install/rh/x86_64/
printf '%s - - [01/Jan/2026:00:00:00 +0000] "HEAD %s HTTP/1.1" 304 - "-" "iPXE"\n' \
"$CN_IP" /tftpboot/xcat/genesis.kernel
} >>"$MN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"this provision was flat"* ]]
}
@test "a service node that served the compute node nothing fails the check" {
seed_logs
take_baseline
access_line 192.0.2.22 4096 /install/rh/x86_64/ >>"$SN_LOG"
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"no boot payload"* ]]
}
@test "an unreadable service node log fails the check instead of passing it" {
seed_logs
take_baseline
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
printf '#!/bin/sh\nexit 1\n' >"$BIN/xdsh"
chmod 0755 "$BIN/xdsh"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"no httpd access log could be read on $SN"* ]]
}
# The management node provisions the service node BEFORE this baseline, so it can serve nothing
# after it. That is the hierarchical result, and it was read as a broken log.
@test "a management node that serves nothing after the baseline passes" {
seed_logs
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
run_check
[ "$status" -eq 0 ]
[[ "$output" == *"provisioning source ok"* ]]
}
@test "a management node log with no entry at all fails the check instead of reading as silence" {
: >"$MN_LOG"
access_line 192.0.2.30 512 /install/rh/x86_64/ >"$SN_LOG"
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"no httpd access log with entries could be read on mn01"* ]]
}
# The compute node fetches the root image with wget from a URL that carries a double slash, so
# the path in the log reads //install/... and the payload filter did not match it.
@test "a boot payload requested under a doubled slash counts" {
seed_logs
take_baseline
access_line "$CN_IP" 978729607 //install/netboot/ubuntu/x86_64/compute/rootimg.cpio.gz >>"$SN_LOG"
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -eq 0 ]
[[ "$output" == *"provisioning source ok"* ]]
}
@test "a doubled-slash boot payload from the management node reads as a flat provision" {
seed_logs
take_baseline
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
{
access_line 192.0.2.21 4096 /install/rh/x86_64/
access_line "$CN_IP" 978729607 //install/netboot/ubuntu/x86_64/compute/rootimg.cpio.gz
} >>"$MN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"this provision was flat"* ]]
}
@test "the Debian per-vhost log format is read as the client address" {
seed_logs
take_baseline
printf 'xcat:80 %s - - [01/Jan/2026:00:00:00 +0000] "GET %s HTTP/1.1" 200 12345678\n' \
"$CN_IP" /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
printf 'xcat:80 %s - - [01/Jan/2026:00:00:00 +0000] "GET %s HTTP/1.1" 200 4096\n' \
192.0.2.21 /install/ubuntu/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -eq 0 ]
[[ "$output" == *"provisioning source ok"* ]]
}
@test "a compute node with no address fails the check" {
seed_logs
take_baseline
printf '#!/bin/sh\nexit 1\n' >"$BIN/lsdef"
printf '#!/bin/sh\nexit 2\n' >"$BIN/getent"
chmod 0755 "$BIN/lsdef" "$BIN/getent"
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >>"$SN_LOG"
access_line 192.0.2.21 4096 /install/rh/x86_64/ >>"$MN_LOG"
run_check
[ "$status" -ne 0 ]
[[ "$output" == *"has no address"* ]]
}
@test "a baseline that cannot reach the service node names the service node" {
seed_logs
printf '#!/bin/sh\nexit 1\n' >"$BIN/xdsh"
chmod 0755 "$BIN/xdsh"
run env XCAT_HTTPD_ACCESS_LOG="$MN_LOG" XCAT_PROV_SOURCE_STATE="$STATE" \
"$SCRIPT" --baseline "$CN" "$SN"
[ "$status" -ne 0 ]
[[ "$output" == *"no access log to baseline on $SN"* ]]
# The message used to append the management node's baseline spec after the node name, so a
# substring match on the name alone passes either way.
[[ "$output" != *"$MN_LOG"* ]]
[ ! -e "$STATE" ]
}
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env bats
#
# makenamed.conf builds the forwarder list of a service node's named from /etc/resolv.conf.
#
# On a host managed by systemd-resolved that file holds the 127.0.0.53 stub, and the stub points
# back at this named for the link. forward-only to it is a loop: the service node answers
# nothing, and every compute node behind it fails to resolve. Measured on xcat22-sn, where
# "dig @<sn> xcat22-sn.xcat22.lab" returned nothing while the same query to the management node
# answered. The EL service node has the real upstream in /etc/resolv.conf, which is why it works.
#
# systemd-resolved writes the real servers to /run/systemd/resolve/resolv.conf.
load 'helpers/shell_source'
setup()
{
SCRIPT="$(require_repo_file 'xCAT-server/sbin/makenamed.conf')"
RESOLV="${BATS_TEST_TMPDIR}/resolv.conf"
UPLINK="${BATS_TEST_TMPDIR}/uplink.conf"
# MAKENAMED_LIB stops the script before it writes anything, so only the routine is loaded.
MAKENAMED_LIB=1 . "$SCRIPT"
export -f forwarder_addresses _fa_usable
}
@test "a real nameserver is a forwarder" {
printf 'nameserver 192.168.222.1\nsearch xcat22.lab\n' >"$RESOLV"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$status" -eq 0 ]
[ "$output" = "192.168.222.1" ]
}
@test "the systemd-resolved stub is not a forwarder, and the uplink answers instead" {
printf 'nameserver 127.0.0.53\noptions edns0 trust-ad\n' >"$RESOLV"
printf 'nameserver 192.168.222.1\nsearch xcat22.lab\n' >"$UPLINK"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$status" -eq 0 ]
[ "$output" = "192.168.222.1" ]
}
@test "any loopback address is rejected, not only the stub" {
printf 'nameserver 127.0.0.1\nnameserver ::1\n' >"$RESOLV"
printf 'nameserver 10.0.0.1\n' >"$UPLINK"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$output" = "10.0.0.1" ]
}
@test "several real nameservers are all forwarders, in order" {
printf 'nameserver 192.168.222.1\nnameserver 10.0.0.2\n' >"$RESOLV"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$output" = "192.168.222.1
10.0.0.2" ]
}
@test "a real nameserver wins over the uplink, which is only the fallback" {
printf 'nameserver 192.168.222.1\n' >"$RESOLV"
printf 'nameserver 10.9.9.9\n' >"$UPLINK"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$output" = "192.168.222.1" ]
}
@test "no usable address anywhere prints nothing rather than a blank forwarder" {
printf 'nameserver 127.0.0.53\n' >"$RESOLV"
: >"$UPLINK"
# Count the lines. A blank line reaches named.conf as an empty forwarder entry, which bind
# rejects as a syntax error, and $output cannot tell one from no output at all.
run bash -c "forwarder_addresses '$RESOLV' '$UPLINK' | wc -l"
[ "$status" -eq 0 ]
[ "$output" = "0" ]
}
@test "an absent uplink file is not an error" {
printf 'nameserver 127.0.0.53\n' >"$RESOLV"
run forwarder_addresses "$RESOLV" "${BATS_TEST_TMPDIR}/absent"
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
# named runs as "bind" on Debian and writes its managed-keys database into the configured
# directory. /var/named is created root-owned, so the write fails, DNSSEC initialisation fails
# with it, and the server answers NXDOMAIN to every query -- including forwarded ones, which is
# how a correct forwarder list still resolved nothing on xcat22-sn.
@test "Ubuntu names the directory its named can write" {
printf 'DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=24.04\n' >"${BATS_TEST_TMPDIR}/lsb"
run named_directory "${BATS_TEST_TMPDIR}/lsb" "${BATS_TEST_TMPDIR}/absent-os" "${BATS_TEST_TMPDIR}/absent-suse"
[ "$output" = "/var/cache/bind" ]
}
@test "SLES keeps its own directory" {
: >"${BATS_TEST_TMPDIR}/lsb"
printf 'ID="sles"\n' >"${BATS_TEST_TMPDIR}/os"
run named_directory "${BATS_TEST_TMPDIR}/lsb" "${BATS_TEST_TMPDIR}/os" "${BATS_TEST_TMPDIR}/absent-suse"
[ "$output" = "/var/lib/named" ]
}
@test "EL keeps /var/named" {
: >"${BATS_TEST_TMPDIR}/lsb"
printf 'ID="almalinux"\n' >"${BATS_TEST_TMPDIR}/os"
run named_directory "${BATS_TEST_TMPDIR}/lsb" "${BATS_TEST_TMPDIR}/os" "${BATS_TEST_TMPDIR}/absent-suse"
[ "$output" = "/var/named" ]
}
@test "an absent lsb-release is not Ubuntu" {
printf 'ID="almalinux"\n' >"${BATS_TEST_TMPDIR}/os"
run named_directory "${BATS_TEST_TMPDIR}/absent-lsb" "${BATS_TEST_TMPDIR}/os" "${BATS_TEST_TMPDIR}/absent-suse"
[ "$output" = "/var/named" ]
}
@@ -0,0 +1,59 @@
#!/usr/bin/env bats
#
# The otherpkgs postscript stages an apt repository that carries a Packages file and no Release.
# apt refuses an unsigned repository outright -- "does not have a Release file" -- so the
# postscript's apt-cache show finds nothing, it deletes the source line it has just written, and
# the package is never installed. On a service node that package is xcatsn.
#
# This drives apt against a real flat repository in a scratch tree, with both spellings of the
# source line, and then holds the postscript to the one apt accepts.
load 'helpers/shell_source'
setup()
{
command -v apt-get >/dev/null 2>&1 || skip 'apt-get is required'
command -v dpkg-scanpackages >/dev/null 2>&1 || skip 'dpkg-scanpackages is required'
REPO="${BATS_TEST_TMPDIR}/repo"
ROOT="${BATS_TEST_TMPDIR}/aptroot"
mkdir -p "$REPO" "$ROOT/etc/apt/sources.list.d" "$ROOT/var/lib/apt/lists/partial" \
"$ROOT/var/lib/dpkg" "$ROOT/var/cache/apt/archives/partial"
: >"$ROOT/var/lib/dpkg/status"
# A minimal binary package, so the index describes something real.
local build="${BATS_TEST_TMPDIR}/pkg/xcatsn"
mkdir -p "$build/DEBIAN"
printf 'Package: xcatsn\nVersion: 2.20.0\nArchitecture: all\nMaintainer: t <t@example.invalid>\nDescription: probe\n' \
>"$build/DEBIAN/control"
dpkg-deb --build -Znone "$build" "$REPO/xcatsn_2.20.0_all.deb" >/dev/null
( cd "$REPO" && dpkg-scanpackages -m . >Packages 2>/dev/null )
}
# Resolve xcatsn through apt with the given source line. Sets OUT and STATUS.
resolve()
{
printf '%s\n' "$1" >"$ROOT/etc/apt/sources.list.d/probe.list"
apt-get -o "Dir=$ROOT" -o "Dir::State::status=$ROOT/var/lib/dpkg/status" \
-o "Dir::Etc::sourcelist=$ROOT/etc/apt/sources.list.d/probe.list" \
-o Dir::Etc::sourceparts=/dev/null -o APT::Get::List-Cleanup=0 \
update >/dev/null 2>&1 || true # an untrusted source makes update itself exit 100,
# and that refusal is what this test measures. Under
# bats' set -e an unguarded failure here aborts the
# function before apt-cache runs, so the test could
# only ever pass where apt-get is absent and it skips.
OUT="$(apt-cache -o "Dir=$ROOT" -o "Dir::State::status=$ROOT/var/lib/dpkg/status" \
show xcatsn 2>&1)" && STATUS=0 || STATUS=$?
}
@test "apt refuses the source line without trusted=yes, so the package cannot be found" {
resolve "deb file://$REPO ./"
[ "$STATUS" -ne 0 ]
[[ "$OUT" != *"Package: xcatsn"* ]]
}
@test "apt resolves the package when the source line is trusted" {
resolve "deb [trusted=yes] file://$REPO ./"
[ "$STATUS" -eq 0 ]
[[ "$OUT" == *"Package: xcatsn"* ]]
}
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env bats
#
# Drive stage_sn_apt_repo.sh, which writes the flat apt index a Debian service node installs
# from. The otherpkgs postscript writes "deb <type>://<otherpkgdir>/<directory> ./", and that
# trailing "./" makes apt read <directory>/Packages and resolve each Filename against it.
#
# dpkg-scanpackages is stubbed, so the test measures the directory selection and the guards and
# not dpkg-dev.
load 'helpers/shell_source'
setup()
{
SCRIPT="$(require_repo_file 'xCAT-test/autotest/testcase/installation/stage_sn_apt_repo.sh')"
REPO="${BATS_TEST_TMPDIR}/repo"
BIN="${BATS_TEST_TMPDIR}/bin"
RECORD="${BATS_TEST_TMPDIR}/scanned"
mkdir -p "$REPO" "$BIN"
# Record the directory it was asked to scan, and emit one stanza per deb found under it.
cat >"$BIN/dpkg-scanpackages" <<STUB
#!/bin/sh
[ "\$1" = "-m" ] && shift
printf '%s\n' "\$1" >>"$RECORD"
find "\$1" -name '*.deb' | while read -r d; do
printf 'Package: %s\nFilename: %s\n\n' "\$(basename "\$d" .deb)" "\$d"
done
exit 0
STUB
chmod 0755 "$BIN/dpkg-scanpackages"
export PATH="$BIN:$PATH"
}
deb()
{
mkdir -p "$(dirname "$REPO/$1")"
: >"$REPO/$1"
}
scanned()
{
read_file_or_empty "$RECORD"
}
@test "a reprepro tree is indexed whole, so the pool is reachable from the flat index" {
deb pool/main/x/xcat/xcatsn_2.20.0_amd64.deb
deb pool/main/x/xcat/xcat-client_2.20.0_all.deb
run "$SCRIPT" "$REPO"
[ "$status" -eq 0 ]
[ "$(scanned)" = "." ]
[[ "$output" == *"2 package(s) indexed"* ]]
grep -q '^Package: xcatsn_2.20.0_amd64$' "$REPO/Packages"
[ -s "$REPO/Packages.gz" ]
}
@test "a tree with one directory per release is indexed from the directory the os version names" {
deb ubuntu22.04/conserver-xcat_8.2.1-1_amd64.deb
deb ubuntu24.04/conserver-xcat_8.2.1-1_amd64.deb
deb ubuntu26.04/conserver-xcat_8.2.1-1_amd64.deb
run "$SCRIPT" "$REPO" ubuntu24.04.4
[ "$status" -eq 0 ]
[ "$(scanned)" = "ubuntu24.04" ]
[[ "$output" == *"1 package(s) indexed"* ]]
}
@test "an os version no directory matches falls back to the whole tree" {
deb ubuntu22.04/conserver-xcat_8.2.1-1_amd64.deb
deb ubuntu26.04/conserver-xcat_8.2.1-1_amd64.deb
run "$SCRIPT" "$REPO" ubuntu24.04.4
[ "$status" -eq 0 ]
[ "$(scanned)" = "." ]
}
@test "a tree with no deb fails instead of writing an empty index" {
mkdir -p "$REPO/pool"
run "$SCRIPT" "$REPO"
[ "$status" -ne 0 ]
[[ "$output" == *"no deb package under"* ]]
[ ! -e "$REPO/Packages" ]
}
@test "a failing dpkg-scanpackages fails the step and leaves no index" {
deb pool/main/x/xcat/xcatsn_2.20.0_amd64.deb
printf '#!/bin/sh\necho "cannot read" >&2\nexit 2\n' >"$BIN/dpkg-scanpackages"
chmod 0755 "$BIN/dpkg-scanpackages"
run "$SCRIPT" "$REPO"
[ "$status" -ne 0 ]
[[ "$output" == *"dpkg-scanpackages failed"* ]]
[ ! -e "$REPO/Packages" ]
}
@test "a directory that does not exist fails" {
run "$SCRIPT" "${BATS_TEST_TMPDIR}/absent"
[ "$status" -ne 0 ]
[[ "$output" == *"is not a directory"* ]]
}
@test "no argument fails" {
run "$SCRIPT"
[ "$status" -ne 0 ]
[[ "$output" == *"usage:"* ]]
}
+21
View File
@@ -0,0 +1,21 @@
# The fast oracle of the Ubuntu 24.04 service node cell.
#
# xCAT-test/quick.sh -f xCAT-test/quick-servicenode-ub.txt
#
# RUN IT ON xcat-master-ub. otherpkgs_apt_source_trusted skips its two resolving assertions where
# apt-get is absent, so on an EL host this list silently proves less and the trusted=yes fix
# reads as unnecessary.
#
# KNOWN HOLES, where a wrong drop comes from. A first run dropped four real fixes:
# - the apt source line the postscript writes. otherpkgs_apt_source_trusted measures APT, not
# what xCAT wrote. STILL OPEN.
# - the NFS export casper needs. CLOSED by service_node_artifacts_ubuntu.t.
# - the xdsh PATH fix. STILL OPEN.
# - the named reload. CLOSED by aasn_named_service_action.t.
xCAT-test/unit/service_node_artifacts_ubuntu.t
xCAT-test/unit/aasn_named_service_action.t
xCAT-test/unit/ubuntu_service_subiquity_template.t
xCAT-test/bats/makenamed_forwarders.bats
xCAT-test/bats/otherpkgs_apt_source_trusted.bats
xCAT-test/bats/stage_sn_apt_repo.bats
+32
View File
@@ -0,0 +1,32 @@
# The fast oracle of the service node change set: one test per fix, each written with its fix.
#
# Run it with: xCAT-test/quick.sh -f xCAT-test/quick-servicenode.txt
#
# These are the tests that failed before their fix landed. Together they stand in for the
# end-to-end service node suite, which provisions a management node, a service node and a
# compute node on two families and takes 45 to 90 minutes a run.
#
# What each one covers:
# check_provisioning_source the compute node was served by the service node, not the MN
# makenamed_forwarders a Debian service node does not forward DNS to its own stub
# otherpkgs_apt_source_trusted the staged apt source apt will accept
# stage_sn_apt_repo the Debian repository index, where createrepo does not exist
# servicenode_builder_repo the builder repository per EL release: crb, powertools, codeready
# svrutils_nfs_export_line a service node that mounts /install still exports it
# ubuntu_service_subiquity_template the service profile resolves a Subiquity template
#
# A SKIP is not a PASS. otherpkgs_apt_source_trusted skips its two resolving assertions where
# apt-get is absent, so on an EL builder that file proves less than it does on a Debian one. Run
# this list on the builder of the family the candidate touches, or read the skips.
#
# NOT covered, and known: the xdsh PATH fix, and the named working directory and reload fixes.
# A candidate that removes those passes this list and fails the end-to-end suite. Fill these in
# before trusting the list for a minimization.
xCAT-test/bats/check_provisioning_source.bats
xCAT-test/bats/makenamed_forwarders.bats
xCAT-test/bats/otherpkgs_apt_source_trusted.bats
xCAT-test/bats/stage_sn_apt_repo.bats
xCAT-test/unit/servicenode_builder_repo.t
xCAT-test/unit/svrutils_nfs_export_line.t
xCAT-test/unit/ubuntu_service_subiquity_template.t
+79
View File
@@ -0,0 +1,79 @@
#!/bin/bash
#
# Run a fast subset of the unit tests: the Perl .t files with prove and the shell .bats files
# with bats, in one command, from the source tree. No build, no installed xCAT, no cluster.
#
# WHY THIS EXISTS. Delta debugging a change set runs the suite once per candidate. The
# end-to-end suite provisions machines and costs 45 to 90 minutes a run, so the rounds run on a
# fast oracle instead, and the slow suite runs once on the set that settles. This is that fast
# oracle, and it is useful on its own: it is the same two commands CI runs, without the wait.
#
# xCAT-test/quick.sh every unit test and every bats file
# xCAT-test/quick.sh path/a.t path/b.bats only those, in the order given
# xCAT-test/quick.sh -f list.txt the files named in list.txt, one per line
#
# Exit status is 0 only when every file passed. A missing file is an error, not a skip: a fast
# oracle that silently runs nothing reports PASS for a tree that contains none of the tests.
set -u -o pipefail
usage() { sed -n '3,20p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; }
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
files=()
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage 0 ;;
-f|--from) shift; [ -r "${1:-}" ] || { echo "quick: cannot read list '${1:-}'" >&2; exit 2; }
while read -r line; do
case "$line" in ''|\#*) continue ;; esac
files+=("$line")
done < "$1" ;;
-*) echo "quick: unknown option '$1'" >&2; usage 2 ;;
*) files+=("$1") ;;
esac
shift
done
cd "$root" || exit 2
if [ "${#files[@]}" -eq 0 ]; then
echo "[quick] whole fast suite: prove -r xCAT-test/unit, then bats -r xCAT-test/bats"
rc=0
prove -r xCAT-test/unit || rc=1
bats -r xCAT-test/bats || rc=1
exit $rc
fi
# A named file that is absent means the caller asked for a test this tree does not have. Say so
# and fail: reporting PASS here is how a minimization drops the commit that adds a test.
missing=()
for f in "${files[@]}"; do [ -e "$f" ] || missing+=("$f"); done
if [ "${#missing[@]}" -gt 0 ]; then
printf '[quick] MISSING from this tree, refusing to report a result:\n' >&2
printf ' %s\n' "${missing[@]}" >&2
exit 2
fi
perl_tests=(); bats_tests=()
for f in "${files[@]}"; do
case "$f" in
*.t) perl_tests+=("$f") ;;
*.bats) bats_tests+=("$f") ;;
*) echo "quick: '$f' is neither a .t nor a .bats" >&2; exit 2 ;;
esac
done
rc=0
if [ "${#perl_tests[@]}" -gt 0 ]; then
echo "[quick] prove ${#perl_tests[@]} Perl test(s)"
prove "${perl_tests[@]}" || rc=1
fi
if [ "${#bats_tests[@]}" -gt 0 ]; then
echo "[quick] bats ${#bats_tests[@]} shell test(s)"
bats "${bats_tests[@]}" || rc=1
fi
[ $rc -eq 0 ] && echo "[quick] PASS (${#files[@]} file(s))" || echo "[quick] FAIL"
exit $rc
@@ -0,0 +1,33 @@
#!/usr/bin/env perl
# setup_DNS writes named.conf through makenamed.conf and then brings the daemon up. It used to
# START the service. On Debian the package already runs named, so a start is a no-op: the daemon
# keeps serving the configuration it read at install time, the zone xCAT has just written is
# never loaded, and a compute node cannot resolve its service node.
#
# A minimization of this change set dropped that fix, because no fast test could see it. The
# decision lives in xCAT::SvrUtils, which a test can load; AAsn.pm cannot be loaded from a source
# tree at all, since it pulls in xCAT::Table and the rest of the server.
use strict;
use warnings;
use FindBin;
use Test::More;
use lib "$FindBin::Bin/../../perl-xCAT";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
use xCAT::SvrUtils;
can_ok('xCAT::SvrUtils', 'named_service_action') or do { done_testing(); exit 1 };
is(xCAT::SvrUtils::named_service_action('linux'), 'restart',
'Linux restarts named, so the configuration just written is the one it serves');
is(xCAT::SvrUtils::named_service_action('aix'), 'start',
'AIX keeps the start it has always used');
is(xCAT::SvrUtils::named_service_action(''), '',
'an unknown platform does nothing rather than guessing an action');
is(xCAT::SvrUtils::named_service_action(undef), '',
'an undefined platform does nothing');
done_testing();
@@ -0,0 +1,57 @@
#!/usr/bin/env perl
# The Ubuntu counterpart of service_node_artifacts_el.t: render the artifacts an Ubuntu service
# node needs and assert each, so the fast oracle fails for any wrong artifact rather than only
# for the defects someone has already hit.
#
# It exists because the first Ubuntu oracle dropped two real fixes. The apt test measured apt's
# behaviour rather than the line the postscript writes, and nothing asserted the export line at
# all, although casper mounts the install tree over NFS from the service node.
use strict;
use warnings;
use FindBin;
use Test::More;
use lib "$FindBin::Bin/../../perl-xCAT";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
# get_file_name takes genos LAST, and update_tables_with_templates passes 'subiquity' for every
# Ubuntu 20.04 and later osimage. Passing the os version there resolves the preseed instead, which
# is the mistake this test would otherwise make about its own subject.
my %SN = (osver => 'ubuntu24.04', arch => 'x86_64', profile => 'service', genos => 'subiquity');
my $SHARE = "$FindBin::Bin/../../xCAT-server/share/xcat/install";
my $have = eval { require xCAT::SvrUtils; 1 } ? 1 : 0;
ok($have, 'xCAT::SvrUtils loads') or do { done_testing(); exit 1 };
# 1. The installer. Ubuntu 20.04 and later install with Subiquity, and xCAT decides that from the
# NAME of the template it resolved. Without a service.subiquity.tmpl the service profile falls
# back to the debian-installer preseed and the node boots the live image without casper.
my $tmpl = xCAT::SvrUtils::get_tmpl_file_name("$SHARE/ubuntu", $SN{profile}, $SN{osver},
$SN{arch}, $SN{genos});
ok(defined $tmpl && length $tmpl, 'the Ubuntu service profile resolves a template');
like($tmpl, qr{subiquity}, '... and it is a Subiquity template, or the node never installs');
like($tmpl, qr{/service[^/]*\.tmpl$}, '... of the service profile, not compute');
# The compute profile must keep resolving too: an assertion that only ever looks at one profile
# cannot tell a profile fix from a lookup fix.
my $compute = xCAT::SvrUtils::get_tmpl_file_name("$SHARE/ubuntu", 'compute', $SN{osver},
$SN{arch}, $SN{genos});
like($compute, qr{subiquity}, 'the compute profile still resolves its own Subiquity template');
# 2. The NFS export. casper mounts the install tree from the service node, so a service node that
# exports nothing stops the compute node at "Unable to find a live file system on the network".
my $can_export = xCAT::SvrUtils->can('nfs_export_line') ? 1 : 0;
ok($can_export, 'SvrUtils can render an export line, without which casper finds no filesystem');
SKIP: {
skip 'nfs_export_line absent', 3 unless $can_export;
my $re = xCAT::SvrUtils->nfs_export_line('/install', reexport => 1);
like($re, qr{\bfsid=\d+}, 're-exporting the mounted /install carries an fsid');
like($re, qr{\bcrossmnt\b}, '... and crossmnt');
like($re, qr{\bno_root_squash\b}, '... and no_root_squash, which the installer needs');
}
done_testing();
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use Test::More;
# A service node with servicenode.nfsserver=1 is asked to "set up file services on this service
# node". When site.installloc is set it MOUNTS /install from the management node, and AAsn.pm
# then exported nothing at all, so the node served no NFS. An Ubuntu compute node boots the live
# installer with casper, which mounts the install tree over NFS from its own service node, and it
# stopped at "Unable to find a live file system on the network". Measured on xcat22-sn:
# showmount -e returned an empty list and /etc/exports was empty while nfs-kernel-server was
# active.
#
# Re-exporting an NFS mount is not the same as exporting a local directory. Linux requires an
# explicit fsid, because it cannot derive one from the underlying filesystem, and refuses the
# export without it.
use lib "$FindBin::Bin/../../perl-xCAT";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
use xCAT::SvrUtils;
can_ok('xCAT::SvrUtils', 'nfs_export_line') or done_testing() && exit;
my $local = xCAT::SvrUtils->nfs_export_line('/install');
is($local, '/install *(rw,no_root_squash,sync,no_subtree_check,insecure)',
'a local directory keeps the options xCAT has always written');
my $reexport = xCAT::SvrUtils->nfs_export_line('/install', reexport => 1);
like($reexport, qr{^/install \*\(}, 're-export names the same directory and clients');
like($reexport, qr{\brw\b}, '... stays read-write');
like($reexport, qr{\bno_root_squash\b}, '... keeps no_root_squash, which the installer needs');
like($reexport, qr{\binsecure\b}, '... keeps insecure, for clients on high ports');
like($reexport, qr{\bfsid=\d+}, '... carries an fsid, without which exportfs refuses an NFS mount');
like($reexport, qr{\bcrossmnt\b}, '... carries crossmnt, so the mount below it is followed');
# The fsid must be stable across runs, or every restart hands clients a new filesystem identity
# and their mounts go stale.
is(xCAT::SvrUtils->nfs_export_line('/install', reexport => 1), $reexport,
'the same directory yields the same fsid every time');
my ($install_fsid) = $reexport =~ /fsid=(\d+)/;
my ($tftp_fsid) = xCAT::SvrUtils->nfs_export_line('/tftpboot', reexport => 1) =~ /fsid=(\d+)/;
isnt($tftp_fsid, $install_fsid, 'two directories do not share one fsid');
cmp_ok($install_fsid, '>', 0, 'the fsid is not 0, which Linux reserves for the export root');
# The guard that keeps it non-zero is only reachable when the checksum itself is 0, which an
# empty path produces. Drive it directly rather than claim a path that cannot reach it.
cmp_ok(xCAT::SvrUtils::_nfs_export_fsid(''), '>', 0,
'a path whose checksum is 0 still yields a usable fsid');
done_testing();
@@ -0,0 +1,61 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use Test::More;
# xCAT decides that an Ubuntu osimage uses the Subiquity installer from the NAME of the template
# it resolved: xCAT_plugin::debian::using_subiquity matches /subiquity/ in the path. That decision
# controls the netboot kernel command line. A template whose name does not match it makes xCAT
# boot the live-server initrd with the debian-installer preseed arguments, without boot=casper and
# without nfsroot, and the node never finds a live filesystem.
#
# The compute profile has compute.subiquity.tmpl. The service profile had only service.tmpl, so an
# Ubuntu 24.04 service node took the preseed path.
use lib "$FindBin::Bin/../../perl-xCAT";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
use xCAT::SvrUtils;
my $plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/debian.pm";
plan skip_all => 'debian.pm not found' unless -r $plugin;
eval { require $plugin; 1 } or plan skip_all => "could not load debian.pm: $@";
my $share = "$FindBin::Bin/../../xCAT-server/share/xcat/install/ubuntu";
plan skip_all => "$share not found" unless -d $share;
# get_file_name takes genos as its last argument. update_tables_with_templates passes 'subiquity'
# for every Ubuntu 20.04 and later osimage.
sub resolved {
my ($profile, $osver, $genos) = @_;
return xCAT::SvrUtils::get_tmpl_file_name($share, $profile, $osver, 'x86_64', $genos);
}
for my $profile (qw(compute service)) {
my $tmpl = resolved($profile, 'ubuntu24.04', 'subiquity');
ok(defined $tmpl && -r $tmpl, "$profile: Ubuntu 24.04 resolves an install template");
ok(xCAT_plugin::debian::using_subiquity('ubuntu24.04', $tmpl),
"$profile: the Ubuntu 24.04 template xCAT resolved is a Subiquity one");
}
# Ubuntu 18.04 predates Subiquity and keeps the preseed path. genos stays the os version there.
for my $profile (qw(compute service)) {
my $tmpl = resolved($profile, 'ubuntu18.04', 'ubuntu18.04');
ok(defined $tmpl && -r $tmpl, "$profile: Ubuntu 18.04 resolves an install template");
ok(!xCAT_plugin::debian::using_subiquity('ubuntu18.04', $tmpl),
"$profile: Ubuntu 18.04 keeps the debian-installer template");
}
# The service template must carry the autoinstall document Subiquity reads. A file named for
# Subiquity that holds a preseed would satisfy using_subiquity and still not install.
my $service = resolved('service', 'ubuntu24.04', 'subiquity');
SKIP: {
skip 'no service template resolved', 3 unless defined $service && -r $service;
my $text = do { local $/; open my $fh, '<', $service or die "$service: $!"; <$fh> };
like($text, qr/^#cloud-config/, 'the service template is a cloud-config document');
like($text, qr/^autoinstall:/m, '... carrying an autoinstall section');
unlike($text, qr/^d-i /m, '... and no debian-installer preseed directives');
}
done_testing();
+3 -1
View File
@@ -828,7 +828,9 @@ while [ $op_index -le $OTHERPKGS_INDEX ]; do
else
type=file
fi
echo "deb $type://$whole_path ./" > $REPOFILE
# apt refuses an unsigned repository outright, and this one carries a
# Packages file and no Release. The rpm arm writes gpgcheck=0 for this.
echo "deb [trusted=yes] $type://$whole_path ./" > $REPOFILE
fi
fi
fi