1a13fedbeSBrenda J. Butlerimport os
2a13fedbeSBrenda J. Butlerimport signal
3a13fedbeSBrenda J. Butlerfrom string import Template
4a13fedbeSBrenda J. Butlerimport subprocess
5a13fedbeSBrenda J. Butlerimport time
6ac9b8293SPedro Tammelafrom multiprocessing import Pool
798cfbe42SPedro Tammelafrom functools import cached_property
8a13fedbeSBrenda J. Butlerfrom TdcPlugin import TdcPlugin
9a13fedbeSBrenda J. Butler
10a13fedbeSBrenda J. Butlerfrom tdc_config import *
11a13fedbeSBrenda J. Butler
12fa63d353SPedro Tammelatry:
13fa63d353SPedro Tammela    from pyroute2 import netns
14fa63d353SPedro Tammela    from pyroute2 import IPRoute
15fa63d353SPedro Tammela    netlink = True
16fa63d353SPedro Tammelaexcept ImportError:
17fa63d353SPedro Tammela    netlink = False
18fa63d353SPedro Tammela    print("!!! Consider installing pyroute2 !!!")
19fa63d353SPedro Tammela
20a13fedbeSBrenda J. Butlerclass SubPlugin(TdcPlugin):
21a13fedbeSBrenda J. Butler    def __init__(self):
22a13fedbeSBrenda J. Butler        self.sub_class = 'ns/SubPlugin'
23a13fedbeSBrenda J. Butler        super().__init__()
24a13fedbeSBrenda J. Butler
2598cfbe42SPedro Tammela    def pre_suite(self, testcount, testlist):
2698cfbe42SPedro Tammela        super().pre_suite(testcount, testlist)
2798cfbe42SPedro Tammela
2850a5988aSPedro Tammela    def prepare_test(self, test):
2950a5988aSPedro Tammela        if 'skip' in test and test['skip'] == 'yes':
3050a5988aSPedro Tammela            return
3198cfbe42SPedro Tammela
3250a5988aSPedro Tammela        if 'nsPlugin' not in test['plugins']:
3350a5988aSPedro Tammela            return
3498cfbe42SPedro Tammela
3550a5988aSPedro Tammela        if netlink == True:
3650a5988aSPedro Tammela            self._nl_ns_create()
3750a5988aSPedro Tammela        else:
3856e16bc6SPedro Tammela            self._ipr2_ns_create()
3950a5988aSPedro Tammela
4050a5988aSPedro Tammela        # Make sure the netns is visible in the fs
414b480cfbSPedro Tammela        ticks = 20
4250a5988aSPedro Tammela        while True:
434b480cfbSPedro Tammela            if ticks == 0:
444b480cfbSPedro Tammela                raise TimeoutError
4550a5988aSPedro Tammela            self._proc_check()
4650a5988aSPedro Tammela            try:
4750a5988aSPedro Tammela                ns = self.args.NAMES['NS']
4850a5988aSPedro Tammela                f = open('/run/netns/{}'.format(ns))
4950a5988aSPedro Tammela                f.close()
5050a5988aSPedro Tammela                break
5150a5988aSPedro Tammela            except:
5250a5988aSPedro Tammela                time.sleep(0.1)
534b480cfbSPedro Tammela                ticks -= 1
5450a5988aSPedro Tammela                continue
5550a5988aSPedro Tammela
5650a5988aSPedro Tammela    def pre_case(self, test, test_skip):
57a13fedbeSBrenda J. Butler        if self.args.verbose:
5898cfbe42SPedro Tammela            print('{}.pre_case'.format(self.sub_class))
5998cfbe42SPedro Tammela
6098cfbe42SPedro Tammela        if test_skip:
6198cfbe42SPedro Tammela            return
6298cfbe42SPedro Tammela
6350a5988aSPedro Tammela        self.prepare_test(test)
6450a5988aSPedro Tammela
6598cfbe42SPedro Tammela    def post_case(self):
6698cfbe42SPedro Tammela        if self.args.verbose:
6798cfbe42SPedro Tammela            print('{}.post_case'.format(self.sub_class))
68a13fedbeSBrenda J. Butler
693d5026fcSPedro Tammela        if netlink == True:
703d5026fcSPedro Tammela            self._nl_ns_destroy()
713d5026fcSPedro Tammela        else:
7256e16bc6SPedro Tammela            self._ipr2_ns_destroy()
73a13fedbeSBrenda J. Butler
7498cfbe42SPedro Tammela    def post_suite(self, index):
7598cfbe42SPedro Tammela        if self.args.verbose:
7698cfbe42SPedro Tammela            print('{}.post_suite'.format(self.sub_class))
7798cfbe42SPedro Tammela
7898cfbe42SPedro Tammela        # Make sure we don't leak resources
79*501679f5SPedro Tammela        cmd = self._replace_keywords("$IP -a netns del")
8098cfbe42SPedro Tammela
8198cfbe42SPedro Tammela        if self.args.verbose > 3:
8298cfbe42SPedro Tammela            print('_exec_cmd:  command "{}"'.format(cmd))
8398cfbe42SPedro Tammela
8498cfbe42SPedro Tammela        subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
8598cfbe42SPedro Tammela
86a13fedbeSBrenda J. Butler    def adjust_command(self, stage, command):
87a13fedbeSBrenda J. Butler        super().adjust_command(stage, command)
88a13fedbeSBrenda J. Butler        cmdform = 'list'
89a13fedbeSBrenda J. Butler        cmdlist = list()
90a13fedbeSBrenda J. Butler
91a13fedbeSBrenda J. Butler        if self.args.verbose:
92a13fedbeSBrenda J. Butler            print('{}.adjust_command'.format(self.sub_class))
93a13fedbeSBrenda J. Butler
94a13fedbeSBrenda J. Butler        if not isinstance(command, list):
95a13fedbeSBrenda J. Butler            cmdform = 'str'
96a13fedbeSBrenda J. Butler            cmdlist = command.split()
97a13fedbeSBrenda J. Butler        else:
98a13fedbeSBrenda J. Butler            cmdlist = command
99a13fedbeSBrenda J. Butler        if stage == 'setup' or stage == 'execute' or stage == 'verify' or stage == 'teardown':
100a13fedbeSBrenda J. Butler            if self.args.verbose:
101a13fedbeSBrenda J. Butler                print('adjust_command:  stage is {}; inserting netns stuff in command [{}] list [{}]'.format(stage, command, cmdlist))
102a13fedbeSBrenda J. Butler            cmdlist.insert(0, self.args.NAMES['NS'])
103a13fedbeSBrenda J. Butler            cmdlist.insert(0, 'exec')
104a13fedbeSBrenda J. Butler            cmdlist.insert(0, 'netns')
10502a3f0d5SDavide Caratti            cmdlist.insert(0, self.args.NAMES['IP'])
106a13fedbeSBrenda J. Butler        else:
107a13fedbeSBrenda J. Butler            pass
108a13fedbeSBrenda J. Butler
109a13fedbeSBrenda J. Butler        if cmdform == 'str':
110a13fedbeSBrenda J. Butler            command = ' '.join(cmdlist)
111a13fedbeSBrenda J. Butler        else:
112a13fedbeSBrenda J. Butler            command = cmdlist
113a13fedbeSBrenda J. Butler
114a13fedbeSBrenda J. Butler        if self.args.verbose:
115a13fedbeSBrenda J. Butler            print('adjust_command:  return command [{}]'.format(command))
116a13fedbeSBrenda J. Butler        return command
117a13fedbeSBrenda J. Butler
118fa63d353SPedro Tammela    def _nl_ns_create(self):
119fa63d353SPedro Tammela        ns = self.args.NAMES["NS"];
120fa63d353SPedro Tammela        dev0 = self.args.NAMES["DEV0"];
121fa63d353SPedro Tammela        dev1 = self.args.NAMES["DEV1"];
122fa63d353SPedro Tammela        dummy = self.args.NAMES["DUMMY"];
12398cfbe42SPedro Tammela
124fa63d353SPedro Tammela        if self.args.verbose:
125fa63d353SPedro Tammela            print('{}._nl_ns_create'.format(self.sub_class))
12698cfbe42SPedro Tammela
127fa63d353SPedro Tammela        netns.create(ns)
128fa63d353SPedro Tammela        netns.pushns(newns=ns)
129fa63d353SPedro Tammela        with IPRoute() as ip:
130fa63d353SPedro Tammela            ip.link('add', ifname=dev1, kind='veth', peer={'ifname': dev0, 'net_ns_fd':'/proc/1/ns/net'})
131fa63d353SPedro Tammela            ip.link('add', ifname=dummy, kind='dummy')
1324b480cfbSPedro Tammela            ticks = 20
133fa63d353SPedro Tammela            while True:
1344b480cfbSPedro Tammela                if ticks == 0:
1354b480cfbSPedro Tammela                    raise TimeoutError
136fa63d353SPedro Tammela                try:
137fa63d353SPedro Tammela                    dev1_idx = ip.link_lookup(ifname=dev1)[0]
138fa63d353SPedro Tammela                    dummy_idx = ip.link_lookup(ifname=dummy)[0]
139fa63d353SPedro Tammela                    ip.link('set', index=dev1_idx, state='up')
140fa63d353SPedro Tammela                    ip.link('set', index=dummy_idx, state='up')
141fa63d353SPedro Tammela                    break
142fa63d353SPedro Tammela                except:
143fa63d353SPedro Tammela                    time.sleep(0.1)
1444b480cfbSPedro Tammela                    ticks -= 1
145fa63d353SPedro Tammela                    continue
146fa63d353SPedro Tammela        netns.popns()
14798cfbe42SPedro Tammela
148fa63d353SPedro Tammela        with IPRoute() as ip:
1494b480cfbSPedro Tammela            ticks = 20
150fa63d353SPedro Tammela            while True:
1514b480cfbSPedro Tammela                if ticks == 0:
1524b480cfbSPedro Tammela                    raise TimeoutError
153fa63d353SPedro Tammela                try:
154fa63d353SPedro Tammela                    dev0_idx = ip.link_lookup(ifname=dev0)[0]
155fa63d353SPedro Tammela                    ip.link('set', index=dev0_idx, state='up')
156fa63d353SPedro Tammela                    break
157fa63d353SPedro Tammela                except:
158fa63d353SPedro Tammela                    time.sleep(0.1)
1594b480cfbSPedro Tammela                    ticks -= 1
160fa63d353SPedro Tammela                    continue
16198cfbe42SPedro Tammela
16256e16bc6SPedro Tammela    def _ipr2_ns_create_cmds(self):
16398cfbe42SPedro Tammela        cmds = []
16498cfbe42SPedro Tammela
16598cfbe42SPedro Tammela        ns = self.args.NAMES['NS']
16698cfbe42SPedro Tammela
16798cfbe42SPedro Tammela        cmds.append(self._replace_keywords('netns add {}'.format(ns)))
168fa63d353SPedro Tammela        cmds.append(self._replace_keywords('link add $DEV1 type veth peer name $DEV0'))
16998cfbe42SPedro Tammela        cmds.append(self._replace_keywords('link set $DEV1 netns {}'.format(ns)))
170fa63d353SPedro Tammela        cmds.append(self._replace_keywords('link add $DUMMY type dummy'.format(ns)))
17198cfbe42SPedro Tammela        cmds.append(self._replace_keywords('link set $DUMMY netns {}'.format(ns)))
17298cfbe42SPedro Tammela        cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV1 up'.format(ns)))
17398cfbe42SPedro Tammela        cmds.append(self._replace_keywords('netns exec {} $IP link set $DUMMY up'.format(ns)))
174fa63d353SPedro Tammela        cmds.append(self._replace_keywords('link set $DEV0 up'.format(ns)))
17598cfbe42SPedro Tammela
17698cfbe42SPedro Tammela        if self.args.device:
17798cfbe42SPedro Tammela            cmds.append(self._replace_keywords('link set $DEV2 netns {}'.format(ns)))
17898cfbe42SPedro Tammela            cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV2 up'.format(ns)))
17998cfbe42SPedro Tammela
18098cfbe42SPedro Tammela        return cmds
181489ce2f4SLucas Bates
18256e16bc6SPedro Tammela    def _ipr2_ns_create(self):
183a13fedbeSBrenda J. Butler        '''
184a13fedbeSBrenda J. Butler        Create the network namespace in which the tests will be run and set up
185a13fedbeSBrenda J. Butler        the required network devices for it.
186a13fedbeSBrenda J. Butler        '''
18756e16bc6SPedro Tammela        self._exec_cmd_batched('pre', self._ipr2_ns_create_cmds())
18898cfbe42SPedro Tammela
1893d5026fcSPedro Tammela    def _nl_ns_destroy(self):
1903d5026fcSPedro Tammela        ns = self.args.NAMES['NS']
1913d5026fcSPedro Tammela        netns.remove(ns)
1923d5026fcSPedro Tammela
19356e16bc6SPedro Tammela    def _ipr2_ns_destroy_cmd(self):
19498cfbe42SPedro Tammela        return self._replace_keywords('netns delete {}'.format(self.args.NAMES['NS']))
195a13fedbeSBrenda J. Butler
19656e16bc6SPedro Tammela    def _ipr2_ns_destroy(self):
197a13fedbeSBrenda J. Butler        '''
198a13fedbeSBrenda J. Butler        Destroy the network namespace for testing (and any associated network
199a13fedbeSBrenda J. Butler        devices as well)
200a13fedbeSBrenda J. Butler        '''
20156e16bc6SPedro Tammela        self._exec_cmd('post', self._ipr2_ns_destroy_cmd())
20298cfbe42SPedro Tammela
20398cfbe42SPedro Tammela    @cached_property
20498cfbe42SPedro Tammela    def _proc(self):
20598cfbe42SPedro Tammela        ip = self._replace_keywords("$IP -b -")
20698cfbe42SPedro Tammela        proc = subprocess.Popen(ip,
20798cfbe42SPedro Tammela            shell=True,
20898cfbe42SPedro Tammela            stdin=subprocess.PIPE,
20998cfbe42SPedro Tammela            env=ENVIR)
21098cfbe42SPedro Tammela
21198cfbe42SPedro Tammela        return proc
21298cfbe42SPedro Tammela
21398cfbe42SPedro Tammela    def _proc_check(self):
21498cfbe42SPedro Tammela        proc = self._proc
21598cfbe42SPedro Tammela
21698cfbe42SPedro Tammela        proc.poll()
21798cfbe42SPedro Tammela
21898cfbe42SPedro Tammela        if proc.returncode is not None and proc.returncode != 0:
21998cfbe42SPedro Tammela            raise RuntimeError("iproute2 exited with an error code")
220a13fedbeSBrenda J. Butler
221a13fedbeSBrenda J. Butler    def _exec_cmd(self, stage, command):
222a13fedbeSBrenda J. Butler        '''
223a13fedbeSBrenda J. Butler        Perform any required modifications on an executable command, then run
224a13fedbeSBrenda J. Butler        it in a subprocess and return the results.
225a13fedbeSBrenda J. Butler        '''
226a13fedbeSBrenda J. Butler
22798cfbe42SPedro Tammela        if self.args.verbose > 3:
228a13fedbeSBrenda J. Butler            print('_exec_cmd:  command "{}"'.format(command))
229a13fedbeSBrenda J. Butler
23098cfbe42SPedro Tammela        proc = self._proc
231a13fedbeSBrenda J. Butler
23298cfbe42SPedro Tammela        proc.stdin.write((command + '\n').encode())
23398cfbe42SPedro Tammela        proc.stdin.flush()
23498cfbe42SPedro Tammela
23598cfbe42SPedro Tammela        if self.args.verbose > 3:
23698cfbe42SPedro Tammela            print('_exec_cmd proc: {}'.format(proc))
23798cfbe42SPedro Tammela
23898cfbe42SPedro Tammela        self._proc_check()
23998cfbe42SPedro Tammela
24098cfbe42SPedro Tammela    def _exec_cmd_batched(self, stage, commands):
24198cfbe42SPedro Tammela        for cmd in commands:
24298cfbe42SPedro Tammela            self._exec_cmd(stage, cmd)
243a13fedbeSBrenda J. Butler
244a13fedbeSBrenda J. Butler    def _replace_keywords(self, cmd):
245a13fedbeSBrenda J. Butler        """
246a13fedbeSBrenda J. Butler        For a given executable command, substitute any known
247a13fedbeSBrenda J. Butler        variables contained within NAMES with the correct values
248a13fedbeSBrenda J. Butler        """
249a13fedbeSBrenda J. Butler        tcmd = Template(cmd)
250a13fedbeSBrenda J. Butler        subcmd = tcmd.safe_substitute(self.args.NAMES)
251a13fedbeSBrenda J. Butler        return subcmd
252