xref: /linux/tools/testing/selftests/net/lib/py/nsim.py (revision 48ba00da2eb4b54a7e6ed2ca3a9f2e575dff48c9)
1# SPDX-License-Identifier: GPL-2.0
2
3import json
4import os
5import random
6import re
7import time
8from .utils import cmd, ip
9
10
11class NetdevSim:
12    """
13    Class for netdevsim netdevice and its attributes.
14    """
15
16    def __init__(self, nsimdev, port_index, ifname, ns=None):
17        # In case udev renamed the netdev to according to new schema,
18        # check if the name matches the port_index.
19        nsimnamere = re.compile(r"eni\d+np(\d+)")
20        match = nsimnamere.match(ifname)
21        if match and int(match.groups()[0]) != port_index + 1:
22            raise Exception("netdevice name mismatches the expected one")
23
24        self.nsimdev = nsimdev
25        self.port_index = port_index
26        ret = ip("-j link show dev %s" % ifname, ns=ns)
27        self.dev = json.loads(ret.stdout)[0]
28
29    def dfs_write(self, path, val):
30        self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val)
31
32
33class NetdevSimDev:
34    """
35    Class for netdevsim bus device and its attributes.
36    """
37    @staticmethod
38    def ctrl_write(path, val):
39        fullpath = os.path.join("/sys/bus/netdevsim/", path)
40        with open(fullpath, "w") as f:
41            f.write(val)
42
43    def dfs_write(self, path, val):
44        fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path)
45        with open(fullpath, "w") as f:
46            f.write(val)
47
48    def __init__(self, port_count=1, ns=None):
49        # nsim will spawn in init_net, we'll set to actual ns once we switch it there
50        self.ns = None
51
52        if not os.path.exists("/sys/bus/netdevsim"):
53            cmd("modprobe netdevsim")
54
55        addr = random.randrange(1 << 15)
56        while True:
57            try:
58                self.ctrl_write("new_device", "%u %u" % (addr, port_count))
59            except OSError as e:
60                if e.errno == errno.ENOSPC:
61                    addr = random.randrange(1 << 15)
62                    continue
63                raise e
64            break
65        self.addr = addr
66
67        # As probe of netdevsim device might happen from a workqueue,
68        # so wait here until all netdevs appear.
69        self.wait_for_netdevs(port_count)
70
71        if ns:
72            cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}")
73            self.ns = ns
74
75        cmd("udevadm settle", ns=self.ns)
76        ifnames = self.get_ifnames()
77
78        self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
79
80        self.nsims = []
81        for port_index in range(port_count):
82            self.nsims.append(NetdevSim(self, port_index, ifnames[port_index],
83                                        ns=ns))
84
85    def get_ifnames(self):
86        ifnames = []
87        listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/",
88                      ns=self.ns).stdout.split()
89        for ifname in listdir:
90            ifnames.append(ifname)
91        ifnames.sort()
92        return ifnames
93
94    def wait_for_netdevs(self, port_count):
95        timeout = 5
96        timeout_start = time.time()
97
98        while True:
99            try:
100                ifnames = self.get_ifnames()
101            except FileNotFoundError as e:
102                ifnames = []
103            if len(ifnames) == port_count:
104                break
105            if time.time() < timeout_start + timeout:
106                continue
107            raise Exception("netdevices did not appear within timeout")
108
109    def remove(self):
110        self.ctrl_write("del_device", "%u" % (self.addr, ))
111
112    def remove_nsim(self, nsim):
113        self.nsims.remove(nsim)
114        self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
115                        "%u" % (nsim.port_index, ))
116