b10-stats-httpd_test.py 48 KB

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