prplos/.gitlab/testbed.yml
Petr Štetiar 647353c929
ci: cram: add report and auto component selection jobs
Merge request changes currently leave all component CRAM jobs dormant.
Reviewers cannot see the selected coverage without resolving the matrix
themselves, and playing a bucket job cannot combine affected components
into one boot.

Add per-board changes anchors and six automatic component jobs. Resolve
their content from refreshed merge request labels and the diff before
any testbed setup. Exit cleanly without DUT cleanup when selection is
empty.

An automatic selection resolves a union of components that can become a
full run, so give these jobs the full-tier 85-minute budget instead of
the 40-minute component budget they would otherwise inherit.

Add an MR-only report job that prints the complete selection matrix
without a build or hardware dependency. Keep all 24 existing bucket jobs
unchanged.

The automatic template originally extended .cram_component as the later
parent after each board template. GitLab's parent precedence therefore
reapplied generic .testbed variables over board-specific values. This
changed DUT_WAN_INTERFACE on Freedom, Omnia, Haze, Mozart, and Valyrian,
and DUT_ARCH_PACKAGES_PATH on OSPv2, Haze, Mozart, and Valyrian. The
resulting Freedom failures exposed the wrong interface:

```text
014-firewall-levels.t: grep $DUT_WAN_INTERFACE -> [1]
045-qos.t: grep $DUT_WAN_INTERFACE -> [1]
```

Make .cram_component_auto a standalone overlay because the board parent
already supplies the component script and testbed chain. Add a committed
guard that resolves all six extends chains and requires each automatic
job to preserve its full-bucket sibling's variables.

Assisted-by: codex:gpt-5.6-sol
References: CI-9
Signed-off-by: Petr Štetiar <petr.stetiar@prplfoundation.org>
2026-08-26 20:37:10 +02:00

405 lines
15 KiB
YAML

