diff --git a/agent/06_agent_create_cluster.sh b/agent/06_agent_create_cluster.sh index d44e0ce6e..1b21dc797 100755 --- a/agent/06_agent_create_cluster.sh +++ b/agent/06_agent_create_cluster.sh @@ -335,6 +335,88 @@ function automate_rendezvousIP_selection(){ done } +function hostname_format_for_node_type(){ + case "$1" in + master) echo "$MASTER_HOSTNAME_FORMAT" ;; + worker) echo "$WORKER_HOSTNAME_FORMAT" ;; + arbiter) echo "$ARBITER_HOSTNAME_FORMAT" ;; + esac +} + +# Configuration step of the 'static_ip' test case: drive the agent TUI to +# assign a static IP and hostname to each node of the given type. The static +# IPs are derived from staticIPForNode (see agent/common.sh). +function automate_static_networking(){ + local node_type=$1 + local node_count=$2 + + local hostname_format + hostname_format="$(hostname_format_for_node_type "$node_type")" + + for (( n=0; n/dev/null) && [[ -n "${actual_hostname}" ]]; then + break + fi + actual_hostname="" + echo "Waiting for ${node_type}-${n} to be reachable at ${node_ip} (attempt ${i}/${retries})" + sleep 30 + done + + if [[ -z "${actual_hostname}" ]]; then + echo "ERROR: static_ip test case failed - could not ssh to ${node_type}-${n} at static IP ${node_ip}" + return 1 + fi + # Compare the short hostname (strip any domain suffix) + if [[ "${actual_hostname%%.*}" != "${expected_hostname}" ]]; then + echo "ERROR: static_ip test case failed - node at ${node_ip} has hostname '${actual_hostname}', expected '${expected_hostname}'" + return 1 + fi + echo "Verified ${node_type}-${n}: reachable at ${node_ip} with hostname ${expected_hostname}" + done +} + function check_assisted_install_UI(){ local rendezvousIP rendezvousIP=$(getRendezvousIP) @@ -838,9 +920,15 @@ case "${AGENT_E2E_TEST_BOOT_MODE}" in echo "aarch64: CDROM media ejected from all VMs" fi - automate_rendezvousIP_selection master "$NUM_MASTERS" - automate_rendezvousIP_selection worker "$NUM_WORKERS" - automate_rendezvousIP_selection arbiter "$NUM_ARBITERS" + if [[ "${AGENT_TEST_CASES:-}" =~ "static_ip" ]]; then + automate_static_networking master "$NUM_MASTERS" + automate_static_networking worker "$NUM_WORKERS" + automate_static_networking arbiter "$NUM_ARBITERS" + else + automate_rendezvousIP_selection master "$NUM_MASTERS" + automate_rendezvousIP_selection worker "$NUM_WORKERS" + automate_rendezvousIP_selection arbiter "$NUM_ARBITERS" + fi check_assisted_install_UI @@ -852,6 +940,18 @@ case "${AGENT_E2E_TEST_BOOT_MODE}" in pushd agent/isobuilder/ui_driven_cluster_installation RENDEZVOUS_IP=$rendezvousIP OCP_DIR=$ocp_dir_abs_path INGRESS_VIPS=$INGRESS_VIPS API_VIPS=$API_VIPS go run main.go popd + + # Verification step of the 'static_ip' test case: now that installation has + # completed, confirm each node came up with the static IP and hostname that + # were configured via the agent TUI. + if [[ "${AGENT_TEST_CASES:-}" =~ "static_ip" ]]; then + echo "Running test scenario: verify static IPs configured via agent-tui" + verify_static_networking master "$NUM_MASTERS" + verify_static_networking worker "$NUM_WORKERS" + verify_static_networking arbiter "$NUM_ARBITERS" + echo "Finished verifying static networking on all nodes" + fi + exit 0 ;; esac diff --git a/agent/common.sh b/agent/common.sh index dbea26bdc..c4499bd7d 100644 --- a/agent/common.sh +++ b/agent/common.sh @@ -99,7 +99,32 @@ if [ "${AGENT_E2E_TEST_BOOT_MODE}" == "ISO_NO_REGISTRY" ] ; then fi fi +# Static IP assignment for the 'static_ip' agent TUI test case. +# The rendezvous node (first master) is assigned .80 and each subsequent node +# increments by one, ordered masters, then workers, then arbiters. +export AGENT_STATIC_IP_PREFIX="192.168.111" +export AGENT_STATIC_IP_BASE=80 + +# staticIPForNode - echo the static IP assigned to a node. +function staticIPForNode() { + local node_type=$1 + local index=$2 + local offset=0 + case "${node_type}" in + master) offset=0 ;; + worker) offset=${NUM_MASTERS} ;; + arbiter) offset=$(( NUM_MASTERS + NUM_WORKERS )) ;; + esac + echo "${AGENT_STATIC_IP_PREFIX}.$(( AGENT_STATIC_IP_BASE + offset + index ))" +} + function getRendezvousIP() { + # For the 'static_ip' test case the rendezvous node is assigned a known + # static IP via the agent TUI, so return that directly. + if [[ "${AGENT_TEST_CASES:-}" =~ "static_ip" ]]; then + staticIPForNode master 0 + return + fi if [[ "${NODES_PLATFORM}" == "baremetal" ]]; then echo "${AGENT_BAREMETAL_IPS%%,*}" return diff --git a/agent/e2e/agent-tui/test-static-ip.sh b/agent/e2e/agent-tui/test-static-ip.sh new file mode 100755 index 000000000..ab243730f --- /dev/null +++ b/agent/e2e/agent-tui/test-static-ip.sh @@ -0,0 +1,180 @@ +#!/bin/bash +# +# test-static-ip.sh - first step of the 'static_ip' e2e test case. +# +# This script performs the *configuration* step of the static IP test: for a +# single node it drives the agent TUI (and nmtui within it) via "virsh send-key" +# to assign a static IPv4 address and hostname, then enters the rendezvous IP. +# It creates a NEW NetworkManager connection (rather than editing the existing +# DHCP one) so that coreos-installer's --copy-network preserves the static +# config into the installed system. +# +# The matching *verification* step runs at the end of the installation (see +# verify_static_networking in agent/06_agent_create_cluster.sh), which confirms +# each node is reachable at its assigned static IP with the expected hostname. +# +# Usage: test-static-ip.sh +# +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../../../" && pwd )" +source "$SCRIPTDIR/common.sh" +source "$SCRIPTDIR/agent/common.sh" +source "$SCRIPTDIR/agent/e2e/agent-tui/utils.sh" + +set +x + +node_name=$1 +node_ip=$2 +node_hostname=$3 + +# The following commands send key presses through "virsh send-key" to interact +# with agent-tui and nmtui to configure static networking. + +# +# Phase 1: Navigate from TUI first screen to nmtui +# The TUI first screen has: Rendezvous IP field, , +# , and at the bottom. +# +pressDown "Navigate to " 3 "$node_name" +pressEnter "Select ''" "" "$node_name" + +# +# Phase 2: Add a new Ethernet connection with static IP in nmtui +# Creating a NEW connection (rather than editing the existing one) ensures +# a new .nmconnection file is created with a birth time after agent-tui start. +# This triggers the --copy-network flag in coreos-installer, which preserves +# the static networking config into the installed system. +# +# nmtui opens with: "Edit a connection", "Activate a connection", +# "Set system hostname", "Radio" (4.16+), "Quit" +# +pressEnter "Select 'Edit a connection'" "" "$node_name" + +# Tab from connection list to button. +# Connection list is one widget; Tab goes to buttons: , , , +pressTab "Goto button" 1 "$node_name" +pressEnter "Select '' button" "" "$node_name" + +# New Connection type dialog: DSL is selected by default. +# Navigate down to Ethernet, then tab past to and confirm. +pressDown "Select Ethernet" 1 "$node_name" +pressTab "Goto button" 1 "$node_name" +pressTab "Goto button" 1 "$node_name" +pressEnter "Create new Ethernet connection" "" "$node_name" + +# Edit Connection form opens with cursor on Profile name field. +# Leave default profile name, tab to Device field and enter interface name. +pressTab "Goto Device field" 1 "$node_name" +pressKeys "Enter device name" "enp2s0" "$node_name" + +# Tab past ETHERNET , 802.1X SECURITY to IPv4 +pressTab "Goto IPv4 CONFIGURATION mode" 3 "$node_name" +pressEnter "Open IPv4 mode selector" "" "$node_name" +# Modes are: Disabled, Automatic, Link-Local, Manual +# From Automatic, Manual is two DOWNs +pressDown "Select Manual" 2 "$node_name" +pressEnter "Confirm Manual" "" "$node_name" + +# After selecting Manual, expand to reveal address fields. +# From the mode selector, is the next tab stop. +pressTab "Goto " 1 "$node_name" +pressEnter "Expand IPv4 details" "" "$node_name" + +# Tab to the Addresses field and enter the static IP. +pressTab "Goto Addresses " 1 "$node_name" +pressEnter "Activate Addresses field" "" "$node_name" +pressKeys "Enter static IP address" "${node_ip}/24" "$node_name" + +# Tab past and to Gateway field +pressTab "Goto Gateway field" 3 "$node_name" +pressKeys "Enter gateway" "192.168.111.1" "$node_name" + +# Tab to DNS servers field +pressTab "Goto DNS servers " 1 "$node_name" +pressEnter "Activate DNS servers field" "" "$node_name" +pressKeys "Enter DNS server" "192.168.111.1" "$node_name" + +# Tab to button +# From DNS servers, tab past: , , Search domains , +# Routing (No custom routes) , +# [ ] Never use this network for default route, +# [ ] Ignore automatically obtained routes, +# [ ] Ignore automatically obtained DNS parameters, +# [ ] Require IPv4 addressing for this connection, +# IPv6 CONFIGURATION , +# [X] Automatically connect, [X] Available to all users, +# , +pressTab "Goto button" 14 "$node_name" +pressEnter "Select '' button" "" "$node_name" + +# +# Phase 3: Activate the new static connection +# After OK we're back at the connection list. Go Back to main menu, +# then activate the new connection (which deactivates the old DHCP one). +# +pressTab "Goto button" 4 "$node_name" +pressEnter "Select '' button" "" "$node_name" +pressDown "Select 'Activate a connection'" 1 "$node_name" +pressEnter "Select 'Activate a connection' menu item" "" "$node_name" +# Activate a connection list layout: +# Ethernet (enp1s0) +# Wired connection 1 <- cursor starts here +# Ethernet (enp2s0) +# * Wired connection 2 <- active DHCP (DOWN 1) +# Ethernet connection 1 <- our new static (DOWN 2) +# Navigate down 2 to our new static connection and activate it. +# NM auto-deactivates the old DHCP connection on the same device. +pressDown "Select 'Ethernet connection 1'" 2 "$node_name" +pressEnter "Activate new static connection" "" "$node_name" +sleep 3 + +# +# Phase 4: Set system hostname in nmtui +# After "Activate a connection" Back, we're at the main menu. +# Menu items: "Edit a connection", "Activate a connection" (cursor here), +# "Set system hostname", "Radio" (4.16+), "Quit" +# +pressTab "Goto button" 2 "$node_name" +pressEnter "Select '' button" "" "$node_name" +pressDown "Select 'Set system hostname'" 1 "$node_name" +pressEnter "Select 'Set system hostname' menu item" "" "$node_name" + +# Clear any existing hostname and enter the new one. +# The dialog has: hostname text field, , . +# Press End to ensure cursor is at end of any existing text, +# then Ctrl+U to clear the line. +pressKey "Goto end of hostname field" KEY_END 1 "$node_name" +echo "Clear hostname field" +sudo virsh send-key "$node_name" KEY_LEFTCTRL KEY_U +sleep 1 +pressKeys "Enter hostname" "$node_hostname" "$node_name" + +# Tab past to and confirm +pressTab "Goto button" 2 "$node_name" +pressEnter "Confirm hostname" "" "$node_name" + +# +# Phase 5: Exit nmtui back to TUI first screen +# After setting hostname, we're back at the main menu on "Set system hostname". +# From here: "Radio" is 1 down, "Quit" is 2 down. +# +pressDown "Select Quit" 2 "$node_name" +pressEnter "Select 'Quit' menu item" "" "$node_name" +pressEsc "Esc from network tree view" 2 "$node_name" +sleep 3 + +# Wait for TUI checks to update after network reconfiguration +sleep 10 + +# +# Phase 6: Save rendezvous IP on the TUI first screen +# After returning from nmtui, cursor is back on the Rendezvous IP field. +# All nodes enter the same rendezvous IP. The rendezvous node recognizes +# the IP as its own; other nodes use it to find the rendezvous. +# +rendezvousIP=$(getRendezvousIP) +pressKeys "Entering rendezvous IP address" "$rendezvousIP" "$node_name" +# After typing in the text field, the first TAB is absorbed, so we need +# 2 TABs to reach : 1 absorbed + 1 to +pressTab "Goto " 2 "$node_name" +pressEnter "" "" "$node_name" +pressEnter "Save and Continue" "" "$node_name" diff --git a/agent/e2e/agent-tui/utils.sh b/agent/e2e/agent-tui/utils.sh index 960781c25..8d1d0e7a2 100644 --- a/agent/e2e/agent-tui/utils.sh +++ b/agent/e2e/agent-tui/utils.sh @@ -35,7 +35,7 @@ function pressKey() { echo "$msg" fi - local node_name=$4 + local node_name=${4:-} for i in $(seq 1 "$numReps"); do _pressKey "$keyCode" "$node_name" done @@ -46,7 +46,7 @@ function pressEnter() { } function pressTab() { - pressKey "$1" KEY_TAB "$2" + pressKey "$1" KEY_TAB "$2" "${3:-}" } function pressDown() { @@ -54,11 +54,11 @@ function pressDown() { } function pressBackspace() { - pressKey "$1" KEY_BACKSPACE "$2" + pressKey "$1" KEY_BACKSPACE "$2" "${3:-}" } function pressEsc() { - pressKey "$1" KEY_ESC "$2" + pressKey "$1" KEY_ESC "$2" "${3:-}" } function pressKeys(){ @@ -85,6 +85,10 @@ function pressKeys(){ c="DOT" elif [[ $c =~ [':'] ]]; then c="LEFTSHIFT KEY_SEMICOLON" + elif [[ $c =~ ['/'] ]]; then + c="SLASH" + elif [[ $c == '-' ]]; then + c="MINUS" fi local keyCode="KEY_"$c diff --git a/config_example.sh b/config_example.sh index f99617c35..b552e5072 100755 --- a/config_example.sh +++ b/config_example.sh @@ -994,7 +994,6 @@ set -x # As the size of the ISO increases in future, increase the expected ISO size accordingly. # export AGENT_OVE_ISO_SIZE=40 - # Uncomment and set the following value to "true" to enable a test scenario # where the DNS is disabled on the hosts by setting its IP address to an incorrect value. # Uncomment and set the following value to one or more test cases below. @@ -1011,6 +1010,19 @@ set -x # then the wait-for commands should timeout and fail. # This test case is only supported when IP_STACK=v4. # +# 2. 'static_ip' test case: +# Configures static networking on the nodes via the agent TUI (nmtui) instead of +# using DHCP, then verifies at the end of the installation that each node came up +# with the configured static IP and hostname. During the agent TUI automation a +# new NetworkManager connection is created on each node with a static IPv4 +# address and hostname; static IPs are assigned starting at 192.168.111.80 +# (rendezvous node), incrementing by 1 for each additional node. After the +# installation completes, the test ssh's to each node at its assigned static IP +# and confirms the hostname matches, failing the deployment if any node is not +# reachable at its static IP. +# This test case requires AGENT_E2E_TEST_BOOT_MODE=ISO_NO_REGISTRY and is only +# supported when IP_STACK=v4. +# # export AGENT_TEST_CASES='bad_dns' # Uncomment the following line to deploy the cluster using the appliance model