socket_cache_test.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. # Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
  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 isc.log
  17. import isc.bind10.socket_cache
  18. import isc.bind10.sockcreator
  19. from isc.net.addr import IPAddr
  20. import os
  21. class Test(unittest.TestCase):
  22. """
  23. Base for the tests here. It replaces the os.close method.
  24. """
  25. def setUp(self):
  26. self._closes = []
  27. isc.bind10.socket_cache.os.close = self.__close
  28. def tearDown(self):
  29. # This is not very clean solution. But when the test stops
  30. # to exist, the method must not be used to destroy the
  31. # object any more. And we can't restore the os.close here
  32. # as we never work with real sockets here.
  33. isc.bind10.socket_cache.os.close = lambda fd: None
  34. def __close(self, fd):
  35. """
  36. Just log a close was called.
  37. """
  38. self._closes.append(fd)
  39. class SocketTest(Test):
  40. """
  41. Test for the Socket class.
  42. """
  43. def setUp(self):
  44. """
  45. Creates the socket to be tested.
  46. It also creates other useful test variables.
  47. """
  48. Test.setUp(self)
  49. self.__address = IPAddr("192.0.2.1")
  50. self.__socket = isc.bind10.socket_cache.Socket('UDP', self.__address,
  51. 1024, 42)
  52. def test_init(self):
  53. """
  54. Checks the intrnals of the cache just after the creation.
  55. """
  56. self.assertEqual('UDP', self.__socket.protocol)
  57. self.assertEqual(self.__address, self.__socket.address)
  58. self.assertEqual(1024, self.__socket.port)
  59. self.assertEqual(42, self.__socket.fileno)
  60. self.assertEqual({}, self.__socket.active_tokens)
  61. self.assertEqual({}, self.__socket.shares)
  62. self.assertEqual(set(), self.__socket.waiting_tokens)
  63. def test_del(self):
  64. """
  65. Check it closes the socket when removed.
  66. """
  67. # This should make the refcount 0 and call the destructor
  68. # right away
  69. self.__socket = None
  70. self.assertEqual([42], self._closes)
  71. def test_share_modes(self):
  72. """
  73. Test the share mode compatibility check function.
  74. """
  75. modes = ['NO', 'SAMEAPP', 'ANY']
  76. # If there are no shares, it is compatible with everything.
  77. for mode in modes:
  78. self.assertTrue(self.__socket.share_compatible(mode, 'anything'))
  79. # There's an NO already, so it is incompatible with everything.
  80. self.__socket.shares = {'token': ('NO', 'anything')}
  81. for mode in modes:
  82. self.assertFalse(self.__socket.share_compatible(mode, 'anything'))
  83. # If there's SAMEAPP, it is compatible with ANY and SAMEAPP with the
  84. # same name.
  85. self.__socket.shares = {'token': ('SAMEAPP', 'app')}
  86. self.assertFalse(self.__socket.share_compatible('NO', 'app'))
  87. self.assertFalse(self.__socket.share_compatible('SAMEAPP',
  88. 'something'))
  89. self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
  90. self.assertTrue(self.__socket.share_compatible('ANY', 'app'))
  91. self.assertFalse(self.__socket.share_compatible('ANY', 'something'))
  92. # If there's ANY, then ANY and SAMEAPP with the same name is compatible
  93. self.__socket.shares = {'token': ('ANY', 'app')}
  94. self.assertFalse(self.__socket.share_compatible('NO', 'app'))
  95. self.assertFalse(self.__socket.share_compatible('SAMEAPP',
  96. 'something'))
  97. self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
  98. self.assertTrue(self.__socket.share_compatible('ANY', 'something'))
  99. # In case there are multiple already inside
  100. self.__socket.shares = {
  101. 'token': ('ANY', 'app'),
  102. 'another': ('SAMEAPP', 'app')
  103. }
  104. self.assertFalse(self.__socket.share_compatible('NO', 'app'))
  105. self.assertFalse(self.__socket.share_compatible('SAMEAPP',
  106. 'something'))
  107. self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
  108. self.assertFalse(self.__socket.share_compatible('ANY', 'something'))
  109. self.assertTrue(self.__socket.share_compatible('ANY', 'app'))
  110. # Invalid inputs are rejected
  111. self.assertRaises(ValueError, self.__socket.share_compatible, 'bad',
  112. 'bad')
  113. class SocketCacheTest(Test):
  114. """
  115. Some tests for the isc.bind10.socket_cache.Cache.
  116. This class, as well as being the testcase, pretends to be the
  117. socket creator so it can hijack all the requests for sockets.
  118. """
  119. def setUp(self):
  120. """
  121. Creates the cache for tests with us being the socket creator.
  122. Also creates some more variables for testing.
  123. """
  124. Test.setUp(self)
  125. self.__cache = isc.bind10.socket_cache.Cache(self)
  126. self.__address = IPAddr("192.0.2.1")
  127. self.__socket = isc.bind10.socket_cache.Socket('UDP', self.__address,
  128. 1024, 42)
  129. self.__get_socket_called = False
  130. def test_init(self):
  131. """
  132. Checks the internals of the cache just after the creation.
  133. """
  134. self.assertEqual(self, self.__cache._creator)
  135. self.assertEqual({}, self.__cache._waiting_tokens)
  136. self.assertEqual({}, self.__cache._active_tokens)
  137. self.assertEqual({}, self.__cache._active_apps)
  138. self.assertEqual({}, self.__cache._sockets)
  139. self.assertEqual(set(), self.__cache._live_tokens)
  140. def get_socket(self, address, port, socktype):
  141. """
  142. Pretend to be a socket creator.
  143. This expects to be called with the _address, port 1024 and 'UDP'.
  144. Returns 42 and notes down it was called.
  145. """
  146. self.assertEqual(self.__address, address)
  147. self.assertEqual(1024, port)
  148. self.assertEqual('UDP', socktype)
  149. self.__get_socket_called = True
  150. return 42
  151. def test_get_token_cached(self):
  152. """
  153. Check the behaviour of get_token when the requested socket is already
  154. cached inside.
  155. """
  156. self.__cache._sockets = {
  157. 'UDP': {'192.0.2.1': {1024: self.__socket}}
  158. }
  159. token = self.__cache.get_token('UDP', self.__address, 1024, 'ANY',
  160. 'test')
  161. # It didn't call get_socket
  162. self.assertFalse(self.__get_socket_called)
  163. # It returned something
  164. self.assertIsNotNone(token)
  165. # The token is both in the waiting sockets and the live tokens
  166. self.assertEqual({token: self.__socket}, self.__cache._waiting_tokens)
  167. self.assertEqual(set([token]), self.__cache._live_tokens)
  168. # The token got the new share to block any relevant queries
  169. self.assertEqual({token: ('ANY', 'test')}, self.__socket.shares)
  170. # The socket knows the token is waiting in it
  171. self.assertEqual(set([token]), self.__socket.waiting_tokens)
  172. # If we request one more, with incompatible share, it is rejected
  173. self.assertRaises(isc.bind10.socket_cache.ShareError,
  174. self.__cache.get_token, 'UDP', self.__address, 1024,
  175. 'NO', 'test')
  176. # The internals are not changed, so the same checks
  177. self.assertEqual({token: self.__socket}, self.__cache._waiting_tokens)
  178. self.assertEqual(set([token]), self.__cache._live_tokens)
  179. self.assertEqual({token: ('ANY', 'test')}, self.__socket.shares)
  180. self.assertEqual(set([token]), self.__socket.waiting_tokens)
  181. def test_get_token_uncached(self):
  182. """
  183. Check a new socket is created when a corresponding one is missing.
  184. """
  185. token = self.__cache.get_token('UDP', self.__address, 1024, 'ANY',
  186. 'test')
  187. # The get_socket was called
  188. self.assertTrue(self.__get_socket_called)
  189. # It returned something
  190. self.assertIsNotNone(token)
  191. # Get the socket and check it looks OK
  192. socket = self.__cache._waiting_tokens[token]
  193. self.assertEqual(self.__address, socket.address)
  194. self.assertEqual(1024, socket.port)
  195. self.assertEqual(42, socket.fileno)
  196. self.assertEqual('UDP', socket.protocol)
  197. # The socket is properly cached
  198. self.assertEqual({
  199. 'UDP': {'192.0.2.1': {1024: socket}}
  200. }, self.__cache._sockets)
  201. # The token is both in the waiting sockets and the live tokens
  202. self.assertEqual({token: socket}, self.__cache._waiting_tokens)
  203. self.assertEqual(set([token]), self.__cache._live_tokens)
  204. # The token got the new share to block any relevant queries
  205. self.assertEqual({token: ('ANY', 'test')}, socket.shares)
  206. # The socket knows the token is waiting in it
  207. self.assertEqual(set([token]), socket.waiting_tokens)
  208. def test_get_token_excs(self):
  209. """
  210. Test that it is handled properly if the socket creator raises
  211. some exceptions.
  212. """
  213. def raiseCreatorError(fatal):
  214. raise isc.bind10.sockcreator.CreatorError('test error', fatal)
  215. # First, fatal socket creator errors are passed through
  216. self.get_socket = lambda addr, port, proto: raiseCreatorError(True)
  217. self.assertRaises(isc.bind10.sockcreator.CreatorError,
  218. self.__cache.get_token, 'UDP', self.__address, 1024,
  219. 'NO', 'test')
  220. # And nonfatal are converted to SocketError
  221. self.get_socket = lambda addr, port, proto: raiseCreatorError(False)
  222. self.assertRaises(isc.bind10.socket_cache.SocketError,
  223. self.__cache.get_token, 'UDP', self.__address, 1024,
  224. 'NO', 'test')
  225. def test_get_socket(self):
  226. """
  227. Test that we can pickup a socket if we know a token.
  228. """
  229. token = "token"
  230. app = 13
  231. # No socket prepared there
  232. self.assertRaises(ValueError, self.__cache.get_socket, token, app)
  233. # Not changed
  234. self.assertEqual({}, self.__cache._active_tokens)
  235. self.assertEqual({}, self.__cache._active_apps)
  236. self.assertEqual({}, self.__cache._sockets)
  237. self.assertEqual(set(), self.__cache._live_tokens)
  238. # Prepare a token there
  239. self.__socket.waiting_tokens = set([token])
  240. self.__socket.shares = {token: ('ANY', 'app')}
  241. self.__cache._waiting_tokens = {token: self.__socket}
  242. self.__cache._sockets = {'UDP': {'192.0.2.1': {1024: self.__socket}}}
  243. self.__cache._live_tokens = set([token])
  244. socket = self.__cache.get_socket(token, app)
  245. # Received the fileno
  246. self.assertEqual(42, socket)
  247. # It moved from waiting to active ones
  248. self.assertEqual({}, self.__cache._waiting_tokens)
  249. self.assertEqual({token: self.__socket}, self.__cache._active_tokens)
  250. self.assertEqual({13: set([token])}, self.__cache._active_apps)
  251. self.assertEqual(set([token]), self.__cache._live_tokens)
  252. self.assertEqual(set(), self.__socket.waiting_tokens)
  253. self.assertEqual({token: 13}, self.__socket.active_tokens)
  254. # Trying to get it again fails
  255. self.assertRaises(ValueError, self.__cache.get_socket, token, app)
  256. def test_drop_application(self):
  257. """
  258. Test that a drop_application calls drop_socket on all the sockets
  259. held by the application.
  260. """
  261. sockets = set()
  262. def drop_socket(token):
  263. sockets.add(token)
  264. # Mock the drop_socket so we know it is called
  265. self.__cache.drop_socket = drop_socket
  266. self.assertRaises(ValueError, self.__cache.drop_application,
  267. 13)
  268. self.assertEqual(set(), sockets)
  269. # Put the tokens into active_apps. Nothing else should be touched
  270. # by this call, so leave it alone.
  271. self.__cache._active_apps = {
  272. 1: set(['t1', 't2']),
  273. 2: set(['t3'])
  274. }
  275. self.__cache.drop_application(1)
  276. # We don't check the _active_apps, as it would be cleaned by
  277. # drop_socket and we removed it.
  278. self.assertEqual(set(['t1', 't2']), sockets)
  279. def test_drop_socket(self):
  280. """
  281. Test the drop_socket call. It tests:
  282. * That a socket that still has something to keep it alive is left alive
  283. (both waiting and active).
  284. * If not, it is deleted.
  285. * All bookkeeping data around are properly removed.
  286. * Of course the exception.
  287. """
  288. self.assertRaises(ValueError, self.__cache.drop_socket, "bad token")
  289. self.__socket.active_tokens = {'t1': 1}
  290. self.__socket.waiting_tokens = set(['t2'])
  291. self.__socket.shares = {'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')}
  292. self.__cache._waiting_tokens = {'t2': self.__socket}
  293. self.__cache._active_tokens = {'t1': self.__socket}
  294. self.__cache._sockets = {'UDP': {'192.0.2.1': {1024: self.__socket}}}
  295. self.__cache._live_tokens = set(['t1', 't2'])
  296. self.__cache._active_apps = {1: set(['t1'])}
  297. # We can't drop what wasn't picket up yet
  298. self.assertRaises(ValueError, self.__cache.drop_socket, 't2')
  299. self.assertEqual({'t1': 1}, self.__socket.active_tokens)
  300. self.assertEqual(set(['t2']), self.__socket.waiting_tokens)
  301. self.assertEqual({'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')},
  302. self.__socket.shares)
  303. self.assertEqual({'t2': self.__socket}, self.__cache._waiting_tokens)
  304. self.assertEqual({'t1': self.__socket}, self.__cache._active_tokens)
  305. self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}},
  306. self.__cache._sockets)
  307. self.assertEqual(set(['t1', 't2']), self.__cache._live_tokens)
  308. self.assertEqual({1: set(['t1'])}, self.__cache._active_apps)
  309. self.assertEqual([], self._closes)
  310. # If we drop this, it survives because it waits for being picked up
  311. self.__cache.drop_socket('t1')
  312. self.assertEqual({}, self.__socket.active_tokens)
  313. self.assertEqual(set(['t2']), self.__socket.waiting_tokens)
  314. self.assertEqual({'t2': ('ANY', 'app2')}, self.__socket.shares)
  315. self.assertEqual({}, self.__cache._active_tokens)
  316. self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}},
  317. self.__cache._sockets)
  318. self.assertEqual(set(['t2']), self.__cache._live_tokens)
  319. self.assertEqual({}, self.__cache._active_apps)
  320. self.assertEqual([], self._closes)
  321. # Fill it again, now two applications having the same socket
  322. self.__socket.active_tokens = {'t1': 1, 't2': 2}
  323. self.__socket.waiting_tokens = set()
  324. self.__socket.shares = {'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')}
  325. self.__cache._waiting_tokens = {}
  326. self.__cache._active_tokens = {
  327. 't1': self.__socket,
  328. 't2': self.__socket
  329. }
  330. self.__cache._live_tokens = set(['t1', 't2', 't3'])
  331. self.assertEqual([], self._closes)
  332. # We cheat here little bit, the t3 doesn't exist anywhere else, but
  333. # we need to check the app isn't removed too soon and it shouldn't
  334. # matter anywhere else, so we just avoid the tiresome filling in
  335. self.__cache._active_apps = {1: set(['t1', 't3']), 2: set(['t2'])}
  336. # Drop it as t1. It should still live.
  337. self.__cache.drop_socket('t1')
  338. self.assertEqual({'t2': 2}, self.__socket.active_tokens)
  339. self.assertEqual(set(), self.__socket.waiting_tokens)
  340. self.assertEqual({'t2': ('ANY', 'app2')}, self.__socket.shares)
  341. self.assertEqual({}, self.__cache._waiting_tokens)
  342. self.assertEqual({'t2': self.__socket}, self.__cache._active_tokens)
  343. self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}},
  344. self.__cache._sockets)
  345. self.assertEqual(set(['t3', 't2']), self.__cache._live_tokens)
  346. self.assertEqual({1: set(['t3']), 2: set(['t2'])},
  347. self.__cache._active_apps)
  348. self.assertEqual([], self._closes)
  349. # Drop it again, from the other application. It should get removed
  350. # and closed.
  351. self.__cache.drop_socket('t2')
  352. self.assertEqual({}, self.__socket.active_tokens)
  353. self.assertEqual(set(), self.__socket.waiting_tokens)
  354. self.assertEqual({}, self.__socket.shares)
  355. self.assertEqual({}, self.__cache._waiting_tokens)
  356. self.assertEqual({}, self.__cache._active_tokens)
  357. self.assertEqual({}, self.__cache._sockets)
  358. self.assertEqual(set(['t3']), self.__cache._live_tokens)
  359. self.assertEqual({1: set(['t3'])}, self.__cache._active_apps)
  360. # The cache doesn't hold the socket. So when we remove it ourself,
  361. # it should get closed.
  362. self.__socket = None
  363. self.assertEqual([42], self._closes)
  364. if __name__ == '__main__':
  365. isc.log.init("bind10")
  366. isc.log.resetUnitTestRootLogger()
  367. unittest.main()