.testbed:
stage: run
image: "registry.gitlab.com/prpl-foundation/prplos/ci/containers/testbed/testbed:v1.3.0@sha256:7f50dde2279c201542569b7f31e965d43a308e245a4ec973df52d581560b0c28"
timeout: 87 minutes
variables:
DUT_WAN_INTERFACE: eth1
TESTBED_MNG_INTERFACE: enp1s0
TESTBED_LAN_INTERFACE: enp2s0
TESTBED_WAN_INTERFACE: enp3s0
TESTBED_TFTP_PATH: /var/lib/tftpboot
TFTP_IMAGE_PATH: bin/targets/$DUT_TARGET/$DUT_SUBTARGET
PACKAGE_DIR: bin/packages
DUT_ARCH_PACKAGES_PATH: $PACKAGE_DIR/${DUT_ARCH}_${DUT_CPU_TYPE}_${DUT_CPU_SUBTYPE}
TARGET_LAN_IP: 192.168.1.1
TARGET_LAN_TEST_HOST: 192.168.1.2
DUT_SLEEP_AFTER_BOOT: 90
DUT_SSH_CONNECT_TIMEOUT: 30
DUT_SSH_CONNECT_ATTEMPTS: 3
DUT_TR181_WAN_INTERFACE: Device.Ethernet.Link.2.
DUT_TR181_LAN_INTERFACE: Device.Ethernet.Link.3.
DUT_OBUSPA_VERBOSE_LEVEL: ''
DUT_OBUSPA_PROTOCOL_TRACE: ''
TEST_SETUP_TEARDOWN_DURATION: 360
CRAM_REMOTE_COMMAND: ssh -n root@$TARGET_LAN_IP
CRAM_REMOTE_COPY: scp -O -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
CRAM_TEST_SUITE: |
.gitlab/tests/cram/generic/acceleration-plan-components
.gitlab/tests/cram/$DUT_BOARD/acceleration-plan-components
.gitlab/tests/cram/generic/lcm
.gitlab/tests/cram/generic
.gitlab/tests/cram/$DUT_BOARD
CRAM_TEST_SUITE_POST: |
.gitlab/tests/cram/post/generic
DUT_INIT_SCRIPTS: |
.gitlab/scripts/dut-init/dut-change-fail-action-to-restart.sh
before_script:
- source .gitlab/scripts/helpers.sh
- section_start "cdrouter-cleanup" "CDRouter Cleanup" "collapsed"
- >
if echo "$CI_RUNNER_DESCRIPTION" | grep -q testbed-02; then
export CDROUTER_API_TOKEN="$CDROUTER_API_TOKEN_TESTBED_02"
fi
- .gitlab/scripts/testbed-cdrouter.py check_connectivity
- .gitlab/scripts/testbed-cdrouter.py package_stop
- section_end "cdrouter-cleanup"
- section_start "testbed-setup" "Testbed Setup" "collapsed"
- touch .run_failed
- sudo ip link set $TESTBED_LAN_INTERFACE up 2> /dev/null
- sudo ip link set $TESTBED_WAN_INTERFACE up 2> /dev/null
- sleep 10
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- |
for f in ${TFTP_IMAGE_FILENAMES:-$TFTP_IMAGE_FILENAME}; do
echo "Using firmware image $TFTP_IMAGE_PATH/$f"
cp "$TFTP_IMAGE_PATH/$f" "$TESTBED_TFTP_PATH"
done
if [ -n "$TFTP_IMAGE_UNPACK_COMMAND" ]; then
echo "Running $TFTP_IMAGE_UNPACK_COMMAND"
eval "$TFTP_IMAGE_UNPACK_COMMAND"
fi
- >
if echo "$CI_RUNNER_DESCRIPTION" | grep -q testbed-02; then
export TB_CONFIG=".testbed/labgrid/testbed-02.yaml"
fi
- section_end "testbed-setup"
- section_start "dut-bringup" "DUT Bringup" "collapsed"
- .gitlab/scripts/testbed-device.py --target $LABGRID_TARGET boot_into shell
- >
.gitlab/scripts/testbed-device.py
--target $LABGRID_TARGET check_network
--network lan
--remote-host $TARGET_LAN_TEST_HOST
- sleep $DUT_SLEEP_AFTER_BOOT
- section_end "dut-bringup"
- section_start "dut-system-info" "DUT System Information" "collapsed"
- echo "Waiting for SSH connection availability with ${DUT_SSH_CONNECT_TIMEOUT}sec timeout, with $DUT_SSH_CONNECT_ATTEMPTS attempts..."
- time ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectionAttempts=$DUT_SSH_CONNECT_ATTEMPTS -o ConnectTimeout=$DUT_SSH_CONNECT_TIMEOUT root@$TARGET_LAN_IP 'exit 0'
- ssh-keyscan $TARGET_LAN_IP >> ~/.ssh/known_hosts 2> /dev/null
- >
if [ -n "${DUT_OBUSPA_VERBOSE_LEVEL:-}" ] ||
[ -n "${DUT_OBUSPA_PROTOCOL_TRACE:-}" ]; then
.gitlab/scripts/dut-init/dut-configure-obuspa-logging.sh;
fi
- ssh root@$TARGET_LAN_IP 'logger -t CI -p local0.info "System accessible over SSH, $(uptime -p)"' || true
- ssh root@$TARGET_LAN_IP "cat /var/log/messages" > syslog-$LABGRID_TARGET.txt 2> /dev/null || true
- ssh root@$TARGET_LAN_IP "ubus call system board" | tee system-$LABGRID_TARGET.json
- >
for dut_init_script in $DUT_INIT_SCRIPTS;
do echo "Running $dut_init_script"; "${CI_PROJECT_DIR}/$dut_init_script";
done
- section_end "dut-system-info"
script:
- source .gitlab/scripts/helpers.sh
- section_start "test-execution" "Test Execution${COMPONENT:+ [$COMPONENT]}" "expanded"
- |
TEST_CUTOFF_TIMEOUT=$((CI_JOB_TIMEOUT - DUT_SLEEP_AFTER_BOOT - TEST_SETUP_TEARDOWN_DURATION))
echo "Job timeout: $CI_JOB_TIMEOUT seconds, test cutoff timeout: $TEST_CUTOFF_TIMEOUT seconds"
- |
rm -fr .run_check_failed
run_check() {
"$@" || {
local statuses=("${PIPESTATUS[@]}")
local command=("$@")
local test_count=0
while [ "${#command[@]}" -gt 0 ] && [[ "${command[-1]}" == *.t ]]; do
unset 'command[-1]'
((test_count += 1))
done
if [ "$test_count" -gt 0 ]; then
command+=("($test_count tests)")
fi
for status in "${statuses[@]}"; do
if [ "$status" -ne 0 ]; then
log_error "run_check: ${command[*]} | FAILED! (status: $status)" | tee -a .run_check_failed
fi
done
}
}
run_check ssh root@$TARGET_LAN_IP 'f=$(logread | grep "procd.*init complete"); echo "${f:-Warning: init is not complete yet,} $(uptime -p)"'
run_check ssh root@$TARGET_LAN_IP 'logger -t CI -p local0.info "Testing starts, $(uptime -p)"'
run_check \
timeout --kill-after=10s --verbose ${TEST_CUTOFF_TIMEOUT}s \
python3 -m cram --xunit-file=cram-result-$LABGRID_TARGET.xunit.xml --verbose $CRAM_TEST_SUITE $CRAM_TEST_SUITE_EXTRA | tee cram-result-$LABGRID_TARGET.txt
run_check python3 -m cram --xunit-file=cram-result-post-$LABGRID_TARGET.xunit.xml --verbose $CRAM_TEST_SUITE_POST | tee --append cram-result-$LABGRID_TARGET.txt
run_check ssh root@$TARGET_LAN_IP 'logger -t CI -p local0.info "Testing finished, $(uptime -p)"'
- section_end "test-execution"
- |
[ -f .run_check_failed ] && cat .run_check_failed && exit 1
rm .run_failed
after_script:
- >-
curl --fail \
--output .gitlab/scripts/cram-xunit-merger.py \
https://gitlab.com/prpl-foundation/tooling/cram-xunit-merger/-/raw/9bc14318ab88c2a024ad0ff8bf09c21d048e1e3b/cram-xunit-merger.py
- python3 .gitlab/scripts/cram-xunit-merger.py \
--output "${CI_PROJECT_DIR}/cram-result-$LABGRID_TARGET.junit.xml" \
"${CI_PROJECT_DIR}/cram-result-$LABGRID_TARGET.xunit.xml" \
"${CI_PROJECT_DIR}/cram-result-post-$LABGRID_TARGET.xunit.xml" || true
- >
[ "$TMATE_ENABLE_SHELL" = "YES" ] && {
echo "Starting tmate session..."
tmate -n "${TMATE_SESSION_TOKEN}-$CI_JOB_ID" -k "$TMATE_API_KEY" -F new "ssh root@$TARGET_LAN_IP" > /tmp/tmate-session.log 2>&1
true
}
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- >
if echo "$CI_RUNNER_DESCRIPTION" | grep -q testbed-02; then
export TB_CONFIG=".testbed/labgrid/testbed-02.yaml"
fi
- >
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=15 root@$TARGET_LAN_IP 'exit 0' || {
.gitlab/scripts/testbed-device.py --target $LABGRID_TARGET console_dump_system_state
.gitlab/scripts/testbed-device.py --target $LABGRID_TARGET console_recover_ssh_access
} || true
- ssh-keyscan $TARGET_LAN_IP >> ~/.ssh/known_hosts 2> /dev/null
- >
ssh root@$TARGET_LAN_IP exit && {
ssh root@$TARGET_LAN_IP ps > processes-$LABGRID_TARGET.txt
ssh root@$TARGET_LAN_IP dmesg > dmesg-$LABGRID_TARGET.txt
ssh root@$TARGET_LAN_IP "cat /var/log/messages" > syslog-$LABGRID_TARGET.txt
ssh root@$TARGET_LAN_IP opkg list > opkg_list-$LABGRID_TARGET.txt
script --quiet --command "ssh -t root@$TARGET_LAN_IP /bin/getDebugInformation --all"
scp -O -r root@${TARGET_LAN_IP}:/tmp/debug-*_all.tar.gz . > /dev/null
scp -O -r root@${TARGET_LAN_IP}:/etc etc > /dev/null
scp -O -r root@${TARGET_LAN_IP}:/tmp/beerocks/logs prplmesh_beerocks_logs > /dev/null
} || true
- .gitlab/scripts/testbed-device.py --target $LABGRID_TARGET power off
- tar xzf debug-*_all.tar.gz || true
- mv debug-*.txt debug-information-$LABGRID_TARGET.txt || true
- ( [ $(tar -tf coredumps_all.tar.gz | wc -l) -eq 0 ] && rm coredumps_all.tar.gz || true ) 2>/dev/null
- mv coredumps_all.tar.gz coredumps-$LABGRID_TARGET.tar.gz 2> /dev/null || true
- mv console_$LABGRID_TARGET console_$LABGRID_TARGET.txt || true
artifacts:
expire_in: 1 month
when: always
paths:
- .gitlab/tests/cram/**/*.t.err
- .gitlab/tests/cram/**/*.actual
- .gitlab/tests/cram/**/*.diff
- etc
- prplmesh_beerocks_logs
- debug-information-$LABGRID_TARGET.txt
- processes-$LABGRID_TARGET.txt
- dmesg-$LABGRID_TARGET.txt
- syslog-$LABGRID_TARGET.txt
- system-$LABGRID_TARGET.json
- console_$LABGRID_TARGET.txt
- cram-result-*
- opkg_list-$LABGRID_TARGET.txt
- coredumps-$LABGRID_TARGET.tar.gz
reports:
junit: cram-result-$LABGRID_TARGET.junit.xml
.cram_component:
extends: .testbed
timeout: 40 minutes
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: never
- if: '$CI_COMMIT_TAG =~ /^prplware-v[0-9]+\.[0-9]+\.[0-9]+.*$/ && $COMPONENT == "full"'
- if: '$COMPONENT == "full" && $CRAM_COMPONENTS =~ /(^|,)full($|,)/'
- if: '$COMPONENT == "prplmesh" && $CRAM_COMPONENTS =~ /(^|,)prplmesh($|,)/'
- if: '$COMPONENT == "lcm" && $CRAM_COMPONENTS =~ /(^|,)lcm($|,)/'
- if: '$COMPONENT == "prplos" && $CRAM_COMPONENTS =~ /(^|,)prplos($|,)/'
- when: manual
allow_failure: true
variables:
COMPONENT: prplos
script:
- |
CRAM_TEST_SUITE="$(
.gitlab/tests/cram/scripts/resolve-component-tests.py \
--test-root .gitlab/tests/cram \
--manifest components.yml \
--board "$DUT_BOARD" \
--component "$COMPONENT"
)"
export CRAM_TEST_SUITE
- !reference [.testbed, script]
.cram_component_auto:
variables:
COMPONENT: null
before_script:
- |
export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0=safe.directory
export GIT_CONFIG_VALUE_0="$CI_PROJECT_DIR"
runtime_labels="${CI_MERGE_REQUEST_LABELS:-}"
labels_json="$(mktemp)"
if curl --fail --silent --show-error \
--header "JOB-TOKEN: $CI_JOB_TOKEN" \
--output "$labels_json" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID"; then
runtime_labels="$(
python3 - "$labels_json" <<'PY'
import json
import sys
with open(sys.argv[1], encoding="utf-8") as source:
print(",".join(json.load(source)["labels"]))
PY
)"
else
echo "WARNING: cannot refresh MR labels; using pipeline snapshot" >&2
fi
rm -f "$labels_json"
base_ref="${CI_MERGE_REQUEST_DIFF_BASE_SHA:-}"
if [ -z "$base_ref" ]; then
echo "error: CI_MERGE_REQUEST_DIFF_BASE_SHA is unset" >&2
exit 1
fi
git cat-file -e "$base_ref^{commit}" 2>/dev/null ||
git fetch --no-tags origin "$base_ref"
set -- \
.gitlab/tests/cram/scripts/resolve-changed-components.py \
--test-root .gitlab/tests/cram \
--manifest components.yml \
--base-ref "$base_ref" \
--board "$DUT_BOARD" \
--labels "$runtime_labels"
if [ "${CRAM_COMPONENTS+x}" = x ]; then
set -- "$@" --cram-components "$CRAM_COMPONENTS"
fi
if ! COMPONENT="$("$@")"; then
echo "error: cram component selection failed" >&2
exit 1
fi
if [ -z "$COMPONENT" ]; then
exit 0
fi
export COMPONENT
touch .cram-auto-selected
- !reference [.testbed, before_script]
after_script:
- |
if [ ! -f .cram-auto-selected ]; then
echo "Skipping DUT cleanup: automatic selection was empty"
exit 0
fi
- !reference [.testbed, after_script]
lint cram component manifest:
stage: run
image: "registry.gitlab.com/prpl-foundation/prplos/ci/containers/testbed/testbed:v1.3.0@sha256:7f50dde2279c201542569b7f31e965d43a308e245a4ec973df52d581560b0c28"
needs: []
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: never
- when: manual
allow_failure: true
script:
- |
export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0=safe.directory
export GIT_CONFIG_VALUE_0="$CI_PROJECT_DIR"
base_ref="${CI_MERGE_REQUEST_DIFF_BASE_SHA:-}"
if [ -n "$base_ref" ]; then
echo "Comparing manifest coverage against MR diff base $base_ref"
git cat-file -e "$base_ref^{commit}" 2>/dev/null ||
git fetch --no-tags origin "$base_ref"
set -- --base-ref "$base_ref"
else
set --
fi
.gitlab/tests/cram/scripts/check-component-coverage.py \
--test-root .gitlab/tests/cram \
--manifest components.yml \
"$@"
.gitlab/tests/cram/scripts/check-auto-job-variables.py
.report_cram_selection:
stage: run
image: "registry.gitlab.com/prpl-foundation/prplos/ci/containers/testbed/testbed:v1.3.0@sha256:7f50dde2279c201542569b7f31e965d43a308e245a4ec973df52d581560b0c28"
script:
- |
export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0=safe.directory
export GIT_CONFIG_VALUE_0="$CI_PROJECT_DIR"
runtime_labels="${CI_MERGE_REQUEST_LABELS:-}"
labels_json="$(mktemp)"
if curl --fail --silent --show-error \
--header "JOB-TOKEN: $CI_JOB_TOKEN" \
--output "$labels_json" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID"; then
runtime_labels="$(
python3 - "$labels_json" <<'PY'
import json
import sys
with open(sys.argv[1], encoding="utf-8") as source:
print(",".join(json.load(source)["labels"]))
PY
)"
else
echo "WARNING: cannot refresh MR labels; using pipeline snapshot" >&2
fi
rm -f "$labels_json"
base_ref="${CI_MERGE_REQUEST_DIFF_BASE_SHA:-}"
if [ -z "$base_ref" ]; then
echo "error: CI_MERGE_REQUEST_DIFF_BASE_SHA is unset" >&2
exit 1
fi
git cat-file -e "$base_ref^{commit}" 2>/dev/null ||
git fetch --no-tags origin "$base_ref"
set -- \
.gitlab/tests/cram/scripts/resolve-changed-components.py \
--test-root .gitlab/tests/cram \
--manifest components.yml \
--base-ref "$base_ref" \
--report \
--labels "$runtime_labels"
if [ "${CRAM_COMPONENTS+x}" = x ]; then
set -- "$@" --cram-components "$CRAM_COMPONENTS"
fi
"$@"
.testbed true.cz:
extends: .cram_component