agama/live/test/hcn/test_integration.bats
2026-07-23 11:43:19 +01:00

646 lines
25 KiB
Bash

#!/usr/bin/env bats
# Integration tests for parse-hcn.sh
# Tests complete end-to-end workflows (module renamed from 99hcnmgr to 99hcn)
FIXTURE_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" && pwd)"
SCRIPT_PATH="${FIXTURE_DIR}/../../live-root/usr/lib/dracut/modules.d/99hcn/parse-hcn.sh"
setup_file() {
export BATS_TMPDIR="${BATS_TEST_TMPDIR:-/tmp}/hcn-integration"
mkdir -p "$BATS_TMPDIR"
}
teardown_file() {
[ -d "$BATS_TMPDIR" ] && rm -rf "$BATS_TMPDIR"
}
setup() {
TEST_WORK_DIR="$(mktemp -d "$BATS_TMPDIR/test.XXXXXX")"
export TEST_WORK_DIR
}
teardown() {
[ -d "$TEST_WORK_DIR" ] && rm -rf "$TEST_WORK_DIR"
}
# ========================================
# Integration Test: Complete Workflow
# ========================================
@test "integration: complete bond discovery and configuration workflow" {
# This test simulates the complete workflow:
# 1. Device discovery from device-tree
# 2. MAPPINGS generation
# 3. Bond configuration generation
# 4. Command line argument generation
# 5. nm-initrd-generator invocation (mocked)
# 6. Connection fixup
# Setup mock environment
export MOCK_PROC="$FIXTURE_DIR/proc"
export MOCK_SYS="$FIXTURE_DIR/sys"
# Expected MAPPINGS based on fixtures
EXPECTED_MAPPINGS="bond333e80f5 enP32775p1s0 2e:7a:30:83:f5:00 primary bond333e80f5 env6 2e:7a:32:2d:3d:06 none"
# Verify MAPPINGS would be built correctly
# Device 1: PCI ethernet (primary)
hcn_id_1=$(hexdump -n 4 -ve '/1 "%02x"' "$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0/ibm,hcn-id")
mode_1=$(tr -d '\0' <"$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0/ibm,hcn-mode" 2>/dev/null | tr -d ' ')
mac_1=$(hexdump -ve '/1 "%02x:"' "$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0/local-mac-address" | sed 's/:$//')
dev_1=$(cat "$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0/ofpathname")
[ "$hcn_id_1" = "333e80f5" ]
[ "$mode_1" = "primary" ]
[ "$mac_1" = "2e:7a:30:83:f5:00" ]
[ "$dev_1" = "enP32775p1s0" ]
# Device 2: VNIC (backup)
hcn_id_2=$(hexdump -n 4 -ve '/1 "%02x"' "$MOCK_PROC/device-tree/vdevice/vnic@30000006/ibm,hcn-id")
mode_2=$(tr -d '\0' <"$MOCK_PROC/device-tree/vdevice/vnic@30000006/ibm,hcn-mode" 2>/dev/null | tr -d ' ')
mac_2=$(hexdump -ve '/1 "%02x:"' "$MOCK_PROC/device-tree/vdevice/vnic@30000006/local-mac-address" | sed 's/:$//')
dev_2=$(cat "$MOCK_PROC/device-tree/vdevice/vnic@30000006/ofpathname")
[ "$hcn_id_2" = "333e80f5" ]
[ "$mode_2" = "backup" ]
[ "$mac_2" = "2e:7a:32:2d:3d:06" ]
[ "$dev_2" = "env6" ]
# Both devices should map to same bond
[ "$hcn_id_1" = "$hcn_id_2" ]
}
@test "integration: bond configuration matches expected format" {
# Verify the bond configuration generated would be correct
BONDNAME="bond333e80f5"
MAPPINGS="bond333e80f5 enP32775p1s0 2e7a3083f500 primary bond333e80f5 env6 2e7a322d3d06 none"
# Extract slaves
SLAVES=""
PRIMARY=""
set -- $MAPPINGS
while [ $# -ge 4 ]; do
if [ "$1" = "$BONDNAME" ]; then
SLAVES="${SLAVES:+$SLAVES,}$2"
[ "$4" = "primary" ] && PRIMARY="$2"
fi
shift 4
done
# Verify extracted values
[ "$SLAVES" = "enP32775p1s0,env6" ]
[ "$PRIMARY" = "enP32775p1s0" ]
# Build bond options
BOND_OPTS="mode=1,miimon=100,fail_over_mac=2${PRIMARY:+,primary=$PRIMARY}"
# Verify bond options
[ "$BOND_OPTS" = "mode=1,miimon=100,fail_over_mac=2,primary=enP32775p1s0" ]
# Verify complete bond argument
BOND_ARG="bond=$BONDNAME:$SLAVES:$BOND_OPTS"
[ "$BOND_ARG" = "bond=bond333e80f5:enP32775p1s0,env6:mode=1,miimon=100,fail_over_mac=2,primary=enP32775p1s0" ]
}
@test "integration: connection fixup transforms UUID to bond name" {
# Copy nm-initrd-generator connections to test directory
mkdir -p "$TEST_WORK_DIR/connections"
cp -r "$FIXTURE_DIR/nm-initrd-generator-connections"/* "$TEST_WORK_DIR/connections/"
# Verify initial state (UUID controller)
grep -q "controller=e2a7b4b8-8297-4185-8421-ed32cf47db1c" \
"$TEST_WORK_DIR/connections/enP32775p1s0.nmconnection"
# Source the parse_nm_connection function
source <(sed -n '/^parse_nm_connection()/,/^}/p' "$SCRIPT_PATH")
# Parse the bond connection to get UUID mapping
IFS='|' read -r bond_id bond_uuid _rest <<EOF
$(parse_nm_connection "$TEST_WORK_DIR/connections/bond333e80f5.nmconnection")
EOF
[ "$bond_id" = "bond333e80f5" ]
[ "$bond_uuid" = "e2a7b4b8-8297-4185-8421-ed32cf47db1c" ]
# Simulate UUID to name mapping
uuid_map="$bond_uuid:$bond_id"
# Apply transformation to slave connection
sed -i "s/controller=$bond_uuid/controller=$bond_id/" \
"$TEST_WORK_DIR/connections/enP32775p1s0.nmconnection"
# Verify transformation
grep -q "controller=bond333e80f5" \
"$TEST_WORK_DIR/connections/enP32775p1s0.nmconnection"
}
@test "integration: connection renaming follows bond-slave pattern" {
# Test the complete renaming workflow
mkdir -p "$TEST_WORK_DIR/connections"
cp "$FIXTURE_DIR/nm-initrd-generator-connections/enP32775p1s0.nmconnection" \
"$TEST_WORK_DIR/connections/"
# Initial state
old_id="enP32775p1s0"
new_id="bond333e80f5-enP32775p1s0"
# Apply renaming
sed -i "s/^id=$old_id$/id=$new_id/" \
"$TEST_WORK_DIR/connections/enP32775p1s0.nmconnection"
mv "$TEST_WORK_DIR/connections/enP32775p1s0.nmconnection" \
"$TEST_WORK_DIR/connections/$new_id.nmconnection"
# Verify
[ -f "$TEST_WORK_DIR/connections/$new_id.nmconnection" ]
[ ! -f "$TEST_WORK_DIR/connections/$old_id.nmconnection" ]
grep -q "^id=$new_id$" "$TEST_WORK_DIR/connections/$new_id.nmconnection"
}
# ========================================
# Integration Test: Real Command Line Scenarios
# ========================================
@test "integration: scenario 1 - DHCP on bond" {
# User boots with: ip=dhcp
# Expected output: bond=bond333e80f5:... ip=bond333e80f5:dhcp
BONDNAME="bond333e80f5"
HCN_IP="dhcp"
# Generate bond argument
BOND_ARG="bond=$BONDNAME:enP32775p1s0,env6:mode=1,miimon=100,fail_over_mac=2,primary=enP32775p1s0"
# Generate IP argument
IP_ARG="ip=$BONDNAME:$HCN_IP"
CMDLINE="$BOND_ARG $IP_ARG"
echo "$CMDLINE" | grep -q "bond=bond333e80f5"
echo "$CMDLINE" | grep -q "ip=bond333e80f5:dhcp"
}
@test "integration: scenario 2 - static IP on slave interface (should be replaced)" {
# User boots with: hcn.ip=10.2.2.69::10.2.0.1:255.255.255.0::env6:off
# Expected: slave 'env6' replaced with 'bond333e80f5'
BONDNAME="bond333e80f5"
HCN_IP="10.2.2.69::10.2.0.1:255.255.255.0::env6:off"
SLAVE_NAMES=" enP32775p1s0 env6"
# Simulate replacement logic
matched=0
for slave in $SLAVE_NAMES; do
slave_dash=$(echo "$slave" | tr ':' '-')
if echo ":$HCN_IP:" | grep -qE ":($slave|$slave_dash):"; then
matched=1
current_hcn_ip=$(echo "$HCN_IP" | sed -E "s/^($slave|$slave_dash)([: ]|$)/$BONDNAME\2/; s/([: ])($slave|$slave_dash)([: ]|$)/\1$BONDNAME\3/g")
break
fi
done
[ "$matched" -eq 1 ]
[ "$current_hcn_ip" = "10.2.2.69::10.2.0.1:255.255.255.0::bond333e80f5:off" ]
}
@test "integration: scenario 3 - route on slave interface (should be replaced)" {
# User boots with: rd.route=default:10.2.0.1:enP32775p1s0
# Expected: slave replaced with bond
BONDNAME="bond333e80f5"
HCN_ROUTE="default:10.2.0.1:enP32775p1s0"
SLAVE_NAMES=" enP32775p1s0 env6"
matched=0
for slave in $SLAVE_NAMES; do
slave_dash=$(echo "$slave" | tr ':' '-')
if echo ":$HCN_ROUTE:" | grep -qE ":($slave|$slave_dash):"; then
matched=1
current_hcn_route=$(echo "$HCN_ROUTE" | sed -E "s/^($slave|$slave_dash)([: ]|$)/$BONDNAME\2/; s/([: ])($slave|$slave_dash)([: ]|$)/\1$BONDNAME\3/g")
break
fi
done
[ "$matched" -eq 1 ]
[ "$current_hcn_route" = "default:10.2.0.1:bond333e80f5" ]
}
@test "integration: scenario 4 - MAC address format in command line" {
# User boots with MAC address in dashed format: ip=dhcp::::2e-7a-30-83-f5-00
# Should match and replace with bond
BONDNAME="bond333e80f5"
CMDLINE_IP="dhcp::::2e-7a-30-83-f5-00"
SLAVE_MACS=" 2e:7a:30:83:f5:00 2e:7a:32:2d:3d:06"
matched=0
for mac in $SLAVE_MACS; do
mac_dash=$(echo "$mac" | tr ':' '-')
if echo ":$CMDLINE_IP:" | grep -qE ":($mac|$mac_dash):"; then
matched=1
break
fi
done
# This should match
[ "$matched" -eq 1 ]
}
# ========================================
# Integration Test: Multiple Bonds
# ========================================
@test "integration: multiple bonds are handled independently" {
# Simulate two different bonds
MAPPINGS="bond333e80f5 enP32775p1s0 2e7a3083f500 primary bond333e80f5 env6 2e7a322d3d06 none bond5558a5a3 enP16386p1s0 none primary bond5558a5a3 env7 none none"
BOND_NAMES=$(echo "$MAPPINGS" | awk '{for(i=1;i<=NF;i+=4) print $i}' | sort -u)
# Should have two bonds
bond_count=$(echo "$BOND_NAMES" | wc -w)
[ "$bond_count" -eq 2 ]
# Generate configuration for each bond
NEW_ARGS=""
for BONDNAME in $BOND_NAMES; do
SLAVES=""
PRIMARY=""
set -- $MAPPINGS
while [ $# -ge 4 ]; do
if [ "$1" = "$BONDNAME" ]; then
SLAVES="${SLAVES:+$SLAVES,}$2"
[ "$4" = "primary" ] && PRIMARY="$2"
fi
shift 4
done
BOND_OPTS="mode=1,miimon=100,fail_over_mac=2${PRIMARY:+,primary=$PRIMARY}"
NEW_ARGS="$NEW_ARGS bond=$BONDNAME:$SLAVES:$BOND_OPTS"
done
# Verify both bonds are configured
echo "$NEW_ARGS" | grep -q "bond=bond333e80f5:enP32775p1s0,env6"
echo "$NEW_ARGS" | grep -q "bond=bond5558a5a3:enP16386p1s0,env7"
}
@test "integration: multiple bonds with multiple IPs are mapped individually based on MAC" {
# This simulates the user's reported bug where multiple rd.hcn.ip= arguments with different MACs
# are passed to multiple bonds, ensuring they are only mapped to the bond containing that MAC.
MAPPINGS="bond22f58d45 enP32800p1s0 2e:7a:3c:6a:1c:00 primary bond156efe56 enP32802p1s0 2e:7a:3c:6a:1c:01 primary bond22f58d45 env5 2e:7a:3e:73:69:05 backup bond156efe56 env6 2e:7a:3e:73:69:06 backup"
# We extract unique bond names
BOND_NAMES=$(echo "$MAPPINGS" | awk '{for(i=1;i<=NF;i+=4) if (!seen[$i]++) print $i}')
# Mock command-line arguments for rd.hcn.ip
MOCK_IPS="10.2.2.65::10.2.0.1:255.255.255.0::2e-7a-3c-6a-1c-00:off 10.2.2.66::10.2.0.1:255.255.255.0::2e-7a-3c-6a-1c-01:off"
# Helper function matching parse-hcn.sh
str_replace() {
echo "$1" | tr "$2" "$3"
}
NEW_ARGS=""
for BONDNAME in $BOND_NAMES; do
SLAVES="" SLAVE_NAMES="" SLAVE_MACS="" PRIMARY=""
OTHER_SLAVE_NAMES="" OTHER_SLAVE_MACS=""
# Extract slaves for this bond and other bonds from MAPPINGS
set -- $MAPPINGS
while [ $# -ge 4 ]; do
if [ "$1" = "$BONDNAME" ]; then
SLAVE_NAMES="$SLAVE_NAMES $2"
[ "$3" != "none" ] && SLAVE_MACS="$SLAVE_MACS $3"
[ "$4" = "primary" ] && PRIMARY="$2"
SLAVES="${SLAVES:+$SLAVES,}$2"
else
OTHER_SLAVE_NAMES="$OTHER_SLAVE_NAMES $2"
[ "$3" != "none" ] && OTHER_SLAVE_MACS="$OTHER_SLAVE_MACS $3"
fi
shift 4
done
# Process mocked rd.hcn.ip arguments
for HCN_IP in $MOCK_IPS; do
matched=0
for slave in $SLAVE_NAMES $SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
if echo ":$HCN_IP:" | grep -q "[:]$slave[:]" || echo ":$HCN_IP:" | grep -q "[:]$slave_dash[:]"; then
matched=1
current_hcn_ip=$(echo "$HCN_IP" | sed -E "s#^($slave|$slave_dash)([: ]|$)#$BONDNAME\2#; s#([: ])($slave|$slave_dash)([: ]|$)#\1$BONDNAME\3#g")
NEW_ARGS="$NEW_ARGS ip=$current_hcn_ip"
break
fi
done
# Check if this IP is targeted at another bond's slave
matched_other=0
for slave in $OTHER_SLAVE_NAMES $OTHER_SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
if echo ":$HCN_IP:" | grep -q "[:]$slave[:]" || echo ":$HCN_IP:" | grep -q "[:]$slave_dash[:]"; then
matched_other=1
break
fi
done
has_bond_ip=0
case "$HCN_IP" in
*:bond[0-9]*) has_bond_ip=1 ;;
esac
# BOND_NAMES is newline-separated; leave unquoted so awk sees a
# single record and returns just the first token (matches the real
# parse-hcn.sh, which computes FIRST_BOND once, unquoted).
# shellcheck disable=SC2086
FIRST_BOND=$(echo $BOND_NAMES | awk '{print $1}')
if [ $matched -eq 0 ] && [ $matched_other -eq 0 ] && [ $has_bond_ip -eq 0 ] && [ "$BONDNAME" = "$FIRST_BOND" ]; then
NEW_ARGS="$NEW_ARGS ip=$BONDNAME:generic"
fi
done
done
# Verify that each IP is assigned to exactly one bond (and correct one)
# The first IP (ending with MAC 1c:00) should only be assigned to bond22f58d45
echo "$NEW_ARGS" | grep -q "ip=10.2.2.65::10.2.0.1:255.255.255.0::bond22f58d45:off"
# The second IP (ending with MAC 1c:01) should only be assigned to bond156efe56
echo "$NEW_ARGS" | grep -q "ip=10.2.2.66::10.2.0.1:255.255.255.0::bond156efe56:off"
# Crucially, check that there is NO cross-assignment of IPs!
# bond22f58d45 should NOT have 10.2.2.66
! echo "$NEW_ARGS" | grep -q "ip=10.2.2.66::10.2.0.1:255.255.255.0::bond22f58d45:off"
# bond156efe56 should NOT have 10.2.2.65
! echo "$NEW_ARGS" | grep -q "ip=10.2.2.65::10.2.0.1:255.255.255.0::bond156efe56:off"
}
@test "integration: unqualified dhcp fans out to every bond" {
# A bare rd.hcn.ip=dhcp carries no per-host address, so each discovered bond
# must obtain its own DHCP lease. This mirrors the method-only fallback in
# parse-hcn.sh (no FIRST_BOND restriction), unlike a static address which
# falls back to the first bond only.
MAPPINGS="bond22f58d45 enP32800p1s0 2e:7a:3c:6a:1c:00 primary bond156efe56 enP32802p1s0 2e:7a:3c:6a:1c:01 primary bond22f58d45 env5 2e:7a:3e:73:69:05 backup bond156efe56 env6 2e:7a:3e:73:69:06 backup"
BOND_NAMES=$(echo "$MAPPINGS" | awk '{for(i=1;i<=NF;i+=4) if (!seen[$i]++) print $i}')
# shellcheck disable=SC2086
FIRST_BOND=$(echo $BOND_NAMES | awk '{print $1}')
str_replace() { echo "$1" | tr "$2" "$3"; }
MOCK_IPS="dhcp"
NEW_ARGS=""
for BONDNAME in $BOND_NAMES; do
SLAVE_NAMES="" SLAVE_MACS="" OTHER_SLAVE_NAMES="" OTHER_SLAVE_MACS=""
set -- $MAPPINGS
while [ $# -ge 4 ]; do
if [ "$1" = "$BONDNAME" ]; then
SLAVE_NAMES="$SLAVE_NAMES $2"
[ "$3" != "none" ] && SLAVE_MACS="$SLAVE_MACS $3"
else
OTHER_SLAVE_NAMES="$OTHER_SLAVE_NAMES $2"
[ "$3" != "none" ] && OTHER_SLAVE_MACS="$OTHER_SLAVE_MACS $3"
fi
shift 4
done
for HCN_IP in $MOCK_IPS; do
matched=0
for slave in $SLAVE_NAMES $SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
if echo ":$HCN_IP:" | grep -q "[:]$slave[:]" || echo ":$HCN_IP:" | grep -q "[:]$slave_dash[:]"; then
matched=1; break
fi
done
matched_other=0
for slave in $OTHER_SLAVE_NAMES $OTHER_SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
if echo ":$HCN_IP:" | grep -q "[:]$slave[:]" || echo ":$HCN_IP:" | grep -q "[:]$slave_dash[:]"; then
matched_other=1; break
fi
done
has_bond_ip=0
case "$HCN_IP" in *:bond[0-9]*) has_bond_ip=1 ;; esac
if [ $matched -eq 0 ] && [ $matched_other -eq 0 ] && [ $has_bond_ip -eq 0 ]; then
case "$HCN_IP" in
dhcp | on | any | single-dhcp | dhcp6 | auto6 | ibft | either6 | link6)
# Method-only: apply to every bond
NEW_ARGS="$NEW_ARGS ip=$BONDNAME:$HCN_IP" ;;
*)
# Static: first bond only
[ "$BONDNAME" = "$FIRST_BOND" ] || continue
NEW_ARGS="$NEW_ARGS ip=$BONDNAME:static-fallback" ;;
esac
fi
done
done
# dhcp must be applied to BOTH bonds, not just the first
echo "$NEW_ARGS" | grep -q "ip=bond22f58d45:dhcp"
echo "$NEW_ARGS" | grep -q "ip=bond156efe56:dhcp"
}
@test "integration: unqualified static config falls back to the first bond only" {
# A fixed address with no interface field can belong to only one bond, so it
# must be applied to the first discovered bond and to no other.
MAPPINGS="bond22f58d45 enP32800p1s0 2e:7a:3c:6a:1c:00 primary bond156efe56 enP32802p1s0 2e:7a:3c:6a:1c:01 primary bond22f58d45 env5 2e:7a:3e:73:69:05 backup bond156efe56 env6 2e:7a:3e:73:69:06 backup"
BOND_NAMES=$(echo "$MAPPINGS" | awk '{for(i=1;i<=NF;i+=4) if (!seen[$i]++) print $i}')
# shellcheck disable=SC2086
FIRST_BOND=$(echo $BOND_NAMES | awk '{print $1}')
str_replace() { echo "$1" | tr "$2" "$3"; }
# Static address, no interface field (5 colons, interface slot empty)
MOCK_IPS="10.2.2.65::10.2.0.1:255.255.255.0::off"
NEW_ARGS=""
for BONDNAME in $BOND_NAMES; do
SLAVE_NAMES="" SLAVE_MACS="" OTHER_SLAVE_NAMES="" OTHER_SLAVE_MACS=""
set -- $MAPPINGS
while [ $# -ge 4 ]; do
if [ "$1" = "$BONDNAME" ]; then
SLAVE_NAMES="$SLAVE_NAMES $2"
[ "$3" != "none" ] && SLAVE_MACS="$SLAVE_MACS $3"
else
OTHER_SLAVE_NAMES="$OTHER_SLAVE_NAMES $2"
[ "$3" != "none" ] && OTHER_SLAVE_MACS="$OTHER_SLAVE_MACS $3"
fi
shift 4
done
for HCN_IP in $MOCK_IPS; do
matched=0
for slave in $SLAVE_NAMES $SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
if echo ":$HCN_IP:" | grep -q "[:]$slave[:]" || echo ":$HCN_IP:" | grep -q "[:]$slave_dash[:]"; then
matched=1; break
fi
done
matched_other=0
for slave in $OTHER_SLAVE_NAMES $OTHER_SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
if echo ":$HCN_IP:" | grep -q "[:]$slave[:]" || echo ":$HCN_IP:" | grep -q "[:]$slave_dash[:]"; then
matched_other=1; break
fi
done
has_bond_ip=0
case "$HCN_IP" in *:bond[0-9]*) has_bond_ip=1 ;; esac
if [ $matched -eq 0 ] && [ $matched_other -eq 0 ] && [ $has_bond_ip -eq 0 ]; then
case "$HCN_IP" in
dhcp | on | any | single-dhcp | dhcp6 | auto6 | ibft | either6 | link6)
NEW_ARGS="$NEW_ARGS ip=$BONDNAME:$HCN_IP" ;;
*)
[ "$BONDNAME" = "$FIRST_BOND" ] || continue
NEW_ARGS="$NEW_ARGS ip=$HCN_IP:$BONDNAME:none" ;;
esac
fi
done
done
# Applied to the first bond only
echo "$NEW_ARGS" | grep -q "ip=10.2.2.65::10.2.0.1:255.255.255.0::off:bond22f58d45:none"
! echo "$NEW_ARGS" | grep -q "bond156efe56:none"
}
@test "integration: uppercase MACs map to the correct bond (case-insensitive)" {
# MACs discovered from device-tree are lowercase, but may be supplied in
# uppercase on the kernel command line. Matching must be case-insensitive
# and must not cross-assign IPs between bonds.
MAPPINGS="bond22f58d45 enP32800p1s0 2e:7a:3c:6a:1c:00 primary bond156efe56 enP32802p1s0 2e:7a:3c:6a:1c:01 primary bond22f58d45 env5 2e:7a:3e:73:69:05 backup bond156efe56 env6 2e:7a:3e:73:69:06 backup"
BOND_NAMES=$(echo "$MAPPINGS" | awk '{for(i=1;i<=NF;i+=4) if (!seen[$i]++) print $i}')
str_replace() { echo "$1" | tr "$2" "$3"; }
# Uppercase, dash-separated MACs on the command line
MOCK_IPS="10.2.2.65::10.2.0.1:255.255.255.0::2E-7A-3C-6A-1C-00:off 10.2.2.66::10.2.0.1:255.255.255.0::2E-7A-3C-6A-1C-01:off"
NEW_ARGS=""
for BONDNAME in $BOND_NAMES; do
SLAVE_NAMES="" SLAVE_MACS=""
set -- $MAPPINGS
while [ $# -ge 4 ]; do
if [ "$1" = "$BONDNAME" ]; then
SLAVE_NAMES="$SLAVE_NAMES $2"
[ "$3" != "none" ] && SLAVE_MACS="$SLAVE_MACS $3"
fi
shift 4
done
for HCN_IP in $MOCK_IPS; do
HCN_IP_LC=$(echo "$HCN_IP" | tr 'A-Z' 'a-z')
for slave in $SLAVE_NAMES $SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
hcn_cmp=$HCN_IP
case "$slave" in *:*) hcn_cmp=$HCN_IP_LC ;; esac
if echo ":$hcn_cmp:" | grep -q "[:]$slave[:]" || echo ":$hcn_cmp:" | grep -q "[:]$slave_dash[:]"; then
current_hcn_ip=$(echo "$hcn_cmp" | sed -E "s#^($slave|$slave_dash)([: ]|$)#$BONDNAME\2#; s#([: ])($slave|$slave_dash)([: ]|$)#\1$BONDNAME\3#g")
NEW_ARGS="$NEW_ARGS ip=$current_hcn_ip"
break
fi
done
done
done
# Uppercase MACs must still resolve to the right bond
echo "$NEW_ARGS" | grep -q "ip=10.2.2.65::10.2.0.1:255.255.255.0::bond22f58d45:off"
echo "$NEW_ARGS" | grep -q "ip=10.2.2.66::10.2.0.1:255.255.255.0::bond156efe56:off"
# No cross-assignment
! echo "$NEW_ARGS" | grep -q "ip=10.2.2.66::10.2.0.1:255.255.255.0::bond22f58d45:off"
! echo "$NEW_ARGS" | grep -q "ip=10.2.2.65::10.2.0.1:255.255.255.0::bond156efe56:off"
}
# ========================================
# Integration Test: Error Conditions
# ========================================
@test "integration: handles missing device-tree gracefully" {
# If /proc/device-tree doesn't exist, script should exit cleanly
# This would be the check in the script
if [ ! -d "/nonexistent/device-tree" ]; then
MAPPINGS=""
fi
[ -z "$MAPPINGS" ]
}
@test "integration: handles no HCN devices gracefully" {
# If no ibm,hcn-id files exist, should result in empty MAPPINGS
MAPPINGS=""
if [ -z "$MAPPINGS" ]; then
# Script should log and exit
result="no HCN devices found"
fi
[ "$result" = "no HCN devices found" ]
}
# ========================================
# Integration Test: Compatibility
# ========================================
@test "integration: works with existing cmdline arguments" {
# If user has other dracut arguments, they should be preserved
EXISTING_CMDLINE="root=/dev/sda1 console=ttyS0"
NEW_ARGS="bond=bond333e80f5:enP32775p1s0,env6:mode=1,miimon=100,fail_over_mac=2,primary=enP32775p1s0 ip=bond333e80f5:dhcp"
FINAL_CMDLINE="$EXISTING_CMDLINE $NEW_ARGS"
# Verify both old and new arguments present
echo "$FINAL_CMDLINE" | grep -q "root=/dev/sda1"
echo "$FINAL_CMDLINE" | grep -q "console=ttyS0"
echo "$FINAL_CMDLINE" | grep -q "bond=bond333e80f5"
echo "$FINAL_CMDLINE" | grep -q "ip=bond333e80f5:dhcp"
}
@test "integration: connection files have correct permissions" {
# NetworkManager connection files should have 600 permissions
mkdir -p "$TEST_WORK_DIR/connections"
cp "$FIXTURE_DIR/hcn-connections/bond333e80f5.nmconnection" \
"$TEST_WORK_DIR/connections/"
# In real scenario, nm-initrd-generator creates with 600
chmod 600 "$TEST_WORK_DIR/connections/bond333e80f5.nmconnection"
# Verify permissions
perms=$(stat -c "%a" "$TEST_WORK_DIR/connections/bond333e80f5.nmconnection")
[ "$perms" = "600" ]
}
# ========================================
# Integration Test: Validation
# ========================================
@test "integration: validates expected vs actual connection transformation" {
# Compare nm-initrd-generator output with expected hcn output
source <(sed -n '/^parse_nm_connection()/,/^}/p' "$SCRIPT_PATH")
# Parse nm-initrd-generator slave connection
IFS='|' read -r nm_id nm_uuid nm_ifname nm_master nm_controller nm_mac <<EOF
$(parse_nm_connection "$FIXTURE_DIR/nm-initrd-generator-connections/enP32775p1s0.nmconnection")
EOF
# Parse expected hcn slave connection
IFS='|' read -r hcn_id hcn_uuid hcn_ifname hcn_master hcn_controller hcn_mac <<EOF
$(parse_nm_connection "$FIXTURE_DIR/hcn-connections/bond333e80f5-enP32775p1s0.nmconnection")
EOF
# Verify transformation expectations
[ "$nm_id" = "enP32775p1s0" ]
[ "$hcn_id" = "bond333e80f5-enP32775p1s0" ]
[ "$nm_ifname" = "enP32775p1s0" ]
[ "$hcn_ifname" = "enP32775p1s0" ]
# Controller changed from UUID to bond name
[ "$nm_controller" = "e2a7b4b8-8297-4185-8421-ed32cf47db1c" ]
[ "$hcn_controller" = "bond333e80f5" ]
}