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