Merge branch 'selftests-vsock-refactor-and-improve-vmtest-infrastructure'

Bobby Eshleman says:

====================
selftests/vsock: refactor and improve vmtest infrastructure

This patch series refactors the vsock selftest VM infrastructure to
improve test run times, improve logging, and prepare for future tests
which make heavy usage of these refactored functions and have new
requirements such as simultaneous QEMU processes.
====================

Link: https://patch.msgid.link/20251108-vsock-selftests-fixes-and-improvements-v4-0-d5e8d6c87289@meta.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2025-11-12 06:19:42 -08:00
+235 -115
View File
@@ -7,6 +7,8 @@
# * virtme-ng
# * busybox-static (used by virtme-ng)
# * qemu (used by virtme-ng)
#
# shellcheck disable=SC2317,SC2119
readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
@@ -22,8 +24,9 @@ readonly SSH_HOST_PORT=2222
readonly VSOCK_CID=1234
readonly WAIT_PERIOD=3
readonly WAIT_PERIOD_MAX=60
readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))
readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_vsock_vmtest_XXXX.pid)
readonly WAIT_QEMU=5
readonly PIDFILE_TEMPLATE=/tmp/vsock_vmtest_XXXX.pid
declare -A PIDFILES
# virtme-ng offers a netdev for ssh when using "--ssh", but we also need a
# control port forwarded for vsock_test. Because virtme-ng doesn't support
@@ -33,12 +36,6 @@ readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_vsock_vmtest_XXXX.pid)
# add the kernel cmdline options that virtme-init uses to setup the interface.
readonly QEMU_TEST_PORT_FWD="hostfwd=tcp::${TEST_HOST_PORT}-:${TEST_GUEST_PORT}"
readonly QEMU_SSH_PORT_FWD="hostfwd=tcp::${SSH_HOST_PORT}-:${SSH_GUEST_PORT}"
readonly QEMU_OPTS="\
-netdev user,id=n0,${QEMU_TEST_PORT_FWD},${QEMU_SSH_PORT_FWD} \
-device virtio-net-pci,netdev=n0 \
-device vhost-vsock-pci,guest-cid=${VSOCK_CID} \
--pidfile ${QEMU_PIDFILE} \
"
readonly KERNEL_CMDLINE="\
virtme.dhcp net.ifnames=0 biosdevname=0 \
virtme.ssh virtme_ssh_channel=tcp virtme_ssh_user=$USER \
@@ -51,6 +48,8 @@ readonly TEST_DESCS=(
"Run vsock_test using the loopback transport in the VM."
)
readonly USE_SHARED_VM=(vm_server_host_client vm_client_host_server vm_loopback)
VERBOSE=0
usage() {
@@ -84,21 +83,33 @@ die() {
exit "${KSFT_FAIL}"
}
check_result() {
local rc arg
rc=$1
arg=$2
cnt_total=$(( cnt_total + 1 ))
if [[ ${rc} -eq ${KSFT_PASS} ]]; then
cnt_pass=$(( cnt_pass + 1 ))
echo "ok ${cnt_total} ${arg}"
elif [[ ${rc} -eq ${KSFT_SKIP} ]]; then
cnt_skip=$(( cnt_skip + 1 ))
echo "ok ${cnt_total} ${arg} # SKIP"
elif [[ ${rc} -eq ${KSFT_FAIL} ]]; then
cnt_fail=$(( cnt_fail + 1 ))
echo "not ok ${cnt_total} ${arg} # exit=${rc}"
fi
}
vm_ssh() {
ssh -q -o UserKnownHostsFile=/dev/null -p ${SSH_HOST_PORT} localhost "$@"
return $?
}
cleanup() {
if [[ -s "${QEMU_PIDFILE}" ]]; then
pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1
fi
# If failure occurred during or before qemu start up, then we need
# to clean this up ourselves.
if [[ -e "${QEMU_PIDFILE}" ]]; then
rm "${QEMU_PIDFILE}"
fi
terminate_pidfiles "${!PIDFILES[@]}"
}
check_args() {
@@ -147,7 +158,7 @@ check_vng() {
local version
local ok
tested_versions=("1.33" "1.36")
tested_versions=("1.33" "1.36" "1.37")
version="$(vng --version)"
ok=0
@@ -188,10 +199,37 @@ handle_build() {
popd &>/dev/null
}
create_pidfile() {
local pidfile
pidfile=$(mktemp "${PIDFILE_TEMPLATE}")
PIDFILES["${pidfile}"]=1
echo "${pidfile}"
}
terminate_pidfiles() {
local pidfile
for pidfile in "$@"; do
if [[ -s "${pidfile}" ]]; then
pkill -SIGTERM -F "${pidfile}" > /dev/null 2>&1
fi
if [[ -e "${pidfile}" ]]; then
rm -f "${pidfile}"
fi
unset "PIDFILES[${pidfile}]"
done
}
vm_start() {
local pidfile=$1
local logfile=/dev/null
local verbose_opt=""
local kernel_opt=""
local qemu_opts=""
local qemu
qemu=$(command -v "${QEMU}")
@@ -201,6 +239,13 @@ vm_start() {
logfile=/dev/stdout
fi
qemu_opts="\
-netdev user,id=n0,${QEMU_TEST_PORT_FWD},${QEMU_SSH_PORT_FWD} \
-device virtio-net-pci,netdev=n0 \
-device vhost-vsock-pci,guest-cid=${VSOCK_CID} \
--pidfile ${pidfile}
"
if [[ "${BUILD}" -eq 1 ]]; then
kernel_opt="${KERNEL_CHECKOUT}"
fi
@@ -209,16 +254,14 @@ vm_start() {
--run \
${kernel_opt} \
${verbose_opt} \
--qemu-opts="${QEMU_OPTS}" \
--qemu-opts="${qemu_opts}" \
--qemu="${qemu}" \
--user root \
--append "${KERNEL_CMDLINE}" \
--rw &> ${logfile} &
if ! timeout ${WAIT_TOTAL} \
bash -c 'while [[ ! -s '"${QEMU_PIDFILE}"' ]]; do sleep 1; done; exit 0'; then
die "failed to boot VM"
fi
timeout "${WAIT_QEMU}" \
bash -c 'while [[ ! -s '"${pidfile}"' ]]; do sleep 1; done; exit 0'
}
vm_wait_for_ssh() {
@@ -251,9 +294,11 @@ wait_for_listener()
# for tcp protocol additionally check the socket state
[ "${protocol}" = "tcp" ] && pattern="${pattern}0A"
for i in $(seq "${max_intervals}"); do
if awk '{print $2" "$4}' /proc/net/"${protocol}"* | \
grep -q "${pattern}"; then
if awk -v pattern="${pattern}" \
'BEGIN {rc=1} $2" "$4 ~ pattern {rc=0} END {exit rc}' \
/proc/net/"${protocol}"*; then
break
fi
sleep "${interval}"
@@ -270,113 +315,196 @@ EOF
}
host_wait_for_listener() {
wait_for_listener "${TEST_HOST_PORT_LISTENER}" "${WAIT_PERIOD}" "${WAIT_PERIOD_MAX}"
local port=$1
wait_for_listener "${port}" "${WAIT_PERIOD}" "${WAIT_PERIOD_MAX}"
}
__log_stdin() {
cat | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }'
vm_vsock_test() {
local host=$1
local cid=$2
local port=$3
local rc
# log output and use pipefail to respect vsock_test errors
set -o pipefail
if [[ "${host}" != server ]]; then
vm_ssh -- "${VSOCK_TEST}" \
--mode=client \
--control-host="${host}" \
--peer-cid="${cid}" \
--control-port="${port}" \
2>&1 | log_guest
rc=$?
else
vm_ssh -- "${VSOCK_TEST}" \
--mode=server \
--peer-cid="${cid}" \
--control-port="${port}" \
2>&1 | log_guest &
rc=$?
if [[ $rc -ne 0 ]]; then
set +o pipefail
return $rc
fi
vm_wait_for_listener "${port}"
rc=$?
fi
set +o pipefail
return $rc
}
__log_args() {
echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }'
host_vsock_test() {
local host=$1
local cid=$2
local port=$3
local rc
# log output and use pipefail to respect vsock_test errors
set -o pipefail
if [[ "${host}" != server ]]; then
${VSOCK_TEST} \
--mode=client \
--peer-cid="${cid}" \
--control-host="${host}" \
--control-port="${port}" 2>&1 | log_host
rc=$?
else
${VSOCK_TEST} \
--mode=server \
--peer-cid="${cid}" \
--control-port="${port}" 2>&1 | log_host &
rc=$?
if [[ $rc -ne 0 ]]; then
set +o pipefail
return $rc
fi
host_wait_for_listener "${port}"
rc=$?
fi
set +o pipefail
return $rc
}
log() {
local prefix="$1"
local redirect
local prefix
shift
local redirect=
if [[ ${VERBOSE} -eq 0 ]]; then
redirect=/dev/null
else
redirect=/dev/stdout
fi
if [[ "$#" -eq 0 ]]; then
__log_stdin | tee -a "${LOG}" > ${redirect}
else
__log_args "$@" | tee -a "${LOG}" > ${redirect}
fi
}
prefix="${LOG_PREFIX:-}"
log_setup() {
log "setup" "$@"
if [[ "$#" -eq 0 ]]; then
if [[ -n "${prefix}" ]]; then
awk -v prefix="${prefix}" '{printf "%s: %s\n", prefix, $0}'
else
cat
fi
else
if [[ -n "${prefix}" ]]; then
echo "${prefix}: " "$@"
else
echo "$@"
fi
fi | tee -a "${LOG}" > "${redirect}"
}
log_host() {
local testname=$1
shift
log "test:${testname}:host" "$@"
LOG_PREFIX=host log "$@"
}
log_guest() {
local testname=$1
shift
log "test:${testname}:guest" "$@"
LOG_PREFIX=guest log "$@"
}
test_vm_server_host_client() {
local testname="${FUNCNAME[0]#test_}"
if ! vm_vsock_test "server" 2 "${TEST_GUEST_PORT}"; then
return "${KSFT_FAIL}"
fi
vm_ssh -- "${VSOCK_TEST}" \
--mode=server \
--control-port="${TEST_GUEST_PORT}" \
--peer-cid=2 \
2>&1 | log_guest "${testname}" &
if ! host_vsock_test "127.0.0.1" "${VSOCK_CID}" "${TEST_HOST_PORT}"; then
return "${KSFT_FAIL}"
fi
vm_wait_for_listener "${TEST_GUEST_PORT}"
${VSOCK_TEST} \
--mode=client \
--control-host=127.0.0.1 \
--peer-cid="${VSOCK_CID}" \
--control-port="${TEST_HOST_PORT}" 2>&1 | log_host "${testname}"
return $?
return "${KSFT_PASS}"
}
test_vm_client_host_server() {
local testname="${FUNCNAME[0]#test_}"
if ! host_vsock_test "server" "${VSOCK_CID}" "${TEST_HOST_PORT_LISTENER}"; then
return "${KSFT_FAIL}"
fi
${VSOCK_TEST} \
--mode "server" \
--control-port "${TEST_HOST_PORT_LISTENER}" \
--peer-cid "${VSOCK_CID}" 2>&1 | log_host "${testname}" &
if ! vm_vsock_test "10.0.2.2" 2 "${TEST_HOST_PORT_LISTENER}"; then
return "${KSFT_FAIL}"
fi
host_wait_for_listener
vm_ssh -- "${VSOCK_TEST}" \
--mode=client \
--control-host=10.0.2.2 \
--peer-cid=2 \
--control-port="${TEST_HOST_PORT_LISTENER}" 2>&1 | log_guest "${testname}"
return $?
return "${KSFT_PASS}"
}
test_vm_loopback() {
local testname="${FUNCNAME[0]#test_}"
local port=60000 # non-forwarded local port
vm_ssh -- "${VSOCK_TEST}" \
--mode=server \
--control-port="${port}" \
--peer-cid=1 2>&1 | log_guest "${testname}" &
vm_ssh -- modprobe vsock_loopback &> /dev/null || :
vm_wait_for_listener "${port}"
if ! vm_vsock_test "server" 1 "${port}"; then
return "${KSFT_FAIL}"
fi
vm_ssh -- "${VSOCK_TEST}" \
--mode=client \
--control-host="127.0.0.1" \
--control-port="${port}" \
--peer-cid=1 2>&1 | log_guest "${testname}"
if ! vm_vsock_test "127.0.0.1" 1 "${port}"; then
return "${KSFT_FAIL}"
fi
return $?
return "${KSFT_PASS}"
}
run_test() {
shared_vm_test() {
local tname
tname="${1}"
for testname in "${USE_SHARED_VM[@]}"; do
if [[ "${tname}" == "${testname}" ]]; then
return 0
fi
done
return 1
}
shared_vm_tests_requested() {
for arg in "$@"; do
if shared_vm_test "${arg}"; then
return 0
fi
done
return 1
}
run_shared_vm_tests() {
local arg
for arg in "$@"; do
if ! shared_vm_test "${arg}"; then
continue
fi
run_shared_vm_test "${arg}"
check_result "$?" "${arg}"
done
}
run_shared_vm_test() {
local host_oops_cnt_before
local host_warn_cnt_before
local vm_oops_cnt_before
@@ -399,31 +527,32 @@ run_test() {
host_oops_cnt_after=$(dmesg | grep -i 'Oops' | wc -l)
if [[ ${host_oops_cnt_after} -gt ${host_oops_cnt_before} ]]; then
echo "FAIL: kernel oops detected on host" | log_host "${name}"
echo "FAIL: kernel oops detected on host" | log_host
rc=$KSFT_FAIL
fi
host_warn_cnt_after=$(dmesg --level=warn | grep -c -i 'vsock')
if [[ ${host_warn_cnt_after} -gt ${host_warn_cnt_before} ]]; then
echo "FAIL: kernel warning detected on host" | log_host "${name}"
echo "FAIL: kernel warning detected on host" | log_host
rc=$KSFT_FAIL
fi
vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l)
if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then
echo "FAIL: kernel oops detected on vm" | log_host "${name}"
echo "FAIL: kernel oops detected on vm" | log_host
rc=$KSFT_FAIL
fi
vm_warn_cnt_after=$(vm_ssh -- dmesg --level=warn | grep -c -i 'vsock')
if [[ ${vm_warn_cnt_after} -gt ${vm_warn_cnt_before} ]]; then
echo "FAIL: kernel warning detected on vm" | log_host "${name}"
echo "FAIL: kernel warning detected on vm" | log_host
rc=$KSFT_FAIL
fi
return "${rc}"
}
BUILD=0
QEMU="qemu-system-$(uname -m)"
while getopts :hvsq:b o
@@ -452,30 +581,21 @@ handle_build
echo "1..${#ARGS[@]}"
log_setup "Booting up VM"
vm_start
vm_wait_for_ssh
log_setup "VM booted up"
cnt_pass=0
cnt_fail=0
cnt_skip=0
cnt_total=0
for arg in "${ARGS[@]}"; do
run_test "${arg}"
rc=$?
if [[ ${rc} -eq $KSFT_PASS ]]; then
cnt_pass=$(( cnt_pass + 1 ))
echo "ok ${cnt_total} ${arg}"
elif [[ ${rc} -eq $KSFT_SKIP ]]; then
cnt_skip=$(( cnt_skip + 1 ))
echo "ok ${cnt_total} ${arg} # SKIP"
elif [[ ${rc} -eq $KSFT_FAIL ]]; then
cnt_fail=$(( cnt_fail + 1 ))
echo "not ok ${cnt_total} ${arg} # exit=$rc"
fi
cnt_total=$(( cnt_total + 1 ))
done
if shared_vm_tests_requested "${ARGS[@]}"; then
log_host "Booting up VM"
pidfile="$(create_pidfile)"
vm_start "${pidfile}"
vm_wait_for_ssh
log_host "VM booted up"
run_shared_vm_tests "${ARGS[@]}"
terminate_pidfiles "${pidfile}"
fi
echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}"
echo "Log: ${LOG}"