utils.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. # SPDX-License-Identifier: GPL-2.0
  2. import errno
  3. import json as _json
  4. import random
  5. import re
  6. import socket
  7. import subprocess
  8. import time
  9. class CmdExitFailure(Exception):
  10. pass
  11. class cmd:
  12. def __init__(self, comm, shell=True, fail=True, ns=None, background=False, host=None, timeout=5):
  13. if ns:
  14. comm = f'ip netns exec {ns} ' + comm
  15. self.stdout = None
  16. self.stderr = None
  17. self.ret = None
  18. self.comm = comm
  19. if host:
  20. self.proc = host.cmd(comm)
  21. else:
  22. self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE,
  23. stderr=subprocess.PIPE)
  24. if not background:
  25. self.process(terminate=False, fail=fail, timeout=timeout)
  26. def process(self, terminate=True, fail=None, timeout=5):
  27. if fail is None:
  28. fail = not terminate
  29. if terminate:
  30. self.proc.terminate()
  31. stdout, stderr = self.proc.communicate(timeout)
  32. self.stdout = stdout.decode("utf-8")
  33. self.stderr = stderr.decode("utf-8")
  34. self.proc.stdout.close()
  35. self.proc.stderr.close()
  36. self.ret = self.proc.returncode
  37. if self.proc.returncode != 0 and fail:
  38. if len(stderr) > 0 and stderr[-1] == "\n":
  39. stderr = stderr[:-1]
  40. raise CmdExitFailure("Command failed: %s\nSTDOUT: %s\nSTDERR: %s" %
  41. (self.proc.args, stdout, stderr))
  42. class bkg(cmd):
  43. def __init__(self, comm, shell=True, fail=None, ns=None, host=None,
  44. exit_wait=False):
  45. super().__init__(comm, background=True,
  46. shell=shell, fail=fail, ns=ns, host=host)
  47. self.terminate = not exit_wait
  48. self.check_fail = fail
  49. def __enter__(self):
  50. return self
  51. def __exit__(self, ex_type, ex_value, ex_tb):
  52. return self.process(terminate=self.terminate, fail=self.check_fail)
  53. global_defer_queue = []
  54. class defer:
  55. def __init__(self, func, *args, **kwargs):
  56. global global_defer_queue
  57. if not callable(func):
  58. raise Exception("defer created with un-callable object, did you call the function instead of passing its name?")
  59. self.func = func
  60. self.args = args
  61. self.kwargs = kwargs
  62. self._queue = global_defer_queue
  63. self._queue.append(self)
  64. def __enter__(self):
  65. return self
  66. def __exit__(self, ex_type, ex_value, ex_tb):
  67. return self.exec()
  68. def exec_only(self):
  69. self.func(*self.args, **self.kwargs)
  70. def cancel(self):
  71. self._queue.remove(self)
  72. def exec(self):
  73. self.cancel()
  74. self.exec_only()
  75. def tool(name, args, json=None, ns=None, host=None):
  76. cmd_str = name + ' '
  77. if json:
  78. cmd_str += '--json '
  79. cmd_str += args
  80. cmd_obj = cmd(cmd_str, ns=ns, host=host)
  81. if json:
  82. return _json.loads(cmd_obj.stdout)
  83. return cmd_obj
  84. def ip(args, json=None, ns=None, host=None):
  85. if ns:
  86. args = f'-netns {ns} ' + args
  87. return tool('ip', args, json=json, host=host)
  88. def ethtool(args, json=None, ns=None, host=None):
  89. return tool('ethtool', args, json=json, ns=ns, host=host)
  90. def rand_port():
  91. """
  92. Get a random unprivileged port, try to make sure it's not already used.
  93. """
  94. for _ in range(1000):
  95. port = random.randint(10000, 65535)
  96. try:
  97. with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
  98. s.bind(("", port))
  99. return port
  100. except OSError as e:
  101. if e.errno != errno.EADDRINUSE:
  102. raise
  103. raise Exception("Can't find any free unprivileged port")
  104. def wait_port_listen(port, proto="tcp", ns=None, host=None, sleep=0.005, deadline=5):
  105. end = time.monotonic() + deadline
  106. pattern = f":{port:04X} .* "
  107. if proto == "tcp": # for tcp protocol additionally check the socket state
  108. pattern += "0A"
  109. pattern = re.compile(pattern)
  110. while True:
  111. data = cmd(f'cat /proc/net/{proto}*', ns=ns, host=host, shell=True).stdout
  112. for row in data.split("\n"):
  113. if pattern.search(row):
  114. return
  115. if time.monotonic() > end:
  116. raise Exception("Waiting for port listen timed out")
  117. time.sleep(sleep)