bind10_test.py.in 83 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342
  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 os.path
  26. import copy
  27. import signal
  28. import socket
  29. from isc.net.addr import IPAddr
  30. import time
  31. import isc
  32. import isc.log
  33. import isc.bind10.socket_cache
  34. import errno
  35. import random
  36. from isc.testutils.parse_args import TestOptParser, OptsError
  37. from isc.testutils.ccsession_mock import MockModuleCCSession
  38. class TestProcessInfo(unittest.TestCase):
  39. def setUp(self):
  40. # redirect stdout to a pipe so we can check that our
  41. # process spawning is doing the right thing with stdout
  42. self.old_stdout = os.dup(sys.stdout.fileno())
  43. self.pipes = os.pipe()
  44. os.dup2(self.pipes[1], sys.stdout.fileno())
  45. os.close(self.pipes[1])
  46. # note that we use dup2() to restore the original stdout
  47. # to the main program ASAP in each test... this prevents
  48. # hangs reading from the child process (as the pipe is only
  49. # open in the child), and also insures nice pretty output
  50. def tearDown(self):
  51. # clean up our stdout munging
  52. os.dup2(self.old_stdout, sys.stdout.fileno())
  53. os.close(self.pipes[0])
  54. def test_init(self):
  55. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
  56. pi.spawn()
  57. os.dup2(self.old_stdout, sys.stdout.fileno())
  58. self.assertEqual(pi.name, 'Test Process')
  59. self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
  60. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  61. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
  62. self.assertEqual(pi.dev_null_stdout, False)
  63. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  64. self.assertNotEqual(pi.process, None)
  65. self.assertTrue(type(pi.pid) is int)
  66. # def test_setting_env(self):
  67. # pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
  68. # os.dup2(self.old_stdout, sys.stdout.fileno())
  69. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  70. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
  71. # 'FOO': 'BAR' })
  72. def test_setting_null_stdout(self):
  73. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
  74. dev_null_stdout=True)
  75. pi.spawn()
  76. os.dup2(self.old_stdout, sys.stdout.fileno())
  77. self.assertEqual(pi.dev_null_stdout, True)
  78. self.assertEqual(os.read(self.pipes[0], 100), b"")
  79. def test_respawn(self):
  80. pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
  81. pi.spawn()
  82. # wait for old process to work...
  83. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  84. # respawn it
  85. old_pid = pi.pid
  86. pi.respawn()
  87. os.dup2(self.old_stdout, sys.stdout.fileno())
  88. # make sure the new one started properly
  89. self.assertEqual(pi.name, 'Test Process')
  90. self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
  91. # self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
  92. # 'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
  93. self.assertEqual(pi.dev_null_stdout, False)
  94. self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
  95. self.assertNotEqual(pi.process, None)
  96. self.assertTrue(type(pi.pid) is int)
  97. self.assertNotEqual(pi.pid, old_pid)
  98. class TestCacheCommands(unittest.TestCase):
  99. """
  100. Test methods of boss related to the socket cache and socket handling.
  101. """
  102. def setUp(self):
  103. """
  104. Prepare the boss for some tests.
  105. Also prepare some variables we need.
  106. """
  107. self.__boss = BoB()
  108. # Fake the cache here so we can pretend it is us and hijack the
  109. # calls to its methods.
  110. self.__boss._socket_cache = self
  111. self.__boss._socket_path = '/socket/path'
  112. self.__raise_exception = None
  113. self.__socket_args = {
  114. "port": 53,
  115. "address": "::",
  116. "protocol": "UDP",
  117. "share_mode": "ANY",
  118. "share_name": "app"
  119. }
  120. # What was and wasn't called.
  121. self.__drop_app_called = None
  122. self.__get_socket_called = None
  123. self.__send_fd_called = None
  124. self.__get_token_called = None
  125. self.__drop_socket_called = None
  126. bind10_src.libutil_io_python.send_fd = self.__send_fd
  127. def __send_fd(self, to, socket):
  128. """
  129. A function to hook the send_fd in the bind10_src.
  130. """
  131. self.__send_fd_called = (to, socket)
  132. class FalseSocket:
  133. """
  134. A socket where we can fake methods we need instead of having a real
  135. socket.
  136. """
  137. def __init__(self):
  138. self.send = b""
  139. def fileno(self):
  140. """
  141. The file number. Used for identifying the remote application.
  142. """
  143. return 42
  144. def sendall(self, data):
  145. """
  146. Adds data to the self.send.
  147. """
  148. self.send += data
  149. def drop_application(self, application):
  150. """
  151. Part of pretending to be the cache. Logs the parameter to
  152. self.__drop_app_called.
  153. In the case self.__raise_exception is set, the exception there
  154. is raised instead.
  155. """
  156. if self.__raise_exception is not None:
  157. raise self.__raise_exception
  158. self.__drop_app_called = application
  159. def test_consumer_dead(self):
  160. """
  161. Test that it calls the drop_application method of the cache.
  162. """
  163. self.__boss.socket_consumer_dead(self.FalseSocket())
  164. self.assertEqual(42, self.__drop_app_called)
  165. def test_consumer_dead_invalid(self):
  166. """
  167. Test that it doesn't crash in case the application is not known to
  168. the cache, the boss doesn't crash, as this actually can happen in
  169. practice.
  170. """
  171. self.__raise_exception = ValueError("This application is unknown")
  172. # This doesn't crash
  173. self.__boss.socket_consumer_dead(self.FalseSocket())
  174. def get_socket(self, token, application):
  175. """
  176. Part of pretending to be the cache. If there's anything in
  177. __raise_exception, it is raised. Otherwise, the call is logged
  178. into __get_socket_called and a number is returned.
  179. """
  180. if self.__raise_exception is not None:
  181. raise self.__raise_exception
  182. self.__get_socket_called = (token, application)
  183. return 13
  184. def test_request_handler(self):
  185. """
  186. Test that a request for socket is forwarded and the socket is sent
  187. back, if it returns a socket.
  188. """
  189. socket = self.FalseSocket()
  190. # An exception from the cache
  191. self.__raise_exception = ValueError("Test value error")
  192. self.__boss.socket_request_handler(b"token", socket)
  193. # It was called, but it threw, so it is not noted here
  194. self.assertIsNone(self.__get_socket_called)
  195. self.assertEqual(b"0\n", socket.send)
  196. # It should not have sent any socket.
  197. self.assertIsNone(self.__send_fd_called)
  198. # Now prepare a valid scenario
  199. self.__raise_exception = None
  200. socket.send = b""
  201. self.__boss.socket_request_handler(b"token", socket)
  202. self.assertEqual(b"1\n", socket.send)
  203. self.assertEqual((42, 13), self.__send_fd_called)
  204. self.assertEqual(("token", 42), self.__get_socket_called)
  205. def get_token(self, protocol, address, port, share_mode, share_name):
  206. """
  207. Part of pretending to be the cache. If there's anything in
  208. __raise_exception, it is raised. Otherwise, the parameters are
  209. logged into __get_token_called and a token is returned.
  210. """
  211. if self.__raise_exception is not None:
  212. raise self.__raise_exception
  213. self.__get_token_called = (protocol, address, port, share_mode,
  214. share_name)
  215. return "token"
  216. def test_get_socket_ok(self):
  217. """
  218. Test the successful scenario of getting a socket.
  219. """
  220. result = self.__boss._get_socket(self.__socket_args)
  221. [code, answer] = result['result']
  222. self.assertEqual(0, code)
  223. self.assertEqual({
  224. 'token': 'token',
  225. 'path': '/socket/path'
  226. }, answer)
  227. addr = self.__get_token_called[1]
  228. self.assertTrue(isinstance(addr, IPAddr))
  229. self.assertEqual("::", str(addr))
  230. self.assertEqual(("UDP", addr, 53, "ANY", "app"),
  231. self.__get_token_called)
  232. def test_get_socket_error(self):
  233. """
  234. Test that bad inputs are handled correctly, etc.
  235. """
  236. def check_code(code, args):
  237. """
  238. Pass the args there and check if it returns success or not.
  239. The rest is not tested, as it is already checked in the
  240. test_get_socket_ok.
  241. """
  242. [rcode, ranswer] = self.__boss._get_socket(args)['result']
  243. self.assertEqual(code, rcode)
  244. if code != 0:
  245. # This should be an error message. The exact formatting
  246. # is unknown, but we check it is string at least
  247. self.assertTrue(isinstance(ranswer, str))
  248. def mod_args(name, value):
  249. """
  250. Override a parameter in the args.
  251. """
  252. result = dict(self.__socket_args)
  253. result[name] = value
  254. return result
  255. # Port too large
  256. check_code(1, mod_args('port', 65536))
  257. # Not numeric address
  258. check_code(1, mod_args('address', 'example.org.'))
  259. # Some bad values of enum-like params
  260. check_code(1, mod_args('protocol', 'BAD PROTO'))
  261. check_code(1, mod_args('share_mode', 'BAD SHARE'))
  262. # Check missing parameters
  263. for param in self.__socket_args.keys():
  264. args = dict(self.__socket_args)
  265. del args[param]
  266. check_code(1, args)
  267. # These are OK values for the enum-like parameters
  268. # The ones from test_get_socket_ok are not tested here
  269. check_code(0, mod_args('protocol', 'TCP'))
  270. check_code(0, mod_args('share_mode', 'SAMEAPP'))
  271. check_code(0, mod_args('share_mode', 'NO'))
  272. # If an exception is raised from within the cache, it is converted
  273. # to an error, not propagated
  274. self.__raise_exception = Exception("Test exception")
  275. check_code(1, self.__socket_args)
  276. # The special "expected" exceptions
  277. self.__raise_exception = \
  278. isc.bind10.socket_cache.ShareError("Not shared")
  279. check_code(3, self.__socket_args)
  280. self.__raise_exception = \
  281. isc.bind10.socket_cache.SocketError("Not shared", 13)
  282. check_code(2, self.__socket_args)
  283. def drop_socket(self, token):
  284. """
  285. Part of pretending to be the cache. If there's anything in
  286. __raise_exception, it is raised. Otherwise, the parameter is stored
  287. in __drop_socket_called.
  288. """
  289. if self.__raise_exception is not None:
  290. raise self.__raise_exception
  291. self.__drop_socket_called = token
  292. def test_drop_socket(self):
  293. """
  294. Check the drop_socket command. It should directly call the method
  295. on the cache. Exceptions should be translated to error messages.
  296. """
  297. # This should be OK and just propagated to the call.
  298. self.assertEqual({"result": [0]},
  299. self.__boss.command_handler("drop_socket",
  300. {"token": "token"}))
  301. self.assertEqual("token", self.__drop_socket_called)
  302. self.__drop_socket_called = None
  303. # Missing parameter
  304. self.assertEqual({"result": [1, "Missing token parameter"]},
  305. self.__boss.command_handler("drop_socket", {}))
  306. self.assertIsNone(self.__drop_socket_called)
  307. # An exception is raised from within the cache
  308. self.__raise_exception = ValueError("Test error")
  309. self.assertEqual({"result": [1, "Test error"]},
  310. self.__boss.command_handler("drop_socket",
  311. {"token": "token"}))
  312. class TestBoB(unittest.TestCase):
  313. def test_init(self):
  314. bob = BoB()
  315. self.assertEqual(bob.verbose, False)
  316. self.assertEqual(bob.msgq_socket_file, None)
  317. self.assertEqual(bob.cc_session, None)
  318. self.assertEqual(bob.ccs, None)
  319. self.assertEqual(bob.components, {})
  320. self.assertEqual(bob.runnable, False)
  321. self.assertEqual(bob.uid, None)
  322. self.assertEqual(bob.username, None)
  323. self.assertIsNone(bob._socket_cache)
  324. def test_set_creator(self):
  325. """
  326. Test the call to set_creator. First time, the cache is created
  327. with the passed creator. The next time, it throws an exception.
  328. """
  329. bob = BoB()
  330. # The cache doesn't use it at start, so just create an empty class
  331. class Creator: pass
  332. creator = Creator()
  333. bob.set_creator(creator)
  334. self.assertTrue(isinstance(bob._socket_cache,
  335. isc.bind10.socket_cache.Cache))
  336. self.assertEqual(creator, bob._socket_cache._creator)
  337. self.assertRaises(ValueError, bob.set_creator, creator)
  338. def test_socket_srv(self):
  339. """Tests init_socket_srv() and remove_socket_srv() work as expected."""
  340. bob = BoB()
  341. self.assertIsNone(bob._srv_socket)
  342. self.assertIsNone(bob._tmpdir)
  343. self.assertIsNone(bob._socket_path)
  344. bob.init_socket_srv()
  345. self.assertIsNotNone(bob._srv_socket)
  346. self.assertNotEqual(-1, bob._srv_socket.fileno())
  347. self.assertEqual(os.path.join(bob._tmpdir, 'sockcreator'),
  348. bob._srv_socket.getsockname())
  349. self.assertIsNotNone(bob._tmpdir)
  350. self.assertTrue(os.path.isdir(bob._tmpdir))
  351. self.assertIsNotNone(bob._socket_path)
  352. self.assertTrue(os.path.exists(bob._socket_path))
  353. # Check that it's possible to connect to the socket file (this
  354. # only works if the socket file exists and the server listens on
  355. # it).
  356. s = socket.socket(socket.AF_UNIX)
  357. try:
  358. s.connect(bob._socket_path)
  359. can_connect = True
  360. s.close()
  361. except socket.error as e:
  362. can_connect = False
  363. self.assertTrue(can_connect)
  364. bob.remove_socket_srv()
  365. self.assertEqual(-1, bob._srv_socket.fileno())
  366. self.assertFalse(os.path.exists(bob._socket_path))
  367. self.assertFalse(os.path.isdir(bob._tmpdir))
  368. # These should not fail either:
  369. # second call
  370. bob.remove_socket_srv()
  371. bob._srv_socket = None
  372. bob.remove_socket_srv()
  373. def test_init_alternate_socket(self):
  374. bob = BoB("alt_socket_file")
  375. self.assertEqual(bob.verbose, False)
  376. self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
  377. self.assertEqual(bob.cc_session, None)
  378. self.assertEqual(bob.ccs, None)
  379. self.assertEqual(bob.components, {})
  380. self.assertEqual(bob.runnable, False)
  381. self.assertEqual(bob.uid, None)
  382. self.assertEqual(bob.username, None)
  383. def test_command_handler(self):
  384. class DummySession():
  385. def group_sendmsg(self, msg, group):
  386. (self.msg, self.group) = (msg, group)
  387. def group_recvmsg(self, nonblock, seq): pass
  388. class DummyModuleCCSession():
  389. module_spec = isc.config.module_spec.ModuleSpec({
  390. "module_name": "Boss",
  391. "statistics": [
  392. {
  393. "item_name": "boot_time",
  394. "item_type": "string",
  395. "item_optional": False,
  396. "item_default": "1970-01-01T00:00:00Z",
  397. "item_title": "Boot time",
  398. "item_description": "A date time when bind10 process starts initially",
  399. "item_format": "date-time"
  400. }
  401. ]
  402. })
  403. def get_module_spec(self):
  404. return self.module_spec
  405. bob = BoB()
  406. bob.verbose = True
  407. bob.cc_session = DummySession()
  408. bob.ccs = DummyModuleCCSession()
  409. # a bad command
  410. self.assertEqual(bob.command_handler(-1, None),
  411. isc.config.ccsession.create_answer(1, "bad command"))
  412. # "shutdown" command
  413. self.assertEqual(bob.command_handler("shutdown", None),
  414. isc.config.ccsession.create_answer(0))
  415. self.assertFalse(bob.runnable)
  416. # "getstats" command
  417. self.assertEqual(bob.command_handler("getstats", None),
  418. isc.config.ccsession.create_answer(0,
  419. { 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME) }))
  420. # "ping" command
  421. self.assertEqual(bob.command_handler("ping", None),
  422. isc.config.ccsession.create_answer(0, "pong"))
  423. # "show_processes" command
  424. self.assertEqual(bob.command_handler("show_processes", None),
  425. isc.config.ccsession.create_answer(0,
  426. bob.get_processes()))
  427. # an unknown command
  428. self.assertEqual(bob.command_handler("__UNKNOWN__", None),
  429. isc.config.ccsession.create_answer(1, "Unknown command"))
  430. # Fake the get_token of cache and test the command works
  431. bob._socket_path = '/socket/path'
  432. class cache:
  433. def get_token(self, protocol, addr, port, share_mode, share_name):
  434. return str(addr) + ':' + str(port)
  435. bob._socket_cache = cache()
  436. args = {
  437. "port": 53,
  438. "address": "0.0.0.0",
  439. "protocol": "UDP",
  440. "share_mode": "ANY",
  441. "share_name": "app"
  442. }
  443. # at all and this is the easiest way to check.
  444. self.assertEqual({'result': [0, {'token': '0.0.0.0:53',
  445. 'path': '/socket/path'}]},
  446. bob.command_handler("get_socket", args))
  447. # The drop_socket is not tested here, but in TestCacheCommands.
  448. # It needs the cache mocks to be in place and they are there.
  449. def test_stop_process(self):
  450. """
  451. Test checking the stop_process method sends the right message over
  452. the message bus.
  453. """
  454. class DummySession():
  455. def group_sendmsg(self, msg, group, instance="*"):
  456. (self.msg, self.group, self.instance) = (msg, group, instance)
  457. bob = BoB()
  458. bob.cc_session = DummySession()
  459. bob.stop_process('process', 'address', 42)
  460. self.assertEqual('address', bob.cc_session.group)
  461. self.assertEqual('address', bob.cc_session.instance)
  462. self.assertEqual({'command': ['shutdown', {'pid': 42}]},
  463. bob.cc_session.msg)
  464. # Mock class for testing BoB's usage of ProcessInfo
  465. class MockProcessInfo:
  466. def __init__(self, name, args, env={}, dev_null_stdout=False,
  467. dev_null_stderr=False):
  468. self.name = name
  469. self.args = args
  470. self.env = env
  471. self.dev_null_stdout = dev_null_stdout
  472. self.dev_null_stderr = dev_null_stderr
  473. self.process = None
  474. self.pid = None
  475. def spawn(self):
  476. # set some pid (only used for testing that it is not None anymore)
  477. self.pid = 42147
  478. # Class for testing the BoB without actually starting processes.
  479. # This is used for testing the start/stop components routines and
  480. # the BoB commands.
  481. #
  482. # Testing that external processes start is outside the scope
  483. # of the unit test, by overriding the process start methods we can check
  484. # that the right processes are started depending on the configuration
  485. # options.
  486. class MockBob(BoB):
  487. def __init__(self):
  488. BoB.__init__(self)
  489. # Set flags as to which of the overridden methods has been run.
  490. self.msgq = False
  491. self.cfgmgr = False
  492. self.ccsession = False
  493. self.auth = False
  494. self.resolver = False
  495. self.xfrout = False
  496. self.xfrin = False
  497. self.zonemgr = False
  498. self.stats = False
  499. self.stats_httpd = False
  500. self.cmdctl = False
  501. self.dhcp6 = False
  502. self.dhcp4 = False
  503. self.c_channel_env = {}
  504. self.components = { }
  505. self.creator = False
  506. self.get_process_exit_status_called = False
  507. class MockSockCreator(isc.bind10.component.Component):
  508. def __init__(self, process, boss, kind, address=None, params=None):
  509. isc.bind10.component.Component.__init__(self, process, boss,
  510. kind, 'SockCreator')
  511. self._start_func = boss.start_creator
  512. specials = isc.bind10.special_component.get_specials()
  513. specials['sockcreator'] = MockSockCreator
  514. self._component_configurator = \
  515. isc.bind10.component.Configurator(self, specials)
  516. def start_creator(self):
  517. self.creator = True
  518. procinfo = ProcessInfo('b10-sockcreator', ['/bin/false'])
  519. procinfo.pid = 1
  520. return procinfo
  521. def _read_bind10_config(self):
  522. # Configuration options are set directly
  523. pass
  524. def start_msgq(self):
  525. self.msgq = True
  526. procinfo = ProcessInfo('b10-msgq', ['/bin/false'])
  527. procinfo.pid = 2
  528. return procinfo
  529. def start_ccsession(self, c_channel_env):
  530. # this is not a process, don't have to do anything with procinfo
  531. self.ccsession = True
  532. def start_cfgmgr(self):
  533. self.cfgmgr = True
  534. procinfo = ProcessInfo('b10-cfgmgr', ['/bin/false'])
  535. procinfo.pid = 3
  536. return procinfo
  537. def start_auth(self):
  538. self.auth = True
  539. procinfo = ProcessInfo('b10-auth', ['/bin/false'])
  540. procinfo.pid = 5
  541. return procinfo
  542. def start_resolver(self):
  543. self.resolver = True
  544. procinfo = ProcessInfo('b10-resolver', ['/bin/false'])
  545. procinfo.pid = 6
  546. return procinfo
  547. def start_simple(self, name):
  548. procmap = { 'b10-zonemgr': self.start_zonemgr,
  549. 'b10-stats': self.start_stats,
  550. 'b10-stats-httpd': self.start_stats_httpd,
  551. 'b10-cmdctl': self.start_cmdctl,
  552. 'b10-dhcp6': self.start_dhcp6,
  553. 'b10-dhcp4': self.start_dhcp4,
  554. 'b10-xfrin': self.start_xfrin,
  555. 'b10-xfrout': self.start_xfrout }
  556. return procmap[name]()
  557. def start_xfrout(self):
  558. self.xfrout = True
  559. procinfo = ProcessInfo('b10-xfrout', ['/bin/false'])
  560. procinfo.pid = 7
  561. return procinfo
  562. def start_xfrin(self):
  563. self.xfrin = True
  564. procinfo = ProcessInfo('b10-xfrin', ['/bin/false'])
  565. procinfo.pid = 8
  566. return procinfo
  567. def start_zonemgr(self):
  568. self.zonemgr = True
  569. procinfo = ProcessInfo('b10-zonemgr', ['/bin/false'])
  570. procinfo.pid = 9
  571. return procinfo
  572. def start_stats(self):
  573. self.stats = True
  574. procinfo = ProcessInfo('b10-stats', ['/bin/false'])
  575. procinfo.pid = 10
  576. return procinfo
  577. def start_stats_httpd(self):
  578. self.stats_httpd = True
  579. procinfo = ProcessInfo('b10-stats-httpd', ['/bin/false'])
  580. procinfo.pid = 11
  581. return procinfo
  582. def start_cmdctl(self):
  583. self.cmdctl = True
  584. procinfo = ProcessInfo('b10-cmdctl', ['/bin/false'])
  585. procinfo.pid = 12
  586. return procinfo
  587. def start_dhcp6(self):
  588. self.dhcp6 = True
  589. procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
  590. procinfo.pid = 13
  591. return procinfo
  592. def start_dhcp4(self):
  593. self.dhcp4 = True
  594. procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
  595. procinfo.pid = 14
  596. return procinfo
  597. def stop_process(self, process, recipient, pid):
  598. procmap = { 'b10-auth': self.stop_auth,
  599. 'b10-resolver': self.stop_resolver,
  600. 'b10-xfrout': self.stop_xfrout,
  601. 'b10-xfrin': self.stop_xfrin,
  602. 'b10-zonemgr': self.stop_zonemgr,
  603. 'b10-stats': self.stop_stats,
  604. 'b10-stats-httpd': self.stop_stats_httpd,
  605. 'b10-cmdctl': self.stop_cmdctl }
  606. procmap[process]()
  607. # Some functions to pretend we stop processes, use by stop_process
  608. def stop_msgq(self):
  609. if self.msgq:
  610. del self.components[2]
  611. self.msgq = False
  612. def stop_cfgmgr(self):
  613. if self.cfgmgr:
  614. del self.components[3]
  615. self.cfgmgr = False
  616. def stop_auth(self):
  617. if self.auth:
  618. del self.components[5]
  619. self.auth = False
  620. def stop_resolver(self):
  621. if self.resolver:
  622. del self.components[6]
  623. self.resolver = False
  624. def stop_xfrout(self):
  625. if self.xfrout:
  626. del self.components[7]
  627. self.xfrout = False
  628. def stop_xfrin(self):
  629. if self.xfrin:
  630. del self.components[8]
  631. self.xfrin = False
  632. def stop_zonemgr(self):
  633. if self.zonemgr:
  634. del self.components[9]
  635. self.zonemgr = False
  636. def stop_stats(self):
  637. if self.stats:
  638. del self.components[10]
  639. self.stats = False
  640. def stop_stats_httpd(self):
  641. if self.stats_httpd:
  642. del self.components[11]
  643. self.stats_httpd = False
  644. def stop_cmdctl(self):
  645. if self.cmdctl:
  646. del self.components[12]
  647. self.cmdctl = False
  648. def _get_process_exit_status(self):
  649. if self.get_process_exit_status_called:
  650. return (0, 0)
  651. self.get_process_exit_status_called = True
  652. return (53, 0)
  653. def _get_process_exit_status_unknown_pid(self):
  654. if self.get_process_exit_status_called:
  655. return (0, 0)
  656. self.get_process_exit_status_called = True
  657. return (42, 0)
  658. def _get_process_exit_status_raises_oserror_echild(self):
  659. raise OSError(errno.ECHILD, 'Mock error')
  660. def _get_process_exit_status_raises_oserror_other(self):
  661. raise OSError(0, 'Mock error')
  662. def _get_process_exit_status_raises_other(self):
  663. raise Exception('Mock error')
  664. def _make_mock_process_info(self, name, args, c_channel_env,
  665. dev_null_stdout=False, dev_null_stderr=False):
  666. return MockProcessInfo(name, args, c_channel_env,
  667. dev_null_stdout, dev_null_stderr)
  668. class MockBobSimple(BoB):
  669. def __init__(self):
  670. BoB.__init__(self)
  671. # Set which process has been started
  672. self.started_process_name = None
  673. self.started_process_args = None
  674. self.started_process_env = None
  675. def _make_mock_process_info(self, name, args, c_channel_env,
  676. dev_null_stdout=False, dev_null_stderr=False):
  677. return MockProcessInfo(name, args, c_channel_env,
  678. dev_null_stdout, dev_null_stderr)
  679. def start_process(self, name, args, c_channel_env, port=None,
  680. address=None):
  681. self.started_process_name = name
  682. self.started_process_args = args
  683. self.started_process_env = c_channel_env
  684. return None
  685. class TestStartStopProcessesBob(unittest.TestCase):
  686. """
  687. Check that the start_all_components method starts the right combination
  688. of components and that the right components are started and stopped
  689. according to changes in configuration.
  690. """
  691. def check_environment_unchanged(self):
  692. # Check whether the environment has not been changed
  693. self.assertEqual(original_os_environ, os.environ)
  694. def check_started(self, bob, core, auth, resolver):
  695. """
  696. Check that the right sets of services are started. The ones that
  697. should be running are specified by the core, auth and resolver parameters
  698. (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
  699. and -zonemgr).
  700. """
  701. self.assertEqual(bob.msgq, core)
  702. self.assertEqual(bob.cfgmgr, core)
  703. self.assertEqual(bob.ccsession, core)
  704. self.assertEqual(bob.creator, core)
  705. self.assertEqual(bob.auth, auth)
  706. self.assertEqual(bob.resolver, resolver)
  707. self.assertEqual(bob.xfrout, auth)
  708. self.assertEqual(bob.xfrin, auth)
  709. self.assertEqual(bob.zonemgr, auth)
  710. self.assertEqual(bob.stats, core)
  711. self.assertEqual(bob.stats_httpd, core)
  712. self.assertEqual(bob.cmdctl, core)
  713. self.check_environment_unchanged()
  714. def check_preconditions(self, bob):
  715. self.check_started(bob, False, False, False)
  716. def check_started_none(self, bob):
  717. """
  718. Check that the situation is according to configuration where no servers
  719. should be started. Some components still need to be running.
  720. """
  721. self.check_started(bob, True, False, False)
  722. self.check_environment_unchanged()
  723. def check_started_both(self, bob):
  724. """
  725. Check the situation is according to configuration where both servers
  726. (auth and resolver) are enabled.
  727. """
  728. self.check_started(bob, True, True, True)
  729. self.check_environment_unchanged()
  730. def check_started_auth(self, bob):
  731. """
  732. Check the set of components needed to run auth only is started.
  733. """
  734. self.check_started(bob, True, True, False)
  735. self.check_environment_unchanged()
  736. def check_started_resolver(self, bob):
  737. """
  738. Check the set of components needed to run resolver only is started.
  739. """
  740. self.check_started(bob, True, False, True)
  741. self.check_environment_unchanged()
  742. def check_started_dhcp(self, bob, v4, v6):
  743. """
  744. Check if proper combinations of DHCPv4 and DHCpv6 can be started
  745. """
  746. self.assertEqual(v4, bob.dhcp4)
  747. self.assertEqual(v6, bob.dhcp6)
  748. self.check_environment_unchanged()
  749. def construct_config(self, start_auth, start_resolver):
  750. # The things that are common, not turned on an off
  751. config = {}
  752. config['b10-stats'] = { 'kind': 'dispensable', 'address': 'Stats' }
  753. config['b10-stats-httpd'] = { 'kind': 'dispensable',
  754. 'address': 'StatsHttpd' }
  755. config['b10-cmdctl'] = { 'kind': 'needed', 'special': 'cmdctl' }
  756. if start_auth:
  757. config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
  758. config['b10-xfrout'] = { 'kind': 'dispensable',
  759. 'address': 'Xfrout' }
  760. config['b10-xfrin'] = { 'kind': 'dispensable',
  761. 'address': 'Xfrin' }
  762. config['b10-zonemgr'] = { 'kind': 'dispensable',
  763. 'address': 'Zonemgr' }
  764. if start_resolver:
  765. config['b10-resolver'] = { 'kind': 'needed',
  766. 'special': 'resolver' }
  767. return {'components': config}
  768. def config_start_init(self, start_auth, start_resolver):
  769. """
  770. Test the configuration is loaded at the startup.
  771. """
  772. bob = MockBob()
  773. config = self.construct_config(start_auth, start_resolver)
  774. class CC:
  775. def get_full_config(self):
  776. return config
  777. # Provide the fake CC with data
  778. bob.ccs = CC()
  779. # And make sure it's not overwritten
  780. def start_ccsession():
  781. bob.ccsession = True
  782. bob.start_ccsession = lambda _: start_ccsession()
  783. # We need to return the original _read_bind10_config
  784. bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
  785. bob.start_all_components()
  786. self.check_started(bob, True, start_auth, start_resolver)
  787. self.check_environment_unchanged()
  788. def test_start_none(self):
  789. self.config_start_init(False, False)
  790. def test_start_resolver(self):
  791. self.config_start_init(False, True)
  792. def test_start_auth(self):
  793. self.config_start_init(True, False)
  794. def test_start_both(self):
  795. self.config_start_init(True, True)
  796. def test_config_start(self):
  797. """
  798. Test that the configuration starts and stops components according
  799. to configuration changes.
  800. """
  801. # Create BoB and ensure correct initialization
  802. bob = MockBob()
  803. self.check_preconditions(bob)
  804. bob.start_all_components()
  805. bob.runnable = True
  806. bob.config_handler(self.construct_config(False, False))
  807. self.check_started_none(bob)
  808. # Enable both at once
  809. bob.config_handler(self.construct_config(True, True))
  810. self.check_started_both(bob)
  811. # Not touched by empty change
  812. bob.config_handler({})
  813. self.check_started_both(bob)
  814. # Not touched by change to the same configuration
  815. bob.config_handler(self.construct_config(True, True))
  816. self.check_started_both(bob)
  817. # Turn them both off again
  818. bob.config_handler(self.construct_config(False, False))
  819. self.check_started_none(bob)
  820. # Not touched by empty change
  821. bob.config_handler({})
  822. self.check_started_none(bob)
  823. # Not touched by change to the same configuration
  824. bob.config_handler(self.construct_config(False, False))
  825. self.check_started_none(bob)
  826. # Start and stop auth separately
  827. bob.config_handler(self.construct_config(True, False))
  828. self.check_started_auth(bob)
  829. bob.config_handler(self.construct_config(False, False))
  830. self.check_started_none(bob)
  831. # Start and stop resolver separately
  832. bob.config_handler(self.construct_config(False, True))
  833. self.check_started_resolver(bob)
  834. bob.config_handler(self.construct_config(False, False))
  835. self.check_started_none(bob)
  836. # Alternate
  837. bob.config_handler(self.construct_config(True, False))
  838. self.check_started_auth(bob)
  839. bob.config_handler(self.construct_config(False, True))
  840. self.check_started_resolver(bob)
  841. bob.config_handler(self.construct_config(True, False))
  842. self.check_started_auth(bob)
  843. def test_config_start_once(self):
  844. """
  845. Tests that a component is started only once.
  846. """
  847. # Create BoB and ensure correct initialization
  848. bob = MockBob()
  849. self.check_preconditions(bob)
  850. bob.start_all_components()
  851. bob.runnable = True
  852. bob.config_handler(self.construct_config(True, True))
  853. self.check_started_both(bob)
  854. bob.start_auth = lambda: self.fail("Started auth again")
  855. bob.start_xfrout = lambda: self.fail("Started xfrout again")
  856. bob.start_xfrin = lambda: self.fail("Started xfrin again")
  857. bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
  858. bob.start_resolver = lambda: self.fail("Started resolver again")
  859. # Send again we want to start them. Should not do it, as they are.
  860. bob.config_handler(self.construct_config(True, True))
  861. def test_config_not_started_early(self):
  862. """
  863. Test that components are not started by the config handler before
  864. startup.
  865. """
  866. bob = MockBob()
  867. self.check_preconditions(bob)
  868. bob.start_auth = lambda: self.fail("Started auth again")
  869. bob.start_xfrout = lambda: self.fail("Started xfrout again")
  870. bob.start_xfrin = lambda: self.fail("Started xfrin again")
  871. bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
  872. bob.start_resolver = lambda: self.fail("Started resolver again")
  873. bob.config_handler({'start_auth': True, 'start_resolver': True})
  874. # Checks that DHCP (v4 and v6) components are started when expected
  875. def test_start_dhcp(self):
  876. # Create BoB and ensure correct initialization
  877. bob = MockBob()
  878. self.check_preconditions(bob)
  879. bob.start_all_components()
  880. bob.config_handler(self.construct_config(False, False))
  881. self.check_started_dhcp(bob, False, False)
  882. def test_start_dhcp_v6only(self):
  883. # Create BoB and ensure correct initialization
  884. bob = MockBob()
  885. self.check_preconditions(bob)
  886. # v6 only enabled
  887. bob.start_all_components()
  888. bob.runnable = True
  889. bob._BoB_started = True
  890. config = self.construct_config(False, False)
  891. config['components']['b10-dhcp6'] = { 'kind': 'needed',
  892. 'address': 'Dhcp6' }
  893. bob.config_handler(config)
  894. self.check_started_dhcp(bob, False, True)
  895. # uncomment when dhcpv4 becomes implemented
  896. # v4 only enabled
  897. #bob.cfg_start_dhcp6 = False
  898. #bob.cfg_start_dhcp4 = True
  899. #self.check_started_dhcp(bob, True, False)
  900. # both v4 and v6 enabled
  901. #bob.cfg_start_dhcp6 = True
  902. #bob.cfg_start_dhcp4 = True
  903. #self.check_started_dhcp(bob, True, True)
  904. class MockComponent:
  905. def __init__(self, name, pid, address=None):
  906. self.name = lambda: name
  907. self.pid = lambda: pid
  908. self.address = lambda: address
  909. self.restarted = False
  910. self.forceful = False
  911. self.running = True
  912. self.has_failed = False
  913. def get_restart_time(self):
  914. return 0 # arbitrary dummy value
  915. def restart(self, now):
  916. self.restarted = True
  917. return True
  918. def is_running(self):
  919. return self.running
  920. def failed(self, status):
  921. return self.has_failed
  922. def kill(self, forceful):
  923. self.forceful = forceful
  924. class TestBossCmd(unittest.TestCase):
  925. def test_ping(self):
  926. """
  927. Confirm simple ping command works.
  928. """
  929. bob = MockBob()
  930. answer = bob.command_handler("ping", None)
  931. self.assertEqual(answer, {'result': [0, 'pong']})
  932. def test_show_processes_empty(self):
  933. """
  934. Confirm getting a list of processes works.
  935. """
  936. bob = MockBob()
  937. answer = bob.command_handler("show_processes", None)
  938. self.assertEqual(answer, {'result': [0, []]})
  939. def test_show_processes(self):
  940. """
  941. Confirm getting a list of processes works.
  942. """
  943. bob = MockBob()
  944. bob.register_process(1, MockComponent('first', 1))
  945. bob.register_process(2, MockComponent('second', 2, 'Second'))
  946. answer = bob.command_handler("show_processes", None)
  947. processes = [[1, 'first', None],
  948. [2, 'second', 'Second']]
  949. self.assertEqual(answer, {'result': [0, processes]})
  950. class TestParseArgs(unittest.TestCase):
  951. """
  952. This tests parsing of arguments of the bind10 master process.
  953. """
  954. #TODO: Write tests for the original parsing, bad options, etc.
  955. def test_no_opts(self):
  956. """
  957. Test correct default values when no options are passed.
  958. """
  959. options = parse_args([], TestOptParser)
  960. self.assertEqual(None, options.data_path)
  961. self.assertEqual(None, options.config_file)
  962. self.assertEqual(None, options.cmdctl_port)
  963. def test_data_path(self):
  964. """
  965. Test it can parse the data path.
  966. """
  967. self.assertRaises(OptsError, parse_args, ['-p'], TestOptParser)
  968. self.assertRaises(OptsError, parse_args, ['--data-path'],
  969. TestOptParser)
  970. options = parse_args(['-p', '/data/path'], TestOptParser)
  971. self.assertEqual('/data/path', options.data_path)
  972. options = parse_args(['--data-path=/data/path'], TestOptParser)
  973. self.assertEqual('/data/path', options.data_path)
  974. def test_config_filename(self):
  975. """
  976. Test it can parse the config switch.
  977. """
  978. self.assertRaises(OptsError, parse_args, ['-c'], TestOptParser)
  979. self.assertRaises(OptsError, parse_args, ['--config-file'],
  980. TestOptParser)
  981. options = parse_args(['-c', 'config-file'], TestOptParser)
  982. self.assertEqual('config-file', options.config_file)
  983. options = parse_args(['--config-file=config-file'], TestOptParser)
  984. self.assertEqual('config-file', options.config_file)
  985. def test_clear_config(self):
  986. options = parse_args([], TestOptParser)
  987. self.assertEqual(False, options.clear_config)
  988. options = parse_args(['--clear-config'], TestOptParser)
  989. self.assertEqual(True, options.clear_config)
  990. def test_nokill(self):
  991. options = parse_args([], TestOptParser)
  992. self.assertEqual(False, options.nokill)
  993. options = parse_args(['--no-kill'], TestOptParser)
  994. self.assertEqual(True, options.nokill)
  995. options = parse_args([], TestOptParser)
  996. self.assertEqual(False, options.nokill)
  997. options = parse_args(['-i'], TestOptParser)
  998. self.assertEqual(True, options.nokill)
  999. def test_cmdctl_port(self):
  1000. """
  1001. Test it can parse the command control port.
  1002. """
  1003. self.assertRaises(OptsError, parse_args, ['--cmdctl-port=abc'],
  1004. TestOptParser)
  1005. self.assertRaises(OptsError, parse_args, ['--cmdctl-port=100000000'],
  1006. TestOptParser)
  1007. self.assertRaises(OptsError, parse_args, ['--cmdctl-port'],
  1008. TestOptParser)
  1009. options = parse_args(['--cmdctl-port=1234'], TestOptParser)
  1010. self.assertEqual(1234, options.cmdctl_port)
  1011. class TestPIDFile(unittest.TestCase):
  1012. def setUp(self):
  1013. self.pid_file = '@builddir@' + os.sep + 'bind10.pid'
  1014. if os.path.exists(self.pid_file):
  1015. os.unlink(self.pid_file)
  1016. def tearDown(self):
  1017. if os.path.exists(self.pid_file):
  1018. os.unlink(self.pid_file)
  1019. def check_pid_file(self):
  1020. # dump PID to the file, and confirm the content is correct
  1021. dump_pid(self.pid_file)
  1022. my_pid = os.getpid()
  1023. with open(self.pid_file, "r") as f:
  1024. self.assertEqual(my_pid, int(f.read()))
  1025. def test_dump_pid(self):
  1026. self.check_pid_file()
  1027. # make sure any existing content will be removed
  1028. with open(self.pid_file, "w") as f:
  1029. f.write('dummy data\n')
  1030. self.check_pid_file()
  1031. def test_unlink_pid_file_notexist(self):
  1032. dummy_data = 'dummy_data\n'
  1033. with open(self.pid_file, "w") as f:
  1034. f.write(dummy_data)
  1035. unlink_pid_file("no_such_pid_file")
  1036. # the file specified for unlink_pid_file doesn't exist,
  1037. # and the original content of the file should be intact.
  1038. with open(self.pid_file, "r") as f:
  1039. self.assertEqual(dummy_data, f.read())
  1040. def test_dump_pid_with_none(self):
  1041. # Check the behavior of dump_pid() and unlink_pid_file() with None.
  1042. # This should be no-op.
  1043. dump_pid(None)
  1044. self.assertFalse(os.path.exists(self.pid_file))
  1045. dummy_data = 'dummy_data\n'
  1046. with open(self.pid_file, "w") as f:
  1047. f.write(dummy_data)
  1048. unlink_pid_file(None)
  1049. with open(self.pid_file, "r") as f:
  1050. self.assertEqual(dummy_data, f.read())
  1051. def test_dump_pid_failure(self):
  1052. # the attempt to open file will fail, which should result in exception.
  1053. self.assertRaises(IOError, dump_pid,
  1054. 'nonexistent_dir' + os.sep + 'bind10.pid')
  1055. class TestBossComponents(unittest.TestCase):
  1056. """
  1057. Test the boss propagates component configuration properly to the
  1058. component configurator and acts sane.
  1059. """
  1060. def setUp(self):
  1061. self.__param = None
  1062. self.__called = False
  1063. self.__compconfig = {
  1064. 'comp': {
  1065. 'kind': 'needed',
  1066. 'process': 'cat'
  1067. }
  1068. }
  1069. self._tmp_time = None
  1070. self._tmp_sleep = None
  1071. self._tmp_module_cc_session = None
  1072. self._tmp_cc_session = None
  1073. def tearDown(self):
  1074. if self._tmp_time is not None:
  1075. time.time = self._tmp_time
  1076. if self._tmp_sleep is not None:
  1077. time.sleep = self._tmp_sleep
  1078. if self._tmp_module_cc_session is not None:
  1079. isc.config.ModuleCCSession = self._tmp_module_cc_session
  1080. if self._tmp_cc_session is not None:
  1081. isc.cc.Session = self._tmp_cc_session
  1082. def __unary_hook(self, param):
  1083. """
  1084. A hook function that stores the parameter for later examination.
  1085. """
  1086. self.__param = param
  1087. def __nullary_hook(self):
  1088. """
  1089. A hook function that notes down it was called.
  1090. """
  1091. self.__called = True
  1092. def __check_core(self, config):
  1093. """
  1094. A function checking that the config contains parts for the valid
  1095. core component configuration.
  1096. """
  1097. self.assertIsNotNone(config)
  1098. for component in ['sockcreator', 'msgq', 'cfgmgr']:
  1099. self.assertTrue(component in config)
  1100. self.assertEqual(component, config[component]['special'])
  1101. self.assertEqual('core', config[component]['kind'])
  1102. def __check_extended(self, config):
  1103. """
  1104. This checks that the config contains the core and one more component.
  1105. """
  1106. self.__check_core(config)
  1107. self.assertTrue('comp' in config)
  1108. self.assertEqual('cat', config['comp']['process'])
  1109. self.assertEqual('needed', config['comp']['kind'])
  1110. self.assertEqual(4, len(config))
  1111. def test_correct_run(self):
  1112. """
  1113. Test the situation when we run in usual scenario, nothing fails,
  1114. we just start, reconfigure and then stop peacefully.
  1115. """
  1116. bob = MockBob()
  1117. # Start it
  1118. orig = bob._component_configurator.startup
  1119. bob._component_configurator.startup = self.__unary_hook
  1120. bob.start_all_components()
  1121. bob._component_configurator.startup = orig
  1122. self.__check_core(self.__param)
  1123. self.assertEqual(3, len(self.__param))
  1124. # Reconfigure it
  1125. self.__param = None
  1126. orig = bob._component_configurator.reconfigure
  1127. bob._component_configurator.reconfigure = self.__unary_hook
  1128. # Otherwise it does not work
  1129. bob.runnable = True
  1130. bob.config_handler({'components': self.__compconfig})
  1131. self.__check_extended(self.__param)
  1132. currconfig = self.__param
  1133. # If we reconfigure it, but it does not contain the components part,
  1134. # nothing is called
  1135. bob.config_handler({})
  1136. self.assertEqual(self.__param, currconfig)
  1137. self.__param = None
  1138. bob._component_configurator.reconfigure = orig
  1139. # Check a configuration that messes up the core components is rejected.
  1140. compconf = dict(self.__compconfig)
  1141. compconf['msgq'] = { 'process': 'echo' }
  1142. result = bob.config_handler({'components': compconf})
  1143. # Check it rejected it
  1144. self.assertEqual(1, result['result'][0])
  1145. # We can't call shutdown, that one relies on the stuff in main
  1146. # We check somewhere else that the shutdown is actually called
  1147. # from there (the test_kills).
  1148. def __real_test_kill(self, nokill=False, ex_on_kill=None):
  1149. """
  1150. Helper function that does the actual kill functionality testing.
  1151. """
  1152. bob = MockBob()
  1153. bob.nokill = nokill
  1154. killed = []
  1155. class ImmortalComponent:
  1156. """
  1157. An immortal component. It does not stop when it is told so
  1158. (anyway it is not told so). It does not die if it is killed
  1159. the first time. It dies only when killed forcefully.
  1160. """
  1161. def __init__(self):
  1162. # number of kill() calls, preventing infinite loop.
  1163. self.__call_count = 0
  1164. def kill(self, forceful=False):
  1165. self.__call_count += 1
  1166. if self.__call_count > 2:
  1167. raise Exception('Too many calls to ImmortalComponent.kill')
  1168. killed.append(forceful)
  1169. if ex_on_kill is not None:
  1170. # If exception is given by the test, raise it here.
  1171. # In the case of ESRCH, the process should have gone
  1172. # somehow, so we clear the components.
  1173. if ex_on_kill.errno == errno.ESRCH:
  1174. bob.components = {}
  1175. raise ex_on_kill
  1176. if forceful:
  1177. bob.components = {}
  1178. def pid(self):
  1179. return 1
  1180. def name(self):
  1181. return "Immortal"
  1182. bob.components = {}
  1183. bob.register_process(1, ImmortalComponent())
  1184. # While at it, we check the configurator shutdown is actually called
  1185. orig = bob._component_configurator.shutdown
  1186. bob._component_configurator.shutdown = self.__nullary_hook
  1187. self.__called = False
  1188. bob.ccs = MockModuleCCSession()
  1189. self.assertFalse(bob.ccs.stopped)
  1190. bob.shutdown()
  1191. self.assertTrue(bob.ccs.stopped)
  1192. # Here, killed is an array where False is added if SIGTERM
  1193. # should be sent, or True if SIGKILL should be sent, in order in
  1194. # which they're sent.
  1195. if nokill:
  1196. self.assertEqual([], killed)
  1197. else:
  1198. if ex_on_kill is not None:
  1199. self.assertEqual([False], killed)
  1200. else:
  1201. self.assertEqual([False, True], killed)
  1202. self.assertTrue(self.__called)
  1203. bob._component_configurator.shutdown = orig
  1204. def test_kills(self):
  1205. """
  1206. Test that the boss kills components which don't want to stop.
  1207. """
  1208. self.__real_test_kill()
  1209. def test_kill_fail(self):
  1210. """Test cases where kill() results in an exception due to OS error.
  1211. The behavior should be different for EPERM, so we test two cases.
  1212. """
  1213. ex = OSError()
  1214. ex.errno, ex.strerror = errno.ESRCH, 'No such process'
  1215. self.__real_test_kill(ex_on_kill=ex)
  1216. ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
  1217. self.__real_test_kill(ex_on_kill=ex)
  1218. def test_nokill(self):
  1219. """
  1220. Test that the boss *doesn't* kill components which don't want to
  1221. stop, when asked not to (by passing the --no-kill option which
  1222. sets bob.nokill to True).
  1223. """
  1224. self.__real_test_kill(True)
  1225. def test_component_shutdown(self):
  1226. """
  1227. Test the component_shutdown sets all variables accordingly.
  1228. """
  1229. bob = MockBob()
  1230. self.assertRaises(Exception, bob.component_shutdown, 1)
  1231. self.assertEqual(1, bob.exitcode)
  1232. bob._BoB__started = True
  1233. bob.component_shutdown(2)
  1234. self.assertEqual(2, bob.exitcode)
  1235. self.assertFalse(bob.runnable)
  1236. def test_init_config(self):
  1237. """
  1238. Test initial configuration is loaded.
  1239. """
  1240. bob = MockBob()
  1241. # Start it
  1242. bob._component_configurator.reconfigure = self.__unary_hook
  1243. # We need to return the original read_bind10_config
  1244. bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
  1245. # And provide a session to read the data from
  1246. class CC:
  1247. pass
  1248. bob.ccs = CC()
  1249. bob.ccs.get_full_config = lambda: {'components': self.__compconfig}
  1250. bob.start_all_components()
  1251. self.__check_extended(self.__param)
  1252. def __setup_restart(self, bob, component):
  1253. '''Common procedure for restarting a component used below.'''
  1254. bob.components_to_restart = { component }
  1255. component.restarted = False
  1256. bob.restart_processes()
  1257. def test_restart_processes(self):
  1258. '''Check some behavior on restarting processes.'''
  1259. bob = MockBob()
  1260. bob.runnable = True
  1261. component = MockComponent('test', 53)
  1262. # A component to be restarted will actually be restarted iff it's
  1263. # in the configurator's configuration.
  1264. # We bruteforce the configurator internal below; ugly, but the easiest
  1265. # way for the test.
  1266. bob._component_configurator._components['test'] = (None, component)
  1267. self.__setup_restart(bob, component)
  1268. self.assertTrue(component.restarted)
  1269. self.assertNotIn(component, bob.components_to_restart)
  1270. # Remove the component from the configuration. It won't be restarted
  1271. # even if scheduled, nor will remain in the to-be-restarted list.
  1272. del bob._component_configurator._components['test']
  1273. self.__setup_restart(bob, component)
  1274. self.assertFalse(component.restarted)
  1275. self.assertNotIn(component, bob.components_to_restart)
  1276. def test_get_processes(self):
  1277. '''Test that procsses are returned correctly, sorted by pid.'''
  1278. bob = MockBob()
  1279. pids = list(range(0, 20))
  1280. random.shuffle(pids)
  1281. for i in range(0, 20):
  1282. pid = pids[i]
  1283. component = MockComponent('test' + str(pid), pid,
  1284. 'Test' + str(pid))
  1285. bob.components[pid] = component
  1286. process_list = bob.get_processes()
  1287. self.assertEqual(20, len(process_list))
  1288. last_pid = -1
  1289. for process in process_list:
  1290. pid = process[0]
  1291. self.assertLessEqual(last_pid, pid)
  1292. last_pid = pid
  1293. self.assertEqual([pid, 'test' + str(pid), 'Test' + str(pid)],
  1294. process)
  1295. def _test_reap_children_helper(self, runnable, is_running, failed):
  1296. '''Construct a BoB instance, set various data in it according to
  1297. passed args and check if the component was added to the list of
  1298. components to restart.'''
  1299. bob = MockBob()
  1300. bob.runnable = runnable
  1301. component = MockComponent('test', 53)
  1302. component.running = is_running
  1303. component.has_failed = failed
  1304. bob.components[53] = component
  1305. self.assertNotIn(component, bob.components_to_restart)
  1306. bob.reap_children()
  1307. if runnable and is_running and not failed:
  1308. self.assertIn(component, bob.components_to_restart)
  1309. else:
  1310. self.assertEqual([], bob.components_to_restart)
  1311. def test_reap_children(self):
  1312. '''Test that children are queued to be restarted when they ask for it.'''
  1313. # test various combinations of 3 booleans
  1314. # (BoB.runnable, component.is_running(), component.failed())
  1315. self._test_reap_children_helper(False, False, False)
  1316. self._test_reap_children_helper(False, False, True)
  1317. self._test_reap_children_helper(False, True, False)
  1318. self._test_reap_children_helper(False, True, True)
  1319. self._test_reap_children_helper(True, False, False)
  1320. self._test_reap_children_helper(True, False, True)
  1321. self._test_reap_children_helper(True, True, False)
  1322. self._test_reap_children_helper(True, True, True)
  1323. # setup for more tests below
  1324. bob = MockBob()
  1325. bob.runnable = True
  1326. component = MockComponent('test', 53)
  1327. bob.components[53] = component
  1328. # case where the returned pid is unknown to us. nothing should
  1329. # happpen then.
  1330. bob.get_process_exit_status_called = False
  1331. bob._get_process_exit_status = bob._get_process_exit_status_unknown_pid
  1332. bob.components_to_restart = []
  1333. # this should do nothing as the pid is unknown
  1334. bob.reap_children()
  1335. self.assertEqual([], bob.components_to_restart)
  1336. # case where bob._get_process_exit_status() raises OSError with
  1337. # errno.ECHILD
  1338. bob._get_process_exit_status = \
  1339. bob._get_process_exit_status_raises_oserror_echild
  1340. bob.components_to_restart = []
  1341. # this should catch and handle the OSError
  1342. bob.reap_children()
  1343. self.assertEqual([], bob.components_to_restart)
  1344. # case where bob._get_process_exit_status() raises OSError with
  1345. # errno other than ECHILD
  1346. bob._get_process_exit_status = \
  1347. bob._get_process_exit_status_raises_oserror_other
  1348. with self.assertRaises(OSError):
  1349. bob.reap_children()
  1350. # case where bob._get_process_exit_status() raises something
  1351. # other than OSError
  1352. bob._get_process_exit_status = \
  1353. bob._get_process_exit_status_raises_other
  1354. with self.assertRaises(Exception):
  1355. bob.reap_children()
  1356. def test_kill_started_components(self):
  1357. '''Test that started components are killed.'''
  1358. bob = MockBob()
  1359. component = MockComponent('test', 53, 'Test')
  1360. bob.components[53] = component
  1361. self.assertEqual([[53, 'test', 'Test']], bob.get_processes())
  1362. bob.kill_started_components()
  1363. self.assertEqual([], bob.get_processes())
  1364. self.assertTrue(component.forceful)
  1365. def _start_msgq_helper(self, bob, verbose):
  1366. bob.verbose = verbose
  1367. pi = bob.start_msgq()
  1368. self.assertEqual('b10-msgq', pi.name)
  1369. self.assertEqual(['b10-msgq'], pi.args)
  1370. self.assertTrue(pi.dev_null_stdout)
  1371. self.assertEqual(pi.dev_null_stderr, not verbose)
  1372. self.assertEqual({'FOO': 'an env string'}, pi.env)
  1373. # this is set by ProcessInfo.spawn()
  1374. self.assertEqual(42147, pi.pid)
  1375. def test_start_msgq(self):
  1376. '''Test that b10-msgq is started.'''
  1377. bob = MockBobSimple()
  1378. bob.c_channel_env = {'FOO': 'an env string'}
  1379. bob.run_under_unittests = True
  1380. # use the MockProcessInfo creator
  1381. bob._make_process_info = bob._make_mock_process_info
  1382. # non-verbose case
  1383. self._start_msgq_helper(bob, False)
  1384. # verbose case
  1385. self._start_msgq_helper(bob, True)
  1386. def test_start_msgq_timeout(self):
  1387. '''Test that b10-msgq startup attempts connections several times
  1388. and times out eventually.'''
  1389. bob = MockBobSimple()
  1390. bob.c_channel_env = {}
  1391. # keep the timeout small for the test to complete quickly
  1392. bob.msgq_timeout = 1
  1393. bob.run_under_unittests = False
  1394. # use the MockProcessInfo creator
  1395. bob._make_process_info = bob._make_mock_process_info
  1396. global attempts
  1397. global tsec
  1398. attempts = 0
  1399. tsec = 0
  1400. self._tmp_time = time.time
  1401. self._tmp_sleep = time.sleep
  1402. def _my_time():
  1403. global attempts
  1404. global tsec
  1405. attempts += 1
  1406. return tsec
  1407. def _my_sleep(nsec):
  1408. global tsec
  1409. tsec += nsec
  1410. time.time = _my_time
  1411. time.sleep = _my_sleep
  1412. global cc_sub
  1413. cc_sub = None
  1414. class DummySessionAlwaysFails():
  1415. def __init__(self, socket_file):
  1416. raise isc.cc.session.SessionError('Connection fails')
  1417. def group_subscribe(self, s):
  1418. global cc_sub
  1419. cc_sub = s
  1420. isc.cc.Session = DummySessionAlwaysFails
  1421. with self.assertRaises(bind10_src.CChannelConnectError):
  1422. # An exception will be thrown here when it eventually times
  1423. # out.
  1424. pi = bob.start_msgq()
  1425. # 1 second of attempts every 0.1 seconds should result in 10
  1426. # attempts. 1 attempt happens at the start. 2 more attempts seem
  1427. # to happen inside start_msgq().
  1428. self.assertEqual(attempts, 13)
  1429. # group_subscribe() should not have been called here.
  1430. self.assertIsNone(cc_sub)
  1431. global cc_socket_file
  1432. cc_socket_file = None
  1433. cc_sub = None
  1434. class DummySession():
  1435. def __init__(self, socket_file):
  1436. global cc_socket_file
  1437. cc_socket_file = socket_file
  1438. def group_subscribe(self, s):
  1439. global cc_sub
  1440. cc_sub = s
  1441. isc.cc.Session = DummySession
  1442. # reset values
  1443. attempts = 0
  1444. tsec = 0
  1445. pi = bob.start_msgq()
  1446. # just one attempt, but 2 calls to time.time()
  1447. self.assertEqual(attempts, 2)
  1448. self.assertEqual(cc_socket_file, bob.msgq_socket_file)
  1449. self.assertEqual(cc_sub, 'Boss')
  1450. # isc.cc.Session, time.time() and time.sleep() are restored
  1451. # during tearDown().
  1452. def test_start_cfgmgr(self):
  1453. '''Test that b10-cfgmgr is started.'''
  1454. class DummySession():
  1455. def __init__(self):
  1456. self._tries = 0
  1457. def group_recvmsg(self):
  1458. self._tries += 1
  1459. # return running on the 3rd try onwards
  1460. if self._tries >= 3:
  1461. return ({'running': 'ConfigManager'}, None)
  1462. else:
  1463. return ({}, None)
  1464. bob = MockBobSimple()
  1465. bob.c_channel_env = {}
  1466. bob.cc_session = DummySession()
  1467. bob.wait_time = 5
  1468. # use the MockProcessInfo creator
  1469. bob._make_process_info = bob._make_mock_process_info
  1470. global attempts
  1471. attempts = 0
  1472. self._tmp_sleep = time.sleep
  1473. def _my_sleep(nsec):
  1474. global attempts
  1475. attempts += 1
  1476. time.sleep = _my_sleep
  1477. # defaults
  1478. pi = bob.start_cfgmgr()
  1479. self.assertEqual('b10-cfgmgr', pi.name)
  1480. self.assertEqual(['b10-cfgmgr'], pi.args)
  1481. self.assertEqual({}, pi.env)
  1482. # this is set by ProcessInfo.spawn()
  1483. self.assertEqual(42147, pi.pid)
  1484. # check that 2 attempts were made. on the 3rd attempt,
  1485. # process_running() returns that ConfigManager is running.
  1486. self.assertEqual(attempts, 2)
  1487. # data_path is specified
  1488. bob.data_path = '/var/lib/test'
  1489. pi = bob.start_cfgmgr()
  1490. self.assertEqual('b10-cfgmgr', pi.name)
  1491. self.assertEqual(['b10-cfgmgr',
  1492. '--data-path=/var/lib/test'],
  1493. pi.args)
  1494. self.assertEqual({}, pi.env)
  1495. # this is set by ProcessInfo.spawn()
  1496. self.assertEqual(42147, pi.pid)
  1497. # config_filename is specified
  1498. bob.config_filename = 'foo.cfg'
  1499. pi = bob.start_cfgmgr()
  1500. self.assertEqual('b10-cfgmgr', pi.name)
  1501. self.assertEqual(['b10-cfgmgr',
  1502. '--data-path=/var/lib/test',
  1503. '--config-filename=foo.cfg'],
  1504. pi.args)
  1505. self.assertEqual({}, pi.env)
  1506. # this is set by ProcessInfo.spawn()
  1507. self.assertEqual(42147, pi.pid)
  1508. # clear_config is specified
  1509. bob.clear_config = True
  1510. pi = bob.start_cfgmgr()
  1511. self.assertEqual('b10-cfgmgr', pi.name)
  1512. self.assertEqual(['b10-cfgmgr',
  1513. '--data-path=/var/lib/test',
  1514. '--config-filename=foo.cfg',
  1515. '--clear-config'],
  1516. pi.args)
  1517. self.assertEqual({}, pi.env)
  1518. # this is set by ProcessInfo.spawn()
  1519. self.assertEqual(42147, pi.pid)
  1520. def test_start_cfgmgr_timeout(self):
  1521. '''Test that b10-cfgmgr startup attempts connections several times
  1522. and times out eventually.'''
  1523. class DummySession():
  1524. def group_recvmsg(self):
  1525. return (None, None)
  1526. bob = MockBobSimple()
  1527. bob.c_channel_env = {}
  1528. bob.cc_session = DummySession()
  1529. # keep the wait time small for the test to complete quickly
  1530. bob.wait_time = 2
  1531. # use the MockProcessInfo creator
  1532. bob._make_process_info = bob._make_mock_process_info
  1533. global attempts
  1534. attempts = 0
  1535. self._tmp_sleep = time.sleep
  1536. def _my_sleep(nsec):
  1537. global attempts
  1538. attempts += 1
  1539. time.sleep = _my_sleep
  1540. thrown = False
  1541. # An exception will be thrown here when it eventually times out.
  1542. try:
  1543. pi = bob.start_cfgmgr()
  1544. except bind10_src.ProcessStartError as e:
  1545. thrown = True
  1546. # We just check that an exception was thrown, and that several
  1547. # attempts were made to connect.
  1548. self.assertTrue(thrown)
  1549. # 2 seconds of attempts every 1 second should result in 2 attempts
  1550. self.assertEqual(attempts, 2)
  1551. # time.sleep() is restored during tearDown().
  1552. def test_start_ccsession(self):
  1553. '''Test that CC session is started.'''
  1554. class DummySession():
  1555. def __init__(self, specfile, config_handler, command_handler,
  1556. socket_file):
  1557. self.specfile = specfile
  1558. self.config_handler = config_handler
  1559. self.command_handler = command_handler
  1560. self.socket_file = socket_file
  1561. self.started = False
  1562. def start(self):
  1563. self.started = True
  1564. bob = MockBobSimple()
  1565. self._tmp_module_cc_session = isc.config.ModuleCCSession
  1566. isc.config.ModuleCCSession = DummySession
  1567. bob.start_ccsession({})
  1568. self.assertEqual(bind10_src.SPECFILE_LOCATION, bob.ccs.specfile)
  1569. self.assertEqual(bob.config_handler, bob.ccs.config_handler)
  1570. self.assertEqual(bob.command_handler, bob.ccs.command_handler)
  1571. self.assertEqual(bob.msgq_socket_file, bob.ccs.socket_file)
  1572. self.assertTrue(bob.ccs.started)
  1573. # isc.config.ModuleCCSession is restored during tearDown().
  1574. def test_start_process(self):
  1575. '''Test that processes can be started.'''
  1576. bob = MockBob()
  1577. # use the MockProcessInfo creator
  1578. bob._make_process_info = bob._make_mock_process_info
  1579. pi = bob.start_process('Test Process', ['/bin/true'], {})
  1580. self.assertEqual('Test Process', pi.name)
  1581. self.assertEqual(['/bin/true'], pi.args)
  1582. self.assertEqual({}, pi.env)
  1583. # this is set by ProcessInfo.spawn()
  1584. self.assertEqual(42147, pi.pid)
  1585. def test_register_process(self):
  1586. '''Test that processes can be registered with BoB.'''
  1587. bob = MockBob()
  1588. component = MockComponent('test', 53, 'Test')
  1589. self.assertFalse(53 in bob.components)
  1590. bob.register_process(53, component)
  1591. self.assertTrue(53 in bob.components)
  1592. self.assertEqual(bob.components[53].name(), 'test')
  1593. self.assertEqual(bob.components[53].pid(), 53)
  1594. self.assertEqual(bob.components[53].address(), 'Test')
  1595. def _start_simple_helper(self, bob, verbose):
  1596. bob.verbose = verbose
  1597. args = ['/bin/true']
  1598. if verbose:
  1599. args.append('-v')
  1600. bob.start_simple('/bin/true')
  1601. self.assertEqual('/bin/true', bob.started_process_name)
  1602. self.assertEqual(args, bob.started_process_args)
  1603. self.assertEqual({}, bob.started_process_env)
  1604. def test_start_simple(self):
  1605. '''Test simple process startup.'''
  1606. bob = MockBobSimple()
  1607. bob.c_channel_env = {}
  1608. # non-verbose case
  1609. self._start_simple_helper(bob, False)
  1610. # verbose case
  1611. self._start_simple_helper(bob, True)
  1612. def _start_auth_helper(self, bob, verbose):
  1613. bob.verbose = verbose
  1614. args = ['b10-auth']
  1615. if verbose:
  1616. args.append('-v')
  1617. bob.start_auth()
  1618. self.assertEqual('b10-auth', bob.started_process_name)
  1619. self.assertEqual(args, bob.started_process_args)
  1620. self.assertEqual({'FOO': 'an env string'}, bob.started_process_env)
  1621. def test_start_auth(self):
  1622. '''Test that b10-auth is started.'''
  1623. bob = MockBobSimple()
  1624. bob.c_channel_env = {'FOO': 'an env string'}
  1625. # non-verbose case
  1626. self._start_auth_helper(bob, False)
  1627. # verbose case
  1628. self._start_auth_helper(bob, True)
  1629. def _start_resolver_helper(self, bob, verbose):
  1630. bob.verbose = verbose
  1631. args = ['b10-resolver']
  1632. if verbose:
  1633. args.append('-v')
  1634. bob.start_resolver()
  1635. self.assertEqual('b10-resolver', bob.started_process_name)
  1636. self.assertEqual(args, bob.started_process_args)
  1637. self.assertEqual({'BAR': 'an env string'}, bob.started_process_env)
  1638. def test_start_resolver(self):
  1639. '''Test that b10-resolver is started.'''
  1640. bob = MockBobSimple()
  1641. bob.c_channel_env = {'BAR': 'an env string'}
  1642. # non-verbose case
  1643. self._start_resolver_helper(bob, False)
  1644. # verbose case
  1645. self._start_resolver_helper(bob, True)
  1646. def _start_cmdctl_helper(self, bob, verbose, port = None):
  1647. bob.verbose = verbose
  1648. args = ['b10-cmdctl']
  1649. if port is not None:
  1650. bob.cmdctl_port = port
  1651. args.append('--port=9353')
  1652. if verbose:
  1653. args.append('-v')
  1654. bob.start_cmdctl()
  1655. self.assertEqual('b10-cmdctl', bob.started_process_name)
  1656. self.assertEqual(args, bob.started_process_args)
  1657. self.assertEqual({'BAZ': 'an env string'}, bob.started_process_env)
  1658. def test_start_cmdctl(self):
  1659. '''Test that b10-cmdctl is started.'''
  1660. bob = MockBobSimple()
  1661. bob.c_channel_env = {'BAZ': 'an env string'}
  1662. # non-verbose case
  1663. self._start_cmdctl_helper(bob, False)
  1664. # verbose case
  1665. self._start_cmdctl_helper(bob, True)
  1666. # with port, non-verbose case
  1667. self._start_cmdctl_helper(bob, False, 9353)
  1668. # with port, verbose case
  1669. self._start_cmdctl_helper(bob, True, 9353)
  1670. def test_socket_data(self):
  1671. '''Test that BoB._socket_data works as expected.'''
  1672. class MockSock:
  1673. def __init__(self, fd, throw):
  1674. self.fd = fd
  1675. self.throw = throw
  1676. self.buf = b'Hello World.\nYou are so nice today.\nXX'
  1677. self.i = 0
  1678. def recv(self, bufsize, flags = 0):
  1679. if bufsize != 1:
  1680. raise Exception('bufsize != 1')
  1681. if flags != socket.MSG_DONTWAIT:
  1682. raise Exception('flags != socket.MSG_DONTWAIT')
  1683. # after 15 recv()s, throw a socket.error with EAGAIN to
  1684. # get _socket_data() to save back what's been read.
  1685. if self.throw and self.i > 15:
  1686. raise socket.error(errno.EAGAIN, 'Try again')
  1687. if self.i >= len(self.buf):
  1688. return b'';
  1689. t = self.i
  1690. self.i += 1
  1691. return self.buf[t:t+1]
  1692. def close(self):
  1693. return
  1694. class MockBobSocketData(BoB):
  1695. def __init__(self, throw):
  1696. self._unix_sockets = {42: (MockSock(42, throw), b'')}
  1697. self.requests = []
  1698. self.dead = []
  1699. def socket_request_handler(self, previous, sock):
  1700. self.requests.append({sock.fd: previous})
  1701. def socket_consumer_dead(self, sock):
  1702. self.dead.append(sock.fd)
  1703. # Case where we get data every time we call recv()
  1704. bob = MockBobSocketData(False)
  1705. bob._socket_data(42)
  1706. self.assertEqual(bob.requests,
  1707. [{42: b'Hello World.'},
  1708. {42: b'You are so nice today.'}])
  1709. self.assertEqual(bob.dead, [42])
  1710. self.assertEqual({}, bob._unix_sockets)
  1711. # Case where socket.recv() raises EAGAIN. In this case, the
  1712. # routine is supposed to save what it has back to
  1713. # BoB._unix_sockets.
  1714. bob = MockBobSocketData(True)
  1715. bob._socket_data(42)
  1716. self.assertEqual(bob.requests, [{42: b'Hello World.'}])
  1717. self.assertFalse(bob.dead)
  1718. self.assertEqual(len(bob._unix_sockets), 1)
  1719. self.assertEqual(bob._unix_sockets[42][1], b'You')
  1720. def test_startup(self):
  1721. '''Test that BoB.startup() handles failures properly.'''
  1722. class MockBobStartup(BoB):
  1723. def __init__(self, throw):
  1724. self.throw = throw
  1725. self.started = False
  1726. self.killed = False
  1727. self.msgq_socket_file = None
  1728. self.curproc = 'myproc'
  1729. self.runnable = False
  1730. def start_all_components(self):
  1731. self.started = True
  1732. if self.throw:
  1733. raise Exception('Assume starting components has failed.')
  1734. def kill_started_components(self):
  1735. self.killed = True
  1736. class DummySession():
  1737. def __init__(self, socket_file):
  1738. raise isc.cc.session.SessionError('This is the expected case.')
  1739. class DummySessionSocketExists():
  1740. def __init__(self, socket_file):
  1741. # simulate that connect passes
  1742. return
  1743. isc.cc.Session = DummySession
  1744. # All is well case, where all components are started
  1745. # successfully. We check that the actual call to
  1746. # start_all_components() is made, and BoB.runnable is true.
  1747. bob = MockBobStartup(False)
  1748. r = bob.startup()
  1749. self.assertIsNone(r)
  1750. self.assertTrue(bob.started)
  1751. self.assertFalse(bob.killed)
  1752. self.assertTrue(bob.runnable)
  1753. self.assertEqual({}, bob.c_channel_env)
  1754. # Case where starting components fails. We check that
  1755. # kill_started_components() is called right after, and
  1756. # BoB.runnable is not modified.
  1757. bob = MockBobStartup(True)
  1758. r = bob.startup()
  1759. # r contains an error message
  1760. self.assertEqual(r, 'Unable to start myproc: Assume starting components has failed.')
  1761. self.assertTrue(bob.started)
  1762. self.assertTrue(bob.killed)
  1763. self.assertFalse(bob.runnable)
  1764. self.assertEqual({}, bob.c_channel_env)
  1765. # Check if msgq_socket_file is carried over
  1766. bob = MockBobStartup(False)
  1767. bob.msgq_socket_file = 'foo'
  1768. r = bob.startup()
  1769. self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env)
  1770. # Check the case when socket file already exists
  1771. isc.cc.Session = DummySessionSocketExists
  1772. bob = MockBobStartup(False)
  1773. r = bob.startup()
  1774. self.assertIn('already running', r)
  1775. # isc.cc.Session is restored during tearDown().
  1776. class SocketSrvTest(unittest.TestCase):
  1777. """
  1778. This tests some methods of boss related to the unix domain sockets used
  1779. to transfer other sockets to applications.
  1780. """
  1781. def setUp(self):
  1782. """
  1783. Create the boss to test, testdata and backup some functions.
  1784. """
  1785. self.__boss = BoB()
  1786. self.__select_backup = bind10_src.select.select
  1787. self.__select_called = None
  1788. self.__socket_data_called = None
  1789. self.__consumer_dead_called = None
  1790. self.__socket_request_handler_called = None
  1791. def tearDown(self):
  1792. """
  1793. Restore functions.
  1794. """
  1795. bind10_src.select.select = self.__select_backup
  1796. class __FalseSocket:
  1797. """
  1798. A mock socket for the select and accept and stuff like that.
  1799. """
  1800. def __init__(self, owner, fileno=42):
  1801. self.__owner = owner
  1802. self.__fileno = fileno
  1803. self.data = None
  1804. self.closed = False
  1805. def fileno(self):
  1806. return self.__fileno
  1807. def accept(self):
  1808. return (self.__class__(self.__owner, 13), "/path/to/socket")
  1809. def recv(self, bufsize, flags=0):
  1810. self.__owner.assertEqual(1, bufsize)
  1811. self.__owner.assertEqual(socket.MSG_DONTWAIT, flags)
  1812. if isinstance(self.data, socket.error):
  1813. raise self.data
  1814. elif self.data is not None:
  1815. if len(self.data):
  1816. result = self.data[0:1]
  1817. self.data = self.data[1:]
  1818. return result
  1819. else:
  1820. raise socket.error(errno.EAGAIN, "Would block")
  1821. else:
  1822. return b''
  1823. def close(self):
  1824. self.closed = True
  1825. class __CCS:
  1826. """
  1827. A mock CCS, just to provide the socket file number.
  1828. """
  1829. class __Socket:
  1830. def fileno(self):
  1831. return 1
  1832. def get_socket(self):
  1833. return self.__Socket()
  1834. def __select_accept(self, r, w, x, t):
  1835. self.__select_called = (r, w, x, t)
  1836. return ([42], [], [])
  1837. def __select_data(self, r, w, x, t):
  1838. self.__select_called = (r, w, x, t)
  1839. return ([13], [], [])
  1840. def __accept(self):
  1841. """
  1842. Hijact the accept method of the boss.
  1843. Notes down it was called and stops the boss.
  1844. """
  1845. self.__accept_called = True
  1846. self.__boss.runnable = False
  1847. def test_srv_accept_called(self):
  1848. """
  1849. Test that the _srv_accept method of boss is called when the listening
  1850. socket is readable.
  1851. """
  1852. self.__boss.runnable = True
  1853. self.__boss._srv_socket = self.__FalseSocket(self)
  1854. self.__boss._srv_accept = self.__accept
  1855. self.__boss.ccs = self.__CCS()
  1856. bind10_src.select.select = self.__select_accept
  1857. self.__boss.run(2)
  1858. # It called the accept
  1859. self.assertTrue(self.__accept_called)
  1860. # And the select had the right parameters
  1861. self.assertEqual(([2, 1, 42], [], [], None), self.__select_called)
  1862. def test_srv_accept(self):
  1863. """
  1864. Test how the _srv_accept method works.
  1865. """
  1866. self.__boss._srv_socket = self.__FalseSocket(self)
  1867. self.__boss._srv_accept()
  1868. # After we accepted, a new socket is added there
  1869. socket = self.__boss._unix_sockets[13][0]
  1870. # The socket is properly stored there
  1871. self.assertTrue(isinstance(socket, self.__FalseSocket))
  1872. # And the buffer (yet empty) is there
  1873. self.assertEqual({13: (socket, b'')}, self.__boss._unix_sockets)
  1874. def __socket_data(self, socket):
  1875. self.__boss.runnable = False
  1876. self.__socket_data_called = socket
  1877. def test_socket_data(self):
  1878. """
  1879. Test that a socket that wants attention gets it.
  1880. """
  1881. self.__boss._srv_socket = self.__FalseSocket(self)
  1882. self.__boss._socket_data = self.__socket_data
  1883. self.__boss.ccs = self.__CCS()
  1884. self.__boss._unix_sockets = {13: (self.__FalseSocket(self, 13), b'')}
  1885. self.__boss.runnable = True
  1886. bind10_src.select.select = self.__select_data
  1887. self.__boss.run(2)
  1888. self.assertEqual(13, self.__socket_data_called)
  1889. self.assertEqual(([2, 1, 42, 13], [], [], None), self.__select_called)
  1890. def __prepare_data(self, data):
  1891. socket = self.__FalseSocket(self, 13)
  1892. self.__boss._unix_sockets = {13: (socket, b'')}
  1893. socket.data = data
  1894. self.__boss.socket_consumer_dead = self.__consumer_dead
  1895. self.__boss.socket_request_handler = self.__socket_request_handler
  1896. return socket
  1897. def __consumer_dead(self, socket):
  1898. self.__consumer_dead_called = socket
  1899. def __socket_request_handler(self, token, socket):
  1900. self.__socket_request_handler_called = (token, socket)
  1901. def test_socket_closed(self):
  1902. """
  1903. Test that a socket is removed and the socket_consumer_dead is called
  1904. when it is closed.
  1905. """
  1906. socket = self.__prepare_data(None)
  1907. self.__boss._socket_data(13)
  1908. self.assertEqual(socket, self.__consumer_dead_called)
  1909. self.assertEqual({}, self.__boss._unix_sockets)
  1910. self.assertTrue(socket.closed)
  1911. def test_socket_short(self):
  1912. """
  1913. Test that if there's not enough data to get the whole socket, it is
  1914. kept there, but nothing is called.
  1915. """
  1916. socket = self.__prepare_data(b'tok')
  1917. self.__boss._socket_data(13)
  1918. self.assertEqual({13: (socket, b'tok')}, self.__boss._unix_sockets)
  1919. self.assertFalse(socket.closed)
  1920. self.assertIsNone(self.__consumer_dead_called)
  1921. self.assertIsNone(self.__socket_request_handler_called)
  1922. def test_socket_continue(self):
  1923. """
  1924. Test that we call the token handling function when the whole token
  1925. comes. This test pretends to continue reading where the previous one
  1926. stopped.
  1927. """
  1928. socket = self.__prepare_data(b"en\nanothe")
  1929. # The data to finish
  1930. self.__boss._unix_sockets[13] = (socket, b'tok')
  1931. self.__boss._socket_data(13)
  1932. self.assertEqual({13: (socket, b'anothe')}, self.__boss._unix_sockets)
  1933. self.assertFalse(socket.closed)
  1934. self.assertIsNone(self.__consumer_dead_called)
  1935. self.assertEqual((b'token', socket),
  1936. self.__socket_request_handler_called)
  1937. def test_broken_socket(self):
  1938. """
  1939. If the socket raises an exception during the read other than EAGAIN,
  1940. it is broken and we remove it.
  1941. """
  1942. sock = self.__prepare_data(socket.error(errno.ENOMEM,
  1943. "There's more memory available, but not for you"))
  1944. self.__boss._socket_data(13)
  1945. self.assertEqual(sock, self.__consumer_dead_called)
  1946. self.assertEqual({}, self.__boss._unix_sockets)
  1947. self.assertTrue(sock.closed)
  1948. class TestFunctions(unittest.TestCase):
  1949. def setUp(self):
  1950. self.lockfile_testpath = \
  1951. "@abs_top_builddir@/src/bin/bind10/tests/lockfile_test"
  1952. self.assertFalse(os.path.exists(self.lockfile_testpath))
  1953. os.mkdir(self.lockfile_testpath)
  1954. self.assertTrue(os.path.isdir(self.lockfile_testpath))
  1955. def tearDown(self):
  1956. os.rmdir(self.lockfile_testpath)
  1957. self.assertFalse(os.path.isdir(self.lockfile_testpath))
  1958. os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
  1959. def test_remove_lock_files(self):
  1960. os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
  1961. # create lockfiles for the testcase
  1962. lockfiles = ["logger_lockfile"]
  1963. for f in lockfiles:
  1964. fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
  1965. self.assertFalse(os.path.exists(fname))
  1966. open(fname, "w").close()
  1967. self.assertTrue(os.path.isfile(fname))
  1968. # first call should clear up all the lockfiles
  1969. bind10_src.remove_lock_files()
  1970. # check if the lockfiles exist
  1971. for f in lockfiles:
  1972. fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
  1973. self.assertFalse(os.path.isfile(fname))
  1974. # second call should not assert anyway
  1975. bind10_src.remove_lock_files()
  1976. def test_get_signame(self):
  1977. # just test with some samples
  1978. signame = bind10_src.get_signame(signal.SIGTERM)
  1979. self.assertEqual('SIGTERM', signame)
  1980. signame = bind10_src.get_signame(signal.SIGKILL)
  1981. self.assertEqual('SIGKILL', signame)
  1982. # 59426 is hopefully an unused signal on most platforms
  1983. signame = bind10_src.get_signame(59426)
  1984. self.assertEqual('Unknown signal 59426', signame)
  1985. def test_fatal_signal(self):
  1986. self.assertIsNone(bind10_src.boss_of_bind)
  1987. bind10_src.boss_of_bind = BoB()
  1988. bind10_src.boss_of_bind.runnable = True
  1989. bind10_src.fatal_signal(signal.SIGTERM, None)
  1990. # Now, runnable must be False
  1991. self.assertFalse(bind10_src.boss_of_bind.runnable)
  1992. bind10_src.boss_of_bind = None
  1993. if __name__ == '__main__':
  1994. # store os.environ for test_unchanged_environment
  1995. original_os_environ = copy.deepcopy(os.environ)
  1996. isc.log.resetUnitTestRootLogger()
  1997. unittest.main()