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