1import os 2import signal 3from string import Template 4import subprocess 5import time 6from functools import cached_property 7from TdcPlugin import TdcPlugin 8 9from tdc_config import * 10 11class SubPlugin(TdcPlugin): 12 def __init__(self): 13 self.sub_class = 'ns/SubPlugin' 14 super().__init__() 15 16 def pre_suite(self, testcount, testlist): 17 super().pre_suite(testcount, testlist) 18 19 print("Setting up namespaces and devices...") 20 21 original = self.args.NAMES 22 23 for t in testlist: 24 if 'skip' in t and t['skip'] == 'yes': 25 continue 26 27 if 'nsPlugin' not in t['plugins']: 28 continue 29 30 shadow = {} 31 shadow['IP'] = original['IP'] 32 shadow['TC'] = original['TC'] 33 shadow['NS'] = '{}-{}'.format(original['NS'], t['random']) 34 shadow['DEV0'] = '{}id{}'.format(original['DEV0'], t['id']) 35 shadow['DEV1'] = '{}id{}'.format(original['DEV1'], t['id']) 36 shadow['DUMMY'] = '{}id{}'.format(original['DUMMY'], t['id']) 37 shadow['DEV2'] = original['DEV2'] 38 self.args.NAMES = shadow 39 40 if self.args.namespace: 41 self._ns_create() 42 else: 43 self._ports_create() 44 45 self.args.NAMES = original 46 47 def pre_case(self, caseinfo, test_skip): 48 if self.args.verbose: 49 print('{}.pre_case'.format(self.sub_class)) 50 51 if test_skip: 52 return 53 54 # Make sure the netns is visible in the fs 55 while True: 56 self._proc_check() 57 try: 58 ns = self.args.NAMES['NS'] 59 f = open('/run/netns/{}'.format(ns)) 60 f.close() 61 break 62 except: 63 continue 64 65 def post_case(self): 66 if self.args.verbose: 67 print('{}.post_case'.format(self.sub_class)) 68 69 if self.args.namespace: 70 self._ns_destroy() 71 else: 72 self._ports_destroy() 73 74 def post_suite(self, index): 75 if self.args.verbose: 76 print('{}.post_suite'.format(self.sub_class)) 77 78 # Make sure we don't leak resources 79 for f in os.listdir('/run/netns/'): 80 cmd = self._replace_keywords("$IP netns del {}".format(f)) 81 82 if self.args.verbose > 3: 83 print('_exec_cmd: command "{}"'.format(cmd)) 84 85 subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 86 87 def add_args(self, parser): 88 super().add_args(parser) 89 self.argparser_group = self.argparser.add_argument_group( 90 'netns', 91 'options for nsPlugin(run commands in net namespace)') 92 self.argparser_group.add_argument( 93 '-N', '--no-namespace', action='store_false', default=True, 94 dest='namespace', help='Don\'t run commands in namespace') 95 return self.argparser 96 97 def adjust_command(self, stage, command): 98 super().adjust_command(stage, command) 99 cmdform = 'list' 100 cmdlist = list() 101 102 if not self.args.namespace: 103 return command 104 105 if self.args.verbose: 106 print('{}.adjust_command'.format(self.sub_class)) 107 108 if not isinstance(command, list): 109 cmdform = 'str' 110 cmdlist = command.split() 111 else: 112 cmdlist = command 113 if stage == 'setup' or stage == 'execute' or stage == 'verify' or stage == 'teardown': 114 if self.args.verbose: 115 print('adjust_command: stage is {}; inserting netns stuff in command [{}] list [{}]'.format(stage, command, cmdlist)) 116 cmdlist.insert(0, self.args.NAMES['NS']) 117 cmdlist.insert(0, 'exec') 118 cmdlist.insert(0, 'netns') 119 cmdlist.insert(0, self.args.NAMES['IP']) 120 else: 121 pass 122 123 if cmdform == 'str': 124 command = ' '.join(cmdlist) 125 else: 126 command = cmdlist 127 128 if self.args.verbose: 129 print('adjust_command: return command [{}]'.format(command)) 130 return command 131 132 def _ports_create_cmds(self): 133 cmds = [] 134 135 cmds.append(self._replace_keywords('link add $DEV0 type veth peer name $DEV1')) 136 cmds.append(self._replace_keywords('link set $DEV0 up')) 137 cmds.append(self._replace_keywords('link add $DUMMY type dummy')) 138 if not self.args.namespace: 139 cmds.append(self._replace_keywords('link set $DEV1 up')) 140 141 return cmds 142 143 def _ports_create(self): 144 self._exec_cmd_batched('pre', self._ports_create_cmds()) 145 146 def _ports_destroy_cmd(self): 147 return self._replace_keywords('link del $DEV0') 148 149 def _ports_destroy(self): 150 self._exec_cmd('post', self._ports_destroy_cmd()) 151 152 def _ns_create_cmds(self): 153 cmds = [] 154 155 if self.args.namespace: 156 ns = self.args.NAMES['NS'] 157 158 cmds.append(self._replace_keywords('netns add {}'.format(ns))) 159 cmds.append(self._replace_keywords('link set $DEV1 netns {}'.format(ns))) 160 cmds.append(self._replace_keywords('link set $DUMMY netns {}'.format(ns))) 161 cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV1 up'.format(ns))) 162 cmds.append(self._replace_keywords('netns exec {} $IP link set $DUMMY 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._ports_create() 176 self._exec_cmd_batched('pre', self._ns_create_cmds()) 177 178 def _ns_destroy_cmd(self): 179 return self._replace_keywords('netns delete {}'.format(self.args.NAMES['NS'])) 180 181 def _ns_destroy(self): 182 ''' 183 Destroy the network namespace for testing (and any associated network 184 devices as well) 185 ''' 186 if self.args.namespace: 187 self._exec_cmd('post', self._ns_destroy_cmd()) 188 self._ports_destroy() 189 190 @cached_property 191 def _proc(self): 192 ip = self._replace_keywords("$IP -b -") 193 proc = subprocess.Popen(ip, 194 shell=True, 195 stdin=subprocess.PIPE, 196 env=ENVIR) 197 198 return proc 199 200 def _proc_check(self): 201 proc = self._proc 202 203 proc.poll() 204 205 if proc.returncode is not None and proc.returncode != 0: 206 raise RuntimeError("iproute2 exited with an error code") 207 208 def _exec_cmd(self, stage, command): 209 ''' 210 Perform any required modifications on an executable command, then run 211 it in a subprocess and return the results. 212 ''' 213 214 if self.args.verbose > 3: 215 print('_exec_cmd: command "{}"'.format(command)) 216 217 proc = self._proc 218 219 proc.stdin.write((command + '\n').encode()) 220 proc.stdin.flush() 221 222 if self.args.verbose > 3: 223 print('_exec_cmd proc: {}'.format(proc)) 224 225 self._proc_check() 226 227 def _exec_cmd_batched(self, stage, commands): 228 for cmd in commands: 229 self._exec_cmd(stage, cmd) 230 231 def _replace_keywords(self, cmd): 232 """ 233 For a given executable command, substitute any known 234 variables contained within NAMES with the correct values 235 """ 236 tcmd = Template(cmd) 237 subcmd = tcmd.safe_substitute(self.args.NAMES) 238 return subcmd 239