mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 20:54:03 +02:00
selftests: drv-net: add a way to wait for a local process
We use wait_port_listen() extensively to wait for a process we spawned to be ready. Not all processes will open listening sockets. Add a method of explicitly waiting for a child to be ready. Pass a FD to the spawned process and wait for it to write a message to us. FD number is passed via KSFT_READY_FD env variable. Similarly use KSFT_WAIT_FD to let the child process for a sign that we are done and child should exit. Sending a signal to a child with shell=True can get tricky. Make use of this method in the queues test to make it less flaky. Acked-by: Stanislav Fomichev <sdf@fomichev.me> Acked-by: Joe Damato <jdamato@fastly.com> Tested-by: Joe Damato <jdamato@fastly.com> Link: https://patch.msgid.link/20250219234956.520599-6-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
d3726ab45c
commit
7147713799
|
|
@ -5,13 +5,12 @@ from lib.py import ksft_disruptive, ksft_exit, ksft_run
|
||||||
from lib.py import ksft_eq, ksft_raises, KsftSkipEx, KsftFailEx
|
from lib.py import ksft_eq, ksft_raises, KsftSkipEx, KsftFailEx
|
||||||
from lib.py import EthtoolFamily, NetdevFamily, NlError
|
from lib.py import EthtoolFamily, NetdevFamily, NlError
|
||||||
from lib.py import NetDrvEnv
|
from lib.py import NetDrvEnv
|
||||||
from lib.py import cmd, defer, ip
|
from lib.py import bkg, cmd, defer, ip
|
||||||
import errno
|
import errno
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def sys_get_queues(ifname, qtype='rx') -> int:
|
def sys_get_queues(ifname, qtype='rx') -> int:
|
||||||
folders = glob.glob(f'/sys/class/net/{ifname}/queues/{qtype}-*')
|
folders = glob.glob(f'/sys/class/net/{ifname}/queues/{qtype}-*')
|
||||||
|
|
@ -32,32 +31,30 @@ def check_xdp(cfg, nl, xdp_queue_id=0) -> None:
|
||||||
elif xdp.ret > 0:
|
elif xdp.ret > 0:
|
||||||
raise KsftFailEx('unable to create AF_XDP socket')
|
raise KsftFailEx('unable to create AF_XDP socket')
|
||||||
|
|
||||||
xdp = subprocess.Popen([cfg.rpath("xdp_helper"), f"{cfg.ifindex}", f"{xdp_queue_id}"],
|
with bkg(f'{cfg.rpath("xdp_helper")} {cfg.ifindex} {xdp_queue_id}',
|
||||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=1,
|
ksft_wait=3):
|
||||||
text=True)
|
|
||||||
defer(xdp.kill)
|
|
||||||
|
|
||||||
stdout, stderr = xdp.communicate(timeout=10)
|
rx = tx = False
|
||||||
rx = tx = False
|
|
||||||
|
|
||||||
queues = nl.queue_get({'ifindex': cfg.ifindex}, dump=True)
|
queues = nl.queue_get({'ifindex': cfg.ifindex}, dump=True)
|
||||||
if not queues:
|
if not queues:
|
||||||
raise KsftSkipEx("Netlink reports no queues")
|
raise KsftSkipEx("Netlink reports no queues")
|
||||||
|
|
||||||
for q in queues:
|
for q in queues:
|
||||||
if q['id'] == 0:
|
if q['id'] == 0:
|
||||||
if q['type'] == 'rx':
|
if q['type'] == 'rx':
|
||||||
rx = True
|
rx = True
|
||||||
if q['type'] == 'tx':
|
if q['type'] == 'tx':
|
||||||
tx = True
|
tx = True
|
||||||
|
|
||||||
ksft_eq(q['xsk'], {})
|
ksft_eq(q['xsk'], {})
|
||||||
else:
|
else:
|
||||||
if 'xsk' in q:
|
if 'xsk' in q:
|
||||||
_fail("Check failed: xsk attribute set.")
|
_fail("Check failed: xsk attribute set.")
|
||||||
|
|
||||||
|
ksft_eq(rx, True)
|
||||||
|
ksft_eq(tx, True)
|
||||||
|
|
||||||
ksft_eq(rx, True)
|
|
||||||
ksft_eq(tx, True)
|
|
||||||
|
|
||||||
def get_queues(cfg, nl) -> None:
|
def get_queues(cfg, nl) -> None:
|
||||||
snl = NetdevFamily(recv_size=4096)
|
snl = NetdevFamily(recv_size=4096)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,54 @@
|
||||||
#define UMEM_SZ (1U << 16)
|
#define UMEM_SZ (1U << 16)
|
||||||
#define NUM_DESC (UMEM_SZ / 2048)
|
#define NUM_DESC (UMEM_SZ / 2048)
|
||||||
|
|
||||||
|
/* Move this to a common header when reused! */
|
||||||
|
static void ksft_ready(void)
|
||||||
|
{
|
||||||
|
const char msg[7] = "ready\n";
|
||||||
|
char *env_str;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
env_str = getenv("KSFT_READY_FD");
|
||||||
|
if (env_str) {
|
||||||
|
fd = atoi(env_str);
|
||||||
|
if (!fd) {
|
||||||
|
fprintf(stderr, "invalid KSFT_READY_FD = '%s'\n",
|
||||||
|
env_str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fd = STDOUT_FILENO;
|
||||||
|
}
|
||||||
|
|
||||||
|
write(fd, msg, sizeof(msg));
|
||||||
|
if (fd != STDOUT_FILENO)
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ksft_wait(void)
|
||||||
|
{
|
||||||
|
char *env_str;
|
||||||
|
char byte;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
env_str = getenv("KSFT_WAIT_FD");
|
||||||
|
if (env_str) {
|
||||||
|
fd = atoi(env_str);
|
||||||
|
if (!fd) {
|
||||||
|
fprintf(stderr, "invalid KSFT_WAIT_FD = '%s'\n",
|
||||||
|
env_str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Not running in KSFT env, wait for input from STDIN instead */
|
||||||
|
fd = STDIN_FILENO;
|
||||||
|
}
|
||||||
|
|
||||||
|
read(fd, &byte, sizeof(byte));
|
||||||
|
if (fd != STDIN_FILENO)
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
/* this is a simple helper program that creates an XDP socket and does the
|
/* this is a simple helper program that creates an XDP socket and does the
|
||||||
* minimum necessary to get bind() to succeed.
|
* minimum necessary to get bind() to succeed.
|
||||||
*
|
*
|
||||||
|
|
@ -32,7 +80,6 @@ int main(int argc, char **argv)
|
||||||
int ifindex;
|
int ifindex;
|
||||||
int sock_fd;
|
int sock_fd;
|
||||||
int queue;
|
int queue;
|
||||||
char byte;
|
|
||||||
|
|
||||||
if (argc != 3) {
|
if (argc != 3) {
|
||||||
fprintf(stderr, "Usage: %s ifindex queue_id\n", argv[0]);
|
fprintf(stderr, "Usage: %s ifindex queue_id\n", argv[0]);
|
||||||
|
|
@ -92,13 +139,12 @@ int main(int argc, char **argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* give the parent program some data when the socket is ready*/
|
ksft_ready();
|
||||||
fprintf(stdout, "%d\n", sock_fd);
|
ksft_wait();
|
||||||
|
|
||||||
/* parent program will write a byte to stdin when its ready for this
|
/* parent program will write a byte to stdin when its ready for this
|
||||||
* helper to exit
|
* helper to exit
|
||||||
*/
|
*/
|
||||||
read(STDIN_FILENO, &byte, 1);
|
|
||||||
|
|
||||||
close(sock_fd);
|
close(sock_fd);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import json as _json
|
import json as _json
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
import select
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
@ -15,21 +17,56 @@ class CmdExitFailure(Exception):
|
||||||
self.cmd = cmd_obj
|
self.cmd = cmd_obj
|
||||||
|
|
||||||
|
|
||||||
|
def fd_read_timeout(fd, timeout):
|
||||||
|
rlist, _, _ = select.select([fd], [], [], timeout)
|
||||||
|
if rlist:
|
||||||
|
return os.read(fd, 1024)
|
||||||
|
else:
|
||||||
|
raise TimeoutError("Timeout waiting for fd read")
|
||||||
|
|
||||||
|
|
||||||
class cmd:
|
class cmd:
|
||||||
def __init__(self, comm, shell=True, fail=True, ns=None, background=False, host=None, timeout=5):
|
"""
|
||||||
|
Execute a command on local or remote host.
|
||||||
|
|
||||||
|
Use bkg() instead to run a command in the background.
|
||||||
|
"""
|
||||||
|
def __init__(self, comm, shell=True, fail=True, ns=None, background=False,
|
||||||
|
host=None, timeout=5, ksft_wait=None):
|
||||||
if ns:
|
if ns:
|
||||||
comm = f'ip netns exec {ns} ' + comm
|
comm = f'ip netns exec {ns} ' + comm
|
||||||
|
|
||||||
self.stdout = None
|
self.stdout = None
|
||||||
self.stderr = None
|
self.stderr = None
|
||||||
self.ret = None
|
self.ret = None
|
||||||
|
self.ksft_term_fd = None
|
||||||
|
|
||||||
self.comm = comm
|
self.comm = comm
|
||||||
if host:
|
if host:
|
||||||
self.proc = host.cmd(comm)
|
self.proc = host.cmd(comm)
|
||||||
else:
|
else:
|
||||||
|
# ksft_wait lets us wait for the background process to fully start,
|
||||||
|
# we pass an FD to the child process, and wait for it to write back.
|
||||||
|
# Similarly term_fd tells child it's time to exit.
|
||||||
|
pass_fds = ()
|
||||||
|
env = os.environ.copy()
|
||||||
|
if ksft_wait is not None:
|
||||||
|
rfd, ready_fd = os.pipe()
|
||||||
|
wait_fd, self.ksft_term_fd = os.pipe()
|
||||||
|
pass_fds = (ready_fd, wait_fd, )
|
||||||
|
env["KSFT_READY_FD"] = str(ready_fd)
|
||||||
|
env["KSFT_WAIT_FD"] = str(wait_fd)
|
||||||
|
|
||||||
self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE,
|
self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE, pass_fds=pass_fds,
|
||||||
|
env=env)
|
||||||
|
if ksft_wait is not None:
|
||||||
|
os.close(ready_fd)
|
||||||
|
os.close(wait_fd)
|
||||||
|
msg = fd_read_timeout(rfd, ksft_wait)
|
||||||
|
os.close(rfd)
|
||||||
|
if not msg:
|
||||||
|
raise Exception("Did not receive ready message")
|
||||||
if not background:
|
if not background:
|
||||||
self.process(terminate=False, fail=fail, timeout=timeout)
|
self.process(terminate=False, fail=fail, timeout=timeout)
|
||||||
|
|
||||||
|
|
@ -37,6 +74,8 @@ class cmd:
|
||||||
if fail is None:
|
if fail is None:
|
||||||
fail = not terminate
|
fail = not terminate
|
||||||
|
|
||||||
|
if self.ksft_term_fd:
|
||||||
|
os.write(self.ksft_term_fd, b"1")
|
||||||
if terminate:
|
if terminate:
|
||||||
self.proc.terminate()
|
self.proc.terminate()
|
||||||
stdout, stderr = self.proc.communicate(timeout)
|
stdout, stderr = self.proc.communicate(timeout)
|
||||||
|
|
@ -54,11 +93,30 @@ class cmd:
|
||||||
|
|
||||||
|
|
||||||
class bkg(cmd):
|
class bkg(cmd):
|
||||||
|
"""
|
||||||
|
Run a command in the background.
|
||||||
|
|
||||||
|
Examples usage:
|
||||||
|
|
||||||
|
Run a command on remote host, and wait for it to finish.
|
||||||
|
This is usually paired with wait_port_listen() to make sure
|
||||||
|
the command has initialized:
|
||||||
|
|
||||||
|
with bkg("socat ...", exit_wait=True, host=cfg.remote) as nc:
|
||||||
|
...
|
||||||
|
|
||||||
|
Run a command and expect it to let us know that it's ready
|
||||||
|
by writing to a special file descriptor passed via KSFT_READY_FD.
|
||||||
|
Command will be terminated when we exit the context manager:
|
||||||
|
|
||||||
|
with bkg("my_binary", ksft_wait=5):
|
||||||
|
"""
|
||||||
def __init__(self, comm, shell=True, fail=None, ns=None, host=None,
|
def __init__(self, comm, shell=True, fail=None, ns=None, host=None,
|
||||||
exit_wait=False):
|
exit_wait=False, ksft_wait=None):
|
||||||
super().__init__(comm, background=True,
|
super().__init__(comm, background=True,
|
||||||
shell=shell, fail=fail, ns=ns, host=host)
|
shell=shell, fail=fail, ns=ns, host=host,
|
||||||
self.terminate = not exit_wait
|
ksft_wait=ksft_wait)
|
||||||
|
self.terminate = not exit_wait and not ksft_wait
|
||||||
self.check_fail = fail
|
self.check_fail = fail
|
||||||
|
|
||||||
if shell and self.terminate:
|
if shell and self.terminate:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user