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 obj._ns_create() 32 33 # Make sure the netns is visible in the fs 34 while True: 35 obj._proc_check() 36 try: 37 ns = obj.args.NAMES['NS'] 38 f = open('/run/netns/{}'.format(ns)) 39 f.close() 40 break 41 except: 42 time.sleep(0.1) 43 continue 44 45 obj.args.NAMES = original 46 47class SubPlugin(TdcPlugin): 48 def __init__(self): 49 self.sub_class = 'ns/SubPlugin' 50 super().__init__() 51 52 def pre_suite(self, testcount, testlist): 53 from itertools import cycle 54 55 super().pre_suite(testcount, testlist) 56 57 print("Setting up namespaces and devices...") 58 59 with Pool(self.args.mp) as p: 60 it = zip(cycle([self]), testlist) 61 p.starmap(prepare_suite, it) 62 63 def pre_case(self, caseinfo, test_skip): 64 if self.args.verbose: 65 print('{}.pre_case'.format(self.sub_class)) 66 67 if test_skip: 68 return 69 70 71 def post_case(self): 72 if self.args.verbose: 73 print('{}.post_case'.format(self.sub_class)) 74 75 self._ns_destroy() 76 77 def post_suite(self, index): 78 if self.args.verbose: 79 print('{}.post_suite'.format(self.sub_class)) 80 81 # Make sure we don't leak resources 82 for f in os.listdir('/run/netns/'): 83 cmd = self._replace_keywords("$IP netns del {}".format(f)) 84 85 if self.args.verbose > 3: 86 print('_exec_cmd: command "{}"'.format(cmd)) 87 88 subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 89 90 def adjust_command(self, stage, command): 91 super().adjust_command(stage, command) 92 cmdform = 'list' 93 cmdlist = list() 94 95 if self.args.verbose: 96 print('{}.adjust_command'.format(self.sub_class)) 97 98 if not isinstance(command, list): 99 cmdform = 'str' 100 cmdlist = command.split() 101 else: 102 cmdlist = command 103 if stage == 'setup' or stage == 'execute' or stage == 'verify' or stage == 'teardown': 104 if self.args.verbose: 105 print('adjust_command: stage is {}; inserting netns stuff in command [{}] list [{}]'.format(stage, command, cmdlist)) 106 cmdlist.insert(0, self.args.NAMES['NS']) 107 cmdlist.insert(0, 'exec') 108 cmdlist.insert(0, 'netns') 109 cmdlist.insert(0, self.args.NAMES['IP']) 110 else: 111 pass 112 113 if cmdform == 'str': 114 command = ' '.join(cmdlist) 115 else: 116 command = cmdlist 117 118 if self.args.verbose: 119 print('adjust_command: return command [{}]'.format(command)) 120 return command 121 122 def _ports_create_cmds(self): 123 cmds = [] 124 125 cmds.append(self._replace_keywords('link add $DEV0 type veth peer name $DEV1')) 126 cmds.append(self._replace_keywords('link set $DEV0 up')) 127 cmds.append(self._replace_keywords('link add $DUMMY type dummy')) 128 129 return cmds 130 131 def _ports_create(self): 132 self._exec_cmd_batched('pre', self._ports_create_cmds()) 133 134 def _ports_destroy_cmd(self): 135 return self._replace_keywords('link del $DEV0') 136 137 def _ports_destroy(self): 138 self._exec_cmd('post', self._ports_destroy_cmd()) 139 140 def _ns_create_cmds(self): 141 cmds = [] 142 143 ns = self.args.NAMES['NS'] 144 145 cmds.append(self._replace_keywords('netns add {}'.format(ns))) 146 cmds.append(self._replace_keywords('link set $DEV1 netns {}'.format(ns))) 147 cmds.append(self._replace_keywords('link set $DUMMY netns {}'.format(ns))) 148 cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV1 up'.format(ns))) 149 cmds.append(self._replace_keywords('netns exec {} $IP link set $DUMMY up'.format(ns))) 150 151 if self.args.device: 152 cmds.append(self._replace_keywords('link set $DEV2 netns {}'.format(ns))) 153 cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV2 up'.format(ns))) 154 155 return cmds 156 157 def _ns_create(self): 158 ''' 159 Create the network namespace in which the tests will be run and set up 160 the required network devices for it. 161 ''' 162 self._ports_create() 163 self._exec_cmd_batched('pre', self._ns_create_cmds()) 164 165 def _ns_destroy_cmd(self): 166 return self._replace_keywords('netns delete {}'.format(self.args.NAMES['NS'])) 167 168 def _ns_destroy(self): 169 ''' 170 Destroy the network namespace for testing (and any associated network 171 devices as well) 172 ''' 173 self._exec_cmd('post', self._ns_destroy_cmd()) 174 self._ports_destroy() 175 176 @cached_property 177 def _proc(self): 178 ip = self._replace_keywords("$IP -b -") 179 proc = subprocess.Popen(ip, 180 shell=True, 181 stdin=subprocess.PIPE, 182 env=ENVIR) 183 184 return proc 185 186 def _proc_check(self): 187 proc = self._proc 188 189 proc.poll() 190 191 if proc.returncode is not None and proc.returncode != 0: 192 raise RuntimeError("iproute2 exited with an error code") 193 194 def _exec_cmd(self, stage, command): 195 ''' 196 Perform any required modifications on an executable command, then run 197 it in a subprocess and return the results. 198 ''' 199 200 if self.args.verbose > 3: 201 print('_exec_cmd: command "{}"'.format(command)) 202 203 proc = self._proc 204 205 proc.stdin.write((command + '\n').encode()) 206 proc.stdin.flush() 207 208 if self.args.verbose > 3: 209 print('_exec_cmd proc: {}'.format(proc)) 210 211 self._proc_check() 212 213 def _exec_cmd_batched(self, stage, commands): 214 for cmd in commands: 215 self._exec_cmd(stage, cmd) 216 217 def _replace_keywords(self, cmd): 218 """ 219 For a given executable command, substitute any known 220 variables contained within NAMES with the correct values 221 """ 222 tcmd = Template(cmd) 223 subcmd = tcmd.safe_substitute(self.args.NAMES) 224 return subcmd 225