mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
Merge branch 'selftests-rds-log-collection-tap-compliance-and-cleanups'
Allison Henderson says: ==================== selftests: rds: Log collection, TAP compliance and cleanups This series is a set of bug fixes and improvements for the rds selftests. Patch 1 bumps the kselftest timeout from 400s to 800s. The original limit was developed against a lean config, but the kselftest harness counts boot time and gcov log collection against the limit, so a default config with gcov enabled needs more headroom. Patch 2 corrects some typos in the run.sh USAGE string and removes an unused "-g" flag. Patch 3 silences a handful of pylint warnings in test.py: it adds a module docstring, suppresses the warnings tied to the sys.path.append import trick, marks the long lived tcpdump Popen with disable-next consider-using-with, and drops unused exception variables from two BlockingIOError except clauses. Patch 4 adds a -t flag to run.sh so the timeout can be overridden if needed. Patch 5 adds a RDS_LOG_DIR environment variable that specifies where logs should be stored, or skips log collection if left unset Patch 6 adds a SUDO_USER environment variable that sets the user for tcpdump --relinquish-privileges. This avoid the permissions drop that would leave pcaps empty on 9pfs since 9p does not support chown Patch 7 removes the initial tmp tcpdumps and instead saves the pcaps directly to the logdir if it is set. Patch 8 hoists the tcpdump shutdown into a helper and calls it from the timeout signal handler so that the processes are properly terminated and dumps are flushed Patch 9 fixes gcov collection by ensuring debugfs is mounted, and specifying the --root folder so that gcov can still find the kernel source when it is run from the ksft test directory. Patch 10 makes the test output TAP compliant so the kselftest runner parses results correctly. ==================== Link: https://patch.msgid.link/20260504054143.4027538-1-achender@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -12,10 +12,12 @@ kernel may optionally be configured to omit the coverage report as well.
|
||||
|
||||
USAGE:
|
||||
run.sh [-d logdir] [-l packet_loss] [-c packet_corruption]
|
||||
[-u packet_duplcate]
|
||||
[-u packet_duplicate] [-t timeout]
|
||||
|
||||
OPTIONS:
|
||||
-d Log directory. Defaults to tools/testing/selftests/net/rds/rds_logs
|
||||
-d Log directory. If set, logs will be stored in the
|
||||
given dir, or skipped if unset. Log dir can also be
|
||||
set through the RDS_LOG_DIR env variable
|
||||
|
||||
-l Simulates a percentage of packet loss
|
||||
|
||||
@@ -23,6 +25,24 @@ OPTIONS:
|
||||
|
||||
-u Simulates a percentage of packet duplication.
|
||||
|
||||
-t Test timeout. Defaults to tools/testing/selftests/net/rds/settings
|
||||
|
||||
ENV VARIABLES:
|
||||
RDS_LOG_DIR Log directory. If set, logs will be stored in
|
||||
the given dir, or skipped if unset. Log dir
|
||||
can also be set with the -d flag.
|
||||
|
||||
Use with --rwdir on the CI path to retain logs after
|
||||
test compleation. Log dir end point must be within
|
||||
the specified --rwdir path for logs to persist on
|
||||
the host.
|
||||
|
||||
SUDO_USER The user name that should be used for tcpdump
|
||||
--relinquish-privileges. Set this to a user
|
||||
belonging to the sudoers group to avoid drop
|
||||
privilege errors with the vng 9p filesystem
|
||||
which may result in empty pcaps
|
||||
|
||||
EXAMPLE:
|
||||
|
||||
# Create a suitable gcov enabled .config
|
||||
@@ -39,6 +59,8 @@ EXAMPLE:
|
||||
|
||||
# launch the tests in a VM
|
||||
vng -v --rwdir ./ --run . --user root --cpus 4 -- \
|
||||
"export PYTHONPATH=tools/testing/selftests/net/; tools/testing/selftests/net/rds/run.sh"
|
||||
"export PYTHONPATH=tools/testing/selftests/net/; \
|
||||
export SUDO_USER=example_user; \
|
||||
export RDS_LOG_DIR=tools/testing/selftests/net/rds/rds_logs; \
|
||||
tools/testing/selftests/net/rds/run.sh"
|
||||
|
||||
An HTML coverage report will be output in tools/testing/selftests/net/rds/rds_logs/coverage/.
|
||||
|
||||
@@ -150,28 +150,30 @@ check_env()
|
||||
fi
|
||||
}
|
||||
|
||||
LOG_DIR="$current_dir"/rds_logs
|
||||
PLOSS=0
|
||||
PCORRUPT=0
|
||||
PDUP=0
|
||||
LOG_DIR="${RDS_LOG_DIR:-}"
|
||||
TIMEOUT=$timeout
|
||||
GENERATE_GCOV_REPORT=1
|
||||
while getopts "d:l:c:u:" opt; do
|
||||
FLAGS=()
|
||||
while getopts "d:l:c:u:t:" opt; do
|
||||
case ${opt} in
|
||||
d)
|
||||
LOG_DIR=${OPTARG}
|
||||
;;
|
||||
l)
|
||||
PLOSS=${OPTARG}
|
||||
FLAGS+=("-l" "${OPTARG}")
|
||||
;;
|
||||
c)
|
||||
PCORRUPT=${OPTARG}
|
||||
FLAGS+=("-c" "${OPTARG}")
|
||||
;;
|
||||
t)
|
||||
TIMEOUT=${OPTARG}
|
||||
;;
|
||||
u)
|
||||
PDUP=${OPTARG}
|
||||
FLAGS+=("-u" "${OPTARG}")
|
||||
;;
|
||||
:)
|
||||
echo "USAGE: run.sh [-d logdir] [-l packet_loss] [-c packet_corruption]" \
|
||||
"[-u packet_duplcate] [-g]"
|
||||
"[-u packet_duplicate] [-t timeout]"
|
||||
exit 1
|
||||
;;
|
||||
?)
|
||||
@@ -181,47 +183,63 @@ while getopts "d:l:c:u:" opt; do
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
check_env
|
||||
check_conf
|
||||
check_gcov_conf
|
||||
|
||||
TRACE_CMD=()
|
||||
if [[ -n "$LOG_DIR" ]]; then
|
||||
rm -fr "$LOG_DIR"
|
||||
FLAGS+=("-d" "$LOG_DIR")
|
||||
|
||||
rm -fr "$LOG_DIR"
|
||||
TRACE_FILE="${LOG_DIR}/rds-strace.txt"
|
||||
COVR_DIR="${LOG_DIR}/coverage/"
|
||||
mkdir -p "$LOG_DIR"
|
||||
mkdir -p "$COVR_DIR"
|
||||
TRACE_FILE="${LOG_DIR}/rds-strace.txt"
|
||||
COVR_DIR="${LOG_DIR}/coverage/"
|
||||
mkdir -p "$LOG_DIR"
|
||||
mkdir -p "$COVR_DIR"
|
||||
|
||||
echo "#Traces will be logged to ${TRACE_FILE}"
|
||||
rm -f "$TRACE_FILE"
|
||||
|
||||
TRACE_CMD=(strace -T -tt -o "${TRACE_FILE}")
|
||||
fi
|
||||
|
||||
set +e
|
||||
echo running RDS tests...
|
||||
echo Traces will be logged to "$TRACE_FILE"
|
||||
rm -f "$TRACE_FILE"
|
||||
strace -T -tt -o "$TRACE_FILE" python3 "$(dirname "$0")/test.py" \
|
||||
--timeout "$timeout" -d "$LOG_DIR" -l "$PLOSS" -c "$PCORRUPT" -u "$PDUP"
|
||||
echo "#running RDS tests..."
|
||||
"${TRACE_CMD[@]}" python3 "$(dirname "$0")/test.py" "${FLAGS[@]}" -t "$TIMEOUT"
|
||||
|
||||
test_rc=$?
|
||||
dmesg > "${LOG_DIR}/dmesg.out"
|
||||
|
||||
if [ "$GENERATE_GCOV_REPORT" -eq 1 ]; then
|
||||
echo saving coverage data...
|
||||
if [[ -n "$LOG_DIR" ]]; then
|
||||
dmesg > "${LOG_DIR}/dmesg.out"
|
||||
fi
|
||||
|
||||
if [[ -n "$LOG_DIR" ]] && [ "$GENERATE_GCOV_REPORT" -eq 1 ]; then
|
||||
echo "# saving coverage data..."
|
||||
|
||||
# Ensure debugfs is mounted before reading gcov data.
|
||||
if ! mountpoint -q /sys/kernel/debug 2>/dev/null; then
|
||||
mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true
|
||||
fi
|
||||
|
||||
(set +x; cd /sys/kernel/debug/gcov; find ./* -name '*.gcda' | \
|
||||
while read -r f
|
||||
do
|
||||
cat < "/sys/kernel/debug/gcov/$f" > "/$f"
|
||||
done)
|
||||
|
||||
echo running gcovr...
|
||||
echo "# running gcovr..."
|
||||
gcovr -s --html-details --gcov-executable "$GCOV_CMD" --gcov-ignore-parse-errors \
|
||||
-o "${COVR_DIR}/gcovr" "${ksrc_dir}/net/rds/"
|
||||
--root "${ksrc_dir}" -o "${COVR_DIR}/gcovr" "${ksrc_dir}/net/rds/" \
|
||||
> "${LOG_DIR}/gcovr.log" 2>&1
|
||||
echo "# gcovr log: ${LOG_DIR}/gcovr.log"
|
||||
else
|
||||
echo "Coverage report will be skipped"
|
||||
echo "# Coverage report will be skipped"
|
||||
fi
|
||||
|
||||
if [ "$test_rc" -eq 0 ]; then
|
||||
echo "PASS: Test completed successfully"
|
||||
echo "# PASS: Test completed successfully"
|
||||
else
|
||||
echo "FAIL: Test failed"
|
||||
echo "# FAIL: Test failed"
|
||||
fi
|
||||
|
||||
exit "$test_rc"
|
||||
|
||||
@@ -1 +1 @@
|
||||
timeout=400
|
||||
timeout=800
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#! /usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
"""
|
||||
This module provides functional testing for the net/rds component.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import ctypes
|
||||
@@ -11,13 +14,14 @@ import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
# Allow utils module to be imported from different directory
|
||||
this_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append(os.path.join(this_dir, "../"))
|
||||
from lib.py.utils import ip
|
||||
# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
|
||||
from lib.py.utils import ip # noqa: E402
|
||||
# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
|
||||
from lib.py.ksft import ksft_pr # noqa: E402
|
||||
|
||||
libc = ctypes.cdll.LoadLibrary('libc.so.6')
|
||||
setns = libc.setns
|
||||
@@ -57,7 +61,7 @@ def netns_socket(netns, *sock_args):
|
||||
# send resulting socket to parent
|
||||
socket.send_fds(u0, [], [sock.fileno()])
|
||||
|
||||
sys.exit(0)
|
||||
os._exit(0)
|
||||
|
||||
# receive socket from child
|
||||
_, fds, _, _ = socket.recv_fds(u1, 0, 1)
|
||||
@@ -66,11 +70,30 @@ def netns_socket(netns, *sock_args):
|
||||
u1.close()
|
||||
return socket.fromfd(fds[0], *sock_args)
|
||||
|
||||
def stop_pcaps():
|
||||
"""Stop tcpdump processes.
|
||||
|
||||
We use pop() here to drain the list in the event that the test
|
||||
completes after the signal handler is fired. List will be empty
|
||||
if logdir is not set
|
||||
"""
|
||||
ksft_pr("Stopping network packet captures")
|
||||
while tcpdump_procs:
|
||||
proc = tcpdump_procs.pop()
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
|
||||
def signal_handler(_sig, _frame):
|
||||
"""
|
||||
Test timed out signal handler
|
||||
"""
|
||||
print('Test timed out')
|
||||
ksft_pr("Test timed out")
|
||||
stop_pcaps()
|
||||
print("not ok 1 rds selftest")
|
||||
sys.exit(1)
|
||||
|
||||
#Parse out command line arguments. We take an optional
|
||||
@@ -78,8 +101,8 @@ def signal_handler(_sig, _frame):
|
||||
parser = argparse.ArgumentParser(description="init script args",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("-d", "--logdir", action="store",
|
||||
help="directory to store logs", default="/tmp")
|
||||
parser.add_argument('--timeout', help="timeout to terminate hung test",
|
||||
help="directory to store logs", default=None)
|
||||
parser.add_argument('-t', '--timeout', help="timeout to terminate hung test",
|
||||
type=int, default=0)
|
||||
parser.add_argument('-l', '--loss', help="Simulate tcp packet loss",
|
||||
type=int, default=0)
|
||||
@@ -124,15 +147,22 @@ ip(f"-n {NET1} route add {addrs[0][0]}/32 dev {VETH1}")
|
||||
# and communicating by doing a single ping
|
||||
ip(f"netns exec {NET0} ping -c 1 {addrs[1][0]}")
|
||||
|
||||
# Start a packet capture on each network
|
||||
tcpdump_procs = []
|
||||
for net in [NET0, NET1]:
|
||||
pcap = logdir+'/'+net+'.pcap'
|
||||
fd, pcap_tmp = tempfile.mkstemp(suffix=".pcap", prefix=f"{net}-", dir="/tmp")
|
||||
p = subprocess.Popen(
|
||||
['ip', 'netns', 'exec', net,
|
||||
'/usr/sbin/tcpdump', '-i', 'any', '-w', pcap_tmp])
|
||||
tcpdump_procs.append((p, pcap_tmp, pcap, fd))
|
||||
# Start a packet capture on each network
|
||||
if logdir is not None:
|
||||
for net in [NET0, NET1]:
|
||||
pcap = logdir+'/'+net+'.pcap'
|
||||
|
||||
tcpdump_cmd = ['ip', 'netns', 'exec', net, '/usr/sbin/tcpdump']
|
||||
sudo_user = os.environ.get('SUDO_USER')
|
||||
if sudo_user:
|
||||
tcpdump_cmd.extend(['-Z', sudo_user])
|
||||
tcpdump_cmd.extend(['-i', 'any', '-w', pcap])
|
||||
|
||||
# pylint: disable-next=consider-using-with
|
||||
p = subprocess.Popen(tcpdump_cmd,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
tcpdump_procs.append(p)
|
||||
|
||||
# simulate packet loss, duplication and corruption
|
||||
for net, iface in [(NET0, VETH0), (NET1, VETH1)]:
|
||||
@@ -140,6 +170,9 @@ for net, iface in [(NET0, VETH0), (NET1, VETH1)]:
|
||||
corrupt {PACKET_CORRUPTION} loss {PACKET_LOSS} duplicate \
|
||||
{PACKET_DUPLICATE}")
|
||||
|
||||
print("TAP version 13")
|
||||
print("1..1")
|
||||
|
||||
# add a timeout
|
||||
if args.timeout > 0:
|
||||
signal.alarm(args.timeout)
|
||||
@@ -178,7 +211,7 @@ nr_recv = 0
|
||||
|
||||
while nr_send < NUM_PACKETS:
|
||||
# Send as much as we can without blocking
|
||||
print("sending...", nr_send, nr_recv)
|
||||
ksft_pr("sending...", nr_send, nr_recv)
|
||||
while nr_send < NUM_PACKETS:
|
||||
send_data = hashlib.sha256(
|
||||
f'packet {nr_send}'.encode('utf-8')).hexdigest().encode('utf-8')
|
||||
@@ -192,7 +225,7 @@ while nr_send < NUM_PACKETS:
|
||||
send_hashes.setdefault((sender.fileno(), receiver.fileno()),
|
||||
hashlib.sha256()).update(f'<{send_data}>'.encode('utf-8'))
|
||||
nr_send = nr_send + 1
|
||||
except BlockingIOError as e:
|
||||
except BlockingIOError:
|
||||
break
|
||||
except OSError as e:
|
||||
if e.errno in [errno.ENOBUFS, errno.ECONNRESET, errno.EPIPE]:
|
||||
@@ -200,7 +233,7 @@ while nr_send < NUM_PACKETS:
|
||||
raise
|
||||
|
||||
# Receive as much as we can without blocking
|
||||
print("receiving...", nr_send, nr_recv)
|
||||
ksft_pr("receiving...", nr_send, nr_recv)
|
||||
while nr_recv < nr_send:
|
||||
for fileno, eventmask in ep.poll():
|
||||
receiver = fileno_to_socket[fileno]
|
||||
@@ -214,7 +247,7 @@ while nr_send < NUM_PACKETS:
|
||||
receiver.fileno()), hashlib.sha256()).update(
|
||||
f'<{recv_data}>'.encode('utf-8'))
|
||||
nr_recv = nr_recv + 1
|
||||
except BlockingIOError as e:
|
||||
except BlockingIOError:
|
||||
break
|
||||
|
||||
# exercise net/rds/tcp.c:rds_tcp_sysctl_reset()
|
||||
@@ -222,7 +255,7 @@ while nr_send < NUM_PACKETS:
|
||||
ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_rcvbuf=10000")
|
||||
ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_sndbuf=10000")
|
||||
|
||||
print("done", nr_send, nr_recv)
|
||||
ksft_pr("done", nr_send, nr_recv)
|
||||
|
||||
# the Python socket module doesn't know these
|
||||
RDS_INFO_FIRST = 10000
|
||||
@@ -245,31 +278,34 @@ for s in sockets:
|
||||
# ignore
|
||||
pass
|
||||
|
||||
print(f"getsockopt(): {nr_success}/{nr_error}")
|
||||
|
||||
print("Stopping network packet captures")
|
||||
for p, pcap_tmp, pcap, fd in tcpdump_procs:
|
||||
p.terminate()
|
||||
p.wait()
|
||||
os.close(fd)
|
||||
shutil.move(pcap_tmp, pcap)
|
||||
ksft_pr(f"getsockopt(): {nr_success}/{nr_error}")
|
||||
stop_pcaps()
|
||||
|
||||
# We're done sending and receiving stuff, now let's check if what
|
||||
# we received is what we sent.
|
||||
ret = 0
|
||||
for (sender, receiver), send_hash in send_hashes.items():
|
||||
recv_hash = recv_hashes.get((sender, receiver))
|
||||
|
||||
if recv_hash is None:
|
||||
print("FAIL: No data received")
|
||||
sys.exit(1)
|
||||
ksft_pr("FAIL: No data received")
|
||||
ret = 1
|
||||
break
|
||||
|
||||
if send_hash.hexdigest() != recv_hash.hexdigest():
|
||||
print("FAIL: Send/recv mismatch")
|
||||
print("hash expected:", send_hash.hexdigest())
|
||||
print("hash received:", recv_hash.hexdigest())
|
||||
sys.exit(1)
|
||||
ksft_pr("FAIL: Send/recv mismatch")
|
||||
ksft_pr("hash expected:", send_hash.hexdigest())
|
||||
ksft_pr("hash received:", recv_hash.hexdigest())
|
||||
ret = 1
|
||||
break
|
||||
|
||||
print(f"{sender}/{receiver}: ok")
|
||||
ksft_pr(f"{sender}/{receiver}: ok")
|
||||
|
||||
print("Success")
|
||||
sys.exit(0)
|
||||
if ret == 0:
|
||||
ksft_pr("Success")
|
||||
print("ok 1 rds selftest")
|
||||
else:
|
||||
print("not ok 1 rds selftest")
|
||||
|
||||
ksft_pr(f"Totals: pass:{1-ret} fail:{ret} skip:0")
|
||||
sys.exit(ret)
|
||||
|
||||
Reference in New Issue
Block a user