public inbox for linux-kselftest@vger.kernel.org
 help / color / mirror / Atom feed
From: David Wei <dw@davidwei.uk>
To: Jakub Kicinski <kuba@kernel.org>, davem@davemloft.net
Cc: netdev@vger.kernel.org, edumazet@google.com, pabeni@redhat.com,
	shuah@kernel.org, sdf@google.com, donald.hunter@gmail.com,
	linux-kselftest@vger.kernel.org, petrm@nvidia.com
Subject: Re: [PATCH net-next 6/7] selftests: drivers: add scaffolding for Netlink tests in Python
Date: Tue, 2 Apr 2024 20:06:34 -0700	[thread overview]
Message-ID: <b39992a9-ebb5-40e1-a00a-ea6019346115@davidwei.uk> (raw)
In-Reply-To: <20240402010520.1209517-7-kuba@kernel.org>

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@kernel.org>
> ---
>  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, ))

  reply	other threads:[~2024-04-03  3:06 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-02  1:05 [PATCH net-next 0/7] selftests: net: groundwork for YNL-based tests Jakub Kicinski
2024-04-02  1:05 ` [PATCH net-next 1/7] netlink: specs: define ethtool header flags Jakub Kicinski
2024-04-02  1:05 ` [PATCH net-next 2/7] tools: ynl: copy netlink error to NlError Jakub Kicinski
2024-04-02  1:05 ` [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python Jakub Kicinski
2024-04-02 15:53   ` Petr Machata
2024-04-02 17:26     ` Jakub Kicinski
2024-04-03  0:09   ` David Wei
2024-04-02  1:05 ` [PATCH net-next 4/7] selftests: nl_netdev: add a trivial Netlink netdev test Jakub Kicinski
2024-04-03  0:15   ` David Wei
2024-04-02  1:05 ` [PATCH net-next 5/7] netdevsim: report stats by default, like a real device Jakub Kicinski
2024-04-03  2:51   ` David Wei
2024-04-02  1:05 ` [PATCH net-next 6/7] selftests: drivers: add scaffolding for Netlink tests in Python Jakub Kicinski
2024-04-03  3:06   ` David Wei [this message]
2024-04-02  1:05 ` [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting Jakub Kicinski
2024-04-02 16:37   ` Petr Machata
2024-04-02 17:31     ` Jakub Kicinski
2024-04-02 17:44       ` Jakub Kicinski
2024-04-02 22:02         ` Petr Machata
2024-04-02 22:04       ` Petr Machata
2024-04-02 23:36         ` Jakub Kicinski
2024-04-03  8:58           ` Petr Machata
2024-04-03 13:55             ` Jakub Kicinski
2024-04-03 16:52               ` Petr Machata
2024-04-03 21:48                 ` Jakub Kicinski
2024-04-03  3:15       ` David Wei
2024-04-03  3:09   ` David Wei

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=b39992a9-ebb5-40e1-a00a-ea6019346115@davidwei.uk \
    --to=dw@davidwei.uk \
    --cc=davem@davemloft.net \
    --cc=donald.hunter@gmail.com \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=petrm@nvidia.com \
    --cc=sdf@google.com \
    --cc=shuah@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox