mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-01 10:42:11 +00:00
Merge branch 'ynl-tests'
Jakub Kicinski says: ==================== selftests: net: groundwork for YNL-based tests Currently the options for writing networking tests are C, bash or some mix of the two. YAML/Netlink gives us the ability to easily interface with Netlink in higher level laguages. In particular, there is a Python library already available in tree, under tools/net. Add the scaffolding which allows writing tests using this library. The "scaffolding" is needed because the library lives under tools/net and uses YAML files from under Documentation/. So we need a small amount of glue code to find those things and add them to TEST_FILES. This series adds both a basic SW sanity test and driver test which can be run against netdevsim or a real device. When I develop core code I usually test with netdevsim, then a real device, and then a backport to Meta's kernel. Because of the lack of integration, until now I had to throw away the (YNL-based) test script and netdevsim code. Running tests in tree directly: $ ./tools/testing/selftests/net/nl_netdev.py KTAP version 1 1..2 ok 1 nl_netdev.empty_check ok 2 nl_netdev.lo_check # Totals: pass:2 fail:0 xfail:0 xpass:0 skip:0 error:0 in tree via make: $ make -C tools/testing/selftests/ TARGETS=net \ TEST_PROGS=nl_netdev.py TEST_GEN_PROGS="" run_tests [ ... ] and installed externally, all seem to work: $ make -C tools/testing/selftests/ TARGETS=net \ install INSTALL_PATH=/tmp/ksft-net $ /tmp/ksft-net/run_kselftest.sh -t net:nl_netdev.py [ ... ] For driver tests I followed the lead of net/forwarding and get the device name from env and/or a config file. v3: - fix up netdevsim C - various small nits in other patches (see changelog in patches) v2: https://lore.kernel.org/all/20240403023426.1762996-1-kuba@kernel.org/ - don't add to TARGETS, create a deperate variable with deps - support and use with - support and use passing arguments to tests v1: https://lore.kernel.org/all/20240402010520.1209517-1-kuba@kernel.org/ ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
a15d80a16d
@ -140,6 +140,13 @@ nsim_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats)
|
||||
{
|
||||
fec_stats->corrected_blocks.total = 123;
|
||||
fec_stats->uncorrectable_blocks.total = 4;
|
||||
}
|
||||
|
||||
static int nsim_get_ts_info(struct net_device *dev,
|
||||
struct ethtool_ts_info *info)
|
||||
{
|
||||
@ -163,6 +170,7 @@ static const struct ethtool_ops nsim_ethtool_ops = {
|
||||
.set_channels = nsim_set_channels,
|
||||
.get_fecparam = nsim_get_fecparam,
|
||||
.set_fecparam = nsim_set_fecparam,
|
||||
.get_fec_stats = nsim_get_fec_stats,
|
||||
.get_ts_info = nsim_get_ts_info,
|
||||
};
|
||||
|
||||
@ -182,6 +190,9 @@ void nsim_ethtool_init(struct netdevsim *ns)
|
||||
|
||||
nsim_ethtool_ring_init(ns);
|
||||
|
||||
ns->ethtool.pauseparam.report_stats_rx = true;
|
||||
ns->ethtool.pauseparam.report_stats_tx = true;
|
||||
|
||||
ns->ethtool.fec.fec = ETHTOOL_FEC_NONE;
|
||||
ns->ethtool.fec.active_fec = ETHTOOL_FEC_NONE;
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/netdev_queues.h>
|
||||
#include <net/netlink.h>
|
||||
#include <net/pkt_cls.h>
|
||||
#include <net/rtnetlink.h>
|
||||
@ -330,6 +331,53 @@ static const struct net_device_ops nsim_vf_netdev_ops = {
|
||||
.ndo_set_features = nsim_set_features,
|
||||
};
|
||||
|
||||
/* We don't have true per-queue stats, yet, so do some random fakery here.
|
||||
* Only report stuff for queue 0.
|
||||
*/
|
||||
static void nsim_get_queue_stats_rx(struct net_device *dev, int idx,
|
||||
struct netdev_queue_stats_rx *stats)
|
||||
{
|
||||
struct rtnl_link_stats64 rtstats = {};
|
||||
|
||||
if (!idx)
|
||||
nsim_get_stats64(dev, &rtstats);
|
||||
|
||||
stats->packets = rtstats.rx_packets - !!rtstats.rx_packets;
|
||||
stats->bytes = rtstats.rx_bytes;
|
||||
}
|
||||
|
||||
static void nsim_get_queue_stats_tx(struct net_device *dev, int idx,
|
||||
struct netdev_queue_stats_tx *stats)
|
||||
{
|
||||
struct rtnl_link_stats64 rtstats = {};
|
||||
|
||||
if (!idx)
|
||||
nsim_get_stats64(dev, &rtstats);
|
||||
|
||||
stats->packets = rtstats.tx_packets - !!rtstats.tx_packets;
|
||||
stats->bytes = rtstats.tx_bytes;
|
||||
}
|
||||
|
||||
static void nsim_get_base_stats(struct net_device *dev,
|
||||
struct netdev_queue_stats_rx *rx,
|
||||
struct netdev_queue_stats_tx *tx)
|
||||
{
|
||||
struct rtnl_link_stats64 rtstats = {};
|
||||
|
||||
nsim_get_stats64(dev, &rtstats);
|
||||
|
||||
rx->packets = !!rtstats.rx_packets;
|
||||
rx->bytes = 0;
|
||||
tx->packets = !!rtstats.tx_packets;
|
||||
tx->bytes = 0;
|
||||
}
|
||||
|
||||
static const struct netdev_stat_ops nsim_stat_ops = {
|
||||
.get_queue_stats_tx = nsim_get_queue_stats_tx,
|
||||
.get_queue_stats_rx = nsim_get_queue_stats_rx,
|
||||
.get_base_stats = nsim_get_base_stats,
|
||||
};
|
||||
|
||||
static void nsim_setup(struct net_device *dev)
|
||||
{
|
||||
ether_setup(dev);
|
||||
@ -360,6 +408,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
|
||||
|
||||
ns->phc = phc;
|
||||
ns->netdev->netdev_ops = &nsim_netdev_ops;
|
||||
ns->netdev->stat_ops = &nsim_stat_ops;
|
||||
|
||||
err = nsim_udp_tunnels_info_create(ns->nsim_dev, ns->netdev);
|
||||
if (err)
|
||||
|
@ -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
|
||||
@ -116,6 +117,13 @@ TARGETS += zram
|
||||
TARGETS_HOTPLUG = cpu-hotplug
|
||||
TARGETS_HOTPLUG += memory-hotplug
|
||||
|
||||
# Networking tests want the net/lib target, include it automatically
|
||||
ifneq ($(filter net drivers/net,$(TARGETS)),)
|
||||
ifeq ($(filter net/lib,$(TARGETS)),)
|
||||
INSTALL_DEP_TARGETS := net/lib
|
||||
endif
|
||||
endif
|
||||
|
||||
# User can optionally provide a TARGETS skiplist. By default we skip
|
||||
# BPF since it has cutting edge build time dependencies which require
|
||||
# more effort to install.
|
||||
@ -245,7 +253,7 @@ ifdef INSTALL_PATH
|
||||
install -m 744 run_kselftest.sh $(INSTALL_PATH)/
|
||||
rm -f $(TEST_LIST)
|
||||
@ret=1; \
|
||||
for TARGET in $(TARGETS); do \
|
||||
for TARGET in $(TARGETS) $(INSTALL_DEP_TARGETS); do \
|
||||
BUILD_TARGET=$$BUILD/$$TARGET; \
|
||||
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install \
|
||||
INSTALL_PATH=$(INSTALL_PATH)/$$TARGET \
|
||||
|
7
tools/testing/selftests/drivers/net/Makefile
Normal file
7
tools/testing/selftests/drivers/net/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
TEST_INCLUDES := $(wildcard lib/py/*.py)
|
||||
|
||||
TEST_PROGS := stats.py
|
||||
|
||||
include ../../lib.mk
|
30
tools/testing/selftests/drivers/net/README.rst
Normal file
30
tools/testing/selftests/drivers/net/README.rst
Normal file
@ -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.
|
17
tools/testing/selftests/drivers/net/lib/py/__init__.py
Normal file
17
tools/testing/selftests/drivers/net/lib/py/__init__.py
Normal file
@ -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 *
|
52
tools/testing/selftests/drivers/net/lib/py/env.py
Normal file
52
tools/testing/selftests/drivers/net/lib/py/env.py
Normal file
@ -0,0 +1,52 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import os
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
from lib.py import ip
|
||||
from lib.py import NetdevSimDev
|
||||
|
||||
class NetDrvEnv:
|
||||
def __init__(self, src_path):
|
||||
self._ns = None
|
||||
|
||||
self.env = os.environ.copy()
|
||||
self._load_env_file(src_path)
|
||||
|
||||
if 'NETIF' in self.env:
|
||||
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 __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, ex_type, ex_value, ex_tb):
|
||||
"""
|
||||
__exit__ gets called at the end of a "with" block.
|
||||
"""
|
||||
self.__del__()
|
||||
|
||||
def __del__(self):
|
||||
if self._ns:
|
||||
self._ns.remove()
|
||||
self._ns = None
|
||||
|
||||
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
|
86
tools/testing/selftests/drivers/net/stats.py
Executable file
86
tools/testing/selftests/drivers/net/stats.py
Executable file
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
from lib.py import ksft_run, ksft_in, ksft_true, KsftSkipEx, KsftXfailEx
|
||||
from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
|
||||
from lib.py import NetDrvEnv
|
||||
|
||||
ethnl = EthtoolFamily()
|
||||
netfam = NetdevFamily()
|
||||
rtnl = RtnlFamily()
|
||||
|
||||
|
||||
def check_pause(cfg) -> None:
|
||||
global ethnl
|
||||
|
||||
try:
|
||||
ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})
|
||||
except NlError as e:
|
||||
if e.error == 95:
|
||||
raise KsftXfailEx("pause not supported by the device")
|
||||
raise
|
||||
|
||||
data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
|
||||
"flags": {'stats'}}})
|
||||
ksft_true(data['stats'], "driver does not report stats")
|
||||
|
||||
|
||||
def check_fec(cfg) -> None:
|
||||
global ethnl
|
||||
|
||||
try:
|
||||
ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})
|
||||
except NlError as e:
|
||||
if e.error == 95:
|
||||
raise KsftXfailEx("FEC not supported by the device")
|
||||
raise
|
||||
|
||||
data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
|
||||
"flags": {'stats'}}})
|
||||
ksft_true(data['stats'], "driver does not report stats")
|
||||
|
||||
|
||||
def pkt_byte_sum(cfg) -> None:
|
||||
global netfam, rtnl
|
||||
|
||||
def get_qstat(test):
|
||||
global netfam
|
||||
stats = netfam.qstats_get({}, dump=True)
|
||||
if stats:
|
||||
for qs in stats:
|
||||
if qs["ifindex"]== test.ifindex:
|
||||
return qs
|
||||
|
||||
qstat = get_qstat(cfg)
|
||||
if qstat is None:
|
||||
raise KsftSkipEx("qstats not supported by the device")
|
||||
|
||||
for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
|
||||
ksft_in(key, qstat, "Drivers should always report basic keys")
|
||||
|
||||
# Compare stats, rtnl stats and qstats must match,
|
||||
# but the interface may be up, so do a series of dumps
|
||||
# each time the more "recent" stats must be higher or same.
|
||||
def stat_cmp(rstat, qstat):
|
||||
for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
|
||||
if rstat[key] != qstat[key]:
|
||||
return rstat[key] - qstat[key]
|
||||
return 0
|
||||
|
||||
for _ in range(10):
|
||||
rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats']
|
||||
if stat_cmp(rtstat, qstat) < 0:
|
||||
raise Exception("RTNL stats are lower, fetched later")
|
||||
qstat = get_qstat(cfg)
|
||||
if stat_cmp(rtstat, qstat) > 0:
|
||||
raise Exception("Qstats are lower, fetched later")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with NetDrvEnv(__file__) as cfg:
|
||||
ksft_run([check_pause, check_fec, pkt_byte_sum],
|
||||
args=(cfg, ))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -34,6 +34,7 @@ TEST_PROGS += gre_gso.sh
|
||||
TEST_PROGS += cmsg_so_mark.sh
|
||||
TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh
|
||||
TEST_PROGS += netns-name.sh
|
||||
TEST_PROGS += nl_netdev.py
|
||||
TEST_PROGS += srv6_end_dt46_l3vpn_test.sh
|
||||
TEST_PROGS += srv6_end_dt4_l3vpn_test.sh
|
||||
TEST_PROGS += srv6_end_dt6_l3vpn_test.sh
|
||||
|
8
tools/testing/selftests/net/lib/Makefile
Normal file
8
tools/testing/selftests/net/lib/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
TEST_FILES := ../../../../../Documentation/netlink/specs
|
||||
TEST_FILES += ../../../../net/ynl
|
||||
|
||||
TEST_INCLUDES := $(wildcard py/*.py)
|
||||
|
||||
include ../../lib.mk
|
7
tools/testing/selftests/net/lib/py/__init__.py
Normal file
7
tools/testing/selftests/net/lib/py/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
from .consts import KSRC
|
||||
from .ksft import *
|
||||
from .nsim import *
|
||||
from .utils import *
|
||||
from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
|
9
tools/testing/selftests/net/lib/py/consts.py
Normal file
9
tools/testing/selftests/net/lib/py/consts.py
Normal file
@ -0,0 +1,9 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
KSFT_DIR = (Path(__file__).parent / "../../..").resolve()
|
||||
KSRC = (Path(__file__).parent / "../../../../../..").resolve()
|
||||
|
||||
KSFT_MAIN_NAME = Path(sys.argv[0]).with_suffix("").name
|
96
tools/testing/selftests/net/lib/py/ksft.py
Normal file
96
tools/testing/selftests/net/lib/py/ksft.py
Normal file
@ -0,0 +1,96 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import builtins
|
||||
from .consts import KSFT_MAIN_NAME
|
||||
|
||||
KSFT_RESULT = None
|
||||
|
||||
|
||||
class KsftSkipEx(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class KsftXfailEx(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def ksft_pr(*objs, **kwargs):
|
||||
print("#", *objs, **kwargs)
|
||||
|
||||
|
||||
def ksft_eq(a, b, comment=""):
|
||||
global KSFT_RESULT
|
||||
if a != b:
|
||||
KSFT_RESULT = False
|
||||
ksft_pr("Check failed", a, "!=", b, comment)
|
||||
|
||||
|
||||
def ksft_true(a, comment=""):
|
||||
global KSFT_RESULT
|
||||
if not a:
|
||||
KSFT_RESULT = False
|
||||
ksft_pr("Check failed", a, "does not eval to True", comment)
|
||||
|
||||
|
||||
def ksft_in(a, b, comment=""):
|
||||
global KSFT_RESULT
|
||||
if a not in b:
|
||||
KSFT_RESULT = False
|
||||
ksft_pr("Check failed", a, "not in", b, comment)
|
||||
|
||||
|
||||
def ksft_ge(a, b, comment=""):
|
||||
global KSFT_RESULT
|
||||
if a < b:
|
||||
KSFT_RESULT = False
|
||||
ksft_pr("Check failed", a, "<", b, comment)
|
||||
|
||||
|
||||
def ktap_result(ok, cnt=1, case="", comment=""):
|
||||
res = ""
|
||||
if not ok:
|
||||
res += "not "
|
||||
res += "ok "
|
||||
res += str(cnt) + " "
|
||||
res += KSFT_MAIN_NAME
|
||||
if case:
|
||||
res += "." + str(case.__name__)
|
||||
if comment:
|
||||
res += " # " + comment
|
||||
print(res)
|
||||
|
||||
|
||||
def ksft_run(cases, args=()):
|
||||
totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0}
|
||||
|
||||
print("KTAP version 1")
|
||||
print("1.." + str(len(cases)))
|
||||
|
||||
global KSFT_RESULT
|
||||
cnt = 0
|
||||
for case in cases:
|
||||
KSFT_RESULT = True
|
||||
cnt += 1
|
||||
try:
|
||||
case(*args)
|
||||
except KsftSkipEx as e:
|
||||
ktap_result(True, cnt, case, comment="SKIP " + str(e))
|
||||
totals['skip'] += 1
|
||||
continue
|
||||
except KsftXfailEx as e:
|
||||
ktap_result(True, cnt, case, comment="XFAIL " + str(e))
|
||||
totals['xfail'] += 1
|
||||
continue
|
||||
except Exception as e:
|
||||
for line in str(e).split('\n'):
|
||||
ksft_pr("Exception|", line)
|
||||
ktap_result(False, cnt, case)
|
||||
totals['fail'] += 1
|
||||
continue
|
||||
|
||||
ktap_result(KSFT_RESULT, cnt, case)
|
||||
totals['pass'] += 1
|
||||
|
||||
print(
|
||||
f"# Totals: pass:{totals['pass']} fail:{totals['fail']} xfail:{totals['xfail']} xpass:0 skip:{totals['skip']} error:0"
|
||||
)
|
115
tools/testing/selftests/net/lib/py/nsim.py
Normal file
115
tools/testing/selftests/net/lib/py/nsim.py
Normal file
@ -0,0 +1,115 @@
|
||||
# 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(r"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.nsimdev = nsimdev
|
||||
self.port_index = 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 there
|
||||
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, ))
|
||||
|
||||
def remove_nsim(self, nsim):
|
||||
self.nsims.remove(nsim)
|
||||
self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
|
||||
"%u" % (nsim.port_index, ))
|
47
tools/testing/selftests/net/lib/py/utils.py
Normal file
47
tools/testing/selftests/net/lib/py/utils.py
Normal file
@ -0,0 +1,47 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import json as _json
|
||||
import subprocess
|
||||
|
||||
class cmd:
|
||||
def __init__(self, comm, shell=True, fail=True, ns=None, background=False):
|
||||
if ns:
|
||||
if isinstance(ns, NetNS):
|
||||
ns = ns.name
|
||||
comm = f'ip netns exec {ns} ' + comm
|
||||
|
||||
self.stdout = None
|
||||
self.stderr = None
|
||||
self.ret = None
|
||||
|
||||
self.comm = comm
|
||||
self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
if not background:
|
||||
self.process(terminate=False, fail=fail)
|
||||
|
||||
def process(self, terminate=True, fail=None):
|
||||
if terminate:
|
||||
self.proc.terminate()
|
||||
stdout, stderr = self.proc.communicate()
|
||||
self.stdout = stdout.decode("utf-8")
|
||||
self.stderr = stderr.decode("utf-8")
|
||||
self.proc.stdout.close()
|
||||
self.proc.stderr.close()
|
||||
self.ret = self.proc.returncode
|
||||
|
||||
if self.proc.returncode != 0 and fail:
|
||||
if len(stderr) > 0 and stderr[-1] == "\n":
|
||||
stderr = stderr[:-1]
|
||||
raise Exception("Command failed: %s\n%s" % (self.proc.args, stderr))
|
||||
|
||||
|
||||
def ip(args, json=None, ns=None):
|
||||
cmd_str = "ip "
|
||||
if json:
|
||||
cmd_str += '-j '
|
||||
cmd_str += args
|
||||
cmd_obj = cmd(cmd_str, ns=ns)
|
||||
if json:
|
||||
return _json.loads(cmd_obj.stdout)
|
||||
return cmd_obj
|
49
tools/testing/selftests/net/lib/py/ynl.py
Normal file
49
tools/testing/selftests/net/lib/py/ynl.py
Normal file
@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from .consts import KSRC, KSFT_DIR
|
||||
from .ksft import ksft_pr, ktap_result
|
||||
|
||||
# Resolve paths
|
||||
try:
|
||||
if (KSFT_DIR / "kselftest-list.txt").exists():
|
||||
# Running in "installed" selftests
|
||||
tools_full_path = KSFT_DIR
|
||||
SPEC_PATH = KSFT_DIR / "net/lib/specs"
|
||||
|
||||
sys.path.append(tools_full_path.as_posix())
|
||||
from net.lib.ynl.lib import YnlFamily, NlError
|
||||
else:
|
||||
# Running in tree
|
||||
tools_full_path = KSRC / "tools"
|
||||
SPEC_PATH = KSRC / "Documentation/netlink/specs"
|
||||
|
||||
sys.path.append(tools_full_path.as_posix())
|
||||
from net.ynl.lib import YnlFamily, NlError
|
||||
except ModuleNotFoundError as e:
|
||||
ksft_pr("Failed importing `ynl` library from kernel sources")
|
||||
ksft_pr(str(e))
|
||||
ktap_result(True, comment="SKIP")
|
||||
sys.exit(4)
|
||||
|
||||
#
|
||||
# Wrapper classes, loading the right specs
|
||||
# Set schema='' to avoid jsonschema validation, it's slow
|
||||
#
|
||||
class EthtoolFamily(YnlFamily):
|
||||
def __init__(self):
|
||||
super().__init__((SPEC_PATH / Path('ethtool.yaml')).as_posix(),
|
||||
schema='')
|
||||
|
||||
|
||||
class RtnlFamily(YnlFamily):
|
||||
def __init__(self):
|
||||
super().__init__((SPEC_PATH / Path('rt_link.yaml')).as_posix(),
|
||||
schema='')
|
||||
|
||||
|
||||
class NetdevFamily(YnlFamily):
|
||||
def __init__(self):
|
||||
super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(),
|
||||
schema='')
|
24
tools/testing/selftests/net/nl_netdev.py
Executable file
24
tools/testing/selftests/net/nl_netdev.py
Executable file
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
from lib.py import ksft_run, ksft_pr, ksft_eq, ksft_ge, NetdevFamily
|
||||
|
||||
|
||||
def empty_check(nf) -> None:
|
||||
devs = nf.dev_get({}, dump=True)
|
||||
ksft_ge(len(devs), 1)
|
||||
|
||||
|
||||
def lo_check(nf) -> None:
|
||||
lo_info = nf.dev_get({"ifindex": 1})
|
||||
ksft_eq(len(lo_info['xdp-features']), 0)
|
||||
ksft_eq(len(lo_info['xdp-rx-metadata-features']), 0)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
nf = NetdevFamily()
|
||||
ksft_run([empty_check, lo_check], args=(nf, ))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user