fix(ci): reload hwsim module between WiFi bundle test suites #250
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: RXNM CI (Native Nspawn) | |
| on: | |
| push: | |
| branches: [ "main", "master", "v1.1.0", "1.1.x-CI", "agentic-review" ] | |
| pull_request: | |
| branches: [ "main", "master", "v1.1.0" ] | |
| jobs: | |
| unit-and-posix: | |
| name: "Unit Tests & POSIX Compat" | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| - name: Install Dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y dash shellcheck valgrind jq | |
| - name: Build & Unit Tests | |
| run: | | |
| echo "=== PHASE 1: Build & Unit Tests ===" | |
| make tiny | |
| make lint | |
| export GITHUB_ACTIONS=true | |
| make test-all | |
| - name: "Path B: POSIX/dash compatibility" | |
| run: | | |
| echo "=== Step 1: dash -n syntax check on lib/*.sh ===" | |
| FAIL=0 | |
| for f in lib/*.sh; do | |
| # rxnm-config-schema.sh uses Bash-only syntax below its POSIX guard. | |
| # It cannot pass dash -n. We skip it here and explicitly lint it as bash. | |
| case "$f" in lib/rxnm-config-schema.sh) continue ;; esac | |
| if ! dash -n "$f"; then | |
| echo "POSIX FAIL [dash -n]: $f" | |
| FAIL=1 | |
| fi | |
| done | |
| for f in bin/rxnm; do | |
| [ -f "$f" ] || continue | |
| if ! dash -n "$f"; then | |
| echo "POSIX FAIL [dash -n]: $f" | |
| FAIL=1 | |
| fi | |
| done | |
| [ "$FAIL" -eq 0 ] || { echo "dash -n check failed"; exit 1; } | |
| echo "=== Step 2: shellcheck strict POSIX on key lib files ===" | |
| shellcheck --shell=sh --exclude=SC3043 \ | |
| lib/rxnm-utils.sh lib/rxnm-nullify.sh lib/rxnm-routes.sh \ | |
| lib/rxnm-wifi.sh lib/rxnm-system.sh lib/rxnm-roaming.sh || exit 1 | |
| # Explicitly lint the schema file as bash to ensure safety | |
| shellcheck --shell=bash lib/rxnm-config-schema.sh || exit 1 | |
| echo "=== Step 3b: Full POSIX library source test ===" | |
| RXNM_SHELL_IS_BASH=false RXNM_AGENT_BIN="/nonexistent/rxnm-agent" \ | |
| dash -c ' | |
| RXNM_LIB_DIR=./lib | |
| for f in ./lib/rxnm-constants.sh ./lib/rxnm-utils.sh \ | |
| ./lib/rxnm-system.sh ./lib/rxnm-wifi.sh \ | |
| ./lib/rxnm-bluetooth.sh ./lib/rxnm-interfaces.sh \ | |
| ./lib/rxnm-routes.sh ./lib/rxnm-profiles.sh \ | |
| ./lib/rxnm-templates.sh ./lib/rxnm-nullify.sh \ | |
| ./lib/rxnm-roaming.sh ./lib/rxnm-virt.sh \ | |
| ./lib/rxnm-vpn.sh ./lib/rxnm-pppoe.sh \ | |
| ./lib/rxnm-tunnel.sh ./lib/rxnm-mpls.sh \ | |
| ./lib/rxnm-ha.sh ./lib/rxnm-plugins.sh \ | |
| ./lib/rxnm-diagnostics.sh ./lib/rxnm-help.sh; do | |
| [ -f "$f" ] || continue | |
| . "$f" || { echo "FAIL: source $f"; exit 1; } | |
| done | |
| json_success "{\"test\":\"posix_source_all\"}" | grep -q "success.*true" || exit 1 | |
| echo "POSIX source all: OK" | |
| ' || exit 1 | |
| integration-wired: | |
| name: "Integration: Wired & Bridge" | |
| runs-on: ubuntu-24.04 | |
| needs: unit-and-posix | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| - name: Prepare Host Environment | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y systemd-container bridge-utils dbus-user-session jq kmod | |
| if systemctl is-active --quiet apparmor; then | |
| sudo systemctl stop apparmor | |
| sudo systemctl disable apparmor | |
| fi | |
| sudo sysctl -w net.ipv4.ip_forward=1 | |
| sudo sysctl -w net.ipv6.conf.all.forwarding=1 | |
| - name: Build RXNM Agent | |
| run: make tiny | |
| - name: Run Integration Tests | |
| run: | | |
| sudo chmod +x tests/integration/run_interop.sh | |
| sudo ./tests/integration/run_interop.sh --skip-wifi | |
| - name: Cleanup before bundle tests | |
| if: always() | |
| run: | | |
| for m in $(sudo machinectl list --no-legend --no-pager 2>/dev/null | awk '{print $1}'); do | |
| sudo machinectl terminate "$m" 2>/dev/null || true | |
| done | |
| sleep 2 | |
| sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true | |
| - name: Run ROCKNIX Bundle Integration Tests | |
| run: | | |
| make rocknix-release | |
| sudo chmod +x tests/integration/run_rocknix_interop.sh | |
| sudo ./tests/integration/run_rocknix_interop.sh --skip-wifi | |
| - name: Cleanup between bundle test suites | |
| if: always() | |
| run: | | |
| for m in $(sudo machinectl list --no-legend --no-pager 2>/dev/null | awk '{print $1}'); do | |
| sudo machinectl terminate "$m" 2>/dev/null || true | |
| done | |
| sleep 3 | |
| sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true | |
| - name: Run Full Combined Bundle Integration Tests | |
| run: | | |
| make combined-full | |
| sudo chmod +x tests/integration/run_rocknix_interop.sh | |
| sudo BUNDLE_BIN=build/rxnm-full ./tests/integration/run_rocknix_interop.sh --skip-wifi | |
| integration-wifi: | |
| name: "Integration: Virtual WiFi (hwsim)" | |
| runs-on: ubuntu-24.04 | |
| needs: unit-and-posix | |
| continue-on-error: true | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| - name: Restore cached hwsim modules | |
| id: hwsim-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: /tmp/hwsim-cache | |
| key: hwsim-modules-${{ runner.os }}-${{ hashFiles('/proc/version') }} | |
| restore-keys: | | |
| hwsim-modules-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y linux-headers-$(uname -r) build-essential iw rfkill jq systemd-container bridge-utils dbus-user-session kmod curl bc libssl-dev libelf-dev libdw-dev flex bison dpkg-dev | |
| sudo mkdir -p /etc/NetworkManager/conf.d | |
| printf '[keyfile]\nunmanaged-devices=driver:mac80211_hwsim\n' | sudo tee /etc/NetworkManager/conf.d/99-hwsim-ignore.conf >/dev/null | |
| sudo systemctl reload-or-restart NetworkManager || true | |
| sudo systemctl stop wpa_supplicant 2>/dev/null || true | |
| sudo systemctl mask wpa_supplicant || true | |
| if systemctl is-active --quiet apparmor; then | |
| sudo systemctl stop apparmor | |
| sudo systemctl disable apparmor | |
| fi | |
| sudo sysctl -w net.ipv4.ip_forward=1 | |
| sudo sysctl -w net.ipv6.conf.all.forwarding=1 | |
| - name: "Load mac80211_hwsim (Stage 1: prebuilt, Stage 2: cache/compile)" | |
| run: | | |
| HWSIM_LOADED=false | |
| #--------------------------------------------------------------- | |
| # Stage 1: Try prebuilt modules from the running kernel | |
| #--------------------------------------------------------------- | |
| echo "=== Stage 1: Attempting prebuilt modprobe ===" | |
| REQUIRED_MODULES="pkcs8_key_parser arc4 md4 cmac ecb des3_ede algif_skcipher algif_hash af_alg cfg80211 mac80211 rfkill ccm cbc sha256_generic sha512_generic mac80211_hwsim" | |
| MISSING_MODULES="" | |
| for mod in $REQUIRED_MODULES; do | |
| if ! sudo modprobe "$mod" 2>/dev/null; then | |
| MISSING_MODULES="$MISSING_MODULES $mod" | |
| fi | |
| done | |
| if [ -n "$MISSING_MODULES" ]; then | |
| echo "Missing: $MISSING_MODULES" | |
| echo "Installing linux-modules-extra packages..." | |
| sudo apt-get install -y "linux-modules-extra-$(uname -r)" 2>/dev/null || true | |
| # Try azure-specific package as fallback | |
| sudo apt-get install -y linux-modules-extra-azure 2>/dev/null || true | |
| MISSING_MODULES="" | |
| for mod in $REQUIRED_MODULES; do | |
| if ! sudo modprobe "$mod" 2>/dev/null; then | |
| MISSING_MODULES="$MISSING_MODULES $mod" | |
| fi | |
| done | |
| fi | |
| if lsmod | grep -q mac80211_hwsim; then | |
| echo "Stage 1 SUCCESS: mac80211_hwsim loaded from prebuilt modules" | |
| HWSIM_LOADED=true | |
| fi | |
| #--------------------------------------------------------------- | |
| # Stage 2: Restore from cache or compile from kernel.org source | |
| #--------------------------------------------------------------- | |
| if [ "$HWSIM_LOADED" = "false" ]; then | |
| echo "=== Stage 2: Cache/Compile path ===" | |
| CACHE_DIR="/tmp/hwsim-cache" | |
| # 2a: Try restoring from actions/cache | |
| if [ -d "$CACHE_DIR" ] && [ -n "$(ls -A "$CACHE_DIR"/*.ko 2>/dev/null)" ]; then | |
| echo "Cache HIT: Restoring compiled modules from cache..." | |
| sudo mkdir -p "/lib/modules/$(uname -r)/updates" | |
| sudo cp "$CACHE_DIR"/*.ko "/lib/modules/$(uname -r)/updates/" | |
| sudo depmod -a | |
| # Verify vermagic before loading | |
| for ko in "$CACHE_DIR"/*.ko; do | |
| echo " $(basename "$ko"): $(modinfo -F vermagic "$ko" 2>/dev/null || echo 'unknown')" | |
| done | |
| for mod in pkcs8_key_parser arc4 md4 cmac ecb des_generic algif_skcipher algif_hash af_alg cfg80211 mac80211 rfkill ccm cbc sha256_generic sha512_generic; do | |
| sudo modprobe "$mod" 2>/dev/null || true | |
| done | |
| sudo modprobe mac80211_hwsim radios=2 2>/dev/null || true | |
| if lsmod | grep -q mac80211_hwsim; then | |
| echo "Stage 2a SUCCESS: mac80211_hwsim loaded from cached modules" | |
| HWSIM_LOADED=true | |
| else | |
| echo "Cache STALE: vermagic mismatch, clearing cache and recompiling..." | |
| rm -rf "$CACHE_DIR" | |
| fi | |
| fi | |
| # 2b: Out-of-tree build using kernel.org source + installed headers | |
| # | |
| # Strategy: Download mainline source for the .c files, but build | |
| # against the INSTALLED kernel headers (which have Module.symvers | |
| # matching the running Azure kernel). This is the standard | |
| # out-of-tree module build approach: | |
| # make -C /lib/modules/$(uname -r)/build M=/path/to/source modules | |
| # | |
| if [ "$HWSIM_LOADED" = "false" ]; then | |
| echo "Cache MISS: Out-of-tree build from kernel.org source..." | |
| echo "Still missing: $MISSING_MODULES" | |
| KBUILD_DIR="/lib/modules/$(uname -r)/build" | |
| if [ ! -d "$KBUILD_DIR" ]; then | |
| echo "::warning::Kernel build dir not found at $KBUILD_DIR" | |
| echo "Available in /lib/modules/$(uname -r)/:" | |
| ls -la "/lib/modules/$(uname -r)/" 2>/dev/null || true | |
| echo "Available in /usr/src/:" | |
| ls -la /usr/src/ 2>/dev/null || true | |
| echo "HWSIM_FAILED=true" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| echo "Kernel build tree: $KBUILD_DIR" | |
| if [ -f "$KBUILD_DIR/Module.symvers" ]; then | |
| echo "Module.symvers: $(wc -l < "$KBUILD_DIR/Module.symvers") symbols" | |
| else | |
| echo "::warning::Module.symvers missing from build tree!" | |
| fi | |
| # Download mainline source for the module .c files | |
| mkdir -p /tmp/linux-src && cd /tmp/linux-src | |
| KVER=$(uname -r | cut -d- -f1) | |
| KVER_MAJ_MIN=$(echo "$KVER" | sed 's/\.0$//') | |
| MAJOR=$(echo "$KVER" | cut -d. -f1) | |
| URL="https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KVER_MAJ_MIN}.tar.xz" | |
| echo "Downloading $URL..." | |
| if ! curl -fSsl -O "$URL"; then | |
| echo "::warning::Failed to download kernel source" | |
| echo "HWSIM_FAILED=true" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| tar -xf "linux-${KVER_MAJ_MIN}.tar.xz" | |
| SRC_DIR="/tmp/linux-src/linux-${KVER_MAJ_MIN}" | |
| # Locate hwsim source (moved to virtual/ in kernel 6.x) | |
| HWSIM_SRC=$(find "$SRC_DIR/drivers/net/wireless" -name "mac80211_hwsim.c" | head -n1) | |
| echo "hwsim source: $HWSIM_SRC" | |
| # Out-of-tree builds need explicit obj-m in a standalone Kbuild. | |
| # The upstream Makefiles use obj-$(CONFIG_*) which isn't set in M= context. | |
| # Build mac80211_hwsim (copy all files — .c needs .h from same dir) | |
| echo "=== Building mac80211_hwsim ===" | |
| HWSIM_BUILD=/tmp/build-hwsim | |
| mkdir -p "$HWSIM_BUILD" | |
| cp "$(dirname "$HWSIM_SRC")"/* "$HWSIM_BUILD/" | |
| echo "obj-m := mac80211_hwsim.o" > "$HWSIM_BUILD/Makefile" | |
| make -C "$KBUILD_DIR" M="$HWSIM_BUILD" modules \ | |
| && echo "hwsim build SUCCESS" \ | |
| || echo "::warning::hwsim build failed" | |
| # Build arc4 (IWD WPA dependency) | |
| echo "=== Building arc4 ===" | |
| ARC4_BUILD=/tmp/build-arc4 | |
| mkdir -p "$ARC4_BUILD" | |
| cp "$SRC_DIR/crypto/arc4.c" "$ARC4_BUILD/" | |
| echo "obj-m := arc4.o" > "$ARC4_BUILD/Makefile" | |
| make -C "$KBUILD_DIR" M="$ARC4_BUILD" modules \ | |
| && echo "arc4 build SUCCESS" \ | |
| || echo "::warning::arc4 build failed" | |
| # Install any successfully compiled .ko files | |
| sudo mkdir -p "/lib/modules/$(uname -r)/updates" | |
| mkdir -p "$CACHE_DIR" | |
| BUILT_COUNT=0 | |
| for ko in $(find /tmp/build-* -name "*.ko" -type f 2>/dev/null); do | |
| echo " Built: $(basename "$ko") vermagic=$(modinfo -F vermagic "$ko" 2>/dev/null || echo N/A)" | |
| sudo cp "$ko" "/lib/modules/$(uname -r)/updates/" | |
| cp "$ko" "$CACHE_DIR/" | |
| BUILT_COUNT=$((BUILT_COUNT + 1)) | |
| done | |
| if [ "$BUILT_COUNT" -eq 0 ]; then | |
| echo "::warning::No .ko files were produced" | |
| echo "HWSIM_FAILED=true" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| sudo depmod -a | |
| # Load modules in dependency order | |
| for mod in pkcs8_key_parser arc4 md4 cmac ecb des_generic algif_skcipher algif_hash af_alg cfg80211 mac80211 rfkill ccm cbc sha256_generic sha512_generic; do | |
| sudo modprobe "$mod" 2>/dev/null || true | |
| done | |
| sudo modprobe mac80211_hwsim radios=2 2>/dev/null || true | |
| if lsmod | grep -q mac80211_hwsim; then | |
| echo "Stage 2b SUCCESS: mac80211_hwsim loaded" | |
| HWSIM_LOADED=true | |
| else | |
| echo "::warning::Modules built but mac80211_hwsim failed to load" | |
| echo "dmesg tail:" | |
| sudo dmesg | tail -n 10 | |
| fi | |
| fi | |
| fi | |
| #--------------------------------------------------------------- | |
| # Final status | |
| #--------------------------------------------------------------- | |
| if [ "$HWSIM_LOADED" = "true" ]; then | |
| echo "HWSIM_FAILED=false" >> "$GITHUB_ENV" | |
| echo "::notice::mac80211_hwsim loaded successfully" | |
| lsmod | grep -E "mac80211|cfg80211|hwsim" | |
| else | |
| echo "HWSIM_FAILED=true" >> "$GITHUB_ENV" | |
| echo "::warning::mac80211_hwsim failed to load on this runner" | |
| echo "--- dmesg (last 40 lines) ---" | |
| sudo dmesg | tail -n 40 | |
| echo "--- modinfo for cached .ko files ---" | |
| if ls /tmp/hwsim-cache/*.ko >/dev/null 2>&1; then | |
| for ko in /tmp/hwsim-cache/*.ko; do | |
| [ -f "$ko" ] && echo " $(basename "$ko"): vermagic=$(modinfo -F vermagic "$ko" 2>/dev/null || echo 'N/A')" | |
| done | |
| fi | |
| echo "Running kernel: $(uname -r)" | |
| fi | |
| - name: Build RXNM Agent | |
| run: make tiny | |
| - name: Run WiFi Integration Tests | |
| if: env.HWSIM_FAILED != 'true' | |
| run: | | |
| sudo chmod +x tests/integration/run_interop.sh | |
| sudo ./tests/integration/run_interop.sh --wifi-only | |
| - name: Cleanup and reset hwsim between test suites | |
| if: always() && env.HWSIM_FAILED != 'true' | |
| run: | | |
| for m in $(sudo machinectl list --no-legend --no-pager 2>/dev/null | awk '{print $1}'); do | |
| sudo machinectl terminate "$m" 2>/dev/null || true | |
| done | |
| sleep 3 | |
| sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true | |
| # Reload hwsim to get fresh virtual radios for the next test suite. | |
| # After PHY injection + container termination, radios can be wedged. | |
| sudo modprobe -r mac80211_hwsim 2>/dev/null || true | |
| sleep 1 | |
| sudo modprobe mac80211_hwsim radios=2 2>/dev/null || true | |
| sleep 2 | |
| - name: Run ROCKNIX Bundle WiFi Tests | |
| if: env.HWSIM_FAILED != 'true' | |
| run: | | |
| make rocknix-release | |
| sudo chmod +x tests/integration/run_rocknix_interop.sh | |
| sudo ./tests/integration/run_rocknix_interop.sh --wifi-only | |
| - name: Cleanup and reset hwsim between test suites | |
| if: always() && env.HWSIM_FAILED != 'true' | |
| run: | | |
| for m in $(sudo machinectl list --no-legend --no-pager 2>/dev/null | awk '{print $1}'); do | |
| sudo machinectl terminate "$m" 2>/dev/null || true | |
| done | |
| sleep 3 | |
| sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true | |
| sudo modprobe -r mac80211_hwsim 2>/dev/null || true | |
| sleep 1 | |
| sudo modprobe mac80211_hwsim radios=2 2>/dev/null || true | |
| sleep 2 | |
| sleep 3 | |
| sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true | |
| - name: Run Full Combined Bundle WiFi Tests | |
| if: env.HWSIM_FAILED != 'true' | |
| run: | | |
| make combined-full | |
| sudo chmod +x tests/integration/run_rocknix_interop.sh | |
| sudo BUNDLE_BIN=build/rxnm-full ./tests/integration/run_rocknix_interop.sh --wifi-only | |
| - name: WiFi tests skipped (hwsim unavailable) | |
| if: env.HWSIM_FAILED == 'true' | |
| run: | | |
| echo "::error::mac80211_hwsim could not be loaded. WiFi integration tests cannot run." | |
| exit 1 |