On 2024-04-01 18:05, Jakub Kicinski wrote: > Add drivers/net as a target for mixed-use tests. > The setup is expected to work similarly to the forwarding tests. > Since we only need one interface (unlike forwarding tests) > read the target device name from NETIF. If not present we'll > try to run the test against netdevsim. > > Signed-off-by: Jakub Kicinski <kuba@xxxxxxxxxx> > --- > tools/testing/selftests/Makefile | 3 +- > tools/testing/selftests/drivers/net/Makefile | 7 ++ > .../testing/selftests/drivers/net/README.rst | 30 +++++ > .../selftests/drivers/net/lib/py/__init__.py | 17 +++ > .../selftests/drivers/net/lib/py/env.py | 41 ++++++ > .../testing/selftests/net/lib/py/__init__.py | 1 + > tools/testing/selftests/net/lib/py/nsim.py | 118 ++++++++++++++++++ > 7 files changed, 216 insertions(+), 1 deletion(-) > create mode 100644 tools/testing/selftests/drivers/net/Makefile > create mode 100644 tools/testing/selftests/drivers/net/README.rst > create mode 100644 tools/testing/selftests/drivers/net/lib/py/__init__.py > create mode 100644 tools/testing/selftests/drivers/net/lib/py/env.py > create mode 100644 tools/testing/selftests/net/lib/py/nsim.py > > diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile > index 0cffdfb4b116..d015ec14a85e 100644 > --- a/tools/testing/selftests/Makefile > +++ b/tools/testing/selftests/Makefile > @@ -17,6 +17,7 @@ TARGETS += devices > TARGETS += dmabuf-heaps > TARGETS += drivers/dma-buf > TARGETS += drivers/s390x/uvdevice > +TARGETS += drivers/net > TARGETS += drivers/net/bonding > TARGETS += drivers/net/team > TARGETS += dt > @@ -117,7 +118,7 @@ TARGETS_HOTPLUG = cpu-hotplug > TARGETS_HOTPLUG += memory-hotplug > > # Networking tests want the net/lib target, include it automatically > -ifneq ($(filter net ,$(TARGETS)),) > +ifneq ($(filter net drivers/net,$(TARGETS)),) > ifeq ($(filter net/lib,$(TARGETS)),) > override TARGETS := $(TARGETS) net/lib > endif > diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile > new file mode 100644 > index 000000000000..379cdb1960a7 > --- /dev/null > +++ b/tools/testing/selftests/drivers/net/Makefile > @@ -0,0 +1,7 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +TEST_INCLUDES := $(wildcard lib/py/*.py) > + > +TEST_PROGS := stats.py > + > +include ../../lib.mk > diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst > new file mode 100644 > index 000000000000..5ef7c417d431 > --- /dev/null > +++ b/tools/testing/selftests/drivers/net/README.rst > @@ -0,0 +1,30 @@ > +Running tests > +============= > + > +Tests are executed within kselftest framework like any other tests. > +By default tests execute against software drivers such as netdevsim. > +All tests must support running against a real device (SW-only tests > +should instead be placed in net/ or drivers/net/netdevsim, HW-only > +tests in drivers/net/hw). > + > +Set appropriate variables to point the tests at a real device. > + > +Variables > +========= > + > +Variables can be set in the environment or by creating a net.config > +file in the same directory as this README file. Example:: > + > + $ NETIF=eth0 ./some_test.sh > + > +or:: > + > + $ cat tools/testing/selftests/drivers/net/net.config > + # Variable set in a file > + NETIF=eth0 > + > +NETIF > +~~~~~ > + > +Name of the netdevice against which the test should be executed. > +When empty or not set software devices will be used. > diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py > new file mode 100644 > index 000000000000..4653dffcd962 > --- /dev/null > +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py > @@ -0,0 +1,17 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +import sys > +from pathlib import Path > + > +KSFT_DIR = (Path(__file__).parent / "../../../..").resolve() > + > +try: > + sys.path.append(KSFT_DIR.as_posix()) > + from net.lib.py import * > +except ModuleNotFoundError as e: > + ksft_pr("Failed importing `net` library from kernel sources") > + ksft_pr(str(e)) > + ktap_result(True, comment="SKIP") > + sys.exit(4) > + > +from .env import * > diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py > new file mode 100644 > index 000000000000..ee4a44555d83 > --- /dev/null > +++ b/tools/testing/selftests/drivers/net/lib/py/env.py > @@ -0,0 +1,41 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +import os > +import shlex > +from pathlib import Path > +from lib.py import ip > +from lib.py import NetdevSimDev nit: these could be on the same line. > + > +class NetDrvEnv: > + def __init__(self, src_path): > + self.env = os.environ.copy() > + self._load_env_file(src_path) > + > + if 'NETIF' in self.env: > + self._ns = None My brain interprets 'ns' as 'namespace'. How about something like nsimdev/nsdev/nsim? > + self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0] > + else: > + self._ns = NetdevSimDev() > + self.dev = self._ns.nsims[0].dev > + self.ifindex = self.dev['ifindex'] > + > + def __del__(self): > + if self._ns: > + self._ns.remove() > + > + def _load_env_file(self, src_path): > + src_dir = Path(src_path).parent.resolve() > + if not (src_dir / "net.config").exists(): > + return > + > + lexer = shlex.shlex(open((src_dir / "net.config").as_posix(), 'r').read()) > + k = None > + for token in lexer: > + if k is None: > + k = token > + self.env[k] = "" > + elif token == "=": > + pass > + else: > + self.env[k] = token > + k = None > diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py > index 81a8d14b68f0..99cfc8dc4dca 100644 > --- a/tools/testing/selftests/net/lib/py/__init__.py > +++ b/tools/testing/selftests/net/lib/py/__init__.py > @@ -3,4 +3,5 @@ > from .ksft import * > from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily > from .consts import KSRC > +from .nsim import * > from .utils import * > diff --git a/tools/testing/selftests/net/lib/py/nsim.py b/tools/testing/selftests/net/lib/py/nsim.py > new file mode 100644 > index 000000000000..13eb42c82829 > --- /dev/null > +++ b/tools/testing/selftests/net/lib/py/nsim.py > @@ -0,0 +1,118 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +import json > +import os > +import random > +import re > +import time > +from .utils import cmd, ip > + > + > +class NetdevSim: > + """ > + Class for netdevsim netdevice and its attributes. > + """ > + > + def __init__(self, nsimdev, port_index, ifname, ns=None): > + # In case udev renamed the netdev to according to new schema, > + # check if the name matches the port_index. > + nsimnamere = re.compile("eni\d+np(\d+)") > + match = nsimnamere.match(ifname) > + if match and int(match.groups()[0]) != port_index + 1: > + raise Exception("netdevice name mismatches the expected one") > + > + self.ifname = ifname > + self.nsimdev = nsimdev > + self.port_index = port_index > + self.ns = ns > + self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index) > + ret = ip("-j link show dev %s" % ifname, ns=ns) > + self.dev = json.loads(ret.stdout)[0] > + > + def dfs_write(self, path, val): > + self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val) > + > + > +class NetdevSimDev: > + """ > + Class for netdevsim bus device and its attributes. > + """ > + @staticmethod > + def ctrl_write(path, val): > + fullpath = os.path.join("/sys/bus/netdevsim/", path) > + with open(fullpath, "w") as f: > + f.write(val) > + > + def dfs_write(self, path, val): > + fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path) > + with open(fullpath, "w") as f: > + f.write(val) > + > + def __init__(self, port_count=1, ns=None): > + # nsim will spawn in init_net, we'll set to actual ns once we switch it the.sre > + self.ns = None > + > + if not os.path.exists("/sys/bus/netdevsim"): > + cmd("modprobe netdevsim") > + > + addr = random.randrange(1 << 15) > + while True: > + try: > + self.ctrl_write("new_device", "%u %u" % (addr, port_count)) > + except OSError as e: > + if e.errno == errno.ENOSPC: > + addr = random.randrange(1 << 15) > + continue > + raise e > + break > + self.addr = addr > + > + # As probe of netdevsim device might happen from a workqueue, > + # so wait here until all netdevs appear. > + self.wait_for_netdevs(port_count) > + > + if ns: > + cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}") > + self.ns = ns > + > + cmd("udevadm settle", ns=self.ns) > + ifnames = self.get_ifnames() > + > + self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr > + > + self.nsims = [] > + for port_index in range(port_count): > + self.nsims.append(NetdevSim(self, port_index, ifnames[port_index], > + ns=ns)) > + > + def get_ifnames(self): > + ifnames = [] > + listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/", > + ns=self.ns).stdout.split() > + for ifname in listdir: > + ifnames.append(ifname) > + ifnames.sort() > + return ifnames > + > + def wait_for_netdevs(self, port_count): > + timeout = 5 > + timeout_start = time.time() > + > + while True: > + try: > + ifnames = self.get_ifnames() > + except FileNotFoundError as e: > + ifnames = [] > + if len(ifnames) == port_count: > + break > + if time.time() < timeout_start + timeout: > + continue > + raise Exception("netdevices did not appear within timeout") > + > + def remove(self): > + self.ctrl_write("del_device", "%u" % (self.addr, )) I really want this to be in the dtor, but couldn't get it to work because Python doesn't let you open files in __del__(). :( > + > + def remove_nsim(self, nsim): > + self.nsims.remove(nsim) > + self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ), > + "%u" % (nsim.port_index, ))