builder_tests.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. # Copyright (C) 2013 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. import unittest
  16. import os
  17. import socket
  18. import select
  19. import threading
  20. import isc.log
  21. from isc.dns import *
  22. import isc.datasrc
  23. from isc.memmgr.builder import *
  24. from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr
  25. from isc.memmgr.datasrc_info import *
  26. TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
  27. # Defined for easier tests with DataSrcClientsMgr.reconfigure(), which
  28. # only needs get_value() method
  29. class MockConfigData:
  30. def __init__(self, data):
  31. self.__data = data
  32. def get_value(self, identifier):
  33. return self.__data[identifier], False
  34. class TestMemorySegmentBuilder(unittest.TestCase):
  35. def _create_builder_thread(self):
  36. (self._master_sock, self._builder_sock) = \
  37. socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
  38. self._builder_command_queue = []
  39. self._builder_response_queue = []
  40. self._builder_lock = threading.Lock()
  41. self._builder_cv = threading.Condition(lock=self._builder_lock)
  42. self._builder = MemorySegmentBuilder(self._builder_sock,
  43. self._builder_cv,
  44. self._builder_command_queue,
  45. self._builder_response_queue)
  46. self._builder_thread = threading.Thread(target=self._builder.run)
  47. def setUp(self):
  48. self._create_builder_thread()
  49. self.__mapped_file_path = None
  50. def tearDown(self):
  51. # It's the tests' responsibility to stop and join the builder
  52. # thread if they start it.
  53. self.assertFalse(self._builder_thread.isAlive())
  54. self._master_sock.close()
  55. self._builder_sock.close()
  56. if self.__mapped_file_path is not None:
  57. if os.path.exists(self.__mapped_file_path):
  58. os.unlink(self.__mapped_file_path)
  59. def test_bad_command(self):
  60. """Tests what happens when a bad command is passed to the
  61. MemorySegmentBuilder.
  62. """
  63. self._builder_thread.start()
  64. # Now that the builder thread is running, send it a bad
  65. # command. The thread should exit its main loop and be joinable.
  66. with self._builder_cv:
  67. self._builder_command_queue.append(('bad_command',))
  68. self._builder_cv.notify_all()
  69. # Wait 5 seconds to receive a notification on the socket from
  70. # the builder.
  71. (reads, _, _) = select.select([self._master_sock], [], [], 5)
  72. self.assertTrue(self._master_sock in reads)
  73. # Reading 1 byte should not block us here, especially as the
  74. # socket is ready to read. It's a hack, but this is just a
  75. # testcase.
  76. got = self._master_sock.recv(1)
  77. self.assertEqual(got, b'x')
  78. # Wait 5 seconds at most for the main loop of the builder to
  79. # exit.
  80. self._builder_thread.join(5)
  81. self.assertFalse(self._builder_thread.isAlive())
  82. # The command queue must be cleared, and the response queue must
  83. # contain a response that a bad command was sent. The thread is
  84. # no longer running, so we can use the queues without a lock.
  85. self.assertEqual(len(self._builder_command_queue), 0)
  86. self.assertEqual(len(self._builder_response_queue), 1)
  87. response = self._builder_response_queue[0]
  88. self.assertTrue(isinstance(response, tuple))
  89. self.assertTupleEqual(response, ('bad_command',))
  90. del self._builder_response_queue[:]
  91. def test_shutdown(self):
  92. """Tests that shutdown command exits the MemorySegmentBuilder
  93. loop.
  94. """
  95. self._builder_thread.start()
  96. # Now that the builder thread is running, send it the "shutdown"
  97. # command. The thread should exit its main loop and be joinable.
  98. with self._builder_cv:
  99. self._builder_command_queue.append(('shutdown',))
  100. # Commands after 'shutdown' must be ignored.
  101. self._builder_command_queue.append(('bad_command_1',))
  102. self._builder_command_queue.append(('bad_command_2',))
  103. self._builder_cv.notify_all()
  104. # Wait 5 seconds at most for the main loop of the builder to
  105. # exit.
  106. self._builder_thread.join(5)
  107. self.assertFalse(self._builder_thread.isAlive())
  108. # The command queue must be cleared, and the response queue must
  109. # be untouched (we don't use it in this test). The thread is no
  110. # longer running, so we can use the queues without a lock.
  111. self.assertEqual(len(self._builder_command_queue), 0)
  112. self.assertEqual(len(self._builder_response_queue), 0)
  113. @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
  114. 'shared memory is not available')
  115. def test_load(self):
  116. """
  117. Test "load" command.
  118. """
  119. mapped_file_dir = os.environ['TESTDATA_WRITE_PATH']
  120. mgr_config = {'mapped_file_dir': mapped_file_dir}
  121. cfg_data = MockConfigData(
  122. {"classes":
  123. {"IN": [{"type": "MasterFiles",
  124. "params": { "example.com": TESTDATA_PATH + "example.com.zone" },
  125. "cache-enable": True,
  126. "cache-type": "mapped"}]
  127. }
  128. })
  129. cmgr = DataSrcClientsMgr(use_cache=True)
  130. cmgr.reconfigure({}, cfg_data)
  131. genid, clients_map = cmgr.get_clients_map()
  132. datasrc_info = DataSrcInfo(genid, clients_map, mgr_config)
  133. self.assertEqual(1, datasrc_info.gen_id)
  134. self.assertEqual(clients_map, datasrc_info.clients_map)
  135. self.assertEqual(1, len(datasrc_info.segment_info_map))
  136. sgmt_info = datasrc_info.segment_info_map[(RRClass.IN, 'MasterFiles')]
  137. self.assertIsNone(sgmt_info.get_reset_param(SegmentInfo.READER))
  138. self.assertIsNotNone(sgmt_info.get_reset_param(SegmentInfo.WRITER))
  139. param = sgmt_info.get_reset_param(SegmentInfo.WRITER)
  140. self.__mapped_file_path = param['mapped-file']
  141. self._builder_thread.start()
  142. # Now that the builder thread is running, send it the "load"
  143. # command. We should be notified when the load operation is
  144. # complete.
  145. with self._builder_cv:
  146. self._builder_command_queue.append(('load',
  147. isc.dns.Name("example.com"),
  148. datasrc_info, RRClass.IN,
  149. 'MasterFiles'))
  150. self._builder_cv.notify_all()
  151. # Wait 60 seconds to receive a notification on the socket from
  152. # the builder.
  153. (reads, _, _) = select.select([self._master_sock], [], [], 60)
  154. self.assertTrue(self._master_sock in reads)
  155. # Reading 1 byte should not block us here, especially as the
  156. # socket is ready to read. It's a hack, but this is just a
  157. # testcase.
  158. got = self._master_sock.recv(1)
  159. self.assertEqual(got, b'x')
  160. with self._builder_lock:
  161. # The command queue must be cleared, and the response queue
  162. # must contain a response that a bad command was sent. The
  163. # thread is no longer running, so we can use the queues
  164. # without a lock.
  165. self.assertEqual(len(self._builder_command_queue), 0)
  166. self.assertEqual(len(self._builder_response_queue), 1)
  167. response = self._builder_response_queue[0]
  168. self.assertTrue(isinstance(response, tuple))
  169. self.assertTupleEqual(response, ('load-completed', datasrc_info,
  170. RRClass.IN, 'MasterFiles'))
  171. del self._builder_response_queue[:]
  172. # Now try looking for some loaded data
  173. clist = datasrc_info.clients_map[RRClass.IN]
  174. dsrc, finder, exact = clist.find(isc.dns.Name("example.com"))
  175. self.assertIsNotNone(dsrc)
  176. self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
  177. self.assertIsNotNone(finder)
  178. self.assertTrue(isinstance(finder, isc.datasrc.ZoneFinder))
  179. self.assertTrue(exact)
  180. # Send the builder thread the "shutdown" command. The thread
  181. # should exit its main loop and be joinable.
  182. with self._builder_cv:
  183. self._builder_command_queue.append(('shutdown',))
  184. self._builder_cv.notify_all()
  185. # Wait 5 seconds at most for the main loop of the builder to
  186. # exit.
  187. self._builder_thread.join(5)
  188. self.assertFalse(self._builder_thread.isAlive())
  189. # The command queue must be cleared, and the response queue must
  190. # be untouched (we don't use it in this test). The thread is no
  191. # longer running, so we can use the queues without a lock.
  192. self.assertEqual(len(self._builder_command_queue), 0)
  193. self.assertEqual(len(self._builder_response_queue), 0)
  194. if __name__ == "__main__":
  195. isc.log.init("bind10-test")
  196. isc.log.resetUnitTestRootLogger()
  197. unittest.main()