bind10_test.py.in 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505
  1. # Copyright (C) 2011 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. # Most of the time, we omit the "bind10_src" for brevity. Sometimes,
  16. # we want to be explicit about what we do, like when hijacking a library
  17. # call used by the bind10_src.
  18. from bind10_src import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, _BASETIME
  19. import bind10_src
  20. # XXX: environment tests are currently disabled, due to the preprocessor
  21. # setup that we have now complicating the environment
  22. import unittest
  23. import sys
  24. import os
  25. import copy
  26. import signal
  27. import socket
  28. from isc.net.addr import IPAddr
  29. import time
  30. import isc
  31. import isc.log
  32. import isc.bind10.socket_cache
  33. import errno
  34. from isc.testutils.parse_args import TestOptParser, OptsError
  35. from isc.testutils.ccsession_mock import MockModuleCCSession
  36. class TestProcessInfo(unittest.TestCase):
  37. def setUp(self):
  38. # redirect stdout to a pipe so we can check that our
  39. # process spawning is doing the right thing with stdout
  40. self.old_stdout = os.dup(sys.stdout.fileno())
  41. self.pipes = os.pipe()
  42. os.dup2(self.pipes[1], sys.stdout.fileno())
  43. os.close(self.pipes[1])
  44. # note that we use dup2() to restore the original stdout
  45. # to the main program ASAP in each test... this prevents
  46. # hangs reading from the child process (as the pipe is only
  47. # open in the child), and also insures nice pretty output
  48. def tearDown(self):
  49. # clean up our stdout munging
  50. os.dup2(self.old_stdout, sys.stdout.fileno())
  51. os.close(self.pipes[0])
  52. def test_init(self):
  53. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
  54. pi.spawn()
  55. os.dup2(self.old_stdout, sys.stdout.fileno())
  56. self.assertEqual(pi.name, 'Test Process')
  57. self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
  58. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  59. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
  60. self.assertEqual(pi.dev_null_stdout, False)
  61. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  62. self.assertNotEqual(pi.process, None)
  63. self.assertTrue(type(pi.pid) is int)
  64. # def test_setting_env(self):
  65. # pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
  66. # os.dup2(self.old_stdout, sys.stdout.fileno())
  67. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  68. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
  69. # 'FOO': 'BAR' })
  70. def test_setting_null_stdout(self):
  71. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
  72. dev_null_stdout=True)
  73. pi.spawn()
  74. os.dup2(self.old_stdout, sys.stdout.fileno())
  75. self.assertEqual(pi.dev_null_stdout, True)
  76. self.assertEqual(os.read(self.pipes[0], 100), b"")
  77. def test_respawn(self):
  78. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
  79. pi.spawn()
  80. # wait for old process to work...
  81. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  82. # respawn it
  83. old_pid = pi.pid
  84. pi.respawn()
  85. os.dup2(self.old_stdout, sys.stdout.fileno())
  86. # make sure the new one started properly
  87. self.assertEqual(pi.name, 'Test Process')
  88. self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
  89. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  90. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
  91. self.assertEqual(pi.dev_null_stdout, False)
  92. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  93. self.assertNotEqual(pi.process, None)
  94. self.assertTrue(type(pi.pid) is int)
  95. self.assertNotEqual(pi.pid, old_pid)
  96. class TestCacheCommands(unittest.TestCase):
  97. """
  98. Test methods of boss related to the socket cache and socket handling.
  99. """
  100. def setUp(self):
  101. """
  102. Prepare the boss for some tests.
  103. Also prepare some variables we need.
  104. """
  105. self.__boss = BoB()
  106. # Fake the cache here so we can pretend it is us and hijack the
  107. # calls to its methods.
  108. self.__boss._socket_cache = self
  109. self.__boss._socket_path = '/socket/path'
  110. self.__raise_exception = None
  111. self.__socket_args = {
  112. "port": 53,
  113. "address": "::",
  114. "protocol": "UDP",
  115. "share_mode": "ANY",
  116. "share_name": "app"
  117. }
  118. # What was and wasn't called.
  119. self.__drop_app_called = None
  120. self.__get_socket_called = None
  121. self.__send_fd_called = None
  122. self.__get_token_called = None
  123. self.__drop_socket_called = None
  124. bind10_src.libutil_io_python.send_fd = self.__send_fd
  125. def __send_fd(self, to, socket):
  126. """
  127. A function to hook the send_fd in the bind10_src.
  128. """
  129. self.__send_fd_called = (to, socket)
  130. class FalseSocket:
  131. """
  132. A socket where we can fake methods we need instead of having a real
  133. socket.
  134. """
  135. def __init__(self):
  136. self.send = b""
  137. def fileno(self):
  138. """
  139. The file number. Used for identifying the remote application.
  140. """
  141. return 42
  142. def sendall(self, data):
  143. """
  144. Adds data to the self.send.
  145. """
  146. self.send += data
  147. def drop_application(self, application):
  148. """
  149. Part of pretending to be the cache. Logs the parameter to
  150. self.__drop_app_called.
  151. In the case self.__raise_exception is set, the exception there
  152. is raised instead.
  153. """
  154. if self.__raise_exception is not None:
  155. raise self.__raise_exception
  156. self.__drop_app_called = application
  157. def test_consumer_dead(self):
  158. """
  159. Test that it calls the drop_application method of the cache.
  160. """
  161. self.__boss.socket_consumer_dead(self.FalseSocket())
  162. self.assertEqual(42, self.__drop_app_called)
  163. def test_consumer_dead_invalid(self):
  164. """
  165. Test that it doesn't crash in case the application is not known to
  166. the cache, the boss doesn't crash, as this actually can happen in
  167. practice.
  168. """
  169. self.__raise_exception = ValueError("This application is unknown")
  170. # This doesn't crash
  171. self.__boss.socket_consumer_dead(self.FalseSocket())
  172. def get_socket(self, token, application):
  173. """
  174. Part of pretending to be the cache. If there's anything in
  175. __raise_exception, it is raised. Otherwise, the call is logged
  176. into __get_socket_called and a number is returned.
  177. """
  178. if self.__raise_exception is not None:
  179. raise self.__raise_exception
  180. self.__get_socket_called = (token, application)
  181. return 13
  182. def test_request_handler(self):
  183. """
  184. Test that a request for socket is forwarded and the socket is sent
  185. back, if it returns a socket.
  186. """
  187. socket = self.FalseSocket()
  188. # An exception from the cache
  189. self.__raise_exception = ValueError("Test value error")
  190. self.__boss.socket_request_handler(b"token", socket)
  191. # It was called, but it threw, so it is not noted here
  192. self.assertIsNone(self.__get_socket_called)
  193. self.assertEqual(b"0\n", socket.send)
  194. # It should not have sent any socket.
  195. self.assertIsNone(self.__send_fd_called)
  196. # Now prepare a valid scenario
  197. self.__raise_exception = None
  198. socket.send = b""
  199. self.__boss.socket_request_handler(b"token", socket)
  200. self.assertEqual(b"1\n", socket.send)
  201. self.assertEqual((42, 13), self.__send_fd_called)
  202. self.assertEqual(("token", 42), self.__get_socket_called)
  203. def get_token(self, protocol, address, port, share_mode, share_name):
  204. """
  205. Part of pretending to be the cache. If there's anything in
  206. __raise_exception, it is raised. Otherwise, the parameters are
  207. logged into __get_token_called and a token is returned.
  208. """
  209. if self.__raise_exception is not None:
  210. raise self.__raise_exception
  211. self.__get_token_called = (protocol, address, port, share_mode,
  212. share_name)
  213. return "token"
  214. def test_get_socket_ok(self):
  215. """
  216. Test the successful scenario of getting a socket.
  217. """
  218. result = self.__boss._get_socket(self.__socket_args)
  219. [code, answer] = result['result']
  220. self.assertEqual(0, code)
  221. self.assertEqual({
  222. 'token': 'token',
  223. 'path': '/socket/path'
  224. }, answer)
  225. addr = self.__get_token_called[1]
  226. self.assertTrue(isinstance(addr, IPAddr))
  227. self.assertEqual("::", str(addr))
  228. self.assertEqual(("UDP", addr, 53, "ANY", "app"),
  229. self.__get_token_called)
  230. def test_get_socket_error(self):
  231. """
  232. Test that bad inputs are handled correctly, etc.
  233. """
  234. def check_code(code, args):
  235. """
  236. Pass the args there and check if it returns success or not.
  237. The rest is not tested, as it is already checked in the
  238. test_get_socket_ok.
  239. """
  240. [rcode, ranswer] = self.__boss._get_socket(args)['result']
  241. self.assertEqual(code, rcode)
  242. if code != 0:
  243. # This should be an error message. The exact formatting
  244. # is unknown, but we check it is string at least
  245. self.assertTrue(isinstance(ranswer, str))
  246. def mod_args(name, value):
  247. """
  248. Override a parameter in the args.
  249. """
  250. result = dict(self.__socket_args)
  251. result[name] = value
  252. return result
  253. # Port too large
  254. check_code(1, mod_args('port', 65536))
  255. # Not numeric address
  256. check_code(1, mod_args('address', 'example.org.'))
  257. # Some bad values of enum-like params
  258. check_code(1, mod_args('protocol', 'BAD PROTO'))
  259. check_code(1, mod_args('share_mode', 'BAD SHARE'))
  260. # Check missing parameters
  261. for param in self.__socket_args.keys():
  262. args = dict(self.__socket_args)
  263. del args[param]
  264. check_code(1, args)
  265. # These are OK values for the enum-like parameters
  266. # The ones from test_get_socket_ok are not tested here
  267. check_code(0, mod_args('protocol', 'TCP'))
  268. check_code(0, mod_args('share_mode', 'SAMEAPP'))
  269. check_code(0, mod_args('share_mode', 'NO'))
  270. # If an exception is raised from within the cache, it is converted
  271. # to an error, not propagated
  272. self.__raise_exception = Exception("Test exception")
  273. check_code(1, self.__socket_args)
  274. # The special "expected" exceptions
  275. self.__raise_exception = \
  276. isc.bind10.socket_cache.ShareError("Not shared")
  277. check_code(3, self.__socket_args)
  278. self.__raise_exception = \
  279. isc.bind10.socket_cache.SocketError("Not shared", 13)
  280. check_code(2, self.__socket_args)
  281. def drop_socket(self, token):
  282. """
  283. Part of pretending to be the cache. If there's anything in
  284. __raise_exception, it is raised. Otherwise, the parameter is stored
  285. in __drop_socket_called.
  286. """
  287. if self.__raise_exception is not None:
  288. raise self.__raise_exception
  289. self.__drop_socket_called = token
  290. def test_drop_socket(self):
  291. """
  292. Check the drop_socket command. It should directly call the method
  293. on the cache. Exceptions should be translated to error messages.
  294. """
  295. # This should be OK and just propagated to the call.
  296. self.assertEqual({"result": [0]},
  297. self.__boss.command_handler("drop_socket",
  298. {"token": "token"}))
  299. self.assertEqual("token", self.__drop_socket_called)
  300. self.__drop_socket_called = None
  301. # Missing parameter
  302. self.assertEqual({"result": [1, "Missing token parameter"]},
  303. self.__boss.command_handler("drop_socket", {}))
  304. self.assertIsNone(self.__drop_socket_called)
  305. # An exception is raised from within the cache
  306. self.__raise_exception = ValueError("Test error")
  307. self.assertEqual({"result": [1, "Test error"]},
  308. self.__boss.command_handler("drop_socket",
  309. {"token": "token"}))
  310. class TestBoB(unittest.TestCase):
  311. def test_init(self):
  312. bob = BoB()
  313. self.assertEqual(bob.verbose, False)
  314. self.assertEqual(bob.msgq_socket_file, None)
  315. self.assertEqual(bob.cc_session, None)
  316. self.assertEqual(bob.ccs, None)
  317. self.assertEqual(bob.components, {})
  318. self.assertEqual(bob.runnable, False)
  319. self.assertEqual(bob.uid, None)
  320. self.assertEqual(bob.username, None)
  321. self.assertEqual(bob.nocache, False)
  322. self.assertIsNone(bob._socket_cache)
  323. def test_set_creator(self):
  324. """
  325. Test the call to set_creator. First time, the cache is created
  326. with the passed creator. The next time, it throws an exception.
  327. """
  328. bob = BoB()
  329. # The cache doesn't use it at start, so just create an empty class
  330. class Creator: pass
  331. creator = Creator()
  332. bob.set_creator(creator)
  333. self.assertTrue(isinstance(bob._socket_cache,
  334. isc.bind10.socket_cache.Cache))
  335. self.assertEqual(creator, bob._socket_cache._creator)
  336. self.assertRaises(ValueError, bob.set_creator, creator)
  337. def test_init_alternate_socket(self):
  338. bob = BoB("alt_socket_file")
  339. self.assertEqual(bob.verbose, False)
  340. self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
  341. self.assertEqual(bob.cc_session, None)
  342. self.assertEqual(bob.ccs, None)
  343. self.assertEqual(bob.components, {})
  344. self.assertEqual(bob.runnable, False)
  345. self.assertEqual(bob.uid, None)
  346. self.assertEqual(bob.username, None)
  347. self.assertEqual(bob.nocache, False)
  348. def test_command_handler(self):
  349. class DummySession():
  350. def group_sendmsg(self, msg, group):
  351. (self.msg, self.group) = (msg, group)
  352. def group_recvmsg(self, nonblock, seq): pass
  353. class DummyModuleCCSession():
  354. module_spec = isc.config.module_spec.ModuleSpec({
  355. "module_name": "Boss",
  356. "statistics": [
  357. {
  358. "item_name": "boot_time",
  359. "item_type": "string",
  360. "item_optional": False,
  361. "item_default": "1970-01-01T00:00:00Z",
  362. "item_title": "Boot time",
  363. "item_description": "A date time when bind10 process starts initially",
  364. "item_format": "date-time"
  365. }
  366. ]
  367. })
  368. def get_module_spec(self):
  369. return self.module_spec
  370. bob = BoB()
  371. bob.verbose = True
  372. bob.cc_session = DummySession()
  373. bob.ccs = DummyModuleCCSession()
  374. # a bad command
  375. self.assertEqual(bob.command_handler(-1, None),
  376. isc.config.ccsession.create_answer(1, "bad command"))
  377. # "shutdown" command
  378. self.assertEqual(bob.command_handler("shutdown", None),
  379. isc.config.ccsession.create_answer(0))
  380. self.assertFalse(bob.runnable)
  381. # "getstats" command
  382. self.assertEqual(bob.command_handler("getstats", None),
  383. isc.config.ccsession.create_answer(0,
  384. { "owner": "Boss",
  385. "data": {
  386. 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
  387. }}))
  388. # "sendstats" command
  389. self.assertEqual(bob.command_handler("sendstats", None),
  390. isc.config.ccsession.create_answer(0))
  391. self.assertEqual(bob.cc_session.group, "Stats")
  392. self.assertEqual(bob.cc_session.msg,
  393. isc.config.ccsession.create_command(
  394. "set", { "owner": "Boss",
  395. "data": {
  396. "boot_time": time.strftime("%Y-%m-%dT%H:%M:%SZ", _BASETIME)
  397. }}))
  398. # "ping" command
  399. self.assertEqual(bob.command_handler("ping", None),
  400. isc.config.ccsession.create_answer(0, "pong"))
  401. # "show_processes" command
  402. self.assertEqual(bob.command_handler("show_processes", None),
  403. isc.config.ccsession.create_answer(0,
  404. bob.get_processes()))
  405. # an unknown command
  406. self.assertEqual(bob.command_handler("__UNKNOWN__", None),
  407. isc.config.ccsession.create_answer(1, "Unknown command"))
  408. # Fake the get_token of cache and test the command works
  409. bob._socket_path = '/socket/path'
  410. class cache:
  411. def get_token(self, protocol, addr, port, share_mode, share_name):
  412. return str(addr) + ':' + str(port)
  413. bob._socket_cache = cache()
  414. args = {
  415. "port": 53,
  416. "address": "0.0.0.0",
  417. "protocol": "UDP",
  418. "share_mode": "ANY",
  419. "share_name": "app"
  420. }
  421. # at all and this is the easiest way to check.
  422. self.assertEqual({'result': [0, {'token': '0.0.0.0:53',
  423. 'path': '/socket/path'}]},
  424. bob.command_handler("get_socket", args))
  425. # The drop_socket is not tested here, but in TestCacheCommands.
  426. # It needs the cache mocks to be in place and they are there.
  427. def test_stop_process(self):
  428. """
  429. Test checking the stop_process method sends the right message over
  430. the message bus.
  431. """
  432. class DummySession():
  433. def group_sendmsg(self, msg, group, instance="*"):
  434. (self.msg, self.group, self.instance) = (msg, group, instance)
  435. bob = BoB()
  436. bob.cc_session = DummySession()
  437. bob.stop_process('process', 'address', 42)
  438. self.assertEqual('address', bob.cc_session.group)
  439. self.assertEqual('address', bob.cc_session.instance)
  440. self.assertEqual({'command': ['shutdown', {'pid': 42}]},
  441. bob.cc_session.msg)
  442. # Class for testing the BoB without actually starting processes.
  443. # This is used for testing the start/stop components routines and
  444. # the BoB commands.
  445. #
  446. # Testing that external processes start is outside the scope
  447. # of the unit test, by overriding the process start methods we can check
  448. # that the right processes are started depending on the configuration
  449. # options.
  450. class MockBob(BoB):
  451. def __init__(self):
  452. BoB.__init__(self)
  453. # Set flags as to which of the overridden methods has been run.
  454. self.msgq = False
  455. self.cfgmgr = False
  456. self.ccsession = False
  457. self.auth = False
  458. self.resolver = False
  459. self.xfrout = False
  460. self.xfrin = False
  461. self.zonemgr = False
  462. self.stats = False
  463. self.stats_httpd = False
  464. self.cmdctl = False
  465. self.dhcp6 = False
  466. self.dhcp4 = False
  467. self.c_channel_env = {}
  468. self.components = { }
  469. self.creator = False
  470. class MockSockCreator(isc.bind10.component.Component):
  471. def __init__(self, process, boss, kind, address=None, params=None):
  472. isc.bind10.component.Component.__init__(self, process, boss,
  473. kind, 'SockCreator')
  474. self._start_func = boss.start_creator
  475. specials = isc.bind10.special_component.get_specials()
  476. specials['sockcreator'] = MockSockCreator
  477. self._component_configurator = \
  478. isc.bind10.component.Configurator(self, specials)
  479. def start_creator(self):
  480. self.creator = True
  481. procinfo = ProcessInfo('b10-sockcreator', ['/bin/false'])
  482. procinfo.pid = 1
  483. return procinfo
  484. def _read_bind10_config(self):
  485. # Configuration options are set directly
  486. pass
  487. def start_msgq(self):
  488. self.msgq = True
  489. procinfo = ProcessInfo('b10-msgq', ['/bin/false'])
  490. procinfo.pid = 2
  491. return procinfo
  492. def start_ccsession(self, c_channel_env):
  493. # this is not a process, don't have to do anything with procinfo
  494. self.ccsession = True
  495. def start_cfgmgr(self):
  496. self.cfgmgr = True
  497. procinfo = ProcessInfo('b10-cfgmgr', ['/bin/false'])
  498. procinfo.pid = 3
  499. return procinfo
  500. def start_auth(self):
  501. self.auth = True
  502. procinfo = ProcessInfo('b10-auth', ['/bin/false'])
  503. procinfo.pid = 5
  504. return procinfo
  505. def start_resolver(self):
  506. self.resolver = True
  507. procinfo = ProcessInfo('b10-resolver', ['/bin/false'])
  508. procinfo.pid = 6
  509. return procinfo
  510. def start_simple(self, name):
  511. procmap = { 'b10-zonemgr': self.start_zonemgr,
  512. 'b10-stats': self.start_stats,
  513. 'b10-stats-httpd': self.start_stats_httpd,
  514. 'b10-cmdctl': self.start_cmdctl,
  515. 'b10-dhcp6': self.start_dhcp6,
  516. 'b10-dhcp4': self.start_dhcp4,
  517. 'b10-xfrin': self.start_xfrin,
  518. 'b10-xfrout': self.start_xfrout }
  519. return procmap[name]()
  520. def start_xfrout(self):
  521. self.xfrout = True
  522. procinfo = ProcessInfo('b10-xfrout', ['/bin/false'])
  523. procinfo.pid = 7
  524. return procinfo
  525. def start_xfrin(self):
  526. self.xfrin = True
  527. procinfo = ProcessInfo('b10-xfrin', ['/bin/false'])
  528. procinfo.pid = 8
  529. return procinfo
  530. def start_zonemgr(self):
  531. self.zonemgr = True
  532. procinfo = ProcessInfo('b10-zonemgr', ['/bin/false'])
  533. procinfo.pid = 9
  534. return procinfo
  535. def start_stats(self):
  536. self.stats = True
  537. procinfo = ProcessInfo('b10-stats', ['/bin/false'])
  538. procinfo.pid = 10
  539. return procinfo
  540. def start_stats_httpd(self):
  541. self.stats_httpd = True
  542. procinfo = ProcessInfo('b10-stats-httpd', ['/bin/false'])
  543. procinfo.pid = 11
  544. return procinfo
  545. def start_cmdctl(self):
  546. self.cmdctl = True
  547. procinfo = ProcessInfo('b10-cmdctl', ['/bin/false'])
  548. procinfo.pid = 12
  549. return procinfo
  550. def start_dhcp6(self):
  551. self.dhcp6 = True
  552. procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
  553. procinfo.pid = 13
  554. return procinfo
  555. def start_dhcp4(self):
  556. self.dhcp4 = True
  557. procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
  558. procinfo.pid = 14
  559. return procinfo
  560. def stop_process(self, process, recipient, pid):
  561. procmap = { 'b10-auth': self.stop_auth,
  562. 'b10-resolver': self.stop_resolver,
  563. 'b10-xfrout': self.stop_xfrout,
  564. 'b10-xfrin': self.stop_xfrin,
  565. 'b10-zonemgr': self.stop_zonemgr,
  566. 'b10-stats': self.stop_stats,
  567. 'b10-stats-httpd': self.stop_stats_httpd,
  568. 'b10-cmdctl': self.stop_cmdctl }
  569. procmap[process]()
  570. # Some functions to pretend we stop processes, use by stop_process
  571. def stop_msgq(self):
  572. if self.msgq:
  573. del self.components[2]
  574. self.msgq = False
  575. def stop_cfgmgr(self):
  576. if self.cfgmgr:
  577. del self.components[3]
  578. self.cfgmgr = False
  579. def stop_auth(self):
  580. if self.auth:
  581. del self.components[5]
  582. self.auth = False
  583. def stop_resolver(self):
  584. if self.resolver:
  585. del self.components[6]
  586. self.resolver = False
  587. def stop_xfrout(self):
  588. if self.xfrout:
  589. del self.components[7]
  590. self.xfrout = False
  591. def stop_xfrin(self):
  592. if self.xfrin:
  593. del self.components[8]
  594. self.xfrin = False
  595. def stop_zonemgr(self):
  596. if self.zonemgr:
  597. del self.components[9]
  598. self.zonemgr = False
  599. def stop_stats(self):
  600. if self.stats:
  601. del self.components[10]
  602. self.stats = False
  603. def stop_stats_httpd(self):
  604. if self.stats_httpd:
  605. del self.components[11]
  606. self.stats_httpd = False
  607. def stop_cmdctl(self):
  608. if self.cmdctl:
  609. del self.components[12]
  610. self.cmdctl = False
  611. class TestStartStopProcessesBob(unittest.TestCase):
  612. """
  613. Check that the start_all_components method starts the right combination
  614. of components and that the right components are started and stopped
  615. according to changes in configuration.
  616. """
  617. def check_environment_unchanged(self):
  618. # Check whether the environment has not been changed
  619. self.assertEqual(original_os_environ, os.environ)
  620. def check_started(self, bob, core, auth, resolver):
  621. """
  622. Check that the right sets of services are started. The ones that
  623. should be running are specified by the core, auth and resolver parameters
  624. (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
  625. and -zonemgr).
  626. """
  627. self.assertEqual(bob.msgq, core)
  628. self.assertEqual(bob.cfgmgr, core)
  629. self.assertEqual(bob.ccsession, core)
  630. self.assertEqual(bob.creator, core)
  631. self.assertEqual(bob.auth, auth)
  632. self.assertEqual(bob.resolver, resolver)
  633. self.assertEqual(bob.xfrout, auth)
  634. self.assertEqual(bob.xfrin, auth)
  635. self.assertEqual(bob.zonemgr, auth)
  636. self.assertEqual(bob.stats, core)
  637. self.assertEqual(bob.stats_httpd, core)
  638. self.assertEqual(bob.cmdctl, core)
  639. self.check_environment_unchanged()
  640. def check_preconditions(self, bob):
  641. self.check_started(bob, False, False, False)
  642. def check_started_none(self, bob):
  643. """
  644. Check that the situation is according to configuration where no servers
  645. should be started. Some components still need to be running.
  646. """
  647. self.check_started(bob, True, False, False)
  648. self.check_environment_unchanged()
  649. def check_started_both(self, bob):
  650. """
  651. Check the situation is according to configuration where both servers
  652. (auth and resolver) are enabled.
  653. """
  654. self.check_started(bob, True, True, True)
  655. self.check_environment_unchanged()
  656. def check_started_auth(self, bob):
  657. """
  658. Check the set of components needed to run auth only is started.
  659. """
  660. self.check_started(bob, True, True, False)
  661. self.check_environment_unchanged()
  662. def check_started_resolver(self, bob):
  663. """
  664. Check the set of components needed to run resolver only is started.
  665. """
  666. self.check_started(bob, True, False, True)
  667. self.check_environment_unchanged()
  668. def check_started_dhcp(self, bob, v4, v6):
  669. """
  670. Check if proper combinations of DHCPv4 and DHCpv6 can be started
  671. """
  672. self.assertEqual(v4, bob.dhcp4)
  673. self.assertEqual(v6, bob.dhcp6)
  674. self.check_environment_unchanged()
  675. def construct_config(self, start_auth, start_resolver):
  676. # The things that are common, not turned on an off
  677. config = {}
  678. config['b10-stats'] = { 'kind': 'dispensable', 'address': 'Stats' }
  679. config['b10-stats-httpd'] = { 'kind': 'dispensable',
  680. 'address': 'StatsHttpd' }
  681. config['b10-cmdctl'] = { 'kind': 'needed', 'special': 'cmdctl' }
  682. if start_auth:
  683. config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
  684. config['b10-xfrout'] = { 'kind': 'dispensable',
  685. 'address': 'Xfrout' }
  686. config['b10-xfrin'] = { 'kind': 'dispensable',
  687. 'address': 'Xfrin' }
  688. config['b10-zonemgr'] = { 'kind': 'dispensable',
  689. 'address': 'Zonemgr' }
  690. if start_resolver:
  691. config['b10-resolver'] = { 'kind': 'needed',
  692. 'special': 'resolver' }
  693. return {'components': config}
  694. def config_start_init(self, start_auth, start_resolver):
  695. """
  696. Test the configuration is loaded at the startup.
  697. """
  698. bob = MockBob()
  699. config = self.construct_config(start_auth, start_resolver)
  700. class CC:
  701. def get_full_config(self):
  702. return config
  703. # Provide the fake CC with data
  704. bob.ccs = CC()
  705. # And make sure it's not overwritten
  706. def start_ccsession():
  707. bob.ccsession = True
  708. bob.start_ccsession = lambda _: start_ccsession()
  709. # We need to return the original _read_bind10_config
  710. bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
  711. bob.start_all_components()
  712. self.check_started(bob, True, start_auth, start_resolver)
  713. self.check_environment_unchanged()
  714. def test_start_none(self):
  715. self.config_start_init(False, False)
  716. def test_start_resolver(self):
  717. self.config_start_init(False, True)
  718. def test_start_auth(self):
  719. self.config_start_init(True, False)
  720. def test_start_both(self):
  721. self.config_start_init(True, True)
  722. def test_config_start(self):
  723. """
  724. Test that the configuration starts and stops components according
  725. to configuration changes.
  726. """
  727. # Create BoB and ensure correct initialization
  728. bob = MockBob()
  729. self.check_preconditions(bob)
  730. bob.start_all_components()
  731. bob.runnable = True
  732. bob.config_handler(self.construct_config(False, False))
  733. self.check_started_none(bob)
  734. # Enable both at once
  735. bob.config_handler(self.construct_config(True, True))
  736. self.check_started_both(bob)
  737. # Not touched by empty change
  738. bob.config_handler({})
  739. self.check_started_both(bob)
  740. # Not touched by change to the same configuration
  741. bob.config_handler(self.construct_config(True, True))
  742. self.check_started_both(bob)
  743. # Turn them both off again
  744. bob.config_handler(self.construct_config(False, False))
  745. self.check_started_none(bob)
  746. # Not touched by empty change
  747. bob.config_handler({})
  748. self.check_started_none(bob)
  749. # Not touched by change to the same configuration
  750. bob.config_handler(self.construct_config(False, False))
  751. self.check_started_none(bob)
  752. # Start and stop auth separately
  753. bob.config_handler(self.construct_config(True, False))
  754. self.check_started_auth(bob)
  755. bob.config_handler(self.construct_config(False, False))
  756. self.check_started_none(bob)
  757. # Start and stop resolver separately
  758. bob.config_handler(self.construct_config(False, True))
  759. self.check_started_resolver(bob)
  760. bob.config_handler(self.construct_config(False, False))
  761. self.check_started_none(bob)
  762. # Alternate
  763. bob.config_handler(self.construct_config(True, False))
  764. self.check_started_auth(bob)
  765. bob.config_handler(self.construct_config(False, True))
  766. self.check_started_resolver(bob)
  767. bob.config_handler(self.construct_config(True, False))
  768. self.check_started_auth(bob)
  769. def test_config_start_once(self):
  770. """
  771. Tests that a component is started only once.
  772. """
  773. # Create BoB and ensure correct initialization
  774. bob = MockBob()
  775. self.check_preconditions(bob)
  776. bob.start_all_components()
  777. bob.runnable = True
  778. bob.config_handler(self.construct_config(True, True))
  779. self.check_started_both(bob)
  780. bob.start_auth = lambda: self.fail("Started auth again")
  781. bob.start_xfrout = lambda: self.fail("Started xfrout again")
  782. bob.start_xfrin = lambda: self.fail("Started xfrin again")
  783. bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
  784. bob.start_resolver = lambda: self.fail("Started resolver again")
  785. # Send again we want to start them. Should not do it, as they are.
  786. bob.config_handler(self.construct_config(True, True))
  787. def test_config_not_started_early(self):
  788. """
  789. Test that components are not started by the config handler before
  790. startup.
  791. """
  792. bob = MockBob()
  793. self.check_preconditions(bob)
  794. bob.start_auth = lambda: self.fail("Started auth again")
  795. bob.start_xfrout = lambda: self.fail("Started xfrout again")
  796. bob.start_xfrin = lambda: self.fail("Started xfrin again")
  797. bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
  798. bob.start_resolver = lambda: self.fail("Started resolver again")
  799. bob.config_handler({'start_auth': True, 'start_resolver': True})
  800. # Checks that DHCP (v4 and v6) components are started when expected
  801. def test_start_dhcp(self):
  802. # Create BoB and ensure correct initialization
  803. bob = MockBob()
  804. self.check_preconditions(bob)
  805. bob.start_all_components()
  806. bob.config_handler(self.construct_config(False, False))
  807. self.check_started_dhcp(bob, False, False)
  808. def test_start_dhcp_v6only(self):
  809. # Create BoB and ensure correct initialization
  810. bob = MockBob()
  811. self.check_preconditions(bob)
  812. # v6 only enabled
  813. bob.start_all_components()
  814. bob.runnable = True
  815. bob._BoB_started = True
  816. config = self.construct_config(False, False)
  817. config['components']['b10-dhcp6'] = { 'kind': 'needed',
  818. 'address': 'Dhcp6' }
  819. bob.config_handler(config)
  820. self.check_started_dhcp(bob, False, True)
  821. # uncomment when dhcpv4 becomes implemented
  822. # v4 only enabled
  823. #bob.cfg_start_dhcp6 = False
  824. #bob.cfg_start_dhcp4 = True
  825. #self.check_started_dhcp(bob, True, False)
  826. # both v4 and v6 enabled
  827. #bob.cfg_start_dhcp6 = True
  828. #bob.cfg_start_dhcp4 = True
  829. #self.check_started_dhcp(bob, True, True)
  830. class MockComponent:
  831. def __init__(self, name, pid):
  832. self.name = lambda: name
  833. self.pid = lambda: pid
  834. class TestBossCmd(unittest.TestCase):
  835. def test_ping(self):
  836. """
  837. Confirm simple ping command works.
  838. """
  839. bob = MockBob()
  840. answer = bob.command_handler("ping", None)
  841. self.assertEqual(answer, {'result': [0, 'pong']})
  842. def test_show_processes_empty(self):
  843. """
  844. Confirm getting a list of processes works.
  845. """
  846. bob = MockBob()
  847. answer = bob.command_handler("show_processes", None)
  848. self.assertEqual(answer, {'result': [0, []]})
  849. def test_show_processes(self):
  850. """
  851. Confirm getting a list of processes works.
  852. """
  853. bob = MockBob()
  854. bob.register_process(1, MockComponent('first', 1))
  855. bob.register_process(2, MockComponent('second', 2))
  856. answer = bob.command_handler("show_processes", None)
  857. processes = [[1, 'first'],
  858. [2, 'second']]
  859. self.assertEqual(answer, {'result': [0, processes]})
  860. class TestParseArgs(unittest.TestCase):
  861. """
  862. This tests parsing of arguments of the bind10 master process.
  863. """
  864. #TODO: Write tests for the original parsing, bad options, etc.
  865. def test_no_opts(self):
  866. """
  867. Test correct default values when no options are passed.
  868. """
  869. options = parse_args([], TestOptParser)
  870. self.assertEqual(None, options.data_path)
  871. self.assertEqual(None, options.config_file)
  872. self.assertEqual(None, options.cmdctl_port)
  873. def test_data_path(self):
  874. """
  875. Test it can parse the data path.
  876. """
  877. self.assertRaises(OptsError, parse_args, ['-p'], TestOptParser)
  878. self.assertRaises(OptsError, parse_args, ['--data-path'],
  879. TestOptParser)
  880. options = parse_args(['-p', '/data/path'], TestOptParser)
  881. self.assertEqual('/data/path', options.data_path)
  882. options = parse_args(['--data-path=/data/path'], TestOptParser)
  883. self.assertEqual('/data/path', options.data_path)
  884. def test_config_filename(self):
  885. """
  886. Test it can parse the config switch.
  887. """
  888. self.assertRaises(OptsError, parse_args, ['-c'], TestOptParser)
  889. self.assertRaises(OptsError, parse_args, ['--config-file'],
  890. TestOptParser)
  891. options = parse_args(['-c', 'config-file'], TestOptParser)
  892. self.assertEqual('config-file', options.config_file)
  893. options = parse_args(['--config-file=config-file'], TestOptParser)
  894. self.assertEqual('config-file', options.config_file)
  895. def test_clear_config(self):
  896. options = parse_args([], TestOptParser)
  897. self.assertEqual(False, options.clear_config)
  898. options = parse_args(['--clear-config'], TestOptParser)
  899. self.assertEqual(True, options.clear_config)
  900. def test_nokill(self):
  901. options = parse_args([], TestOptParser)
  902. self.assertEqual(False, options.nokill)
  903. options = parse_args(['--no-kill'], TestOptParser)
  904. self.assertEqual(True, options.nokill)
  905. options = parse_args([], TestOptParser)
  906. self.assertEqual(False, options.nokill)
  907. options = parse_args(['-i'], TestOptParser)
  908. self.assertEqual(True, options.nokill)
  909. def test_cmdctl_port(self):
  910. """
  911. Test it can parse the command control port.
  912. """
  913. self.assertRaises(OptsError, parse_args, ['--cmdctl-port=abc'],
  914. TestOptParser)
  915. self.assertRaises(OptsError, parse_args, ['--cmdctl-port=100000000'],
  916. TestOptParser)
  917. self.assertRaises(OptsError, parse_args, ['--cmdctl-port'],
  918. TestOptParser)
  919. options = parse_args(['--cmdctl-port=1234'], TestOptParser)
  920. self.assertEqual(1234, options.cmdctl_port)
  921. class TestPIDFile(unittest.TestCase):
  922. def setUp(self):
  923. self.pid_file = '@builddir@' + os.sep + 'bind10.pid'
  924. if os.path.exists(self.pid_file):
  925. os.unlink(self.pid_file)
  926. def tearDown(self):
  927. if os.path.exists(self.pid_file):
  928. os.unlink(self.pid_file)
  929. def check_pid_file(self):
  930. # dump PID to the file, and confirm the content is correct
  931. dump_pid(self.pid_file)
  932. my_pid = os.getpid()
  933. self.assertEqual(my_pid, int(open(self.pid_file, "r").read()))
  934. def test_dump_pid(self):
  935. self.check_pid_file()
  936. # make sure any existing content will be removed
  937. open(self.pid_file, "w").write('dummy data\n')
  938. self.check_pid_file()
  939. def test_unlink_pid_file_notexist(self):
  940. dummy_data = 'dummy_data\n'
  941. open(self.pid_file, "w").write(dummy_data)
  942. unlink_pid_file("no_such_pid_file")
  943. # the file specified for unlink_pid_file doesn't exist,
  944. # and the original content of the file should be intact.
  945. self.assertEqual(dummy_data, open(self.pid_file, "r").read())
  946. def test_dump_pid_with_none(self):
  947. # Check the behavior of dump_pid() and unlink_pid_file() with None.
  948. # This should be no-op.
  949. dump_pid(None)
  950. self.assertFalse(os.path.exists(self.pid_file))
  951. dummy_data = 'dummy_data\n'
  952. open(self.pid_file, "w").write(dummy_data)
  953. unlink_pid_file(None)
  954. self.assertEqual(dummy_data, open(self.pid_file, "r").read())
  955. def test_dump_pid_failure(self):
  956. # the attempt to open file will fail, which should result in exception.
  957. self.assertRaises(IOError, dump_pid,
  958. 'nonexistent_dir' + os.sep + 'bind10.pid')
  959. class TestBossComponents(unittest.TestCase):
  960. """
  961. Test the boss propagates component configuration properly to the
  962. component configurator and acts sane.
  963. """
  964. def setUp(self):
  965. self.__param = None
  966. self.__called = False
  967. self.__compconfig = {
  968. 'comp': {
  969. 'kind': 'needed',
  970. 'process': 'cat'
  971. }
  972. }
  973. def __unary_hook(self, param):
  974. """
  975. A hook function that stores the parameter for later examination.
  976. """
  977. self.__param = param
  978. def __nullary_hook(self):
  979. """
  980. A hook function that notes down it was called.
  981. """
  982. self.__called = True
  983. def __check_core(self, config):
  984. """
  985. A function checking that the config contains parts for the valid
  986. core component configuration.
  987. """
  988. self.assertIsNotNone(config)
  989. for component in ['sockcreator', 'msgq', 'cfgmgr']:
  990. self.assertTrue(component in config)
  991. self.assertEqual(component, config[component]['special'])
  992. self.assertEqual('core', config[component]['kind'])
  993. def __check_extended(self, config):
  994. """
  995. This checks that the config contains the core and one more component.
  996. """
  997. self.__check_core(config)
  998. self.assertTrue('comp' in config)
  999. self.assertEqual('cat', config['comp']['process'])
  1000. self.assertEqual('needed', config['comp']['kind'])
  1001. self.assertEqual(4, len(config))
  1002. def test_correct_run(self):
  1003. """
  1004. Test the situation when we run in usual scenario, nothing fails,
  1005. we just start, reconfigure and then stop peacefully.
  1006. """
  1007. bob = MockBob()
  1008. # Start it
  1009. orig = bob._component_configurator.startup
  1010. bob._component_configurator.startup = self.__unary_hook
  1011. bob.start_all_components()
  1012. bob._component_configurator.startup = orig
  1013. self.__check_core(self.__param)
  1014. self.assertEqual(3, len(self.__param))
  1015. # Reconfigure it
  1016. self.__param = None
  1017. orig = bob._component_configurator.reconfigure
  1018. bob._component_configurator.reconfigure = self.__unary_hook
  1019. # Otherwise it does not work
  1020. bob.runnable = True
  1021. bob.config_handler({'components': self.__compconfig})
  1022. self.__check_extended(self.__param)
  1023. currconfig = self.__param
  1024. # If we reconfigure it, but it does not contain the components part,
  1025. # nothing is called
  1026. bob.config_handler({})
  1027. self.assertEqual(self.__param, currconfig)
  1028. self.__param = None
  1029. bob._component_configurator.reconfigure = orig
  1030. # Check a configuration that messes up the core components is rejected.
  1031. compconf = dict(self.__compconfig)
  1032. compconf['msgq'] = { 'process': 'echo' }
  1033. result = bob.config_handler({'components': compconf})
  1034. # Check it rejected it
  1035. self.assertEqual(1, result['result'][0])
  1036. # We can't call shutdown, that one relies on the stuff in main
  1037. # We check somewhere else that the shutdown is actually called
  1038. # from there (the test_kills).
  1039. def __real_test_kill(self, nokill = False):
  1040. """
  1041. Helper function that does the actual kill functionality testing.
  1042. """
  1043. bob = MockBob()
  1044. bob.nokill = nokill
  1045. killed = []
  1046. class ImmortalComponent:
  1047. """
  1048. An immortal component. It does not stop when it is told so
  1049. (anyway it is not told so). It does not die if it is killed
  1050. the first time. It dies only when killed forcefully.
  1051. """
  1052. def kill(self, forceful=False):
  1053. killed.append(forceful)
  1054. if forceful:
  1055. bob.components = {}
  1056. def pid(self):
  1057. return 1
  1058. def name(self):
  1059. return "Immortal"
  1060. bob.components = {}
  1061. bob.register_process(1, ImmortalComponent())
  1062. # While at it, we check the configurator shutdown is actually called
  1063. orig = bob._component_configurator.shutdown
  1064. bob._component_configurator.shutdown = self.__nullary_hook
  1065. self.__called = False
  1066. bob.ccs = MockModuleCCSession()
  1067. self.assertFalse(bob.ccs.stopped)
  1068. bob.shutdown()
  1069. self.assertTrue(bob.ccs.stopped)
  1070. # Here, killed is an array where False is added if SIGTERM
  1071. # should be sent, or True if SIGKILL should be sent, in order in
  1072. # which they're sent.
  1073. if nokill:
  1074. self.assertEqual([], killed)
  1075. else:
  1076. self.assertEqual([False, True], killed)
  1077. self.assertTrue(self.__called)
  1078. bob._component_configurator.shutdown = orig
  1079. def test_kills(self):
  1080. """
  1081. Test that the boss kills components which don't want to stop.
  1082. """
  1083. self.__real_test_kill()
  1084. def test_nokill(self):
  1085. """
  1086. Test that the boss *doesn't* kill components which don't want to
  1087. stop, when asked not to (by passing the --no-kill option which
  1088. sets bob.nokill to True).
  1089. """
  1090. self.__real_test_kill(True)
  1091. def test_component_shutdown(self):
  1092. """
  1093. Test the component_shutdown sets all variables accordingly.
  1094. """
  1095. bob = MockBob()
  1096. self.assertRaises(Exception, bob.component_shutdown, 1)
  1097. self.assertEqual(1, bob.exitcode)
  1098. bob._BoB__started = True
  1099. bob.component_shutdown(2)
  1100. self.assertEqual(2, bob.exitcode)
  1101. self.assertFalse(bob.runnable)
  1102. def test_init_config(self):
  1103. """
  1104. Test initial configuration is loaded.
  1105. """
  1106. bob = MockBob()
  1107. # Start it
  1108. bob._component_configurator.reconfigure = self.__unary_hook
  1109. # We need to return the original read_bind10_config
  1110. bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
  1111. # And provide a session to read the data from
  1112. class CC:
  1113. pass
  1114. bob.ccs = CC()
  1115. bob.ccs.get_full_config = lambda: {'components': self.__compconfig}
  1116. bob.start_all_components()
  1117. self.__check_extended(self.__param)
  1118. class SocketSrvTest(unittest.TestCase):
  1119. """
  1120. This tests some methods of boss related to the unix domain sockets used
  1121. to transfer other sockets to applications.
  1122. """
  1123. def setUp(self):
  1124. """
  1125. Create the boss to test, testdata and backup some functions.
  1126. """
  1127. self.__boss = BoB()
  1128. self.__select_backup = bind10_src.select.select
  1129. self.__select_called = None
  1130. self.__socket_data_called = None
  1131. self.__consumer_dead_called = None
  1132. self.__socket_request_handler_called = None
  1133. def tearDown(self):
  1134. """
  1135. Restore functions.
  1136. """
  1137. bind10_src.select.select = self.__select_backup
  1138. class __FalseSocket:
  1139. """
  1140. A mock socket for the select and accept and stuff like that.
  1141. """
  1142. def __init__(self, owner, fileno=42):
  1143. self.__owner = owner
  1144. self.__fileno = fileno
  1145. self.data = None
  1146. self.closed = False
  1147. def fileno(self):
  1148. return self.__fileno
  1149. def accept(self):
  1150. return (self.__class__(self.__owner, 13), "/path/to/socket")
  1151. def recv(self, bufsize, flags=0):
  1152. self.__owner.assertEqual(1, bufsize)
  1153. self.__owner.assertEqual(socket.MSG_DONTWAIT, flags)
  1154. if isinstance(self.data, socket.error):
  1155. raise self.data
  1156. elif self.data is not None:
  1157. if len(self.data):
  1158. result = self.data[0:1]
  1159. self.data = self.data[1:]
  1160. return result
  1161. else:
  1162. raise socket.error(errno.EAGAIN, "Would block")
  1163. else:
  1164. return b''
  1165. def close(self):
  1166. self.closed = True
  1167. class __CCS:
  1168. """
  1169. A mock CCS, just to provide the socket file number.
  1170. """
  1171. class __Socket:
  1172. def fileno(self):
  1173. return 1
  1174. def get_socket(self):
  1175. return self.__Socket()
  1176. def __select_accept(self, r, w, x, t):
  1177. self.__select_called = (r, w, x, t)
  1178. return ([42], [], [])
  1179. def __select_data(self, r, w, x, t):
  1180. self.__select_called = (r, w, x, t)
  1181. return ([13], [], [])
  1182. def __accept(self):
  1183. """
  1184. Hijact the accept method of the boss.
  1185. Notes down it was called and stops the boss.
  1186. """
  1187. self.__accept_called = True
  1188. self.__boss.runnable = False
  1189. def test_srv_accept_called(self):
  1190. """
  1191. Test that the _srv_accept method of boss is called when the listening
  1192. socket is readable.
  1193. """
  1194. self.__boss.runnable = True
  1195. self.__boss._srv_socket = self.__FalseSocket(self)
  1196. self.__boss._srv_accept = self.__accept
  1197. self.__boss.ccs = self.__CCS()
  1198. bind10_src.select.select = self.__select_accept
  1199. self.__boss.run(2)
  1200. # It called the accept
  1201. self.assertTrue(self.__accept_called)
  1202. # And the select had the right parameters
  1203. self.assertEqual(([2, 1, 42], [], [], None), self.__select_called)
  1204. def test_srv_accept(self):
  1205. """
  1206. Test how the _srv_accept method works.
  1207. """
  1208. self.__boss._srv_socket = self.__FalseSocket(self)
  1209. self.__boss._srv_accept()
  1210. # After we accepted, a new socket is added there
  1211. socket = self.__boss._unix_sockets[13][0]
  1212. # The socket is properly stored there
  1213. self.assertTrue(isinstance(socket, self.__FalseSocket))
  1214. # And the buffer (yet empty) is there
  1215. self.assertEqual({13: (socket, b'')}, self.__boss._unix_sockets)
  1216. def __socket_data(self, socket):
  1217. self.__boss.runnable = False
  1218. self.__socket_data_called = socket
  1219. def test_socket_data(self):
  1220. """
  1221. Test that a socket that wants attention gets it.
  1222. """
  1223. self.__boss._srv_socket = self.__FalseSocket(self)
  1224. self.__boss._socket_data = self.__socket_data
  1225. self.__boss.ccs = self.__CCS()
  1226. self.__boss._unix_sockets = {13: (self.__FalseSocket(self, 13), b'')}
  1227. self.__boss.runnable = True
  1228. bind10_src.select.select = self.__select_data
  1229. self.__boss.run(2)
  1230. self.assertEqual(13, self.__socket_data_called)
  1231. self.assertEqual(([2, 1, 42, 13], [], [], None), self.__select_called)
  1232. def __prepare_data(self, data):
  1233. socket = self.__FalseSocket(self, 13)
  1234. self.__boss._unix_sockets = {13: (socket, b'')}
  1235. socket.data = data
  1236. self.__boss.socket_consumer_dead = self.__consumer_dead
  1237. self.__boss.socket_request_handler = self.__socket_request_handler
  1238. return socket
  1239. def __consumer_dead(self, socket):
  1240. self.__consumer_dead_called = socket
  1241. def __socket_request_handler(self, token, socket):
  1242. self.__socket_request_handler_called = (token, socket)
  1243. def test_socket_closed(self):
  1244. """
  1245. Test that a socket is removed and the socket_consumer_dead is called
  1246. when it is closed.
  1247. """
  1248. socket = self.__prepare_data(None)
  1249. self.__boss._socket_data(13)
  1250. self.assertEqual(socket, self.__consumer_dead_called)
  1251. self.assertEqual({}, self.__boss._unix_sockets)
  1252. self.assertTrue(socket.closed)
  1253. def test_socket_short(self):
  1254. """
  1255. Test that if there's not enough data to get the whole socket, it is
  1256. kept there, but nothing is called.
  1257. """
  1258. socket = self.__prepare_data(b'tok')
  1259. self.__boss._socket_data(13)
  1260. self.assertEqual({13: (socket, b'tok')}, self.__boss._unix_sockets)
  1261. self.assertFalse(socket.closed)
  1262. self.assertIsNone(self.__consumer_dead_called)
  1263. self.assertIsNone(self.__socket_request_handler_called)
  1264. def test_socket_continue(self):
  1265. """
  1266. Test that we call the token handling function when the whole token
  1267. comes. This test pretends to continue reading where the previous one
  1268. stopped.
  1269. """
  1270. socket = self.__prepare_data(b"en\nanothe")
  1271. # The data to finish
  1272. self.__boss._unix_sockets[13] = (socket, b'tok')
  1273. self.__boss._socket_data(13)
  1274. self.assertEqual({13: (socket, b'anothe')}, self.__boss._unix_sockets)
  1275. self.assertFalse(socket.closed)
  1276. self.assertIsNone(self.__consumer_dead_called)
  1277. self.assertEqual((b'token', socket),
  1278. self.__socket_request_handler_called)
  1279. def test_broken_socket(self):
  1280. """
  1281. If the socket raises an exception during the read other than EAGAIN,
  1282. it is broken and we remove it.
  1283. """
  1284. sock = self.__prepare_data(socket.error(errno.ENOMEM,
  1285. "There's more memory available, but not for you"))
  1286. self.__boss._socket_data(13)
  1287. self.assertEqual(sock, self.__consumer_dead_called)
  1288. self.assertEqual({}, self.__boss._unix_sockets)
  1289. self.assertTrue(sock.closed)
  1290. class TestFunctions(unittest.TestCase):
  1291. def setUp(self):
  1292. self.lockfile_testpath = \
  1293. "@abs_top_builddir@/src/bin/bind10/tests/lockfile_test"
  1294. self.assertFalse(os.path.exists(self.lockfile_testpath))
  1295. os.mkdir(self.lockfile_testpath)
  1296. self.assertTrue(os.path.isdir(self.lockfile_testpath))
  1297. def tearDown(self):
  1298. os.rmdir(self.lockfile_testpath)
  1299. self.assertFalse(os.path.isdir(self.lockfile_testpath))
  1300. os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
  1301. def test_remove_lock_files(self):
  1302. os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
  1303. # create lockfiles for the testcase
  1304. lockfiles = ["logger_lockfile"]
  1305. for f in lockfiles:
  1306. fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
  1307. self.assertFalse(os.path.exists(fname))
  1308. open(fname, "w").close()
  1309. self.assertTrue(os.path.isfile(fname))
  1310. # first call should clear up all the lockfiles
  1311. bind10_src.remove_lock_files()
  1312. # check if the lockfiles exist
  1313. for f in lockfiles:
  1314. fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
  1315. self.assertFalse(os.path.isfile(fname))
  1316. # second call should not assert anyway
  1317. bind10_src.remove_lock_files()
  1318. if __name__ == '__main__':
  1319. # store os.environ for test_unchanged_environment
  1320. original_os_environ = copy.deepcopy(os.environ)
  1321. isc.log.resetUnitTestRootLogger()
  1322. unittest.main()