mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-02 00:41:42 +00:00
test(xcat-test): the hierarchy cases cannot tell a hierarchical provision from a flat one
Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# check_provisioning_source.sh <compute node> <service node>
|
||||
# check_provisioning_source.sh --count <ip>
|
||||
#
|
||||
# Answer which server sent the compute node its boot payload.
|
||||
#
|
||||
# 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. The service node must have served the
|
||||
# compute node, and the management node must have served it nothing.
|
||||
#
|
||||
# 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
|
||||
|
||||
# The first argument to --count is the address to count. Every readable candidate log is read:
|
||||
# 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.
|
||||
count_local_requests()
|
||||
{
|
||||
ip="$1"
|
||||
logs=""
|
||||
for f in ${XCAT_HTTPD_ACCESS_LOG:-} \
|
||||
/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
|
||||
logs="$logs $f"
|
||||
done
|
||||
|
||||
if [ -z "$logs" ]; then
|
||||
echo "$TOKEN nolog 0 0"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
awk -v ip="$ip" -v token="$TOKEN" \
|
||||
'$1 == ip || $2 == ip { n++ } END { print token, "ok", n+0, NR+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); 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:-}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CN="${1:-}"
|
||||
SN="${2:-}"
|
||||
if [ -z "$CN" ] || [ -z "$SN" ]; then
|
||||
echo "provisioning source error: usage: $0 <compute node> <service node>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
SELF=$(readlink -f "$0")
|
||||
MN=$(hostname)
|
||||
|
||||
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" | read_counts)
|
||||
SN_COUNTS=$(xdsh "$SN" -e "$SELF" --count "$CN_IP" 2>&1 | read_counts)
|
||||
|
||||
set -- $MN_COUNTS
|
||||
MN_STATE="${1:-none}" MN_REQ="${2:-0}" MN_LINES="${3:-0}"
|
||||
set -- $SN_COUNTS
|
||||
SN_STATE="${1:-none}" SN_REQ="${2:-0}" SN_LINES="${3:-0}"
|
||||
|
||||
echo "$SN served $CN $SN_REQ request(s) (log $SN_STATE, $SN_LINES lines)"
|
||||
echo "$MN served $CN $MN_REQ request(s) (log $MN_STATE, $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
|
||||
|
||||
# The management node provisioned the service node over http, so its log is never empty on a
|
||||
# hierarchical run. An empty log cannot show that the management node served nothing.
|
||||
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 nothing, 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 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"
|
||||
@@ -74,6 +74,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
|
||||
|
||||
@@ -82,6 +82,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
|
||||
@@ -200,6 +207,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,133 @@
|
||||
#!/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.
|
||||
#
|
||||
# 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"
|
||||
mkdir -p "$BIN"
|
||||
|
||||
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, from $1, for $2 bytes.
|
||||
access_line()
|
||||
{
|
||||
printf '%s - - [01/Jan/2026:00:00:00 +0000] "GET %s HTTP/1.1" 200 %s "-" "iPXE"\n' "$1" "$3" "$2"
|
||||
}
|
||||
|
||||
run_check()
|
||||
{
|
||||
run env XCAT_HTTPD_ACCESS_LOG="$MN_LOG" "$SCRIPT" "$CN" "$SN"
|
||||
}
|
||||
|
||||
@test "a service node that served the compute node and a silent management node pass" {
|
||||
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" {
|
||||
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" {
|
||||
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" {
|
||||
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" == *"served $CN nothing"* ]]
|
||||
}
|
||||
|
||||
@test "an unreadable service node log fails the check instead of passing it" {
|
||||
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"* ]]
|
||||
}
|
||||
|
||||
@test "an empty management node log fails the check instead of reading as silence" {
|
||||
access_line "$CN_IP" 12345678 /tftpboot/xcat/genesis.kernel >"$SN_LOG"
|
||||
: >"$MN_LOG"
|
||||
|
||||
run_check
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"no httpd access log with entries could be read on mn01"* ]]
|
||||
}
|
||||
|
||||
@test "the Debian per-vhost log format is read as the client address" {
|
||||
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" {
|
||||
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"* ]]
|
||||
}
|
||||
Reference in New Issue
Block a user