stats_test.py 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429
  1. # Copyright (C) 2010-2013 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 module in a close
  20. to real environment.
  21. """
  22. import unittest
  23. import os
  24. import io
  25. import time
  26. import imp
  27. import sys
  28. import stats
  29. import isc.log
  30. from test_utils import MyStats
  31. from isc.config.ccsession import create_answer
  32. class TestUtilties(unittest.TestCase):
  33. items = [
  34. { 'item_name': 'test_int1', 'item_type': 'integer', 'item_default': 12345 },
  35. { 'item_name': 'test_real1', 'item_type': 'real', 'item_default': 12345.6789 },
  36. { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True },
  37. { 'item_name': 'test_str1', 'item_type': 'string', 'item_default': 'ABCD' },
  38. { 'item_name': 'test_list1', 'item_type': 'list', 'item_default': [1,2,3],
  39. 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
  40. { 'item_name': 'test_map1', 'item_type': 'map', 'item_default': {'a':1,'b':2,'c':3},
  41. 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'integer'},
  42. { 'item_name': 'b', 'item_type': 'integer'},
  43. { 'item_name': 'c', 'item_type': 'integer'} ] },
  44. { 'item_name': 'test_int2', 'item_type': 'integer' },
  45. { 'item_name': 'test_real2', 'item_type': 'real' },
  46. { 'item_name': 'test_bool2', 'item_type': 'boolean' },
  47. { 'item_name': 'test_str2', 'item_type': 'string' },
  48. { 'item_name': 'test_list2', 'item_type': 'list',
  49. 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
  50. { 'item_name': 'test_map2', 'item_type': 'map',
  51. 'map_item_spec' : [ { 'item_name': 'A', 'item_type': 'integer'},
  52. { 'item_name': 'B', 'item_type': 'integer'},
  53. { 'item_name': 'C', 'item_type': 'integer'} ] },
  54. { 'item_name': 'test_none', 'item_type': 'none' },
  55. { 'item_name': 'test_list3', 'item_type': 'list', 'item_default': ["one","two","three"],
  56. 'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
  57. { 'item_name': 'test_map3', 'item_type': 'map', 'item_default': {'a':'one','b':'two','c':'three'},
  58. 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'string'},
  59. { 'item_name': 'b', 'item_type': 'string'},
  60. { 'item_name': 'c', 'item_type': 'string'} ] },
  61. {
  62. 'item_name': 'test_named_set',
  63. 'item_type': 'named_set',
  64. 'item_default': { },
  65. 'named_set_item_spec': {
  66. 'item_name': 'name',
  67. 'item_type': 'map',
  68. 'item_default': { },
  69. 'map_item_spec': [
  70. {
  71. 'item_name': 'number1',
  72. 'item_type': 'integer'
  73. },
  74. {
  75. 'item_name': 'number2',
  76. 'item_type': 'integer'
  77. }
  78. ]
  79. }
  80. }
  81. ]
  82. def setUp(self):
  83. self.const_timestamp = 1308730448.965706
  84. self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
  85. self.const_datetime = '2011-06-22T08:14:08Z'
  86. self.__orig_time = stats.time
  87. self.__orig_gmtime = stats.gmtime
  88. stats.time = lambda : self.const_timestamp
  89. stats.gmtime = lambda : self.const_timetuple
  90. def tearDown(self):
  91. stats.time = self.__orig_time
  92. stats.gmtime = self.__orig_gmtime
  93. def test_get_spec_defaults(self):
  94. self.assertEqual(
  95. stats.get_spec_defaults(self.items), {
  96. 'test_int1' : 12345 ,
  97. 'test_real1' : 12345.6789 ,
  98. 'test_bool1' : True ,
  99. 'test_str1' : 'ABCD' ,
  100. 'test_list1' : [1,2,3] ,
  101. 'test_map1' : {'a':1,'b':2,'c':3},
  102. 'test_int2' : 0 ,
  103. 'test_real2' : 0.0,
  104. 'test_bool2' : False,
  105. 'test_str2' : "",
  106. 'test_list2' : [0],
  107. 'test_map2' : { 'A' : 0, 'B' : 0, 'C' : 0 },
  108. 'test_none' : None,
  109. 'test_list3' : [ "one", "two", "three" ],
  110. 'test_map3' : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
  111. 'test_named_set' : {} })
  112. self.assertEqual(stats.get_spec_defaults(None), {})
  113. self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
  114. def test_get_timestamp(self):
  115. self.assertEqual(stats.get_timestamp(), self.const_timestamp)
  116. def test_get_datetime(self):
  117. self.assertEqual(stats.get_datetime(), self.const_datetime)
  118. self.assertNotEqual(stats.get_datetime(
  119. (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
  120. def test__accum(self):
  121. self.assertEqual(stats._accum(None, None), None)
  122. self.assertEqual(stats._accum(None, "b"), "b")
  123. self.assertEqual(stats._accum("a", None), "a")
  124. self.assertEqual(stats._accum(1, 2), 3)
  125. self.assertEqual(stats._accum(0.5, 0.3), 0.8)
  126. self.assertEqual(stats._accum(1, 0.3), 1.3)
  127. self.assertEqual(stats._accum(0.5, 2), 2.5)
  128. self.assertEqual(stats._accum('aa','bb'), 'bb')
  129. self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
  130. '2012-08-09T09:33:31Z')
  131. self.assertEqual(stats._accum(
  132. [1, 2, 3], [4, 5]), [5, 7, 3])
  133. self.assertEqual(stats._accum(
  134. [4, 5], [1, 2, 3]), [5, 7, 3])
  135. self.assertEqual(stats._accum(
  136. [1, 2, 3], [None, 5, 6]), [1, 7, 9])
  137. self.assertEqual(stats._accum(
  138. [None, 5, 6], [1, 2, 3]), [1, 7, 9])
  139. self.assertEqual(stats._accum(
  140. [1, 2, 3], [None, None, None, None]), [1,2,3,None])
  141. self.assertEqual(stats._accum(
  142. [[1,2],3],[[],5,6]), [[1,2],8,6])
  143. self.assertEqual(stats._accum(
  144. {'one': 1, 'two': 2, 'three': 3},
  145. {'one': 4, 'two': 5}),
  146. {'one': 5, 'two': 7, 'three': 3})
  147. self.assertEqual(stats._accum(
  148. {'one': 1, 'two': 2, 'three': 3},
  149. {'four': 4, 'five': 5}),
  150. {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
  151. self.assertEqual(stats._accum(
  152. {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
  153. {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
  154. {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
  155. self.assertEqual(stats._accum(
  156. [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
  157. [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
  158. [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
  159. def test_merge_oldnre(self):
  160. self.assertEqual(stats.merge_oldnew(1, 2), 2)
  161. self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
  162. self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
  163. self.assertEqual(stats.merge_oldnew(
  164. [1, 2, 3], [4, 5]), [4, 5, 3])
  165. self.assertEqual(stats.merge_oldnew(
  166. [4, 5], [1, 2, 3]), [1, 2, 3])
  167. self.assertEqual(stats.merge_oldnew(
  168. [1, 2, 3], [None, 5, 6]), [None, 5, 6])
  169. self.assertEqual(stats.merge_oldnew(
  170. [None, 5, 6], [1, 2, 3]), [1, 2, 3])
  171. self.assertEqual(stats.merge_oldnew(
  172. [1, 2, 3], [None, None, None, None]), [None, None, None, None])
  173. self.assertEqual(stats.merge_oldnew(
  174. [[1,2],3],[[],5,6]), [[1,2],5,6])
  175. self.assertEqual(stats.merge_oldnew(
  176. {'one': 1, 'two': 2, 'three': 3},
  177. {'one': 4, 'two': 5}),
  178. {'one': 4, 'two': 5, 'three': 3})
  179. self.assertEqual(stats.merge_oldnew(
  180. {'one': 1, 'two': 2, 'three': 3},
  181. {'four': 4, 'five': 5}),
  182. {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
  183. self.assertEqual(stats.merge_oldnew(
  184. {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
  185. {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
  186. {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
  187. self.assertEqual(stats.merge_oldnew(
  188. [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
  189. [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
  190. [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
  191. class TestCallback(unittest.TestCase):
  192. def setUp(self):
  193. self.dummy_func = lambda *x, **y : (x, y)
  194. self.dummy_args = (1,2,3)
  195. self.dummy_kwargs = {'a':1,'b':2,'c':3}
  196. self.cback1 = stats.Callback(
  197. command=self.dummy_func,
  198. args=self.dummy_args,
  199. kwargs=self.dummy_kwargs
  200. )
  201. self.cback2 = stats.Callback(
  202. args=self.dummy_args,
  203. kwargs=self.dummy_kwargs
  204. )
  205. self.cback3 = stats.Callback(
  206. command=self.dummy_func,
  207. kwargs=self.dummy_kwargs
  208. )
  209. self.cback4 = stats.Callback(
  210. command=self.dummy_func,
  211. args=self.dummy_args
  212. )
  213. def test_init(self):
  214. self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
  215. (self.dummy_func, self.dummy_args, self.dummy_kwargs))
  216. self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
  217. (None, self.dummy_args, self.dummy_kwargs))
  218. self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
  219. (self.dummy_func, (), self.dummy_kwargs))
  220. self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
  221. (self.dummy_func, self.dummy_args, {}))
  222. def test_call(self):
  223. self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
  224. self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
  225. self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
  226. self.assertEqual(self.cback2(), None)
  227. self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
  228. self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
  229. self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
  230. self.assertEqual(self.cback4(), (self.dummy_args, {}))
  231. self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
  232. self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
  233. class TestStats(unittest.TestCase):
  234. def setUp(self):
  235. # set the signal handler for deadlock
  236. self.const_timestamp = 1308730448.965706
  237. self.const_datetime = '2011-06-22T08:14:08Z'
  238. self.const_default_datetime = '1970-01-01T00:00:00Z'
  239. # Record original module-defined functions in case we replace them
  240. self.__orig_timestamp = stats.get_timestamp
  241. self.__orig_get_datetime = stats.get_datetime
  242. def tearDown(self):
  243. # restore the stored original function in case we replaced them
  244. stats.get_timestamp = self.__orig_timestamp
  245. stats.get_datetime = self.__orig_get_datetime
  246. def test_init(self):
  247. self.stats = MyStats()
  248. self.assertEqual(self.stats.module_name, 'Stats')
  249. self.assertFalse(self.stats.running)
  250. self.assertTrue('command_show' in self.stats.callbacks)
  251. self.assertTrue('command_status' in self.stats.callbacks)
  252. self.assertTrue('command_shutdown' in self.stats.callbacks)
  253. self.assertTrue('command_show' in self.stats.callbacks)
  254. self.assertTrue('command_showschema' in self.stats.callbacks)
  255. self.assertEqual(self.stats.config['poll-interval'], 60)
  256. def test_init_undefcmd(self):
  257. spec_str = """\
  258. {
  259. "module_spec": {
  260. "module_name": "Stats",
  261. "module_description": "Stats daemon",
  262. "config_data": [],
  263. "commands": [
  264. {
  265. "command_name": "_undef_command_",
  266. "command_description": "a undefined command in stats",
  267. "command_args": []
  268. }
  269. ],
  270. "statistics": []
  271. }
  272. }
  273. """
  274. orig_spec_location = stats.SPECFILE_LOCATION
  275. stats.SPECFILE_LOCATION = io.StringIO(spec_str)
  276. self.assertRaises(stats.StatsError, MyStats)
  277. stats.SPECFILE_LOCATION = orig_spec_location
  278. def __send_command(self, stats, command_name, params=None):
  279. '''Emulate a command arriving to stats by directly calling callback'''
  280. return isc.config.ccsession.parse_answer(
  281. stats.command_handler(command_name, params))
  282. def test_start(self):
  283. # Define a separate exception class so we can be sure that's actually
  284. # the one raised in __check_start() below
  285. class CheckException(Exception):
  286. pass
  287. def __check_start(tested_stats):
  288. self.assertTrue(tested_stats.running)
  289. raise CheckException # terminate the loop
  290. # start without err
  291. self.stats = MyStats()
  292. self.assertFalse(self.stats.running)
  293. self.stats._check_command = lambda: __check_start(self.stats)
  294. # We are going to confirm start() will set running to True, avoiding
  295. # to fall into a loop with the exception trick.
  296. self.assertRaises(CheckException, self.stats.start)
  297. self.assertEqual(self.__send_command(self.stats, "status"),
  298. (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
  299. def test_shutdown(self):
  300. def __check_shutdown(tested_stats):
  301. self.assertTrue(tested_stats.running)
  302. self.assertEqual(self.__send_command(tested_stats, "shutdown"),
  303. (0, None))
  304. self.assertFalse(tested_stats.running)
  305. # override get_interval() so it won't go poll statistics
  306. tested_stats.get_interval = lambda : 0
  307. self.stats = MyStats()
  308. self.stats._check_command = lambda: __check_shutdown(self.stats)
  309. self.stats.start()
  310. self.assertTrue(self.stats.mccs.stopped)
  311. def test_handlers(self):
  312. """Test command_handler"""
  313. __stats = MyStats()
  314. # 'show' command. We're going to check the expected methods are
  315. # called in the expected order, and check the resulting response.
  316. # Details of each method are tested separately.
  317. call_log = []
  318. def __steal_method(fn_name, *arg):
  319. call_log.append((fn_name, arg))
  320. if fn_name == 'update_stat':
  321. return False # "no error"
  322. if fn_name == 'showschema':
  323. return isc.config.create_answer(0, 'no error')
  324. # Fake some methods and attributes for inspection
  325. __stats.do_polling = lambda: __steal_method('polling')
  326. __stats.update_statistics_data = \
  327. lambda x, y, z: __steal_method('update_stat', x, y, z)
  328. __stats.update_modules = lambda: __steal_method('update_module')
  329. __stats.mccs.lname = 'test lname'
  330. __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
  331. # skip initial polling
  332. stats.get_timestamp = lambda: 0
  333. __stats._lasttime_poll = 0
  334. stats.get_datetime = lambda: 42 # make the result predictable
  335. # now send the command
  336. self.assertEqual(
  337. self.__send_command(
  338. __stats, 'show',
  339. params={ 'owner' : 'Init', 'name' : 'boot_time' }),
  340. (0, {'Init': {'boot_time': self.const_datetime}}))
  341. # Check if expected methods are called
  342. self.assertEqual([('update_stat',
  343. ('Stats', 'test lname',
  344. {'timestamp': 0,
  345. 'report_time': 42})),
  346. ('update_module', ())], call_log)
  347. # Then update faked timestamp so the initial polling will happen, and
  348. # confirm that.
  349. call_log = []
  350. stats.get_timestamp = lambda: 10
  351. self.assertEqual(
  352. self.__send_command(
  353. __stats, 'show',
  354. params={ 'owner' : 'Init', 'name' : 'boot_time' }),
  355. (0, {'Init': {'boot_time': self.const_datetime}}))
  356. self.assertEqual([('polling', ()),
  357. ('update_stat',
  358. ('Stats', 'test lname',
  359. {'timestamp': 10,
  360. 'report_time': 42})),
  361. ('update_module', ())], call_log)
  362. # 'status' command. We can confirm the behavior without any fake
  363. self.assertEqual(
  364. self.__send_command(__stats, 'status'),
  365. (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
  366. # 'showschema' command. update_modules() will be called, which
  367. # (implicitly) confirms the correct method is called; further details
  368. # are tested separately.
  369. call_log = []
  370. (rcode, value) = self.__send_command(__stats, 'showschema')
  371. self.assertEqual([('update_module', ())], call_log)
  372. # Unknown command. Error should be returned
  373. self.assertEqual(
  374. self.__send_command(__stats, '__UNKNOWN__'),
  375. (1, "Unknown command: '__UNKNOWN__'"))
  376. def test_update_modules(self):
  377. """Confirm the behavior of Stats.update_modules().
  378. It checks whether the expected command is sent to ConfigManager,
  379. and whether the answer from ConfigManager is handled as expected.
  380. """
  381. def __check_rpc_call(command, group):
  382. self.assertEqual('ConfigManager', group)
  383. self.assertEqual(command,
  384. isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
  385. answer_value = {'Init': [{
  386. "item_name": "boot_time",
  387. "item_type": "string",
  388. "item_optional": False,
  389. # Use a different default so we can check it below
  390. "item_default": "2013-01-01T00:00:01Z",
  391. "item_title": "Boot time",
  392. "item_description": "dummy desc",
  393. "item_format": "date-time"
  394. }]}
  395. return answer_value
  396. self.stats = MyStats()
  397. self.stats.cc_session.rpc_call = __check_rpc_call
  398. self.stats.update_modules()
  399. # Stats is always incorporated. For others, only the ones returned
  400. # by group_recvmsg() above is available.
  401. self.assertTrue('Stats' in self.stats.modules)
  402. self.assertTrue('Init' in self.stats.modules)
  403. self.assertFalse('Dummy' in self.stats.modules)
  404. my_statistics_data = stats.get_spec_defaults(
  405. self.stats.modules['Stats'].get_statistics_spec())
  406. self.assertTrue('report_time' in my_statistics_data)
  407. self.assertTrue('boot_time' in my_statistics_data)
  408. self.assertTrue('last_update_time' in my_statistics_data)
  409. self.assertTrue('timestamp' in my_statistics_data)
  410. self.assertTrue('lname' in my_statistics_data)
  411. self.assertEqual(my_statistics_data['report_time'],
  412. self.const_default_datetime)
  413. self.assertEqual(my_statistics_data['boot_time'],
  414. self.const_default_datetime)
  415. self.assertEqual(my_statistics_data['last_update_time'],
  416. self.const_default_datetime)
  417. self.assertEqual(my_statistics_data['timestamp'], 0.0)
  418. self.assertEqual(my_statistics_data['lname'], "")
  419. my_statistics_data = stats.get_spec_defaults(
  420. self.stats.modules['Init'].get_statistics_spec())
  421. self.assertTrue('boot_time' in my_statistics_data)
  422. self.assertEqual(my_statistics_data['boot_time'],
  423. "2013-01-01T00:00:01Z")
  424. # Error case
  425. def __raise_on_rpc_call(x, y):
  426. raise isc.config.RPCError(99, 'error')
  427. orig_parse_answer = stats.isc.config.ccsession.parse_answer
  428. self.stats.cc_session.rpc_call = __raise_on_rpc_call
  429. self.assertRaises(stats.StatsError, self.stats.update_modules)
  430. def test_get_statistics_data(self):
  431. """Confirm the behavior of Stats.get_statistics_data().
  432. It should first call update_modules(), and then retrieve the requested
  433. data from statistics_data. We confirm this by fake update_modules()
  434. where we set the expected data in statistics_data.
  435. """
  436. self.stats = MyStats()
  437. def __faked_update_modules():
  438. self.stats.statistics_data = { \
  439. 'Stats': {
  440. 'report_time': self.const_default_datetime,
  441. 'boot_time': None,
  442. 'last_update_time': None,
  443. 'timestamp': 0.0,
  444. 'lname': 'dummy name'
  445. },
  446. 'Init': { 'boot_time': None }
  447. }
  448. self.stats.update_modules = __faked_update_modules
  449. my_statistics_data = self.stats.get_statistics_data()
  450. self.assertTrue('Stats' in my_statistics_data)
  451. self.assertTrue('Init' in my_statistics_data)
  452. self.assertTrue('boot_time' in my_statistics_data['Init'])
  453. my_statistics_data = self.stats.get_statistics_data(owner='Stats')
  454. self.assertTrue('Stats' in my_statistics_data)
  455. self.assertTrue('report_time' in my_statistics_data['Stats'])
  456. self.assertTrue('boot_time' in my_statistics_data['Stats'])
  457. self.assertTrue('last_update_time' in my_statistics_data['Stats'])
  458. self.assertTrue('timestamp' in my_statistics_data['Stats'])
  459. self.assertTrue('lname' in my_statistics_data['Stats'])
  460. self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
  461. owner='Foo')
  462. my_statistics_data = self.stats.get_statistics_data(
  463. owner='Stats', name='report_time')
  464. self.assertEqual(my_statistics_data['Stats']['report_time'],
  465. self.const_default_datetime)
  466. my_statistics_data = self.stats.get_statistics_data(
  467. owner='Stats', name='boot_time')
  468. self.assertTrue('boot_time' in my_statistics_data['Stats'])
  469. my_statistics_data = self.stats.get_statistics_data(
  470. owner='Stats', name='last_update_time')
  471. self.assertTrue('last_update_time' in my_statistics_data['Stats'])
  472. my_statistics_data = self.stats.get_statistics_data(
  473. owner='Stats', name='timestamp')
  474. self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
  475. my_statistics_data = self.stats.get_statistics_data(
  476. owner='Stats', name='lname')
  477. self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
  478. self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
  479. owner='Stats', name='Bar')
  480. self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
  481. owner='Foo', name='Bar')
  482. self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
  483. name='Bar')
  484. def test_update_statistics_data(self):
  485. """test for list-type statistics"""
  486. self.stats = MyStats()
  487. _test_exp1 = {
  488. 'zonename': 'test1.example',
  489. 'queries.tcp': 5,
  490. 'queries.udp': 4
  491. }
  492. _test_exp2 = {
  493. 'zonename': 'test2.example',
  494. 'queries.tcp': 3,
  495. 'queries.udp': 2
  496. }
  497. _test_exp3 = {}
  498. _test_exp4 = {
  499. 'queries.udp': 4
  500. }
  501. _test_exp5_1 = {
  502. 'queries.perzone': [
  503. { },
  504. {
  505. 'queries.udp': 9876
  506. }
  507. ]
  508. }
  509. _test_exp5_2 = {
  510. 'queries.perzone[1]/queries.udp':
  511. isc.cc.data.find(_test_exp5_1,
  512. 'queries.perzone[1]/queries.udp')
  513. }
  514. # Success cases
  515. self.assertEqual(self.stats.statistics_data['Stats']['lname'],
  516. self.stats.cc_session.lname)
  517. self.stats.update_statistics_data(
  518. 'Stats', self.stats.cc_session.lname,
  519. {'lname': 'foo@bar'})
  520. self.assertEqual(self.stats.statistics_data['Stats']['lname'],
  521. 'foo@bar')
  522. self.assertIsNone(self.stats.update_statistics_data(
  523. 'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
  524. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  525. ['foo1']['queries.perzone'],\
  526. [_test_exp1])
  527. self.assertIsNone(self.stats.update_statistics_data(
  528. 'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
  529. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  530. ['foo1']['queries.perzone'],\
  531. [_test_exp2])
  532. self.assertIsNone(self.stats.update_statistics_data(
  533. 'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
  534. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  535. ['foo1']['queries.perzone'],
  536. [_test_exp1,_test_exp2])
  537. # differential update
  538. self.assertIsNone(self.stats.update_statistics_data(
  539. 'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
  540. _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
  541. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  542. ['foo1']['queries.perzone'], \
  543. [_test_exp1,_new_data])
  544. self.assertIsNone(self.stats.update_statistics_data(
  545. 'Auth', 'foo1', _test_exp5_2))
  546. _new_data = stats.merge_oldnew(_new_data,
  547. _test_exp5_1['queries.perzone'][1])
  548. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  549. ['foo1']['queries.perzone'], \
  550. [_test_exp1,_new_data])
  551. # Error cases
  552. self.assertEqual(self.stats.update_statistics_data('Stats', None,
  553. {'lname': 0.0}),
  554. ['0.0 should be a string'])
  555. self.assertEqual(self.stats.update_statistics_data('Dummy', None,
  556. {'foo': 'bar'}),
  557. ['unknown module name: Dummy'])
  558. self.assertEqual(self.stats.update_statistics_data(
  559. 'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
  560. def test_update_statistics_data_pt2(self):
  561. """test for named_set-type statistics"""
  562. self.stats = MyStats()
  563. _test_exp1 = \
  564. { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
  565. _test_exp2 = \
  566. { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
  567. _test_exp3 = {}
  568. _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
  569. _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
  570. _test_exp5_2 ={
  571. 'nds_queries.perzone/test10.example/queries.udp':
  572. isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
  573. }
  574. _test_exp6 = { 'foo/bar': 'brabra' }
  575. _test_exp7 = { 'foo[100]': 'bar' }
  576. # Success cases
  577. self.assertIsNone(self.stats.update_statistics_data(
  578. 'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
  579. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  580. ['foo1']['nds_queries.perzone'],\
  581. _test_exp1)
  582. self.assertIsNone(self.stats.update_statistics_data(
  583. 'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
  584. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  585. ['foo1']['nds_queries.perzone'],\
  586. dict(_test_exp1,**_test_exp2))
  587. self.assertIsNone(self.stats.update_statistics_data(
  588. 'Auth', 'foo1', {'nds_queries.perzone':
  589. dict(_test_exp1, **_test_exp2)}))
  590. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  591. ['foo1']['nds_queries.perzone'],
  592. dict(_test_exp1, **_test_exp2))
  593. # differential update
  594. self.assertIsNone(self.stats.update_statistics_data(
  595. 'Auth', 'foo1', {'nds_queries.perzone':
  596. dict(_test_exp3, **_test_exp4)}))
  597. _new_val = dict(_test_exp1,
  598. **stats.merge_oldnew(_test_exp2,_test_exp4))
  599. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  600. ['foo1']['nds_queries.perzone'],\
  601. _new_val)
  602. self.assertIsNone(self.stats.update_statistics_data(
  603. 'Auth', 'foo1', _test_exp5_2))
  604. _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
  605. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  606. ['foo1']['nds_queries.perzone'],\
  607. _new_val)
  608. self.assertIsNone(self.stats.update_statistics_data(
  609. 'Auth', 'foo2', _test_exp5_2))
  610. _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
  611. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  612. ['foo2']['nds_queries.perzone'],\
  613. _test_exp5_1)
  614. # Error cases
  615. self.assertEqual(self.stats.update_statistics_data(
  616. 'Auth', 'foo1', {'nds_queries.perzone': None}),
  617. ['None should be a map'])
  618. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  619. ['foo1']['nds_queries.perzone'],\
  620. _new_val)
  621. self.assertEqual(self.stats.update_statistics_data(
  622. 'Auth', 'foo1', _test_exp6), ['unknown item foo'])
  623. self.assertEqual(self.stats.statistics_data_bymid['Auth']\
  624. ['foo1']['nds_queries.perzone'],\
  625. _new_val)
  626. self.assertEqual(self.stats.update_statistics_data(
  627. 'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
  628. self.assertEqual(self.stats.update_statistics_data(
  629. 'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
  630. def test_update_statistics_data_withmid(self):
  631. self.stats = MyStats()
  632. # This test relies on existing statistics data at the Stats object.
  633. # This version of test prepares the data using the do_polling() method;
  634. # that's a bad practice because a unittest for a method
  635. # (update_statistics_data) would heavily depend on details of another
  636. # method (do_polling). However, there's currently no direct test
  637. # for do_polling (which is also bad), so we still keep that approach,
  638. # partly for testing do_polling indirectly. #2781 should provide
  639. # direct test for do_polling, with which this test scenario should
  640. # also be changed to be more stand-alone.
  641. # We use the knowledge of what kind of messages are sent via
  642. # do_polling, and return the following faked answer directly.
  643. self.stats._answers = [
  644. # Answer for "show_processes"
  645. (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
  646. [1035, 'b10-auth-2', 'Auth']]), None),
  647. # Answers for "getstats". 2 for Auth instances and 1 for Init.
  648. # we return some bogus values for Init, but the rest of the test
  649. # doesn't need it, so it's okay.
  650. (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
  651. (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
  652. (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
  653. ]
  654. # do_polling calls update_modules internally; in our scenario there's
  655. # no change in modules, so we make it no-op.
  656. self.stats.update_modules = lambda: None
  657. # Now call do_polling.
  658. self.stats.do_polling()
  659. # samples of query number
  660. bar1_tcp = 1001
  661. bar2_tcp = 2001
  662. bar3_tcp = 1002
  663. bar3_udp = 1003
  664. # two auth instances invoked, so we double the pre-set stat values
  665. sum_qtcp = self.stats._queries_tcp * 2
  666. sum_qudp = self.stats._queries_udp * 2
  667. self.stats.update_statistics_data('Auth', "bar1@foo",
  668. {'queries.tcp': bar1_tcp})
  669. self.assertTrue('Auth' in self.stats.statistics_data)
  670. self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
  671. self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
  672. bar1_tcp + sum_qtcp)
  673. self.assertTrue('Auth' in self.stats.statistics_data_bymid)
  674. self.assertTrue('bar1@foo' in self.stats.statistics_data_bymid['Auth'])
  675. self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
  676. ['Auth']['bar1@foo'])
  677. self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1@foo'],
  678. {'queries.tcp': bar1_tcp})
  679. # check consolidation of statistics data even if there is
  680. # non-existent mid of Auth
  681. self.stats.update_statistics_data('Auth', "bar2@foo",
  682. {'queries.tcp': bar2_tcp})
  683. self.assertTrue('Auth' in self.stats.statistics_data)
  684. self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
  685. self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
  686. bar1_tcp + bar2_tcp + sum_qtcp)
  687. self.assertTrue('Auth' in self.stats.statistics_data_bymid)
  688. self.assertTrue('bar1@foo' in self.stats.statistics_data_bymid['Auth'])
  689. self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1@foo'])
  690. self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1@foo'],
  691. {'queries.tcp': bar1_tcp})
  692. self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2@foo'],
  693. {'queries.tcp': bar2_tcp})
  694. # kill running Auth but the statistics data doesn't change
  695. self.stats.update_statistics_data()
  696. self.assertTrue('Auth' in self.stats.statistics_data)
  697. self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
  698. self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
  699. self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
  700. bar1_tcp + bar2_tcp + sum_qtcp)
  701. self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
  702. sum_qudp)
  703. self.assertTrue('Auth' in self.stats.statistics_data_bymid)
  704. # restore statistics data of killed auth
  705. self.stats.update_statistics_data('Auth',
  706. "bar1@foo",
  707. {'queries.tcp': bar1_tcp})
  708. # set another mid of Auth
  709. self.stats.update_statistics_data('Auth',
  710. "bar3@foo",
  711. {'queries.tcp':bar3_tcp,
  712. 'queries.udp':bar3_udp})
  713. self.assertTrue('Auth' in self.stats.statistics_data)
  714. self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
  715. self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
  716. self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
  717. bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
  718. self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
  719. bar3_udp + sum_qudp)
  720. self.assertTrue('Auth' in self.stats.statistics_data_bymid)
  721. self.assertTrue('bar1@foo' in self.stats.statistics_data_bymid['Auth'])
  722. self.assertTrue('bar3@foo' in self.stats.statistics_data_bymid['Auth'])
  723. self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1@foo'])
  724. self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3@foo'])
  725. self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3@foo'])
  726. self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1@foo']['queries.tcp'], bar1_tcp)
  727. self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3@foo']['queries.tcp'], bar3_tcp)
  728. self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3@foo']['queries.udp'], bar3_udp)
  729. def test_config(self):
  730. orig_get_timestamp = stats.get_timestamp
  731. stats.get_timestamp = lambda : self.const_timestamp
  732. stat = MyStats()
  733. # test updating poll-interval
  734. self.assertEqual(stat.config['poll-interval'], 60)
  735. self.assertEqual(stat.get_interval(), 60)
  736. self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
  737. self.assertEqual(stat.config_handler({'poll-interval': 120}),
  738. isc.config.create_answer(0))
  739. self.assertEqual(stat.config['poll-interval'], 120)
  740. self.assertEqual(stat.get_interval(), 120)
  741. self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
  742. stats.get_timestamp = orig_get_timestamp
  743. self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
  744. isc.config.create_answer(1, 'foo should be an integer'))
  745. self.assertEqual(stat.config_handler({'poll-interval': -1}),
  746. isc.config.create_answer(1, 'Negative integer ignored'))
  747. # unknown item
  748. self.assertEqual(
  749. stat.config_handler({'_UNKNOWN_KEY_': None}),
  750. isc.config.ccsession.create_answer(
  751. 1, "unknown item _UNKNOWN_KEY_"))
  752. # test no change if zero interval time
  753. self.assertEqual(stat.config_handler({'poll-interval': 0}),
  754. isc.config.create_answer(0))
  755. self.assertEqual(stat.config['poll-interval'], 0)
  756. # see the comment for test_update_statistics_data_withmid. We abuse
  757. # do_polling here, too. With #2781 we should make it more direct.
  758. stat._answers = [\
  759. # Answer for "show_processes"
  760. (create_answer(0, []), None),
  761. # Answers for "getstats" for Init (the other one for Auth, but
  762. # that doesn't matter for this test)
  763. (create_answer(0, stat._init_sdata), {'from': 'init'}),
  764. (create_answer(0, stat._init_sdata), {'from': 'init'})
  765. ]
  766. stat.update_modules = lambda: None
  767. self.assertEqual(
  768. self.__send_command(
  769. stat, 'show',
  770. params={ 'owner' : 'Init', 'name' : 'boot_time' }),
  771. (0, {'Init': {'boot_time': self.const_datetime}}))
  772. def test_commands(self):
  773. self.stats = MyStats()
  774. # status
  775. self.assertEqual(self.stats.command_status(),
  776. isc.config.create_answer(
  777. 0, "Stats is up. (PID " + str(os.getpid()) + ")"))
  778. # shutdown
  779. self.stats.running = True
  780. self.assertEqual(self.stats.command_shutdown(),
  781. isc.config.create_answer(0))
  782. self.assertFalse(self.stats.running)
  783. def test_command_show_error(self):
  784. self.stats = MyStats()
  785. self.assertEqual(self.stats.command_show(owner='Foo', name=None),
  786. isc.config.create_answer(
  787. 1,
  788. "specified arguments are incorrect: owner: Foo, name: None"))
  789. self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
  790. isc.config.create_answer(
  791. 1,
  792. "specified arguments are incorrect: owner: Foo, name: _bar_"))
  793. self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
  794. isc.config.create_answer(
  795. 1,
  796. "specified arguments are incorrect: owner: Foo, name: bar"))
  797. def test_command_show_auth(self):
  798. self.stats = MyStats()
  799. self.stats.update_modules = lambda: None
  800. # Test data borrowed from test_update_statistics_data_withmid
  801. self.stats._answers = [
  802. (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
  803. [1035, 'b10-auth-2', 'Auth']]), None),
  804. (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
  805. (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
  806. (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
  807. ]
  808. num_instances = 2
  809. sum_qtcp = 0
  810. sum_qudp = 0
  811. sum_qtcp_perzone1 = 0
  812. sum_qudp_perzone1 = 0
  813. sum_qtcp_perzone2 = 4 * num_instances
  814. sum_qudp_perzone2 = 3 * num_instances
  815. sum_qtcp_nds_perzone10 = 0
  816. sum_qudp_nds_perzone10 = 0
  817. sum_qtcp_nds_perzone20 = 4 * num_instances
  818. sum_qudp_nds_perzone20 = 3 * num_instances
  819. self.maxDiff = None
  820. for a in (0, num_instances):
  821. sum_qtcp += self.stats._queries_tcp
  822. sum_qudp += self.stats._queries_udp
  823. sum_qtcp_perzone1 += self.stats._queries_per_zone[0]['queries.tcp']
  824. sum_qudp_perzone1 += self.stats._queries_per_zone[0]['queries.udp']
  825. sum_qtcp_nds_perzone10 += \
  826. self.stats._nds_queries_per_zone['test10.example']['queries.tcp']
  827. sum_qudp_nds_perzone10 += \
  828. self.stats._nds_queries_per_zone['test10.example']['queries.udp']
  829. self.assertEqual(self.stats.command_show(owner='Auth'),
  830. isc.config.create_answer(
  831. 0, {'Auth':{ 'queries.udp': sum_qudp,
  832. 'queries.tcp': sum_qtcp,
  833. 'queries.perzone': [{ 'zonename': 'test1.example',
  834. 'queries.udp': sum_qudp_perzone1,
  835. 'queries.tcp': sum_qtcp_perzone1 },
  836. { 'zonename': 'test2.example',
  837. 'queries.udp': sum_qudp_perzone2,
  838. 'queries.tcp': sum_qtcp_perzone2 }
  839. ],
  840. 'nds_queries.perzone': { 'test10.example' : {
  841. 'queries.udp': sum_qudp_nds_perzone10,
  842. 'queries.tcp': sum_qtcp_nds_perzone10 },
  843. 'test20.example' : {
  844. 'queries.udp': sum_qudp_nds_perzone20,
  845. 'queries.tcp': sum_qtcp_nds_perzone20 }
  846. }}}))
  847. self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
  848. isc.config.create_answer(
  849. 0, {'Auth': {'queries.udp': sum_qudp}}))
  850. self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
  851. isc.config.create_answer(
  852. 0, {'Auth': {'queries.perzone': [
  853. { 'zonename': 'test1.example',
  854. 'queries.udp': sum_qudp_perzone1,
  855. 'queries.tcp': sum_qtcp_perzone1 },
  856. { 'zonename': 'test2.example',
  857. 'queries.udp': sum_qudp_perzone2,
  858. 'queries.tcp': sum_qtcp_perzone2 }]}}))
  859. self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
  860. isc.config.create_answer(
  861. 0, {'Auth': {'nds_queries.perzone': {
  862. 'test10.example': {
  863. 'queries.udp': sum_qudp_nds_perzone10,
  864. 'queries.tcp': sum_qtcp_nds_perzone10 },
  865. 'test20.example': {
  866. 'queries.udp': sum_qudp_nds_perzone20,
  867. 'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
  868. def test_command_show_stats(self):
  869. self.stats = MyStats()
  870. orig_get_datetime = stats.get_datetime
  871. orig_get_timestamp = stats.get_timestamp
  872. stats.get_datetime = lambda x=None: self.const_datetime
  873. stats.get_timestamp = lambda : self.const_timestamp
  874. self.assertEqual(self.stats.command_show(owner='Stats',
  875. name='report_time'),
  876. isc.config.create_answer(
  877. 0, {'Stats': {'report_time':self.const_datetime}}))
  878. self.assertEqual(self.stats.command_show(owner='Stats',
  879. name='timestamp'),
  880. isc.config.create_answer(
  881. 0, {'Stats': {'timestamp':self.const_timestamp}}))
  882. stats.get_datetime = orig_get_datetime
  883. stats.get_timestamp = orig_get_timestamp
  884. self.stats.do_polling = lambda : None
  885. self.stats.modules[self.stats.module_name] = \
  886. isc.config.module_spec.ModuleSpec(
  887. { "module_name": self.stats.module_name, "statistics": [] } )
  888. self.assertRaises(
  889. stats.StatsError, self.stats.command_show,
  890. owner=self.stats.module_name, name='bar')
  891. def test_command_showchema(self):
  892. self.stats = MyStats()
  893. (rcode, value) = isc.config.ccsession.parse_answer(
  894. self.stats.command_showschema())
  895. self.assertEqual(rcode, 0)
  896. self.assertEqual(len(value), 3)
  897. self.assertTrue('Stats' in value)
  898. self.assertTrue('Init' in value)
  899. self.assertTrue('Auth' in value)
  900. self.assertFalse('__Dummy__' in value)
  901. schema = value['Stats']
  902. self.assertEqual(len(schema), 5)
  903. for item in schema:
  904. self.assertTrue(len(item) == 6 or len(item) == 7)
  905. self.assertTrue('item_name' in item)
  906. self.assertTrue('item_type' in item)
  907. self.assertTrue('item_optional' in item)
  908. self.assertTrue('item_default' in item)
  909. self.assertTrue('item_title' in item)
  910. self.assertTrue('item_description' in item)
  911. if len(item) == 7:
  912. self.assertTrue('item_format' in item)
  913. schema = value['Init']
  914. self.assertEqual(len(schema), 1)
  915. for item in schema:
  916. self.assertTrue(len(item) == 7)
  917. self.assertTrue('item_name' in item)
  918. self.assertTrue('item_type' in item)
  919. self.assertTrue('item_optional' in item)
  920. self.assertTrue('item_default' in item)
  921. self.assertTrue('item_title' in item)
  922. self.assertTrue('item_description' in item)
  923. self.assertTrue('item_format' in item)
  924. schema = value['Auth']
  925. self.assertEqual(len(schema), 4)
  926. for item in schema:
  927. if item['item_type'] == 'list' or item['item_type'] == 'named_set':
  928. self.assertEqual(len(item), 7)
  929. else:
  930. self.assertEqual(len(item), 6)
  931. self.assertTrue('item_name' in item)
  932. self.assertTrue('item_type' in item)
  933. self.assertTrue('item_optional' in item)
  934. self.assertTrue('item_default' in item)
  935. self.assertTrue('item_title' in item)
  936. self.assertTrue('item_description' in item)
  937. (rcode, value) = isc.config.ccsession.parse_answer(
  938. self.stats.command_showschema(owner='Stats'))
  939. self.assertEqual(rcode, 0)
  940. self.assertTrue('Stats' in value)
  941. self.assertFalse('Init' in value)
  942. self.assertFalse('Auth' in value)
  943. for item in value['Stats']:
  944. self.assertTrue(len(item) == 6 or len(item) == 7)
  945. self.assertTrue('item_name' in item)
  946. self.assertTrue('item_type' in item)
  947. self.assertTrue('item_optional' in item)
  948. self.assertTrue('item_default' in item)
  949. self.assertTrue('item_title' in item)
  950. self.assertTrue('item_description' in item)
  951. if len(item) == 7:
  952. self.assertTrue('item_format' in item)
  953. (rcode, value) = isc.config.ccsession.parse_answer(
  954. self.stats.command_showschema(owner='Stats', name='report_time'))
  955. self.assertEqual(rcode, 0)
  956. self.assertTrue('Stats' in value)
  957. self.assertFalse('Init' in value)
  958. self.assertFalse('Auth' in value)
  959. self.assertEqual(len(value['Stats'][0]), 7)
  960. self.assertTrue('item_name' in value['Stats'][0])
  961. self.assertTrue('item_type' in value['Stats'][0])
  962. self.assertTrue('item_optional' in value['Stats'][0])
  963. self.assertTrue('item_default' in value['Stats'][0])
  964. self.assertTrue('item_title' in value['Stats'][0])
  965. self.assertTrue('item_description' in value['Stats'][0])
  966. self.assertTrue('item_format' in value['Stats'][0])
  967. self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
  968. self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
  969. self.assertEqual(self.stats.command_showschema(owner='Foo'),
  970. isc.config.create_answer(
  971. 1, "specified arguments are incorrect: owner: Foo, name: None"))
  972. self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
  973. isc.config.create_answer(
  974. 1, "specified arguments are incorrect: owner: Foo, name: bar"))
  975. self.assertEqual(self.stats.command_showschema(owner='Auth'),
  976. isc.config.create_answer(
  977. 0, {'Auth': [{
  978. "item_default": 0,
  979. "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
  980. "item_name": "queries.tcp",
  981. "item_optional": False,
  982. "item_title": "Queries TCP",
  983. "item_type": "integer"
  984. },
  985. {
  986. "item_default": 0,
  987. "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
  988. "item_name": "queries.udp",
  989. "item_optional": False,
  990. "item_title": "Queries UDP",
  991. "item_type": "integer"
  992. },
  993. {
  994. "item_name": "queries.perzone",
  995. "item_type": "list",
  996. "item_optional": False,
  997. "item_default": [
  998. {
  999. "zonename" : "test1.example",
  1000. "queries.udp" : 1,
  1001. "queries.tcp" : 2
  1002. },
  1003. {
  1004. "zonename" : "test2.example",
  1005. "queries.udp" : 3,
  1006. "queries.tcp" : 4
  1007. }
  1008. ],
  1009. "item_title": "Queries per zone",
  1010. "item_description": "Queries per zone",
  1011. "list_item_spec": {
  1012. "item_name": "zones",
  1013. "item_type": "map",
  1014. "item_optional": False,
  1015. "item_default": {},
  1016. "map_item_spec": [
  1017. {
  1018. "item_name": "zonename",
  1019. "item_type": "string",
  1020. "item_optional": False,
  1021. "item_default": "",
  1022. "item_title": "Zonename",
  1023. "item_description": "Zonename"
  1024. },
  1025. {
  1026. "item_name": "queries.udp",
  1027. "item_type": "integer",
  1028. "item_optional": False,
  1029. "item_default": 0,
  1030. "item_title": "Queries UDP per zone",
  1031. "item_description": "A number of UDP query counts per zone"
  1032. },
  1033. {
  1034. "item_name": "queries.tcp",
  1035. "item_type": "integer",
  1036. "item_optional": False,
  1037. "item_default": 0,
  1038. "item_title": "Queries TCP per zone",
  1039. "item_description": "A number of TCP query counts per zone"
  1040. }
  1041. ]
  1042. }
  1043. },
  1044. {
  1045. "item_name": "nds_queries.perzone",
  1046. "item_type": "named_set",
  1047. "item_optional": False,
  1048. "item_default": {
  1049. "test10.example" : {
  1050. "queries.udp" : 1,
  1051. "queries.tcp" : 2
  1052. },
  1053. "test20.example" : {
  1054. "queries.udp" : 3,
  1055. "queries.tcp" : 4
  1056. }
  1057. },
  1058. "item_title": "Queries per zone",
  1059. "item_description": "Queries per zone",
  1060. "named_set_item_spec": {
  1061. "item_name": "zonename",
  1062. "item_type": "map",
  1063. "item_optional": False,
  1064. "item_default": {},
  1065. "item_title": "Zonename",
  1066. "item_description": "Zonename",
  1067. "map_item_spec": [
  1068. {
  1069. "item_name": "queries.udp",
  1070. "item_type": "integer",
  1071. "item_optional": False,
  1072. "item_default": 0,
  1073. "item_title": "Queries UDP per zone",
  1074. "item_description": "A number of UDP query counts per zone"
  1075. },
  1076. {
  1077. "item_name": "queries.tcp",
  1078. "item_type": "integer",
  1079. "item_optional": False,
  1080. "item_default": 0,
  1081. "item_title": "Queries TCP per zone",
  1082. "item_description": "A number of TCP query counts per zone"
  1083. }
  1084. ]
  1085. }
  1086. }]}))
  1087. self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
  1088. isc.config.create_answer(
  1089. 0, {'Auth': [{
  1090. "item_default": 0,
  1091. "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
  1092. "item_name": "queries.tcp",
  1093. "item_optional": False,
  1094. "item_title": "Queries TCP",
  1095. "item_type": "integer"
  1096. }]}))
  1097. self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
  1098. isc.config.create_answer(
  1099. 0, {'Auth':[{
  1100. "item_name": "queries.perzone",
  1101. "item_type": "list",
  1102. "item_optional": False,
  1103. "item_default": [
  1104. {
  1105. "zonename" : "test1.example",
  1106. "queries.udp" : 1,
  1107. "queries.tcp" : 2
  1108. },
  1109. {
  1110. "zonename" : "test2.example",
  1111. "queries.udp" : 3,
  1112. "queries.tcp" : 4
  1113. }
  1114. ],
  1115. "item_title": "Queries per zone",
  1116. "item_description": "Queries per zone",
  1117. "list_item_spec": {
  1118. "item_name": "zones",
  1119. "item_type": "map",
  1120. "item_optional": False,
  1121. "item_default": {},
  1122. "map_item_spec": [
  1123. {
  1124. "item_name": "zonename",
  1125. "item_type": "string",
  1126. "item_optional": False,
  1127. "item_default": "",
  1128. "item_title": "Zonename",
  1129. "item_description": "Zonename"
  1130. },
  1131. {
  1132. "item_name": "queries.udp",
  1133. "item_type": "integer",
  1134. "item_optional": False,
  1135. "item_default": 0,
  1136. "item_title": "Queries UDP per zone",
  1137. "item_description": "A number of UDP query counts per zone"
  1138. },
  1139. {
  1140. "item_name": "queries.tcp",
  1141. "item_type": "integer",
  1142. "item_optional": False,
  1143. "item_default": 0,
  1144. "item_title": "Queries TCP per zone",
  1145. "item_description": "A number of TCP query counts per zone"
  1146. }
  1147. ]
  1148. }
  1149. }]}))
  1150. self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
  1151. isc.config.create_answer(
  1152. 0, {'Auth':[{
  1153. "item_name": "nds_queries.perzone",
  1154. "item_type": "named_set",
  1155. "item_optional": False,
  1156. "item_default": {
  1157. "test10.example" : {
  1158. "queries.udp" : 1,
  1159. "queries.tcp" : 2
  1160. },
  1161. "test20.example" : {
  1162. "queries.udp" : 3,
  1163. "queries.tcp" : 4
  1164. }
  1165. },
  1166. "item_title": "Queries per zone",
  1167. "item_description": "Queries per zone",
  1168. "named_set_item_spec": {
  1169. "item_name": "zonename",
  1170. "item_type": "map",
  1171. "item_optional": False,
  1172. "item_default": {},
  1173. "item_title": "Zonename",
  1174. "item_description": "Zonename",
  1175. "map_item_spec": [
  1176. {
  1177. "item_name": "queries.udp",
  1178. "item_type": "integer",
  1179. "item_optional": False,
  1180. "item_default": 0,
  1181. "item_title": "Queries UDP per zone",
  1182. "item_description": "A number of UDP query counts per zone"
  1183. },
  1184. {
  1185. "item_name": "queries.tcp",
  1186. "item_type": "integer",
  1187. "item_optional": False,
  1188. "item_default": 0,
  1189. "item_title": "Queries TCP per zone",
  1190. "item_description": "A number of TCP query counts per zone"
  1191. }
  1192. ]
  1193. }
  1194. }]}))
  1195. self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
  1196. isc.config.create_answer(
  1197. 1, "specified arguments are incorrect: owner: Stats, name: bar"))
  1198. self.assertEqual(self.stats.command_showschema(name='bar'),
  1199. isc.config.create_answer(
  1200. 1, "module name is not specified"))
  1201. def test_polling_init(self):
  1202. """check statistics data of 'Init'."""
  1203. stat = MyStats()
  1204. # At this point 'stat' is initialized with statistics for Stats,
  1205. # Init and Auth modules. In this test, we only need to check for Init
  1206. # statistics, while do_polling() can ask for module statistics in an
  1207. # unpredictable order (if hash randomization is enabled, which is
  1208. # the case by default for Python 3.3). To make it predictable and
  1209. # ensure the prepared answer doesn't cause disruption, we remove the
  1210. # information for the Auth module for this test.
  1211. del stat.statistics_data['Auth']
  1212. stat.update_modules = lambda: None
  1213. stat._answers = [
  1214. # Answer for "show_processes"
  1215. (create_answer(0, []), None),
  1216. # Answers for "getstats" for Init (type of boot_time is invalid)
  1217. (create_answer(0, {'boot_time': self.const_datetime}),
  1218. {'from': 'init'}),
  1219. ]
  1220. stat.do_polling()
  1221. self.assertEqual(
  1222. stat.statistics_data_bymid['Init']['init'],
  1223. {'boot_time': self.const_datetime})
  1224. def test_polling_consolidate(self):
  1225. """check statistics data of multiple instances of same module."""
  1226. stat = MyStats()
  1227. stat.update_modules = lambda: None
  1228. # Test data borrowed from test_update_statistics_data_withmid
  1229. stat._answers = [
  1230. (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
  1231. [1035, 'b10-auth-2', 'Auth']]), None),
  1232. (create_answer(0, stat._auth_sdata), {'from': 'auth1'}),
  1233. (create_answer(0, stat._auth_sdata), {'from': 'auth2'}),
  1234. (create_answer(0, stat._auth_sdata), {'from': 'auth3'})
  1235. ]
  1236. stat.do_polling()
  1237. # check statistics data of each 'Auth' instances. expected data
  1238. # for 'nds_queries.perzone' is special as it needs data merge.
  1239. self.assertEqual(2, len(stat.statistics_data_bymid['Auth'].values()))
  1240. for s in stat.statistics_data_bymid['Auth'].values():
  1241. self.assertEqual(
  1242. s, {'queries.perzone': stat._auth_sdata['queries.perzone'],
  1243. 'nds_queries.perzone': stat._nds_queries_per_zone,
  1244. 'queries.tcp': stat._auth_sdata['queries.tcp'],
  1245. 'queries.udp': stat._auth_sdata['queries.udp']})
  1246. # check consolidation of statistics data of the auth instances.
  1247. # it's union of the reported data and the spec default.
  1248. n = len(stat.statistics_data_bymid['Auth'].values())
  1249. self.maxDiff = None
  1250. self.assertEqual(
  1251. stat.statistics_data['Auth'],
  1252. {'queries.perzone': [
  1253. {'zonename': 'test1.example',
  1254. 'queries.tcp': 5 * n,
  1255. 'queries.udp': 4 * n},
  1256. {'zonename': 'test2.example',
  1257. 'queries.tcp': 4 * n,
  1258. 'queries.udp': 3 * n},
  1259. ],
  1260. 'nds_queries.perzone': {
  1261. 'test10.example': {
  1262. 'queries.tcp': 5 * n,
  1263. 'queries.udp': 4 * n
  1264. },
  1265. 'test20.example': {
  1266. 'queries.tcp': 4 * n,
  1267. 'queries.udp': 3 * n
  1268. },
  1269. },
  1270. 'queries.tcp': 3 * n,
  1271. 'queries.udp': 2 * n})
  1272. def test_polling_stats(self):
  1273. """Check statistics data of 'Stats'
  1274. This is actually irrelevant to do_polling(), but provided to
  1275. compatibility of older tests.
  1276. """
  1277. stat = MyStats()
  1278. self.assertEqual(len(stat.statistics_data['Stats']), 5)
  1279. self.assertTrue('boot_time' in stat.statistics_data['Stats'])
  1280. self.assertTrue('last_update_time' in stat.statistics_data['Stats'])
  1281. self.assertTrue('report_time' in stat.statistics_data['Stats'])
  1282. self.assertTrue('timestamp' in stat.statistics_data['Stats'])
  1283. self.assertEqual(stat.statistics_data['Stats']['lname'],
  1284. stat.mccs._session.lname)
  1285. def test_refresh_statistics_broken_statistics_data(self):
  1286. """Test _refresh_statistics() doesn't incorporate broken statistics data
  1287. """
  1288. stat = MyStats()
  1289. # check default statistics data of 'Init'
  1290. self.assertEqual(
  1291. {'boot_time': self.const_default_datetime},
  1292. stat.statistics_data['Init'])
  1293. last_update_time = stat.statistics_data['Stats']['last_update_time']
  1294. # _refresh_statistics() should ignore the invalid statistics_data(type
  1295. # of boot_time is invalid); default data shouldn't be replaced.
  1296. arg = [('Init', 'lname', {'boot_time': 1})]
  1297. stat._refresh_statistics(arg)
  1298. self.assertEqual(
  1299. {'boot_time': self.const_default_datetime},
  1300. stat.statistics_data['Init'])
  1301. # 'last_update_time' doesn't change
  1302. self.assertEqual(
  1303. last_update_time,
  1304. stat.statistics_data['Stats']['last_update_time'])
  1305. class Z_TestOSEnv(unittest.TestCase):
  1306. # Running this test would break logging setting. To prevent it from
  1307. # affecting other tests we use the same workaround as Z_TestOSEnv in
  1308. # stats-httpd_test.py.
  1309. def test_osenv(self):
  1310. """
  1311. test for the environ variable "B10_FROM_SOURCE"
  1312. "B10_FROM_SOURCE" is set in Makefile
  1313. """
  1314. # test case having B10_FROM_SOURCE
  1315. self.assertTrue("B10_FROM_SOURCE" in os.environ)
  1316. self.assertEqual(stats.SPECFILE_LOCATION, \
  1317. os.environ["B10_FROM_SOURCE"] + os.sep + \
  1318. "src" + os.sep + "bin" + os.sep + "stats" + \
  1319. os.sep + "stats.spec")
  1320. # test case not having B10_FROM_SOURCE
  1321. path = os.environ["B10_FROM_SOURCE"]
  1322. os.environ.pop("B10_FROM_SOURCE")
  1323. self.assertFalse("B10_FROM_SOURCE" in os.environ)
  1324. # import stats again
  1325. imp.reload(stats)
  1326. # revert the changes
  1327. os.environ["B10_FROM_SOURCE"] = path
  1328. imp.reload(stats)
  1329. if __name__ == "__main__":
  1330. isc.log.resetUnitTestRootLogger()
  1331. unittest.main()