test_utils.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. """
  2. Utilities and mock modules for unittests of statistics modules
  3. """
  4. import os
  5. import io
  6. import time
  7. import sys
  8. import threading
  9. import tempfile
  10. import json
  11. import signal
  12. import msgq
  13. import isc.config.cfgmgr
  14. import stats
  15. import stats_httpd
  16. # Change value of BIND10_MSGQ_SOCKET_FILE in environment variables
  17. if 'BIND10_MSGQ_SOCKET_FILE' not in os.environ:
  18. os.environ['BIND10_MSGQ_SOCKET_FILE'] = tempfile.mktemp(prefix='msgq_socket_')
  19. class SignalHandler():
  20. """A signal handler class for deadlock in unittest"""
  21. def __init__(self, fail_handler, timeout=20):
  22. """sets a schedule in SIGARM for invoking the handler via
  23. unittest.TestCase after timeout seconds (default is 20)"""
  24. self.fail_handler = fail_handler
  25. self.orig_handler = signal.signal(signal.SIGALRM, self.sig_handler)
  26. signal.alarm(timeout)
  27. def reset(self):
  28. """resets the schedule in SIGALRM"""
  29. signal.alarm(0)
  30. signal.signal(signal.SIGALRM, self.orig_handler)
  31. def sig_handler(self, signal, frame):
  32. """envokes unittest.TestCase.fail as a signal handler"""
  33. self.fail_handler("A deadlock might be detected")
  34. def send_command(command_name, module_name, params=None, session=None, nonblock=False, timeout=None):
  35. if session is not None:
  36. cc_session = session
  37. else:
  38. cc_session = isc.cc.Session()
  39. if timeout is not None:
  40. orig_timeout = cc_session.get_timeout()
  41. cc_session.set_timeout(timeout * 1000)
  42. command = isc.config.ccsession.create_command(command_name, params)
  43. seq = cc_session.group_sendmsg(command, module_name)
  44. try:
  45. (answer, env) = cc_session.group_recvmsg(nonblock, seq)
  46. if answer:
  47. return isc.config.ccsession.parse_answer(answer)
  48. except isc.cc.SessionTimeout:
  49. pass
  50. finally:
  51. if timeout is not None:
  52. cc_session.set_timeout(orig_timeout)
  53. if session is None:
  54. cc_session.close()
  55. def send_shutdown(module_name, **kwargs):
  56. return send_command("shutdown", module_name, **kwargs)
  57. class ThreadingServerManager:
  58. def __init__(self, server, *args, **kwargs):
  59. self.server = None
  60. n = 0
  61. while True:
  62. try:
  63. self.server = server(*args, **kwargs)
  64. except isc.cc.session.SessionTimeout:
  65. if self.server is not None:
  66. self.server.shutdown()
  67. # retrying until 3 times
  68. if n >2: raise
  69. n = n + 1
  70. continue
  71. else: break
  72. self.server_name = server.__name__
  73. self.server._thread = threading.Thread(
  74. name=self.server_name, target=self.server.run)
  75. self.server._thread.daemon = True
  76. def run(self):
  77. self.server._thread.start()
  78. self.server._started.wait()
  79. self.server._started.clear()
  80. def shutdown(self):
  81. self.server.shutdown()
  82. self.server._thread.join(0) # timeout is 0
  83. def do_nothing(*args, **kwargs): pass
  84. class dummy_sys:
  85. """Dummy for sys"""
  86. class dummy_io:
  87. write = do_nothing
  88. stdout = stderr = dummy_io()
  89. class MockMsgq:
  90. def __init__(self):
  91. self._started = threading.Event()
  92. # suppress output to stdout and stderr
  93. msgq.sys = dummy_sys()
  94. msgq.print = do_nothing
  95. self.msgq = msgq.MsgQ(verbose=False)
  96. result = self.msgq.setup()
  97. if result:
  98. sys.exit("Error on Msgq startup: %s" % result)
  99. def run(self):
  100. self._started.set()
  101. try:
  102. self.msgq.run()
  103. except Exception:
  104. pass
  105. finally:
  106. # explicitly shut down the socket of the msgq before
  107. # shutting down the msgq
  108. self.msgq.listen_socket.shutdown(msgq.socket.SHUT_RDWR)
  109. self.msgq.shutdown()
  110. def shutdown(self):
  111. # do nothing for avoiding shutting down the msgq twice
  112. pass
  113. class MockCfgmgr:
  114. def __init__(self):
  115. self._started = threading.Event()
  116. self.cfgmgr = isc.config.cfgmgr.ConfigManager(
  117. os.environ['CONFIG_TESTDATA_PATH'], "b10-config.db")
  118. self.cfgmgr.read_config()
  119. def run(self):
  120. self._started.set()
  121. try:
  122. self.cfgmgr.run()
  123. except Exception:
  124. pass
  125. def shutdown(self):
  126. self.cfgmgr.running = False
  127. class MockBoss:
  128. spec_str = """\
  129. {
  130. "module_spec": {
  131. "module_name": "Boss",
  132. "module_description": "Mock Master process",
  133. "config_data": [],
  134. "commands": [
  135. {
  136. "command_name": "sendstats",
  137. "command_description": "Send data to a statistics module at once",
  138. "command_args": []
  139. }
  140. ],
  141. "statistics": [
  142. {
  143. "item_name": "boot_time",
  144. "item_type": "string",
  145. "item_optional": false,
  146. "item_default": "1970-01-01T00:00:00Z",
  147. "item_title": "Boot time",
  148. "item_description": "A date time when bind10 process starts initially",
  149. "item_format": "date-time"
  150. }
  151. ]
  152. }
  153. }
  154. """
  155. _BASETIME = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
  156. def __init__(self):
  157. self._started = threading.Event()
  158. self.running = False
  159. self.spec_file = io.StringIO(self.spec_str)
  160. # create ModuleCCSession object
  161. self.mccs = isc.config.ModuleCCSession(
  162. self.spec_file,
  163. self.config_handler,
  164. self.command_handler)
  165. self.spec_file.close()
  166. self.cc_session = self.mccs._session
  167. self.got_command_name = ''
  168. def run(self):
  169. self.mccs.start()
  170. self.running = True
  171. self._started.set()
  172. try:
  173. while self.running:
  174. self.mccs.check_command(False)
  175. except Exception:
  176. pass
  177. def shutdown(self):
  178. self.running = False
  179. def config_handler(self, new_config):
  180. return isc.config.create_answer(0)
  181. def command_handler(self, command, *args, **kwargs):
  182. self._started.set()
  183. self.got_command_name = command
  184. params = { "owner": "Boss",
  185. "data": {
  186. 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', self._BASETIME)
  187. }
  188. }
  189. if command == 'sendstats':
  190. send_command("set", "Stats", params=params, session=self.cc_session)
  191. return isc.config.create_answer(0)
  192. elif command == 'getstats':
  193. return isc.config.create_answer(0, params)
  194. return isc.config.create_answer(1, "Unknown Command")
  195. class MockAuth:
  196. spec_str = """\
  197. {
  198. "module_spec": {
  199. "module_name": "Auth",
  200. "module_description": "Mock Authoritative service",
  201. "config_data": [],
  202. "commands": [
  203. {
  204. "command_name": "sendstats",
  205. "command_description": "Send data to a statistics module at once",
  206. "command_args": []
  207. }
  208. ],
  209. "statistics": [
  210. {
  211. "item_name": "queries.tcp",
  212. "item_type": "integer",
  213. "item_optional": false,
  214. "item_default": 0,
  215. "item_title": "Queries TCP",
  216. "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
  217. },
  218. {
  219. "item_name": "queries.udp",
  220. "item_type": "integer",
  221. "item_optional": false,
  222. "item_default": 0,
  223. "item_title": "Queries UDP",
  224. "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
  225. }
  226. ]
  227. }
  228. }
  229. """
  230. def __init__(self):
  231. self._started = threading.Event()
  232. self.running = False
  233. self.spec_file = io.StringIO(self.spec_str)
  234. # create ModuleCCSession object
  235. self.mccs = isc.config.ModuleCCSession(
  236. self.spec_file,
  237. self.config_handler,
  238. self.command_handler)
  239. self.spec_file.close()
  240. self.cc_session = self.mccs._session
  241. self.got_command_name = ''
  242. self.queries_tcp = 3
  243. self.queries_udp = 2
  244. def run(self):
  245. self.mccs.start()
  246. self.running = True
  247. self._started.set()
  248. try:
  249. while self.running:
  250. self.mccs.check_command(False)
  251. except Exception:
  252. pass
  253. def shutdown(self):
  254. self.running = False
  255. def config_handler(self, new_config):
  256. return isc.config.create_answer(0)
  257. def command_handler(self, command, *args, **kwargs):
  258. self.got_command_name = command
  259. if command == 'sendstats':
  260. params = { "owner": "Auth",
  261. "data": { 'queries.tcp': self.queries_tcp,
  262. 'queries.udp': self.queries_udp } }
  263. return send_command("set", "Stats", params=params, session=self.cc_session)
  264. return isc.config.create_answer(1, "Unknown Command")
  265. class MyStats(stats.Stats):
  266. def __init__(self):
  267. self._started = threading.Event()
  268. stats.Stats.__init__(self)
  269. def run(self):
  270. self._started.set()
  271. try:
  272. self.start()
  273. except Exception:
  274. pass
  275. def shutdown(self):
  276. self.command_shutdown()
  277. class MyStatsHttpd(stats_httpd.StatsHttpd):
  278. ORIG_SPECFILE_LOCATION = stats_httpd.SPECFILE_LOCATION
  279. def __init__(self, *server_address):
  280. self._started = threading.Event()
  281. if server_address:
  282. stats_httpd.SPECFILE_LOCATION = self.create_specfile(*server_address)
  283. try:
  284. stats_httpd.StatsHttpd.__init__(self)
  285. finally:
  286. if hasattr(stats_httpd.SPECFILE_LOCATION, "close"):
  287. stats_httpd.SPECFILE_LOCATION.close()
  288. stats_httpd.SPECFILE_LOCATION = self.ORIG_SPECFILE_LOCATION
  289. else:
  290. stats_httpd.StatsHttpd.__init__(self)
  291. def create_specfile(self, *server_address):
  292. spec_io = open(self.ORIG_SPECFILE_LOCATION)
  293. try:
  294. spec = json.load(spec_io)
  295. spec_io.close()
  296. config = spec['module_spec']['config_data']
  297. for i in range(len(config)):
  298. if config[i]['item_name'] == 'listen_on':
  299. config[i]['item_default'] = \
  300. [ dict(address=a[0], port=a[1]) for a in server_address ]
  301. break
  302. return io.StringIO(json.dumps(spec))
  303. finally:
  304. spec_io.close()
  305. def run(self):
  306. self._started.set()
  307. try:
  308. self.start()
  309. except Exception:
  310. pass
  311. def shutdown(self):
  312. self.command_handler('shutdown', None)
  313. class BaseModules:
  314. def __init__(self):
  315. # MockMsgq
  316. self.msgq = ThreadingServerManager(MockMsgq)
  317. self.msgq.run()
  318. # Check whether msgq is ready. A SessionTimeout is raised here if not.
  319. isc.cc.session.Session().close()
  320. # MockCfgmgr
  321. self.cfgmgr = ThreadingServerManager(MockCfgmgr)
  322. self.cfgmgr.run()
  323. # MockBoss
  324. self.boss = ThreadingServerManager(MockBoss)
  325. self.boss.run()
  326. # MockAuth
  327. self.auth = ThreadingServerManager(MockAuth)
  328. self.auth.run()
  329. def shutdown(self):
  330. # MockAuth
  331. self.auth.shutdown()
  332. # MockBoss
  333. self.boss.shutdown()
  334. # MockCfgmgr
  335. self.cfgmgr.shutdown()
  336. # MockMsgq
  337. self.msgq.shutdown()