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