b10-stats-httpd_test.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. import unittest
  16. import os
  17. import http.server
  18. import string
  19. import fake_select
  20. import imp
  21. import sys
  22. import fake_socket
  23. import isc.cc
  24. import stats_httpd
  25. stats_httpd.socket = fake_socket
  26. stats_httpd.select = fake_select
  27. DUMMY_DATA = {
  28. "auth.queries.tcp": 10000,
  29. "auth.queries.udp": 12000,
  30. "bind10.boot_time": "2011-03-04T11:59:05Z",
  31. "report_time": "2011-03-04T11:59:19Z",
  32. "stats.boot_time": "2011-03-04T11:59:06Z",
  33. "stats.last_update_time": "2011-03-04T11:59:07Z",
  34. "stats.lname": "4d70d40a_c@host",
  35. "stats.start_time": "2011-03-04T11:59:06Z",
  36. "stats.timestamp": 1299239959.560846
  37. }
  38. def push_answer(stats_httpd):
  39. stats_httpd.cc_session.group_sendmsg(
  40. { 'result':
  41. [ 0, DUMMY_DATA ] }, "Stats")
  42. def pull_query(stats_httpd):
  43. (msg, env) = stats_httpd.cc_session.group_recvmsg()
  44. if 'result' in msg:
  45. (ret, arg) = isc.config.ccsession.parse_answer(msg)
  46. else:
  47. (ret, arg) = isc.config.ccsession.parse_command(msg)
  48. return (ret, arg, env)
  49. class TestHttpHandler(unittest.TestCase):
  50. """Tests for HttpHandler class"""
  51. def setUp(self):
  52. self.stats_httpd = stats_httpd.StatsHttpd()
  53. self.assertTrue(type(self.stats_httpd.httpd) is list)
  54. self.httpd = self.stats_httpd.httpd
  55. def test_do_GET(self):
  56. for ht in self.httpd:
  57. self._test_do_GET(ht._handler)
  58. def _test_do_GET(self, handler):
  59. # URL is '/bind10/statistics/xml'
  60. handler.path = stats_httpd.XML_URL_PATH
  61. push_answer(self.stats_httpd)
  62. handler.do_GET()
  63. (ret, arg, env) = pull_query(self.stats_httpd)
  64. self.assertEqual(ret, "show")
  65. self.assertIsNone(arg)
  66. self.assertTrue('group' in env)
  67. self.assertEqual(env['group'], 'Stats')
  68. self.assertEqual(handler.response.code, 200)
  69. self.assertEqual(handler.response.headers["Content-type"], "text/xml")
  70. self.assertTrue(handler.response.headers["Content-Length"] > 0)
  71. self.assertTrue(handler.response.wrote_headers)
  72. self.assertTrue(handler.response.body.find(stats_httpd.XSD_NAMESPACE)>0)
  73. self.assertTrue(handler.response.body.find(stats_httpd.XSD_URL_PATH)>0)
  74. for (k, v) in DUMMY_DATA.items():
  75. self.assertTrue(handler.response.body.find(str(k))>0)
  76. self.assertTrue(handler.response.body.find(str(v))>0)
  77. # URL is '/bind10/statitics/xsd'
  78. handler.path = stats_httpd.XSD_URL_PATH
  79. handler.do_GET()
  80. self.assertEqual(handler.response.code, 200)
  81. self.assertEqual(handler.response.headers["Content-type"], "text/xml")
  82. self.assertTrue(handler.response.headers["Content-Length"] > 0)
  83. self.assertTrue(handler.response.wrote_headers)
  84. self.assertTrue(handler.response.body.find(stats_httpd.XSD_NAMESPACE)>0)
  85. for (k, v) in DUMMY_DATA.items():
  86. self.assertTrue(handler.response.body.find(str(k))>0)
  87. # URL is '/bind10/statitics/xsl'
  88. handler.path = stats_httpd.XSL_URL_PATH
  89. handler.do_GET()
  90. self.assertEqual(handler.response.code, 200)
  91. self.assertEqual(handler.response.headers["Content-type"], "text/xml")
  92. self.assertTrue(handler.response.headers["Content-Length"] > 0)
  93. self.assertTrue(handler.response.wrote_headers)
  94. self.assertTrue(handler.response.body.find(stats_httpd.XSD_NAMESPACE)>0)
  95. for (k, v) in DUMMY_DATA.items():
  96. self.assertTrue(handler.response.body.find(str(k))>0)
  97. # 302 redirect
  98. handler.path = '/'
  99. handler.headers = {'Host': 'my.host.domain'}
  100. handler.do_GET()
  101. self.assertEqual(handler.response.code, 302)
  102. self.assertEqual(handler.response.headers["Location"],
  103. "http://my.host.domain%s" % stats_httpd.XML_URL_PATH)
  104. # 404 NotFound
  105. handler.path = '/path/to/foo/bar'
  106. handler.headers = {}
  107. handler.do_GET()
  108. self.assertEqual(handler.response.code, 404)
  109. # failure case(connection with Stats is down)
  110. handler.path = stats_httpd.XML_URL_PATH
  111. push_answer(self.stats_httpd)
  112. self.assertFalse(self.stats_httpd.cc_session._socket._closed)
  113. self.stats_httpd.cc_session._socket._closed = True
  114. handler.do_GET()
  115. self.stats_httpd.cc_session._socket._closed = False
  116. self.assertEqual(handler.response.code, 500)
  117. self.stats_httpd.cc_session._clear_queues()
  118. # failure case(Stats module returns err)
  119. handler.path = stats_httpd.XML_URL_PATH
  120. self.stats_httpd.cc_session.group_sendmsg(
  121. { 'result': [ 1, "I have an error." ] }, "Stats")
  122. self.assertFalse(self.stats_httpd.cc_session._socket._closed)
  123. self.stats_httpd.cc_session._socket._closed = False
  124. handler.do_GET()
  125. self.assertEqual(handler.response.code, 500)
  126. self.stats_httpd.cc_session._clear_queues()
  127. def test_do_HEAD(self):
  128. for ht in self.httpd:
  129. self._test_do_HEAD(ht._handler)
  130. def _test_do_HEAD(self, handler):
  131. handler.path = '/path/to/foo/bar'
  132. handler.do_HEAD()
  133. self.assertEqual(handler.response.code, 404)
  134. class TestHttpServerError(unittest.TestCase):
  135. """Tests for HttpServerError exception"""
  136. def test_raises(self):
  137. try:
  138. raise stats_httpd.HttpServerError('Nothing')
  139. except stats_httpd.HttpServerError as err:
  140. self.assertEqual(str(err), 'Nothing')
  141. class TestHttpServer(unittest.TestCase):
  142. """Tests for HttpServer class"""
  143. def test_httpserver(self):
  144. self.stats_httpd = stats_httpd.StatsHttpd()
  145. for ht in self.stats_httpd.httpd:
  146. self.assertTrue(ht.server_address in self.stats_httpd.http_addrs)
  147. self.assertEqual(ht.xml_handler, self.stats_httpd.xml_handler)
  148. self.assertEqual(ht.xsd_handler, self.stats_httpd.xsd_handler)
  149. self.assertEqual(ht.xsl_handler, self.stats_httpd.xsl_handler)
  150. self.assertEqual(ht.log_writer, self.stats_httpd.write_log)
  151. self.assertTrue(isinstance(ht._handler, stats_httpd.HttpHandler))
  152. self.assertTrue(isinstance(ht.socket, fake_socket.socket))
  153. class TestStatsHttpdError(unittest.TestCase):
  154. """Tests for StatsHttpdError exception"""
  155. def test_raises(self):
  156. try:
  157. raise stats_httpd.StatsHttpdError('Nothing')
  158. except stats_httpd.StatsHttpdError as err:
  159. self.assertEqual(str(err), 'Nothing')
  160. class TestStatsHttpd(unittest.TestCase):
  161. """Tests for StatsHttpd class"""
  162. def setUp(self):
  163. fake_socket._CLOSED = False
  164. fake_socket.has_ipv6 = True
  165. self.stats_httpd = stats_httpd.StatsHttpd()
  166. def tearDown(self):
  167. self.stats_httpd.stop()
  168. def test_init(self):
  169. self.assertFalse(self.stats_httpd.mccs.get_socket()._closed)
  170. self.assertEqual(self.stats_httpd.mccs.get_socket().fileno(),
  171. id(self.stats_httpd.mccs.get_socket()))
  172. for ht in self.stats_httpd.httpd:
  173. self.assertFalse(ht.socket._closed)
  174. self.assertEqual(ht.socket.fileno(), id(ht.socket))
  175. fake_socket._CLOSED = True
  176. self.assertRaises(isc.cc.session.SessionError,
  177. stats_httpd.StatsHttpd)
  178. fake_socket._CLOSED = False
  179. def test_mccs(self):
  180. self.stats_httpd.open_mccs()
  181. self.assertTrue(
  182. isinstance(self.stats_httpd.mccs.get_socket(), fake_socket.socket))
  183. self.assertTrue(
  184. isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
  185. self.assertTrue(
  186. isinstance(self.stats_httpd.stats_module_spec, isc.config.ModuleSpec))
  187. for cfg in self.stats_httpd.stats_config_spec:
  188. self.assertTrue('item_name' in cfg)
  189. self.assertTrue(cfg['item_name'] in DUMMY_DATA)
  190. self.assertTrue(len(self.stats_httpd.stats_config_spec), len(DUMMY_DATA))
  191. def test_load_config(self):
  192. self.stats_httpd.load_config()
  193. self.assertTrue(('127.0.0.1', 8000) in set(self.stats_httpd.http_addrs))
  194. def test_httpd(self):
  195. # dual stack (addresses is ipv4 and ipv6)
  196. fake_socket.has_ipv6 = True
  197. self.assertTrue(('127.0.0.1', 8000) in set(self.stats_httpd.http_addrs))
  198. self.stats_httpd.http_addrs = [ ('::1', 8000), ('127.0.0.1', 8000) ]
  199. self.assertTrue(
  200. stats_httpd.HttpServer.address_family in set([fake_socket.AF_INET, fake_socket.AF_INET6]))
  201. self.stats_httpd.open_httpd()
  202. for ht in self.stats_httpd.httpd:
  203. self.assertTrue(isinstance(ht.socket, fake_socket.socket))
  204. self.stats_httpd.close_httpd()
  205. # dual stack (address is ipv6)
  206. fake_socket.has_ipv6 = True
  207. self.stats_httpd.http_addrs = [ ('::1', 8000) ]
  208. self.stats_httpd.open_httpd()
  209. for ht in self.stats_httpd.httpd:
  210. self.assertTrue(isinstance(ht.socket, fake_socket.socket))
  211. self.stats_httpd.close_httpd()
  212. # dual stack (address is ipv4)
  213. fake_socket.has_ipv6 = True
  214. self.stats_httpd.http_addrs = [ ('127.0.0.1', 8000) ]
  215. self.stats_httpd.open_httpd()
  216. for ht in self.stats_httpd.httpd:
  217. self.assertTrue(isinstance(ht.socket, fake_socket.socket))
  218. self.stats_httpd.close_httpd()
  219. # only-ipv4 single stack
  220. fake_socket.has_ipv6 = False
  221. self.stats_httpd.http_addrs = [ ('127.0.0.1', 8000) ]
  222. self.stats_httpd.open_httpd()
  223. for ht in self.stats_httpd.httpd:
  224. self.assertTrue(isinstance(ht.socket, fake_socket.socket))
  225. self.stats_httpd.close_httpd()
  226. # only-ipv4 single stack (force set ipv6 )
  227. fake_socket.has_ipv6 = False
  228. self.stats_httpd.http_addrs = [ ('::1', 8000) ]
  229. self.assertRaises(stats_httpd.HttpServerError,
  230. self.stats_httpd.open_httpd)
  231. # hostname
  232. self.stats_httpd.http_addrs = [ ('localhost', 8000) ]
  233. self.stats_httpd.open_httpd()
  234. for ht in self.stats_httpd.httpd:
  235. self.assertTrue(isinstance(ht.socket, fake_socket.socket))
  236. self.stats_httpd.close_httpd()
  237. self.stats_httpd.http_addrs = [ ('my.host.domain', 8000) ]
  238. self.stats_httpd.open_httpd()
  239. for ht in self.stats_httpd.httpd:
  240. self.assertTrue(isinstance(ht.socket, fake_socket.socket))
  241. self.stats_httpd.close_httpd()
  242. # over flow of port number
  243. self.stats_httpd.http_addrs = [ ('', 80000) ]
  244. self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
  245. # negative
  246. self.stats_httpd.http_addrs = [ ('', -8000) ]
  247. self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
  248. # alphabet
  249. self.stats_httpd.http_addrs = [ ('', 'ABCDE') ]
  250. self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
  251. def test_start(self):
  252. self.stats_httpd.cc_session.group_sendmsg(
  253. { 'command': [ "shutdown" ] }, "StatsHttpd")
  254. self.stats_httpd.start()
  255. self.stats_httpd = stats_httpd.StatsHttpd()
  256. self.assertRaises(
  257. fake_select.error, self.stats_httpd.start)
  258. def test_stop(self):
  259. # success case
  260. fake_socket._CLOSED = False
  261. self.stats_httpd.stop()
  262. self.assertFalse(self.stats_httpd.running)
  263. self.assertIsNone(self.stats_httpd.mccs)
  264. for ht in self.stats_httpd.httpd:
  265. self.assertTrue(ht.socket._closed)
  266. self.assertTrue(self.stats_httpd.cc_session._socket._closed)
  267. # failure case
  268. self.stats_httpd.cc_session._socket._closed = False
  269. self.stats_httpd.open_mccs()
  270. self.stats_httpd.cc_session._socket._closed = True
  271. self.stats_httpd.stop() # No excetion raises
  272. self.stats_httpd.cc_session._socket._closed = False
  273. def test_open_template(self):
  274. # successful conditions
  275. tmpl = self.stats_httpd.open_template(stats_httpd.XML_TEMPLATE_LOCATION)
  276. self.assertTrue(isinstance(tmpl, string.Template))
  277. opts = dict(
  278. xml_string="<dummy></dummy>",
  279. xsd_namespace="http://host/path/to/",
  280. xsd_url_path="/path/to/",
  281. xsl_url_path="/path/to/")
  282. lines = tmpl.substitute(opts)
  283. for n in opts:
  284. self.assertTrue(lines.find(opts[n])>0)
  285. tmpl = self.stats_httpd.open_template(stats_httpd.XSD_TEMPLATE_LOCATION)
  286. self.assertTrue(isinstance(tmpl, string.Template))
  287. opts = dict(
  288. xsd_string="<dummy></dummy>",
  289. xsd_namespace="http://host/path/to/")
  290. lines = tmpl.substitute(opts)
  291. for n in opts:
  292. self.assertTrue(lines.find(opts[n])>0)
  293. tmpl = self.stats_httpd.open_template(stats_httpd.XSL_TEMPLATE_LOCATION)
  294. self.assertTrue(isinstance(tmpl, string.Template))
  295. opts = dict(
  296. xsl_string="<dummy></dummy>",
  297. xsd_namespace="http://host/path/to/")
  298. lines = tmpl.substitute(opts)
  299. for n in opts:
  300. self.assertTrue(lines.find(opts[n])>0)
  301. # unsuccessful condition
  302. self.assertRaises(
  303. IOError,
  304. self.stats_httpd.open_template, '/path/to/foo/bar')
  305. def test_commands(self):
  306. self.assertEqual(self.stats_httpd.command_handler("status", None),
  307. isc.config.ccsession.create_answer(
  308. 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
  309. self.stats_httpd.running = True
  310. self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
  311. isc.config.ccsession.create_answer(
  312. 0, "Stats Httpd is shutting down."))
  313. self.assertFalse(self.stats_httpd.running)
  314. self.assertEqual(
  315. self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
  316. isc.config.ccsession.create_answer(
  317. 1, "Unknown command: __UNKNOWN_COMMAND__"))
  318. def test_config(self):
  319. self.assertEqual(
  320. self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
  321. isc.config.ccsession.create_answer(
  322. 1, "Unknown known config: _UNKNOWN_KEY_"))
  323. self.assertEqual(
  324. self.stats_httpd.config_handler(
  325. dict(listen_on=[dict(address="::2",port=8000)])),
  326. isc.config.ccsession.create_answer(0))
  327. self.assertTrue("listen_on" in self.stats_httpd.config)
  328. for addr in self.stats_httpd.config["listen_on"]:
  329. self.assertTrue("address" in addr)
  330. self.assertTrue("port" in addr)
  331. self.assertTrue(addr["address"] == "::2")
  332. self.assertTrue(addr["port"] == 8000)
  333. self.assertEqual(
  334. self.stats_httpd.config_handler(
  335. dict(listen_on=[dict(address="::1",port=80)])),
  336. isc.config.ccsession.create_answer(0))
  337. self.assertTrue("listen_on" in self.stats_httpd.config)
  338. for addr in self.stats_httpd.config["listen_on"]:
  339. self.assertTrue("address" in addr)
  340. self.assertTrue("port" in addr)
  341. self.assertTrue(addr["address"] == "::1")
  342. self.assertTrue(addr["port"] == 80)
  343. self.assertEqual(
  344. self.stats_httpd.config_handler(
  345. dict(listen_on=[dict(address="1.2.3.4",port=54321)])),
  346. isc.config.ccsession.create_answer(0))
  347. self.assertTrue("listen_on" in self.stats_httpd.config)
  348. for addr in self.stats_httpd.config["listen_on"]:
  349. self.assertTrue("address" in addr)
  350. self.assertTrue("port" in addr)
  351. self.assertTrue(addr["address"] == "1.2.3.4")
  352. self.assertTrue(addr["port"] == 54321)
  353. (ret, arg) = isc.config.ccsession.parse_answer(
  354. self.stats_httpd.config_handler(
  355. dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
  356. )
  357. self.assertEqual(ret, 1)
  358. def test_for_without_B10_FROM_SOURCE(self):
  359. # just lets it go through the code without B10_FROM_SOURCE env
  360. # variable
  361. if "B10_FROM_SOURCE" in os.environ:
  362. tmppath = os.environ["B10_FROM_SOURCE"]
  363. os.environ.pop("B10_FROM_SOURCE")
  364. imp.reload(stats_httpd)
  365. os.environ["B10_FROM_SOURCE"] = tmppath
  366. imp.reload(stats_httpd)
  367. stats_httpd.socket = fake_socket
  368. stats_httpd.select = fake_select
  369. if __name__ == "__main__":
  370. unittest.main()