bind10_test.py.in 41 KB

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