Re: [PATCH net-next 6/7] selftests: drivers: add scaffolding for Netlink tests in Python

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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, ))




[Index of Archives]     [Linux Wireless]     [Linux Kernel]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Share Photos]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]

  Powered by Linux