bind10_test.py.in 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987
  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. from bind10_src import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, _BASETIME
  16. # XXX: environment tests are currently disabled, due to the preprocessor
  17. # setup that we have now complicating the environment
  18. import unittest
  19. import sys
  20. import os
  21. import copy
  22. import signal
  23. import socket
  24. from isc.net.addr import IPAddr
  25. import time
  26. import isc
  27. import isc.log
  28. from isc.testutils.parse_args import TestOptParser, OptsError
  29. class TestProcessInfo(unittest.TestCase):
  30. def setUp(self):
  31. # redirect stdout to a pipe so we can check that our
  32. # process spawning is doing the right thing with stdout
  33. self.old_stdout = os.dup(sys.stdout.fileno())
  34. self.pipes = os.pipe()
  35. os.dup2(self.pipes[1], sys.stdout.fileno())
  36. os.close(self.pipes[1])
  37. # note that we use dup2() to restore the original stdout
  38. # to the main program ASAP in each test... this prevents
  39. # hangs reading from the child process (as the pipe is only
  40. # open in the child), and also insures nice pretty output
  41. def tearDown(self):
  42. # clean up our stdout munging
  43. os.dup2(self.old_stdout, sys.stdout.fileno())
  44. os.close(self.pipes[0])
  45. def test_init(self):
  46. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
  47. pi.spawn()
  48. os.dup2(self.old_stdout, sys.stdout.fileno())
  49. self.assertEqual(pi.name, 'Test Process')
  50. self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
  51. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  52. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
  53. self.assertEqual(pi.dev_null_stdout, False)
  54. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  55. self.assertNotEqual(pi.process, None)
  56. self.assertTrue(type(pi.pid) is int)
  57. # def test_setting_env(self):
  58. # pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
  59. # os.dup2(self.old_stdout, sys.stdout.fileno())
  60. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  61. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
  62. # 'FOO': 'BAR' })
  63. def test_setting_null_stdout(self):
  64. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
  65. dev_null_stdout=True)
  66. pi.spawn()
  67. os.dup2(self.old_stdout, sys.stdout.fileno())
  68. self.assertEqual(pi.dev_null_stdout, True)
  69. self.assertEqual(os.read(self.pipes[0], 100), b"")
  70. def test_respawn(self):
  71. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
  72. pi.spawn()
  73. # wait for old process to work...
  74. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  75. # respawn it
  76. old_pid = pi.pid
  77. pi.respawn()
  78. os.dup2(self.old_stdout, sys.stdout.fileno())
  79. # make sure the new one started properly
  80. self.assertEqual(pi.name, 'Test Process')
  81. self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
  82. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  83. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
  84. self.assertEqual(pi.dev_null_stdout, False)
  85. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  86. self.assertNotEqual(pi.process, None)
  87. self.assertTrue(type(pi.pid) is int)
  88. self.assertNotEqual(pi.pid, old_pid)
  89. class TestBoB(unittest.TestCase):
  90. def test_init(self):
  91. bob = BoB()
  92. self.assertEqual(bob.verbose, False)
  93. self.assertEqual(bob.msgq_socket_file, None)
  94. self.assertEqual(bob.cc_session, None)
  95. self.assertEqual(bob.ccs, None)
  96. self.assertEqual(bob.components, {})
  97. self.assertEqual(bob.dead_processes, {})
  98. self.assertEqual(bob.runnable, False)
  99. self.assertEqual(bob.uid, None)
  100. self.assertEqual(bob.username, None)
  101. self.assertEqual(bob.nocache, False)
  102. self.assertEqual(bob.cfg_start_auth, True)
  103. self.assertEqual(bob.cfg_start_resolver, False)
  104. self.assertEqual(bob.cfg_start_dhcp4, False)
  105. self.assertEqual(bob.cfg_start_dhcp6, False)
  106. def test_init_alternate_socket(self):
  107. bob = BoB("alt_socket_file")
  108. self.assertEqual(bob.verbose, False)
  109. self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
  110. self.assertEqual(bob.cc_session, None)
  111. self.assertEqual(bob.ccs, None)
  112. self.assertEqual(bob.components, {})
  113. self.assertEqual(bob.dead_processes, {})
  114. self.assertEqual(bob.runnable, False)
  115. self.assertEqual(bob.uid, None)
  116. self.assertEqual(bob.username, None)
  117. self.assertEqual(bob.nocache, False)
  118. self.assertEqual(bob.cfg_start_auth, True)
  119. self.assertEqual(bob.cfg_start_resolver, False)
  120. self.assertEqual(bob.cfg_start_dhcp4, False)
  121. self.assertEqual(bob.cfg_start_dhcp6, False)
  122. def test_command_handler(self):
  123. class DummySession():
  124. def group_sendmsg(self, msg, group):
  125. (self.msg, self.group) = (msg, group)
  126. def group_recvmsg(self, nonblock, seq): pass
  127. class DummyModuleCCSession():
  128. module_spec = isc.config.module_spec.ModuleSpec({
  129. "module_name": "Boss",
  130. "statistics": [
  131. {
  132. "item_name": "boot_time",
  133. "item_type": "string",
  134. "item_optional": False,
  135. "item_default": "1970-01-01T00:00:00Z",
  136. "item_title": "Boot time",
  137. "item_description": "A date time when bind10 process starts initially",
  138. "item_format": "date-time"
  139. }
  140. ]
  141. })
  142. def get_module_spec(self):
  143. return self.module_spec
  144. bob = BoB()
  145. bob.verbose = True
  146. bob.cc_session = DummySession()
  147. bob.ccs = DummyModuleCCSession()
  148. # a bad command
  149. self.assertEqual(bob.command_handler(-1, None),
  150. isc.config.ccsession.create_answer(1, "bad command"))
  151. # "shutdown" command
  152. self.assertEqual(bob.command_handler("shutdown", None),
  153. isc.config.ccsession.create_answer(0))
  154. self.assertFalse(bob.runnable)
  155. # "getstats" command
  156. self.assertEqual(bob.command_handler("getstats", None),
  157. isc.config.ccsession.create_answer(0,
  158. { "owner": "Boss",
  159. "data": {
  160. 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
  161. }}))
  162. # "sendstats" command
  163. self.assertEqual(bob.command_handler("sendstats", None),
  164. isc.config.ccsession.create_answer(0))
  165. self.assertEqual(bob.cc_session.group, "Stats")
  166. self.assertEqual(bob.cc_session.msg,
  167. isc.config.ccsession.create_command(
  168. "set", { "owner": "Boss",
  169. "data": {
  170. "boot_time": time.strftime("%Y-%m-%dT%H:%M:%SZ", _BASETIME)
  171. }}))
  172. # "ping" command
  173. self.assertEqual(bob.command_handler("ping", None),
  174. isc.config.ccsession.create_answer(0, "pong"))
  175. # "show_processes" command
  176. self.assertEqual(bob.command_handler("show_processes", None),
  177. isc.config.ccsession.create_answer(0,
  178. bob.get_processes()))
  179. # an unknown command
  180. self.assertEqual(bob.command_handler("__UNKNOWN__", None),
  181. isc.config.ccsession.create_answer(1, "Unknown command"))
  182. # Class for testing the BoB without actually starting processes.
  183. # This is used for testing the start/stop components routines and
  184. # the BoB commands.
  185. #
  186. # Testing that external processes start is outside the scope
  187. # of the unit test, by overriding the process start methods we can check
  188. # that the right processes are started depending on the configuration
  189. # options.
  190. class MockBob(BoB):
  191. def __init__(self):
  192. BoB.__init__(self)
  193. # Set flags as to which of the overridden methods has been run.
  194. self.msgq = False
  195. self.cfgmgr = False
  196. self.ccsession = False
  197. self.auth = False
  198. self.resolver = False
  199. self.xfrout = False
  200. self.xfrin = False
  201. self.zonemgr = False
  202. self.stats = False
  203. self.stats_httpd = False
  204. self.cmdctl = False
  205. self.dhcp6 = False
  206. self.dhcp4 = False
  207. self.c_channel_env = {}
  208. self.components = { }
  209. self.creator = False
  210. class MockSockCreator(isc.bind10.component.Component):
  211. def __init__(self, process, boss, kind, address=None, params=None):
  212. isc.bind10.component.Component.__init__(self, process, boss,
  213. kind, 'SockCreator')
  214. self._start_func = boss.start_creator
  215. specials = isc.bind10.special_component.get_specials()
  216. specials['sockcreator'] = MockSockCreator
  217. self._component_configurator = \
  218. isc.bind10.component.Configurator(self, specials)
  219. def start_creator(self):
  220. self.creator = True
  221. procinfo = ProcessInfo('b10-sockcreator', ['/bin/false'])
  222. procinfo.pid = 1
  223. return procinfo
  224. def _read_bind10_config(self):
  225. # Configuration options are set directly
  226. pass
  227. def start_msgq(self):
  228. self.msgq = True
  229. procinfo = ProcessInfo('b10-msgq', ['/bin/false'])
  230. procinfo.pid = 2
  231. return procinfo
  232. def start_ccsession(self, c_channel_env):
  233. # this is not a process, don't have to do anything with procinfo
  234. self.ccsession = True
  235. def start_cfgmgr(self):
  236. self.cfgmgr = True
  237. procinfo = ProcessInfo('b10-cfgmgr', ['/bin/false'])
  238. procinfo.pid = 3
  239. return procinfo
  240. def start_auth(self):
  241. self.auth = True
  242. procinfo = ProcessInfo('b10-auth', ['/bin/false'])
  243. procinfo.pid = 5
  244. return procinfo
  245. def start_resolver(self):
  246. self.resolver = True
  247. procinfo = ProcessInfo('b10-resolver', ['/bin/false'])
  248. procinfo.pid = 6
  249. return procinfo
  250. def start_simple(self, name):
  251. procmap = { 'b10-xfrout': self.start_xfrout,
  252. 'b10-zonemgr': self.start_zonemgr,
  253. 'b10-stats': self.start_stats,
  254. 'b10-stats-httpd': self.start_stats_httpd,
  255. 'b10-cmdctl': self.start_cmdctl,
  256. 'b10-dhcp6': self.start_dhcp6,
  257. 'b10-dhcp4': self.start_dhcp4 }
  258. return procmap[name]()
  259. def start_xfrout(self):
  260. self.xfrout = True
  261. procinfo = ProcessInfo('b10-xfrout', ['/bin/false'])
  262. procinfo.pid = 7
  263. return procinfo
  264. def start_xfrin(self):
  265. self.xfrin = True
  266. procinfo = ProcessInfo('b10-xfrin', ['/bin/false'])
  267. procinfo.pid = 8
  268. return procinfo
  269. def start_zonemgr(self):
  270. self.zonemgr = True
  271. procinfo = ProcessInfo('b10-zonemgr', ['/bin/false'])
  272. procinfo.pid = 9
  273. return procinfo
  274. def start_stats(self):
  275. self.stats = True
  276. procinfo = ProcessInfo('b10-stats', ['/bin/false'])
  277. procinfo.pid = 10
  278. return procinfo
  279. def start_stats_httpd(self):
  280. self.stats_httpd = True
  281. procinfo = ProcessInfo('b10-stats-httpd', ['/bin/false'])
  282. procinfo.pid = 11
  283. return procinfo
  284. def start_cmdctl(self):
  285. self.cmdctl = True
  286. procinfo = ProcessInfo('b10-cmdctl', ['/bin/false'])
  287. procinfo.pid = 12
  288. return procinfo
  289. def start_dhcp6(self):
  290. self.dhcp6 = True
  291. procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
  292. procinfo.pid = 13
  293. return procinfo
  294. def start_dhcp4(self):
  295. self.dhcp4 = True
  296. procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
  297. procinfo.pid = 14
  298. return procinfo
  299. def stop_process(self, process, recipient):
  300. procmap = { 'b10-auth': self.stop_auth,
  301. 'b10-resolver': self.stop_resolver,
  302. 'b10-xfrout': self.stop_xfrout,
  303. 'b10-xfrin': self.stop_xfrin,
  304. 'b10-zonemgr': self.stop_zonemgr,
  305. 'b10-stats': self.stop_stats,
  306. 'b10-stats-httpd': self.stop_stats_httpd,
  307. 'b10-cmdctl': self.stop_cmdctl }
  308. procmap[process]()
  309. # Some functions to pretend we stop processes, use by stop_process
  310. def stop_msgq(self):
  311. if self.msgq:
  312. del self.components[2]
  313. self.msgq = False
  314. def stop_cfgmgr(self):
  315. if self.cfgmgr:
  316. del self.components[3]
  317. self.cfgmgr = False
  318. def stop_auth(self):
  319. if self.auth:
  320. del self.components[5]
  321. self.auth = False
  322. def stop_resolver(self):
  323. if self.resolver:
  324. del self.components[6]
  325. self.resolver = False
  326. def stop_xfrout(self):
  327. if self.xfrout:
  328. del self.components[7]
  329. self.xfrout = False
  330. def stop_xfrin(self):
  331. if self.xfrin:
  332. del self.components[8]
  333. self.xfrin = False
  334. def stop_zonemgr(self):
  335. if self.zonemgr:
  336. del self.components[9]
  337. self.zonemgr = False
  338. def stop_stats(self):
  339. if self.stats:
  340. del self.components[10]
  341. self.stats = False
  342. def stop_stats_httpd(self):
  343. if self.stats_httpd:
  344. del self.components[11]
  345. self.stats_httpd = False
  346. def stop_cmdctl(self):
  347. if self.cmdctl:
  348. del self.components[12]
  349. self.cmdctl = False
  350. class TestStartStopProcessesBob(unittest.TestCase):
  351. """
  352. Check that the start_all_components method starts the right combination
  353. of components and that the right components are started and stopped
  354. according to changes in configuration.
  355. """
  356. def check_environment_unchanged(self):
  357. # Check whether the environment has not been changed
  358. self.assertEqual(original_os_environ, os.environ)
  359. def check_started(self, bob, core, auth, resolver):
  360. """
  361. Check that the right sets of services are started. The ones that
  362. should be running are specified by the core, auth and resolver parameters
  363. (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
  364. and -zonemgr).
  365. """
  366. self.assertEqual(bob.msgq, core)
  367. self.assertEqual(bob.cfgmgr, core)
  368. self.assertEqual(bob.ccsession, core)
  369. self.assertEqual(bob.creator, core)
  370. self.assertEqual(bob.auth, auth)
  371. self.assertEqual(bob.resolver, resolver)
  372. self.assertEqual(bob.xfrout, auth)
  373. self.assertEqual(bob.xfrin, auth)
  374. self.assertEqual(bob.zonemgr, auth)
  375. self.assertEqual(bob.stats, core)
  376. self.assertEqual(bob.stats_httpd, core)
  377. self.assertEqual(bob.cmdctl, core)
  378. self.check_environment_unchanged()
  379. def check_preconditions(self, bob):
  380. self.check_started(bob, False, False, False)
  381. def check_started_none(self, bob):
  382. """
  383. Check that the situation is according to configuration where no servers
  384. should be started. Some components still need to be running.
  385. """
  386. self.check_started(bob, True, False, False)
  387. self.check_environment_unchanged()
  388. def check_started_both(self, bob):
  389. """
  390. Check the situation is according to configuration where both servers
  391. (auth and resolver) are enabled.
  392. """
  393. self.check_started(bob, True, True, True)
  394. self.check_environment_unchanged()
  395. def check_started_auth(self, bob):
  396. """
  397. Check the set of components needed to run auth only is started.
  398. """
  399. self.check_started(bob, True, True, False)
  400. self.check_environment_unchanged()
  401. def check_started_resolver(self, bob):
  402. """
  403. Check the set of components needed to run resolver only is started.
  404. """
  405. self.check_started(bob, True, False, True)
  406. self.check_environment_unchanged()
  407. def check_started_dhcp(self, bob, v4, v6):
  408. """
  409. Check if proper combinations of DHCPv4 and DHCpv6 can be started
  410. """
  411. self.assertEqual(v4, bob.dhcp4)
  412. self.assertEqual(v6, bob.dhcp6)
  413. self.check_environment_unchanged()
  414. def construct_config(self, start_auth, start_resolver):
  415. # The things that are common, not turned on an off
  416. config = {}
  417. config['b10-stats'] = { 'kind': 'dispensable', 'address': 'Stats' }
  418. config['b10-stats-httpd'] = { 'kind': 'dispensable',
  419. 'address': 'StatsHttpd' }
  420. config['b10-cmdctl'] = { 'kind': 'needed', 'special': 'cmdctl' }
  421. if start_auth:
  422. config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
  423. config['b10-xfrout'] = { 'kind': 'dispensable',
  424. 'address': 'Xfrout' }
  425. config['b10-xfrin'] = { 'kind': 'dispensable', 'special': 'xfrin' }
  426. config['b10-zonemgr'] = { 'kind': 'dispensable',
  427. 'address': 'Zonemgr' }
  428. if start_resolver:
  429. config['b10-resolver'] = { 'kind': 'needed',
  430. 'special': 'resolver' }
  431. return {'components': config}
  432. def config_start_init(self, start_auth, start_resolver):
  433. """
  434. Test the configuration is loaded at the startup.
  435. """
  436. bob = MockBob()
  437. config = self.construct_config(start_auth, start_resolver)
  438. class CC:
  439. def get_full_config(self):
  440. return config
  441. # Provide the fake CC with data
  442. bob.ccs = CC()
  443. # And make sure it's not overwritten
  444. def start_ccsession():
  445. bob.ccsession = True
  446. bob.start_ccsession = lambda _: start_ccsession()
  447. # We need to return the original _read_bind10_config
  448. bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
  449. bob.start_all_components()
  450. self.check_started(bob, True, start_auth, start_resolver)
  451. self.check_environment_unchanged()
  452. def test_start_none(self):
  453. self.config_start_init(False, False)
  454. def test_start_resolver(self):
  455. self.config_start_init(False, True)
  456. def test_start_auth(self):
  457. self.config_start_init(True, False)
  458. def test_start_both(self):
  459. self.config_start_init(True, True)
  460. def test_config_start(self):
  461. """
  462. Test that the configuration starts and stops components according
  463. to configuration changes.
  464. """
  465. # Create BoB and ensure correct initialization
  466. bob = MockBob()
  467. self.check_preconditions(bob)
  468. bob.start_all_components()
  469. bob.runnable = True
  470. bob.config_handler(self.construct_config(False, False))
  471. self.check_started_none(bob)
  472. # Enable both at once
  473. bob.config_handler(self.construct_config(True, True))
  474. self.check_started_both(bob)
  475. # Not touched by empty change
  476. bob.config_handler({})
  477. self.check_started_both(bob)
  478. # Not touched by change to the same configuration
  479. bob.config_handler(self.construct_config(True, True))
  480. self.check_started_both(bob)
  481. # Turn them both off again
  482. bob.config_handler(self.construct_config(False, False))
  483. self.check_started_none(bob)
  484. # Not touched by empty change
  485. bob.config_handler({})
  486. self.check_started_none(bob)
  487. # Not touched by change to the same configuration
  488. bob.config_handler(self.construct_config(False, False))
  489. self.check_started_none(bob)
  490. # Start and stop auth separately
  491. bob.config_handler(self.construct_config(True, False))
  492. self.check_started_auth(bob)
  493. bob.config_handler(self.construct_config(False, False))
  494. self.check_started_none(bob)
  495. # Start and stop resolver separately
  496. bob.config_handler(self.construct_config(False, True))
  497. self.check_started_resolver(bob)
  498. bob.config_handler(self.construct_config(False, False))
  499. self.check_started_none(bob)
  500. # Alternate
  501. bob.config_handler(self.construct_config(True, False))
  502. self.check_started_auth(bob)
  503. bob.config_handler(self.construct_config(False, True))
  504. self.check_started_resolver(bob)
  505. bob.config_handler(self.construct_config(True, False))
  506. self.check_started_auth(bob)
  507. def test_config_start_once(self):
  508. """
  509. Tests that a component is started only once.
  510. """
  511. # Create BoB and ensure correct initialization
  512. bob = MockBob()
  513. self.check_preconditions(bob)
  514. bob.start_all_components()
  515. bob.runnable = True
  516. bob.config_handler(self.construct_config(True, True))
  517. self.check_started_both(bob)
  518. bob.start_auth = lambda: self.fail("Started auth again")
  519. bob.start_xfrout = lambda: self.fail("Started xfrout again")
  520. bob.start_xfrin = lambda: self.fail("Started xfrin again")
  521. bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
  522. bob.start_resolver = lambda: self.fail("Started resolver again")
  523. # Send again we want to start them. Should not do it, as they are.
  524. bob.config_handler(self.construct_config(True, True))
  525. def test_config_not_started_early(self):
  526. """
  527. Test that components are not started by the config handler before
  528. startup.
  529. """
  530. bob = MockBob()
  531. self.check_preconditions(bob)
  532. bob.start_auth = lambda: self.fail("Started auth again")
  533. bob.start_xfrout = lambda: self.fail("Started xfrout again")
  534. bob.start_xfrin = lambda: self.fail("Started xfrin again")
  535. bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
  536. bob.start_resolver = lambda: self.fail("Started resolver again")
  537. bob.config_handler({'start_auth': True, 'start_resolver': True})
  538. # Checks that DHCP (v4 and v6) components are started when expected
  539. def test_start_dhcp(self):
  540. # Create BoB and ensure correct initialization
  541. bob = MockBob()
  542. self.check_preconditions(bob)
  543. bob.start_all_components()
  544. bob.config_handler(self.construct_config(False, False))
  545. self.check_started_dhcp(bob, False, False)
  546. def test_start_dhcp_v6only(self):
  547. # Create BoB and ensure correct initialization
  548. bob = MockBob()
  549. self.check_preconditions(bob)
  550. # v6 only enabled
  551. bob.start_all_components()
  552. bob.runnable = True
  553. bob._BoB_started = True
  554. config = self.construct_config(False, False)
  555. config['components']['b10-dhcp6'] = { 'kind': 'needed',
  556. 'address': 'Dhcp6' }
  557. bob.config_handler(config)
  558. self.check_started_dhcp(bob, False, True)
  559. # uncomment when dhcpv4 becomes implemented
  560. # v4 only enabled
  561. #bob.cfg_start_dhcp6 = False
  562. #bob.cfg_start_dhcp4 = True
  563. #self.check_started_dhcp(bob, True, False)
  564. # both v4 and v6 enabled
  565. #bob.cfg_start_dhcp6 = True
  566. #bob.cfg_start_dhcp4 = True
  567. #self.check_started_dhcp(bob, True, True)
  568. class MockComponent:
  569. def __init__(self, name, pid):
  570. self.name = lambda: name
  571. self.pid = lambda: pid
  572. class TestBossCmd(unittest.TestCase):
  573. def test_ping(self):
  574. """
  575. Confirm simple ping command works.
  576. """
  577. bob = MockBob()
  578. answer = bob.command_handler("ping", None)
  579. self.assertEqual(answer, {'result': [0, 'pong']})
  580. def test_show_processes_empty(self):
  581. """
  582. Confirm getting a list of processes works.
  583. """
  584. bob = MockBob()
  585. answer = bob.command_handler("show_processes", None)
  586. self.assertEqual(answer, {'result': [0, []]})
  587. def test_show_processes(self):
  588. """
  589. Confirm getting a list of processes works.
  590. """
  591. bob = MockBob()
  592. bob.register_process(1, MockComponent('first', 1))
  593. bob.register_process(2, MockComponent('second', 2))
  594. answer = bob.command_handler("show_processes", None)
  595. processes = [[1, 'first'],
  596. [2, 'second']]
  597. self.assertEqual(answer, {'result': [0, processes]})
  598. class TestParseArgs(unittest.TestCase):
  599. """
  600. This tests parsing of arguments of the bind10 master process.
  601. """
  602. #TODO: Write tests for the original parsing, bad options, etc.
  603. def test_no_opts(self):
  604. """
  605. Test correct default values when no options are passed.
  606. """
  607. options = parse_args([], TestOptParser)
  608. self.assertEqual(None, options.data_path)
  609. self.assertEqual(None, options.config_file)
  610. self.assertEqual(None, options.cmdctl_port)
  611. def test_data_path(self):
  612. """
  613. Test it can parse the data path.
  614. """
  615. self.assertRaises(OptsError, parse_args, ['-p'], TestOptParser)
  616. self.assertRaises(OptsError, parse_args, ['--data-path'],
  617. TestOptParser)
  618. options = parse_args(['-p', '/data/path'], TestOptParser)
  619. self.assertEqual('/data/path', options.data_path)
  620. options = parse_args(['--data-path=/data/path'], TestOptParser)
  621. self.assertEqual('/data/path', options.data_path)
  622. def test_config_filename(self):
  623. """
  624. Test it can parse the config switch.
  625. """
  626. self.assertRaises(OptsError, parse_args, ['-c'], TestOptParser)
  627. self.assertRaises(OptsError, parse_args, ['--config-file'],
  628. TestOptParser)
  629. options = parse_args(['-c', 'config-file'], TestOptParser)
  630. self.assertEqual('config-file', options.config_file)
  631. options = parse_args(['--config-file=config-file'], TestOptParser)
  632. self.assertEqual('config-file', options.config_file)
  633. def test_cmdctl_port(self):
  634. """
  635. Test it can parse the command control port.
  636. """
  637. self.assertRaises(OptsError, parse_args, ['--cmdctl-port=abc'],
  638. TestOptParser)
  639. self.assertRaises(OptsError, parse_args, ['--cmdctl-port=100000000'],
  640. TestOptParser)
  641. self.assertRaises(OptsError, parse_args, ['--cmdctl-port'],
  642. TestOptParser)
  643. options = parse_args(['--cmdctl-port=1234'], TestOptParser)
  644. self.assertEqual(1234, options.cmdctl_port)
  645. def test_brittle(self):
  646. """
  647. Test we can use the "brittle" flag.
  648. """
  649. options = parse_args([], TestOptParser)
  650. self.assertFalse(options.brittle)
  651. options = parse_args(['--brittle'], TestOptParser)
  652. self.assertTrue(options.brittle)
  653. class TestPIDFile(unittest.TestCase):
  654. def setUp(self):
  655. self.pid_file = '@builddir@' + os.sep + 'bind10.pid'
  656. if os.path.exists(self.pid_file):
  657. os.unlink(self.pid_file)
  658. def tearDown(self):
  659. if os.path.exists(self.pid_file):
  660. os.unlink(self.pid_file)
  661. def check_pid_file(self):
  662. # dump PID to the file, and confirm the content is correct
  663. dump_pid(self.pid_file)
  664. my_pid = os.getpid()
  665. self.assertEqual(my_pid, int(open(self.pid_file, "r").read()))
  666. def test_dump_pid(self):
  667. self.check_pid_file()
  668. # make sure any existing content will be removed
  669. open(self.pid_file, "w").write('dummy data\n')
  670. self.check_pid_file()
  671. def test_unlink_pid_file_notexist(self):
  672. dummy_data = 'dummy_data\n'
  673. open(self.pid_file, "w").write(dummy_data)
  674. unlink_pid_file("no_such_pid_file")
  675. # the file specified for unlink_pid_file doesn't exist,
  676. # and the original content of the file should be intact.
  677. self.assertEqual(dummy_data, open(self.pid_file, "r").read())
  678. def test_dump_pid_with_none(self):
  679. # Check the behavior of dump_pid() and unlink_pid_file() with None.
  680. # This should be no-op.
  681. dump_pid(None)
  682. self.assertFalse(os.path.exists(self.pid_file))
  683. dummy_data = 'dummy_data\n'
  684. open(self.pid_file, "w").write(dummy_data)
  685. unlink_pid_file(None)
  686. self.assertEqual(dummy_data, open(self.pid_file, "r").read())
  687. def test_dump_pid_failure(self):
  688. # the attempt to open file will fail, which should result in exception.
  689. self.assertRaises(IOError, dump_pid,
  690. 'nonexistent_dir' + os.sep + 'bind10.pid')
  691. # TODO: Do we want brittle mode? Probably yes. So we need to re-enable to after that.
  692. @unittest.skip("Brittle mode temporarily broken")
  693. class TestBrittle(unittest.TestCase):
  694. def test_brittle_disabled(self):
  695. bob = MockBob()
  696. bob.start_all_components()
  697. bob.runnable = True
  698. bob.reap_children()
  699. self.assertTrue(bob.runnable)
  700. def simulated_exit(self):
  701. ret_val = self.exit_info
  702. self.exit_info = (0, 0)
  703. return ret_val
  704. def test_brittle_enabled(self):
  705. bob = MockBob()
  706. bob.start_all_components()
  707. bob.runnable = True
  708. bob.brittle = True
  709. self.exit_info = (5, 0)
  710. bob._get_process_exit_status = self.simulated_exit
  711. old_stdout = sys.stdout
  712. sys.stdout = open("/dev/null", "w")
  713. bob.reap_children()
  714. sys.stdout = old_stdout
  715. self.assertFalse(bob.runnable)
  716. class TestBossComponents(unittest.TestCase):
  717. """
  718. Test the boss propagates component configuration properly to the
  719. component configurator and acts sane.
  720. """
  721. def setUp(self):
  722. self.__param = None
  723. self.__called = False
  724. self.__compconfig = {
  725. 'comp': {
  726. 'kind': 'needed',
  727. 'process': 'cat'
  728. }
  729. }
  730. def __unary_hook(self, param):
  731. """
  732. A hook function that stores the parameter for later examination.
  733. """
  734. self.__param = param
  735. def __nullary_hook(self):
  736. """
  737. A hook function that notes down it was called.
  738. """
  739. self.__called = True
  740. def __check_core(self, config):
  741. """
  742. A function checking that the config contains parts for the valid
  743. core component configuration.
  744. """
  745. self.assertIsNotNone(config)
  746. for component in ['sockcreator', 'msgq', 'cfgmgr']:
  747. self.assertTrue(component in config)
  748. self.assertEqual(component, config[component]['special'])
  749. self.assertEqual('core', config[component]['kind'])
  750. def __check_extended(self, config):
  751. """
  752. This checks that the config contains the core and one more component.
  753. """
  754. self.__check_core(config)
  755. self.assertTrue('comp' in config)
  756. self.assertEqual('cat', config['comp']['process'])
  757. self.assertEqual('needed', config['comp']['kind'])
  758. self.assertEqual(4, len(config))
  759. def test_correct_run(self):
  760. """
  761. Test the situation when we run in usual scenario, nothing fails,
  762. we just start, reconfigure and then stop peacefully.
  763. """
  764. bob = MockBob()
  765. # Start it
  766. orig = bob._component_configurator.startup
  767. bob._component_configurator.startup = self.__unary_hook
  768. bob.start_all_components()
  769. bob._component_configurator.startup = orig
  770. self.__check_core(self.__param)
  771. self.assertEqual(3, len(self.__param))
  772. # Reconfigure it
  773. self.__param = None
  774. orig = bob._component_configurator.reconfigure
  775. bob._component_configurator.reconfigure = self.__unary_hook
  776. # Otherwise it does not work
  777. bob.runnable = True
  778. bob.config_handler({'components': self.__compconfig})
  779. self.__check_extended(self.__param)
  780. currconfig = self.__param
  781. # If we reconfigure it, but it does not contain the components part,
  782. # nothing is called
  783. bob.config_handler({})
  784. self.assertEqual(self.__param, currconfig)
  785. self.__param = None
  786. bob._component_configurator.reconfigure = orig
  787. # Check a configuration that messes up the core components is rejected.
  788. compconf = dict(self.__compconfig)
  789. compconf['msgq'] = { 'process': 'echo' }
  790. result = bob.config_handler({'components': compconf})
  791. # Check it rejected it
  792. self.assertEqual(1, result['result'][0])
  793. # We can't call shutdown, that one relies on the stuff in main
  794. # We check somewhere else that the shutdown is actually called
  795. # from there (the test_kills).
  796. def test_kills(self):
  797. """
  798. Test that the boss kills components which don't want to stop.
  799. """
  800. bob = MockBob()
  801. killed = []
  802. class ImmortalComponent:
  803. """
  804. An immortal component. It does not stop when it is told so
  805. (anyway it is not told so). It does not die if it is killed
  806. the first time. It dies only when killed forcefully.
  807. """
  808. def kill(self, forcefull=False):
  809. killed.append(forcefull)
  810. if forcefull:
  811. bob.components = {}
  812. def pid(self):
  813. return 1
  814. def name(self):
  815. return "Immortal"
  816. bob.components = {}
  817. bob.register_process(1, ImmortalComponent())
  818. # While at it, we check the configurator shutdown is actually called
  819. orig = bob._component_configurator.shutdown
  820. bob._component_configurator.shutdown = self.__nullary_hook
  821. self.__called = False
  822. bob.shutdown()
  823. self.assertEqual([False, True], killed)
  824. self.assertTrue(self.__called)
  825. bob._component_configurator.shutdown = orig
  826. def test_component_shutdown(self):
  827. """
  828. Test the component_shutdown sets all variables accordingly.
  829. """
  830. bob = MockBob()
  831. self.assertRaises(Exception, bob.component_shutdown, 1)
  832. self.assertEqual(1, bob.exitcode)
  833. bob._BoB__started = True
  834. bob.component_shutdown(2)
  835. self.assertEqual(2, bob.exitcode)
  836. self.assertFalse(bob.runnable)
  837. def test_init_config(self):
  838. """
  839. Test initial configuration is loaded.
  840. """
  841. bob = MockBob()
  842. # Start it
  843. bob._component_configurator.reconfigure = self.__unary_hook
  844. # We need to return the original read_bind10_config
  845. bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
  846. # And provide a session to read the data from
  847. class CC:
  848. pass
  849. bob.ccs = CC()
  850. bob.ccs.get_full_config = lambda: {'components': self.__compconfig}
  851. bob.start_all_components()
  852. self.__check_extended(self.__param)
  853. if __name__ == '__main__':
  854. # store os.environ for test_unchanged_environment
  855. original_os_environ = copy.deepcopy(os.environ)
  856. isc.log.resetUnitTestRootLogger()
  857. unittest.main()