mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-21 23:28:19 +00:00
Merge pull request #7925 from VersatusHPC/test/r26-genesis-behavior
test(genesis): exercise startup and OpenEmbedded build contracts
This commit is contained in:
@@ -4,13 +4,15 @@ on:
|
||||
paths:
|
||||
- 'xCAT-genesis-base/oe/**'
|
||||
- 'xCAT-genesis-scripts/**'
|
||||
- 'xCAT-test/native/genesis_metadata.py'
|
||||
- 'xCAT-test/native/genesis_console.py'
|
||||
- '.github/workflows/genesis_openembedded.yml'
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
genesis_openembedded:
|
||||
name: OpenEmbedded metadata
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
@@ -20,14 +22,18 @@ jobs:
|
||||
cache-dependency-path: xCAT-genesis-base/oe/requirements.txt
|
||||
- name: Install OpenEmbedded host dependencies
|
||||
run: >-
|
||||
sudo env DEBIAN_FRONTEND=noninteractive apt-get update &&
|
||||
sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y
|
||||
--no-install-recommends --no-install-suggests
|
||||
build-essential chrpath cpio diffstat file gawk git locales
|
||||
lz4 openssl socat texinfo unzip wget xz-utils zstd
|
||||
meson ninja-build pkg-config libnewt-dev libsystemd-dev
|
||||
- name: Install KAS
|
||||
run: python -m pip install -r xCAT-genesis-base/oe/requirements.txt
|
||||
- name: Enable BitBake user namespaces
|
||||
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
- name: Build and run the Genesis console
|
||||
run: python3 xCAT-test/native/genesis_console.py
|
||||
- name: Validate every architecture configuration
|
||||
shell: bash
|
||||
env:
|
||||
@@ -37,8 +43,10 @@ jobs:
|
||||
for architecture in x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64 s390x; do
|
||||
export KAS_BUILD_DIR="${RUNNER_TEMP}/kas-build-${architecture}"
|
||||
configuration="xCAT-genesis-base/oe/kas/${architecture}.yml"
|
||||
kas dump "${configuration}" >/dev/null
|
||||
kas dump --format json "${configuration}" >"${KAS_BUILD_DIR}.json"
|
||||
kas shell "${configuration}" -c 'bitbake -p'
|
||||
kas shell "${configuration}" -c \
|
||||
"GENESIS_ARCHITECTURE='${architecture}' GENESIS_KAS_CONFIG='${KAS_BUILD_DIR}.json' python3 '${GITHUB_WORKSPACE}/xCAT-test/native/genesis_metadata.py'"
|
||||
done
|
||||
- name: Validate the image task graph
|
||||
shell: bash
|
||||
|
||||
@@ -43,3 +43,17 @@ The two legacy Red Hat version cases use GNU awk. CI installs both interpreters.
|
||||
Package-manager commands are test doubles, so these cases check selection,
|
||||
arguments and error handling, not repository
|
||||
availability or package installation.
|
||||
|
||||
## Genesis startup
|
||||
|
||||
`genesis_ib_modules.bats` and `genesis_secondary_dhcp.bats` run the complete
|
||||
`doxcat` script in a private filesystem and network namespace. They replace
|
||||
external services and device commands, then check the boot interface and the
|
||||
IPv4 and IPv6 DHCP requests. They do not contact a DHCP server or a BMC.
|
||||
|
||||
The startup cases require Linux, bats-core 1.4 or newer, Bubblewrap, Bash, GNU
|
||||
core utilities, grep, sed, awk and permission to create user namespaces.
|
||||
Missing Linux prerequisites fail these cases. Non-Linux hosts skip them.
|
||||
The RPM prerequisite check requires `rpmspec` but does not use the sandbox.
|
||||
Run containers with an init process to
|
||||
reap children created by deliberate-fault tests.
|
||||
|
||||
@@ -1,118 +1,66 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load 'helpers/shell_source'
|
||||
load 'helpers/doxcat_sandbox'
|
||||
|
||||
setup()
|
||||
{
|
||||
GENESIS_SPEC="$(repo_path 'xCAT-genesis-base/xCAT-genesis-base.spec')"
|
||||
DRACUT_MODULE="$(repo_path 'xCAT-genesis-base/dracut_105/el/module-setup.sh')"
|
||||
DOXCAT="$(repo_path 'xCAT-genesis-scripts/usr/bin/doxcat')"
|
||||
[ -r "$GENESIS_SPEC" ] || skip "$GENESIS_SPEC is required"
|
||||
[ -r "$DRACUT_MODULE" ] || skip "$DRACUT_MODULE is required"
|
||||
[ -r "$DOXCAT" ] || skip "$DOXCAT is required"
|
||||
export GENESIS_SPEC DRACUT_MODULE DOXCAT
|
||||
GENESIS_SPEC=$(repo_path xCAT-genesis-base/xCAT-genesis-base.spec)
|
||||
DRACUT_MODULE=$(repo_path xCAT-genesis-base/dracut_105/el/module-setup.sh)
|
||||
[ -r "$GENESIS_SPEC" ]
|
||||
[ -r "$DRACUT_MODULE" ]
|
||||
}
|
||||
|
||||
run_installkernel()
|
||||
{
|
||||
local modules_root="$1"
|
||||
local instmods_log="$2"
|
||||
|
||||
kernel=5.14.0-test
|
||||
DRACUT_MODULES_ROOT="$modules_root"
|
||||
instmods()
|
||||
{
|
||||
printf '%s\n' "$1" >>"$instmods_log"
|
||||
}
|
||||
|
||||
DRACUT_MODULES_ROOT=$1
|
||||
local instmods_log=$2
|
||||
instmods() { printf '%s\n' "$1" >>"$instmods_log"; }
|
||||
source "$DRACUT_MODULE"
|
||||
installkernel
|
||||
}
|
||||
|
||||
run_doxcat_modprobe_preamble()
|
||||
{
|
||||
local preamble="$1"
|
||||
local modprobe_log="$2"
|
||||
|
||||
modprobe()
|
||||
{
|
||||
printf '%s\n' "$*" >>"$modprobe_log"
|
||||
}
|
||||
|
||||
eval "$preamble"
|
||||
}
|
||||
|
||||
run_doxcat_bootif_block()
|
||||
{
|
||||
local block="$1"
|
||||
|
||||
BOOTIF=01-aa-bb-cc-dd-ee-ff
|
||||
bootnic=
|
||||
log_label=test
|
||||
gripeiter=2
|
||||
|
||||
logger() { :; }
|
||||
sleep() { :; }
|
||||
ip()
|
||||
{
|
||||
printf '%s\n' "$*" >>"$IP_LOG"
|
||||
if [ "$*" = "link show" ]; then
|
||||
cat <<'EOF'
|
||||
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
|
||||
link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff
|
||||
3: ib0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65520 qdisc mq state UP mode DEFAULT group default qlen 256
|
||||
link/infiniband 00:bb:cc:dd:ee:ff brd 00:ff:ff:ff:ff:ff
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
eval "$block"
|
||||
printf '%s\n' "$bootnic"
|
||||
}
|
||||
|
||||
@test "genesis build requires kernel module packages" {
|
||||
grep -Fxq 'BuildRequires: kernel-core' "$GENESIS_SPEC"
|
||||
grep -Fxq 'BuildRequires: kernel-modules' "$GENESIS_SPEC"
|
||||
grep -Fxq 'BuildRequires: kernel-modules-extra' "$GENESIS_SPEC"
|
||||
command -v rpmspec >/dev/null
|
||||
run rpmspec -q --buildrequires --target x86_64 \
|
||||
--undefine openEuler --undefine rhel --undefine suse_version "$GENESIS_SPEC"
|
||||
[ "$status" -eq 0 ]
|
||||
local package
|
||||
for package in kernel-core kernel-modules kernel-modules-extra; do
|
||||
printf '%s\n' "$output" | grep -Fx "$package"
|
||||
done
|
||||
}
|
||||
|
||||
@test "dracut genesis module installs every module from modules.dep" {
|
||||
local modules_root="${BATS_TEST_TMPDIR}/modules"
|
||||
local modules_dep="${modules_root}/5.14.0-test/modules.dep"
|
||||
local instmods_log="${BATS_TEST_TMPDIR}/instmods.log"
|
||||
|
||||
mkdir -p "${modules_root}/5.14.0-test"
|
||||
cat >"$modules_dep" <<'EOF'
|
||||
kernel/drivers/infiniband/ulp/ipoib/ib_ipoib.ko.xz:
|
||||
kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko.xz:
|
||||
EOF
|
||||
local modules_root="$BATS_TEST_TMPDIR/modules"
|
||||
local instmods_log="$BATS_TEST_TMPDIR/instmods.log"
|
||||
mkdir -p "$modules_root/5.14.0-test"
|
||||
printf '%s\n' \
|
||||
'kernel/drivers/infiniband/ulp/ipoib/ib_ipoib.ko.xz:' \
|
||||
'kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko.xz:' \
|
||||
>"$modules_root/5.14.0-test/modules.dep"
|
||||
|
||||
run run_installkernel "$modules_root" "$instmods_log"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -Fxq 'ib_ipoib' "$instmods_log"
|
||||
grep -Fxq 'e1000e' "$instmods_log"
|
||||
grep -Fxq ib_ipoib "$instmods_log"
|
||||
grep -Fxq e1000e "$instmods_log"
|
||||
}
|
||||
|
||||
@test "doxcat loads IP over InfiniBand support during startup" {
|
||||
local preamble
|
||||
local modprobe_log="${BATS_TEST_TMPDIR}/modprobe.log"
|
||||
|
||||
preamble="$(extract_line_range "$DOXCAT" '^modprobe acpi_cpufreq' '^modprobe ib_ipoib$')" || return 1
|
||||
|
||||
run run_doxcat_modprobe_preamble "$preamble" "$modprobe_log"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -Fxq 'ib_ipoib' "$modprobe_log"
|
||||
@test "doxcat loads IPoIB and selects an InfiniBand boot interface" {
|
||||
setup_doxcat
|
||||
add_doxcat_link eth0 BROADCAST,MULTICAST,UP ether 02:bb:cc:dd:ee:ff
|
||||
add_doxcat_link ib0 BROADCAST,MULTICAST,UP infiniband "$ib_address"
|
||||
run_genesis 0 --setenv BOOTIF 01-aa-bb-cc-dd-ee-ff
|
||||
[ "$(cat "$fixture/bootnic")" = ib0 ]
|
||||
grep -Fxq ib_ipoib "$fixture/modules"
|
||||
assert_doxcat_dhcp
|
||||
}
|
||||
|
||||
@test "doxcat falls back to InfiniBand BOOTIF lookup after Ethernet lookup misses" {
|
||||
local block
|
||||
|
||||
IP_LOG="${BATS_TEST_TMPDIR}/ip.log"
|
||||
export IP_LOG
|
||||
block="$(extract_shell_if_block "$DOXCAT" 'if [ ! -z "$BOOTIF" ]; then')" || return 1
|
||||
|
||||
run run_doxcat_bootif_block "$block"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "ib0" ]
|
||||
[ "$(grep -c '^link show$' "$IP_LOG")" -eq 2 ]
|
||||
@test "doxcat prefers an exact Ethernet BOOTIF over an InfiniBand suffix" {
|
||||
setup_doxcat
|
||||
add_doxcat_link eth0 BROADCAST,MULTICAST,UP ether aa:bb:cc:dd:ee:ff
|
||||
add_doxcat_link ib0 BROADCAST,MULTICAST,UP infiniband "$ib_address"
|
||||
run_genesis 0 --setenv BOOTIF 01-aa-bb-cc-dd-ee-ff
|
||||
[ "$(cat "$fixture/bootnic")" = eth0 ]
|
||||
assert_doxcat_dhcp
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load 'helpers/doxcat_sandbox'
|
||||
|
||||
setup()
|
||||
{
|
||||
setup_doxcat
|
||||
add_doxcat_link boot0 BROADCAST,MULTICAST,UP ether aa:bb:cc:dd:ee:ff
|
||||
}
|
||||
|
||||
physical_nic()
|
||||
{
|
||||
local nic=$1 vendor=${2:-} product=${3:-}
|
||||
mkdir -p "$fixture/sys/devices/$nic/interface"
|
||||
ln -s "/sys/devices/$nic/interface" "$fixture/sys/class/net/$nic/device"
|
||||
[ -z "$vendor" ] || printf '%s\n' "$vendor" >"$fixture/sys/devices/$nic/idVendor"
|
||||
[ -z "$product" ] || printf '%s\n' "$product" >"$fixture/sys/devices/$nic/idProduct"
|
||||
}
|
||||
|
||||
enslave_nic()
|
||||
{
|
||||
mkdir -p "$fixture/sys/class/net/bond0"
|
||||
ln -s /sys/class/net/bond0 "$fixture/sys/class/net/$1/master"
|
||||
}
|
||||
|
||||
check_secondary()
|
||||
{
|
||||
: >"$fixture/dhcp"
|
||||
rm -f "$fixture/bootnic" "$fixture/selected"
|
||||
run_genesis 0 --setenv BOOTIF 01-aa-bb-cc-dd-ee-ff
|
||||
[ "$(cat "$fixture/bootnic")" = boot0 ]
|
||||
assert_doxcat_dhcp "$@"
|
||||
}
|
||||
|
||||
@test "DOWN virtual interface keeps the legacy DHCP path" {
|
||||
add_doxcat_link veth0 BROADCAST,MULTICAST
|
||||
check_secondary veth0
|
||||
}
|
||||
|
||||
@test "DOWN interface short-circuits every ownership guard" {
|
||||
add_doxcat_link eno1 BROADCAST,MULTICAST
|
||||
physical_nic eno1 046b ffb0
|
||||
enslave_nic eno1
|
||||
printf 'eno1\n' >"$fixture/tmp/tsmhostnic"
|
||||
printf '2: eno1 inet 192.0.2.21/24 scope global eno1\n' >"$fixture/addrs/eno1"
|
||||
check_secondary eno1
|
||||
}
|
||||
|
||||
@test "operational state does not replace the IFF_UP flag" {
|
||||
add_doxcat_link eno2 BROADCAST,MULTICAST
|
||||
check_secondary eno2
|
||||
}
|
||||
|
||||
@test "UP physical unaddressed interface gets both DHCP families" {
|
||||
add_doxcat_link eno3 BROADCAST,MULTICAST,UP,LOWER_UP
|
||||
physical_nic eno3
|
||||
check_secondary eno3
|
||||
}
|
||||
|
||||
@test "UP without carrier is still administratively UP" {
|
||||
add_doxcat_link eno4 BROADCAST,MULTICAST,UP
|
||||
sed -i '/ eno4: /s/state UP/state DOWN/' "$fixture/links/eno4" "$fixture/link-list"
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "IFF_UP is recognized as the first flag" {
|
||||
add_doxcat_link eno5 UP,BROADCAST
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "IFF_UP is recognized as the only flag" {
|
||||
add_doxcat_link eno6 UP
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "IFF_UP is recognized as the last flag" {
|
||||
add_doxcat_link eno7 BROADCAST,UP
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "IFF_UP is recognized before the carrier flag" {
|
||||
add_doxcat_link eno18 BROADCAST,MULTICAST,UP,LOWER_UP
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "physical IPoIB interface remains eligible" {
|
||||
add_doxcat_link ib0 BROADCAST,MULTICAST,UP,LOWER_UP infiniband "$ib_address"
|
||||
physical_nic ib0
|
||||
check_secondary ib0
|
||||
}
|
||||
|
||||
@test "link-local IPv4 and IPv6 addresses do not claim the interface" {
|
||||
add_doxcat_link eno8 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno8
|
||||
printf '%s\n' '2: eno8 inet 169.254.10.20/16 scope link eno8' \
|
||||
'2: eno8 inet6 fe80::20/64 scope link' '2: eno8 inet6 feb0::20/64 scope link' \
|
||||
>"$fixture/addrs/eno8"
|
||||
check_secondary eno8
|
||||
}
|
||||
|
||||
@test "global IPv4 address preserves existing ownership" {
|
||||
add_doxcat_link eno9 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno9
|
||||
printf '2: eno9 inet 198.51.100.9/24 scope global eno9\n' >"$fixture/addrs/eno9"
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "global IPv6 address preserves existing ownership" {
|
||||
add_doxcat_link eno10 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno10
|
||||
printf '2: eno10 inet6 2001:db8::10/64 scope global\n' >"$fixture/addrs/eno10"
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "global address wins when link-local addresses also exist" {
|
||||
add_doxcat_link eno11 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno11
|
||||
printf '%s\n' '2: eno11 inet6 fe80::11/64 scope link' \
|
||||
'2: eno11 inet 203.0.113.11/24 scope global eno11' >"$fixture/addrs/eno11"
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "TSM-owned interface is excluded by exact name" {
|
||||
add_doxcat_link eno12 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno12
|
||||
printf 'eno12\n' >"$fixture/tmp/tsmhostnic"
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "known management USB devices are excluded before setup markers" {
|
||||
add_doxcat_link enp18s0f0u1 BROADCAST,MULTICAST,UP
|
||||
add_doxcat_link enp18s0f0u2 BROADCAST,MULTICAST,UP
|
||||
physical_nic enp18s0f0u1 046b ffb0
|
||||
physical_nic enp18s0f0u2 04b3 4010
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "other USB device identities remain eligible" {
|
||||
local nic vendor product
|
||||
while read -r nic vendor product; do
|
||||
add_doxcat_link "$nic" BROADCAST,MULTICAST,UP
|
||||
physical_nic "$nic" "$vendor" "$product"
|
||||
done <<'NICS'
|
||||
enp18s0f0u3 046b ffb1
|
||||
enp18s0f0u4 04b4 4010
|
||||
enp18s0f0u5 046c ffb0
|
||||
enp18s0f0u6 04b3 4011
|
||||
NICS
|
||||
check_secondary enp18s0f0u3 enp18s0f0u4 enp18s0f0u5 enp18s0f0u6
|
||||
}
|
||||
|
||||
@test "TSM ownership does not use substring matching" {
|
||||
add_doxcat_link eno13 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno13
|
||||
printf 'eno130\n' >"$fixture/tmp/tsmhostnic"
|
||||
check_secondary eno13
|
||||
}
|
||||
|
||||
@test "UP virtual interface is excluded" {
|
||||
add_doxcat_link vnet0 BROADCAST,MULTICAST,UP
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "UP physical slave is excluded" {
|
||||
add_doxcat_link eno14 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno14
|
||||
enslave_nic eno14
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "link-state query failure is closed" {
|
||||
add_doxcat_link eno15 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno15
|
||||
touch "$fixture/links/eno15.fail"
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "address query failure is closed" {
|
||||
add_doxcat_link eno16 BROADCAST,MULTICAST,UP
|
||||
physical_nic eno16
|
||||
touch "$fixture/addrs/eno16.fail"
|
||||
check_secondary
|
||||
}
|
||||
|
||||
@test "caller excludes loopback, boot and USB interfaces on repeated startup" {
|
||||
local nic
|
||||
add_doxcat_link lo LOOPBACK,UP
|
||||
physical_nic lo
|
||||
physical_nic boot0
|
||||
for nic in usb0 eno17; do
|
||||
add_doxcat_link "$nic" BROADCAST,MULTICAST,UP
|
||||
physical_nic "$nic"
|
||||
done
|
||||
check_secondary eno17
|
||||
check_secondary eno17
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
load 'helpers/genesis_sandbox'
|
||||
|
||||
setup_doxcat()
|
||||
{
|
||||
setup_genesis_sandbox doxcat
|
||||
ib_address=80:00:00:48:fe:80:00:00:00:00:00:00:00:02:00:bb:cc:dd:ee:ff
|
||||
local utility fips library
|
||||
fips=$(repo_path xCAT-genesis-scripts/usr/lib/xcat/fips.sh)
|
||||
[ -r "$fips" ]
|
||||
mkdir -p "$fixture"/{etc/ssh,etc/pki/tls,real,links,addrs}
|
||||
for utility in bash sh cat cp awk grep sed sort tr tail rm wc cut head uname \
|
||||
timeout env ls mkdir touch readlink basename sleep; do
|
||||
cp -L "$(PATH=/usr/bin:/bin type -P "$utility")" "$fixture/bin/$utility"
|
||||
done
|
||||
if [ -x /usr/bin/coreutils ]; then
|
||||
cp -L /usr/bin/coreutils "$fixture/bin/coreutils"
|
||||
fi
|
||||
mv "$fixture/bin/sleep" "$fixture/real/sleep"
|
||||
printf '#!/bin/sh\nexit 0\n' >"$fixture/bin/quiet"
|
||||
for utility in sleep rpcbind rpc.statd ssh-keygen lldpad chronyd; do
|
||||
cp "$fixture/bin/quiet" "$fixture/bin/$utility"
|
||||
done
|
||||
cp "$fixture/bin/quiet" "$fixture/bin/sshd"
|
||||
cp "$fixture/bin/quiet" "$fixture/bin/getcert"
|
||||
cp "$fixture/bin/quiet" "$fixture/bin/getdestiny"
|
||||
printf '#!/bin/sh\nexit 1\n' >"$fixture/bin/ipmitool"
|
||||
printf '#!/bin/sh\nprintf "rsyslogd 8.0\\n"\n' >"$fixture/bin/rsyslogd"
|
||||
cat >"$fixture/bin/openssl" <<'SH'
|
||||
#!/bin/sh
|
||||
printf 'fixture-public-key\n'
|
||||
SH
|
||||
cat >"$fixture/bin/modprobe" <<'SH'
|
||||
#!/bin/sh
|
||||
printf '%s\n' "$*" >>/run/fixture/modules
|
||||
SH
|
||||
cat >"$fixture/bin/logger" <<'SH'
|
||||
#!/bin/sh
|
||||
printf '%s\n' "$*" >>/run/fixture/messages
|
||||
SH
|
||||
cat >"$fixture/bin/ip" <<'SH'
|
||||
#!/bin/sh
|
||||
case "$*" in
|
||||
'link'|'link show')
|
||||
cat /run/fixture/link-list
|
||||
if grep -Fxq ib_ipoib /run/fixture/modules; then
|
||||
cat /run/fixture/infiniband-links
|
||||
fi ;;
|
||||
'-o link show dev '*)
|
||||
[ ! -e "/run/fixture/links/$5.fail" ] || exit 17
|
||||
cat "/run/fixture/links/$5" ;;
|
||||
'-o addr show dev '*)
|
||||
[ ! -e "/run/fixture/addrs/$5.fail" ] || exit 19
|
||||
cat "/run/fixture/addrs/$5" ;;
|
||||
'-4 -o a show dev '*) printf '2: %s inet 192.0.2.20/24 scope global\n' "$6" ;;
|
||||
*) printf 'unexpected ip %s\n' "$*" >&2; exit 97 ;;
|
||||
esac
|
||||
SH
|
||||
cat >"$fixture/bin/ethtool" <<'SH'
|
||||
#!/bin/sh
|
||||
printf 'Link detected: yes\n'
|
||||
SH
|
||||
cat >"$fixture/bin/dhclient" <<'SH'
|
||||
#!/bin/sh
|
||||
printf '%s\n' "$*" >>/run/fixture/dhcp
|
||||
SH
|
||||
cat >"$fixture/bin/finish" <<'SH'
|
||||
#!/bin/sh
|
||||
printf '%s\n' "$DEVICE" > /run/fixture/bootnic
|
||||
printf '%s\n' "$NICSTOBRINGUP" > /run/fixture/selected
|
||||
set -- $NICSTOBRINGUP
|
||||
expected=$((2 + 2 * $#))
|
||||
tries=0
|
||||
while [ "$(wc -l </run/fixture/dhcp)" -lt "$expected" ] && [ "$tries" -lt 1000 ]; do
|
||||
/run/fixture/real/sleep 0.01
|
||||
tries=$((tries + 1))
|
||||
done
|
||||
SH
|
||||
printf '#!/bin/sh\nprintf "runcmd=exit 0\\n"\n' >"$fixture/bin/nextdestiny"
|
||||
chmod +x "$fixture/bin/"*
|
||||
: >"$fixture/etc/rsyslog.conf"
|
||||
: >"$fixture/link-list"
|
||||
: >"$fixture/infiniband-links"
|
||||
: >"$fixture/modules"
|
||||
: >"$fixture/dhcp"
|
||||
printf '0\n' >"$fixture/fips_enabled"
|
||||
sandbox+=(--ro-bind "$fixture/bin" /usr/bin
|
||||
--tmpfs /proc/sys
|
||||
--ro-bind "$fixture/fips_enabled" /proc/sys/crypto/fips_enabled
|
||||
--tmpfs /usr/lib)
|
||||
if [ ! -L /usr/sbin ]; then
|
||||
sandbox+=(--ro-bind "$fixture/bin" /usr/sbin)
|
||||
fi
|
||||
if [ ! -L /bin ]; then
|
||||
sandbox+=(--ro-bind "$fixture/bin" /bin)
|
||||
fi
|
||||
if [ ! -L /sbin ]; then
|
||||
sandbox+=(--ro-bind "$fixture/bin" /sbin)
|
||||
fi
|
||||
for library in /usr/lib/*; do
|
||||
[ "${library##*/}" = xcat ] && continue
|
||||
sandbox+=(--ro-bind "$library" "$library")
|
||||
done
|
||||
sandbox+=(--ro-bind "$fips" /usr/lib/xcat/fips.sh
|
||||
--setenv PATH /usr/bin:/usr/sbin
|
||||
--setenv destiny runcmd=/run/fixture/bin/finish)
|
||||
}
|
||||
|
||||
add_doxcat_link()
|
||||
{
|
||||
local nic=$1 flags=$2 kind=${3:-ether} address=${4:-00:11:22:33:44:55}
|
||||
mkdir -p "$fixture/sys/class/net/$nic"
|
||||
printf '2: %s: <%s> mtu 1500 state UP\n link/%s %s\n' \
|
||||
"$nic" "$flags" "$kind" "$address" >"$fixture/links/$nic"
|
||||
if [ "$kind" = infiniband ]; then
|
||||
cat "$fixture/links/$nic" >>"$fixture/infiniband-links"
|
||||
else
|
||||
cat "$fixture/links/$nic" >>"$fixture/link-list"
|
||||
fi
|
||||
: >"$fixture/addrs/$nic"
|
||||
}
|
||||
|
||||
assert_doxcat_dhcp()
|
||||
{
|
||||
local nic
|
||||
printf '%s\n' "$@" | sed '/^$/d' | sort >"$fixture/expected-selection"
|
||||
tr ' ' '\n' <"$fixture/selected" | sed '/^$/d' | sort >"$fixture/actual-selection"
|
||||
run diff -u "$fixture/expected-selection" "$fixture/actual-selection"
|
||||
[ "$status" -eq 0 ] || { printf '%s\n' "$output" >&2; return 1; }
|
||||
: >"$fixture/expected-dhcp"
|
||||
for nic in "$(cat "$fixture/bootnic")" "$@"; do
|
||||
printf '%s\n' "-cf /etc/dhclient.conf -pf /var/run/dhclient.$nic.pid $nic" \
|
||||
"-6 -pf /var/run/dhclient6.$nic.pid $nic -lf /var/lib/dhclient/dhclient6.leases" \
|
||||
>>"$fixture/expected-dhcp"
|
||||
done
|
||||
sort "$fixture/expected-dhcp" >"$fixture/expected-sorted"
|
||||
sort "$fixture/dhcp" >"$fixture/actual-sorted"
|
||||
run diff -u "$fixture/expected-sorted" "$fixture/actual-sorted"
|
||||
[ "$status" -eq 0 ] || { printf '%s\n' "$output" >&2; return 1; }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
load 'helpers/shell_source'
|
||||
|
||||
setup_genesis_sandbox()
|
||||
{
|
||||
[ "$(uname -s)" = Linux ] || skip 'Genesis filesystem isolation requires Linux'
|
||||
local bwrap utility source
|
||||
bwrap=$(PATH=/usr/bin:/bin type -P bwrap) || {
|
||||
echo 'Install bubblewrap to run the Genesis script tests' >&2
|
||||
return 1
|
||||
}
|
||||
for utility in bash cat cp awk grep sed sort tr tail rm wc cut head uname timeout; do
|
||||
PATH=/usr/bin:/bin type -P "$utility" >/dev/null || {
|
||||
echo "Required utility is unavailable: $utility" >&2
|
||||
return 1
|
||||
}
|
||||
done
|
||||
source=$(repo_path "xCAT-genesis-scripts/usr/bin/$1")
|
||||
[ -r "$source" ] || {
|
||||
echo "Required Genesis script is unreadable: $source" >&2
|
||||
return 1
|
||||
}
|
||||
fixture="${BATS_TEST_TMPDIR:?bats-core 1.4 or newer is required}/fixture"
|
||||
mkdir -p "$fixture"/{bin,etc/xcat,sys/class/net/lo,tmp,var/lib/dhclient}
|
||||
: >"$fixture/cmdline"
|
||||
sandbox=(env -i PATH=/usr/bin:/bin LC_ALL=C "$bwrap"
|
||||
--unshare-all --die-with-parent --new-session
|
||||
--ro-bind / / --tmpfs /run --proc /proc --dev /dev
|
||||
--bind "$fixture" /run/fixture --bind "$fixture/tmp" /tmp
|
||||
--bind "$fixture/etc" /etc --ro-bind "$fixture/sys" /sys
|
||||
--bind "$fixture/var" /var
|
||||
--ro-bind "$fixture/cmdline" /proc/cmdline
|
||||
--ro-bind "$source" /run/script --chdir /tmp
|
||||
--setenv PATH /run/fixture/bin:/usr/bin:/bin --setenv LC_ALL C)
|
||||
if [ -d /etc/alternatives ]; then
|
||||
sandbox+=(--ro-bind /etc/alternatives /etc/alternatives)
|
||||
fi
|
||||
run "${sandbox[@]}" /bin/bash -c 'command -v awk && test -r /run/script'
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo "Cannot isolate Genesis script: $output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_genesis()
|
||||
{
|
||||
local expected_status=$1
|
||||
shift
|
||||
run "${sandbox[@]}" "$@" timeout 20 /bin/bash /run/script </dev/null
|
||||
if [ "$status" -ne "$expected_status" ]; then
|
||||
printf 'Expected exit %s, got %s\n%s\n' "$expected_status" "$status" "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -103,3 +103,36 @@ EL6 or Ubuntu 14.04 environment. These cases do not qualify native SUSE package
|
||||
transactions, dependency resolution, a running xcatd daemon, or machine boot.
|
||||
The namespace fixture targets merged-/usr hosts. Container runs need namespace
|
||||
permissions and an init process that reaps orphaned children.
|
||||
|
||||
## Genesis OpenEmbedded
|
||||
|
||||
`genesis_metadata.py` runs inside a Genesis KAS shell. It uses BitBake to
|
||||
evaluate machine and package policy, install local-file recipes into temporary
|
||||
directories, generate extension manifests and reject invalid extension
|
||||
metadata. It does not extract or evaluate recipe source fragments.
|
||||
|
||||
For example, from the source checkout:
|
||||
|
||||
```sh
|
||||
export KAS_WORK_DIR=/path/to/kas-work
|
||||
export KAS_BUILD_DIR=/path/to/kas-build-x86_64
|
||||
source_tree=$PWD
|
||||
kas dump --format json xCAT-genesis-base/oe/kas/x86_64.yml >"${KAS_BUILD_DIR}.json"
|
||||
kas shell xCAT-genesis-base/oe/kas/x86_64.yml -c \
|
||||
"GENESIS_ARCHITECTURE=x86_64 GENESIS_KAS_CONFIG='${KAS_BUILD_DIR}.json' python3 '${source_tree}/xCAT-test/native/genesis_metadata.py'"
|
||||
```
|
||||
|
||||
Use the pinned KAS requirements and OpenEmbedded host prerequisites from
|
||||
`xCAT-genesis-base/oe`. Run as an ordinary user with an executable temporary
|
||||
directory. BitBake's build directory must use a supported local filesystem.
|
||||
The `genesis_openembedded` workflow runs the metadata tests for x86, x86_64,
|
||||
ppc64, ppc64le, armv7hf, aarch64, riscv64 and s390x.
|
||||
|
||||
`python3 xCAT-test/native/genesis_console.py` builds and installs the complete
|
||||
console with Meson, then runs the installed program. It requires a C compiler,
|
||||
Meson, Ninja, pkg-config, and development packages for Newt and systemd.
|
||||
|
||||
The unit test retains checks of the vendored release key, requested kernel
|
||||
fragments and console service configuration. The BATS tests retain runtime,
|
||||
console, hardware and packet behavior. None of these checks replaces a full
|
||||
image build, inspection of its resolved kernel configuration or a boot test.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the Genesis console with its installed Meson definition."""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SOURCE = ROOT / "xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/files/xcat-genesis-console"
|
||||
|
||||
|
||||
class ConsoleBuild(unittest.TestCase):
|
||||
def test_build_install_and_snapshot(self):
|
||||
with tempfile.TemporaryDirectory(prefix="genesis-console-") as temporary:
|
||||
root = Path(temporary)
|
||||
build = root / "build"
|
||||
stage = root / "stage"
|
||||
subprocess.run(["meson", "setup", str(build), str(SOURCE), "--prefix=/usr"], check=True)
|
||||
options = json.loads(subprocess.check_output(["meson", "introspect", "--buildoptions", str(build)]))
|
||||
options = {option["name"]: option["value"] for option in options}
|
||||
self.assertEqual(options["c_std"], "c17")
|
||||
self.assertIs(options["werror"], True)
|
||||
subprocess.run(["meson", "compile", "-C", str(build)], check=True)
|
||||
subprocess.run(["meson", "install", "-C", str(build), "--destdir", str(stage)], check=True)
|
||||
binary = stage / "usr/sbin/xcat-genesis-console"
|
||||
self.assertTrue(os.access(binary, os.X_OK))
|
||||
environment = {"PATH": os.environ["PATH"], "LC_ALL": "C"}
|
||||
for name in ("CMDLINE_FILE", "UPTIME_FILE", "OS_RELEASE", "STATUS_DIR", "STATE_FILE",
|
||||
"DESTINY_FILE", "RESPONSE_FILE", "SYS_ROOT", "PROC_ROOT", "EXTENSION_DIR", "PROVIDER_DIR"):
|
||||
environment["XCAT_" + name] = str(root / name.lower())
|
||||
Path(environment["XCAT_RESPONSE_FILE"]).write_text("XCAT_NODE_NAME=console-test-node\n")
|
||||
result = subprocess.run([str(binary), "--once"], env=environment, text=True,
|
||||
capture_output=True, timeout=10, check=True)
|
||||
self.assertIn("xCAT Genesis", result.stdout)
|
||||
self.assertIn("node: console-test-node", result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
@@ -0,0 +1,363 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run in a configured kas shell; exercise BitBake's evaluated recipes and tasks."""
|
||||
|
||||
import configparser
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
BITBAKE = shutil.which("bitbake")
|
||||
if not BITBAKE:
|
||||
raise SystemExit("Run this test inside the Genesis kas shell")
|
||||
sys.path.insert(0, str(Path(BITBAKE).resolve().parents[1] / "lib"))
|
||||
import bb.build
|
||||
import bb.fetch2
|
||||
import bb.process
|
||||
import bb.providers
|
||||
import bb.tinfoil
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
LAYER = ROOT / "xCAT-genesis-base/oe/meta-xcat-genesis"
|
||||
MACHINES = {
|
||||
"x86": ("i686", "ttyS0"),
|
||||
"x86_64": ("x86-64", "ttyS0"),
|
||||
"ppc64": ("powerpc64", "hvc0"),
|
||||
"ppc64le": ("ppc64p8le", "hvc0"),
|
||||
"armv7hf": ("armv7ahf", "ttyAMA0"),
|
||||
"aarch64": ("aarch64", "ttyAMA0"),
|
||||
"riscv64": ("riscv64", "ttyS0"),
|
||||
"s390x": ("s390x", "ttysclp0"),
|
||||
}
|
||||
|
||||
|
||||
class GenesisMetadata(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.tinfoil = bb.tinfoil.Tinfoil()
|
||||
cls.addClassCleanup(cls.tinfoil.shutdown)
|
||||
cls.tinfoil.prepare(quiet=2)
|
||||
sys.path.insert(0, str(Path(cls.tinfoil.config_data.getVar("COREBASE")) / "meta/lib"))
|
||||
from oe import utils
|
||||
cls.oe_utils = utils
|
||||
cls.recipes = {}
|
||||
cls.image = cls.recipe("xcat-genesis-image")
|
||||
cls.arch = cls.image.getVar("XCAT_GENESIS_ARCHITECTURE")
|
||||
if cls.arch not in MACHINES:
|
||||
raise AssertionError("Unknown Genesis architecture: " + str(cls.arch))
|
||||
if cls.arch != os.environ["GENESIS_ARCHITECTURE"]:
|
||||
raise AssertionError("Configured machine does not match the requested architecture")
|
||||
|
||||
@classmethod
|
||||
def recipe(cls, name):
|
||||
if name not in cls.recipes:
|
||||
data = cls.tinfoil.parse_recipe(name)
|
||||
if name.startswith("xcat-genesis") or name == "packagegroup-xcat-genesis-hardware":
|
||||
Path(data.getVar("FILE")).resolve().relative_to(LAYER)
|
||||
cls.recipes[name] = data
|
||||
return cls.recipes[name].createCopy()
|
||||
|
||||
def words(self, data, variable):
|
||||
return set((data.getVar(variable) or "").split())
|
||||
|
||||
def dependencies(self, recipe):
|
||||
data = self.recipe(recipe)
|
||||
return self.words(data, "RDEPENDS:" + data.getVar("PN"))
|
||||
|
||||
def task_data(self, recipe):
|
||||
data = self.recipe(recipe)
|
||||
temporary = tempfile.TemporaryDirectory(prefix="genesis-metadata-")
|
||||
self.addCleanup(temporary.cleanup)
|
||||
work = Path(temporary.name)
|
||||
for variable, directory in (("WORKDIR", work), ("UNPACKDIR", work / "sources"),
|
||||
("D", work / "image"), ("T", work / "temp")):
|
||||
directory.mkdir(exist_ok=True)
|
||||
data.setVar(variable, str(directory))
|
||||
# These local-file tasks need host tools, not an unbuilt target sysroot.
|
||||
data.setVar("PATH", os.environ["PATH"])
|
||||
return data
|
||||
|
||||
def install(self, recipe):
|
||||
data = self.task_data(recipe)
|
||||
urls = data.getVar("SRC_URI").split()
|
||||
self.assertTrue(urls)
|
||||
self.assertTrue(all(url.startswith("file://") for url in urls))
|
||||
fetcher = bb.fetch2.Fetch(urls, data)
|
||||
fetcher.download()
|
||||
fetcher.unpack(data.getVar("UNPACKDIR"))
|
||||
bb.build.exec_func("do_install", data, dirs=[data.getVar("WORKDIR")])
|
||||
return data, Path(data.getVar("D"))
|
||||
|
||||
def installed(self, data, root, variable, suffix, mode):
|
||||
file = root / data.getVar(variable).lstrip("/") / suffix
|
||||
self.assertTrue(file.is_file(), str(file))
|
||||
self.assertEqual(stat.S_IMODE(file.stat().st_mode), mode, str(file))
|
||||
return file
|
||||
|
||||
def unit(self, file):
|
||||
parser = configparser.ConfigParser(interpolation=None, strict=False)
|
||||
parser.optionxform = str
|
||||
parser.read(file)
|
||||
return parser
|
||||
|
||||
def test_release_configuration(self):
|
||||
config = json.loads(Path(os.environ["GENESIS_KAS_CONFIG"]).read_text())
|
||||
self.assertEqual(config["machine"], self.image.getVar("MACHINE"))
|
||||
self.assertEqual(config["distro"], "xcat-genesis")
|
||||
pins = {
|
||||
"bitbake": "acfe02fa38b5da9e6a36c6cedcf91d4fcbefbfbd",
|
||||
"openembedded-core": "5d1aa5c806c061a2994f4decb59016610f093213",
|
||||
"meta-openembedded": "af8b6d6b2f0b11595b0a0d5b82efa3129d52a628",
|
||||
}
|
||||
for name, revision in pins.items():
|
||||
self.assertEqual(config["repos"][name]["commit"], revision)
|
||||
for name in ("bitbake", "openembedded-core"):
|
||||
repo = config["repos"][name]
|
||||
self.assertEqual(repo["tag"], "yocto-6.0.2")
|
||||
self.assertIs(repo["signed"], True)
|
||||
self.assertEqual(repo["allowed_signers"], ["yocto-release"])
|
||||
self.assertEqual(config["signers"]["yocto-release"], {
|
||||
"fingerprint": "2AFB13F28FBBB0D1B9DAF63087EB3D32FB631AD9",
|
||||
"repo": "xcat-core", "path": "xCAT-genesis-base/oe/keys/yocto-release.asc",
|
||||
})
|
||||
self.assertNotIn("gpg_keyserver", config)
|
||||
self.assertIn("xCAT-genesis-base/oe/meta-xcat-genesis", config["repos"]["xcat-core"]["layers"])
|
||||
self.assertLessEqual({"archiver", "create-spdx", "vex"}, self.words(self.image, "INHERIT"))
|
||||
for flag, value in (("src", "original"), ("diff", "1"), ("recipe", "1")):
|
||||
self.assertEqual(self.image.getVarFlag("ARCHIVER_MODE", flag), value)
|
||||
|
||||
def test_machine_and_distro(self):
|
||||
data = self.image
|
||||
tune, terminal = MACHINES[self.arch]
|
||||
for variable, value in {
|
||||
"DEFAULTTUNE": tune, "XCAT_GENESIS_CONSOLE_TTY": terminal,
|
||||
"DISTRO_CODENAME": "cheetah", "LINUX_VERSION_EXTENSION": "-xCAT-genesis",
|
||||
"TCLIBC": "glibc", "INIT_MANAGER": "systemd", "NO_RECOMMENDATIONS": "1",
|
||||
"QB_DEFAULT_FSTYPE": "ext4",
|
||||
}.items():
|
||||
self.assertEqual(data.getVar(variable), value, variable)
|
||||
self.assertEqual(self.words(data, "IMAGE_FSTYPES"), {"cpio.gz", "ext4"})
|
||||
self.assertEqual(self.words(data, "DISTRO_FEATURES"),
|
||||
{"acl", "ipv4", "ipv6", "largefile", "pci", "seccomp", "systemd", "usrmerge"})
|
||||
self.assertEqual("pci" in self.words(data, "MACHINE_FEATURES"), self.arch != "s390x")
|
||||
if self.arch == "x86_64":
|
||||
self.assertEqual(self.words(data, "MACHINE_FEATURES"), {"pci", "qemu-usermode", "rtc"})
|
||||
self.assertNotIn("xcat.debug-shell", self.words(data, "QB_KERNEL_CMDLINE_APPEND"))
|
||||
if self.arch == "ppc64le":
|
||||
self.assertEqual(data.getVar("KERNEL_IMAGETYPE"), "vmlinux")
|
||||
if self.arch == "x86":
|
||||
self.assertEqual(data.getVar("QB_SYSTEM_NAME"), "qemu-system-i386")
|
||||
if self.arch == "armv7hf":
|
||||
self.assertEqual(data.getVar("ARM_INSTRUCTION_SET"), "arm")
|
||||
self.assertNotIn("neon", self.words(data, "TUNE_FEATURES"))
|
||||
if self.arch == "riscv64":
|
||||
self.assertIn("opensbi", self.words(data, "EXTRA_IMAGEDEPENDS"))
|
||||
self.assertEqual(data.getVar("QB_DEFAULT_BIOS"), "fw_jump.elf")
|
||||
qemu = {
|
||||
"ppc64": ("-machine pseries", "-cpu POWER8"),
|
||||
"ppc64le": ("-machine pseries", "-cpu POWER8"),
|
||||
"armv7hf": ("-machine virt,highmem=off", "-cpu cortex-a15"),
|
||||
"aarch64": ("-machine virt", "-cpu cortex-a57"),
|
||||
"riscv64": ("-machine virt", "-cpu rva23s64,pmp=true"),
|
||||
}
|
||||
if self.arch in qemu:
|
||||
self.assertEqual((data.getVar("QB_MACHINE"), data.getVar("QB_CPU")), qemu[self.arch])
|
||||
|
||||
def test_image_and_hardware_packages(self):
|
||||
image = self.words(self.image, "IMAGE_INSTALL")
|
||||
self.assertLessEqual({"xcat-genesis-network", "xcat-genesis-protocol", "xcat-genesis-extensions",
|
||||
"xcat-genesis-console", "packagegroup-xcat-genesis-hardware"}, image)
|
||||
self.assertFalse(image & {"dhclient", "dhcpcd", "busybox-udhcpc", "systemd-networkd"})
|
||||
hardware = self.dependencies("packagegroup-xcat-genesis-hardware")
|
||||
self.assertLessEqual(set("pciutils usbutils hwloc ethtool rdma-core iproute2-rdma lldpd ipmitool curl jq "
|
||||
"nvme-cli smartmontools sg3-utils lsscsi mdadm hdparm parted lvm2 memtester "
|
||||
"stress-ng edac-utils kernel-modules mstflint xcat-genesis-hardware-control".split()), hardware)
|
||||
scoped = {"x86_64": {"dmidecode", "lshw", "mcelog"}, "x86": {"dmidecode", "lshw"},
|
||||
"armv7hf": {"dmidecode", "lshw"}, "aarch64": {"dmidecode", "lshw"},
|
||||
"ppc64": {"dmidecode", "xcat-genesis-hardware-control-iprutils"},
|
||||
"ppc64le": {"dmidecode", "xcat-genesis-hardware-control-iprutils"},
|
||||
"riscv64": {"lshw"}, "s390x": set()}
|
||||
self.assertEqual(hardware & {"dmidecode", "lshw", "mcelog", "xcat-genesis-hardware-control-iprutils"},
|
||||
scoped[self.arch])
|
||||
self.assertFalse((image | hardware) & {"storcli", "perccli", "ssacli", "arcconf", "nvidia-smi", "rocm-smi"})
|
||||
|
||||
def test_runtime_dependencies(self):
|
||||
for recipe, expected in {
|
||||
"xcat-genesis-network": "networkmanager-daemon networkmanager-nmcli",
|
||||
"xcat-genesis-protocol": "bash openssl-bin",
|
||||
"xcat-genesis-extensions": "bash coreutils jq openssl-bin systemd xcat-genesis-init",
|
||||
"xcat-genesis-hardware-control": "bash coreutils jq mstflint nvme-cli util-linux-flock",
|
||||
"xcat-genesis-console": "libnewt libsystemd ncurses-terminfo-base xcat-genesis-init",
|
||||
"mstflint": "python3-modules",
|
||||
}.items():
|
||||
self.assertEqual(self.dependencies(recipe), set(expected.split()), recipe)
|
||||
self.assertLessEqual({"coreutils", "util-linux-logger"}, self.dependencies("xcat-genesis-init"))
|
||||
console = self.recipe("xcat-genesis-console")
|
||||
self.assertLessEqual({"libnewt", "systemd"}, self.words(console, "DEPENDS"))
|
||||
self.assertIn("sysext", self.words(self.recipe("systemd"), "PACKAGECONFIG"))
|
||||
self.assertIn("rdma", self.words(self.recipe("iproute2"), "PACKAGECONFIG"))
|
||||
network = self.recipe("networkmanager")
|
||||
self.assertEqual(self.words(network, "PACKAGECONFIG"),
|
||||
set("crypto-null libedit man-resolv-conf nmcli systemd".split()))
|
||||
self.assertEqual(network.getVar("NETWORKMANAGER_DHCP_DEFAULT"), "internal")
|
||||
self.assertEqual(network.getVar("NETWORKMANAGER_DNS_RC_MANAGER_DEFAULT"), "symlink")
|
||||
self.assertIn("-Dtests=no", self.words(network, "EXTRA_OEMESON"))
|
||||
|
||||
def test_hardware_recipe_policy(self):
|
||||
data = self.recipe("mstflint")
|
||||
self.assertEqual(data.getVar("LICENSE"), "Linux-OpenIB & MIT & BSD-2-Clause")
|
||||
self.assertEqual(data.getVar("SRCREV"), "b9d9e844a14ae1c85cb90c76ccd4a56a0eccd599")
|
||||
self.assertEqual(self.words(data, "PACKAGECONFIG"), set("adb cables dc inband openssl".split()))
|
||||
self.assertFalse(self.words(data, "EXTRA_OECONF") & {"--enable-fw-mgr", "--enable-nvml"})
|
||||
if self.arch in {"ppc64", "ppc64le"}:
|
||||
data = self.recipe("iprutils")
|
||||
self.assertEqual(data.getVar("COMPATIBLE_HOST"), "powerpc64.*-linux")
|
||||
self.assertEqual(data.getVar("LICENSE"), "CPL-1.0")
|
||||
self.assertEqual(data.getVar("SRCREV"), "9961e538736a6e81f1072a926c3ad91b8513c8c1")
|
||||
self.assertLessEqual({"--without-systemd", "--without-initscripts"}, self.words(data, "EXTRA_OECONF"))
|
||||
control = self.recipe("xcat-genesis-hardware-control")
|
||||
self.assertIn("xcat-genesis-hardware-control-iprutils", self.words(control, "PACKAGES"))
|
||||
self.assertEqual(self.words(control, "RDEPENDS:xcat-genesis-hardware-control-iprutils"),
|
||||
{"xcat-genesis-hardware-control", "bash", "iprutils"})
|
||||
else:
|
||||
with self.assertRaisesRegex(bb.providers.NoProvider, "incompatible with host"):
|
||||
self.tinfoil.get_recipe_file("iprutils")
|
||||
|
||||
def test_kernel_fragment_selection(self):
|
||||
data = self.recipe("linux-yocto")
|
||||
fragments = {
|
||||
"x86": {"x86-common", "x86"}, "x86_64": {"x86-common", "x86-64"},
|
||||
"ppc64": {"powerpc64", "ppc64"}, "ppc64le": {"powerpc64"},
|
||||
}.get(self.arch, {self.arch})
|
||||
urls = {url.split(";")[0] for url in data.getVar("SRC_URI").split()}
|
||||
self.assertLessEqual({"file://xcat-genesis-" + name + ".cfg" for name in fragments}, urls)
|
||||
if self.arch not in {"x86", "x86_64"}:
|
||||
self.assertEqual(data.getVar("SRCREV_machine"), "b1ba5428513b52c2bd6acfd3ad0a910f699bc395")
|
||||
self.assertEqual(data.getVar("KERNEL_MODULE_PACKAGE_SUFFIX"), "-" + data.getVar("KERNEL_VERSION_PKG_NAME"))
|
||||
|
||||
def test_installed_protocol(self):
|
||||
data, root = self.install("xcat-genesis-protocol")
|
||||
for name in ("getdestiny", "nextdestiny"):
|
||||
file = self.installed(data, root, "bindir", name, 0o755)
|
||||
self.assertEqual(file.read_bytes(), (ROOT / "xCAT-genesis-scripts/usr/bin" / name).read_bytes())
|
||||
self.assertFalse(list(root.rglob("doxcat")))
|
||||
|
||||
def test_installed_network(self):
|
||||
data, root = self.install("xcat-genesis-network")
|
||||
file = self.installed(data, root, "sysconfdir", "NetworkManager/conf.d/10-xcat-genesis.conf", 0o644)
|
||||
config = self.unit(file)
|
||||
self.assertEqual(config["connection"]["ipv4.dhcp-timeout"], "20")
|
||||
self.assertEqual(config["connection"]["ipv6.dhcp-timeout"], "20")
|
||||
self.assertEqual(config["connection"]["ipv6.dhcp-duid"], "ll")
|
||||
|
||||
def test_installed_hardware_providers(self):
|
||||
data, root = self.install("xcat-genesis-hardware-control")
|
||||
providers = {"mstflint", "nvme"}
|
||||
if self.arch in {"ppc64", "ppc64le"}:
|
||||
providers.add("iprutils")
|
||||
directory = root / data.getVar("datadir").lstrip("/") / "xcat/genesis/providers"
|
||||
self.assertEqual({file.stem for file in directory.glob("*.json")}, providers)
|
||||
for name in providers:
|
||||
file = self.installed(data, root, "datadir", "xcat/genesis/providers/" + name + ".json", 0o644)
|
||||
manifest = json.loads(file.read_text())
|
||||
self.assertEqual(manifest["name"], name)
|
||||
self.assertTrue(manifest["capabilities"])
|
||||
for capability in manifest["capabilities"]:
|
||||
self.assertIs(capability["destructive"], False)
|
||||
self.installed(data, root, "libexecdir", "xcat/genesis/providers/" + name, 0o755)
|
||||
|
||||
def test_installed_init(self):
|
||||
data, root = self.install("xcat-genesis-init")
|
||||
self.installed(data, root, "libexecdir", "xcat/genesis-functions", 0o644)
|
||||
for name in ("genesis-status", "genesis-maintenance-shell"):
|
||||
self.installed(data, root, "libexecdir", "xcat/" + name, 0o755)
|
||||
self.assertFalse(list(root.rglob("*debug-shell*")))
|
||||
network = self.unit(self.installed(data, root, "systemd_system_unitdir", "xcat-genesis-network-state.service", 0o644))
|
||||
self.assertEqual(network["Unit"]["After"], "NetworkManager.service")
|
||||
self.assertEqual(network["Unit"]["Before"], "xcat-genesis-network-ready.target")
|
||||
register = self.unit(self.installed(data, root, "systemd_system_unitdir", "xcat-genesis-register.service", 0o644))
|
||||
for option in ("After", "Requires"):
|
||||
self.assertEqual(set(register["Unit"][option].split()),
|
||||
{"xcat-genesis-network-ready.target", "xcat-genesis-extensions.service"})
|
||||
preset = root / data.getVar("systemd_unitdir").lstrip("/") / "system-preset/00-xcat-genesis.preset"
|
||||
self.assertEqual(preset.read_text(), "disable getty@.service\n")
|
||||
|
||||
def test_installed_extension_service(self):
|
||||
data, root = self.install("xcat-genesis-extensions")
|
||||
service = self.unit(self.installed(data, root, "systemd_system_unitdir", "xcat-genesis-extensions.service", 0o644))
|
||||
self.assertEqual(service["Unit"]["ConditionDirectoryNotEmpty"], "/var/lib/xcat/genesis/extensions")
|
||||
self.assertEqual(service["Unit"]["Before"], "xcat-genesis-register.service")
|
||||
|
||||
def test_extension_manifest(self):
|
||||
data = self.task_data("xcat-genesis-extension-smoke")
|
||||
self.assertEqual(self.words(data, "IMAGE_FSTYPES"), {"squashfs-zst"})
|
||||
self.assertEqual(data.getVar("XCAT_GENESIS_EXTENSION_ARCHITECTURE"), self.arch)
|
||||
deploy = Path(data.getVar("WORKDIR")) / "deploy"
|
||||
deploy.mkdir()
|
||||
data.setVar("IMGDEPLOYDIR", str(deploy))
|
||||
data.setVar("IMAGE_LINK_NAME", "test-extension")
|
||||
payload = b"extension image fixture\n"
|
||||
image = deploy / "test-extension.squashfs-zst"
|
||||
image.write_bytes(payload)
|
||||
expected = {
|
||||
"architecture": self.arch, "capabilities": ["diagnostic.smoke"],
|
||||
"genesis_release": data.getVar("DISTRO_VERSION"), "kernel_modules": False,
|
||||
"kernel_release": None, "key_id": "xcat-release", "license_class": "open",
|
||||
"name": "xcat-smoke", "pci_ids": [], "schema": 1,
|
||||
"sha256": hashlib.sha256(payload).hexdigest(), "version": "1.0",
|
||||
}
|
||||
for unused in range(2):
|
||||
self.oe_utils.execute_pre_post_process(data, data.getVar("IMAGE_POSTPROCESS_COMMAND"))
|
||||
self.assertEqual(json.loads((deploy / "test-extension.manifest.json").read_text()), expected)
|
||||
data.setVar("XCAT_GENESIS_EXTENSION_KERNEL_MODULES", "true")
|
||||
data.setVar("XCAT_GENESIS_EXTENSION_KERNEL_RELEASE", "6.18-test")
|
||||
expected.update(kernel_modules=True, kernel_release="6.18-test")
|
||||
self.oe_utils.execute_pre_post_process(data, data.getVar("IMAGE_POSTPROCESS_COMMAND"))
|
||||
self.assertEqual(json.loads((deploy / "test-extension.manifest.json").read_text()), expected)
|
||||
image.unlink()
|
||||
with self.assertRaises(bb.process.ExecutionError) as failure:
|
||||
self.oe_utils.execute_pre_post_process(data, data.getVar("IMAGE_POSTPROCESS_COMMAND"))
|
||||
self.assertEqual(failure.exception.exitcode, 1)
|
||||
|
||||
def test_extension_parse_policy(self):
|
||||
recipe = self.tinfoil.get_recipe_file("xcat-genesis-extension-smoke")
|
||||
cases = [
|
||||
({"ARCHITECTURE": "armv7"}, "Invalid Genesis extension architecture"),
|
||||
({"NAME": "../escape"}, "Invalid Genesis extension name"),
|
||||
({"VERSION": "bad version"}, "Invalid Genesis extension version"),
|
||||
({"KEY_ID": "bad/key"}, "Invalid Genesis extension key ID"),
|
||||
({"LICENSE_CLASS": "unknown"}, "Invalid Genesis extension license class"),
|
||||
({"LICENSE_CLASS": "restricted"}, "Restricted Genesis extensions must set LICENSE_FLAGS"),
|
||||
({"KERNEL_MODULES": "yes"}, "must be true or false"),
|
||||
({"KERNEL_MODULES": "true"}, "Kernel extensions must declare"),
|
||||
({"CAPABILITIES": "invalid"}, "Invalid JSON"),
|
||||
({"CAPABILITIES": "{}"}, "must be a JSON string array"),
|
||||
({"PCI_IDS": '["not-a-pci-id"]'}, "Invalid value"),
|
||||
]
|
||||
for values, message in cases:
|
||||
with self.subTest(values=values):
|
||||
config = self.tinfoil.config_data.createCopy()
|
||||
for name, value in values.items():
|
||||
config.setVar("XCAT_GENESIS_EXTENSION_" + name + ":forcevariable", value)
|
||||
with self.assertLogs("BitBake", level="ERROR") as errors:
|
||||
with self.assertRaises(bb.BBHandledException):
|
||||
self.tinfoil.parse_recipe_file(recipe, config_data=config)
|
||||
self.assertTrue(any(message in error for error in errors.output), errors.output)
|
||||
for values in ({"ARCHITECTURE": "armv7hf"},
|
||||
{"LICENSE_CLASS": "restricted"},
|
||||
{"KERNEL_MODULES": "true", "KERNEL_RELEASE": "6.18-test"}):
|
||||
config = self.tinfoil.config_data.createCopy()
|
||||
config.setVar("LICENSE_FLAGS:forcevariable", "commercial_test")
|
||||
config.setVar("LICENSE_FLAGS_ACCEPTED", "commercial_test")
|
||||
for name, value in values.items():
|
||||
config.setVar("XCAT_GENESIS_EXTENSION_" + name + ":forcevariable", value)
|
||||
self.assertIsNotNone(self.tinfoil.parse_recipe_file(recipe, config_data=config))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
@@ -7,431 +7,22 @@ use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use Test::More;
|
||||
|
||||
use XCAT::Test::File qw(repo_path slurp_repo_file);
|
||||
|
||||
# The Yocto configuration, recipes, kernel fragments and units of the OpenEmbedded Genesis
|
||||
# image are the artifact: kas and bitbake read these lines. The scripts and programs the image
|
||||
# runs are tested in xCAT-test/bats/genesis_openembedded_*.bats.
|
||||
|
||||
my $kas = slurp_repo_file('xCAT-genesis-base/oe/kas/common.yml');
|
||||
like( $kas, qr/tag: yocto-6\.0\.2/, 'build uses Yocto 6.0.2' );
|
||||
like( $kas, qr/fingerprint: 2AFB13F28FBBB0D1B9DAF63087EB3D32FB631AD9/,
|
||||
'build trusts the Yocto release key' );
|
||||
like( $kas,
|
||||
qr{yocto-release:.*?repo: xcat-core.*?path: xCAT-genesis-base/oe/keys/yocto-release\.asc}s,
|
||||
'build loads the Yocto release key from the source tree' );
|
||||
like( $kas,
|
||||
qr{xcat-core:.*?layers:.*?xCAT-genesis-base/oe/meta-xcat-genesis:}s,
|
||||
'build loads the Genesis layer from the source tree' );
|
||||
unlike( $kas, qr/gpg_keyserver:/,
|
||||
'build does not depend on a public keyserver' );
|
||||
like( $kas, qr/INHERIT \+= "archiver create-spdx vex"/,
|
||||
'build generates source archives, an SBOM, and VEX metadata' );
|
||||
like( $kas, qr/ARCHIVER_MODE\[src\] = "original"/,
|
||||
'release archives retain original sources' );
|
||||
like( $kas, qr/ARCHIVER_MODE\[diff\] = "1"/,
|
||||
'release archives retain applied changes' );
|
||||
like( $kas, qr/ARCHIVER_MODE\[recipe\] = "1"/,
|
||||
'release archives retain recipe metadata' );
|
||||
use XCAT::Test::File qw(slurp_repo_file);
|
||||
|
||||
# Fragments specify requested kernel options; they do not prove the built kernel configuration.
|
||||
my $yocto_release_key =
|
||||
slurp_repo_file('xCAT-genesis-base/oe/keys/yocto-release.asc');
|
||||
is( sha256_hex($yocto_release_key),
|
||||
'42d49e59f2aa01a1c1417a52d3c915a24a64060c852f33e3bd04fbb738457e70',
|
||||
'vendored Yocto release key matches the reviewed key material' );
|
||||
like( $kas,
|
||||
qr{bitbake:.*?commit: acfe02fa38b5da9e6a36c6cedcf91d4fcbefbfbd.*?signed: true}s,
|
||||
'BitBake revision requires a signed tag' );
|
||||
like( $kas,
|
||||
qr{openembedded-core:.*?commit: 5d1aa5c806c061a2994f4decb59016610f093213.*?signed: true}s,
|
||||
'OpenEmbedded-Core revision requires a signed tag' );
|
||||
like( $kas,
|
||||
qr{meta-openembedded:.*?commit: af8b6d6b2f0b11595b0a0d5b82efa3129d52a628}s,
|
||||
'meta-openembedded revision is pinned' );
|
||||
|
||||
my $x86_kas = slurp_repo_file('xCAT-genesis-base/oe/kas/x86_64.yml');
|
||||
like( $x86_kas, qr{includes:\s*\n\s*- xCAT-genesis-base/oe/kas/common\.yml},
|
||||
'x86_64 build includes the common configuration' );
|
||||
like( $x86_kas, qr/^machine: xcat-genesis-x86-64$/m,
|
||||
'x86_64 build selects its machine' );
|
||||
|
||||
my $x86_32_kas = slurp_repo_file('xCAT-genesis-base/oe/kas/x86.yml');
|
||||
like( $x86_32_kas, qr{includes:\s*\n\s*- xCAT-genesis-base/oe/kas/common\.yml},
|
||||
'x86 build includes the common configuration' );
|
||||
like( $x86_32_kas, qr/^machine: xcat-genesis-x86$/m,
|
||||
'x86 build selects its machine' );
|
||||
|
||||
my $armv7hf_kas = slurp_repo_file('xCAT-genesis-base/oe/kas/armv7hf.yml');
|
||||
like( $armv7hf_kas, qr{includes:\s*\n\s*- xCAT-genesis-base/oe/kas/common\.yml},
|
||||
'armv7hf build includes the common configuration' );
|
||||
like( $armv7hf_kas, qr/^machine: xcat-genesis-armv7hf$/m,
|
||||
'armv7hf build selects its machine' );
|
||||
|
||||
my $aarch64_kas = slurp_repo_file('xCAT-genesis-base/oe/kas/aarch64.yml');
|
||||
like( $aarch64_kas, qr{includes:\s*\n\s*- xCAT-genesis-base/oe/kas/common\.yml},
|
||||
'aarch64 build includes the common configuration' );
|
||||
like( $aarch64_kas, qr/^machine: xcat-genesis-aarch64$/m,
|
||||
'aarch64 build selects its machine' );
|
||||
|
||||
my $riscv64_kas = slurp_repo_file('xCAT-genesis-base/oe/kas/riscv64.yml');
|
||||
like( $riscv64_kas, qr{includes:\s*\n\s*- xCAT-genesis-base/oe/kas/common\.yml},
|
||||
'riscv64 build includes the common configuration' );
|
||||
like( $riscv64_kas, qr/^machine: xcat-genesis-riscv64$/m,
|
||||
'riscv64 build selects its machine' );
|
||||
|
||||
my $s390x_kas = slurp_repo_file('xCAT-genesis-base/oe/kas/s390x.yml');
|
||||
like( $s390x_kas, qr{includes:\s*\n\s*- xCAT-genesis-base/oe/kas/common\.yml},
|
||||
's390x build includes the common configuration' );
|
||||
like( $s390x_kas, qr/^machine: xcat-genesis-s390x$/m,
|
||||
's390x build selects its machine' );
|
||||
|
||||
my $ppc64le_kas = slurp_repo_file('xCAT-genesis-base/oe/kas/ppc64le.yml');
|
||||
like( $ppc64le_kas, qr{includes:\s*\n\s*- xCAT-genesis-base/oe/kas/common\.yml},
|
||||
'ppc64le build includes the common configuration' );
|
||||
like( $ppc64le_kas, qr/^machine: xcat-genesis-ppc64le$/m,
|
||||
'ppc64le build selects its machine' );
|
||||
|
||||
my $ppc64_kas = slurp_repo_file('xCAT-genesis-base/oe/kas/ppc64.yml');
|
||||
like( $ppc64_kas, qr{includes:\s*\n\s*- xCAT-genesis-base/oe/kas/common\.yml},
|
||||
'ppc64 build includes the common configuration' );
|
||||
like( $ppc64_kas, qr/^machine: xcat-genesis-ppc64$/m,
|
||||
'ppc64 build selects its machine' );
|
||||
|
||||
|
||||
my $distro = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/conf/distro/xcat-genesis.conf'
|
||||
);
|
||||
like( $distro, qr/^DISTRO_CODENAME = "cheetah"$/m,
|
||||
'distro uses the Cheetah codename' );
|
||||
like( $distro, qr/^LINUX_VERSION_EXTENSION = "-xCAT-genesis"$/m,
|
||||
'kernel release identifies xCAT Genesis' );
|
||||
like( $distro,
|
||||
qr/^KERNEL_MODULE_PACKAGE_SUFFIX = "-\$\{KERNEL_VERSION_PKG_NAME\}"$/m,
|
||||
'kernel module package names use the normalized kernel release' );
|
||||
like( $distro, qr/^TCLIBC = "glibc"$/m, 'distro uses glibc' );
|
||||
like( $distro, qr/^INIT_MANAGER = "systemd"$/m,
|
||||
'distro uses systemd' );
|
||||
like( $distro, qr/^DISTRO_FEATURES_DEFAULTS = ""$/m,
|
||||
'distro disables inherited feature defaults' );
|
||||
like( $distro,
|
||||
qr/^DISTRO_FEATURES = "acl ipv4 ipv6 largefile pci seccomp"$/m,
|
||||
'distro keeps an explicit feature list' );
|
||||
like( $distro, qr/^NO_RECOMMENDATIONS = "1"$/m,
|
||||
'image excludes package recommendations' );
|
||||
like( $distro, qr/PACKAGECONFIG:pn-systemd = ".*\bsysext\b.*"/,
|
||||
'systemd includes system extensions' );
|
||||
like( $distro,
|
||||
qr/^PACKAGECONFIG:pn-networkmanager = "crypto-null libedit man-resolv-conf nmcli systemd"$/m,
|
||||
'NetworkManager keeps the minimal feature set' );
|
||||
like( $distro, qr/^PACKAGECONFIG:append:pn-iproute2 = " rdma"$/m,
|
||||
'iproute2 includes the RDMA command' );
|
||||
like( $distro, qr/^NETWORKMANAGER_DHCP_DEFAULT:pn-networkmanager = "internal"$/m,
|
||||
'NetworkManager uses its internal DHCP client' );
|
||||
like( $distro,
|
||||
qr/^NETWORKMANAGER_DNS_RC_MANAGER_DEFAULT:pn-networkmanager = "symlink"$/m,
|
||||
'NetworkManager manages the resolver symlink' );
|
||||
unlike( $distro, qr/\b(?:dhclient|dhcpcd)\b/,
|
||||
'distro excludes external DHCP clients' );
|
||||
|
||||
my $machine = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/conf/machine/xcat-genesis-x86-64.conf'
|
||||
);
|
||||
like( $machine, qr/^DEFAULTTUNE = "x86-64"$/m,
|
||||
'x86_64 uses the baseline tune' );
|
||||
unlike( $machine, qr/x86-64-v[234]/,
|
||||
'x86_64 does not require a newer ISA level' );
|
||||
like( $machine, qr/^MACHINE_FEATURES_DEFAULTS = ""$/m,
|
||||
'machine disables inherited feature defaults' );
|
||||
like( $machine, qr/^MACHINE_FEATURES = "pci"$/m,
|
||||
'machine keeps an explicit feature list' );
|
||||
like( $machine, qr/^IMAGE_FSTYPES = "cpio\.gz ext4"$/m,
|
||||
'machine builds netboot and VM filesystems' );
|
||||
like( $machine, qr/^QB_DEFAULT_FSTYPE = "ext4"$/m,
|
||||
'QEMU uses a generated filesystem type' );
|
||||
like( $machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyS0"$/m,
|
||||
'x86_64 status console uses its serial terminal' );
|
||||
like( $machine, qr/^XCAT_GENESIS_ARCHITECTURE = "x86_64"$/m,
|
||||
'x86_64 exports its canonical extension identity' );
|
||||
|
||||
my $ppc64le_machine = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/conf/machine/xcat-genesis-ppc64le.conf'
|
||||
);
|
||||
like( $ppc64le_machine, qr/^DEFAULTTUNE = "ppc64p8le"$/m,
|
||||
'ppc64le uses the POWER8 little-endian tune' );
|
||||
like( $ppc64le_machine, qr/^KERNEL_IMAGETYPE = "vmlinux"$/m,
|
||||
'ppc64le builds a directly bootable kernel' );
|
||||
like( $ppc64le_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "hvc0"$/m,
|
||||
'ppc64le status console uses its hypervisor terminal' );
|
||||
like( $ppc64le_machine, qr/^QB_MACHINE = "-machine pseries"$/m,
|
||||
'ppc64le QEMU target uses pSeries' );
|
||||
like( $ppc64le_machine, qr/^QB_CPU = "-cpu POWER8"$/m,
|
||||
'ppc64le QEMU target uses the supported CPU baseline' );
|
||||
unlike( $ppc64le_machine, qr/^DEFAULTTUNE = "ppc64"$/m,
|
||||
'ppc64le is not aliased to big-endian ppc64' );
|
||||
like( $ppc64le_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "ppc64le"$/m,
|
||||
'ppc64le exports its canonical extension identity' );
|
||||
|
||||
my $ppc64_machine = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/conf/machine/xcat-genesis-ppc64.conf'
|
||||
);
|
||||
like( $ppc64_machine, qr/^DEFAULTTUNE = "powerpc64"$/m,
|
||||
'ppc64 uses the generic big-endian ABI' );
|
||||
unlike( $ppc64_machine, qr/^DEFAULTTUNE = "ppc64p8le"$/m,
|
||||
'ppc64 is not aliased to little-endian ppc64le' );
|
||||
like( $ppc64_machine, qr/^QB_MACHINE = "-machine pseries"$/m,
|
||||
'ppc64 QEMU target uses pSeries' );
|
||||
like( $ppc64_machine, qr/^QB_CPU = "-cpu POWER8"$/m,
|
||||
'ppc64 QEMU target can emulate the generic ABI' );
|
||||
like( $ppc64_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "hvc0"$/m,
|
||||
'ppc64 status console uses its hypervisor terminal' );
|
||||
like( $ppc64_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "ppc64"$/m,
|
||||
'ppc64 exports its canonical extension identity' );
|
||||
|
||||
my $x86_machine = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/conf/machine/xcat-genesis-x86.conf'
|
||||
);
|
||||
like( $x86_machine, qr/^DEFAULTTUNE = "i686"$/m,
|
||||
'x86 uses the i686 baseline' );
|
||||
unlike( $x86_machine, qr/^DEFAULTTUNE = "i586"$/m,
|
||||
'x86 does not retain the i586 baseline' );
|
||||
like( $x86_machine, qr/^QB_SYSTEM_NAME = "qemu-system-i386"$/m,
|
||||
'x86 uses the 32-bit QEMU system target' );
|
||||
like( $x86_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyS0"$/m,
|
||||
'x86 status console uses its serial terminal' );
|
||||
like( $x86_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "x86"$/m,
|
||||
'x86 exports its canonical extension identity' );
|
||||
|
||||
my $armv7hf_machine = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/conf/machine/xcat-genesis-armv7hf.conf'
|
||||
);
|
||||
like( $armv7hf_machine, qr/^DEFAULTTUNE = "armv7ahf"$/m,
|
||||
'armv7hf uses the ARMv7-A hard-float tune' );
|
||||
like( $armv7hf_machine, qr/^ARM_INSTRUCTION_SET = "arm"$/m,
|
||||
'armv7hf does not require Thumb instructions' );
|
||||
unlike( $armv7hf_machine, qr/neon/i,
|
||||
'armv7hf does not require NEON' );
|
||||
like( $armv7hf_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "armv7hf"$/m,
|
||||
'armv7hf exports its canonical extension identity' );
|
||||
like( $armv7hf_machine, qr/^QB_MACHINE = "-machine virt,highmem=off"$/m,
|
||||
'armv7hf QEMU target uses the virtual platform' );
|
||||
like( $armv7hf_machine, qr/^QB_CPU = "-cpu cortex-a15"$/m,
|
||||
'armv7hf QEMU target uses an ARMv7 CPU' );
|
||||
like( $armv7hf_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyAMA0"$/m,
|
||||
'armv7hf status console uses its PL011 terminal' );
|
||||
|
||||
my $aarch64_machine = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/conf/machine/xcat-genesis-aarch64.conf'
|
||||
);
|
||||
like( $aarch64_machine, qr/^DEFAULTTUNE = "aarch64"$/m,
|
||||
'aarch64 uses the generic ARMv8-A tune' );
|
||||
like( $aarch64_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "aarch64"$/m,
|
||||
'aarch64 exports its canonical extension identity' );
|
||||
like( $aarch64_machine, qr/^QB_MACHINE = "-machine virt"$/m,
|
||||
'aarch64 QEMU target uses the virtual platform' );
|
||||
like( $aarch64_machine, qr/^QB_CPU = "-cpu cortex-a57"$/m,
|
||||
'aarch64 QEMU target uses its conservative CPU baseline' );
|
||||
like( $aarch64_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyAMA0"$/m,
|
||||
'aarch64 status console uses its PL011 terminal' );
|
||||
|
||||
my $riscv64_machine = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/conf/machine/xcat-genesis-riscv64.conf'
|
||||
);
|
||||
like( $riscv64_machine, qr/^DEFAULTTUNE = "riscv64"$/m,
|
||||
'riscv64 uses the RV64GC tune' );
|
||||
like( $riscv64_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "riscv64"$/m,
|
||||
'riscv64 exports its canonical extension identity' );
|
||||
like( $riscv64_machine, qr/^EXTRA_IMAGEDEPENDS = "opensbi"$/m,
|
||||
'riscv64 builds its OpenSBI firmware' );
|
||||
like( $riscv64_machine, qr/^QB_DEFAULT_BIOS = "fw_jump\.elf"$/m,
|
||||
'riscv64 QEMU target boots the built OpenSBI firmware' );
|
||||
like( $riscv64_machine, qr/^QB_MACHINE = "-machine virt"$/m,
|
||||
'riscv64 QEMU target uses the virtual platform' );
|
||||
like( $riscv64_machine, qr/^QB_CPU = "-cpu rv64"$/m,
|
||||
'riscv64 QEMU target uses the generic CPU' );
|
||||
like( $riscv64_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyS0"$/m,
|
||||
'riscv64 status console uses its 8250 terminal' );
|
||||
|
||||
for my $machine_console (
|
||||
[ x86_64 => $machine ],
|
||||
[ x86 => $x86_machine ],
|
||||
[ ppc64 => $ppc64_machine ],
|
||||
[ ppc64le => $ppc64le_machine ],
|
||||
[ armv7hf => $armv7hf_machine ],
|
||||
[ aarch64 => $aarch64_machine ],
|
||||
[ riscv64 => $riscv64_machine ],
|
||||
)
|
||||
{
|
||||
unlike( $machine_console->[1],
|
||||
qr/^QB_KERNEL_CMDLINE_APPEND = ".*xcat\.debug-shell/m,
|
||||
"$machine_console->[0] VM starts the status console" );
|
||||
}
|
||||
|
||||
my $image = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/images/xcat-genesis-image.bb'
|
||||
);
|
||||
unlike( $image,
|
||||
qr/\b(?:dhclient|dhcpcd|busybox-udhcpc|systemd-networkd)\b/,
|
||||
'base image excludes alternate network clients' );
|
||||
like( $image, qr/\bxcat-genesis-network\b/,
|
||||
'base image includes the Genesis network policy' );
|
||||
like( $image, qr/\bxcat-genesis-protocol\b/,
|
||||
'base image includes the destiny clients' );
|
||||
like( $image, qr/\bpackagegroup-xcat-genesis-hardware\b/,
|
||||
'base image includes open hardware tools' );
|
||||
like( $image, qr/\bxcat-genesis-extensions\b/,
|
||||
'base image includes the signed extension loader' );
|
||||
like( $image, qr/\bxcat-genesis-console\b/,
|
||||
'base image includes the status console' );
|
||||
|
||||
my $hardware_group = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/packagegroups/packagegroup-xcat-genesis-hardware.bb'
|
||||
);
|
||||
for my $tool (
|
||||
qw(
|
||||
pciutils usbutils hwloc ethtool rdma-core iproute2-rdma lldpd ipmitool curl jq
|
||||
nvme-cli smartmontools sg3-utils lsscsi mdadm hdparm parted lvm2
|
||||
memtester stress-ng edac-utils kernel-modules mstflint
|
||||
xcat-genesis-hardware-control
|
||||
)
|
||||
)
|
||||
{
|
||||
like( $hardware_group, qr/\b\Q$tool\E\b/,
|
||||
"hardware group includes $tool" );
|
||||
}
|
||||
like( $hardware_group,
|
||||
qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-x86-64 = " dmidecode lshw mcelog"$/m,
|
||||
'x86_64 diagnostics are architecture scoped' );
|
||||
like( $hardware_group,
|
||||
qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-x86 = " dmidecode lshw"$/m,
|
||||
'x86 omits the unsupported machine-check daemon' );
|
||||
like( $hardware_group,
|
||||
qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-armv7hf = " dmidecode lshw"$/m,
|
||||
'armv7hf diagnostics are architecture scoped' );
|
||||
unlike( $hardware_group, qr/xcat-genesis-armv7(?:\s|\b)/,
|
||||
'hardware packages do not use the old ARMv7 name' );
|
||||
like( $hardware_group,
|
||||
qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-ppc64 = " dmidecode xcat-genesis-hardware-control-iprutils"$/m,
|
||||
'ppc64 includes the Power RAID provider' );
|
||||
like( $hardware_group,
|
||||
qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-ppc64le = " dmidecode xcat-genesis-hardware-control-iprutils"$/m,
|
||||
'ppc64le includes the Power RAID provider' );
|
||||
like( $hardware_group,
|
||||
qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-riscv64 = " lshw"$/m,
|
||||
'RISC-V omits unsupported DMI tools' );
|
||||
unlike( $hardware_group,
|
||||
qr/\b(?:storcli|perccli|ssacli|arcconf|nvidia-smi|rocm-smi)\b/i,
|
||||
'base image excludes vendor-only tools' );
|
||||
|
||||
my $hardware_recipe = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-hardware-control/xcat-genesis-hardware-control_1.0.bb'
|
||||
);
|
||||
like( $hardware_recipe,
|
||||
qr/^RDEPENDS:\$\{PN\} = "bash coreutils jq mstflint nvme-cli util-linux-flock"$/m,
|
||||
'hardware control dependencies are explicit' );
|
||||
like( $hardware_recipe,
|
||||
qr/^PACKAGES:prepend:xcat-genesis-ppc64 = "\$\{PN\}-iprutils "$/m,
|
||||
'iprutils provider package supports ppc64' );
|
||||
like( $hardware_recipe,
|
||||
qr/^PACKAGES:prepend:xcat-genesis-ppc64le = "\$\{PN\}-iprutils "$/m,
|
||||
'iprutils provider package supports ppc64le' );
|
||||
like( $hardware_recipe,
|
||||
qr/^RDEPENDS:\$\{PN\}-iprutils:xcat-genesis-ppc64 = "\$\{PN\} bash iprutils"$/m,
|
||||
'ppc64 iprutils dependencies are architecture scoped' );
|
||||
like( $hardware_recipe,
|
||||
qr/^RDEPENDS:\$\{PN\}-iprutils:xcat-genesis-ppc64le = "\$\{PN\} bash iprutils"$/m,
|
||||
'iprutils provider dependencies are architecture scoped' );
|
||||
like( $hardware_recipe, qr/\$\{datadir\}\/xcat\/genesis\/providers/,
|
||||
'provider manifests use an architecture-neutral data path' );
|
||||
unlike( $hardware_recipe,
|
||||
qr/\b(?:storcli|perccli|ssacli|arcconf|nvidia-smi|rocm-smi)\b/i,
|
||||
'hardware control excludes vendor-only tools' );
|
||||
|
||||
|
||||
for my $provider (qw(nvme mstflint iprutils)) {
|
||||
my $manifest = slurp_repo_file(
|
||||
"xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-hardware-control/files/$provider.json"
|
||||
);
|
||||
unlike( $manifest, qr/"destructive"\s*:\s*true/,
|
||||
"$provider base provider is read-only" );
|
||||
}
|
||||
|
||||
my $mstflint = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-support/mstflint/mstflint_4.36.0.bb'
|
||||
);
|
||||
like( $mstflint,
|
||||
qr/^LICENSE = "Linux-OpenIB & MIT & BSD-2-Clause"$/m,
|
||||
'mstflint records every linked source license' );
|
||||
like( $mstflint,
|
||||
qr/^SRCREV = "b9d9e844a14ae1c85cb90c76ccd4a56a0eccd599"$/m,
|
||||
'mstflint source revision is pinned' );
|
||||
like( $mstflint, qr/^RDEPENDS:\$\{PN\} = "python3-modules"$/m,
|
||||
'mstflint installs its Python runtime' );
|
||||
like( $mstflint, qr/^PACKAGECONFIG \?\?= "adb cables dc inband openssl"$/m,
|
||||
'mstflint omits the non-portable rdmem tool' );
|
||||
unlike( $mstflint, qr/--enable-(?:fw-mgr|nvml)/,
|
||||
'mstflint excludes vendor-coupled features' );
|
||||
|
||||
my $iprutils = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-support/iprutils/iprutils_2.4.19.bb'
|
||||
);
|
||||
like( $iprutils, qr/^LICENSE = "CPL-1\.0"$/m,
|
||||
'iprutils records its source license' );
|
||||
like( $iprutils,
|
||||
qr/^SRCREV = "9961e538736a6e81f1072a926c3ad91b8513c8c1"$/m,
|
||||
'iprutils source revision is pinned' );
|
||||
like( $iprutils, qr/^COMPATIBLE_HOST = "powerpc64\.\*-linux"$/m,
|
||||
'iprutils is restricted to 64-bit Power targets' );
|
||||
like( $iprutils, qr/--without-systemd --without-initscripts/,
|
||||
'iprutils omits unused background services' );
|
||||
|
||||
my $kernel_append = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto_%.bbappend'
|
||||
);
|
||||
like( $kernel_append, qr/file:\/\/xcat-genesis-x86-64\.cfg/,
|
||||
'x86_64 kernel uses the Genesis hardware fragment' );
|
||||
like( $kernel_append, qr/file:\/\/xcat-genesis-x86-common\.cfg/,
|
||||
'x86 targets share their hardware fragment' );
|
||||
like( $kernel_append, qr/file:\/\/xcat-genesis-x86\.cfg/,
|
||||
'x86 kernel uses its baseline fragment' );
|
||||
like( $kernel_append, qr/do_kernel_metadata:prepend:xcat-genesis-x86/,
|
||||
'x86 replaces the upstream QEMU CPU policy' );
|
||||
like( $kernel_append, qr/file:\/\/xcat-genesis-armv7hf\.cfg/,
|
||||
'armv7hf kernel uses the Genesis hardware fragment' );
|
||||
like( $kernel_append,
|
||||
qr/^SRCREV_machine:xcat-genesis-armv7hf = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m,
|
||||
'armv7hf kernel revision is pinned' );
|
||||
like( $kernel_append, qr/file:\/\/xcat-genesis-aarch64\.cfg/,
|
||||
'aarch64 kernel uses the Genesis hardware fragment' );
|
||||
like( $kernel_append,
|
||||
qr/^SRCREV_machine:xcat-genesis-aarch64 = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m,
|
||||
'aarch64 kernel revision is pinned' );
|
||||
like( $kernel_append, qr/file:\/\/xcat-genesis-riscv64\.cfg/,
|
||||
'riscv64 kernel uses the Genesis hardware fragment' );
|
||||
like( $kernel_append,
|
||||
qr/^SRCREV_machine:xcat-genesis-riscv64 = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m,
|
||||
'riscv64 kernel revision is pinned' );
|
||||
like( $kernel_append, qr/file:\/\/xcat-genesis-powerpc64\.cfg/,
|
||||
'64-bit Power kernels share the Genesis hardware fragment' );
|
||||
like( $kernel_append,
|
||||
qr/^SRCREV_machine:xcat-genesis-ppc64le = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m,
|
||||
'ppc64le kernel revision is pinned' );
|
||||
like( $kernel_append, qr/do_kernel_metadata:prepend:xcat-genesis-ppc64le/,
|
||||
'ppc64le repairs the upstream kernel metadata before configuration' );
|
||||
like( $kernel_append,
|
||||
qr/^SRCREV_machine:xcat-genesis-ppc64 = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m,
|
||||
'ppc64 kernel revision is pinned' );
|
||||
like( $kernel_append, qr/do_kernel_metadata:prepend:xcat-genesis-ppc64\(\)/,
|
||||
'ppc64 repairs the upstream kernel metadata before configuration' );
|
||||
like( $kernel_append, qr/file:\/\/xcat-genesis-ppc64\.cfg/,
|
||||
'ppc64 kernel uses its big-endian fragment' );
|
||||
|
||||
my $ppc64_kernel_config = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-ppc64.cfg'
|
||||
);
|
||||
like( $ppc64_kernel_config, qr/^CONFIG_CPU_BIG_ENDIAN=y$/m,
|
||||
'ppc64 kernel is big endian' );
|
||||
'ppc64 fragment requests big endian' );
|
||||
like( $ppc64_kernel_config, qr/^# CONFIG_CPU_LITTLE_ENDIAN is not set$/m,
|
||||
'ppc64 kernel is not little endian' );
|
||||
'ppc64 fragment disables little endian' );
|
||||
|
||||
my $kernel_config = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-x86-common.cfg'
|
||||
@@ -447,24 +38,24 @@ for my $symbol (
|
||||
)
|
||||
{
|
||||
like( $kernel_config, qr/^\Q$symbol\E=[ym]$/m,
|
||||
"x86_64 kernel enables $symbol" );
|
||||
"x86_64 fragment requests $symbol" );
|
||||
}
|
||||
|
||||
my $x86_kernel_config = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-x86.cfg'
|
||||
);
|
||||
like( $x86_kernel_config, qr/^CONFIG_M686=y$/m,
|
||||
'x86 kernel selects the i686 processor family' );
|
||||
'x86 fragment selects the i686 processor family' );
|
||||
like( $x86_kernel_config, qr/^# CONFIG_M586 is not set$/m,
|
||||
'x86 kernel does not retain i586' );
|
||||
'x86 fragment disables i586' );
|
||||
like( $x86_kernel_config, qr/^CONFIG_HIGHMEM4G=y$/m,
|
||||
'x86 kernel supports the 32-bit high-memory model' );
|
||||
'x86 fragment requests the 32-bit high-memory model' );
|
||||
like( $x86_kernel_config, qr/^# CONFIG_X86_MCE is not set$/m,
|
||||
'x86 kernel supports processors without machine-check registers' );
|
||||
'x86 fragment disables machine-check registers' );
|
||||
for my $vendor (qw(AMD INTEL)) {
|
||||
like( $x86_kernel_config,
|
||||
qr/^# CONFIG_X86_MCE_\Q$vendor\E is not set$/m,
|
||||
"x86 kernel disables the $vendor machine-check feature" );
|
||||
"x86 fragment disables the $vendor machine-check feature" );
|
||||
}
|
||||
|
||||
my $x86_64_kernel_config = slurp_repo_file(
|
||||
@@ -488,7 +79,7 @@ for my $symbol (
|
||||
)
|
||||
{
|
||||
like( $armv7hf_kernel_config, qr/^\Q$symbol\E=[ym]$/m,
|
||||
"armv7hf kernel enables $symbol" );
|
||||
"armv7hf fragment requests $symbol" );
|
||||
}
|
||||
|
||||
my $aarch64_kernel_config = slurp_repo_file(
|
||||
@@ -507,7 +98,7 @@ for my $symbol (
|
||||
)
|
||||
{
|
||||
like( $aarch64_kernel_config, qr/^\Q$symbol\E=[ym]$/m,
|
||||
"aarch64 kernel enables $symbol" );
|
||||
"aarch64 fragment requests $symbol" );
|
||||
}
|
||||
|
||||
my $riscv64_kernel_config = slurp_repo_file(
|
||||
@@ -526,7 +117,7 @@ for my $symbol (
|
||||
)
|
||||
{
|
||||
like( $riscv64_kernel_config, qr/^\Q$symbol\E=[ym]$/m,
|
||||
"riscv64 kernel enables $symbol" );
|
||||
"riscv64 fragment requests $symbol" );
|
||||
}
|
||||
|
||||
for my $symbol (
|
||||
@@ -537,7 +128,7 @@ for my $symbol (
|
||||
)
|
||||
{
|
||||
like( $riscv64_kernel_config, qr/^\Q$symbol\E=[ym]$/m,
|
||||
"riscv64 kernel enables $symbol to boot on ACPI firmware" );
|
||||
"riscv64 fragment requests $symbol to boot on ACPI firmware" );
|
||||
}
|
||||
|
||||
my $powerpc64_kernel_config = slurp_repo_file(
|
||||
@@ -555,104 +146,16 @@ for my $symbol (
|
||||
)
|
||||
{
|
||||
like( $powerpc64_kernel_config, qr/^\Q$symbol\E=[ym]$/m,
|
||||
"64-bit Power kernel enables $symbol" );
|
||||
"64-bit Power fragment requests $symbol" );
|
||||
}
|
||||
like( $powerpc64_kernel_config, qr/^# CONFIG_PPC_VAS is not set$/m,
|
||||
'POWER8 image disables unsupported VAS facilities' );
|
||||
|
||||
my $protocol_recipe = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-protocol/xcat-genesis-protocol_1.0.bb'
|
||||
);
|
||||
like( $protocol_recipe,
|
||||
qr{^FILESEXTRAPATHS:prepend := "\$\{THISDIR\}/\.\./\.\./\.\./\.\./\.\./xCAT-genesis-scripts/usr/bin:"$}m,
|
||||
'destiny clients use the canonical scripts' );
|
||||
like( $protocol_recipe, qr/file:\/\/getdestiny.*file:\/\/nextdestiny/s,
|
||||
'both destiny clients are packaged' );
|
||||
like( $protocol_recipe, qr/^RDEPENDS:\$\{PN\} = "bash openssl-bin"$/m,
|
||||
'destiny client dependencies are explicit' );
|
||||
unlike( $protocol_recipe, qr/\bdoxcat\b/,
|
||||
'obsolete bootstrap script is excluded' );
|
||||
|
||||
my $networkmanager = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-connectivity/networkmanager/networkmanager_%.bbappend'
|
||||
);
|
||||
like( $networkmanager, qr/^EXTRA_OEMESON:append = " -Dtests=no"$/m,
|
||||
'NetworkManager omits its test suite' );
|
||||
like( $networkmanager,
|
||||
qr{sed -i '/\^Also=NetworkManager-wait-online\.service\$/d'},
|
||||
'NetworkManager does not enable wait-online' );
|
||||
|
||||
my $network_recipe = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/xcat-genesis-network_1.0.bb'
|
||||
);
|
||||
like( $network_recipe,
|
||||
qr/^RDEPENDS:\$\{PN\} = "networkmanager-daemon networkmanager-nmcli"$/m,
|
||||
'network policy installs the daemon and nmcli' );
|
||||
|
||||
my $network_config = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/files/10-xcat-genesis.conf'
|
||||
);
|
||||
like( $network_config, qr/^ipv4\.dhcp-timeout=20$/m,
|
||||
'IPv4 DHCP has a bounded timeout' );
|
||||
like( $network_config, qr/^ipv6\.dhcp-duid=ll$/m,
|
||||
'IPv6 DHCP uses the link-layer DUID' );
|
||||
like( $network_config, qr/^ipv6\.dhcp-timeout=20$/m,
|
||||
'IPv6 DHCP has a bounded timeout' );
|
||||
|
||||
my $init_recipe = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/xcat-genesis-init_1.0.bb'
|
||||
);
|
||||
like( $init_recipe, qr/\bcoreutils\b/,
|
||||
'runtime status dependencies are explicit' );
|
||||
like( $init_recipe, qr/\butil-linux-logger\b/,
|
||||
'runtime logging dependency is explicit' );
|
||||
like( $init_recipe, qr/file:\/\/genesis-status/,
|
||||
'runtime status helper is packaged' );
|
||||
like( $init_recipe, qr/file:\/\/genesis-maintenance-shell/,
|
||||
'maintenance shell is packaged' );
|
||||
like( $init_recipe, qr/file:\/\/genesis-functions/,
|
||||
'shared runtime functions are packaged' );
|
||||
like( $init_recipe,
|
||||
qr/install -m 0644 .*genesis-functions.*\$\{libexecdir\}\/xcat\/genesis-functions/s,
|
||||
'shared runtime functions are installed as data' );
|
||||
unlike( $init_recipe, qr/genesis-debug-shell|XCAT_GENESIS_DEBUG/,
|
||||
'legacy debug shell is absent from the image recipe' );
|
||||
|
||||
my $INIT_FILES = 'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files';
|
||||
my $legacy_debug_shell = repo_path("$INIT_FILES/genesis-debug-shell");
|
||||
my $legacy_debug_service = repo_path("$INIT_FILES/xcat-genesis-debug-shell\@.service");
|
||||
ok( !-e $legacy_debug_shell, 'legacy debug shell script is removed' );
|
||||
ok( !-e $legacy_debug_service, 'legacy debug shell service is removed' );
|
||||
|
||||
|
||||
my $console_recipe = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/xcat-genesis-console_1.0.bb'
|
||||
);
|
||||
like( $console_recipe, qr/^DEPENDS = "libnewt systemd"$/m,
|
||||
'status console builds against libnewt and the journal API' );
|
||||
like( $console_recipe,
|
||||
qr/^RDEPENDS:\$\{PN\} = "libnewt libsystemd ncurses-terminfo-base xcat-genesis-init"$/m,
|
||||
'status console installs its terminal and maintenance-shell runtime' );
|
||||
like( $console_recipe, qr/^inherit meson pkgconfig systemd$/m,
|
||||
'status console uses the project build definition' );
|
||||
unlike( $console_recipe, qr/\b(?:whiptail|popt)\b/,
|
||||
'status console omits the dialog wrapper' );
|
||||
|
||||
my $console_build = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/files/xcat-genesis-console/meson.build'
|
||||
);
|
||||
like( $console_build, qr/'c_std=c17'/,
|
||||
'status console uses the C17 language contract' );
|
||||
like( $console_build, qr/dependency\('libnewt'\).*dependency\('libsystemd'\)/s,
|
||||
'status console links Newt and the journal client library' );
|
||||
like( $console_build, qr/'werror=true'/,
|
||||
'status console treats compiler warnings as build failures' );
|
||||
'POWER8 fragment disables VAS facilities' );
|
||||
|
||||
my $console_service = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/files/xcat-genesis-console@.service'
|
||||
);
|
||||
like( $console_service, qr/^Environment=TERM=xterm$/m,
|
||||
'status console redraws line graphics after a late attach' );
|
||||
'console service sets the terminal type' );
|
||||
like( $console_service, qr/^Environment=COLUMNS=80$/m,
|
||||
'status console fixes the serial width' );
|
||||
like( $console_service, qr/^Environment=LINES=24$/m,
|
||||
@@ -662,80 +165,5 @@ like( $console_service, qr/^TTYPath=\/dev\/%I$/m,
|
||||
unlike( $console_service, qr/xcat\.debug-shell|genesis-debug-shell/,
|
||||
'status console has no boot-time debug escape' );
|
||||
|
||||
ok( !-e repo_path(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/files/xcat-genesis-console.c'
|
||||
),
|
||||
'status console is not kept as a monolithic source file' );
|
||||
|
||||
my $network_state_service = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-network-state.service'
|
||||
);
|
||||
like( $network_state_service, qr/^After=NetworkManager\.service$/m,
|
||||
'network selection follows NetworkManager' );
|
||||
like( $network_state_service,
|
||||
qr/^Before=xcat-genesis-network-ready\.target$/m,
|
||||
'network selection gates Genesis readiness' );
|
||||
|
||||
my $register_service = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-register.service'
|
||||
);
|
||||
like( $register_service,
|
||||
qr/^Requires=xcat-genesis-network-ready\.target xcat-genesis-extensions\.service$/m,
|
||||
'registration requires Genesis network readiness' );
|
||||
like( $register_service,
|
||||
qr/^After=xcat-genesis-network-ready\.target xcat-genesis-extensions\.service$/m,
|
||||
'registration waits for extension verification' );
|
||||
unlike( $register_service, qr/network-online\.target/,
|
||||
'registration avoids generic network readiness' );
|
||||
|
||||
my $extension_recipe = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/xcat-genesis-extensions_1.0.bb'
|
||||
);
|
||||
like( $extension_recipe,
|
||||
qr/^RDEPENDS:\$\{PN\} = "bash coreutils jq openssl-bin systemd xcat-genesis-init"$/m,
|
||||
'extension verifier dependencies are explicit' );
|
||||
|
||||
|
||||
my $extension_service = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/files/xcat-genesis-extensions.service'
|
||||
);
|
||||
like( $extension_service,
|
||||
qr{^ConditionDirectoryNotEmpty=/var/lib/xcat/genesis/extensions$}m,
|
||||
'extension loading requires staged artifacts' );
|
||||
like( $extension_service, qr/^Before=xcat-genesis-register\.service$/m,
|
||||
'extensions load before xCAT registration' );
|
||||
|
||||
my $extension_class = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/classes-recipe/xcat-genesis-extension.bbclass'
|
||||
);
|
||||
like( $extension_class, qr/^inherit sysext-image$/m,
|
||||
'extensions use the upstream systemd image class' );
|
||||
like( $extension_class, qr/^IMAGE_FSTYPES = "squashfs-zst"$/m,
|
||||
'extensions are immutable SquashFS images' );
|
||||
like( $extension_class,
|
||||
qr/^XCAT_GENESIS_EXTENSION_ARCHITECTURE \?\?= "\$\{XCAT_GENESIS_ARCHITECTURE\}"$/m,
|
||||
'extension manifests use the canonical machine identity' );
|
||||
like( $extension_class, qr/"armv7hf"/,
|
||||
'extension manifests accept the canonical ARMv7 identity' );
|
||||
unlike( $extension_class, qr/"armv7"/,
|
||||
'extension manifests reject the ambiguous ARMv7 identity' );
|
||||
like( $extension_class, qr/XCAT_GENESIS_EXTENSION_KERNEL_RELEASE/,
|
||||
'extension metadata supports kernel ABI pinning' );
|
||||
like( $extension_class, qr/Restricted Genesis extensions must set LICENSE_FLAGS/,
|
||||
'restricted extensions require explicit license acceptance' );
|
||||
like( $extension_class, qr/"sha256":/,
|
||||
'extension manifests record the image digest' );
|
||||
|
||||
my $smoke_extension = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-extensions/images/xcat-genesis-extension-smoke.bb'
|
||||
);
|
||||
like( $smoke_extension, qr/^XCAT_GENESIS_EXTENSION_NAME = "xcat-smoke"$/m,
|
||||
'open smoke extension exercises the build path' );
|
||||
|
||||
my $preset = slurp_repo_file(
|
||||
'xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/00-xcat-genesis.preset'
|
||||
);
|
||||
is( $preset, "disable getty\@.service\n",
|
||||
'preset disables the unused virtual-terminal getty' );
|
||||
|
||||
done_testing();
|
||||
|
||||
@@ -1,473 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir tempfile);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
|
||||
my $doxcat = File::Spec->catfile(
|
||||
$repo_root, qw(xCAT-genesis-scripts usr bin doxcat)
|
||||
);
|
||||
|
||||
open( my $doxcat_fh, '<', $doxcat ) or die "Unable to read $doxcat: $!";
|
||||
my $source = do { local $/; <$doxcat_fh> };
|
||||
close($doxcat_fh);
|
||||
|
||||
my ($helper) =
|
||||
$source =~ /^(secondary_nic_needs_dhcp\(\)\s*\{.*?^\}\n)/ms;
|
||||
ok( defined($helper), 'doxcat defines the secondary DHCP eligibility helper' );
|
||||
|
||||
my ($selection) =
|
||||
$source =~ /(^\s*NICCANDIDATES=`.*?^\s*export NICSTOBRINGUP\s*$)/ms;
|
||||
ok( defined($selection), 'doxcat filters candidates before exporting them' );
|
||||
|
||||
# Keep the test runnable against the old source for a behavioral negative control.
|
||||
if ( !defined($helper) ) {
|
||||
$helper = "secondary_nic_needs_dhcp() { return 0; }\n";
|
||||
}
|
||||
if ( !defined($selection) ) {
|
||||
($selection) = $source =~ /(^\s*NICSTOBRINGUP=`ip link.*?`\s*$)/m;
|
||||
$selection = defined($selection)
|
||||
? "$selection\nexport NICSTOBRINGUP"
|
||||
: "NICSTOBRINGUP=\nexport NICSTOBRINGUP";
|
||||
}
|
||||
|
||||
my $test_tsm_file = '"$TEST_TSM_FILE"';
|
||||
my $test_sys_class_net = '"$TEST_SYS_CLASS_NET"';
|
||||
$selection =~ s{/tmp/tsmhostnic}{$test_tsm_file}g;
|
||||
$selection =~ s{/sys/class/net}{$test_sys_class_net}g;
|
||||
|
||||
my $scratch = tempdir( CLEANUP => 1 );
|
||||
my ( $runner_fh, $runner ) = tempfile( DIR => $scratch, UNLINK => 1 );
|
||||
print {$runner_fh} $helper;
|
||||
print {$runner_fh} <<'BASH';
|
||||
|
||||
ip() {
|
||||
if [ "$#" -eq 1 ] && [ "$1" = link ]; then
|
||||
command cat "$TEST_FIXTURE/link-list"
|
||||
elif [ "$#" -eq 5 ] && [ "$1" = -o ] && [ "$2" = link ] &&
|
||||
[ "$3" = show ] && [ "$4" = dev ]; then
|
||||
[ ! -e "$TEST_FIXTURE/links/$5.fail" ] || return 17
|
||||
[ -f "$TEST_FIXTURE/links/$5" ] || return 18
|
||||
command cat "$TEST_FIXTURE/links/$5"
|
||||
elif [ "$#" -eq 5 ] && [ "$1" = -o ] && [ "$2" = addr ] &&
|
||||
[ "$3" = show ] && [ "$4" = dev ]; then
|
||||
[ ! -e "$TEST_FIXTURE/addrs/$5.fail" ] || return 19
|
||||
[ -f "$TEST_FIXTURE/addrs/$5" ] || return 20
|
||||
command cat "$TEST_FIXTURE/addrs/$5"
|
||||
else
|
||||
return 64
|
||||
fi
|
||||
}
|
||||
|
||||
bootnic=$TEST_BOOTNIC
|
||||
BASH
|
||||
print {$runner_fh} "$selection\n";
|
||||
print {$runner_fh} <<'BASH';
|
||||
printf '%s\n' "$NICSTOBRINGUP"
|
||||
BASH
|
||||
close($runner_fh);
|
||||
|
||||
sub write_file {
|
||||
my ( $path, $contents ) = @_;
|
||||
open( my $fh, '>', $path ) or die "Unable to write $path: $!";
|
||||
print {$fh} $contents;
|
||||
close($fh);
|
||||
}
|
||||
|
||||
sub link_output {
|
||||
my ( $interface, $index ) = @_;
|
||||
my $flags = $interface->{flags};
|
||||
my $state = exists( $interface->{state} )
|
||||
? $interface->{state}
|
||||
: ( $flags =~ /(?:^|,)UP(?:,|$)/ ? 'UP' : 'DOWN' );
|
||||
my $link_type = $interface->{link_type} || 'ether';
|
||||
|
||||
return sprintf(
|
||||
"%d: %s: <%s> mtu 1500 qdisc noop state %s mode DEFAULT group default qlen 1000\n" .
|
||||
" link/%s 02:00:00:00:00:%02x brd ff:ff:ff:ff:ff:ff\n",
|
||||
$index, $interface->{nic}, $flags, $state, $link_type, $index
|
||||
);
|
||||
}
|
||||
|
||||
sub run_case {
|
||||
my ($case) = @_;
|
||||
my $root = tempdir( DIR => $scratch, CLEANUP => 1 );
|
||||
my $links = File::Spec->catdir( $root, 'links' );
|
||||
my $addrs = File::Spec->catdir( $root, 'addrs' );
|
||||
my $sys_class_net = File::Spec->catdir( $root, qw(sys class net) );
|
||||
my $devices = File::Spec->catdir( $root, 'devices' );
|
||||
my $masters = File::Spec->catdir( $root, 'masters' );
|
||||
make_path( $links, $addrs, $sys_class_net, $devices, $masters );
|
||||
|
||||
my $link_list = '';
|
||||
my $index = 1;
|
||||
foreach my $interface ( @{ $case->{interfaces} } ) {
|
||||
my $nic = $interface->{nic};
|
||||
my $output = link_output( $interface, $index++ );
|
||||
$link_list .= $output;
|
||||
|
||||
if ( $interface->{link_fail} ) {
|
||||
write_file( File::Spec->catfile( $links, "$nic.fail" ), '' );
|
||||
} else {
|
||||
write_file( File::Spec->catfile( $links, $nic ), $output );
|
||||
}
|
||||
|
||||
if ( $interface->{addr_fail} ) {
|
||||
write_file( File::Spec->catfile( $addrs, "$nic.fail" ), '' );
|
||||
} elsif ( exists( $interface->{addresses} ) ) {
|
||||
write_file(
|
||||
File::Spec->catfile( $addrs, $nic ),
|
||||
$interface->{addresses}
|
||||
);
|
||||
}
|
||||
|
||||
my $sys_nic = File::Spec->catdir( $sys_class_net, $nic );
|
||||
make_path($sys_nic);
|
||||
if ( $interface->{physical} ) {
|
||||
my $device_root = File::Spec->catdir( $devices, $nic );
|
||||
my $device = File::Spec->catdir( $device_root, 'interface' );
|
||||
make_path($device);
|
||||
if ( exists( $interface->{usb_vendor} ) ) {
|
||||
write_file(
|
||||
File::Spec->catfile( $device_root, 'idVendor' ),
|
||||
"$interface->{usb_vendor}\n"
|
||||
);
|
||||
}
|
||||
if ( exists( $interface->{usb_product} ) ) {
|
||||
write_file(
|
||||
File::Spec->catfile( $device_root, 'idProduct' ),
|
||||
"$interface->{usb_product}\n"
|
||||
);
|
||||
}
|
||||
symlink( $device, File::Spec->catfile( $sys_nic, 'device' ) )
|
||||
or die "Unable to link physical device for $nic: $!";
|
||||
}
|
||||
if ( $interface->{master} ) {
|
||||
my $master = File::Spec->catdir( $masters, $nic );
|
||||
make_path($master);
|
||||
symlink( $master, File::Spec->catfile( $sys_nic, 'master' ) )
|
||||
or die "Unable to link master for $nic: $!";
|
||||
}
|
||||
}
|
||||
write_file( File::Spec->catfile( $root, 'link-list' ), $link_list );
|
||||
|
||||
my $tsm_file = File::Spec->catfile( $root, 'tsmhostnic' );
|
||||
write_file( $tsm_file, $case->{tsmnic} ) if defined( $case->{tsmnic} );
|
||||
|
||||
local %ENV = (
|
||||
%ENV,
|
||||
TEST_BOOTNIC => $case->{bootnic} || 'boot0',
|
||||
TEST_FIXTURE => $root,
|
||||
TEST_SYS_CLASS_NET => $sys_class_net,
|
||||
TEST_TSM_FILE => $tsm_file,
|
||||
);
|
||||
|
||||
open( my $result_fh, '-|', 'bash', '--noprofile', '--norc', $runner )
|
||||
or die "Unable to run secondary DHCP harness: $!";
|
||||
my $output = do { local $/; <$result_fh> };
|
||||
close($result_fh);
|
||||
my $status = $? >> 8;
|
||||
my @actual = grep { length($_) } split /\s+/, $output;
|
||||
|
||||
is( $status, 0, "$case->{name}: selector succeeds" );
|
||||
is_deeply( \@actual, $case->{expected}, "$case->{name}: selected NICs" );
|
||||
}
|
||||
|
||||
my @cases = (
|
||||
{
|
||||
name => 'DOWN virtual interface keeps the legacy path',
|
||||
interfaces => [
|
||||
{ nic => 'veth0', flags => 'BROADCAST,MULTICAST', state => 'DOWN' },
|
||||
],
|
||||
expected => ['veth0'],
|
||||
},
|
||||
{
|
||||
name => 'DOWN interface short-circuits every ownership guard',
|
||||
tsmnic => 'eno1',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno1',
|
||||
flags => 'BROADCAST,MULTICAST',
|
||||
physical => 1,
|
||||
master => 1,
|
||||
usb_vendor => '046b',
|
||||
usb_product => 'ffb0',
|
||||
addresses => "2: eno1 inet 192.0.2.21/24 scope global eno1\n",
|
||||
},
|
||||
],
|
||||
expected => ['eno1'],
|
||||
},
|
||||
{
|
||||
name => 'operational state does not replace the IFF_UP flag',
|
||||
interfaces => [
|
||||
{ nic => 'eno2', flags => 'BROADCAST,MULTICAST', state => 'UP' },
|
||||
],
|
||||
expected => ['eno2'],
|
||||
},
|
||||
{
|
||||
name => 'UP physical unaddressed interface is selected',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno3',
|
||||
flags => 'BROADCAST,MULTICAST,UP,LOWER_UP',
|
||||
physical => 1,
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => ['eno3'],
|
||||
},
|
||||
{
|
||||
name => 'UP without carrier is still administratively UP',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno4',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
state => 'DOWN',
|
||||
physical => 1,
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => ['eno4'],
|
||||
},
|
||||
{
|
||||
name => 'IFF_UP is recognized as the first flag',
|
||||
interfaces => [
|
||||
{ nic => 'eno5', flags => 'UP,BROADCAST', physical => 1, addresses => '' },
|
||||
],
|
||||
expected => ['eno5'],
|
||||
},
|
||||
{
|
||||
name => 'IFF_UP is recognized as the only flag',
|
||||
interfaces => [
|
||||
{ nic => 'eno6', flags => 'UP', physical => 1, addresses => '' },
|
||||
],
|
||||
expected => ['eno6'],
|
||||
},
|
||||
{
|
||||
name => 'IFF_UP is recognized as the last flag',
|
||||
interfaces => [
|
||||
{ nic => 'eno7', flags => 'BROADCAST,UP', physical => 1, addresses => '' },
|
||||
],
|
||||
expected => ['eno7'],
|
||||
},
|
||||
{
|
||||
name => 'physical IPoIB interface remains eligible',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'ib0',
|
||||
flags => 'BROADCAST,MULTICAST,UP,LOWER_UP',
|
||||
link_type => 'infiniband',
|
||||
physical => 1,
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => ['ib0'],
|
||||
},
|
||||
{
|
||||
name => 'link-local IPv4 and IPv6 addresses do not claim the interface',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno8',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
addresses =>
|
||||
"2: eno8 inet 169.254.10.20/16 scope link eno8\n" .
|
||||
"2: eno8 inet6 fe80::20/64 scope link\n" .
|
||||
"2: eno8 inet6 feb0::20/64 scope link\n",
|
||||
},
|
||||
],
|
||||
expected => ['eno8'],
|
||||
},
|
||||
{
|
||||
name => 'global IPv4 address preserves existing ownership',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno9',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
addresses => "2: eno9 inet 198.51.100.9/24 scope global eno9\n",
|
||||
},
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'global IPv6 address preserves existing ownership',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno10',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
addresses => "2: eno10 inet6 2001:db8::10/64 scope global\n",
|
||||
},
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'global address wins when link-local addresses also exist',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno11',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
addresses =>
|
||||
"2: eno11 inet6 fe80::11/64 scope link\n" .
|
||||
"2: eno11 inet 203.0.113.11/24 scope global eno11\n",
|
||||
},
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'TSM-owned interface is excluded by exact name',
|
||||
tsmnic => 'eno12',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno12',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'known management USB devices are excluded before setup markers',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'enp18s0f0u1',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
usb_vendor => '046b',
|
||||
usb_product => 'ffb0',
|
||||
addresses => '',
|
||||
},
|
||||
{
|
||||
nic => 'enp18s0f0u2',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
usb_vendor => '04b3',
|
||||
usb_product => '4010',
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'other USB device identities remain eligible',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'enp18s0f0u3',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
usb_vendor => '046b',
|
||||
usb_product => 'ffb1',
|
||||
addresses => '',
|
||||
},
|
||||
{
|
||||
nic => 'enp18s0f0u4',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
usb_vendor => '04b4',
|
||||
usb_product => '4010',
|
||||
addresses => '',
|
||||
},
|
||||
{
|
||||
nic => 'enp18s0f0u5',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
usb_vendor => '046c',
|
||||
usb_product => 'ffb0',
|
||||
addresses => '',
|
||||
},
|
||||
{
|
||||
nic => 'enp18s0f0u6',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
usb_vendor => '04b3',
|
||||
usb_product => '4011',
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => [
|
||||
'enp18s0f0u3', 'enp18s0f0u4',
|
||||
'enp18s0f0u5', 'enp18s0f0u6'
|
||||
],
|
||||
},
|
||||
{
|
||||
name => 'TSM ownership does not use substring matching',
|
||||
tsmnic => 'eno130',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno13',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => ['eno13'],
|
||||
},
|
||||
{
|
||||
name => 'UP virtual interface is excluded',
|
||||
interfaces => [
|
||||
{ nic => 'vnet0', flags => 'BROADCAST,MULTICAST,UP', addresses => '' },
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'UP physical slave is excluded',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno14',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
master => 1,
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'link-state query failure is closed',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno15',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
link_fail => 1,
|
||||
addresses => '',
|
||||
},
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'address query failure is closed',
|
||||
interfaces => [
|
||||
{
|
||||
nic => 'eno16',
|
||||
flags => 'BROADCAST,MULTICAST,UP',
|
||||
physical => 1,
|
||||
addr_fail => 1,
|
||||
},
|
||||
],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'caller excludes loopback, boot, and USB interfaces',
|
||||
bootnic => 'boot0',
|
||||
interfaces => [
|
||||
{ nic => 'lo', flags => 'LOOPBACK,UP,LOWER_UP', physical => 1, addresses => '' },
|
||||
{ nic => 'boot0', flags => 'BROADCAST,MULTICAST,UP', physical => 1, addresses => '' },
|
||||
{ nic => 'usb0', flags => 'BROADCAST,MULTICAST,UP', physical => 1, addresses => '' },
|
||||
{ nic => 'eno17', flags => 'BROADCAST,MULTICAST,UP', physical => 1, addresses => '' },
|
||||
],
|
||||
expected => ['eno17'],
|
||||
},
|
||||
);
|
||||
|
||||
run_case($_) foreach @cases;
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user