test_utils.py 11 KB

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