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