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