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.
This commit is contained in:
Herbert Wolverson committed 2026-09-23 13:45:38 -05:00
1 parent 297cb9ea37
commit 14a7fca24c
6 files changed
+62 -62

No files matched your search

+6 -10
View File
@@ -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"
}
+45 -19
View File
@@ -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"; }
+5 -16
View File
@@ -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"
+1
View File
@@ -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 = ""
+4 -16
View File
@@ -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
}
+1 -1
View File
@@ -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