From 14a7fca24c0d7dd39c43deaba18400e672caf719 Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Wed, 23 Sep 2026 13:45:38 -0500 Subject: [PATCH] Address the final review findings - management_ip() returned non-zero when no lease was active, which made wait_for_management_ip exit before its retry loop. It now always returns 0, filters on the ipv4 protocol column, and parses lease rows with bash instead of per-line awk subprocesses. - wait_for_cloud_init() treated exit 2 as 'disabled'; cloud-init returns 2 for recoverable errors and 0 when disabled, so a failed package install was silently accepted. Only 0 is accepted now, and the long status is printed on failure. - check-package now waits for cloud-init through the shared wait_for_guest_ready helper, asserts the guest OS, and reports the missing-package error instead of exiting silently under pipefail. - Resolve the guest OS and the expected os-release string once in lib/common.sh; configure.sh and check-package share the assertion. - Delete the unused package-list helper and warn when bpftool is missing after the best-effort extras install. - build_rust.sh stops when prerequisite installation fails, matching build_pkg_debian.sh. - Add a sync pointer above the harness UISP config block. --- radius-harness/lab | 16 +++----- radius-harness/lib/common.sh | 64 +++++++++++++++++++++--------- radius-harness/lib/configure.sh | 21 +++------- radius-harness/templates/lqos.conf | 1 + src/build_prereqs.sh | 20 ++-------- src/build_rust.sh | 2 +- 6 files changed, 62 insertions(+), 62 deletions(-) diff --git a/radius-harness/lab b/radius-harness/lab index 1df43150..af5b0483 100755 --- a/radius-harness/lab +++ b/radius-harness/lab @@ -5,11 +5,7 @@ LAB_ROOT=$(cd -- "$(dirname -- "$0")" && pwd) source "$LAB_ROOT/lib/common.sh" source "$LAB_ROOT/lab.env" -GUEST_OS=${LAB_GUEST_OS:-} -if [[ -z $GUEST_OS && -f $IMAGE_DIR/guest-os ]]; then - GUEST_OS=$(<"$IMAGE_DIR/guest-os") -fi -GUEST_OS=${GUEST_OS:-ubuntu} +GUEST_OS=$(resolve_guest_os) case "$GUEST_OS" in ubuntu) GUEST_IMAGE_URL=$UBUNTU_IMAGE_URL @@ -30,7 +26,6 @@ esac GUEST_OS_VARIANT=${LAB_OS_VARIANT:-$DEFAULT_GUEST_OS_VARIANT} readonly GUEST_OS GUEST_IMAGE_URL GUEST_IMAGE_CHECKSUM GUEST_IMAGE_CHECKSUM_TOOL GUEST_OS_VARIANT -export GUEST_OS readonly GUEST_BASE="$IMAGE_DIR/$(basename "$GUEST_IMAGE_URL")" readonly ROUTER_ARCHIVE="$IMAGE_DIR/chr-$ROUTEROS_VERSION.img.zip" readonly ROUTER_BASE="$IMAGE_DIR/chr-$ROUTEROS_VERSION.qcow2" @@ -171,17 +166,18 @@ cmd_check_package() { local package_path=${1:-} lqos_ip simulate_output require_host_tools if [[ -z $package_path ]]; then - package_path=$(ls -1t "$REPO_ROOT"/src/dist/libreqos_*_amd64.deb 2>/dev/null | head -n 1) + package_path=$(ls -1t "$REPO_ROOT"/src/dist/libreqos_*_amd64.deb 2>/dev/null | head -n 1) || true fi [[ -n $package_path && -f $package_path ]] || die "no LibreQoS package found; build one with src/build_pkg_debian.sh or pass a path" domain_exists "$LQOS_DOMAIN" || die "the lab is not running; use ./radius-harness/lab up first" - lqos_ip=$(wait_for_management_ip 52:54:00:10:00:10) - wait_for_ssh "$lqos_ip" + lqos_ip=$(wait_for_guest_ready 52:54:00:10:00:10) + note "verifying the LibreQoS guest operating system" + assert_guest_os "$lqos_ip" "$GUEST_OS" note "checking package dependency resolution inside the $GUEST_OS guest" lab_scp "$package_path" "$lqos_ip" /tmp/ if ! simulate_output=$(lab_ssh "$lqos_ip" "sudo apt-get install --simulate '/tmp/$(basename "$package_path")' 2>&1"); then printf '%s\n' "$simulate_output" - die "package dependency resolution failed in the $GUEST_OS guest" + die "package check failed in the $GUEST_OS guest" fi note "package dependency resolution passed" } diff --git a/radius-harness/lib/common.sh b/radius-harness/lib/common.sh index 02b9c14e..a3ce397b 100755 --- a/radius-harness/lib/common.sh +++ b/radius-harness/lib/common.sh @@ -38,25 +38,42 @@ ensure_lab_stopped() { done } +# Prints the guest OS recorded for this lab: LAB_GUEST_OS, the images/guest-os +# marker written by init, or the ubuntu default. +resolve_guest_os() { + local guest_os=${LAB_GUEST_OS:-} + if [[ -z $guest_os && -f $IMAGE_DIR/guest-os ]]; then + guest_os=$(<"$IMAGE_DIR/guest-os") + fi + printf '%s\n' "${guest_os:-ubuntu}" +} + +# Prints the /etc/os-release "ID VERSION_ID" string expected for a guest OS. +guest_expected_os_release() { + case "$1" in + ubuntu) printf 'ubuntu 24.04\n' ;; + debian) printf 'debian 13\n' ;; + *) return 1 ;; + esac +} + management_ip() { - local mac=$1 now line expires epoch ip best_ip= best_expires=0 + local mac=$1 now line day time mac_field proto ip_field rest best_ip= best_expires=0 epoch now=$(date +%s) while IFS= read -r line; do - if [[ ${line,,} != *"${mac,,}"* ]]; then + read -r day time mac_field proto ip_field rest <<<"$line" || continue + [[ ${mac_field,,} == "${mac,,}" ]] || continue + [[ ${proto,,} == "ipv4" ]] || continue + epoch=$(date -d "$day $time" +%s 2>/dev/null) || continue + if (( epoch <= now )); then continue fi - expires=$(awk '{ print $1 " " $2 }' <<<"$line") - epoch=$(date -d "$expires" +%s 2>/dev/null) || continue - if [[ $epoch -le $now ]]; then - continue - fi - ip=$(awk '{ print $5 }' <<<"$line") - if [[ $epoch -gt $best_expires ]]; then + if (( epoch > best_expires )); then best_expires=$epoch - best_ip=${ip%%/*} + best_ip=${ip_field%%/*} fi done < <(virsh_lab net-dhcp-leases "$MANAGEMENT_NETWORK") - [[ -n $best_ip ]] && printf '%s\n' "$best_ip" + printf '%s\n' "$best_ip" } wait_for_management_ip() { @@ -79,20 +96,29 @@ wait_for_ssh() { } wait_for_cloud_init() { - local host=$1 output status - if output=$(lab_ssh "$host" 'sudo cloud-init status --wait' 2>&1); then - return 0 - else - status=$? - fi - # Exit code 2 means cloud-init is disabled, which is not a failure here. - if [ "$status" -eq 2 ]; then + local host=$1 output + if output=$(lab_ssh "$host" 'sudo cloud-init status --wait --long' 2>&1); then return 0 fi printf '%s\n' "$output" die "cloud-init did not complete successfully on $host" } +wait_for_guest_ready() { + local mac=$1 ip + ip=$(wait_for_management_ip "$mac") + wait_for_ssh "$ip" + wait_for_cloud_init "$ip" + printf '%s\n' "$ip" +} + +assert_guest_os() { + local host=$1 guest_os=$2 expected actual + expected=$(guest_expected_os_release "$guest_os") || die "unsupported GUEST_OS '$guest_os'" + actual=$(lab_ssh "$host" '. /etc/os-release && printf "%s %s" "$ID" "$VERSION_ID"') + [[ $actual == "$expected" ]] || die "LibreQoS guest is '$actual', expected '$expected'" +} + lab_ssh() { local host=$1; shift; ssh -i "$RUN_DIR/id_ed25519" -o IdentitiesOnly=yes -o BatchMode=yes -o UserKnownHostsFile="$RUN_DIR/known_hosts" -o StrictHostKeyChecking=accept-new "lab@$host" "$@"; } lab_scp() { local source=$1 host=$2 destination=$3; scp -i "$RUN_DIR/id_ed25519" -o IdentitiesOnly=yes -p -o BatchMode=yes -o UserKnownHostsFile="$RUN_DIR/known_hosts" -o StrictHostKeyChecking=accept-new -r "$source" "lab@$host:$destination"; } diff --git a/radius-harness/lib/configure.sh b/radius-harness/lib/configure.sh index 147053ce..6ff137c6 100755 --- a/radius-harness/lib/configure.sh +++ b/radius-harness/lib/configure.sh @@ -10,25 +10,14 @@ require_command sshpass require_lab_secret RADIUS_SHARED_SECRET require_lab_secret ROUTEROS_ADMIN_PASSWORD -note "discovering guest management addresses" -lqos_ip=$(wait_for_management_ip 52:54:00:10:00:10) -radius_ip=$(wait_for_management_ip 52:54:00:20:00:10) -client_ip=$(wait_for_management_ip 52:54:00:30:00:10) +note "waiting for the LibreQoS, FreeRADIUS, and PPPoE client guests" +lqos_ip=$(wait_for_guest_ready 52:54:00:10:00:10) +radius_ip=$(wait_for_guest_ready 52:54:00:20:00:10) +client_ip=$(wait_for_guest_ready 52:54:00:30:00:10) router_ip=$(wait_for_management_ip 52:54:00:40:00:10) -note "waiting for SSH on LibreQoS, FreeRADIUS, and PPPoE client guests" -wait_for_ssh "$lqos_ip"; wait_for_ssh "$radius_ip"; wait_for_ssh "$client_ip" - -note "waiting for cloud-init to finish on the lab guests" -wait_for_cloud_init "$lqos_ip"; wait_for_cloud_init "$radius_ip"; wait_for_cloud_init "$client_ip" note "verifying the LibreQoS guest operating system" -case ${GUEST_OS:-ubuntu} in - ubuntu) expected_os_release='ubuntu 24.04' ;; - debian) expected_os_release='debian 13' ;; - *) die "unsupported GUEST_OS '${GUEST_OS:-}'" ;; -esac -actual_os_release=$(lab_ssh "$lqos_ip" '. /etc/os-release && printf "%s %s" "$ID" "$VERSION_ID"') -[[ $actual_os_release == "$expected_os_release" ]] || die "LibreQoS guest is '$actual_os_release', expected '$expected_os_release'" +assert_guest_os "$lqos_ip" "$(resolve_guest_os)" note "rendering isolated lab configuration" install -d -m 0700 "$RUN_DIR/config" diff --git a/radius-harness/templates/lqos.conf b/radius-harness/templates/lqos.conf index 6d748d58..5b167dc7 100644 --- a/radius-harness/templates/lqos.conf +++ b/radius-harness/templates/lqos.conf @@ -56,6 +56,7 @@ queue_refresh_interval_mins = 30 use_mikrotik_ipv6 = false ethernet_port_limits_enabled = true +# Keep this block in sync with the [uisp_integration] section in src/lqos.example. [uisp_integration] enable_uisp = false token = "" diff --git a/src/build_prereqs.sh b/src/build_prereqs.sh index cfa319ae..da3c11ae 100755 --- a/src/build_prereqs.sh +++ b/src/build_prereqs.sh @@ -47,11 +47,6 @@ lqos_detect_distro() { printf 'unknown\n' } -# Prints the packages shared by all supported distributions. -lqos_common_build_packages() { - printf '%s\n' "${LQOS_COMMON_BUILD_PACKAGES[@]}" -} - # Prints the distribution-specific build packages. # Returns non-zero for distributions without a known package set. lqos_distro_build_extras() { @@ -72,16 +67,6 @@ lqos_distro_build_extras() { esac } -# Prints the full build prerequisite package list for a distribution. -lqos_build_packages() { - local extras - if ! extras=$(lqos_distro_build_extras "$1"); then - return 1 - fi - lqos_common_build_packages - printf '%s\n' "$extras" -} - # Installs the build prerequisites for the current or overridden distribution. # Side effects: refreshes apt package lists, installs packages, and prepends # /usr/sbin to PATH so the build can find bpftool on Debian. @@ -97,7 +82,7 @@ lqos_install_build_prerequisites() { if ! extras=$(lqos_distro_build_extras "$distro"); then echo "Unsupported distribution '$distro' for automatic prerequisite install." echo "Install these packages with your distribution's package manager:" - lqos_common_build_packages | sed 's/^/ /' + printf '%s\n' "${LQOS_COMMON_BUILD_PACKAGES[@]}" | sed 's/^/ /' echo "Also install bpftool (required to build lqos_sys) and, optionally, perf." return 1 fi @@ -114,4 +99,7 @@ lqos_install_build_prerequisites() { echo "Warning: failed to install: $extras" echo "Install the distribution equivalents of bpftool (required by lqos_sys) and perf manually if the build fails." fi + if ! command -v bpftool >/dev/null 2>&1; then + echo "Warning: bpftool was not found; lqos_sys cannot build without it." + fi } diff --git a/src/build_rust.sh b/src/build_rust.sh index 8b05c2b6..5d993d8a 100755 --- a/src/build_rust.sh +++ b/src/build_rust.sh @@ -28,7 +28,7 @@ VENV_PYTHON="/opt/libreqos/venv/bin/python" SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=build_prereqs.sh source "$SCRIPT_DIR/build_prereqs.sh" -lqos_install_build_prerequisites +lqos_install_build_prerequisites || exit 1 if ! rustup -V &> /dev/null then