mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-21 15:18:19 +00:00
598 lines
25 KiB
Bash
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 ]
|
|
}
|