diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/files/10-xcat-genesis.conf b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/files/10-xcat-genesis.conf new file mode 100644 index 000000000..d4a805a0d --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/files/10-xcat-genesis.conf @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/xcat-genesis-network_1.0.bb b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/xcat-genesis-network_1.0.bb new file mode 100644 index 000000000..849a601bb --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/xcat-genesis-network_1.0.bb @@ -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" diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/00-xcat-genesis.preset b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/00-xcat-genesis.preset new file mode 100644 index 000000000..c6cf64a3f --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/00-xcat-genesis.preset @@ -0,0 +1 @@ +disable getty@.service diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-action b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-action new file mode 100755 index 000000000..e026054d9 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-action @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-functions b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-functions new file mode 100644 index 000000000..97819475d --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-functions @@ -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 +} diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-maintenance-shell b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-maintenance-shell new file mode 100644 index 000000000..396b50593 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-maintenance-shell @@ -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 &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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-network-refresh b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-network-refresh new file mode 100644 index 000000000..b7ec0a0b5 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-network-refresh @@ -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" diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-network-state b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-network-state new file mode 100644 index 000000000..cd6a8f10f --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-network-state @@ -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=
: 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" diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-register b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-register new file mode 100644 index 000000000..3de7dc2d3 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-register @@ -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" diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-status b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-status new file mode 100755 index 000000000..5ddd86f85 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-status @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-action.service b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-action.service new file mode 100644 index 000000000..bf30395c1 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-action.service @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-metrics.service b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-metrics.service new file mode 100644 index 000000000..0185ee1fe --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-metrics.service @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-network-ready.target b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-network-ready.target new file mode 100644 index 000000000..c4412d622 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-network-ready.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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-network-state.service b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-network-state.service new file mode 100644 index 000000000..41f22e0ad --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-network-state.service @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-register.service b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-register.service new file mode 100644 index 000000000..2e5c839c4 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-register.service @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/xcat-genesis-init_1.0.bb b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/xcat-genesis-init_1.0.bb new file mode 100644 index 000000000..8d643a4a9 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/xcat-genesis-init_1.0.bb @@ -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 \ +" diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-credential-callback b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-credential-callback new file mode 100755 index 000000000..86d6cdebb --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-credential-callback @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-discover b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-discover new file mode 100755 index 000000000..28453d65f --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-discover @@ -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//&/\&} + value=${value///\>} + value=${value//\"/\"} + value=${value//\'/\'} + printf '%s' "$value" +} + +append_element() { + local name=$1 value=${2:-} + + [[ -n $value ]] || return 0 + printf '<%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' '' 'findme' \ + '1' >"$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' '' >>"$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' '' >>"$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' '' '' '' \ + >>"$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' diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-discovery-callback b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-discovery-callback new file mode 100755 index 000000000..c868ac0fd --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-discovery-callback @@ -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' diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-getcert b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-getcert new file mode 100755 index 000000000..b6cace3a1 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-getcert @@ -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' '' 'getcredentials' \ + 'x509cert' '300' '' + cat "$csr_file" + printf '%s\n' '' '' '' '' +} >"$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" diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-udp-send.c b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-udp-send.c new file mode 100644 index 000000000..4b54258dd --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/genesis-udp-send.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: EPL-1.0 + +#define _POSIX_C_SOURCE 200112L + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-credential.socket b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-credential.socket new file mode 100644 index 000000000..bc901307d --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-credential.socket @@ -0,0 +1,9 @@ +[Unit] +Description=Answer xCAT credential checks + +[Socket] +ListenStream=300 +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-credential@.service b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-credential@.service new file mode 100644 index 000000000..7e94c86ad --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-credential@.service @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-discovery.socket b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-discovery.socket new file mode 100644 index 000000000..5936b28a1 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-discovery.socket @@ -0,0 +1,9 @@ +[Unit] +Description=Receive xCAT discovery callbacks + +[Socket] +ListenStream=3001 +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-discovery@.service b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-discovery@.service new file mode 100644 index 000000000..d1aa3476d --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/files/xcat-genesis-discovery@.service @@ -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 diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/xcat-genesis-discovery_1.0.bb b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/xcat-genesis-discovery_1.0.bb new file mode 100644 index 000000000..52b1bd8b9 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-discovery/xcat-genesis-discovery_1.0.bb @@ -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 \ +" diff --git a/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-protocol/xcat-genesis-protocol_1.0.bb b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-protocol/xcat-genesis-protocol_1.0.bb new file mode 100644 index 000000000..bb0721469 --- /dev/null +++ b/xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-protocol/xcat-genesis-protocol_1.0.bb @@ -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 +} diff --git a/xCAT-genesis-scripts/usr/bin/getdestiny b/xCAT-genesis-scripts/usr/bin/getdestiny index 6f3a44f01..9fd2685da 100755 --- a/xCAT-genesis-scripts/usr/bin/getdestiny +++ b/xCAT-genesis-scripts/usr/bin/getdestiny @@ -1,30 +1,111 @@ #!/bin/bash -if [ -z "$XCATDEST" ]; then - XCATDEST=$1 -fi -echo " -getdestiny -300 -" > /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 '' /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' \ + '' \ + 'getdestiny' \ + '300' \ + '' >"$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 '' "$response_file" \ + && grep -q '' "$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>\([^<]*\).*:\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" diff --git a/xCAT-genesis-scripts/usr/bin/nextdestiny b/xCAT-genesis-scripts/usr/bin/nextdestiny index 298826afb..de523290b 100755 --- a/xCAT-genesis-scripts/usr/bin/nextdestiny +++ b/xCAT-genesis-scripts/usr/bin/nextdestiny @@ -1,36 +1,115 @@ #!/bin/bash -if [ -z "$XCATDEST" ]; then - XCATDEST=$1 -fi -echo " -nextdestiny -300 -" > /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 '' /tmp/destiny.xml | awk -F'>' '{print $2}'|awk -F'<' '{print $1}')" -if [ -z "$DESTINY" ]; then - ERROR="$(grep '' /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' \ + '' \ + 'nextdestiny' \ + '300' \ + '' >"$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 '' "$response_file" || grep -q '' "$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>\([^<]*\).*:\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"