diff --git a/tools/testing/selftests/net/rds/README.txt b/tools/testing/selftests/net/rds/README.txt index c6fe003d503b..295dc82c0770 100644 --- a/tools/testing/selftests/net/rds/README.txt +++ b/tools/testing/selftests/net/rds/README.txt @@ -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/. diff --git a/tools/testing/selftests/net/rds/run.sh b/tools/testing/selftests/net/rds/run.sh index 897d17d1b8db..2404a889767a 100755 --- a/tools/testing/selftests/net/rds/run.sh +++ b/tools/testing/selftests/net/rds/run.sh @@ -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" diff --git a/tools/testing/selftests/net/rds/settings b/tools/testing/selftests/net/rds/settings index d2009a64589c..8cb41e6a83cc 100644 --- a/tools/testing/selftests/net/rds/settings +++ b/tools/testing/selftests/net/rds/settings @@ -1 +1 @@ -timeout=400 +timeout=800 diff --git a/tools/testing/selftests/net/rds/test.py b/tools/testing/selftests/net/rds/test.py index 93e23e8b256c..d19d30e5ec6f 100755 --- a/tools/testing/selftests/net/rds/test.py +++ b/tools/testing/selftests/net/rds/test.py @@ -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)