xref: /linux/tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py (revision 50a5988a7a540fb1ad4e620e1bbf11cc646e3dc7)
1import os
2import signal
3from string import Template
4import subprocess
5import time
6from multiprocessing import Pool
7from functools import cached_property
8from TdcPlugin import TdcPlugin
9
10from tdc_config import *
11
12try:
13    from pyroute2 import netns
14    from pyroute2 import IPRoute
15    netlink = True
16except ImportError:
17    netlink = False
18    print("!!! Consider installing pyroute2 !!!")
19
20class SubPlugin(TdcPlugin):
21    def __init__(self):
22        self.sub_class = 'ns/SubPlugin'
23        super().__init__()
24
25    def pre_suite(self, testcount, testlist):
26        from itertools import cycle
27
28        super().pre_suite(testcount, testlist)
29
30    def prepare_test(self, test):
31        if 'skip' in test and test['skip'] == 'yes':
32            return
33
34        if 'nsPlugin' not in test['plugins']:
35            return
36
37        if netlink == True:
38            self._nl_ns_create()
39        else:
40            self._ns_create()
41
42        # Make sure the netns is visible in the fs
43        while True:
44            self._proc_check()
45            try:
46                ns = self.args.NAMES['NS']
47                f = open('/run/netns/{}'.format(ns))
48                f.close()
49                break
50            except:
51                time.sleep(0.1)
52                continue
53
54    def pre_case(self, test, test_skip):
55        if self.args.verbose:
56            print('{}.pre_case'.format(self.sub_class))
57
58        if test_skip:
59            return
60
61        self.prepare_test(test)
62
63    def post_case(self):
64        if self.args.verbose:
65            print('{}.post_case'.format(self.sub_class))
66
67        self._ns_destroy()
68
69    def post_suite(self, index):
70        if self.args.verbose:
71            print('{}.post_suite'.format(self.sub_class))
72
73        # Make sure we don't leak resources
74        for f in os.listdir('/run/netns/'):
75            cmd = self._replace_keywords("$IP netns del {}".format(f))
76
77            if self.args.verbose > 3:
78                print('_exec_cmd:  command "{}"'.format(cmd))
79
80            subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
81
82    def adjust_command(self, stage, command):
83        super().adjust_command(stage, command)
84        cmdform = 'list'
85        cmdlist = list()
86
87        if self.args.verbose:
88            print('{}.adjust_command'.format(self.sub_class))
89
90        if not isinstance(command, list):
91            cmdform = 'str'
92            cmdlist = command.split()
93        else:
94            cmdlist = command
95        if stage == 'setup' or stage == 'execute' or stage == 'verify' or stage == 'teardown':
96            if self.args.verbose:
97                print('adjust_command:  stage is {}; inserting netns stuff in command [{}] list [{}]'.format(stage, command, cmdlist))
98            cmdlist.insert(0, self.args.NAMES['NS'])
99            cmdlist.insert(0, 'exec')
100            cmdlist.insert(0, 'netns')
101            cmdlist.insert(0, self.args.NAMES['IP'])
102        else:
103            pass
104
105        if cmdform == 'str':
106            command = ' '.join(cmdlist)
107        else:
108            command = cmdlist
109
110        if self.args.verbose:
111            print('adjust_command:  return command [{}]'.format(command))
112        return command
113
114    def _nl_ns_create(self):
115        ns = self.args.NAMES["NS"];
116        dev0 = self.args.NAMES["DEV0"];
117        dev1 = self.args.NAMES["DEV1"];
118        dummy = self.args.NAMES["DUMMY"];
119
120        if self.args.verbose:
121            print('{}._nl_ns_create'.format(self.sub_class))
122
123        netns.create(ns)
124        netns.pushns(newns=ns)
125        with IPRoute() as ip:
126            ip.link('add', ifname=dev1, kind='veth', peer={'ifname': dev0, 'net_ns_fd':'/proc/1/ns/net'})
127            ip.link('add', ifname=dummy, kind='dummy')
128            while True:
129                try:
130                    dev1_idx = ip.link_lookup(ifname=dev1)[0]
131                    dummy_idx = ip.link_lookup(ifname=dummy)[0]
132                    ip.link('set', index=dev1_idx, state='up')
133                    ip.link('set', index=dummy_idx, state='up')
134                    break
135                except:
136                    time.sleep(0.1)
137                    continue
138        netns.popns()
139
140        with IPRoute() as ip:
141            while True:
142                try:
143                    dev0_idx = ip.link_lookup(ifname=dev0)[0]
144                    ip.link('set', index=dev0_idx, state='up')
145                    break
146                except:
147                    time.sleep(0.1)
148                    continue
149
150    def _ns_create_cmds(self):
151        cmds = []
152
153        ns = self.args.NAMES['NS']
154
155        cmds.append(self._replace_keywords('netns add {}'.format(ns)))
156        cmds.append(self._replace_keywords('link add $DEV1 type veth peer name $DEV0'))
157        cmds.append(self._replace_keywords('link set $DEV1 netns {}'.format(ns)))
158        cmds.append(self._replace_keywords('link add $DUMMY type dummy'.format(ns)))
159        cmds.append(self._replace_keywords('link set $DUMMY netns {}'.format(ns)))
160        cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV1 up'.format(ns)))
161        cmds.append(self._replace_keywords('netns exec {} $IP link set $DUMMY up'.format(ns)))
162        cmds.append(self._replace_keywords('link set $DEV0 up'.format(ns)))
163
164        if self.args.device:
165            cmds.append(self._replace_keywords('link set $DEV2 netns {}'.format(ns)))
166            cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV2 up'.format(ns)))
167
168        return cmds
169
170    def _ns_create(self):
171        '''
172        Create the network namespace in which the tests will be run and set up
173        the required network devices for it.
174        '''
175        self._exec_cmd_batched('pre', self._ns_create_cmds())
176
177    def _ns_destroy_cmd(self):
178        return self._replace_keywords('netns delete {}'.format(self.args.NAMES['NS']))
179
180    def _ns_destroy(self):
181        '''
182        Destroy the network namespace for testing (and any associated network
183        devices as well)
184        '''
185        self._exec_cmd('post', self._ns_destroy_cmd())
186
187    @cached_property
188    def _proc(self):
189        ip = self._replace_keywords("$IP -b -")
190        proc = subprocess.Popen(ip,
191            shell=True,
192            stdin=subprocess.PIPE,
193            env=ENVIR)
194
195        return proc
196
197    def _proc_check(self):
198        proc = self._proc
199
200        proc.poll()
201
202        if proc.returncode is not None and proc.returncode != 0:
203            raise RuntimeError("iproute2 exited with an error code")
204
205    def _exec_cmd(self, stage, command):
206        '''
207        Perform any required modifications on an executable command, then run
208        it in a subprocess and return the results.
209        '''
210
211        if self.args.verbose > 3:
212            print('_exec_cmd:  command "{}"'.format(command))
213
214        proc = self._proc
215
216        proc.stdin.write((command + '\n').encode())
217        proc.stdin.flush()
218
219        if self.args.verbose > 3:
220            print('_exec_cmd proc: {}'.format(proc))
221
222        self._proc_check()
223
224    def _exec_cmd_batched(self, stage, commands):
225        for cmd in commands:
226            self._exec_cmd(stage, cmd)
227
228    def _replace_keywords(self, cmd):
229        """
230        For a given executable command, substitute any known
231        variables contained within NAMES with the correct values
232        """
233        tcmd = Template(cmd)
234        subcmd = tcmd.safe_substitute(self.args.NAMES)
235        return subcmd
236