xref: /linux/tools/testing/selftests/net/lib/py/nsim.py (revision 9acb51e9617c28a92f9ce2af767db6bd660a6d4f)
1 # SPDX-License-Identifier: GPL-2.0
2 
3 import json
4 import os
5 import random
6 import re
7 import time
8 from .utils import cmd, ip
9 
10 
11 class 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.ifname = ifname
25         self.nsimdev = nsimdev
26         self.port_index = port_index
27         self.ns = ns
28         self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
29         ret = ip("-j link show dev %s" % ifname, ns=ns)
30         self.dev = json.loads(ret.stdout)[0]
31         self.ifindex = self.dev["ifindex"]
32 
33     def dfs_write(self, path, val):
34         self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val)
35 
36 
37 class NetdevSimDev:
38     """
39     Class for netdevsim bus device and its attributes.
40     """
41     @staticmethod
42     def ctrl_write(path, val):
43         fullpath = os.path.join("/sys/bus/netdevsim/", path)
44         with open(fullpath, "w") as f:
45             f.write(val)
46 
47     def dfs_write(self, path, val):
48         fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path)
49         with open(fullpath, "w") as f:
50             f.write(val)
51 
52     def __init__(self, port_count=1, queue_count=1, ns=None):
53         # nsim will spawn in init_net, we'll set to actual ns once we switch it there
54         self.ns = None
55 
56         if not os.path.exists("/sys/bus/netdevsim"):
57             cmd("modprobe netdevsim")
58 
59         addr = random.randrange(1 << 15)
60         while True:
61             try:
62                 self.ctrl_write("new_device", "%u %u %u" % (addr, port_count, queue_count))
63             except OSError as e:
64                 if e.errno == errno.ENOSPC:
65                     addr = random.randrange(1 << 15)
66                     continue
67                 raise e
68             break
69         self.addr = addr
70 
71         # As probe of netdevsim device might happen from a workqueue,
72         # so wait here until all netdevs appear.
73         self.wait_for_netdevs(port_count)
74 
75         if ns:
76             cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}")
77             self.ns = ns
78 
79         cmd("udevadm settle", ns=self.ns)
80         ifnames = self.get_ifnames()
81 
82         self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
83 
84         self.nsims = []
85         for port_index in range(port_count):
86             self.nsims.append(self._make_port(port_index, ifnames[port_index]))
87 
88         self.removed = False
89 
90     def __enter__(self):
91         return self
92 
93     def __exit__(self, ex_type, ex_value, ex_tb):
94         """
95         __exit__ gets called at the end of a "with" block.
96         """
97         self.remove()
98 
99     def _make_port(self, port_index, ifname):
100         return NetdevSim(self, port_index, ifname, self.ns)
101 
102     def get_ifnames(self):
103         ifnames = []
104         listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/",
105                       ns=self.ns).stdout.split()
106         for ifname in listdir:
107             ifnames.append(ifname)
108         ifnames.sort()
109         return ifnames
110 
111     def wait_for_netdevs(self, port_count):
112         timeout = 5
113         timeout_start = time.time()
114 
115         while True:
116             try:
117                 ifnames = self.get_ifnames()
118             except FileNotFoundError as e:
119                 ifnames = []
120             if len(ifnames) == port_count:
121                 break
122             if time.time() < timeout_start + timeout:
123                 continue
124             raise Exception("netdevices did not appear within timeout")
125 
126     def remove(self):
127         if not self.removed:
128             self.ctrl_write("del_device", "%u" % (self.addr, ))
129             self.removed = True
130 
131     def remove_nsim(self, nsim):
132         self.nsims.remove(nsim)
133         self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
134                         "%u" % (nsim.port_index, ))
135