2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-12 08:12:33 +00:00

feat(genesis): add provisioning runtime

This commit is contained in:
Vinícius Ferrão
2026-08-20 13:12:17 -03:00
parent 260a3a7079
commit b632a6a94f
30 changed files with 2191 additions and 61 deletions
@@ -0,0 +1,10 @@
[main]
dhcp=internal
dns=default
plugins=keyfile
rc-manager=symlink
[connection]
ipv4.dhcp-timeout=20
ipv6.dhcp-duid=ll
ipv6.dhcp-timeout=20
@@ -0,0 +1,16 @@
SUMMARY = "xCAT Genesis network configuration"
LICENSE = "EPL-1.0"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/EPL-1.0;md5=57f8d5e2b3e98ac6e088986c12bf94e6"
SRC_URI = "file://10-xcat-genesis.conf"
S = "${UNPACKDIR}"
RDEPENDS:${PN} = "networkmanager-daemon networkmanager-nmcli"
do_install() {
install -d ${D}${sysconfdir}/NetworkManager/conf.d
install -m 0644 ${UNPACKDIR}/10-xcat-genesis.conf \
${D}${sysconfdir}/NetworkManager/conf.d/10-xcat-genesis.conf
}
FILES:${PN} = "${sysconfdir}/NetworkManager/conf.d/10-xcat-genesis.conf"
@@ -0,0 +1,273 @@
#!/bin/bash
set -euo pipefail
functions_file=${XCAT_GENESIS_FUNCTIONS:-/usr/libexec/xcat/genesis-functions}
# shellcheck source=genesis-functions
source "$functions_file"
state_dir=${XCAT_STATE_DIR:-/run/xcat}
status_command=${XCAT_STATUS_COMMAND:-/usr/libexec/xcat/genesis-status}
destiny_file=${XCAT_DESTINY_FILE:-$state_dir/destiny}
metadata_file=${XCAT_METADATA_FILE:-$state_dir/xcat-response.env}
network_file=${XCAT_NETWORK_FILE:-$state_dir/genesis.env}
discover_command=${XCAT_DISCOVER_COMMAND:-/usr/libexec/xcat/genesis-discover}
certificate_command=${XCAT_CERTIFICATE_COMMAND:-/usr/libexec/xcat/genesis-getcert}
certificate_file=${XCAT_CERTIFICATE_FILE:-/etc/xcat/cert.pem}
getdestiny_command=${XCAT_GETDESTINY_COMMAND:-getdestiny}
nextdestiny_command=${XCAT_NEXTDESTINY_COMMAND:-nextdestiny}
approved_command_dir=${XCAT_ACTION_COMMAND_DIR:-/usr/libexec/xcat/genesis/actions}
reboot_command=${XCAT_REBOOT_COMMAND:-systemctl}
poweroff_command=${XCAT_POWEROFF_COMMAND:-systemctl}
poll_seconds=${XCAT_ACTION_POLL_SECONDS:-15}
request_timeout=${XCAT_ACTION_REQUEST_TIMEOUT:-20}
operation_timeout=${XCAT_ACTION_OPERATION_TIMEOUT:-300}
maximum_steps=${XCAT_ACTION_MAX_STEPS:-0}
failure_code=ACTION_FAILED
failure_detail='Genesis action failed'
failure_recovery='Review Genesis logs and the assigned xCAT action'
current_action=
current_target=
last_verified_seconds=
[[ "$poll_seconds" =~ ^[0-9]+$ ]] || poll_seconds=15
[[ "$request_timeout" =~ ^[1-9][0-9]*$ ]] || request_timeout=20
[[ "$operation_timeout" =~ ^[1-9][0-9]*$ ]] || operation_timeout=300
[[ "$maximum_steps" =~ ^[0-9]+$ ]] || maximum_steps=0
publish_status() {
local state=$1 detail=$2
local -a fields
shift 2
fields=("ACTION=$current_action" "TARGET=$current_target")
[[ -z $last_verified_seconds ]] \
|| fields+=("VERIFIED_SECONDS=$last_verified_seconds")
genesis_publish_status "$status_command" action xcat-genesis-action \
"$state" "$detail" "${fields[@]}" "$@"
}
# shellcheck disable=SC2329
finish_status() {
local result=$?
trap - EXIT
if ((result != 0)); then
publish_status FAILED "$failure_detail" \
"CODE=$failure_code" "RECOVERY=$failure_recovery"
fi
exit "$result"
}
fail_action() {
failure_code=$1
failure_detail=$2
failure_recovery=$3
logger -t xcat-genesis-action -- "$failure_detail"
exit 1
}
parse_destiny() {
local raw=$1
if ! genesis_parse_destiny "$raw"; then
fail_action INVALID_XCAT_ACTION \
'xCAT returned an invalid action' \
'Correct the assigned node destiny'
fi
current_action=$XCAT_GENESIS_ACTION
current_target=$XCAT_GENESIS_TARGET
}
read_destiny() {
local raw
[[ -r $destiny_file ]] || fail_action XCAT_ACTION_MISSING \
'No xCAT action is available' \
'Retry registration or assign a node destiny'
IFS= read -r raw <"$destiny_file" || true
[[ -n $raw && ${#raw} -le 4096 ]] || fail_action XCAT_ACTION_INVALID \
'The assigned xCAT action is empty or too large' \
'Correct the assigned node destiny'
parse_destiny "$raw"
}
store_destiny() {
local raw=$1 temporary_file
[[ -n $raw && ${#raw} -le 4096 ]] || return 1
umask 077
temporary_file=${destiny_file}.new
printf '%s\n' "$raw" >"$temporary_file"
mv "$temporary_file" "$destiny_file"
parse_destiny "$raw"
}
request_current_action() {
local action
if ! action=$(timeout "$request_timeout" "$getdestiny_command" \
"$XCATDEST" --metadata "$metadata_file" --once); then
return 1
fi
store_destiny "$action" || return 1
last_verified_seconds=$(genesis_uptime_seconds)
}
request_next_action() {
local action
if ! action=$(timeout "$request_timeout" "$nextdestiny_command" \
"$XCATDEST" --metadata "$metadata_file" --once); then
return 1
fi
store_destiny "$action" || return 1
last_verified_seconds=$(genesis_uptime_seconds)
}
run_approved_command() {
local command_name argument_text executor
local -a arguments=()
command_name=${current_target%%[[:space:]]*}
[[ $command_name =~ ^[a-z][a-z0-9._-]{0,63}$ ]] \
|| fail_action ACTION_COMMAND_INVALID \
'xCAT assigned an invalid command name' \
'Use an action packaged in the Genesis image'
executor=$approved_command_dir/$command_name
[[ -f $executor && ! -L $executor && -x $executor ]] \
|| fail_action ACTION_COMMAND_NOT_APPROVED \
"The command is not approved by this Genesis image: $command_name" \
'Use a packaged Genesis action or signed system extension'
argument_text=${current_target#"$command_name"}
argument_text=${argument_text#"${argument_text%%[![:space:]]*}"}
if [[ -n $argument_text ]]; then
read -r -a arguments <<<"$argument_text"
fi
publish_status RUNNING "Running approved action $command_name"
timeout "$operation_timeout" "$executor" "${arguments[@]}" \
|| fail_action ACTION_COMMAND_FAILED \
"The approved action failed: $command_name" \
'Review Genesis logs and the action parameters'
}
prepare_network_boot() {
if ipmitool mc info >/dev/null 2>&1; then
ipmitool chassis bootdev pxe >/dev/null 2>&1 \
|| logger -t xcat-genesis-action -- \
'unable to set the in-band PXE boot override'
fi
}
trap finish_status EXIT HUP INT TERM
[[ -r $network_file ]] || fail_action ACTION_NETWORK_STATE_MISSING \
'Management network state is unavailable' \
'Wait for Genesis networking or review its failure'
# shellcheck disable=SC1090
source "$network_file"
: "${XCATDEST:?xCAT endpoint is missing from network state}"
read_destiny
if [[ $current_action != discover && ! -s $certificate_file ]]; then
publish_status RUNNING 'Requesting the node certificate'
timeout "$operation_timeout" "$certificate_command" \
|| fail_action CERTIFICATE_ACTION_FAILED \
'The node could not obtain an xCAT certificate' \
'Check callback port 300 and xCAT credentials policy'
fi
for ((step = 1; ; step++)); do
if ((maximum_steps > 0 && step > maximum_steps)); then
exit 0
fi
case "$current_action" in
discover)
publish_status RUNNING 'Discovering node hardware'
timeout "$operation_timeout" "$discover_command" \
|| fail_action DISCOVERY_ACTION_FAILED \
'xCAT did not accept the node discovery' \
'Review active discovery methods and node matching'
publish_status RUNNING 'Loading the discovered node assignment'
request_current_action \
|| fail_action DISCOVERY_ASSIGNMENT_FAILED \
'The discovered node has no xCAT assignment' \
'Review the discovered node definition and chain'
timeout "$operation_timeout" "$certificate_command" \
|| fail_action DISCOVERY_CERTIFICATE_FAILED \
'The discovered node could not obtain a certificate' \
'Check callback port 300 and xCAT credentials policy'
;;
standby|shell|offline)
publish_status IDLE 'Waiting for an action from xCAT' \
"NEXT_RETRY_SECONDS=$poll_seconds"
sleep "$poll_seconds"
if ! request_current_action; then
publish_status DEGRADED 'xCAT did not answer the action poll' \
'CODE=XCAT_ACTION_POLL_FAILED' \
'RECOVERY=Check xcatd and the management network' \
"NEXT_RETRY_SECONDS=$poll_seconds"
fi
;;
osimage|ondiscover)
publish_status RUNNING 'Advancing the xCAT action chain'
request_next_action \
|| fail_action XCAT_CHAIN_ADVANCE_FAILED \
'xCAT did not advance the node action chain' \
'Review the node chain and xcatd logs'
;;
runcmd)
run_approved_command
request_next_action \
|| fail_action XCAT_CHAIN_ADVANCE_FAILED \
'xCAT did not advance after the approved action' \
'Review the node chain and xcatd logs'
;;
runimage)
fail_action UNSAFE_LEGACY_ACTION \
'Unsigned runimage actions are not supported' \
'Package the operation as a signed Genesis system extension'
;;
configraid)
fail_action LEGACY_STORAGE_ACTION \
'Legacy configraid actions are not supported' \
'Assign a validated Genesis hardware-provider task'
;;
sysclone)
fail_action LEGACY_SYSCLONE_ACTION \
'Legacy sysclone actions are not supported' \
'Use an xCAT image workflow supported by this Genesis release'
;;
boot|reboot)
publish_status RUNNING 'Advancing the chain before reboot'
request_next_action \
|| fail_action XCAT_CHAIN_ADVANCE_FAILED \
'xCAT did not advance the node action chain' \
'Review the node chain and xcatd logs'
prepare_network_boot
publish_status RUNNING 'Rebooting into the assigned action'
"$reboot_command" reboot
exit 0
;;
install|netboot|statelite)
prepare_network_boot
publish_status RUNNING 'Rebooting into the assigned image'
"$reboot_command" reboot
exit 0
;;
shutdown)
publish_status RUNNING 'Powering off at xCAT request'
"$poweroff_command" poweroff
exit 0
;;
error)
fail_action XCAT_ACTION_ERROR \
"xCAT rejected the action: ${current_target:-no detail}" \
'Correct the node destiny or action chain'
;;
*)
fail_action XCAT_ACTION_UNSUPPORTED \
"Unsupported xCAT action: $current_action" \
'Assign an action supported by this Genesis release'
;;
esac
done
@@ -0,0 +1,53 @@
# Shared Bash helpers for the Genesis runtime.
genesis_publish_status() {
local status_command=$1 component=$2 log_tag=$3
shift 3
"$status_command" "$component" "$@" \
|| logger -t "$log_tag" -- "unable to publish $component status"
}
genesis_uptime_seconds() {
local uptime_file=${1:-${XCAT_UPTIME_FILE:-/proc/uptime}}
local seconds=0
if [[ -r $uptime_file ]]; then
read -r seconds _ <"$uptime_file" || seconds=0
seconds=${seconds%%.*}
[[ $seconds =~ ^[0-9]+$ ]] || seconds=0
fi
printf '%s\n' "$seconds"
}
genesis_parse_destiny() {
local raw=$1 prefix
XCAT_GENESIS_ACTION=
XCAT_GENESIS_TARGET=
[[ -n $raw && ${#raw} -le 4096 ]] || return 1
prefix=${raw%%=*}
XCAT_GENESIS_ACTION=${prefix%%[[:space:]]*}
if [[ $raw = *=* ]]; then
XCAT_GENESIS_TARGET=${raw#*=}
elif [[ $raw = *[[:space:]]* ]]; then
XCAT_GENESIS_TARGET=${raw#"$XCAT_GENESIS_ACTION"}
XCAT_GENESIS_TARGET=${XCAT_GENESIS_TARGET#"${XCAT_GENESIS_TARGET%%[![:space:]]*}"}
fi
[[ $XCAT_GENESIS_ACTION =~ ^[a-z][a-z0-9_-]*$ ]]
}
genesis_component_state() {
local status_dir=$1 component=$2 key value
[[ -r $status_dir/$component.env ]] || return 1
while IFS='=' read -r key value; do
if [[ $key == STATE ]]; then
printf '%s\n' "$value"
return 0
fi
done <"$status_dir/$component.env"
return 1
}
@@ -0,0 +1,39 @@
#!/bin/bash
set -u
functions_file=${XCAT_GENESIS_FUNCTIONS:-/usr/libexec/xcat/genesis-functions}
# shellcheck source=genesis-functions
source "$functions_file"
status_dir=${XCAT_STATUS_DIR:-/run/xcat/status}
status=STARTING
for component in network extensions registration; do
component_state=$(genesis_component_state "$status_dir" "$component") \
|| continue
if [[ ${component_state} == FAILED ]]; then
status=${component_state}
break
fi
done
if [[ ${status} != FAILED ]]; then
for component in action registration network; do
component_state=$(genesis_component_state "$status_dir" "$component") \
|| continue
status=${component_state}
break
done
fi
node=unknown
[[ -r /proc/sys/kernel/hostname ]] && read -r node </proc/sys/kernel/hostname
printf '\n'
printf 'xCAT Genesis maintenance shell\n'
printf 'Node: %s\n' "${node}"
printf 'Overall state: %s\n' "${status}"
printf 'Exit returns to the status console.\n\n'
export HISTFILE=/dev/null
export PS1='genesis# '
exec /bin/bash --noprofile --norc -i
@@ -0,0 +1,87 @@
#!/bin/sh
set -eu
registration_file=${XCAT_REGISTRATION_STATUS_FILE:-/run/xcat/status/registration.env}
meminfo_file=${XCAT_MEMINFO_FILE:-/proc/meminfo}
uptime_file=${XCAT_UPTIME_FILE:-/proc/uptime}
fail() {
printf 'genesis-metrics: %s\n' "$*" >&2
exit 1
}
case "$#" in
0) output_file= ;;
2)
[ "$1" = '--output' ] || fail 'usage: genesis-metrics [--output FILE]'
output_file=$2
;;
*) fail 'usage: genesis-metrics [--output FILE]' ;;
esac
schema=
registration_state=
boot_ready_seconds=
while IFS='=' read -r name value; do
case "$name" in
SCHEMA) schema=$value ;;
STATE) registration_state=$value ;;
UPDATED_SECONDS) boot_ready_seconds=$value ;;
esac
done <"$registration_file"
[ "$schema" = 1 ] && [ "$registration_state" = ACTION_RECEIVED ] \
|| fail 'Genesis has not received an xCAT action'
case "$boot_ready_seconds" in
''|*[!0-9]*) fail 'registration time is invalid' ;;
esac
read -r capture_uptime_seconds _ <"$uptime_file"
capture_uptime_seconds=${capture_uptime_seconds%%.*}
case "$capture_uptime_seconds" in
''|*[!0-9]*) fail 'uptime is invalid' ;;
esac
memory_total_kib=
memory_available_kib=
while read -r name value _; do
case "$name" in
MemTotal:) memory_total_kib=$value ;;
MemAvailable:) memory_available_kib=$value ;;
esac
done <"$meminfo_file"
case "$memory_total_kib" in
''|*[!0-9]*) fail 'total memory is invalid' ;;
esac
case "$memory_available_kib" in
''|*[!0-9]*) fail 'available memory is invalid' ;;
esac
[ "$memory_available_kib" -le "$memory_total_kib" ] \
|| fail 'available memory exceeds total memory'
memory_used_kib=$((memory_total_kib - memory_available_kib))
write_metrics() {
printf 'SCHEMA=1\n'
printf 'BOOT_READY_SECONDS=%s\n' "$boot_ready_seconds"
printf 'CAPTURE_UPTIME_SECONDS=%s\n' "$capture_uptime_seconds"
printf 'MEMORY_TOTAL_KIB=%s\n' "$memory_total_kib"
printf 'MEMORY_AVAILABLE_KIB=%s\n' "$memory_available_kib"
printf 'MEMORY_USED_KIB=%s\n' "$memory_used_kib"
}
if [ -z "$output_file" ]; then
write_metrics
exit 0
fi
case "$output_file" in
*/*) output_dir=${output_file%/*}; [ -n "$output_dir" ] || output_dir=/ ;;
*) output_dir=. ;;
esac
install -d -m 0755 "$output_dir"
temporary_file=$(mktemp "$output_dir/.metrics.XXXXXX")
trap 'rm -f -- "$temporary_file"' EXIT HUP INT TERM
write_metrics >"$temporary_file"
chmod 0644 "$temporary_file"
mv -f -- "$temporary_file" "$output_file"
trap - EXIT HUP INT TERM
@@ -0,0 +1,55 @@
#!/bin/bash
set -euo pipefail
state_dir=${XCAT_STATE_DIR:-/run/xcat}
network_file=${XCAT_NETWORK_FILE:-$state_dir/genesis.env}
network_state_command=${XCAT_NETWORK_STATE_COMMAND:-/usr/libexec/xcat/genesis-network-state}
sys_class_net=${XCAT_SYS_CLASS_NET:-/sys/class/net}
callback=${1:-restart}
[[ -r $network_file ]] || exit 1
# shellcheck disable=SC1090
source "$network_file"
[[ ${XCAT_NETWORK_METHOD:-auto} = auto ]] || exit 0
: "${XCAT_INTERFACE:?management interface is missing from network state}"
declare -a requested_interfaces=()
if [[ $callback = 'restart ('*')' ]]; then
requested=${callback#restart (}
requested=${requested%)}
IFS='|' read -r -a requested_interfaces <<<"$requested"
fi
if ((${#requested_interfaces[@]} == 0)); then
requested_interfaces=("$XCAT_INTERFACE")
fi
for interface in "${requested_interfaces[@]}"; do
[[ $interface =~ ^[A-Za-z0-9_.-]+$ && -e $sys_class_net/$interface ]] \
|| exit 1
done
if [[ $callback = 'restart ('*')' ]]; then
while IFS=: read -r interface device_type; do
[[ $device_type = ethernet ]] || continue
keep=0
for requested_interface in "${requested_interfaces[@]}"; do
if [[ $interface = "$requested_interface" ]]; then
keep=1
break
fi
done
((keep == 1)) \
|| nmcli --wait 10 device disconnect "$interface" >/dev/null 2>&1 \
|| true
done < <(nmcli -t -f DEVICE,TYPE device status 2>/dev/null || true)
fi
for interface in "${requested_interfaces[@]}"; do
nmcli --wait 10 device disconnect "$interface" >/dev/null 2>&1 || true
done
for interface in "${requested_interfaces[@]}"; do
nmcli --wait 30 device connect "$interface" >/dev/null
done
"$network_state_command"
@@ -0,0 +1,327 @@
#!/bin/bash
set -eu
functions_file=${XCAT_GENESIS_FUNCTIONS:-/usr/libexec/xcat/genesis-functions}
# shellcheck source=genesis-functions
source "$functions_file"
log() {
logger -t xcat-genesis-network-state -- "$*"
}
status_command=${XCAT_STATUS_COMMAND:-/usr/libexec/xcat/genesis-status}
failure_code=NETWORK_CONFIGURATION_FAILED
failure_detail='Network configuration failed'
failure_recovery='Check the management network and boot parameters'
publish_status() {
genesis_publish_status "$status_command" network \
xcat-genesis-network-state "$@"
}
finish_status() {
local result=$?
trap - EXIT
if [ "$result" -ne 0 ]; then
publish_status FAILED "$failure_detail" \
"CODE=$failure_code" "RECOVERY=$failure_recovery"
fi
exit "$result"
}
fail_network() {
failure_code=$1
failure_detail=$2
failure_recovery=$3
log "$failure_detail"
exit 1
}
trap finish_status EXIT
publish_status CONFIGURING_NETWORK 'Selecting the management network'
tcp_probe() {
local probe_pid probe_wait
( exec 3<>"/dev/tcp/$master/$port" ) 2>/dev/null &
probe_pid=$!
for ((probe_wait = 0; probe_wait < 10; probe_wait++)); do
if ! kill -0 "$probe_pid" 2>/dev/null; then
wait "$probe_pid"
return $?
fi
sleep 0.1
done
kill "$probe_pid" 2>/dev/null || true
wait "$probe_pid" 2>/dev/null || true
return 1
}
netmask_to_prefix() {
local mask=$1 octet prefix=0 partial=0
local -a octets
IFS=. read -r -a octets <<<"$mask"
[ "${#octets[@]}" -eq 4 ] || return 1
for octet in "${octets[@]}"; do
[ "$partial" -eq 0 ] || [ "$octet" = 0 ] || return 1
case "$octet" in
255) prefix=$((prefix + 8)) ;;
254) prefix=$((prefix + 7)); partial=1 ;;
252) prefix=$((prefix + 6)); partial=1 ;;
248) prefix=$((prefix + 5)); partial=1 ;;
240) prefix=$((prefix + 4)); partial=1 ;;
224) prefix=$((prefix + 3)); partial=1 ;;
192) prefix=$((prefix + 2)); partial=1 ;;
128) prefix=$((prefix + 1)); partial=1 ;;
0) partial=1 ;;
*) return 1 ;;
esac
done
printf '%s\n' "$prefix"
}
destination=
boot_mac=
host_ip=
netmask=
gateway=
cmdline_file=${XCAT_CMDLINE_FILE:-/proc/cmdline}
sys_class_net=${XCAT_SYS_CLASS_NET:-/sys/class/net}
state_dir=${XCAT_STATE_DIR:-/run/xcat}
uptime_file=${XCAT_UPTIME_FILE:-/proc/uptime}
declare -a cmdline_arguments
read -r -a cmdline_arguments <"$cmdline_file"
for argument in "${cmdline_arguments[@]}"; do
case "$argument" in
xcatd=*) destination=${argument#xcatd=} ;;
BOOTIF=*) boot_mac=${argument#BOOTIF=} ;;
hostip=*|ipaddr=*) host_ip=${argument#*=} ;;
netmask=*) netmask=${argument#netmask=} ;;
gateway=*) gateway=${argument#gateway=} ;;
esac
done
[ -n "$destination" ] || fail_network INVALID_BOOT_PARAMETERS \
'xcatd is missing from the kernel command line' \
'Add xcatd=<address>:<port> to the kernel command line'
if [[ "$destination" =~ ^\[([^]]+)\]:([0-9]+)$ ]]; then
master=${BASH_REMATCH[1]}
port=${BASH_REMATCH[2]}
elif [[ "$destination" =~ ^([^:]+):([0-9]+)$ ]]; then
master=${BASH_REMATCH[1]}
port=${BASH_REMATCH[2]}
elif [[ "$destination" = *:* ]]; then
master=$destination
port=3001
destination="[$master]:$port"
else
master=$destination
port=3001
destination="$master:$port"
fi
[[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ] || {
fail_network INVALID_XCAT_ENDPOINT "Invalid xcatd port: $port" \
'Correct the xcatd endpoint in the boot parameters'
}
preferred_interface=
if [ -n "$boot_mac" ]; then
boot_mac=${boot_mac#01-}
boot_mac=$(printf '%s' "$boot_mac" | tr 'A-Z-' 'a-z:')
for address_file in "$sys_class_net"/*/address; do
[ -r "$address_file" ] || continue
if [ "$(<"$address_file")" = "$boot_mac" ]; then
preferred_interface=${address_file%/address}
preferred_interface=${preferred_interface##*/}
break
fi
done
[ -n "$preferred_interface" ] || fail_network BOOT_INTERFACE_NOT_FOUND \
"BOOTIF device not found: $boot_mac" \
'Check the PXE interface and BOOTIF value'
fi
network_method=auto
if [ -n "$host_ip" ] || [ -n "$netmask" ] || [ -n "$gateway" ]; then
network_method=static
[ -n "$host_ip" ] && [ -n "$netmask" ] && [ -n "$gateway" ] \
|| fail_network INVALID_STATIC_NETWORK \
'Static networking requires hostip, netmask, and gateway' \
'Supply all static network boot parameters or remove them'
[ -n "$preferred_interface" ] || fail_network INVALID_STATIC_NETWORK \
'Static networking requires BOOTIF' \
'Add BOOTIF for the management interface'
if [[ "$host_ip" = */* ]]; then
address=$host_ip
elif [[ "$netmask" =~ ^[0-9]+$ ]]; then
address="$host_ip/$netmask"
elif [[ "$host_ip" = *:* ]]; then
fail_network INVALID_STATIC_NETWORK \
"Invalid IPv6 prefix length: $netmask" \
'Use an IPv6 prefix length from 0 to 128'
else
prefix=$(netmask_to_prefix "$netmask") \
|| fail_network INVALID_STATIC_NETWORK \
"Invalid IPv4 netmask: $netmask" \
'Use a valid dotted netmask or prefix length'
address="$host_ip/$prefix"
fi
nmcli connection delete xcat-genesis-static >/dev/null 2>&1 || true
if [[ "$host_ip" = *:* ]]; then
network_arguments=(
ipv4.method disabled
ipv6.method manual
ipv6.addresses "$address"
ipv6.gateway "$gateway"
)
else
network_arguments=(
ipv4.method manual
ipv4.addresses "$address"
ipv4.gateway "$gateway"
ipv6.method auto
)
fi
if ! nmcli connection add type ethernet ifname "$preferred_interface" \
con-name xcat-genesis-static "${network_arguments[@]}" \
connection.autoconnect yes >/dev/null; then
fail_network STATIC_NETWORK_REJECTED \
"NetworkManager rejected the static configuration on $preferred_interface" \
'Check the address, gateway, and interface'
fi
if ! nmcli --wait 20 connection up xcat-genesis-static >/dev/null; then
fail_network STATIC_NETWORK_FAILED \
"Static network activation failed on $preferred_interface" \
'Check link state and the static network boot parameters'
fi
fi
selected_interface=
source_address=
publish_status WAITING_FOR_LINK "Waiting for a route to $destination" \
'ATTEMPT=1' 'ATTEMPT_LIMIT=60'
for ((attempt = 0; attempt < 60; attempt++)); do
if [[ "$master" = *:* ]]; then
route=$(ip -6 -o route get "$master" 2>/dev/null || true)
elif [[ "$master" =~ ^[0-9]+(\.[0-9]+){3}$ ]]; then
route=$(ip -4 -o route get "$master" 2>/dev/null || true)
else
route=$(ip -4 -o route get "$master" 2>/dev/null \
|| ip -6 -o route get "$master" 2>/dev/null || true)
fi
route_interface=$(awk '{for (i=1; i<=NF; i++) if ($i == "dev") print $(i+1)}' <<<"$route")
route_source=$(awk '{for (i=1; i<=NF; i++) if ($i == "src") print $(i+1)}' <<<"$route")
if [ -n "$route_interface" ]; then
state=$(nmcli -g GENERAL.STATE device show "$route_interface" 2>/dev/null || true)
if [[ "$state" = 100* ]]; then
if [ -z "$preferred_interface" ] || \
[ "$route_interface" = "$preferred_interface" ] || \
[ "$attempt" -ge 20 ]; then
selected_interface=$route_interface
source_address=$route_source
break
fi
fi
fi
publish_status WAITING_FOR_LINK "Waiting for a route to $destination" \
"ATTEMPT=$((attempt + 1))" 'ATTEMPT_LIMIT=60' \
'NEXT_RETRY_SECONDS=1'
sleep 1
done
[ -n "$selected_interface" ] || fail_network MANAGEMENT_ROUTE_UNAVAILABLE \
"No interface can route to $master" \
'Check link, DHCP or static settings, VLANs, and routing'
master_reachable=0
publish_status CONTACTING_XCAT "Contacting xCAT at $destination" \
'ATTEMPT=1' 'ATTEMPT_LIMIT=10'
for ((attempt = 0; attempt < 10; attempt++)); do
if tcp_probe; then
master_reachable=1
break
fi
publish_status CONTACTING_XCAT "Contacting xCAT at $destination" \
"ATTEMPT=$((attempt + 1))" 'ATTEMPT_LIMIT=10' \
'NEXT_RETRY_SECONDS=1'
sleep 1
done
[ "$master_reachable" -eq 1 ] || fail_network XCAT_ENDPOINT_UNREACHABLE \
"Cannot connect to $destination" \
'Check xcatd, firewalls, routing, and the configured port'
route_gateway=$(awk '{for (i=1; i<=NF; i++) if ($i == "via") print $(i+1)}' <<<"$route")
source_prefixed_address=
if [[ "$source_address" = *:* ]]; then
address_family=-6
else
address_family=-4
fi
while read -r candidate; do
if [ "${candidate%%/*}" = "$source_address" ]; then
source_prefixed_address=$candidate
break
fi
done < <(ip "$address_family" -o address show dev "$selected_interface" \
scope global 2>/dev/null | awk '{print $4}')
[ -n "$source_prefixed_address" ] || source_prefixed_address=$source_address
actual_gateway=${route_gateway:-$gateway}
dns_servers=
while IFS= read -r dns_line; do
IFS='|' read -r -a dns_candidates <<<"$dns_line"
for dns_server in "${dns_candidates[@]}"; do
dns_server=${dns_server//[[:space:]]/}
[ -n "$dns_server" ] || continue
if [ -n "$dns_servers" ]; then
dns_servers="$dns_servers,$dns_server"
else
dns_servers=$dns_server
fi
done
done < <(nmcli --terse --escape no -g IP4.DNS,IP6.DNS \
device show "$selected_interface" \
2>/dev/null || true)
link_state=unknown
[ ! -r "$sys_class_net/$selected_interface/operstate" ] \
|| read -r link_state <"$sys_class_net/$selected_interface/operstate"
mac_address=
[ ! -r "$sys_class_net/$selected_interface/address" ] \
|| read -r mac_address <"$sys_class_net/$selected_interface/address"
verified_seconds=$(genesis_uptime_seconds "$uptime_file")
mkdir -p "$state_dir"
chmod 0755 "$state_dir"
umask 077
state_file=$state_dir/genesis.env
temporary_file=${state_file}.new
{
printf 'XCATDEST=%s\n' "$destination"
printf 'XCATMASTER=%s\n' "$master"
printf 'XCATPORT=%s\n' "$port"
printf 'XCAT_INTERFACE=%s\n' "$selected_interface"
printf 'XCAT_SOURCE_ADDRESS=%s\n' "$source_address"
printf 'XCAT_SOURCE_PREFIXED_ADDRESS=%s\n' "$source_prefixed_address"
printf 'XCAT_GATEWAY=%s\n' "$actual_gateway"
printf 'XCAT_DNS_SERVERS=%s\n' "$dns_servers"
printf 'XCAT_NETWORK_METHOD=%s\n' "$network_method"
printf 'XCAT_LINK_STATE=%s\n' "$link_state"
printf 'XCAT_MAC_ADDRESS=%s\n' "$mac_address"
printf 'XCAT_VERIFIED_SECONDS=%s\n' "$verified_seconds"
} >"$temporary_file"
mv "$temporary_file" "$state_file"
publish_status READY "Management network ready on $selected_interface" \
"VERIFIED_SECONDS=$verified_seconds"
log "using $selected_interface to reach $destination"
@@ -0,0 +1,120 @@
#!/bin/bash
set -eu
functions_file=${XCAT_GENESIS_FUNCTIONS:-/usr/libexec/xcat/genesis-functions}
# shellcheck source=genesis-functions
source "$functions_file"
state_dir=${XCAT_STATE_DIR:-/run/xcat}
cmdline_file=${XCAT_CMDLINE_FILE:-/proc/cmdline}
status_command=${XCAT_STATUS_COMMAND:-/usr/libexec/xcat/genesis-status}
metadata_file=$state_dir/xcat-response.env
kernel_destiny=
failure_code=XCAT_REGISTRATION_FAILED
failure_detail='xCAT registration failed'
failure_recovery='Check xcatd, certificates, and the management network'
maximum_attempts=${XCAT_REGISTRATION_ATTEMPTS:-6}
retry_seconds=${XCAT_REGISTRATION_RETRY_SECONDS:-5}
request_timeout=${XCAT_REGISTRATION_REQUEST_TIMEOUT:-10}
declare -a cmdline_arguments
[[ "$maximum_attempts" =~ ^[1-9][0-9]*$ ]] \
|| maximum_attempts=6
[[ "$retry_seconds" =~ ^[0-9]+$ ]] \
|| retry_seconds=5
[[ "$request_timeout" =~ ^[1-9][0-9]*$ ]] \
|| request_timeout=10
publish_status() {
genesis_publish_status "$status_command" registration \
xcat-genesis-register "$@"
}
finish_status() {
local result=$?
trap - EXIT
if [ "$result" -ne 0 ]; then
publish_status FAILED "$failure_detail" \
"CODE=$failure_code" "RECOVERY=$failure_recovery"
fi
exit "$result"
}
trap finish_status EXIT
read -r -a cmdline_arguments <"$cmdline_file"
for argument in "${cmdline_arguments[@]}"; do
case "$argument" in
destiny=*) kernel_destiny=${argument#destiny=} ;;
esac
done
selected_destiny=
for ((attempt = 1; attempt <= maximum_attempts; attempt++)); do
publish_status CONTACTING_XCAT 'Requesting an action from xCAT' \
"ATTEMPT=$attempt" "ATTEMPT_LIMIT=$maximum_attempts"
if [ -n "$kernel_destiny" ]; then
if timeout "$request_timeout" getdestiny "$XCATDEST" \
--metadata "$metadata_file" --once >/dev/null; then
selected_destiny=$kernel_destiny
break
fi
elif selected_destiny=$(timeout "$request_timeout" getdestiny "$XCATDEST" \
--metadata "$metadata_file" --once); then
break
fi
selected_destiny=
if [ "$attempt" -lt "$maximum_attempts" ]; then
publish_status CONTACTING_XCAT 'xCAT has not answered yet' \
"ATTEMPT=$attempt" "ATTEMPT_LIMIT=$maximum_attempts" \
"NEXT_RETRY_SECONDS=$retry_seconds"
sleep "$retry_seconds"
fi
done
[ -n "$selected_destiny" ] || {
failure_code=XCAT_RESPONSE_UNAVAILABLE
failure_detail="No valid response from xCAT after $maximum_attempts attempts"
failure_recovery='Check xcatd, certificates, and the assigned node action'
logger -t xcat-genesis-register -- "$failure_detail"
exit 1
}
genesis_parse_destiny "$selected_destiny" || {
failure_code=INVALID_XCAT_ACTION
failure_detail='xCAT returned an invalid action'
failure_recovery='Correct the node destiny on the xCAT management node'
logger -t xcat-genesis-register -- "$failure_detail"
exit 1
}
selected_action=$XCAT_GENESIS_ACTION
selected_target=$XCAT_GENESIS_TARGET
confirmed_node=
if [ -r "$metadata_file" ]; then
while IFS='=' read -r name value; do
case "$name" in
XCAT_NODE_NAME) confirmed_node=$value ;;
esac
done <"$metadata_file"
fi
verified_seconds=$(genesis_uptime_seconds)
umask 077
temporary_file=$state_dir/destiny.new
printf '%s\n' "$selected_destiny" >"$temporary_file"
mv "$temporary_file" "$state_dir/destiny"
declare -a status_fields
status_fields=(
"VERIFIED_SECONDS=$verified_seconds"
"ACTION=$selected_action"
"TARGET=$selected_target"
)
[ -z "$confirmed_node" ] || status_fields+=("NODE_NAME=$confirmed_node")
publish_status ACTION_RECEIVED "Action $selected_action received" \
"${status_fields[@]}"
logger -t xcat-genesis-register -- \
"received action from $XCATDEST: $selected_action"
@@ -0,0 +1,110 @@
#!/bin/sh
set -eu
LC_ALL=C
export LC_ALL
status_dir=${XCAT_STATUS_DIR:-/run/xcat/status}
uptime_file=${XCAT_UPTIME_FILE:-/proc/uptime}
fail() {
printf 'genesis-status: %s\n' "$*" >&2
exit 1
}
[ "$#" -ge 2 ] \
|| fail 'usage: genesis-status COMPONENT STATE [DETAIL] [NAME=VALUE ...]'
component=$1
state=$2
shift 2
detail=${1:-}
[ "$#" -eq 0 ] || shift
case "$component" in
''|*[!a-z0-9-]*) fail 'invalid component' ;;
esac
[ "${#component}" -le 32 ] || fail 'invalid component'
case "$state" in
STARTING|IDLE|WAITING_FOR_LINK|CONFIGURING_NETWORK|CONTACTING_XCAT|ACTION_RECEIVED|RUNNING|READY|DEGRADED|FAILED) ;;
*) fail 'invalid state' ;;
esac
detail=$(printf '%s' "$detail" \
| tr '\r\n\t' ' ' \
| tr -cd '\040-\176' \
| cut -c 1-160)
uptime=0
if [ -r "$uptime_file" ]; then
read -r uptime _ <"$uptime_file" || uptime=0
uptime=${uptime%%.*}
case "$uptime" in
''|*[!0-9]*) uptime=0 ;;
esac
fi
started_seconds=$uptime
status_file=$status_dir/$component.env
if [ -r "$status_file" ]; then
previous_schema=
previous_state=
previous_started=
while IFS='=' read -r name value; do
case "$name" in
SCHEMA) previous_schema=$value ;;
STATE) previous_state=$value ;;
STARTED_SECONDS) previous_started=$value ;;
esac
done <"$status_file"
case "$previous_started" in
''|*[!0-9]*) ;;
*)
if [ "$previous_schema" = 1 ] && [ "$previous_state" = "$state" ]; then
started_seconds=$previous_started
fi
;;
esac
fi
install -d -m 0755 "$status_dir"
temporary_file=$(mktemp "$status_dir/.${component}.XXXXXX")
trap 'rm -f -- "$temporary_file"' EXIT HUP INT TERM
{
printf 'SCHEMA=1\n'
printf 'STATE=%s\n' "$state"
printf 'DETAIL=%s\n' "$detail"
printf 'STARTED_SECONDS=%s\n' "$started_seconds"
printf 'UPDATED_SECONDS=%s\n' "$uptime"
seen_fields=' '
for field in "$@"; do
case "$field" in
*=*) name=${field%%=*}; value=${field#*=} ;;
*) fail 'invalid field' ;;
esac
case "$name" in
CODE|RECOVERY|ATTEMPT|ATTEMPT_LIMIT|NEXT_RETRY_SECONDS|VERIFIED_SECONDS|NODE_NAME|ACTION|TARGET|PROGRESS_PERCENT) ;;
*) fail 'invalid field name' ;;
esac
case "$seen_fields" in
*" $name "*) fail 'duplicate field name' ;;
esac
seen_fields="$seen_fields$name "
value=$(printf '%s' "$value" \
| tr '\r\n\t' ' ' \
| tr -cd '\040-\176' \
| cut -c 1-240)
case "$name" in
ATTEMPT|ATTEMPT_LIMIT|NEXT_RETRY_SECONDS|VERIFIED_SECONDS|PROGRESS_PERCENT)
case "$value" in
''|*[!0-9]*) fail 'invalid numeric field' ;;
esac
;;
esac
printf '%s=%s\n' "$name" "$value"
done
} >"$temporary_file"
chmod 0644 "$temporary_file"
mv -f -- "$temporary_file" "$status_dir/$component.env"
trap - EXIT HUP INT TERM
@@ -0,0 +1,16 @@
[Unit]
Description=Execute the assigned xCAT Genesis action
Requires=xcat-genesis-register.service
After=xcat-genesis-register.service
StartLimitIntervalSec=0
[Service]
Type=simple
EnvironmentFile=/run/xcat/genesis.env
ExecStart=/usr/libexec/xcat/genesis-action
Restart=on-failure
RestartSec=10
TimeoutStartSec=infinity
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,13 @@
[Unit]
Description=Record xCAT Genesis boot metrics
ConditionKernelCommandLine=xcatd
Requires=xcat-genesis-register.service
After=xcat-genesis-register.service
[Service]
Type=oneshot
ExecStart=/usr/libexec/xcat/genesis-metrics --output /run/xcat/metrics.env
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,8 @@
[Unit]
Description=xCAT Genesis management network is ready
ConditionKernelCommandLine=xcatd
Requires=xcat-genesis-network-state.service
After=xcat-genesis-network-state.service
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,12 @@
[Unit]
Description=Select the xCAT Genesis management network
ConditionKernelCommandLine=xcatd
Wants=NetworkManager.service
After=NetworkManager.service
Before=xcat-genesis-network-ready.target
[Service]
Type=oneshot
ExecStart=/usr/libexec/xcat/genesis-network-state
RemainAfterExit=yes
TimeoutStartSec=120
@@ -0,0 +1,15 @@
[Unit]
Description=Register Genesis with xCAT
ConditionKernelCommandLine=xcatd
Requires=xcat-genesis-network-ready.target xcat-genesis-extensions.service
After=xcat-genesis-network-ready.target xcat-genesis-extensions.service
[Service]
Type=oneshot
EnvironmentFile=/run/xcat/genesis.env
ExecStart=/usr/libexec/xcat/genesis-register
RemainAfterExit=yes
TimeoutStartSec=90
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,83 @@
SUMMARY = "xCAT Genesis systemd units"
LICENSE = "EPL-1.0"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/EPL-1.0;md5=57f8d5e2b3e98ac6e088986c12bf94e6"
SRC_URI = "file://00-xcat-genesis.preset \
file://genesis-functions \
file://genesis-maintenance-shell \
file://genesis-action \
file://genesis-metrics \
file://genesis-network-refresh \
file://genesis-network-state \
file://genesis-register \
file://genesis-status \
file://xcat-genesis-action.service \
file://xcat-genesis-metrics.service \
file://xcat-genesis-network-ready.target \
file://xcat-genesis-network-state.service \
file://xcat-genesis-register.service \
"
S = "${UNPACKDIR}"
inherit systemd
RDEPENDS:${PN} = "bash coreutils ipmitool iproute2 networkmanager-nmcli xcat-genesis-discovery xcat-genesis-protocol"
SYSTEMD_SERVICE:${PN} = "xcat-genesis-action.service \
xcat-genesis-metrics.service \
xcat-genesis-network-ready.target \
xcat-genesis-register.service \
"
SYSTEMD_AUTO_ENABLE = "enable"
do_install() {
install -d ${D}${libexecdir}/xcat
install -m 0644 ${UNPACKDIR}/genesis-functions \
${D}${libexecdir}/xcat/genesis-functions
install -m 0755 ${UNPACKDIR}/genesis-maintenance-shell \
${D}${libexecdir}/xcat/genesis-maintenance-shell
install -m 0755 ${UNPACKDIR}/genesis-action \
${D}${libexecdir}/xcat/genesis-action
install -m 0755 ${UNPACKDIR}/genesis-metrics \
${D}${libexecdir}/xcat/genesis-metrics
install -m 0755 ${UNPACKDIR}/genesis-network-refresh \
${D}${libexecdir}/xcat/genesis-network-refresh
install -m 0755 ${UNPACKDIR}/genesis-network-state \
${D}${libexecdir}/xcat/genesis-network-state
install -m 0755 ${UNPACKDIR}/genesis-register \
${D}${libexecdir}/xcat/genesis-register
install -m 0755 ${UNPACKDIR}/genesis-status \
${D}${libexecdir}/xcat/genesis-status
install -d ${D}${systemd_system_unitdir}
install -m 0644 ${UNPACKDIR}/xcat-genesis-action.service \
${D}${systemd_system_unitdir}/xcat-genesis-action.service
install -m 0644 ${UNPACKDIR}/xcat-genesis-metrics.service \
${D}${systemd_system_unitdir}/xcat-genesis-metrics.service
install -m 0644 ${UNPACKDIR}/xcat-genesis-network-ready.target \
${D}${systemd_system_unitdir}/xcat-genesis-network-ready.target
install -m 0644 ${UNPACKDIR}/xcat-genesis-network-state.service \
${D}${systemd_system_unitdir}/xcat-genesis-network-state.service
install -m 0644 ${UNPACKDIR}/xcat-genesis-register.service \
${D}${systemd_system_unitdir}/xcat-genesis-register.service
install -d ${D}${systemd_unitdir}/system-preset
install -m 0644 ${UNPACKDIR}/00-xcat-genesis.preset \
${D}${systemd_unitdir}/system-preset/00-xcat-genesis.preset
}
FILES:${PN} = "${libexecdir}/xcat/genesis-maintenance-shell \
${libexecdir}/xcat/genesis-functions \
${libexecdir}/xcat/genesis-action \
${libexecdir}/xcat/genesis-metrics \
${libexecdir}/xcat/genesis-network-refresh \
${libexecdir}/xcat/genesis-network-state \
${libexecdir}/xcat/genesis-register \
${libexecdir}/xcat/genesis-status \
${systemd_system_unitdir}/xcat-genesis-action.service \
${systemd_system_unitdir}/xcat-genesis-metrics.service \
${systemd_system_unitdir}/xcat-genesis-network-ready.target \
${systemd_system_unitdir}/xcat-genesis-network-state.service \
${systemd_system_unitdir}/xcat-genesis-register.service \
${systemd_unitdir}/system-preset/00-xcat-genesis.preset \
"
@@ -0,0 +1,12 @@
#!/bin/bash
set -eu
request=
IFS= read -r request || true
if [[ $request != *CREDOKBYYOU?* ]]; then
logger -t xcat-genesis-credential-callback -- \
'ignored an invalid credential check'
exit 1
fi
printf '%s\n' CREDOKBYME
@@ -0,0 +1,317 @@
#!/bin/bash
set -euo pipefail
state_dir=${XCAT_STATE_DIR:-/run/xcat}
status_command=${XCAT_STATUS_COMMAND:-/usr/libexec/xcat/genesis-status}
network_file=${XCAT_NETWORK_FILE:-$state_dir/genesis.env}
response_file=${XCAT_DISCOVERY_RESPONSE_FILE:-$state_dir/discovery-response}
network_refresh_command=${XCAT_NETWORK_REFRESH_COMMAND:-/usr/libexec/xcat/genesis-network-refresh}
sys_root=${XCAT_SYS_ROOT:-}
proc_root=${XCAT_PROC_ROOT:-/proc}
key_dir=${XCAT_KEY_DIR:-/etc/xcat}
maximum_attempts=${XCAT_DISCOVERY_ATTEMPTS:-36}
retry_seconds=${XCAT_DISCOVERY_RETRY_SECONDS:-5}
ipmitool_command=${XCAT_IPMITOOL_COMMAND:-ipmitool}
lldp_command=${XCAT_LLDP_COMMAND:-lldpcli}
failure_code=DISCOVERY_FAILED
failure_detail='Node discovery failed'
failure_recovery='Check xCAT discovery policy and the management network'
temporary_dir=
[[ "$maximum_attempts" =~ ^[1-9][0-9]*$ ]] || maximum_attempts=36
[[ "$retry_seconds" =~ ^[0-9]+$ ]] || retry_seconds=5
publish_status() {
"$status_command" discovery "$@" \
|| logger -t xcat-genesis-discover -- 'unable to publish discovery status'
}
# shellcheck disable=SC2329
finish_status() {
local result=$?
trap - EXIT
[[ -z $temporary_dir ]] || rm -rf -- "$temporary_dir"
if ((result != 0)); then
publish_status FAILED "$failure_detail" \
"CODE=$failure_code" "RECOVERY=$failure_recovery"
fi
exit "$result"
}
fail_discovery() {
failure_code=$1
failure_detail=$2
failure_recovery=$3
logger -t xcat-genesis-discover -- "$failure_detail"
exit 1
}
xml_escape() {
local value=$1
value=${value//&/\&amp;}
value=${value//</\&lt;}
value=${value//>/\&gt;}
value=${value//\"/\&quot;}
value=${value//\'/\&apos;}
printf '%s' "$value"
}
append_element() {
local name=$1 value=${2:-}
[[ -n $value ]] || return 0
printf '<%s>%s</%s>\n' "$name" "$(xml_escape "$value")" "$name" \
>>"$unsigned_packet"
}
read_value() {
local value_file=$1
[[ -r $value_file ]] || return 1
tr -d '\000\r\n' <"$value_file" | head -c 512
}
send_packet() {
if [[ -n ${XCAT_DISCOVERY_SEND_COMMAND:-} ]]; then
"$XCAT_DISCOVERY_SEND_COMMAND" "$compressed_packet" "$XCATMASTER" "$XCATPORT"
return
fi
/usr/libexec/xcat/genesis-udp-send \
"$compressed_packet" "$XCATMASTER" "$XCATPORT"
}
trap finish_status EXIT HUP INT TERM
[[ -r $network_file ]] || fail_discovery DISCOVERY_NETWORK_STATE_MISSING \
'Management network state is unavailable' \
'Wait for Genesis networking or review its failure'
# shellcheck disable=SC1090
source "$network_file"
: "${XCATMASTER:?xCAT master is missing from network state}"
: "${XCATPORT:?xCAT port is missing from network state}"
: "${XCAT_INTERFACE:?management interface is missing from network state}"
install -d -m 0700 "$key_dir"
if [[ ! -s $key_dir/privkey.pem ]]; then
umask 077
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out "$key_dir/privkey.pem" >/dev/null 2>&1 \
|| fail_discovery DISCOVERY_KEY_FAILED \
'Unable to create the discovery identity' \
'Review entropy and OpenSSL diagnostics'
fi
openssl pkey -in "$key_dir/privkey.pem" -noout >/dev/null 2>&1 \
|| fail_discovery DISCOVERY_KEY_INVALID \
'The discovery identity is invalid' \
'Replace the damaged Genesis runtime state and retry'
temporary_dir=$(mktemp -d "${TMPDIR:-/tmp}/genesis-discovery.XXXXXX")
unsigned_packet=$temporary_dir/request.xml
signed_packet=$temporary_dir/request-signed.xml
signature_file=$temporary_dir/signature
compressed_packet=$temporary_dir/request.xml.gz
architecture=$(uname -m)
case "$architecture" in
i?86) architecture=x86 ;;
armv7l) architecture=armv7hf ;;
esac
product_name=$(read_value "$sys_root/sys/devices/virtual/dmi/id/product_name" || true)
system_vendor=$(read_value "$sys_root/sys/devices/virtual/dmi/id/sys_vendor" || true)
serial=$(read_value "$sys_root/sys/devices/virtual/dmi/id/product_serial" || true)
uuid=$(read_value "$sys_root/sys/devices/virtual/dmi/id/product_uuid" || true)
machine_type=
[[ -z $system_vendor && -z $product_name ]] \
|| machine_type=${system_vendor:+$system_vendor:}$product_name
platform=$(awk -F ':' \
'/^platform[[:space:]]*:/ {sub(/^[[:space:]]+/, "", $2); print $2; exit}' \
"$proc_root/cpuinfo" 2>/dev/null || true)
if [[ -z $product_name ]]; then
product_name=$(read_value "$proc_root/device-tree/model" || true)
[[ -n $product_name ]] \
|| product_name=$(read_value \
"$sys_root/sys/firmware/devicetree/base/model" || true)
machine_type=${product_name##*,}
serial=$(read_value "$proc_root/device-tree/system-id" || true)
[[ -n $serial ]] \
|| serial=$(read_value \
"$sys_root/sys/firmware/devicetree/base/serial-number" || true)
serial=${serial##*,}
fi
machine_type=${machine_type//./}
serial=${serial//./}
node_type=
case "$system_vendor $product_name" in
*KVM*|*QEMU*|*VMware*|*VirtualBox*|*Bochs*|*Hyper-V*) node_type=virtual ;;
esac
cpu_count=$(awk '/^(processor|cpu)[[:space:]]*:/ {count++} END {print count + 0}' \
"$proc_root/cpuinfo" 2>/dev/null || printf '0')
cpu_type=$(awk -F ':' \
'/^(model name|cpu)[[:space:]]*:/ {sub(/^[[:space:]]+/, "", $2); print $2; exit}' \
"$proc_root/cpuinfo" 2>/dev/null || true)
memory=$(awk '/^MemTotal:/ {printf "%.0fMB", $2 / 1024; exit}' \
"$proc_root/meminfo" 2>/dev/null || true)
disk_size=$(lsblk -b -dn -o NAME,SIZE,TYPE 2>/dev/null \
| awk '$3 == "disk" {printf "%s%s:%.0fGB", separator, $1, $2 / 1073741824; separator=","}')
printf '%s\n' '<xcatrequest>' '<command>findme</command>' \
'<sequential>1</sequential>' >"$unsigned_packet"
append_element arch "$architecture"
append_element nodetype "$node_type"
append_element cpucount "$cpu_count"
append_element cputype "$cpu_type"
append_element memory "$memory"
append_element disksize "$disk_size"
append_element mtm "$machine_type"
append_element serial "$serial"
append_element platform "$platform"
if [[ -e $sys_root/dev/ipmi0 || -e $sys_root/dev/ipmi/0 || -e $sys_root/dev/ipmidev/0 ]]; then
append_element bmcinband 1
fi
if command -v "$ipmitool_command" >/dev/null 2>&1 \
&& "$ipmitool_command" mc info >/dev/null 2>&1; then
lan_channel=$("$ipmitool_command" sol info 2>/dev/null \
| awk -F ':' '/Payload Channel/ {gsub(/[[:space:]]/, "", $2); print $2; exit}')
bmc_data=$("$ipmitool_command" lan print "$lan_channel" 2>/dev/null \
|| "$ipmitool_command" lan print 2>/dev/null || true)
bmc_source=$(awk -F ':' \
'/IP Address Source/ {sub(/^[[:space:]]+/, "", $2); print $2; exit}' \
<<<"$bmc_data")
bmc_address=$(awk -F ':' \
'/^IP Address[[:space:]]*:/ {sub(/^[[:space:]]+/, "", $2); print $2; exit}' \
<<<"$bmc_data")
bmc_mac=$(awk -F ':' \
'/MAC Address/ {sub(/^[[:space:]]+/, "", $2); print $2 ":" $3 ":" $4 ":" $5 ":" $6 ":" $7; exit}' \
<<<"$bmc_data")
[[ $bmc_source != *Static* ]] || append_element bmc "$bmc_address"
append_element bmcmac "$bmc_mac"
fi
shopt -s nullglob
first_mac=
for interface_dir in "$sys_root/sys/class/net"/*; do
interface=${interface_dir##*/}
[[ $interface != lo && -r $interface_dir/address ]] || continue
mac_address=$(read_value "$interface_dir/address" || true)
[[ -n $mac_address ]] || continue
driver=unknown
if [[ -L $interface_dir/device/driver ]]; then
driver=$(basename "$(readlink -f "$interface_dir/device/driver")")
fi
normalized_mac=$(printf '%s' "$mac_address" | tr 'a-f' 'A-F')
[[ -n $first_mac ]] || first_mac=${normalized_mac//:/}
ipv4_address=$(ip -4 -o address show dev "$interface" scope global 2>/dev/null \
| awk 'NR == 1 {print $4}')
ipv6_address=$(ip -6 -o address show dev "$interface" scope global 2>/dev/null \
| awk 'NR == 1 {print $4}')
address=${ipv4_address:-$ipv6_address}
pci_slot=$(awk -F= '$1 == "PCI_SLOT_NAME" {print $2; exit}' \
"$interface_dir/device/uevent" 2>/dev/null || true)
mac_record="$driver|$interface|$normalized_mac|$address"
append_element mac "$mac_record"
printf '%s\n' '<nic>' >>"$unsigned_packet"
append_element devname "$interface"
append_element driver "$driver"
append_element ip4address "$ipv4_address"
append_element ip6address "$ipv6_address"
append_element hwaddr "$normalized_mac"
append_element pcidev "$pci_slot"
location=$(read_value "$interface_dir/device/physical_slot" || true)
[[ -n $location ]] \
|| location=$(read_value "$interface_dir/device/label" || true)
append_element location "$location"
if command -v "$lldp_command" >/dev/null 2>&1; then
lldp_data=$("$lldp_command" show neighbors ports "$interface" \
details -f keyvalue 2>/dev/null || true)
switch_name=$(awk -F= \
'/\.chassis\.name=/ {print substr($0, index($0, "=") + 1); exit}' \
<<<"$lldp_data")
switch_address=$(awk -F= \
'/\.chassis\.mgmt-ip=/ {print substr($0, index($0, "=") + 1); exit}' \
<<<"$lldp_data")
switch_description=$(awk -F= \
'/\.chassis\.descr=/ {print substr($0, index($0, "=") + 1); exit}' \
<<<"$lldp_data")
switch_port=$(awk -F= \
'/\.port\.descr=/ {print substr($0, index($0, "=") + 1); exit}' \
<<<"$lldp_data")
append_element switchname "$switch_name"
append_element switchaddr "$switch_address"
append_element switchdesc "$switch_description"
append_element switchport "$switch_port"
fi
printf '%s\n' '</nic>' >>"$unsigned_packet"
done
if [[ -z $uuid ]]; then
uuid=${machine_type:-$architecture}-${serial:-unknown}-${first_mac:-unknown}
uuid=$(printf '%s' "$uuid" | tr '[:upper:]' '[:lower:]')
fi
append_element uuid "$uuid"
public_key=$(openssl pkey -in "$key_dir/privkey.pem" -pubout 2>/dev/null \
| awk '!/PUBLIC KEY/ {printf "%s", $0}')
append_element xcatpubkey "$public_key"
printf '%s\n' '<sha512sig>' '</sha512sig>' '</xcatrequest>' \
>>"$unsigned_packet"
openssl dgst -sha512 -sign "$key_dir/privkey.pem" \
-out "$signature_file" "$unsigned_packet" \
|| fail_discovery DISCOVERY_SIGNATURE_FAILED \
'Unable to sign the discovery inventory' \
'Review the Genesis discovery identity'
signature=$(openssl base64 -A -in "$signature_file")
awk -v signature="$signature" \
'/<\/sha512sig>/ {print signature} {print}' \
"$unsigned_packet" >"$signed_packet"
gzip -9 -c "$signed_packet" >"$compressed_packet"
packet_size=$(wc -c <"$compressed_packet")
((packet_size <= 60000)) || fail_discovery DISCOVERY_PACKET_TOO_LARGE \
"Discovery inventory is too large: $packet_size bytes" \
'Review the attached device inventory'
rm -f -- "$response_file"
for ((attempt = 1; attempt <= maximum_attempts; attempt++)); do
publish_status RUNNING 'Sending hardware inventory to xCAT' \
"ATTEMPT=$attempt" "ATTEMPT_LIMIT=$maximum_attempts"
if ! send_packet; then
logger -t xcat-genesis-discover -- 'unable to send discovery inventory'
fi
for ((waited = 0; waited < retry_seconds; waited++)); do
if [[ -s $response_file ]]; then
response=$(head -n 1 "$response_file")
case "$response" in
restart|restart\ \(*\))
publish_status CONFIGURING_NETWORK \
'Applying the discovered network identity'
"$network_refresh_command" "$response" \
|| fail_discovery DISCOVERY_NETWORK_REFRESH_FAILED \
'The discovered network identity could not be applied' \
'Check the new DHCP reservation and management interface'
publish_status READY 'xCAT accepted the discovered node' \
'PROGRESS_PERCENT=100'
logger -t xcat-genesis-discover -- \
'xCAT accepted the discovery inventory'
exit 0
;;
processed)
fail_discovery DISCOVERY_NOT_MATCHED \
'xCAT received the node but did not match it' \
'Review active discovery methods and node definitions'
;;
processing) rm -f -- "$response_file" ;;
esac
fi
sleep 1
done
done
fail_discovery DISCOVERY_TIMEOUT \
"xCAT did not accept the node after $maximum_attempts attempts" \
'Check discovery status, node matching, and callback connectivity'
@@ -0,0 +1,24 @@
#!/bin/bash
set -eu
state_dir=${XCAT_STATE_DIR:-/run/xcat}
response_file=${XCAT_DISCOVERY_RESPONSE_FILE:-$state_dir/discovery-response}
response=
IFS= read -r response || true
case "$response" in
restart|restart\ \(*\)|processing|processed) ;;
*)
logger -t xcat-genesis-discovery-callback -- \
'ignored an invalid discovery callback'
exit 1
;;
esac
mkdir -p "$state_dir"
chmod 0755 "$state_dir"
umask 077
temporary_file=${response_file}.new
printf '%s\n' "$response" >"$temporary_file"
mv "$temporary_file" "$response_file"
printf '%s\n' 'callback received'
@@ -0,0 +1,145 @@
#!/bin/bash
set -euo pipefail
state_dir=${XCAT_STATE_DIR:-/run/xcat}
status_command=${XCAT_STATUS_COMMAND:-/usr/libexec/xcat/genesis-status}
network_file=${XCAT_NETWORK_FILE:-$state_dir/genesis.env}
metadata_file=${XCAT_METADATA_FILE:-$state_dir/xcat-response.env}
key_dir=${XCAT_KEY_DIR:-/etc/xcat}
request_timeout=${XCAT_CREDENTIAL_REQUEST_TIMEOUT:-20}
failure_code=CERTIFICATE_REQUEST_FAILED
failure_detail='Unable to obtain an xCAT client certificate'
failure_recovery='Check node identity, callback port 300, and xCAT credentials policy'
temporary_dir=
[[ "$request_timeout" =~ ^[1-9][0-9]*$ ]] || request_timeout=20
publish_status() {
"$status_command" credentials "$@" \
|| logger -t xcat-genesis-getcert -- 'unable to publish credential status'
}
# shellcheck disable=SC2329
finish_status() {
local result=$?
trap - EXIT
[[ -z $temporary_dir ]] || rm -rf -- "$temporary_dir"
if ((result != 0)); then
publish_status FAILED "$failure_detail" \
"CODE=$failure_code" "RECOVERY=$failure_recovery"
fi
exit "$result"
}
fail_certificate() {
failure_code=$1
failure_detail=$2
failure_recovery=$3
logger -t xcat-genesis-getcert -- "$failure_detail"
exit 1
}
trap finish_status EXIT HUP INT TERM
[[ -r $network_file ]] || fail_certificate CERTIFICATE_NETWORK_STATE_MISSING \
'Management network state is unavailable' \
'Wait for Genesis networking or review its failure'
# shellcheck disable=SC1090
source "$network_file"
: "${XCATDEST:?xCAT endpoint is missing from network state}"
node_name=
if [[ -r $metadata_file ]]; then
while IFS='=' read -r name value; do
case "$name" in
XCAT_NODE_NAME) node_name=$value ;;
esac
done <"$metadata_file"
fi
[[ $node_name =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,254}$ ]] \
|| fail_certificate CERTIFICATE_NODE_IDENTITY_MISSING \
'xCAT has not confirmed the node name' \
'Complete discovery and refresh the node assignment'
install -d -m 0700 "$key_dir"
if [[ ! -s $key_dir/privkey.pem ]]; then
umask 077
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out "$key_dir/privkey.pem" >/dev/null 2>&1 \
|| fail_certificate CERTIFICATE_IDENTITY_KEY_FAILED \
'Unable to create the Genesis identity key' \
'Review entropy and OpenSSL diagnostics'
fi
if [[ ! -s $key_dir/certkey.pem ]]; then
umask 077
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
-out "$key_dir/certkey.pem" >/dev/null 2>&1 \
|| fail_certificate CERTIFICATE_KEY_FAILED \
'Unable to create the client certificate key' \
'Review entropy and OpenSSL diagnostics'
fi
openssl pkey -in "$key_dir/privkey.pem" -noout >/dev/null 2>&1 \
|| fail_certificate CERTIFICATE_IDENTITY_KEY_INVALID \
'The Genesis identity key is invalid' \
'Replace the damaged Genesis runtime state and retry'
openssl pkey -in "$key_dir/certkey.pem" -noout >/dev/null 2>&1 \
|| fail_certificate CERTIFICATE_KEY_INVALID \
'The client certificate key is invalid' \
'Replace the damaged Genesis runtime state and retry'
temporary_dir=$(mktemp -d "${TMPDIR:-/tmp}/genesis-certificate.XXXXXX")
csr_file=$temporary_dir/client.csr
unsigned_request=$temporary_dir/request.xml
signed_request=$temporary_dir/request-signed.xml
signature_file=$temporary_dir/signature
response_file=$temporary_dir/response.xml
certificate_file=$temporary_dir/client.pem
openssl req -new -key "$key_dir/certkey.pem" -out "$csr_file" \
-subj "/CN=$node_name" >/dev/null 2>&1 \
|| fail_certificate CERTIFICATE_CSR_FAILED \
'Unable to create the client certificate request' \
'Review the client key and confirmed node name'
{
printf '%s\n' '<xcatrequest>' '<command>getcredentials</command>' \
'<arg>x509cert</arg>' '<callback_port>300</callback_port>' '<csr>'
cat "$csr_file"
printf '%s\n' '</csr>' '<sha512sig>' '</sha512sig>' '</xcatrequest>'
} >"$unsigned_request"
openssl dgst -sha512 -sign "$key_dir/privkey.pem" \
-out "$signature_file" "$unsigned_request" \
|| fail_certificate CERTIFICATE_SIGNATURE_FAILED \
'Unable to sign the client certificate request' \
'Review the Genesis identity key'
signature=$(openssl base64 -A -in "$signature_file")
awk -v signature="$signature" \
'/<\/sha512sig>/ {print signature} {print}' \
"$unsigned_request" >"$signed_request"
publish_status RUNNING 'Requesting an xCAT client certificate' \
"NODE_NAME=$node_name"
if ! timeout "$request_timeout" openssl s_client -connect "$XCATDEST" \
-quiet 2>/dev/null <"$signed_request" >"$response_file"; then
fail_certificate CERTIFICATE_RESPONSE_UNAVAILABLE \
'xCAT did not answer the client certificate request' \
'Check xcatd and callback connectivity to port 300'
fi
awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/' \
"$response_file" >"$certificate_file"
[[ -s $certificate_file ]] \
|| fail_certificate CERTIFICATE_RESPONSE_INVALID \
'xCAT returned no client certificate' \
'Check node identity and xCAT credentials policy'
openssl x509 -in "$certificate_file" -noout >/dev/null 2>&1 \
|| fail_certificate CERTIFICATE_RESPONSE_INVALID \
'xCAT returned an invalid client certificate' \
'Review xcatd certificate service diagnostics'
chmod 0600 "$certificate_file"
mv -f -- "$certificate_file" "$key_dir/cert.pem"
publish_status READY 'xCAT client certificate installed' \
"NODE_NAME=$node_name" 'PROGRESS_PERCENT=100'
logger -t xcat-genesis-getcert -- \
"installed an xCAT client certificate for $node_name"
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: EPL-1.0
#define _POSIX_C_SOURCE 200112L
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
enum {
SOURCE_PORT = 301,
MAX_DATAGRAM = 65507,
};
int main(int argc, char **argv)
{
struct addrinfo hints = { 0 };
struct addrinfo *addresses = NULL;
unsigned char packet[MAX_DATAGRAM + 1];
char *end = NULL;
long port;
FILE *input;
size_t length;
int error;
int last_error = 0;
int socket_fd;
int result = EXIT_FAILURE;
if (argc != 4) {
fprintf(stderr, "usage: %s packet host port\n", argv[0]);
return EXIT_FAILURE;
}
errno = 0;
port = strtol(argv[3], &end, 10);
if (errno || !end || *end || port < 1 || port > UINT16_MAX) {
fprintf(stderr, "invalid destination port: %s\n", argv[3]);
return EXIT_FAILURE;
}
input = fopen(argv[1], "rb");
if (!input) {
perror("unable to open discovery packet");
return EXIT_FAILURE;
}
length = fread(packet, 1, sizeof(packet), input);
if (ferror(input) || length == 0 || length > MAX_DATAGRAM) {
fprintf(stderr, "invalid discovery packet size\n");
fclose(input);
return EXIT_FAILURE;
}
fclose(input);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
error = getaddrinfo(argv[2], argv[3], &hints, &addresses);
if (error != 0) {
fprintf(stderr, "unable to resolve discovery endpoint: %s\n",
gai_strerror(error));
return EXIT_FAILURE;
}
for (struct addrinfo *address = addresses; address; address = address->ai_next) {
struct sockaddr_storage source = { 0 };
socklen_t source_length;
if (address->ai_family == AF_INET) {
struct sockaddr_in *source4 = (struct sockaddr_in *)&source;
source4->sin_family = AF_INET;
source4->sin_addr.s_addr = htonl(INADDR_ANY);
source4->sin_port = htons(SOURCE_PORT);
source_length = sizeof(*source4);
} else if (address->ai_family == AF_INET6) {
struct sockaddr_in6 *source6 = (struct sockaddr_in6 *)&source;
source6->sin6_family = AF_INET6;
source6->sin6_addr = in6addr_any;
source6->sin6_port = htons(SOURCE_PORT);
source_length = sizeof(*source6);
} else {
continue;
}
socket_fd = socket(address->ai_family, address->ai_socktype,
address->ai_protocol);
if (socket_fd < 0) {
last_error = errno;
continue;
}
if (bind(socket_fd, (struct sockaddr *)&source, source_length) < 0) {
last_error = errno;
close(socket_fd);
continue;
}
if (sendto(socket_fd, packet, length, 0, address->ai_addr,
address->ai_addrlen) == (ssize_t)length) {
close(socket_fd);
result = EXIT_SUCCESS;
break;
}
last_error = errno;
close(socket_fd);
}
if (result != EXIT_SUCCESS && last_error) {
errno = last_error;
perror("unable to send discovery packet");
}
freeaddrinfo(addresses);
return result;
}
@@ -0,0 +1,9 @@
[Unit]
Description=Answer xCAT credential checks
[Socket]
ListenStream=300
Accept=yes
[Install]
WantedBy=sockets.target
@@ -0,0 +1,9 @@
[Unit]
Description=Handle an xCAT credential check
[Service]
Type=oneshot
ExecStart=/usr/libexec/xcat/genesis-credential-callback
StandardInput=socket
StandardOutput=socket
StandardError=journal
@@ -0,0 +1,9 @@
[Unit]
Description=Receive xCAT discovery callbacks
[Socket]
ListenStream=3001
Accept=yes
[Install]
WantedBy=sockets.target
@@ -0,0 +1,9 @@
[Unit]
Description=Handle an xCAT discovery callback
[Service]
Type=oneshot
ExecStart=/usr/libexec/xcat/genesis-discovery-callback
StandardInput=socket
StandardOutput=socket
StandardError=journal
@@ -0,0 +1,65 @@
SUMMARY = "xCAT Genesis node discovery"
LICENSE = "EPL-1.0"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/EPL-1.0;md5=57f8d5e2b3e98ac6e088986c12bf94e6"
SRC_URI = "file://genesis-discover \
file://genesis-udp-send.c \
file://genesis-discovery-callback \
file://genesis-getcert \
file://genesis-credential-callback \
file://xcat-genesis-discovery.socket \
file://xcat-genesis-discovery@.service \
file://xcat-genesis-credential.socket \
file://xcat-genesis-credential@.service \
"
S = "${UNPACKDIR}"
inherit systemd
do_compile() {
${CC} ${CPPFLAGS} ${CFLAGS} -std=c17 -Wall -Wextra -Wpedantic -Werror \
${LDFLAGS} ${UNPACKDIR}/genesis-udp-send.c \
-o genesis-udp-send
}
RDEPENDS:${PN} = "bash coreutils gzip iproute2 openssl-bin util-linux-lsblk"
SYSTEMD_SERVICE:${PN} = "xcat-genesis-discovery.socket \
xcat-genesis-credential.socket \
"
SYSTEMD_AUTO_ENABLE = "enable"
do_install() {
install -d ${D}${libexecdir}/xcat
install -m 0755 ${UNPACKDIR}/genesis-discover \
${D}${libexecdir}/xcat/genesis-discover
install -m 0755 ${B}/genesis-udp-send \
${D}${libexecdir}/xcat/genesis-udp-send
install -m 0755 ${UNPACKDIR}/genesis-discovery-callback \
${D}${libexecdir}/xcat/genesis-discovery-callback
install -m 0755 ${UNPACKDIR}/genesis-getcert \
${D}${libexecdir}/xcat/genesis-getcert
install -m 0755 ${UNPACKDIR}/genesis-credential-callback \
${D}${libexecdir}/xcat/genesis-credential-callback
install -d ${D}${systemd_system_unitdir}
install -m 0644 ${UNPACKDIR}/xcat-genesis-discovery.socket \
${D}${systemd_system_unitdir}/xcat-genesis-discovery.socket
install -m 0644 ${UNPACKDIR}/xcat-genesis-discovery@.service \
${D}${systemd_system_unitdir}/xcat-genesis-discovery@.service
install -m 0644 ${UNPACKDIR}/xcat-genesis-credential.socket \
${D}${systemd_system_unitdir}/xcat-genesis-credential.socket
install -m 0644 ${UNPACKDIR}/xcat-genesis-credential@.service \
${D}${systemd_system_unitdir}/xcat-genesis-credential@.service
}
FILES:${PN} = "${libexecdir}/xcat/genesis-discover \
${libexecdir}/xcat/genesis-udp-send \
${libexecdir}/xcat/genesis-discovery-callback \
${libexecdir}/xcat/genesis-getcert \
${libexecdir}/xcat/genesis-credential-callback \
${systemd_system_unitdir}/xcat-genesis-discovery.socket \
${systemd_system_unitdir}/xcat-genesis-discovery@.service \
${systemd_system_unitdir}/xcat-genesis-credential.socket \
${systemd_system_unitdir}/xcat-genesis-credential@.service \
"
@@ -0,0 +1,18 @@
SUMMARY = "xCAT Genesis protocol clients"
LICENSE = "EPL-1.0"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/EPL-1.0;md5=57f8d5e2b3e98ac6e088986c12bf94e6"
FILESEXTRAPATHS:prepend := "${THISDIR}/../../../../../xCAT-genesis-scripts/usr/bin:"
SRC_URI = "file://getdestiny \
file://nextdestiny \
"
S = "${UNPACKDIR}"
RDEPENDS:${PN} = "bash openssl-bin"
do_install() {
install -d ${D}${bindir}
install -m 0755 ${UNPACKDIR}/getdestiny ${D}${bindir}/getdestiny
install -m 0755 ${UNPACKDIR}/nextdestiny ${D}${bindir}/nextdestiny
}
+109 -28
View File
@@ -1,30 +1,111 @@
#!/bin/bash
if [ -z "$XCATDEST" ]; then
XCATDEST=$1
fi
echo "<xcatrequest>
<command>getdestiny</command>
<callback_port>300</callback_port>
</xcatrequest>" > /tmp/destreq.xml
if [ -f /tmp/destiny.xml ]; then rm /tmp/destiny.xml; fi
while [ ! -f /tmp/destiny.xml ] || grep error /tmp/destiny.xml; do
if [ -f /tmp/destiny.xml ]; then
timer=60
while [ $timer -gt 0 ]; do
echo -en "Retrying in $timer seconds \r" >&2
sleep 1
timer=$(($timer-1));
done
fi
echo " " >&2
if [ -f /etc/xcat/cert.pem -a -f /etc/xcat/certkey.pem ]; then #use client cert if available
openssl s_client -key /etc/xcat/certkey.pem -cert /etc/xcat/cert.pem -connect "$XCATDEST" -quiet 2> /dev/null < /tmp/destreq.xml > /tmp/destiny.xml
else
openssl s_client -connect "$XCATDEST" -quiet 2> /dev/null < /tmp/destreq.xml > /tmp/destiny.xml
fi
done
rm /tmp/destreq.xml
DESTINY="$(grep '<destiny>' /tmp/destiny.xml | awk -F'>' '{print $2}'|awk -F'<' '{print $1}')"
rm /tmp/destiny.xml
echo "$DESTINY"
set -u
metadata_file=
metadata_temporary=
once=0
destination=${XCATDEST:-}
usage() {
printf '%s\n' 'Usage: getdestiny [ENDPOINT] [--metadata FILE] [--once]' >&2
exit 2
}
while (($# > 0)); do
case "$1" in
--metadata)
(($# >= 2)) || usage
metadata_file=$2
shift 2
;;
--once)
once=1
shift
;;
--)
shift
(($# <= 1)) || usage
if (($# == 1)); then
destination=$1
fi
break
;;
-*) usage ;;
*)
if [[ -z $destination ]]; then
destination=$1
fi
shift
;;
esac
done
[[ -n $destination ]] || usage
temporary_dir=$(mktemp -d "${TMPDIR:-/tmp}/getdestiny.XXXXXX") || exit 1
request_file=$temporary_dir/request.xml
response_file=$temporary_dir/response.xml
trap 'rm -rf -- "$temporary_dir"; [[ -z $metadata_temporary ]] || rm -f -- "$metadata_temporary"' EXIT HUP INT TERM
printf '%s\n' \
'<xcatrequest>' \
'<command>getdestiny</command>' \
'<callback_port>300</callback_port>' \
'</xcatrequest>' >"$request_file"
request_destiny() {
if [[ -f /etc/xcat/cert.pem && -f /etc/xcat/certkey.pem ]]; then
openssl s_client -key /etc/xcat/certkey.pem \
-cert /etc/xcat/cert.pem -connect "$destination" -quiet \
2>/dev/null <"$request_file" >"$response_file"
else
openssl s_client -connect "$destination" -quiet \
2>/dev/null <"$request_file" >"$response_file"
fi
[[ -s $response_file ]] && ! grep -q '<error>' "$response_file" \
&& grep -q '<destiny>' "$response_file"
}
while ! request_destiny; do
if ((once)); then
exit 1
fi
for ((timer = 60; timer > 0; timer--)); do
printf 'Retrying in %s seconds \r' "$timer" >&2
sleep 1
done
printf '%*s\r' 48 '' >&2
done
extract_element() {
local element=$1
sed -n "s:.*<$element>\([^<]*\)</$element>.*:\1:p" "$response_file" \
| head -n 1 | tr -cd '\40-\176'
}
destiny=$(extract_element destiny)
[[ -n $destiny ]] || exit 1
if [[ -n $metadata_file ]]; then
metadata_dir=${metadata_file%/*}
if [[ $metadata_dir == "$metadata_file" ]]; then
metadata_dir=.
elif [[ -z $metadata_dir ]]; then
metadata_dir=/
fi
[[ -d $metadata_dir ]] || exit 1
metadata_temporary=$(mktemp "$metadata_dir/.getdestiny.XXXXXX") || exit 1
{
printf 'XCAT_NODE_NAME=%s\n' "$(extract_element name)"
printf 'XCAT_DESTINY=%s\n' "$destiny"
printf 'XCAT_IMAGE_SERVER=%s\n' "$(extract_element imgserver)"
printf 'XCAT_KERNEL=%s\n' "$(extract_element kernel)"
printf 'XCAT_INITRD=%s\n' "$(extract_element initrd)"
printf 'XCAT_KERNEL_COMMAND_LINE=%s\n' "$(extract_element kcmdline)"
} >"$metadata_temporary"
chmod 0600 "$metadata_temporary"
mv -f -- "$metadata_temporary" "$metadata_file"
fi
printf '%s\n' "$destiny"
+112 -33
View File
@@ -1,36 +1,115 @@
#!/bin/bash
if [ -z "$XCATDEST" ]; then
XCATDEST=$1
fi
echo "<xcatrequest>
<command>nextdestiny</command>
<callback_port>300</callback_port>
</xcatrequest>" > /tmp/destreq.xml
if [ -f /tmp/destiny.xml ]; then rm /tmp/destiny.xml; fi
timer=0
while [ ! -f /tmp/destiny.xml ]; do
while [ $timer -gt 0 ]; do
echo -en "No destiny command received, retrying in $timer seconds \r" >&2
sleep 1
timer=$(($timer-1));
done
echo " " >&2;
if [ -f /etc/xcat/cert.pem -a -f /etc/xcat/certkey.pem ]; then #use client cert if available
openssl s_client -key /etc/xcat/certkey.pem -cert /etc/xcat/cert.pem -connect "$XCATDEST" -quiet 2> /dev/null < /tmp/destreq.xml > /tmp/destiny.xml
else
openssl s_client -connect "$XCATDEST" -quiet 2> /dev/null < /tmp/destreq.xml > /tmp/destiny.xml
fi
timer=60
set -u
metadata_file=
metadata_temporary=
once=0
destination=${XCATDEST:-}
usage() {
printf '%s\n' 'Usage: nextdestiny [ENDPOINT] [--metadata FILE] [--once]' >&2
exit 2
}
while (($# > 0)); do
case "$1" in
--metadata)
(($# >= 2)) || usage
metadata_file=$2
shift 2
;;
--once)
once=1
shift
;;
--)
shift
(($# <= 1)) || usage
if (($# == 1)); then
destination=$1
fi
break
;;
-*) usage ;;
*)
if [[ -z $destination ]]; then
destination=$1
fi
shift
;;
esac
done
rm /tmp/destreq.xml
DESTINY="$(grep '<destiny>' /tmp/destiny.xml | awk -F'>' '{print $2}'|awk -F'<' '{print $1}')"
if [ -z "$DESTINY" ]; then
ERROR="$(grep '<error>' /tmp/destiny.xml | awk -F'>' '{print $2}'|awk -F'<' '{print $1}')"
if [ ! -z "$ERROR" ]; then
DESTINY="error=$ERROR"
else
DESTINY="error=No destiny command received"
fi
[[ -n $destination ]] || usage
temporary_dir=$(mktemp -d "${TMPDIR:-/tmp}/nextdestiny.XXXXXX") || exit 1
request_file=$temporary_dir/request.xml
response_file=$temporary_dir/response.xml
trap 'rm -rf -- "$temporary_dir"; [[ -z $metadata_temporary ]] || rm -f -- "$metadata_temporary"' EXIT HUP INT TERM
printf '%s\n' \
'<xcatrequest>' \
'<command>nextdestiny</command>' \
'<callback_port>300</callback_port>' \
'</xcatrequest>' >"$request_file"
request_destiny() {
if [[ -f /etc/xcat/cert.pem && -f /etc/xcat/certkey.pem ]]; then
openssl s_client -key /etc/xcat/certkey.pem \
-cert /etc/xcat/cert.pem -connect "$destination" -quiet \
2>/dev/null <"$request_file" >"$response_file"
else
openssl s_client -connect "$destination" -quiet \
2>/dev/null <"$request_file" >"$response_file"
fi
[[ -s $response_file ]] && \
{ grep -q '<destiny>' "$response_file" || grep -q '<error>' "$response_file"; }
}
while ! request_destiny; do
if ((once)); then
exit 1
fi
for ((timer = 60; timer > 0; timer--)); do
printf 'Retrying in %s seconds \r' "$timer" >&2
sleep 1
done
printf '%*s\r' 48 '' >&2
done
extract_element() {
local element=$1
sed -n "s:.*<$element>\([^<]*\)</$element>.*:\1:p" "$response_file" \
| head -n 1 | tr -cd '\40-\176'
}
destiny=$(extract_element destiny)
if [[ -z $destiny ]]; then
error=$(extract_element error)
[[ -n $error ]] || error='No destiny command received'
destiny="error=$error"
fi
rm /tmp/destiny.xml
echo "$DESTINY"
if [[ -n $metadata_file ]]; then
metadata_dir=${metadata_file%/*}
if [[ $metadata_dir == "$metadata_file" ]]; then
metadata_dir=.
elif [[ -z $metadata_dir ]]; then
metadata_dir=/
fi
[[ -d $metadata_dir ]] || exit 1
metadata_temporary=$(mktemp "$metadata_dir/.nextdestiny.XXXXXX") || exit 1
{
printf 'XCAT_NODE_NAME=%s\n' "$(extract_element name)"
printf 'XCAT_DESTINY=%s\n' "$destiny"
printf 'XCAT_IMAGE_SERVER=%s\n' "$(extract_element imgserver)"
printf 'XCAT_KERNEL=%s\n' "$(extract_element kernel)"
printf 'XCAT_INITRD=%s\n' "$(extract_element initrd)"
printf 'XCAT_KERNEL_COMMAND_LINE=%s\n' "$(extract_element kcmdline)"
} >"$metadata_temporary"
chmod 0600 "$metadata_temporary"
mv -f -- "$metadata_temporary" "$metadata_file"
fi
printf '%s\n' "$destiny"