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