2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-06 17:46:55 +00:00

refactor(provtest): say each thing in the fixture once

Four repetitions, each of them a place for a later edit to be made in
one copy and not the others:

- the settings every scenario is given, retyped at nineteen calls
  beside a helper that built them and was never called;
- three unit-name lookups with identical bodies;
- the node reset two stages open with;
- the eight settings the three ordering scenarios share.

tftpdir and installdir are asked of xcatd once rather than nine times
in a stage, and the name encodings now call provtest's own netutil
rather than a second implementation in shell that no test covers.
This commit is contained in:
Daniel Hilst
2026-09-11 09:37:50 -03:00
parent 9d63035a4d
commit 9cae1ea9f8
@@ -164,18 +164,22 @@ site_attr() {
| grep "$1=" | awk -F= '{print $2}'
}
# Asked once each. Every one of these costs an lsdef, which is a fork and a
# round trip to xcatd, and one stage resolves tftpdir nine times for the same
# answer. Neither is changed by this fixture or by anything it runs, so the
# first answer is the answer for the life of the process.
tftpdir() {
local dir
dir=$(site_attr tftpdir)
echo "${dir:-/tftpboot}"
[ -n "${TFTPDIR:-}" ] || TFTPDIR=$(site_attr tftpdir)
echo "${TFTPDIR:-/tftpboot}"
}
installdir() {
local dir
dir=$(site_attr installdir)
echo "${dir:-/install}"
[ -n "${INSTALLDIR:-}" ] || INSTALLDIR=$(site_attr installdir)
echo "${INSTALLDIR:-/install}"
}
# Not cached, unlike the two above: setup moves site.httpport when it can, so
# the answer changes within a single run.
httpport() {
local port
port=$(site_attr httpport)
@@ -219,30 +223,32 @@ http_confdir() {
pkgdir_for() { echo "$(installdir)/$OSVERS/$1"; }
# The names the loaders ask for, computed here rather than asserted here: an
# encoding that is wrong by one digit produces no error anywhere, so the
# fixture has to arrive at the name the same way the firmware does.
hex_ip() {
local a b c d
IFS=. read -r a b c d <<< "$1"
printf '%02X%02X%02X%02X' "$a" "$b" "$c" "$d"
# The names the loaders and the resolver ask for, computed here rather than
# asserted here: an encoding that is wrong by one digit produces no error
# anywhere, so the fixture has to arrive at the name the same way the firmware
# does rather than ask xCAT what it wrote.
#
# Arrived at by calling provtest's own encoders, not by a second implementation
# of them in shell. Two implementations of a name that nothing validates are
# two chances to be wrong and no way to notice, and only one of them would have
# the unit tests that provtest_lib.netutil already has.
netutil() {
local fn=$1
shift
python3 -c 'import sys
sys.path.insert(0, sys.argv[1])
from provtest_lib import netutil
sys.stdout.write(str(getattr(netutil, sys.argv[2])(*sys.argv[3:])))' \
"$PROVTEST/src" "$fn" "$@"
}
# The name a per-network file is written under: as many hex digits as the
# netmask covers, rounded up to a whole digit. A /24 is six digits, which is
# what a loader asks for after failing to find a file for its own address.
hex_net() {
local ip=$1 prefix=$2
hex_ip "$ip" | cut -c1-$(( (prefix + 3) / 4 ))
}
dashed_mac() { echo "$1" | tr ':' '-'; }
reverse_name() {
local a b c d
IFS=. read -r a b c d <<< "$1"
echo "$d.$c.$b.$a.in-addr.arpa"
}
hex_ip() { netutil hex_ip "$1"; }
# As many hex digits as the netmask covers, rounded up to a whole digit: a /24
# is six, which is what a loader asks for after failing to find a file for its
# own address.
hex_net() { netutil hex_net "$1" "$2"; }
dashed_mac() { netutil dashed_mac "$1"; }
reverse_name() { netutil reverse_name "$1"; }
# A port nothing answers on: one for the web server to be moved to, and one
# for the control half of P-39, which needs a port that demonstrably refuses.
@@ -338,34 +344,25 @@ assert_serving() {
|| die "nothing holds $proto/$port, so $what cannot answer"
}
dns_unit() {
# The first of these unit names this machine has. Which name a service goes by
# is the distribution's choice and nothing else about it differs, so the three
# services are asked the same question with different lists.
first_unit() {
local unit
for unit in named.service bind9.service named-chroot.service; do
for unit in "$@"; do
systemctl list-unit-files "$unit" >/dev/null 2>&1 && { echo "$unit"; return 0; }
done
return 1
}
http_unit() {
local unit
for unit in httpd.service apache2.service; do
systemctl list-unit-files "$unit" >/dev/null 2>&1 && { echo "$unit"; return 0; }
done
return 1
}
tftp_unit() {
local unit
for unit in tftp.socket tftpd-hpa.service xinetd.service; do
systemctl list-unit-files "$unit" >/dev/null 2>&1 && { echo "$unit"; return 0; }
done
return 1
}
dns_unit() { first_unit named.service bind9.service named-chroot.service; }
http_unit() { first_unit httpd.service apache2.service; }
tftp_unit() { first_unit tftp.socket tftpd-hpa.service xinetd.service; }
# --- check ----------------------------------------------------------------
do_check() {
local leftover
local leftover pkgdir port
[ "$(id -u)" = 0 ] || skip "the wire cases bind source addresses and low ports, which needs root"
command -v ip >/dev/null 2>&1 || skip "iproute2 is not installed"
command -v nodeset >/dev/null 2>&1 || skip "nodeset is not on PATH, so this is not a management node"
@@ -401,7 +398,8 @@ do_check() {
&& skip "a node called $leftover is already defined"
done
lsdef -t network -o "$NETOBJ" >/dev/null 2>&1 && skip "a network called $NETOBJ is already defined"
[ -d "$(pkgdir_for $NODE_ARCH)" ] && skip "$(pkgdir_for $NODE_ARCH) already exists"
pkgdir=$(pkgdir_for "$NODE_ARCH")
[ -d "$pkgdir" ] && skip "$pkgdir already exists"
# P-43 is about a network xCAT does not manage. If this machine happens to
# manage it, the scenario would be asserting the opposite of what it says.
@@ -412,8 +410,9 @@ do_check() {
# nothing to do with the web server, and a machine that cannot run the HTTP
# ones can still run those. Setup may yet fix this by moving the web server
# to a port of its own, so this is a warning and not a verdict.
http_serves_xcat "$(httpport)" \
|| say "the web server on port $(httpport) answers 404 for $(tftpdir); unless setup can move the web server, the HTTP stages will be left out"
port=$(httpport)
http_serves_xcat "$port" \
|| say "the web server on port $port answers 404 for $(tftpdir); unless setup can move the web server, the HTTP stages will be left out"
say "environment is able to run the wire cases"
}
@@ -793,8 +792,20 @@ provtest_run() {
( cd "$PROVTEST" && in_ns python3 src/provtest run "$@" )
}
common_set() {
echo "--set server=$SRV_IP --set client=$NODE_IP"
# What every scenario file is told: who is being asked, and whose address the
# asking is done from. Named once rather than retyped at each call, so that a
# third universal setting, or a renamed one, is one edit and not nineteen.
COMMON=(--set server="$SRV_IP" --set client="$NODE_IP")
# The node put back to the state setup left it in. Two stages need this and
# they need the same thing: a node still set to install, with the configuration
# that says so on disk. Checked rather than assumed, because nodeset exits
# non-zero when it cannot reach the DHCP backend and still writes the file --
# so the file is the evidence and the exit status is not.
reset_node() {
nodeset "$NODE" osimage="$OSIMAGE" >/dev/null 2>&1
generated "$NODE_NETBOOT" "$NODE" "$NODE_IP" \
|| die "nodeset wrote no $NODE_NETBOOT configuration for $NODE"
}
# Stage 1. The forwarded-name scenario is selected separately because it needs
@@ -807,7 +818,7 @@ do_run_dns() {
scenarios="-s node-forward -s node-reverse -s node-alias -s local-nxdomain -s master-resolves"
provtest_run $scenarios \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set node="$NODE" --set domain="$DOMAIN" \
--set nodeip="$NODE_IP" --set revname="$(reverse_name "$NODE_IP")" \
--set alias="$ALIAS" --set master="$MASTER_NODE.$DOMAIN" \
@@ -818,7 +829,7 @@ do_run_dns() {
if dig +short +time=5 +tries=1 "$FORWARDED" A >/dev/null 2>&1 && \
[ -n "$(dig +short +time=5 +tries=1 "$FORWARDED" A 2>/dev/null)" ]; then
provtest_run -s forwarded-name \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set node="$NODE" --set domain="$DOMAIN" \
--set nodeip="$NODE_IP" --set revname="$(reverse_name "$NODE_IP")" \
--set alias="$ALIAS" --set master="$MASTER_NODE.$DOMAIN" \
@@ -834,7 +845,7 @@ do_run_dns() {
# Stage 3. One file per netboot method, one node per file: the method is an
# attribute of the node, so a single node cannot answer for all four.
do_run_tftp() {
local rc=0 loader tftp scenarios xnba
local rc=0 loader tftp scenarios xnba port
assert_serving udp 69 "the TFTP server"
tftp=$(tftpdir)
@@ -850,7 +861,7 @@ do_run_tftp() {
stage_skip "$tftp/$loader is not present, so the loader fetches are left out"
fi
provtest_run $scenarios \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set node="$NODE" --set hexip="$(hex_ip "$NODE_IP")" \
--set macdashes="$(dashed_mac "$NODE_MAC")" --set mac="$NODE_MAC" \
--set loader="$loader" --set bootfile="$loader" \
@@ -858,7 +869,7 @@ do_run_tftp() {
conf/tftp-grub2.conf || rc=1
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set node="$PXE_NODE" --set hexip="$(hex_ip "$PXE_IP")" \
--set master="$SRV_IP" --set xcatport="$XCATPORT" --set destiny="$DESTINY" \
--set bootnode="$BOOT_NODE" \
@@ -868,21 +879,22 @@ do_run_tftp() {
# web server serving the wrong tree can fail for a reason that has nothing
# to do with the script it fetched. The script is asserted either way; the
# kernel it names is asserted only where something serves it.
if http_serves_xcat "$(httpport)"; then
port=$(httpport)
if http_serves_xcat "$port"; then
xnba="-s xnba-script -s xnba-kernel"
else
xnba="-s xnba-script"
stage_skip "the web server on port $(httpport) does not serve $tftp, so the kernel half of the xnba case is left out"
stage_skip "the web server on port $port does not serve $tftp, so the kernel half of the xnba case is left out"
fi
# shellcheck disable=SC2086
provtest_run $xnba \
--set server="$SRV_IP" --set client="$NODE_IP" \
--set node="$XNBA_NODE" --set httpport="$(httpport)" \
"${COMMON[@]}" \
--set node="$XNBA_NODE" --set httpport="$port" \
conf/tftp-xnba.conf || rc=1
if [ -f "$tftp/petitboot/$PTB_NODE" ]; then
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set node="$PTB_NODE" --set hexip="$(hex_ip "$PTB_IP")" \
--set master="$SRV_IP" --set xcatport="$XCATPORT" --set destiny="$DESTINY" \
conf/tftp-petitboot.conf || rc=1
@@ -920,7 +932,7 @@ do_run_http() {
# shellcheck disable=SC2086
provtest_run $select \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set node="$NODE" --set httpport="$port" --set otherport="$other" \
--set hexip="$(hex_ip "$NODE_IP")" \
--set knownfile="boot/grub2/grub.cfg-$(hex_ip "$NODE_IP")" \
@@ -956,7 +968,7 @@ do_run_genesis() {
# itself, and there is nothing in it a word split can damage.
# shellcheck disable=SC2086
provtest_run $select \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set hexnet="$hexnet" --set netfile="$NET_FILE" \
--set master="$SRV_IP" --set xcatport="$XCATPORT" \
conf/discovery-artefacts.conf
@@ -969,11 +981,11 @@ do_run_discovery() {
assert_serving udp "$XCATPORT" "the xcatd flow-control listener"
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
conf/flowcontrol.conf || rc=1
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set foreign="$FOREIGN_IP" \
conf/findme.conf || rc=1
return $rc
@@ -987,18 +999,18 @@ do_run_xcatd() {
assert_serving tcp "$XCATPORT" "xcatd"
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set xcatport="$XCATPORT" --set refused=rpower --set node="$NODE" \
conf/xcatd-policy.conf || rc=1
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set unknown="$UNKNOWN_IP" --set node="$NODE" \
--set xcatport="$XCATPORT" --set monitorport="$MONITORPORT" \
conf/xcatd-postscript.conf || rc=1
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set xcatport="$XCATPORT" --set credtype=xcat_server_cred \
conf/xcatd-credentials.conf || rc=1
@@ -1007,12 +1019,10 @@ do_run_xcatd() {
# question, it is a write, and a second run of this stage against the node
# it left behind would be asserting install against a node that is now set
# to boot. Resetting here is what makes the stage repeatable.
nodeset "$NODE" osimage="$OSIMAGE" >/dev/null 2>&1
generated "$NODE_NETBOOT" "$NODE" "$NODE_IP" \
|| die "nodeset wrote no $NODE_NETBOOT configuration for $NODE"
reset_node
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set unknown="$UNKNOWN_IP" --set node="$NODE" \
--set destiny="$DESTINY" --set master="$SRV_IP" --set xcatport="$XCATPORT" \
conf/xcatd-destiny.conf || rc=1
@@ -1024,7 +1034,7 @@ do_run_monitor() {
assert_serving tcp "$MONITORPORT" "the xcatd install monitor"
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set unknown="$UNKNOWN_IP" --set monitorport="$MONITORPORT" \
--set xcatport="$XCATPORT" \
conf/monitor.conf
@@ -1034,9 +1044,19 @@ do_run_monitor() {
# state, so the fixture drives them in order and selects them by name rather
# than running the file whole.
do_run_ordering() {
local rc=0
local rc=0 flags
assert_serving tcp "$XCATPORT" "xcatd"
# The three scenarios below are the same question asked of a cluster in
# three states, so they are given the same settings and differ only in
# which one is selected. Built once, because three copies of one list is
# three places to add the next setting to and two places to forget it.
flags=("${COMMON[@]}"
--set node="$NODE" --set hexip="$(hex_ip "$NODE_IP")"
--set xcatport="$XCATPORT" --set unreachable="$UNREACHABLE_IP"
--set master="$SRV_IP"
--set destiny="$DESTINY" --set second="$SECOND_DESTINY")
# This stage ends by withdrawing the node's name, and everything before
# that needs it back: nodeset resolves the node to name the config it
# writes. Putting it back here rather than in teardown is what lets the
@@ -1051,17 +1071,9 @@ do_run_ordering() {
# P-74 is about a node that is still set to install being told to discover
# itself anyway. Against a node already set to boot it would assert
# nothing.
nodeset "$NODE" osimage="$OSIMAGE" >/dev/null 2>&1
generated "$NODE_NETBOOT" "$NODE" "$NODE_IP" \
|| die "nodeset wrote no $NODE_NETBOOT configuration for $NODE"
reset_node
provtest_run -s unreachable-master \
--set server="$SRV_IP" --set client="$NODE_IP" \
--set node="$NODE" --set hexip="$(hex_ip "$NODE_IP")" \
--set xcatport="$XCATPORT" --set unreachable="$UNREACHABLE_IP" \
--set master="$SRV_IP" \
--set destiny="$DESTINY" --set second="$SECOND_DESTINY" \
conf/ordering.conf || rc=1
provtest_run -s unreachable-master "${flags[@]}" conf/ordering.conf || rc=1
# P-75 before P-74, and the order is forced rather than chosen: the second
# nodeset has to resolve the node to write a config named after its
@@ -1074,13 +1086,7 @@ do_run_ordering() {
nodeset "$NODE" "$SECOND_DESTINY" >/dev/null 2>&1
generated "$NODE_NETBOOT" "$NODE" "$NODE_IP" \
|| die "nodeset $NODE $SECOND_DESTINY wrote no $NODE_NETBOOT configuration"
provtest_run -s state-replaced \
--set server="$SRV_IP" --set client="$NODE_IP" \
--set node="$NODE" --set hexip="$(hex_ip "$NODE_IP")" \
--set xcatport="$XCATPORT" --set unreachable="$UNREACHABLE_IP" \
--set master="$SRV_IP" \
--set destiny="$DESTINY" --set second="$SECOND_DESTINY" \
conf/ordering.conf || rc=1
provtest_run -s state-replaced "${flags[@]}" conf/ordering.conf || rc=1
else
stage_skip "genesis has not been built here, so there is no second state to set"
fi
@@ -1096,13 +1102,7 @@ do_run_ordering() {
makedns -d "$NODE" >/dev/null 2>&1 || say "makedns -d $NODE reported an error"
makehosts -d "$NODE" >/dev/null 2>&1 || say "makehosts -d $NODE reported an error"
echo done > "$STATE/ptrgone"
provtest_run -s missing-ptr \
--set server="$SRV_IP" --set client="$NODE_IP" \
--set node="$NODE" --set hexip="$(hex_ip "$NODE_IP")" \
--set xcatport="$XCATPORT" --set unreachable="$UNREACHABLE_IP" \
--set master="$SRV_IP" \
--set destiny="$DESTINY" --set second="$SECOND_DESTINY" \
conf/ordering.conf || rc=1
provtest_run -s missing-ptr "${flags[@]}" conf/ordering.conf || rc=1
return $rc
}
@@ -1116,7 +1116,7 @@ do_run_dns_removal() {
echo done > "$STATE/ptrgone"
provtest_run \
--set server="$SRV_IP" --set client="$NODE_IP" \
"${COMMON[@]}" \
--set node="$NODE" --set domain="$DOMAIN" \
--set revname="$(reverse_name "$NODE_IP")" \
conf/dns-removal.conf