b10-stats-httpd_test.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. # Copyright (C) 2011-2012 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. """
  16. In each of these tests we start several virtual components. They are
  17. not the real components, no external processes are started. They are
  18. just simple mock objects running each in its own thread and pretending
  19. to be bind10 modules. This helps testing the stats http server in a
  20. close to real environment.
  21. """
  22. import unittest
  23. import os
  24. import imp
  25. import socket
  26. import errno
  27. import select
  28. import string
  29. import time
  30. import threading
  31. import http.client
  32. import xml.etree.ElementTree
  33. import random
  34. import urllib.parse
  35. import sys
  36. # load this module for xml validation with xsd. For this test, an
  37. # installation of lxml is required in advance. See http://lxml.de/.
  38. try:
  39. from lxml import etree as lxml_etree
  40. except ImportError:
  41. lxml_etree = None
  42. import isc
  43. import isc.log
  44. import stats_httpd
  45. import stats
  46. from test_utils import BaseModules, ThreadingServerManager, MyStats,\
  47. MyStatsHttpd, SignalHandler,\
  48. send_command, send_shutdown, CONST_BASETIME
  49. from isc.testutils.ccsession_mock import MockModuleCCSession
  50. # This test suite uses xml.etree.ElementTree.XMLParser via
  51. # xml.etree.ElementTree.parse. On the platform where expat isn't
  52. # installed, ImportError is raised and it's failed. Check expat is
  53. # available before the test invocation. Skip this test if it's
  54. # unavailable.
  55. try:
  56. # ImportError raised if xpat is unavailable
  57. xml_parser = xml.etree.ElementTree.XMLParser()
  58. except ImportError:
  59. xml_parser = None
  60. # set XML Namespaces for testing
  61. XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
  62. XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
  63. XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
  64. XMLNS_XSI = stats_httpd.XMLNS_XSI
  65. DUMMY_DATA = {
  66. 'Init' : {
  67. "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
  68. },
  69. 'Auth' : {
  70. "queries.tcp": 6,
  71. "queries.udp": 4,
  72. "queries.perzone": [{
  73. "zonename": "test1.example",
  74. "queries.tcp": 10,
  75. "queries.udp": 8
  76. }, {
  77. "zonename": "test2.example",
  78. "queries.tcp": 8,
  79. "queries.udp": 6
  80. }],
  81. "nds_queries.perzone": {
  82. "test10.example": {
  83. "queries.tcp": 10,
  84. "queries.udp": 8
  85. },
  86. "test20.example": {
  87. "queries.tcp": 8,
  88. "queries.udp": 6
  89. }
  90. }
  91. },
  92. 'Stats' : {
  93. "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
  94. "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
  95. "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
  96. "lname": "4d70d40a_c@host",
  97. "timestamp": time.mktime(CONST_BASETIME)
  98. }
  99. }
  100. def get_availaddr(address='127.0.0.1', port=8001):
  101. """returns a tuple of address and port which is available to
  102. listen on the platform. The first argument is a address for
  103. search. The second argument is a port for search. If a set of
  104. address and port is failed on the search for the availability, the
  105. port number is increased and it goes on the next trial until the
  106. available set of address and port is looked up. If the port number
  107. reaches over 65535, it may stop the search and raise a
  108. OverflowError exception."""
  109. while True:
  110. for addr in socket.getaddrinfo(
  111. address, port, 0,
  112. socket.SOCK_STREAM, socket.IPPROTO_TCP):
  113. sock = socket.socket(addr[0], socket.SOCK_STREAM)
  114. try:
  115. sock.bind((address, port))
  116. return (address, port)
  117. except socket.error:
  118. continue
  119. finally:
  120. if sock: sock.close()
  121. # This address and port number are already in use.
  122. # next port number is added
  123. port = port + 1
  124. def is_ipv6_enabled(address='::1', port=8001):
  125. """checks IPv6 enabled on the platform. address for check is '::1'
  126. and port for check is random number between 8001 and
  127. 65535. Retrying is 3 times even if it fails. The built-in socket
  128. module provides a 'has_ipv6' parameter, but it's not used here
  129. because there may be a situation where the value is True on an
  130. environment where the IPv6 config is disabled."""
  131. for p in random.sample(range(port, 65535), 3):
  132. try:
  133. sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
  134. sock.bind((address, p))
  135. return True
  136. except socket.error:
  137. continue
  138. finally:
  139. if sock: sock.close()
  140. return False
  141. class TestItemNameList(unittest.TestCase):
  142. def test_item_name_list(self):
  143. # for a one-element list
  144. self.assertEqual(['a'],
  145. stats_httpd.item_name_list({'a':1}, 'a'))
  146. # for a dict under a dict
  147. self.assertEqual(['a','a/b'],
  148. stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
  149. self.assertEqual(['a/b'],
  150. stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
  151. self.assertEqual(['a','a/b','a/b/c'],
  152. stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
  153. self.assertEqual(['a/b','a/b/c'],
  154. stats_httpd.item_name_list({'a':{'b':{'c':1}}},
  155. 'a/b'))
  156. self.assertEqual(['a/b/c'],
  157. stats_httpd.item_name_list({'a':{'b':{'c':1}}},
  158. 'a/b/c'))
  159. # for a list under a dict
  160. self.assertEqual(['a[2]'],
  161. stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
  162. self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
  163. stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
  164. self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
  165. stats_httpd.item_name_list({'a':[1,2,3]}, ''))
  166. # for a list under adict under a dict
  167. self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
  168. stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
  169. self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
  170. stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
  171. self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
  172. stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
  173. # for a mixed case of the above
  174. self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
  175. stats_httpd.item_name_list(
  176. {'a':{'b':[1,2,3], 'c':1}}, 'a'))
  177. self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
  178. stats_httpd.item_name_list(
  179. {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
  180. self.assertEqual(['a/c'],
  181. stats_httpd.item_name_list(
  182. {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
  183. # for specifying a wrong identifier which is not found in
  184. # element
  185. self.assertRaises(isc.cc.data.DataNotFoundError,
  186. stats_httpd.item_name_list, {'x':1}, 'a')
  187. # for specifying a string in element and an empty string in
  188. # identifier
  189. self.assertEqual([],
  190. stats_httpd.item_name_list('a', ''))
  191. # for specifying empty strings in element and identifier
  192. self.assertEqual([],
  193. stats_httpd.item_name_list('', ''))
  194. # for specifying wrong element, which is an non-empty string,
  195. # and an non-empty string in identifier
  196. self.assertRaises(isc.cc.data.DataTypeError,
  197. stats_httpd.item_name_list, 'a', 'a')
  198. # for specifying None in element and identifier
  199. self.assertRaises(isc.cc.data.DataTypeError,
  200. stats_httpd.item_name_list, None, None)
  201. # for specifying non-dict in element
  202. self.assertRaises(isc.cc.data.DataTypeError,
  203. stats_httpd.item_name_list, [1,2,3], 'a')
  204. self.assertRaises(isc.cc.data.DataTypeError,
  205. stats_httpd.item_name_list, [1,2,3], '')
  206. # for checking key names sorted which consist of element
  207. num = 11
  208. keys = [ 'a', 'aa', 'b' ]
  209. keys.sort(reverse=True)
  210. dictlist = dict([ (k, list(range(num))) for k in keys ])
  211. keys.sort()
  212. ans = []
  213. for k in keys:
  214. ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
  215. self.assertEqual(ans,
  216. stats_httpd.item_name_list(dictlist, ''))
  217. class TestHttpHandler(unittest.TestCase):
  218. """Tests for HttpHandler class"""
  219. def setUp(self):
  220. # set the signal handler for deadlock
  221. self.sig_handler = SignalHandler(self.fail)
  222. self.base = BaseModules()
  223. self.stats_server = ThreadingServerManager(MyStats)
  224. self.stats = self.stats_server.server
  225. DUMMY_DATA['Stats']['lname'] = self.stats.cc_session.lname
  226. self.stats_server.run()
  227. (self.address, self.port) = get_availaddr()
  228. self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, (self.address, self.port))
  229. self.stats_httpd = self.stats_httpd_server.server
  230. self.stats_httpd_server.run()
  231. self.client = http.client.HTTPConnection(self.address, self.port)
  232. self.client._http_vsn_str = 'HTTP/1.0\n'
  233. self.client.connect()
  234. def tearDown(self):
  235. self.client.close()
  236. self.stats_httpd_server.shutdown()
  237. self.stats_server.shutdown()
  238. self.base.shutdown()
  239. # reset the signal handler
  240. self.sig_handler.reset()
  241. @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
  242. @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
  243. def test_do_GET(self):
  244. self.assertTrue(type(self.stats_httpd.httpd) is list)
  245. self.assertEqual(len(self.stats_httpd.httpd), 1)
  246. self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
  247. def check_XML_URL_PATH(path=''):
  248. url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
  249. url_path = urllib.parse.quote(url_path)
  250. self.client.putrequest('GET', url_path)
  251. self.client.endheaders()
  252. response = self.client.getresponse()
  253. self.assertEqual(response.getheader("Content-type"), "text/xml")
  254. self.assertGreater(int(response.getheader("Content-Length")), 0)
  255. self.assertEqual(response.status, 200)
  256. xml_doctype = response.readline().decode()
  257. xsl_doctype = response.readline().decode()
  258. self.assertGreater(len(xml_doctype), 0)
  259. self.assertGreater(len(xsl_doctype), 0)
  260. root = xml.etree.ElementTree.parse(response).getroot()
  261. self.assertGreater(root.tag.find('statistics'), 0)
  262. schema_loc = '{%s}schemaLocation' % XMLNS_XSI
  263. # check the path of XSD
  264. self.assertEqual(root.attrib[schema_loc],
  265. stats_httpd.XSD_NAMESPACE + ' '
  266. + stats_httpd.XSD_URL_PATH)
  267. # check the path of XSL
  268. self.assertTrue(xsl_doctype.startswith(
  269. '<?xml-stylesheet type="text/xsl" href="' +
  270. stats_httpd.XSL_URL_PATH
  271. + '"?>'))
  272. # check whether the list of 'identifier' attributes in
  273. # root is same as the list of item names in DUMMY_DATA
  274. id_list = [ elm.attrib['identifier'] for elm in root ]
  275. item_list = [ it for it in \
  276. stats_httpd.item_name_list(DUMMY_DATA, path) \
  277. if len(it.split('/')) > 1 ]
  278. self.assertEqual(id_list, item_list)
  279. for elem in root:
  280. attr = elem.attrib
  281. value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
  282. # No 'value' attribute should be found in the 'item'
  283. # element when datatype of the value is list or dict.
  284. if type(value) is list or type(value) is dict:
  285. self.assertFalse('value' in attr)
  286. # The value of the 'value' attribute should be checked
  287. # after casting it to string type if datatype of the
  288. # value is int or float. Because attr['value'] returns
  289. # string type even if its value is int or float.
  290. elif type(value) is int or type(value) is float:
  291. self.assertEqual(attr['value'], str(value))
  292. else:
  293. self.assertEqual(attr['value'], value)
  294. # URL is '/bind10/statistics/xml'
  295. check_XML_URL_PATH()
  296. for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
  297. check_XML_URL_PATH(path)
  298. def check_XSD_URL_PATH():
  299. url_path = stats_httpd.XSD_URL_PATH
  300. url_path = urllib.parse.quote(url_path)
  301. self.client.putrequest('GET', url_path)
  302. self.client.endheaders()
  303. response = self.client.getresponse()
  304. self.assertEqual(response.getheader("Content-type"), "text/xml")
  305. self.assertGreater(int(response.getheader("Content-Length")), 0)
  306. self.assertEqual(response.status, 200)
  307. root = xml.etree.ElementTree.parse(response).getroot()
  308. url_xmlschema = '{%s}' % XMLNS_XSD
  309. self.assertGreater(root.tag.find('schema'), 0)
  310. self.assertTrue(hasattr(root, 'attrib'))
  311. self.assertTrue('targetNamespace' in root.attrib)
  312. self.assertEqual(root.attrib['targetNamespace'],
  313. stats_httpd.XSD_NAMESPACE)
  314. # URL is '/bind10/statistics/xsd'
  315. check_XSD_URL_PATH()
  316. def check_XSL_URL_PATH():
  317. url_path = stats_httpd.XSL_URL_PATH
  318. url_path = urllib.parse.quote(url_path)
  319. self.client.putrequest('GET', url_path)
  320. self.client.endheaders()
  321. response = self.client.getresponse()
  322. self.assertEqual(response.getheader("Content-type"), "text/xml")
  323. self.assertGreater(int(response.getheader("Content-Length")), 0)
  324. self.assertEqual(response.status, 200)
  325. root = xml.etree.ElementTree.parse(response).getroot()
  326. url_trans = '{%s}' % XMLNS_XSL
  327. url_xhtml = '{%s}' % XMLNS_XHTML
  328. self.assertEqual(root.tag, url_trans + 'stylesheet')
  329. # URL is '/bind10/statistics/xsl'
  330. check_XSL_URL_PATH()
  331. # 302 redirect
  332. self.client._http_vsn_str = 'HTTP/1.1'
  333. self.client.putrequest('GET', '/')
  334. self.client.putheader('Host', self.address)
  335. self.client.endheaders()
  336. response = self.client.getresponse()
  337. self.assertEqual(response.status, 302)
  338. self.assertEqual(response.getheader('Location'),
  339. "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
  340. # 404 NotFound (random path)
  341. self.client._http_vsn_str = 'HTTP/1.0'
  342. self.client.putrequest('GET', '/path/to/foo/bar')
  343. self.client.endheaders()
  344. response = self.client.getresponse()
  345. self.assertEqual(response.status, 404)
  346. self.client._http_vsn_str = 'HTTP/1.0'
  347. self.client.putrequest('GET', '/bind10/foo')
  348. self.client.endheaders()
  349. response = self.client.getresponse()
  350. self.assertEqual(response.status, 404)
  351. self.client._http_vsn_str = 'HTTP/1.0'
  352. self.client.putrequest('GET', '/bind10/statistics/foo')
  353. self.client.endheaders()
  354. response = self.client.getresponse()
  355. self.assertEqual(response.status, 404)
  356. self.client._http_vsn_str = 'HTTP/1.0'
  357. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
  358. self.client.endheaders()
  359. response = self.client.getresponse()
  360. self.assertEqual(response.status, 404)
  361. # 200 ok
  362. self.client._http_vsn_str = 'HTTP/1.0'
  363. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
  364. self.client.endheaders()
  365. response = self.client.getresponse()
  366. self.assertEqual(response.status, 200)
  367. self.client._http_vsn_str = 'HTTP/1.0'
  368. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
  369. self.client.endheaders()
  370. response = self.client.getresponse()
  371. self.assertEqual(response.status, 200)
  372. self.client._http_vsn_str = 'HTTP/1.0'
  373. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
  374. self.client.endheaders()
  375. response = self.client.getresponse()
  376. self.assertEqual(response.status, 200)
  377. # 404 NotFound (too long path)
  378. self.client._http_vsn_str = 'HTTP/1.0'
  379. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
  380. self.client.endheaders()
  381. response = self.client.getresponse()
  382. self.assertEqual(response.status, 404)
  383. # 404 NotFound (nonexistent module name)
  384. self.client._http_vsn_str = 'HTTP/1.0'
  385. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
  386. self.client.endheaders()
  387. response = self.client.getresponse()
  388. self.assertEqual(response.status, 404)
  389. self.client._http_vsn_str = 'HTTP/1.0'
  390. self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
  391. self.client.endheaders()
  392. response = self.client.getresponse()
  393. self.assertEqual(response.status, 404)
  394. self.client._http_vsn_str = 'HTTP/1.0'
  395. self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
  396. self.client.endheaders()
  397. response = self.client.getresponse()
  398. self.assertEqual(response.status, 404)
  399. # 404 NotFound (nonexistent item name)
  400. self.client._http_vsn_str = 'HTTP/1.0'
  401. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
  402. self.client.endheaders()
  403. response = self.client.getresponse()
  404. self.assertEqual(response.status, 404)
  405. self.client._http_vsn_str = 'HTTP/1.0'
  406. self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
  407. self.client.endheaders()
  408. response = self.client.getresponse()
  409. self.assertEqual(response.status, 404)
  410. self.client._http_vsn_str = 'HTTP/1.0'
  411. self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
  412. self.client.endheaders()
  413. response = self.client.getresponse()
  414. self.assertEqual(response.status, 404)
  415. # 404 NotFound (existent module but nonexistent item name)
  416. self.client._http_vsn_str = 'HTTP/1.0'
  417. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
  418. self.client.endheaders()
  419. response = self.client.getresponse()
  420. self.assertEqual(response.status, 404)
  421. self.client._http_vsn_str = 'HTTP/1.0'
  422. self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
  423. self.client.endheaders()
  424. response = self.client.getresponse()
  425. self.assertEqual(response.status, 404)
  426. self.client._http_vsn_str = 'HTTP/1.0'
  427. self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
  428. self.client.endheaders()
  429. response = self.client.getresponse()
  430. self.assertEqual(response.status, 404)
  431. def test_do_GET_failed1(self):
  432. # checks status
  433. self.assertEqual(send_command("status", "Stats"),
  434. (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
  435. # failure case(Stats is down)
  436. self.assertTrue(self.stats.running)
  437. self.assertEqual(send_shutdown("Stats"), (0, None)) # Stats is down
  438. self.assertFalse(self.stats.running)
  439. self.stats_httpd.cc_session.set_timeout(milliseconds=100)
  440. # request XML
  441. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
  442. self.client.endheaders()
  443. response = self.client.getresponse()
  444. self.assertEqual(response.status, 500)
  445. # request XSD
  446. self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
  447. self.client.endheaders()
  448. response = self.client.getresponse()
  449. self.assertEqual(response.status, 200)
  450. # request XSL
  451. self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
  452. self.client.endheaders()
  453. response = self.client.getresponse()
  454. self.assertEqual(response.status, 200)
  455. def test_do_GET_failed2(self):
  456. # failure case(Stats replies an error)
  457. self.stats.mccs.set_command_handler(
  458. lambda cmd, args: \
  459. isc.config.ccsession.create_answer(1, "specified arguments are incorrect: I have an error.")
  460. )
  461. # request XML
  462. self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
  463. self.client.endheaders()
  464. response = self.client.getresponse()
  465. self.assertEqual(response.status, 404)
  466. # request XSD
  467. self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
  468. self.client.endheaders()
  469. response = self.client.getresponse()
  470. self.assertEqual(response.status, 200)
  471. # request XSL
  472. self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
  473. self.client.endheaders()
  474. response = self.client.getresponse()
  475. self.assertEqual(response.status, 200)
  476. def test_do_HEAD(self):
  477. self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
  478. self.client.endheaders()
  479. response = self.client.getresponse()
  480. self.assertEqual(response.status, 200)
  481. self.client.putrequest('HEAD', '/path/to/foo/bar')
  482. self.client.endheaders()
  483. response = self.client.getresponse()
  484. self.assertEqual(response.status, 404)
  485. @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
  486. def test_xml_validation_with_xsd(self):
  487. """Tests for XML validation with XSD. If lxml is not
  488. installed, this tests would be skipped."""
  489. def request_xsd():
  490. url_path = stats_httpd.XSD_URL_PATH
  491. url_path = urllib.parse.quote(url_path)
  492. self.client.putrequest('GET', url_path)
  493. self.client.endheaders()
  494. xsd_doc = self.client.getresponse()
  495. xsd_doc = lxml_etree.parse(xsd_doc)
  496. return lxml_etree.XMLSchema(xsd_doc)
  497. def request_xmldoc(path=''):
  498. url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
  499. url_path = urllib.parse.quote(url_path)
  500. self.client.putrequest('GET', url_path)
  501. self.client.endheaders()
  502. xml_doc = self.client.getresponse()
  503. return lxml_etree.parse(xml_doc)
  504. # request XSD and XML
  505. xsd = request_xsd()
  506. xml_doc = request_xmldoc()
  507. # do validation
  508. self.assertTrue(xsd.validate(xml_doc))
  509. # validate each paths in DUMMY_DATA
  510. for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
  511. # request XML
  512. xml_doc = request_xmldoc(path)
  513. # do validation
  514. self.assertTrue(xsd.validate(xml_doc))
  515. class TestHttpServerError(unittest.TestCase):
  516. """Tests for HttpServerError exception"""
  517. def test_raises(self):
  518. try:
  519. raise stats_httpd.HttpServerError('Nothing')
  520. except stats_httpd.HttpServerError as err:
  521. self.assertEqual(str(err), 'Nothing')
  522. class TestHttpServer(unittest.TestCase):
  523. """Tests for HttpServer class"""
  524. def setUp(self):
  525. # set the signal handler for deadlock
  526. self.sig_handler = SignalHandler(self.fail)
  527. self.base = BaseModules()
  528. def tearDown(self):
  529. if hasattr(self, "stats_httpd"):
  530. self.stats_httpd.stop()
  531. self.base.shutdown()
  532. # reset the signal handler
  533. self.sig_handler.reset()
  534. def test_httpserver(self):
  535. self.stats_httpd = MyStatsHttpd(get_availaddr())
  536. self.assertEqual(type(self.stats_httpd.httpd), list)
  537. self.assertEqual(len(self.stats_httpd.httpd), 1)
  538. for httpd in self.stats_httpd.httpd:
  539. self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
  540. class TestStatsHttpdError(unittest.TestCase):
  541. """Tests for StatsHttpdError exception"""
  542. def test_raises1(self):
  543. try:
  544. raise stats_httpd.StatsHttpdError('Nothing')
  545. except stats_httpd.StatsHttpdError as err:
  546. self.assertEqual(str(err), 'Nothing')
  547. def test_raises2(self):
  548. try:
  549. raise stats_httpd.StatsHttpdDataError('Nothing')
  550. except stats_httpd.StatsHttpdDataError as err:
  551. self.assertEqual(str(err), 'Nothing')
  552. class TestStatsHttpd(unittest.TestCase):
  553. """Tests for StatsHttpd class"""
  554. def setUp(self):
  555. # set the signal handler for deadlock
  556. self.sig_handler = SignalHandler(self.fail)
  557. self.base = BaseModules()
  558. self.stats_server = ThreadingServerManager(MyStats)
  559. self.stats_server.run()
  560. # checking IPv6 enabled on this platform
  561. self.ipv6_enabled = is_ipv6_enabled()
  562. def tearDown(self):
  563. if hasattr(self, "stats_httpd"):
  564. self.stats_httpd.stop()
  565. self.stats_server.shutdown()
  566. self.base.shutdown()
  567. # reset the signal handler
  568. self.sig_handler.reset()
  569. def test_init(self):
  570. server_address = get_availaddr()
  571. self.stats_httpd = MyStatsHttpd(server_address)
  572. self.assertEqual(self.stats_httpd.running, False)
  573. self.assertEqual(self.stats_httpd.poll_intval, 0.5)
  574. self.assertNotEqual(len(self.stats_httpd.httpd), 0)
  575. self.assertEqual(type(self.stats_httpd.mccs), isc.config.ModuleCCSession)
  576. self.assertEqual(type(self.stats_httpd.cc_session), isc.cc.Session)
  577. self.assertEqual(len(self.stats_httpd.config), 2)
  578. self.assertTrue('listen_on' in self.stats_httpd.config)
  579. self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
  580. self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
  581. self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
  582. self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
  583. ans = send_command(
  584. isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
  585. "ConfigManager", {"module_name":"StatsHttpd"})
  586. # assert StatsHttpd is added to ConfigManager
  587. self.assertNotEqual(ans, (0,{}))
  588. self.assertTrue(ans[1]['module_name'], 'StatsHttpd')
  589. def test_init_hterr(self):
  590. orig_open_httpd = stats_httpd.StatsHttpd.open_httpd
  591. def err_open_httpd(arg): raise stats_httpd.HttpServerError
  592. stats_httpd.StatsHttpd.open_httpd = err_open_httpd
  593. self.assertRaises(stats_httpd.HttpServerError, stats_httpd.StatsHttpd)
  594. ans = send_command(
  595. isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
  596. "ConfigManager", {"module_name":"StatsHttpd"})
  597. # assert StatsHttpd is removed from ConfigManager
  598. self.assertEqual(ans, (0,{}))
  599. stats_httpd.StatsHttpd.open_httpd = orig_open_httpd
  600. def test_openclose_mccs(self):
  601. self.stats_httpd = MyStatsHttpd(get_availaddr())
  602. mccs = MockModuleCCSession()
  603. self.stats_httpd.mccs = mccs
  604. self.assertFalse(self.stats_httpd.mccs.stopped)
  605. self.assertFalse(self.stats_httpd.mccs.closed)
  606. self.stats_httpd.close_mccs()
  607. self.assertTrue(mccs.stopped)
  608. self.assertTrue(mccs.closed)
  609. self.assertEqual(self.stats_httpd.mccs, None)
  610. self.stats_httpd.open_mccs()
  611. self.assertIsNotNone(self.stats_httpd.mccs)
  612. self.stats_httpd.mccs = None
  613. self.assertEqual(self.stats_httpd.mccs, None)
  614. self.assertEqual(self.stats_httpd.close_mccs(), None)
  615. def test_mccs(self):
  616. self.stats_httpd = MyStatsHttpd(get_availaddr())
  617. self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
  618. self.assertTrue(
  619. isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
  620. self.assertTrue(
  621. isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
  622. statistics_spec = self.stats_httpd.get_stats_spec()
  623. for mod in DUMMY_DATA:
  624. self.assertTrue(mod in statistics_spec)
  625. for cfg in statistics_spec[mod]:
  626. self.assertTrue('item_name' in cfg)
  627. self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
  628. self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
  629. self.stats_httpd.close_mccs()
  630. self.assertIsNone(self.stats_httpd.mccs)
  631. def test_httpd(self):
  632. # dual stack (addresses is ipv4 and ipv6)
  633. if self.ipv6_enabled:
  634. server_addresses = (get_availaddr('::1'), get_availaddr())
  635. self.stats_httpd = MyStatsHttpd(*server_addresses)
  636. for ht in self.stats_httpd.httpd:
  637. self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
  638. self.assertTrue(ht.address_family in set([socket.AF_INET, socket.AF_INET6]))
  639. self.assertTrue(isinstance(ht.socket, socket.socket))
  640. # dual stack (address is ipv6)
  641. if self.ipv6_enabled:
  642. server_addresses = get_availaddr('::1')
  643. self.stats_httpd = MyStatsHttpd(server_addresses)
  644. for ht in self.stats_httpd.httpd:
  645. self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
  646. self.assertEqual(ht.address_family, socket.AF_INET6)
  647. self.assertTrue(isinstance(ht.socket, socket.socket))
  648. # dual/single stack (address is ipv4)
  649. server_addresses = get_availaddr()
  650. self.stats_httpd = MyStatsHttpd(server_addresses)
  651. for ht in self.stats_httpd.httpd:
  652. self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
  653. self.assertEqual(ht.address_family, socket.AF_INET)
  654. self.assertTrue(isinstance(ht.socket, socket.socket))
  655. def test_httpd_anyIPv4(self):
  656. # any address (IPv4)
  657. server_addresses = get_availaddr(address='0.0.0.0')
  658. self.stats_httpd = MyStatsHttpd(server_addresses)
  659. for ht in self.stats_httpd.httpd:
  660. self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
  661. self.assertEqual(ht.address_family,socket.AF_INET)
  662. self.assertTrue(isinstance(ht.socket, socket.socket))
  663. def test_httpd_anyIPv6(self):
  664. # any address (IPv6)
  665. if self.ipv6_enabled:
  666. server_addresses = get_availaddr(address='::')
  667. self.stats_httpd = MyStatsHttpd(server_addresses)
  668. for ht in self.stats_httpd.httpd:
  669. self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
  670. self.assertEqual(ht.address_family,socket.AF_INET6)
  671. self.assertTrue(isinstance(ht.socket, socket.socket))
  672. def test_httpd_failed(self):
  673. # existent hostname
  674. self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
  675. get_availaddr(address='localhost'))
  676. # nonexistent hostname
  677. self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('my.host.domain', 8000))
  678. # over flow of port number
  679. self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 80000))
  680. # negative
  681. self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', -8000))
  682. # alphabet
  683. self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 'ABCDE'))
  684. # Address already in use
  685. server_addresses = get_availaddr()
  686. self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, server_addresses)
  687. self.stats_httpd_server.run()
  688. self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, server_addresses)
  689. send_shutdown("StatsHttpd")
  690. def test_running(self):
  691. self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
  692. self.stats_httpd = self.stats_httpd_server.server
  693. self.assertFalse(self.stats_httpd.running)
  694. self.stats_httpd_server.run()
  695. self.assertEqual(send_command("status", "StatsHttpd"),
  696. (0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
  697. self.assertTrue(self.stats_httpd.running)
  698. self.assertEqual(send_shutdown("StatsHttpd"), (0, None))
  699. self.assertFalse(self.stats_httpd.running)
  700. self.stats_httpd_server.shutdown()
  701. # failure case
  702. self.stats_httpd = MyStatsHttpd(get_availaddr())
  703. self.stats_httpd.cc_session.close()
  704. self.assertRaises(ValueError, self.stats_httpd.start)
  705. def test_failure_with_a_select_error (self):
  706. """checks select.error is raised if the exception except
  707. errno.EINTR is raised while it's selecting"""
  708. def raise_select_except(*args):
  709. raise select.error('dummy error')
  710. orig_select = stats_httpd.select.select
  711. stats_httpd.select.select = raise_select_except
  712. self.stats_httpd = MyStatsHttpd(get_availaddr())
  713. self.assertRaises(select.error, self.stats_httpd.start)
  714. stats_httpd.select.select = orig_select
  715. def test_nofailure_with_errno_EINTR(self):
  716. """checks no exception is raised if errno.EINTR is raised
  717. while it's selecting"""
  718. def raise_select_except(*args):
  719. raise select.error(errno.EINTR)
  720. orig_select = stats_httpd.select.select
  721. stats_httpd.select.select = raise_select_except
  722. self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
  723. self.stats_httpd_server.run()
  724. self.stats_httpd_server.shutdown()
  725. stats_httpd.select.select = orig_select
  726. def test_open_template(self):
  727. self.stats_httpd = MyStatsHttpd(get_availaddr())
  728. # successful conditions
  729. tmpl = self.stats_httpd.open_template(stats_httpd.XML_TEMPLATE_LOCATION)
  730. self.assertTrue(isinstance(tmpl, string.Template))
  731. opts = dict(
  732. xml_string="<dummy></dummy>",
  733. xsl_url_path="/path/to/")
  734. lines = tmpl.substitute(opts)
  735. for n in opts:
  736. self.assertGreater(lines.find(opts[n]), 0)
  737. tmpl = self.stats_httpd.open_template(stats_httpd.XSD_TEMPLATE_LOCATION)
  738. self.assertTrue(isinstance(tmpl, string.Template))
  739. opts = dict(xsd_namespace="http://host/path/to/")
  740. lines = tmpl.substitute(opts)
  741. for n in opts:
  742. self.assertGreater(lines.find(opts[n]), 0)
  743. tmpl = self.stats_httpd.open_template(stats_httpd.XSL_TEMPLATE_LOCATION)
  744. self.assertTrue(isinstance(tmpl, string.Template))
  745. opts = dict(xsd_namespace="http://host/path/to/")
  746. lines = tmpl.substitute(opts)
  747. for n in opts:
  748. self.assertGreater(lines.find(opts[n]), 0)
  749. # unsuccessful condition
  750. self.assertRaises(
  751. stats_httpd.StatsHttpdDataError,
  752. self.stats_httpd.open_template, '/path/to/foo/bar')
  753. def test_commands(self):
  754. self.stats_httpd = MyStatsHttpd(get_availaddr())
  755. self.assertEqual(self.stats_httpd.command_handler("status", None),
  756. isc.config.ccsession.create_answer(
  757. 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
  758. self.stats_httpd.running = True
  759. self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
  760. isc.config.ccsession.create_answer(0))
  761. self.assertFalse(self.stats_httpd.running)
  762. self.assertEqual(
  763. self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
  764. isc.config.ccsession.create_answer(
  765. 1, "Unknown command: __UNKNOWN_COMMAND__"))
  766. def test_config(self):
  767. self.stats_httpd = MyStatsHttpd(get_availaddr())
  768. self.assertEqual(
  769. self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
  770. isc.config.ccsession.create_answer(
  771. 1, "unknown item _UNKNOWN_KEY_"))
  772. addresses = get_availaddr()
  773. self.assertEqual(
  774. self.stats_httpd.config_handler(
  775. dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
  776. isc.config.ccsession.create_answer(0))
  777. self.assertTrue("listen_on" in self.stats_httpd.config)
  778. for addr in self.stats_httpd.config["listen_on"]:
  779. self.assertTrue("address" in addr)
  780. self.assertTrue("port" in addr)
  781. self.assertTrue(addr["address"] == addresses[0])
  782. self.assertTrue(addr["port"] == addresses[1])
  783. if self.ipv6_enabled:
  784. addresses = get_availaddr("::1")
  785. self.assertEqual(
  786. self.stats_httpd.config_handler(
  787. dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
  788. isc.config.ccsession.create_answer(0))
  789. self.assertTrue("listen_on" in self.stats_httpd.config)
  790. for addr in self.stats_httpd.config["listen_on"]:
  791. self.assertTrue("address" in addr)
  792. self.assertTrue("port" in addr)
  793. self.assertTrue(addr["address"] == addresses[0])
  794. self.assertTrue(addr["port"] == addresses[1])
  795. addresses = get_availaddr()
  796. self.assertEqual(
  797. self.stats_httpd.config_handler(
  798. dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
  799. isc.config.ccsession.create_answer(0))
  800. self.assertTrue("listen_on" in self.stats_httpd.config)
  801. for addr in self.stats_httpd.config["listen_on"]:
  802. self.assertTrue("address" in addr)
  803. self.assertTrue("port" in addr)
  804. self.assertTrue(addr["address"] == addresses[0])
  805. self.assertTrue(addr["port"] == addresses[1])
  806. (ret, arg) = isc.config.ccsession.parse_answer(
  807. self.stats_httpd.config_handler(
  808. dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
  809. )
  810. self.assertEqual(ret, 1)
  811. @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
  812. def test_xml_handler(self):
  813. self.stats_httpd = MyStatsHttpd(get_availaddr())
  814. module_name = 'Dummy'
  815. stats_spec = \
  816. { module_name :
  817. [{
  818. "item_name": "foo",
  819. "item_type": "string",
  820. "item_optional": False,
  821. "item_default": "bar",
  822. "item_description": "foo is bar",
  823. "item_title": "Foo"
  824. },
  825. {
  826. "item_name": "foo2",
  827. "item_type": "list",
  828. "item_optional": False,
  829. "item_default": [
  830. {
  831. "zonename" : "test1",
  832. "queries.udp" : 1,
  833. "queries.tcp" : 2
  834. },
  835. {
  836. "zonename" : "test2",
  837. "queries.udp" : 3,
  838. "queries.tcp" : 4
  839. }
  840. ],
  841. "item_title": "Foo bar",
  842. "item_description": "Foo bar",
  843. "list_item_spec": {
  844. "item_name": "foo2-1",
  845. "item_type": "map",
  846. "item_optional": False,
  847. "item_default": {},
  848. "map_item_spec": [
  849. {
  850. "item_name": "foo2-1-1",
  851. "item_type": "string",
  852. "item_optional": False,
  853. "item_default": "",
  854. "item_title": "Foo2 1 1",
  855. "item_description": "Foo bar"
  856. },
  857. {
  858. "item_name": "foo2-1-2",
  859. "item_type": "integer",
  860. "item_optional": False,
  861. "item_default": 0,
  862. "item_title": "Foo2 1 2",
  863. "item_description": "Foo bar"
  864. },
  865. {
  866. "item_name": "foo2-1-3",
  867. "item_type": "integer",
  868. "item_optional": False,
  869. "item_default": 0,
  870. "item_title": "Foo2 1 3",
  871. "item_description": "Foo bar"
  872. }
  873. ]
  874. }
  875. }]
  876. }
  877. stats_data = \
  878. { module_name : { 'foo':'bar',
  879. 'foo2': [
  880. {
  881. "foo2-1-1" : "bar1",
  882. "foo2-1-2" : 10,
  883. "foo2-1-3" : 9
  884. },
  885. {
  886. "foo2-1-1" : "bar2",
  887. "foo2-1-2" : 8,
  888. "foo2-1-3" : 7
  889. }
  890. ] } }
  891. self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
  892. self.stats_httpd.get_stats_data = lambda x,y: stats_data
  893. xml_string = self.stats_httpd.xml_handler()
  894. stats_xml = xml.etree.ElementTree.fromstring(xml_string)
  895. schema_loc = '{%s}schemaLocation' % XMLNS_XSI
  896. self.assertEqual(stats_xml.attrib[schema_loc],
  897. stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
  898. stats_data = stats_data[module_name]
  899. stats_spec = stats_spec[module_name]
  900. names = stats_httpd.item_name_list(stats_data, '')
  901. for i in range(0, len(names)):
  902. self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
  903. value = isc.cc.data.find(stats_data, names[i])
  904. if type(value) is int:
  905. value = str(value)
  906. if type(value) is dict or type(value) is list:
  907. self.assertFalse('value' in stats_xml[i].attrib)
  908. else:
  909. self.assertEqual(value, stats_xml[i].attrib['value'])
  910. self.assertEqual(module_name, stats_xml[i].attrib['owner'])
  911. self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
  912. module_name, names[i])),
  913. stats_xml[i].attrib['uri'])
  914. spec = isc.config.find_spec_part(stats_spec, names[i])
  915. self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
  916. self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
  917. self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
  918. self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
  919. self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
  920. default = spec['item_default']
  921. if type(default) is int:
  922. default = str(default)
  923. if type(default) is dict or type(default) is list:
  924. self.assertFalse('default' in stats_xml[i].attrib)
  925. else:
  926. self.assertEqual(default, stats_xml[i].attrib['default'])
  927. self.assertFalse('item_format' in spec)
  928. self.assertFalse('format' in stats_xml[i].attrib)
  929. @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
  930. def test_xsd_handler(self):
  931. self.stats_httpd = MyStatsHttpd(get_availaddr())
  932. xsd_string = self.stats_httpd.xsd_handler()
  933. stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
  934. ns = '{%s}' % XMLNS_XSD
  935. stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
  936. self.assertEqual('item', stats_xsd.attrib['name'])
  937. stats_xsd = stats_xsd.find('%scomplexType' % ns)
  938. type_types = ('boolean', 'integer', 'real', 'string', 'map', \
  939. 'list', 'named_set', 'any')
  940. attribs = [('identifier', 'string', 'required'),
  941. ('value', 'string', 'optional'),
  942. ('owner', 'string', 'required'),
  943. ('uri', 'anyURI', 'required'),
  944. ('name', 'string', 'required'),
  945. ('type', type_types, 'required'),
  946. ('description', 'string', 'optional'),
  947. ('title', 'string', 'optional'),
  948. ('optional', 'boolean', 'optional'),
  949. ('default', 'string', 'optional'),
  950. ('format', 'string', 'optional')]
  951. for i in range(0, len(attribs)):
  952. self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
  953. if attribs[i][0] == 'type':
  954. stats_xsd_types = \
  955. stats_xsd[i].find('%ssimpleType/%srestriction' % \
  956. ((ns,)*2))
  957. for j in range(0, len(attribs[i][1])):
  958. self.assertEqual(attribs[i][1][j], \
  959. stats_xsd_types[j].attrib['value'])
  960. else:
  961. self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
  962. self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
  963. @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
  964. def test_xsl_handler(self):
  965. self.stats_httpd = MyStatsHttpd(get_availaddr())
  966. xsl_string = self.stats_httpd.xsl_handler()
  967. stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
  968. nst = '{%s}' % XMLNS_XSL
  969. nsx = '{%s}' % XMLNS_XHTML
  970. self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
  971. stats_xsl = stats_xsl[2].find('%stable' % nsx)
  972. self.assertEqual('item', stats_xsl[1].attrib['select'])
  973. stats_xsl = stats_xsl[1].find('%str' % nsx)
  974. self.assertEqual('@uri', stats_xsl[0].find(
  975. '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
  976. self.assertEqual('@identifier', stats_xsl[0].find(
  977. '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
  978. self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
  979. self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
  980. self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
  981. self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
  982. def test_for_without_B10_FROM_SOURCE(self):
  983. # just lets it go through the code without B10_FROM_SOURCE env
  984. # variable
  985. if "B10_FROM_SOURCE" in os.environ:
  986. tmppath = os.environ["B10_FROM_SOURCE"]
  987. os.environ.pop("B10_FROM_SOURCE")
  988. imp.reload(stats_httpd)
  989. os.environ["B10_FROM_SOURCE"] = tmppath
  990. imp.reload(stats_httpd)
  991. if __name__ == "__main__":
  992. isc.log.resetUnitTestRootLogger()
  993. unittest.main()