2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-21 15:18:19 +00:00
Files
xcat-core/xCAT-test/bats/genesis_openembedded_runtime.bats
T
2026-10-13 22:16:50 -03:00

598 lines
25 KiB
Bash

#!/usr/bin/env bats
#
# The runtime scripts of the OpenEmbedded Genesis image: getdestiny and nextdestiny ask xCAT
# for the node action, genesis-network-state writes the management network state,
# genesis-network-refresh renews the DHCP lease, genesis-register records the action,
# genesis-status publishes one status record, and genesis-maintenance-shell reports them.
# openssl, ip, nmcli and logger are scratch scripts. A local TCP listener stands in for xcatd.
load 'helpers/shell_source'
INIT_FILES='xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files'
setup_file()
{
# A listener that accepts and closes connections and logs one line for each.
export LISTENER_LOG="$BATS_FILE_TMPDIR/listener.log"
: >"$LISTENER_LOG"
perl -MIO::Socket::INET -e '
my $s = IO::Socket::INET->new(LocalAddr => "127.0.0.1", LocalPort => 0,
Listen => 5, ReuseAddr => 1) or die "listen: $!";
open(my $port, ">", $ARGV[0]) or die; print {$port} $s->sockport, "\n"; close $port;
while (my $c = $s->accept) {
open(my $log, ">>", $ARGV[1]) or die; print {$log} "accept\n"; close $log; close $c;
}' "$BATS_FILE_TMPDIR/port" "$LISTENER_LOG" </dev/null >/dev/null 2>&1 3>&- &
echo "$!" >"$BATS_FILE_TMPDIR/listener.pid"
for _ in $(seq 50); do [ -s "$BATS_FILE_TMPDIR/port" ] && break; sleep 0.1; done
[ -s "$BATS_FILE_TMPDIR/port" ]
}
teardown_file()
{
kill "$(cat "$BATS_FILE_TMPDIR/listener.pid")" 2>/dev/null || true
}
setup()
{
NETWORK_SCRIPT="$(require_repo_file "$INIT_FILES/genesis-network-state")"
REFRESH_SCRIPT="$(require_repo_file "$INIT_FILES/genesis-network-refresh")"
REGISTER_SCRIPT="$(require_repo_file "$INIT_FILES/genesis-register")"
STATUS_SCRIPT="$(require_repo_file "$INIT_FILES/genesis-status")"
SHELL_SCRIPT="$(require_repo_file "$INIT_FILES/genesis-maintenance-shell")"
GETDESTINY="$(require_repo_file 'xCAT-genesis-scripts/usr/bin/getdestiny')"
NEXTDESTINY="$(require_repo_file 'xCAT-genesis-scripts/usr/bin/nextdestiny')"
port="$(cat "$BATS_FILE_TMPDIR/port")"
root="$BATS_TEST_TMPDIR"
bin="$root/bin"
state_dir="$root/run"
status_dir="$state_dir/status"
metadata="$state_dir/xcat-response.env"
mkdir -p "$bin" "$state_dir" "$root/sys/class/net/eth0" "$root/sys/class/net/eth1"
printf '52:54:00:00:00:35\n' >"$root/sys/class/net/eth0/address"
printf 'up\n' >"$root/sys/class/net/eth0/operstate"
printf '123.45 456.78\n' >"$root/uptime"
: >"$root/commands.log"
printf '#!/bin/sh\nprintf "logger %%s\\n" "$*" >>"$XCAT_TEST_LOG"\n[ -z "${XCAT_TEST_LOGGER_FAIL-}" ]\n' >"$bin/logger"
cat >"$bin/ip" <<'SH'
#!/bin/sh
case "$*" in
'-4 -o route get '*) printf '%s\n' "$XCAT_TEST_ROUTE" ;;
'-4 -o address show dev eth0 scope global')
printf '%s\n' '2: eth0 inet 192.0.2.98/24 scope global eth0'
;;
esac
SH
cat >"$bin/nmcli" <<'SH'
#!/bin/sh
printf 'nmcli %s\n' "$*" >>"$XCAT_TEST_LOG"
case "$*" in
'-t -f DEVICE,TYPE device status')
printf '%s\n' 'eth0:ethernet' 'eth1:ethernet' 'lo:loopback'
;;
'-g GENERAL.CON-UUID device show eth0') printf '%s\n' 'connection-eth0' ;;
'-g GENERAL.CON-UUID device show eth1') printf '%s\n' 'connection-eth1' ;;
'-g GENERAL.STATE device show '*) printf '%s\n' '100 (connected)' ;;
'--terse --escape no -g IP4.DNS,IP6.DNS device show eth0')
printf '%s\n' "${XCAT_TEST_DNS-192.0.2.53 | 2001:db8::53}"
;;
'--wait 30 device connect eth0')
[ -z "${XCAT_TEST_CONNECT_FAIL-}" ] || exit 1
;;
esac
SH
printf '#!/bin/sh\nprintf "%%s\\n" network-state-refresh >>"$XCAT_TEST_LOG"\n' >"$bin/network-state-refresh"
cat >"$bin/getdestiny" <<'SH'
#!/bin/sh
printf 'getdestiny %s\n' "$*" >>"$XCAT_TEST_LOG"
metadata=
while [ "$#" -gt 0 ]; do
case "$1" in
--metadata) metadata=$2; shift 2 ;;
*) shift ;;
esac
done
if [ -n "$metadata" ]; then
printf '%s\n' XCAT_NODE_NAME=node042 XCAT_DESTINY=osimage=test-image \
XCAT_IMAGE_SERVER=192.0.2.10 XCAT_KERNEL=/tftp/vmlinuz XCAT_INITRD=/tftp/initrd \
'XCAT_KERNEL_COMMAND_LINE=console=ttyS0 quiet' >"$metadata"
fi
printf '%s\n' "${XCAT_TEST_DESTINY-standby}"
SH
cat >"$bin/openssl" <<'SH'
#!/bin/sh
printf 'openssl %s\n' "$*" >>"$XCAT_TEST_LOG"
cat >"$XCAT_TEST_REQUEST_FILE"
printf '%s\n' "$$" >"$XCAT_TEST_REQUEST_PID"
[ -z "${XCAT_TEST_OPENSSL_DELAY-}" ] || exec sleep "$XCAT_TEST_OPENSSL_DELAY"
[ -z "${XCAT_TEST_OPENSSL_STATUS-}" ] || exit "$XCAT_TEST_OPENSSL_STATUS"
if [ -n "${XCAT_TEST_FIRST_ERROR-}" ] && [ ! -f "$XCAT_TEST_FIRST_ERROR" ]; then
touch "$XCAT_TEST_FIRST_ERROR"
printf '<xcatresponse><error>busy</error></xcatresponse>\n'
exit 1
fi
cat "$XCAT_TEST_RESPONSE_FILE"
exit "${XCAT_TEST_OPENSSL_AFTER_STATUS:-0}"
SH
chmod 0755 "$bin"/*
export PATH="$bin:$PATH"
export XCAT_CMDLINE_FILE="$root/cmdline"
export XCAT_STATE_DIR="$state_dir"
export XCAT_STATUS_COMMAND="$STATUS_SCRIPT"
export XCAT_STATUS_DIR="$status_dir"
export XCAT_SYS_CLASS_NET="$root/sys/class/net"
export XCAT_TEST_LOG="$root/commands.log"
export XCAT_TEST_RESPONSE_FILE="$root/destiny-response.xml"
export XCAT_TEST_REQUEST_FILE="$root/request.xml"
export XCAT_TEST_REQUEST_PID="$root/request.pid"
export XCAT_TEST_ROUTE='127.0.0.1 via 192.0.2.1 dev eth0 src 192.0.2.98'
export XCAT_UPTIME_FILE="$root/uptime"
export XCAT_GENESIS_FUNCTIONS="$(require_repo_file "$INIT_FILES/genesis-functions")"
export XCAT_REGISTRATION_ATTEMPTS=1
export XCAT_REGISTRATION_RETRY_SECONDS=0
export XCAT_REGISTRATION_REQUEST_TIMEOUT=1
}
EXPECTED_METADATA='XCAT_NODE_NAME=node042
XCAT_DESTINY=osimage=test-image
XCAT_IMAGE_SERVER=192.0.2.10
XCAT_KERNEL=/tftp/vmlinuz
XCAT_INITRD=/tftp/initrd
XCAT_KERNEL_COMMAND_LINE=console=ttyS0 quiet'
# An xCAT response naming node042, its image server, and an action.
response()
{
printf '%s\n' '<xcatresponse>' '<node>' '<name>node042</name>' "<destiny>$1</destiny>" \
'<kernel>/tftp/vmlinuz</kernel>' '<initrd>/tftp/initrd</initrd>' \
'<kcmdline>console=ttyS0 quiet</kcmdline>' '<imgserver>192.0.2.10</imgserver>' \
'</node>' '</xcatresponse>' >"$XCAT_TEST_RESPONSE_FILE"
}
# Stop the command and print its exit status, or "hung" if it does not exit.
terminate_after()
{
local pid rc=0
"$@" </dev/null >/dev/null 2>&1 &
pid=$!
if [ "${XCAT_TEST_WAIT_REQUEST:-0}" -eq 1 ]; then
for _ in $(seq 50); do
[ -s "$XCAT_TEST_REQUEST_PID" ] && break
sleep 0.1
done
else
sleep 0.2
fi
kill -TERM "$pid"
for _ in $(seq 30); do
kill -0 "$pid" 2>/dev/null || { wait "$pid" || rc=$?; echo "$rc"; return; }
sleep 0.1
done
kill -KILL "$pid"
echo hung
}
network_state()
{
status=0
/bin/bash "$NETWORK_SCRIPT" </dev/null >/dev/null 2>&1 || status=$?
}
@test "getdestiny accepts a complete response, keeps the complete action, and records the metadata" {
response osimage=test-image
run /bin/bash "$GETDESTINY" 192.0.2.10:3001 --once --metadata "$metadata"
[ "$status" -eq 0 ]
[ "$output" = osimage=test-image ]
[ "$(cat "$metadata")" = "$EXPECTED_METADATA" ]
}
@test "getdestiny rejects an error response, and the failed request keeps the prior metadata" {
response osimage=test-image
/bin/bash "$GETDESTINY" 192.0.2.10:3001 --once --metadata "$metadata"
printf '<xcatresponse><error>denied</error></xcatresponse>\n' >"$XCAT_TEST_RESPONSE_FILE"
run /bin/bash "$GETDESTINY" 192.0.2.10:3001 --once --metadata "$metadata"
[ "$status" -ne 0 ]
[ "$(cat "$metadata")" = "$EXPECTED_METADATA" ]
}
@test "legacy getdestiny retries an error response and a connection failure until stopped" {
printf '<xcatresponse><error>denied</error></xcatresponse>\n' >"$XCAT_TEST_RESPONSE_FILE"
[ "$(terminate_after /bin/bash "$GETDESTINY" 192.0.2.10:3001)" = 143 ]
[ "$(XCAT_TEST_OPENSSL_STATUS=1 terminate_after /bin/bash "$GETDESTINY" 192.0.2.10:3001)" = 143 ]
}
@test "without a destiny, legacy getdestiny returns an empty one and one-shot getdestiny fails" {
printf '<xcatresponse/>\n' >"$XCAT_TEST_RESPONSE_FILE"
run /bin/bash "$GETDESTINY" 192.0.2.10:3001
[ "$status" -eq 0 ]
[ "$output" = '' ]
[ "$(/bin/bash "$GETDESTINY" 192.0.2.10:3001 | od -An -c | tr -d ' ')" = '\n' ]
run /bin/bash "$GETDESTINY" 192.0.2.10:3001 --once
[ "$status" -ne 0 ]
}
@test "nextdestiny accepts a complete response and keeps a legacy action with arguments" {
response 'install rocky9.7-x86_64-compute'
run /bin/bash "$NEXTDESTINY" 192.0.2.10:3001 --once --metadata "$metadata"
[ "$status" -eq 0 ]
[ "$output" = 'install rocky9.7-x86_64-compute' ]
grep -qx 'XCAT_DESTINY=install rocky9.7-x86_64-compute' "$metadata"
}
@test "nextdestiny returns a server error as an action" {
printf '<xcatresponse><error>chain unavailable</error></xcatresponse>\n' >"$XCAT_TEST_RESPONSE_FILE"
run /bin/bash "$NEXTDESTINY" 192.0.2.10:3001 --once
[ "$status" -eq 0 ]
[ "$output" = 'error=chain unavailable' ]
}
@test "one-shot nextdestiny rejects an incomplete response, legacy nextdestiny returns it" {
printf '<xcatresponse/>\n' >"$XCAT_TEST_RESPONSE_FILE"
run /bin/bash "$NEXTDESTINY" 192.0.2.10:3001 --once
[ "$status" -ne 0 ]
run /bin/bash "$NEXTDESTINY" 192.0.2.10:3001
[ "$status" -eq 0 ]
[ "$output" = 'error=No destiny command received' ]
}
@test "SIGTERM stops getdestiny and nextdestiny during a request" {
export XCAT_TEST_OPENSSL_DELAY=30
[ "$(terminate_after /bin/bash "$GETDESTINY" 192.0.2.10:3001)" = 143 ]
[ "$(terminate_after /bin/bash "$NEXTDESTINY" 192.0.2.10:3001)" = 143 ]
}
@test "destiny commands send distinct requests and honor endpoint precedence" {
response standby
local command name
for command in "$GETDESTINY" "$NEXTDESTINY"; do
name=${command##*/}
run env XCATDEST=192.0.2.20:3001 /bin/bash "$command" 192.0.2.10:3001 --once
[ "$status" -eq 0 ]
[ "$(cat "$XCAT_TEST_REQUEST_FILE")" = "<xcatrequest>
<command>$name</command>
<callback_port>300</callback_port>
</xcatrequest>" ]
grep -q -- '-connect 192.0.2.20:3001 -quiet$' "$XCAT_TEST_LOG"
: >"$XCAT_TEST_LOG"
run env XCATDEST=192.0.2.20:3001 /bin/bash "$command" --once -- 192.0.2.30:3001
[ "$status" -eq 0 ]
grep -q -- '-connect 192.0.2.30:3001 -quiet$' "$XCAT_TEST_LOG"
: >"$XCAT_TEST_LOG"
done
}
@test "destiny commands accept a complete response despite a TLS exit error" {
response standby
local command
for command in "$GETDESTINY" "$NEXTDESTINY"; do
run env XCAT_TEST_OPENSSL_AFTER_STATUS=1 /bin/bash "$command" 192.0.2.10:3001 --once
[ "$status" -eq 0 ]
[ "$output" = standby ]
done
}
@test "destiny clients use a certificate only when both credential files exist" {
command -v bwrap >/dev/null || { echo 'bubblewrap is required for isolated credential tests' >&2; return 1; }
response standby
mkdir -p "$root/cert-etc/xcat"
local command credentials expected
for credentials in absent certificate key both; do
rm -f "$root/cert-etc/xcat/cert.pem" "$root/cert-etc/xcat/certkey.pem"
case "$credentials" in
certificate|both) printf certificate >"$root/cert-etc/xcat/cert.pem" ;;
esac
case "$credentials" in
key|both) printf key >"$root/cert-etc/xcat/certkey.pem" ;;
esac
expected='openssl s_client -connect 192.0.2.10:3001 -quiet'
if [ "$credentials" = both ]; then
expected='openssl s_client -key /etc/xcat/certkey.pem -cert /etc/xcat/cert.pem -connect 192.0.2.10:3001 -quiet'
fi
for command in "$GETDESTINY" "$NEXTDESTINY"; do
: >"$XCAT_TEST_LOG"
run bwrap --unshare-all --die-with-parent --ro-bind / / \
--proc /proc --dev /dev --bind "$root" "$root" \
--ro-bind "$root/cert-etc" /etc --setenv TMPDIR "$root" \
/bin/bash "$command" 192.0.2.10:3001 --once
[ "$status" -eq 0 ]
[ "$(cat "$XCAT_TEST_LOG")" = "$expected" ]
done
done
}
@test "destiny metadata uses private modes and supports a relative output name" {
response osimage=test-image
local command
for command in "$GETDESTINY" "$NEXTDESTINY"; do
(cd "$state_dir" && /bin/bash "$command" 192.0.2.10:3001 --once --metadata response.env)
[ "$(cat "$state_dir/response.env")" = "$EXPECTED_METADATA" ]
[ "$(stat -c %a "$state_dir/response.env")" = 600 ]
[ -z "$(find "$state_dir" -name '.*destiny.*' -print)" ]
done
}
@test "destiny metadata retains the first printable element and empty fields" {
printf '<xcatresponse>\n<name>node\001042</name>\n<name>other</name>\n<destiny>standby</destiny>\n</xcatresponse>\n' >"$XCAT_TEST_RESPONSE_FILE"
local command
for command in "$GETDESTINY" "$NEXTDESTINY"; do
/bin/bash "$command" 192.0.2.10:3001 --once --metadata "$metadata"
[ "$(cat "$metadata")" = 'XCAT_NODE_NAME=node042
XCAT_DESTINY=standby
XCAT_IMAGE_SERVER=
XCAT_KERNEL=
XCAT_INITRD=
XCAT_KERNEL_COMMAND_LINE=' ]
done
}
@test "destiny commands reject a missing metadata directory without leaving request files" {
response standby
mkdir "$root/requests"
local command
for command in "$GETDESTINY" "$NEXTDESTINY"; do
run env TMPDIR="$root/requests" /bin/bash "$command" 192.0.2.10:3001 --once --metadata "$root/absent/response"
[ "$status" -eq 1 ]
[ -z "$(find "$root/requests" -mindepth 1 -print)" ]
done
}
@test "terminating a destiny request stops its TLS child and removes temporary files" {
export XCAT_TEST_OPENSSL_DELAY=30 XCAT_TEST_WAIT_REQUEST=1
mkdir "$root/requests"
local command
for command in "$GETDESTINY" "$NEXTDESTINY"; do
rm -f "$XCAT_TEST_REQUEST_PID"
[ "$(TMPDIR="$root/requests" terminate_after /bin/bash "$command" 192.0.2.10:3001)" = 143 ]
[ -s "$XCAT_TEST_REQUEST_PID" ]
run kill -0 "$(cat "$XCAT_TEST_REQUEST_PID")"
[ "$status" -ne 0 ]
[ -z "$(find "$root/requests" -mindepth 1 -print)" ]
done
}
@test "legacy getdestiny retries an error before accepting the next response" {
response standby
printf '#!/bin/sh\nprintf "sleep %%s\\n" "$*" >>"$XCAT_TEST_LOG"\n' >"$bin/sleep"
chmod 0755 "$bin/sleep"
run env XCAT_TEST_FIRST_ERROR="$root/first-error" timeout 30 /bin/bash "$GETDESTINY" 192.0.2.10:3001
[ "$status" -eq 0 ]
[ "$(grep -c '^openssl ' "$XCAT_TEST_LOG")" = 2 ]
[ "$(grep -c '^sleep 1$' "$XCAT_TEST_LOG")" = 60 ]
[[ "$output" == *standby ]]
}
@test "the network state accepts DHCP boot state, probes the xCAT port, and publishes readiness" {
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35\n' "$port" >"$XCAT_CMDLINE_FILE"
accepted_before="$(wc -l <"$LISTENER_LOG")"
network_state
[ "$status" -eq 0 ]
[ "$(wc -l <"$LISTENER_LOG")" -gt "$accepted_before" ]
[ "$(cat "$state_dir/genesis.env")" = "XCATDEST=127.0.0.1:$port
XCATMASTER=127.0.0.1
XCATPORT=$port
XCAT_INTERFACE=eth0
XCAT_SOURCE_ADDRESS=192.0.2.98
XCAT_SOURCE_PREFIXED_ADDRESS=192.0.2.98/24
XCAT_GATEWAY=192.0.2.1
XCAT_DNS_SERVERS=192.0.2.53,2001:db8::53
XCAT_NETWORK_METHOD=auto
XCAT_LINK_STATE=up
XCAT_MAC_ADDRESS=52:54:00:00:00:35
XCAT_VERIFIED_SECONDS=123" ]
[ "$(cat "$status_dir/network.env")" = 'SCHEMA=1
STATE=READY
DETAIL=Management network ready on eth0
STARTED_SECONDS=123
UPDATED_SECONDS=123
VERIFIED_SECONDS=123' ]
}
@test "network readiness does not depend on logging" {
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35\n' "$port" >"$XCAT_CMDLINE_FILE"
XCAT_TEST_LOGGER_FAIL=1 network_state
[ "$status" -eq 0 ]
}
@test "network status failures use the caller diagnostic and propagate a logging failure" {
export XCAT_STATUS_COMMAND=/bin/false
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35\n' "$port" >"$XCAT_CMDLINE_FILE"
network_state
[ "$status" -eq 0 ]
grep -Fx 'logger -t xcat-genesis-network-state -- unable to publish network status' "$XCAT_TEST_LOG"
[ -s "$state_dir/genesis.env" ]
XCAT_TEST_LOGGER_FAIL=1 network_state
[ "$status" -eq 1 ]
}
@test "unsafe network data is rejected, never evaluated, and does not replace the prior state" {
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35\n' "$port" >"$XCAT_CMDLINE_FILE"
network_state
safe="$(cat "$state_dir/genesis.env")"
XCAT_TEST_DNS="192.0.2.53;touch$root/injected" network_state
[ "$status" -ne 0 ]
[ "$(cat "$state_dir/genesis.env")" = "$safe" ]
[ ! -e "$root/injected" ]
grep -qx CODE=UNSAFE_NETWORK_STATE "$status_dir/network.env"
}
# Refresh renews the lease of the interface named in the current network state.
with_network_state()
{
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35\n' "$port" >"$XCAT_CMDLINE_FILE"
network_state
[ "$status" -eq 0 ]
: >"$XCAT_TEST_LOG"
}
@test "discovery refreshes its DHCP identity on the selected interface and rebuilds the state" {
with_network_state
XCAT_NETWORK_STATE_COMMAND="$bin/network-state-refresh" run /bin/bash "$REFRESH_SCRIPT" 'restart (eth0)'
[ "$status" -eq 0 ]
grep -qx 'nmcli --wait 10 device disconnect eth1' "$XCAT_TEST_LOG"
grep -qx 'nmcli --wait 10 device disconnect eth0' "$XCAT_TEST_LOG"
grep -qx 'nmcli --wait 30 device connect eth0' "$XCAT_TEST_LOG"
grep -qx network-state-refresh "$XCAT_TEST_LOG"
}
@test "a failed DHCP renewal returns an error, restores the connections, and publishes nothing" {
with_network_state
XCAT_TEST_CONNECT_FAIL=1 XCAT_NETWORK_STATE_COMMAND="$bin/network-state-refresh" \
run /bin/bash "$REFRESH_SCRIPT" 'restart (eth0)'
[ "$status" -ne 0 ]
grep -qx 'nmcli --wait 30 connection up uuid connection-eth0 ifname eth0' "$XCAT_TEST_LOG"
grep -qx 'nmcli --wait 30 connection up uuid connection-eth1 ifname eth1' "$XCAT_TEST_LOG"
refute_grep -qx network-state-refresh "$XCAT_TEST_LOG"
}
@test "static boot settings are accepted, and a dotted netmask becomes a CIDR prefix" {
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35 hostip=192.0.2.98 netmask=255.255.255.192 gateway=192.0.2.1\n' \
"$port" >"$XCAT_CMDLINE_FILE"
network_state
[ "$status" -eq 0 ]
grep -q 'ipv4\.addresses 192\.0\.2\.98/26' "$XCAT_TEST_LOG"
}
@test "static IPv6 settings disable IPv4 and configure the IPv6 route" {
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35 hostip=2001:db8::98/64 netmask=64 gateway=2001:db8::1\n' \
"$port" >"$XCAT_CMDLINE_FILE"
network_state
[ "$status" -eq 0 ]
grep -q 'ipv4\.method disabled ipv6\.method manual ipv6\.addresses 2001:db8::98/64 ipv6\.gateway 2001:db8::1' \
"$XCAT_TEST_LOG"
}
@test "partial static settings fail closed and replace the status with the rejected configuration" {
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35\n' "$port" >"$XCAT_CMDLINE_FILE"
network_state
printf 'xcatd=127.0.0.1:%s BOOTIF=01-52-54-00-00-00-35 hostip=192.0.2.98 netmask=255.255.255.0\n' \
"$port" >"$XCAT_CMDLINE_FILE"
network_state
[ "$status" -ne 0 ]
grep -qx STATE=FAILED "$status_dir/network.env"
grep -qx CODE=INVALID_STATIC_NETWORK "$status_dir/network.env"
}
@test "registration keeps a kernel destiny, still contacts xCAT, and publishes the identity" {
export XCATDEST=192.0.2.213:3001
printf 'xcatd=192.0.2.213:3001 destiny=shell\n' >"$XCAT_CMDLINE_FILE"
run /bin/bash "$REGISTER_SCRIPT"
[ "$status" -eq 0 ]
[ "$(cat "$state_dir/destiny")" = shell ]
grep -q 'getdestiny 192\.0\.2\.213:3001' "$XCAT_TEST_LOG"
grep -qx STATE=ACTION_RECEIVED "$status_dir/registration.env"
grep -A2 -x ACTION=shell "$status_dir/registration.env" | paste -sd' ' | grep -qx 'ACTION=shell TARGET= NODE_NAME=node042'
XCAT_TEST_LOGGER_FAIL=1 run /bin/bash "$REGISTER_SCRIPT"
[ "$status" -eq 0 ]
}
@test "registration status failures use the caller diagnostic and propagate a logging failure" {
export XCATDEST=192.0.2.213:3001 XCAT_STATUS_COMMAND=/bin/false
printf 'xcatd=192.0.2.213:3001 destiny=shell\n' >"$XCAT_CMDLINE_FILE"
run /bin/bash "$REGISTER_SCRIPT"
[ "$status" -eq 0 ]
[ "$(cat "$state_dir/destiny")" = shell ]
grep -Fx 'logger -t xcat-genesis-register -- unable to publish registration status' "$XCAT_TEST_LOG"
XCAT_TEST_LOGGER_FAIL=1 run /bin/bash "$REGISTER_SCRIPT"
[ "$status" -eq 1 ]
}
@test "registration requests a missing destiny, and rejects an empty one without replacing state" {
export XCATDEST=192.0.2.213:3001
printf 'xcatd=192.0.2.213:3001\n' >"$XCAT_CMDLINE_FILE"
run /bin/bash "$REGISTER_SCRIPT"
[ "$status" -eq 0 ]
[ "$(cat "$state_dir/destiny")" = standby ]
XCAT_TEST_DESTINY='' run /bin/bash "$REGISTER_SCRIPT"
[ "$status" -ne 0 ]
[ "$(cat "$state_dir/destiny")" = standby ]
grep -qx STATE=FAILED "$status_dir/registration.env"
grep -A1 -x CODE=XCAT_RESPONSE_UNAVAILABLE "$status_dir/registration.env" | tail -n1 | grep -q '^RECOVERY='
}
@test "registration separates a legacy action from its argument" {
export XCATDEST=192.0.2.213:3001
printf 'xcatd=192.0.2.213:3001\n' >"$XCAT_CMDLINE_FILE"
XCAT_TEST_DESTINY='install rocky9.7-x86_64-compute' run /bin/bash "$REGISTER_SCRIPT"
[ "$status" -eq 0 ]
grep -A1 -x ACTION=install "$status_dir/registration.env" | tail -n1 | grep -qx 'TARGET=rocky9.7-x86_64-compute'
}
@test "the status helper reduces the detail to printable text and keeps the stage start time" {
run /bin/sh "$STATUS_SCRIPT" console DEGRADED $'bad\n\tvalue\x01'
[ "$status" -eq 0 ]
grep -qx 'DETAIL=bad value' "$status_dir/console.env"
printf '130.00 500.00\n' >"$XCAT_UPTIME_FILE"
run /bin/sh "$STATUS_SCRIPT" console DEGRADED 'waiting for operator' CODE=OPERATOR_WAIT \
'RECOVERY=Review diagnostics'
[ "$status" -eq 0 ]
[ "$(cat "$status_dir/console.env")" = 'SCHEMA=1
STATE=DEGRADED
DETAIL=waiting for operator
STARTED_SECONDS=123
UPDATED_SECONDS=130
CODE=OPERATOR_WAIT
RECOVERY=Review diagnostics' ]
}
@test "the status helper rejects an unsafe component, an unknown state and a bad number" {
run /bin/sh "$STATUS_SCRIPT" ../console READY
[ "$status" -ne 0 ]
[[ "$output" == *'invalid component'* ]]
[ ! -e "$state_dir/console.env" ]
run /bin/sh "$STATUS_SCRIPT" console UNKNOWN
[ "$status" -ne 0 ]
[[ "$output" == *'invalid state'* ]]
run /bin/sh "$STATUS_SCRIPT" console READY '' ATTEMPT=invalid
[ "$status" -ne 0 ]
[[ "$output" == *'invalid numeric field'* ]]
[ ! -e "$status_dir/console.env" ]
}
@test "the maintenance shell reports a failure as the overall state, then prefers the action state" {
mkdir -p "$status_dir"
printf 'STATE=READY\n' >"$status_dir/network.env"
printf 'STATE=FAILED\n' >"$status_dir/extensions.env"
printf 'STATE=READY\n' >"$status_dir/registration.env"
printf 'STATE=IDLE\n' >"$status_dir/action.env"
run /bin/bash -c 'printf "exit\n" | /bin/bash "$1" 2>&1' genesis-maintenance-shell "$SHELL_SCRIPT"
[ "$status" -eq 0 ]
grep -qx 'Overall state: FAILED' <<<"$output"
printf 'STATE=READY\n' >"$status_dir/extensions.env"
run /bin/bash -c 'printf "exit\n" | /bin/bash "$1" 2>&1' genesis-maintenance-shell "$SHELL_SCRIPT"
grep -qx 'Overall state: IDLE' <<<"$output"
}
@test "the maintenance shell runs by its own interpreter, identifies Genesis, and explains the return" {
mkdir -p "$status_dir"
IFS= read -r shebang <"$SHELL_SCRIPT"
[ "$shebang" = '#!/bin/bash' ]
run bash -c 'printf "exit\n" | "$1" "$2" 2>&1' genesis-maintenance-shell "${shebang#\#!}" "$SHELL_SCRIPT"
[ "$status" -eq 0 ]
grep -qx 'xCAT Genesis maintenance shell' <<<"$output"
grep -qx 'Exit returns to the status console.' <<<"$output"
}
@test "the maintenance shell starts an isolated Bash with the genesis prompt" {
mkdir -p "$status_dir" "$root/home"
printf 'echo profile-was-read\n' >"$root/home/.bash_profile"
printf 'echo rc-was-read\n' >"$root/home/.bashrc"
run env HOME="$root/home" /bin/bash -c 'printf "exit\n" | /bin/bash "$1" 2>&1' \
genesis-maintenance-shell "$SHELL_SCRIPT"
[ "$status" -eq 0 ]
[[ "$output" == *'genesis# '* ]]
[[ "$output" != *profile-was-read* ]]
[[ "$output" != *rc-was-read* ]]
}
@test "a status record replaces the previous one through a temporary file in the same directory" {
# mv logs its arguments, then moves.
printf '#!/bin/sh\nprintf "mv %%s\\n" "$*" >>"$XCAT_TEST_LOG"\nexec /bin/mv "$@"\n' >"$bin/mv"
chmod 0755 "$bin/mv"
run /bin/sh "$STATUS_SCRIPT" console READY 'ready'
[ "$status" -eq 0 ]
grep -Eqx "mv -f -- $status_dir/\.console\.[A-Za-z0-9]{6} $status_dir/console\.env" "$XCAT_TEST_LOG"
[ "$(ls -A "$status_dir")" = console.env ]
}