nsPlugin.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import os
  2. import signal
  3. from string import Template
  4. import subprocess
  5. import time
  6. from multiprocessing import Pool
  7. from functools import cached_property
  8. from TdcPlugin import TdcPlugin
  9. from tdc_config import *
  10. try:
  11. from pyroute2 import netns
  12. from pyroute2 import IPRoute
  13. netlink = True
  14. except ImportError:
  15. netlink = False
  16. print("!!! Consider installing pyroute2 !!!")
  17. class SubPlugin(TdcPlugin):
  18. def __init__(self):
  19. self.sub_class = 'ns/SubPlugin'
  20. super().__init__()
  21. def pre_suite(self, testcount, testlist):
  22. super().pre_suite(testcount, testlist)
  23. def prepare_test(self, test):
  24. if 'skip' in test and test['skip'] == 'yes':
  25. return
  26. if 'nsPlugin' not in test['plugins']:
  27. return
  28. if netlink == True:
  29. self._nl_ns_create()
  30. else:
  31. self._ipr2_ns_create()
  32. # Make sure the netns is visible in the fs
  33. ticks = 20
  34. while True:
  35. if ticks == 0:
  36. raise TimeoutError
  37. self._proc_check()
  38. try:
  39. ns = self.args.NAMES['NS']
  40. f = open('/run/netns/{}'.format(ns))
  41. f.close()
  42. break
  43. except:
  44. time.sleep(0.1)
  45. ticks -= 1
  46. continue
  47. def pre_case(self, test, test_skip):
  48. if self.args.verbose:
  49. print('{}.pre_case'.format(self.sub_class))
  50. if test_skip:
  51. return
  52. self.prepare_test(test)
  53. def post_case(self):
  54. if self.args.verbose:
  55. print('{}.post_case'.format(self.sub_class))
  56. if netlink == True:
  57. self._nl_ns_destroy()
  58. else:
  59. self._ipr2_ns_destroy()
  60. def post_suite(self, index):
  61. if self.args.verbose:
  62. print('{}.post_suite'.format(self.sub_class))
  63. # Make sure we don't leak resources
  64. cmd = self._replace_keywords("$IP -a netns del")
  65. if self.args.verbose > 3:
  66. print('_exec_cmd: command "{}"'.format(cmd))
  67. subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  68. def adjust_command(self, stage, command):
  69. super().adjust_command(stage, command)
  70. cmdform = 'list'
  71. cmdlist = list()
  72. if self.args.verbose:
  73. print('{}.adjust_command'.format(self.sub_class))
  74. if not isinstance(command, list):
  75. cmdform = 'str'
  76. cmdlist = command.split()
  77. else:
  78. cmdlist = command
  79. if stage == 'setup' or stage == 'execute' or stage == 'verify' or stage == 'teardown':
  80. if self.args.verbose:
  81. print('adjust_command: stage is {}; inserting netns stuff in command [{}] list [{}]'.format(stage, command, cmdlist))
  82. cmdlist.insert(0, self.args.NAMES['NS'])
  83. cmdlist.insert(0, 'exec')
  84. cmdlist.insert(0, 'netns')
  85. cmdlist.insert(0, self.args.NAMES['IP'])
  86. else:
  87. pass
  88. if cmdform == 'str':
  89. command = ' '.join(cmdlist)
  90. else:
  91. command = cmdlist
  92. if self.args.verbose:
  93. print('adjust_command: return command [{}]'.format(command))
  94. return command
  95. def _nl_ns_create(self):
  96. ns = self.args.NAMES["NS"];
  97. dev0 = self.args.NAMES["DEV0"];
  98. dev1 = self.args.NAMES["DEV1"];
  99. dummy = self.args.NAMES["DUMMY"];
  100. if self.args.verbose:
  101. print('{}._nl_ns_create'.format(self.sub_class))
  102. netns.create(ns)
  103. netns.pushns(newns=ns)
  104. with IPRoute() as ip:
  105. ip.link('add', ifname=dev1, kind='veth', peer={'ifname': dev0, 'net_ns_fd':'/proc/1/ns/net'})
  106. ip.link('add', ifname=dummy, kind='dummy')
  107. ticks = 20
  108. while True:
  109. if ticks == 0:
  110. raise TimeoutError
  111. try:
  112. dev1_idx = ip.link_lookup(ifname=dev1)[0]
  113. dummy_idx = ip.link_lookup(ifname=dummy)[0]
  114. ip.link('set', index=dev1_idx, state='up')
  115. ip.link('set', index=dummy_idx, state='up')
  116. break
  117. except:
  118. time.sleep(0.1)
  119. ticks -= 1
  120. continue
  121. netns.popns()
  122. with IPRoute() as ip:
  123. ticks = 20
  124. while True:
  125. if ticks == 0:
  126. raise TimeoutError
  127. try:
  128. dev0_idx = ip.link_lookup(ifname=dev0)[0]
  129. ip.link('set', index=dev0_idx, state='up')
  130. break
  131. except:
  132. time.sleep(0.1)
  133. ticks -= 1
  134. continue
  135. def _ipr2_ns_create_cmds(self):
  136. cmds = []
  137. ns = self.args.NAMES['NS']
  138. cmds.append(self._replace_keywords('netns add {}'.format(ns)))
  139. cmds.append(self._replace_keywords('link add $DEV1 type veth peer name $DEV0'))
  140. cmds.append(self._replace_keywords('link set $DEV1 netns {}'.format(ns)))
  141. cmds.append(self._replace_keywords('link add $DUMMY type dummy'.format(ns)))
  142. cmds.append(self._replace_keywords('link set $DUMMY netns {}'.format(ns)))
  143. cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV1 up'.format(ns)))
  144. cmds.append(self._replace_keywords('netns exec {} $IP link set $DUMMY up'.format(ns)))
  145. cmds.append(self._replace_keywords('link set $DEV0 up'.format(ns)))
  146. if self.args.device:
  147. cmds.append(self._replace_keywords('link set $DEV2 netns {}'.format(ns)))
  148. cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV2 up'.format(ns)))
  149. return cmds
  150. def _ipr2_ns_create(self):
  151. '''
  152. Create the network namespace in which the tests will be run and set up
  153. the required network devices for it.
  154. '''
  155. self._exec_cmd_batched('pre', self._ipr2_ns_create_cmds())
  156. def _nl_ns_destroy(self):
  157. ns = self.args.NAMES['NS']
  158. netns.remove(ns)
  159. def _ipr2_ns_destroy_cmd(self):
  160. return self._replace_keywords('netns delete {}'.format(self.args.NAMES['NS']))
  161. def _ipr2_ns_destroy(self):
  162. '''
  163. Destroy the network namespace for testing (and any associated network
  164. devices as well)
  165. '''
  166. self._exec_cmd('post', self._ipr2_ns_destroy_cmd())
  167. @cached_property
  168. def _proc(self):
  169. ip = self._replace_keywords("$IP -b -")
  170. proc = subprocess.Popen(ip,
  171. shell=True,
  172. stdin=subprocess.PIPE,
  173. env=ENVIR)
  174. return proc
  175. def _proc_check(self):
  176. proc = self._proc
  177. proc.poll()
  178. if proc.returncode is not None and proc.returncode != 0:
  179. raise RuntimeError("iproute2 exited with an error code")
  180. def _exec_cmd(self, stage, command):
  181. '''
  182. Perform any required modifications on an executable command, then run
  183. it in a subprocess and return the results.
  184. '''
  185. if self.args.verbose > 3:
  186. print('_exec_cmd: command "{}"'.format(command))
  187. proc = self._proc
  188. proc.stdin.write((command + '\n').encode())
  189. proc.stdin.flush()
  190. if self.args.verbose > 3:
  191. print('_exec_cmd proc: {}'.format(proc))
  192. self._proc_check()
  193. def _exec_cmd_batched(self, stage, commands):
  194. for cmd in commands:
  195. self._exec_cmd(stage, cmd)
  196. def _replace_keywords(self, cmd):
  197. """
  198. For a given executable command, substitute any known
  199. variables contained within NAMES with the correct values
  200. """
  201. tcmd = Template(cmd)
  202. subcmd = tcmd.safe_substitute(self.args.NAMES)
  203. return subcmd