notify_out_test.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. # Copyright (C) 2010 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 sys
  17. import os
  18. import tempfile
  19. import time
  20. import socket
  21. from isc.datasrc import sqlite3_ds
  22. from isc.notify import notify_out, SOCK_DATA
  23. # our fake socket, where we can read and insert messages
  24. class MockSocket():
  25. def __init__(self, family, type):
  26. self.family = family
  27. self.type = type
  28. self._local_sock, self._remote_sock = socket.socketpair()
  29. def connect(self, to):
  30. pass
  31. def fileno(self):
  32. return self._local_sock.fileno()
  33. def close(self):
  34. self._local_sock.close()
  35. self._remote_sock.close()
  36. def sendto(self, data, flag, dst):
  37. return self._local_sock.send(data)
  38. def recvfrom(self, length):
  39. data = self._local_sock.recv(length)
  40. return (data, None)
  41. # provide a remote end which can write data to MockSocket for testing.
  42. def remote_end(self):
  43. return self._remote_sock
  44. # We subclass the ZoneNotifyInfo class we're testing here, only
  45. # to override the prepare_notify_out() method.
  46. class MockZoneNotifyInfo(notify_out.ZoneNotifyInfo):
  47. def prepare_notify_out(self):
  48. super().prepare_notify_out();
  49. self._sock.close()
  50. self._sock = MockSocket(socket.AF_INET, socket.SOCK_DGRAM)
  51. class TestZoneNotifyInfo(unittest.TestCase):
  52. def setUp(self):
  53. self.info = notify_out.ZoneNotifyInfo('example.net.', 'IN')
  54. def test_prepare_finish_notify_out(self):
  55. self.info.prepare_notify_out()
  56. self.assertNotEqual(self.info._sock, None)
  57. self.assertIsNone(self.info._notify_current)
  58. self.info.finish_notify_out()
  59. self.assertEqual(self.info._sock, None)
  60. def test_set_next_notify_target(self):
  61. self.info.notify_slaves.append(('127.0.0.1', 53))
  62. self.info.notify_slaves.append(('1.1.1.1', 5353))
  63. self.info.prepare_notify_out()
  64. self.assertEqual(self.info.get_current_notify_target(), ('127.0.0.1', 53))
  65. self.assertEqual('127.0.0.1#53', notify_out.addr_to_str(('127.0.0.1', 53)))
  66. self.info.set_next_notify_target()
  67. self.assertEqual(self.info.get_current_notify_target(), ('1.1.1.1', 5353))
  68. self.info.set_next_notify_target()
  69. self.assertIsNone(self.info.get_current_notify_target())
  70. temp_info = notify_out.ZoneNotifyInfo('example.com.', 'IN')
  71. temp_info.prepare_notify_out()
  72. self.assertIsNone(temp_info.get_current_notify_target())
  73. class TestNotifyOut(unittest.TestCase):
  74. def setUp(self):
  75. self._db_file = tempfile.NamedTemporaryFile(delete=False)
  76. sqlite3_ds.load(self._db_file.name, 'example.net.', self._example_net_data_reader)
  77. sqlite3_ds.load(self._db_file.name, 'example.com.', self._example_com_data_reader)
  78. self._notify = notify_out.NotifyOut(self._db_file.name)
  79. self._notify._notify_infos[('example.com.', 'IN')] = MockZoneNotifyInfo('example.com.', 'IN')
  80. self._notify._notify_infos[('example.com.', 'CH')] = MockZoneNotifyInfo('example.com.', 'CH')
  81. self._notify._notify_infos[('example.net.', 'IN')] = MockZoneNotifyInfo('example.net.', 'IN')
  82. self._notify._notify_infos[('example.org.', 'IN')] = MockZoneNotifyInfo('example.org.', 'IN')
  83. self._notify._notify_infos[('example.org.', 'CH')] = MockZoneNotifyInfo('example.org.', 'CH')
  84. net_info = self._notify._notify_infos[('example.net.', 'IN')]
  85. net_info.notify_slaves.append(('127.0.0.1', 53))
  86. net_info.notify_slaves.append(('1.1.1.1', 5353))
  87. com_info = self._notify._notify_infos[('example.com.', 'IN')]
  88. com_info.notify_slaves.append(('1.1.1.1', 5353))
  89. com_ch_info = self._notify._notify_infos[('example.com.', 'CH')]
  90. com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
  91. def tearDown(self):
  92. self._db_file.close()
  93. os.unlink(self._db_file.name)
  94. def test_send_notify(self):
  95. notify_out._MAX_NOTIFY_NUM = 2
  96. self._notify.send_notify('example.net')
  97. self.assertEqual(self._notify.notify_num, 1)
  98. self.assertEqual(self._notify._notifying_zones[0], ('example.net.', 'IN'))
  99. self._notify.send_notify('example.com')
  100. self.assertEqual(self._notify.notify_num, 2)
  101. self.assertEqual(self._notify._notifying_zones[1], ('example.com.', 'IN'))
  102. # notify_num is equal to MAX_NOTIFY_NUM, append it to waiting_zones list.
  103. self._notify.send_notify('example.com', 'CH')
  104. self.assertEqual(self._notify.notify_num, 2)
  105. self.assertEqual(1, len(self._notify._waiting_zones))
  106. # zone_id is already in notifying_zones list, append it to waiting_zones list.
  107. self._notify.send_notify('example.net')
  108. self.assertEqual(2, len(self._notify._waiting_zones))
  109. self.assertEqual(self._notify._waiting_zones[1], ('example.net.', 'IN'))
  110. # zone_id is already in waiting_zones list, skip it.
  111. self._notify.send_notify('example.net')
  112. self.assertEqual(2, len(self._notify._waiting_zones))
  113. # has no slave masters, skip it.
  114. self._notify.send_notify('example.org.', 'CH')
  115. self.assertEqual(self._notify.notify_num, 2)
  116. self.assertEqual(2, len(self._notify._waiting_zones))
  117. self._notify.send_notify('example.org.')
  118. self.assertEqual(self._notify.notify_num, 2)
  119. self.assertEqual(2, len(self._notify._waiting_zones))
  120. def test_wait_for_notify_reply(self):
  121. self._notify.send_notify('example.net.')
  122. self._notify.send_notify('example.com.')
  123. notify_out._MAX_NOTIFY_NUM = 2
  124. self._notify.send_notify('example.org.')
  125. replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
  126. self.assertEqual(len(replied_zones), 0)
  127. self.assertEqual(len(timeout_zones), 2)
  128. # Now make one socket be readable
  129. self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 10
  130. self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 10
  131. #Send some data to socket 12340, to make the target socket be readable
  132. self._notify._notify_infos[('example.net.', 'IN')]._sock.remote_end().send(b'data')
  133. replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
  134. self.assertEqual(len(replied_zones), 1)
  135. self.assertEqual(len(timeout_zones), 1)
  136. self.assertTrue(('example.net.', 'IN') in replied_zones.keys())
  137. self.assertTrue(('example.com.', 'IN') in timeout_zones.keys())
  138. self.assertLess(time.time(), self._notify._notify_infos[('example.com.', 'IN')].notify_timeout)
  139. def test_wait_for_notify_reply_2(self):
  140. # Test the returned value when the read_side socket is readable.
  141. self._notify.send_notify('example.net.')
  142. self._notify.send_notify('example.com.')
  143. # Now make one socket be readable
  144. self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 10
  145. self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 10
  146. self._notify._read_sock, self._notify._write_sock = socket.socketpair()
  147. self._notify._write_sock.send(SOCK_DATA)
  148. replied_zones, timeout_zones = self._notify._wait_for_notify_reply()
  149. self.assertEqual(0, len(replied_zones))
  150. self.assertEqual(0, len(timeout_zones))
  151. def test_notify_next_target(self):
  152. self._notify.send_notify('example.net.')
  153. self._notify.send_notify('example.com.')
  154. notify_out._MAX_NOTIFY_NUM = 2
  155. # zone example.org. has no slave servers.
  156. self._notify.send_notify('example.org.')
  157. self._notify.send_notify('example.com.', 'CH')
  158. info = self._notify._notify_infos[('example.net.', 'IN')]
  159. self._notify._notify_next_target(info)
  160. self.assertEqual(0, info.notify_try_num)
  161. self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
  162. self.assertEqual(2, self._notify.notify_num)
  163. self.assertEqual(1, len(self._notify._waiting_zones))
  164. self._notify._notify_next_target(info)
  165. self.assertEqual(0, info.notify_try_num)
  166. self.assertIsNone(info.get_current_notify_target())
  167. self.assertEqual(2, self._notify.notify_num)
  168. self.assertEqual(0, len(self._notify._waiting_zones))
  169. example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
  170. self._notify._notify_next_target(example_com_info)
  171. self.assertEqual(1, self._notify.notify_num)
  172. self.assertEqual(1, len(self._notify._notifying_zones))
  173. self.assertEqual(0, len(self._notify._waiting_zones))
  174. def test_handle_notify_reply(self):
  175. self.assertEqual(notify_out._BAD_REPLY_PACKET, self._notify._handle_notify_reply(None, b'badmsg'))
  176. example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
  177. example_com_info.notify_msg_id = 0X2f18
  178. # test with right notify reply message
  179. data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
  180. self.assertEqual(notify_out._REPLY_OK, self._notify._handle_notify_reply(example_com_info, data))
  181. # test with unright query id
  182. data = b'\x2e\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
  183. self.assertEqual(notify_out._BAD_QUERY_ID, self._notify._handle_notify_reply(example_com_info, data))
  184. # test with unright query name
  185. data = b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03net\x00\x00\x06\x00\x01'
  186. self.assertEqual(notify_out._BAD_QUERY_NAME, self._notify._handle_notify_reply(example_com_info, data))
  187. # test with unright opcode
  188. data = b'\x2f\x18\x80\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
  189. self.assertEqual(notify_out._BAD_OPCODE, self._notify._handle_notify_reply(example_com_info, data))
  190. # test with unright qr
  191. data = b'\x2f\x18\x10\x10\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01'
  192. self.assertEqual(notify_out._BAD_QR, self._notify._handle_notify_reply(example_com_info, data))
  193. def test_send_notify_message_udp(self):
  194. example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
  195. example_com_info.prepare_notify_out()
  196. ret = self._notify._send_notify_message_udp(example_com_info, ('1.1.1.1', 53))
  197. self.assertTrue(ret)
  198. def test_zone_notify_handler(self):
  199. old_send_msg = self._notify._send_notify_message_udp
  200. def _fake_send_notify_message_udp(va1, va2):
  201. pass
  202. self._notify._send_notify_message_udp = _fake_send_notify_message_udp
  203. self._notify.send_notify('example.net.')
  204. self._notify.send_notify('example.com.')
  205. notify_out._MAX_NOTIFY_NUM = 2
  206. self._notify.send_notify('example.org.')
  207. example_net_info = self._notify._notify_infos[('example.net.', 'IN')]
  208. example_net_info.prepare_notify_out()
  209. example_net_info.notify_try_num = 2
  210. self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
  211. self.assertEqual(3, example_net_info.notify_try_num)
  212. time1 = example_net_info.notify_timeout
  213. self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
  214. self.assertEqual(4, example_net_info.notify_try_num)
  215. self.assertGreater(example_net_info.notify_timeout, time1 + 2) # bigger than 2 seconds
  216. cur_tgt = example_net_info._notify_current
  217. example_net_info.notify_try_num = notify_out._MAX_NOTIFY_TRY_NUM
  218. self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_NONE)
  219. self.assertNotEqual(cur_tgt, example_net_info._notify_current)
  220. def _example_net_data_reader(self):
  221. zone_data = [
  222. ('example.net.', '1000', 'IN', 'SOA', 'a.dns.example.net. mail.example.net. 1 1 1 1 1'),
  223. ('example.net.', '1000', 'IN', 'NS', 'a.dns.example.net.'),
  224. ('example.net.', '1000', 'IN', 'NS', 'b.dns.example.net.'),
  225. ('example.net.', '1000', 'IN', 'NS', 'c.dns.example.net.'),
  226. ('a.dns.example.net.', '1000', 'IN', 'A', '1.1.1.1'),
  227. ('a.dns.example.net.', '1000', 'IN', 'AAAA', '2:2::2:2'),
  228. ('b.dns.example.net.', '1000', 'IN', 'A', '3.3.3.3'),
  229. ('b.dns.example.net.', '1000', 'IN', 'AAAA', '4:4::4:4'),
  230. ('b.dns.example.net.', '1000', 'IN', 'AAAA', '5:5::5:5'),
  231. ('c.dns.example.net.', '1000', 'IN', 'A', '6.6.6.6'),
  232. ('c.dns.example.net.', '1000', 'IN', 'A', '7.7.7.7'),
  233. ('c.dns.example.net.', '1000', 'IN', 'AAAA', '8:8::8:8')]
  234. for item in zone_data:
  235. yield item
  236. def _example_com_data_reader(self):
  237. zone_data = [
  238. ('example.com.', '1000', 'IN', 'SOA', 'a.dns.example.com. mail.example.com. 1 1 1 1 1'),
  239. ('example.com.', '1000', 'IN', 'NS', 'a.dns.example.com.'),
  240. ('example.com.', '1000', 'IN', 'NS', 'b.dns.example.com.'),
  241. ('example.com.', '1000', 'IN', 'NS', 'c.dns.example.com.'),
  242. ('a.dns.example.com.', '1000', 'IN', 'A', '1.1.1.1'),
  243. ('b.dns.example.com.', '1000', 'IN', 'A', '3.3.3.3'),
  244. ('b.dns.example.com.', '1000', 'IN', 'AAAA', '4:4::4:4'),
  245. ('b.dns.example.com.', '1000', 'IN', 'AAAA', '5:5::5:5')]
  246. for item in zone_data:
  247. yield item
  248. def test_get_notify_slaves_from_ns(self):
  249. records = self._notify._get_notify_slaves_from_ns('example.net.')
  250. self.assertEqual(6, len(records))
  251. self.assertEqual('8:8::8:8', records[5])
  252. self.assertEqual('7.7.7.7', records[4])
  253. self.assertEqual('6.6.6.6', records[3])
  254. self.assertEqual('5:5::5:5', records[2])
  255. self.assertEqual('4:4::4:4', records[1])
  256. self.assertEqual('3.3.3.3', records[0])
  257. records = self._notify._get_notify_slaves_from_ns('example.com.')
  258. self.assertEqual(3, len(records))
  259. self.assertEqual('5:5::5:5', records[2])
  260. self.assertEqual('4:4::4:4', records[1])
  261. self.assertEqual('3.3.3.3', records[0])
  262. def test_init_notify_out(self):
  263. self._notify._init_notify_out(self._db_file.name)
  264. self.assertListEqual([('3.3.3.3', 53), ('4:4::4:4', 53), ('5:5::5:5', 53)],
  265. self._notify._notify_infos[('example.com.', 'IN')].notify_slaves)
  266. def test_prepare_select_info(self):
  267. timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
  268. self.assertEqual(notify_out._IDLE_SLEEP_TIME, timeout)
  269. self.assertListEqual([], valid_fds)
  270. self._notify._notify_infos[('example.net.', 'IN')]._sock = 1
  271. self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() + 5
  272. timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
  273. self.assertGreater(timeout, 0)
  274. self.assertListEqual([1], valid_fds)
  275. self._notify._notify_infos[('example.net.', 'IN')]._sock = 1
  276. self._notify._notify_infos[('example.net.', 'IN')].notify_timeout = time.time() - 5
  277. timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
  278. self.assertEqual(timeout, 0)
  279. self.assertListEqual([1], valid_fds)
  280. self._notify._notify_infos[('example.com.', 'IN')]._sock = 2
  281. self._notify._notify_infos[('example.com.', 'IN')].notify_timeout = time.time() + 5
  282. timeout, valid_fds, notifying_zones = self._notify._prepare_select_info()
  283. self.assertEqual(timeout, 0)
  284. self.assertListEqual([2, 1], valid_fds)
  285. def test_shutdown(self):
  286. thread = self._notify.dispatcher()
  287. self.assertTrue(thread.is_alive())
  288. self._notify.shutdown()
  289. self.assertFalse(thread.is_alive())
  290. if __name__== "__main__":
  291. unittest.main()