datasrc_test.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. # Copyright (C) 2011 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. import isc.log
  16. import isc.datasrc
  17. from isc.datasrc import ZoneFinder, ZoneJournalReader
  18. from isc.dns import *
  19. from isc.testutils.rrset_utils import rrsets_equal
  20. import unittest
  21. import sqlite3
  22. import os
  23. import shutil
  24. import sys
  25. import json
  26. TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
  27. TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
  28. READ_ZONE_DB_FILE = TESTDATA_PATH + "example.com.sqlite3"
  29. WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
  30. READ_ZONE_DB_CONFIG = "{ \"database_file\": \"" + READ_ZONE_DB_FILE + "\" }"
  31. WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
  32. STATIC_ZONE_CONFIG = '"' + TESTDATA_PATH + "static.zone" + '"'
  33. def add_rrset(rrset_list, name, rrclass, rrtype, ttl, rdatas):
  34. rrset_to_add = isc.dns.RRset(name, rrclass, rrtype, ttl)
  35. if rdatas is not None:
  36. for rdata in rdatas:
  37. rrset_to_add.add_rdata(isc.dns.Rdata(rrtype, rrclass, rdata))
  38. rrset_list.append(rrset_to_add)
  39. # returns true if rrset is in expected_rrsets
  40. # will remove the rrset from expected_rrsets if found
  41. def check_for_rrset(expected_rrsets, rrset):
  42. for cur_rrset in expected_rrsets[:]:
  43. if rrsets_equal(cur_rrset, rrset):
  44. expected_rrsets.remove(cur_rrset)
  45. return True
  46. return False
  47. def create_soa(serial):
  48. soa = RRset(Name('example.org'), RRClass.IN, RRType.SOA, RRTTL(3600))
  49. soa.add_rdata(Rdata(RRType.SOA, RRClass.IN,
  50. 'ns1.example.org. admin.example.org. ' +
  51. str(serial) + ' 3600 1800 2419200 7200'))
  52. return soa
  53. def test_findall_common(self, tested):
  54. """
  55. Common part of the find_all test. It tests a find_all method on the passed
  56. object.
  57. """
  58. # Some "failure" responses
  59. result, rrset, _ = tested.find_all(isc.dns.Name("www.sql1.example.com"),
  60. ZoneFinder.FIND_DEFAULT)
  61. self.assertEqual(ZoneFinder.DELEGATION, result)
  62. expected = RRset(Name('sql1.example.com.'), RRClass.IN, RRType.NS,
  63. RRTTL(3600))
  64. expected.add_rdata(Rdata(RRType.NS, RRClass.IN,
  65. 'dns01.example.com.'))
  66. expected.add_rdata(Rdata(RRType.NS, RRClass.IN,
  67. 'dns02.example.com.'))
  68. expected.add_rdata(Rdata(RRType.NS, RRClass.IN,
  69. 'dns03.example.com.'))
  70. self.assertTrue(rrsets_equal(expected, rrset))
  71. result, rrset, _ = tested.find_all(isc.dns.Name("nxdomain.example.com"),
  72. ZoneFinder.FIND_DEFAULT)
  73. self.assertEqual(ZoneFinder.NXDOMAIN, result)
  74. self.assertIsNone(None, rrset)
  75. # A success. It should return the list now.
  76. # This also tests we can ommit the options parameter
  77. result, rrsets, _ = tested.find_all(isc.dns.Name("mix.example.com."))
  78. self.assertEqual(ZoneFinder.SUCCESS, result)
  79. self.assertEqual(2, len(rrsets))
  80. rrsets.sort(key=lambda rrset: rrset.get_type().to_text())
  81. expected = [
  82. RRset(Name('mix.example.com.'), RRClass.IN, RRType.A,
  83. RRTTL(3600)),
  84. RRset(Name('mix.example.com.'), RRClass.IN, RRType.AAAA,
  85. RRTTL(3600))
  86. ]
  87. expected[0].add_rdata(Rdata(RRType.A, RRClass.IN, "192.0.2.1"))
  88. expected[0].add_rdata(Rdata(RRType.A, RRClass.IN, "192.0.2.2"))
  89. expected[1].add_rdata(Rdata(RRType.AAAA, RRClass.IN,
  90. "2001:db8::1"))
  91. expected[1].add_rdata(Rdata(RRType.AAAA, RRClass.IN,
  92. "2001:db8::2"))
  93. for (rrset, exp) in zip(rrsets, expected):
  94. self.assertTrue(rrsets_equal(exp, rrset))
  95. # Check the reference counts on them. The getrefcount returns one more,
  96. # as for the reference in its own parameter - see its docs.
  97. # Two - one for the variable, one for parameter
  98. self.assertEqual(2, sys.getrefcount(rrsets))
  99. for rrset in rrsets:
  100. # 3 - one as the element of list, one for the rrset variable
  101. # and one for the parameter.
  102. self.assertEqual(3, sys.getrefcount(rrset))
  103. class DataSrcClient(unittest.TestCase):
  104. def test_(self):
  105. # can't construct directly
  106. self.assertRaises(TypeError, isc.datasrc.ZoneIterator)
  107. self.assertRaises(TypeError, isc.datasrc.DataSourceClient, 1, "{}")
  108. self.assertRaises(TypeError, isc.datasrc.DataSourceClient, "sqlite3", 1)
  109. self.assertRaises(isc.datasrc.Error,
  110. isc.datasrc.DataSourceClient, "foo", "{}")
  111. self.assertRaises(isc.datasrc.Error,
  112. isc.datasrc.DataSourceClient, "sqlite3", "")
  113. self.assertRaises(isc.datasrc.Error,
  114. isc.datasrc.DataSourceClient, "sqlite3", "{}")
  115. self.assertRaises(isc.datasrc.Error,
  116. isc.datasrc.DataSourceClient, "sqlite3",
  117. "{ \"foo\": 1 }")
  118. def test_iterate(self):
  119. dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
  120. # for RRSIGS, the TTL's are currently modified. This test should
  121. # start failing when we fix that.
  122. rrs = dsc.get_iterator(isc.dns.Name("sql1.example.com."), True)
  123. # we do not know the order in which they are returned by the iterator
  124. # but we do want to check them, so we put all records into one list
  125. # sort it (doesn't matter which way it is sorted, as long as it is
  126. # sorted)
  127. # RRset is (atm) an unorderable type, and within an rrset, the
  128. # rdatas and rrsigs may also be in random order. In theory the
  129. # rrsets themselves can be returned in any order.
  130. #
  131. # So we create a second list with all rrsets we expect, and for each
  132. # rrset we get from the iterator, see if it is in that list, and
  133. # remove it.
  134. #
  135. # When the iterator is empty, we check no rrsets are left in the
  136. # list of expected ones
  137. expected_rrset_list = []
  138. name = isc.dns.Name("sql1.example.com")
  139. rrclass = isc.dns.RRClass.IN
  140. add_rrset(expected_rrset_list, name, rrclass,
  141. isc.dns.RRType.DNSKEY, isc.dns.RRTTL(3600),
  142. [
  143. "256 3 5 AwEAAdYdRhBAEY67R/8G1N5AjGF6asIiNh/pNGeQ8xDQP13J"+
  144. "N2lo+sNqWcmpYNhuVqRbLB+mamsU1XcCICSBvAlSmfz/ZUdafX23knAr"+
  145. "TlALxMmspcfdpqun3Yr3YYnztuj06rV7RqmveYckWvAUXVYMSMQZfJ30"+
  146. "5fs0dE/xLztL/CzZ"
  147. ])
  148. add_rrset(expected_rrset_list, name, rrclass,
  149. isc.dns.RRType.DNSKEY, isc.dns.RRTTL(3600),
  150. [
  151. "257 3 5 AwEAAbaKDSa9XEFTsjSYpUTHRotTS9Tz3krfDucugW5UokGQ"+
  152. "KC26QlyHXlPTZkC+aRFUs/dicJX2kopndLcnlNAPWiKnKtrsFSCnIJDB"+
  153. "ZIyvcKq+9RXmV3HK3bUdHnQZ88IZWBRmWKfZ6wnzHo53kdYKAemTErkz"+
  154. "taX3lRRPLYWpxRcDPEjysXT3Lh0vfL5D+CIO1yKw/q7C+v6+/kYAxc2l"+
  155. "fbNE3HpklSuF+dyX4nXxWgzbcFuLz5Bwfq6ZJ9RYe/kNkA0uMWNa1KkG"+
  156. "eRh8gg22kgD/KT5hPTnpezUWLvoY5Qc7IB3T0y4n2JIwiF2ZrZYVrWgD"+
  157. "jRWAzGsxJiJyjd6w2k0="
  158. ])
  159. add_rrset(expected_rrset_list, name, rrclass,
  160. isc.dns.RRType.NS, isc.dns.RRTTL(3600),
  161. [
  162. "dns01.example.com."
  163. ])
  164. add_rrset(expected_rrset_list, name, rrclass,
  165. isc.dns.RRType.NS, isc.dns.RRTTL(3600),
  166. [
  167. "dns02.example.com."
  168. ])
  169. add_rrset(expected_rrset_list, name, rrclass,
  170. isc.dns.RRType.NS, isc.dns.RRTTL(3600),
  171. [
  172. "dns03.example.com."
  173. ])
  174. add_rrset(expected_rrset_list, name, rrclass,
  175. isc.dns.RRType.NSEC, isc.dns.RRTTL(7200),
  176. [
  177. "www.sql1.example.com. NS SOA RRSIG NSEC DNSKEY"
  178. ])
  179. # For RRSIGS, we can't add the fake data through the API, so we
  180. # simply pass no rdata at all (which is skipped by the check later)
  181. # Since we passed separate_rrs = True to get_iterator, we get several
  182. # sets of RRSIGs, one for each TTL
  183. add_rrset(expected_rrset_list, name, rrclass,
  184. isc.dns.RRType.RRSIG, isc.dns.RRTTL(3600), None)
  185. add_rrset(expected_rrset_list, name, rrclass,
  186. isc.dns.RRType.RRSIG, isc.dns.RRTTL(3600), None)
  187. add_rrset(expected_rrset_list, name, rrclass,
  188. isc.dns.RRType.RRSIG, isc.dns.RRTTL(3600), None)
  189. add_rrset(expected_rrset_list, name, rrclass,
  190. isc.dns.RRType.RRSIG, isc.dns.RRTTL(3600), None)
  191. add_rrset(expected_rrset_list, name, rrclass,
  192. isc.dns.RRType.RRSIG, isc.dns.RRTTL(7200), None)
  193. add_rrset(expected_rrset_list, name, rrclass,
  194. isc.dns.RRType.SOA, isc.dns.RRTTL(3600),
  195. [
  196. "master.example.com. admin.example.com. 678 3600 1800 2419200 7200"
  197. ])
  198. name = isc.dns.Name("www.sql1.example.com.")
  199. add_rrset(expected_rrset_list, name, rrclass,
  200. isc.dns.RRType.A, isc.dns.RRTTL(3600),
  201. [
  202. "192.0.2.100"
  203. ])
  204. name = isc.dns.Name("www.sql1.example.com.")
  205. add_rrset(expected_rrset_list, name, rrclass,
  206. isc.dns.RRType.NSEC, isc.dns.RRTTL(7200),
  207. [
  208. "sql1.example.com. A RRSIG NSEC"
  209. ])
  210. add_rrset(expected_rrset_list, name, rrclass,
  211. isc.dns.RRType.RRSIG, isc.dns.RRTTL(3600), None)
  212. add_rrset(expected_rrset_list, name, rrclass,
  213. isc.dns.RRType.RRSIG, isc.dns.RRTTL(7200), None)
  214. # rrs is an iterator, but also has direct get_next_rrset(), use
  215. # the latter one here
  216. rrset_to_check = rrs.get_next_rrset()
  217. while (rrset_to_check != None):
  218. self.assertTrue(check_for_rrset(expected_rrset_list,
  219. rrset_to_check),
  220. "Unexpected rrset returned by iterator:\n" +
  221. rrset_to_check.to_text())
  222. rrset_to_check = rrs.get_next_rrset()
  223. # Now check there are none left
  224. self.assertEqual(0, len(expected_rrset_list),
  225. "RRset(s) not returned by iterator: " +
  226. str([rrset.get_name().to_text() + '/' +
  227. rrset.get_type().to_text() for rrset in
  228. expected_rrset_list ]
  229. ))
  230. # TODO should we catch this (iterating past end) and just return None
  231. # instead of failing?
  232. self.assertRaises(isc.datasrc.Error, rrs.get_next_rrset)
  233. # Without the separate_rrs argument, it should return 55 RRsets
  234. dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
  235. rrets = dsc.get_iterator(isc.dns.Name("example.com"))
  236. # there are more than 80 RRs in this zone... let's just count them
  237. # (already did a full check of the smaller zone above)
  238. # There are 40 non-RRSIG RRsets and 32 dinstinct RRSIGs.
  239. self.assertEqual(72, len(list(rrets)))
  240. # same test, but now with explicit False argument for separate_rrs
  241. dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
  242. rrets = dsc.get_iterator(isc.dns.Name("example.com"), False)
  243. # there are more than 80 RRs in this zone... let's just count them
  244. # (already did a full check of the smaller zone above)
  245. self.assertEqual(72, len(list(rrets)))
  246. dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
  247. rrets = dsc.get_iterator(isc.dns.Name("example.com"), True)
  248. # there are more than 80 RRs in this zone... let's just count them
  249. # (already did a full check of the smaller zone above)
  250. self.assertEqual(84, len(list(rrets)))
  251. # TODO should we catch this (iterating past end) and just return None
  252. # instead of failing?
  253. self.assertRaises(isc.datasrc.Error, rrs.get_next_rrset)
  254. self.assertRaises(TypeError, dsc.get_iterator, "asdf")
  255. def test_iterator_soa(self):
  256. dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
  257. iterator = dsc.get_iterator(isc.dns.Name("sql1.example.com."))
  258. expected_soa = isc.dns.RRset(isc.dns.Name("sql1.example.com."),
  259. isc.dns.RRClass.IN,
  260. isc.dns.RRType.SOA,
  261. isc.dns.RRTTL(3600))
  262. expected_soa.add_rdata(isc.dns.Rdata(isc.dns.RRType.SOA,
  263. isc.dns.RRClass.IN,
  264. "master.example.com. " +
  265. "admin.example.com. 678 " +
  266. "3600 1800 2419200 7200"))
  267. self.assertTrue(rrsets_equal(expected_soa, iterator.get_soa()))
  268. def test_construct(self):
  269. # can't construct directly
  270. self.assertRaises(TypeError, isc.datasrc.ZoneFinder)
  271. def test_findoptions(self):
  272. '''A simple test to confirm no option is specified by default.
  273. '''
  274. self.assertFalse(ZoneFinder.FIND_DEFAULT & ZoneFinder.FIND_GLUE_OK)
  275. self.assertFalse(ZoneFinder.FIND_DEFAULT & ZoneFinder.FIND_DNSSEC)
  276. self.assertFalse(ZoneFinder.FIND_DEFAULT & ZoneFinder.NO_WILDCARD)
  277. def test_findresults(self):
  278. '''A simple test to confirm result codes are (defined and) different
  279. for some combinations.
  280. '''
  281. self.assertNotEqual(ZoneFinder.SUCCESS, ZoneFinder.DELEGATION)
  282. self.assertNotEqual(ZoneFinder.DELEGATION, ZoneFinder.NXDOMAIN)
  283. self.assertNotEqual(ZoneFinder.NXDOMAIN, ZoneFinder.NXRRSET)
  284. self.assertNotEqual(ZoneFinder.NXRRSET, ZoneFinder.CNAME)
  285. self.assertNotEqual(ZoneFinder.CNAME, ZoneFinder.DNAME)
  286. def test_findresultflags(self):
  287. '''A simple test just confirming the flags are all different.'''
  288. self.assertNotEqual(ZoneFinder.RESULT_WILDCARD,
  289. ZoneFinder.RESULT_NSEC_SIGNED)
  290. self.assertNotEqual(ZoneFinder.RESULT_NSEC_SIGNED,
  291. ZoneFinder.RESULT_NSEC3_SIGNED)
  292. self.assertNotEqual(ZoneFinder.RESULT_NSEC3_SIGNED,
  293. ZoneFinder.RESULT_WILDCARD)
  294. def test_findall(self):
  295. """
  296. A test for the find_all method.
  297. """
  298. dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
  299. result, finder = dsc.find_zone(isc.dns.Name("example.com"))
  300. self.assertEqual(finder.SUCCESS, result)
  301. self.assertEqual(isc.dns.RRClass.IN, finder.get_class())
  302. self.assertEqual("example.com.", finder.get_origin().to_text())
  303. test_findall_common(self, finder)
  304. def test_find(self):
  305. dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
  306. result, finder = dsc.find_zone(isc.dns.Name("example.com"))
  307. self.assertEqual(finder.SUCCESS, result)
  308. self.assertEqual(isc.dns.RRClass.IN, finder.get_class())
  309. self.assertEqual("example.com.", finder.get_origin().to_text())
  310. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  311. isc.dns.RRType.A,
  312. finder.FIND_DEFAULT)
  313. self.assertEqual(finder.SUCCESS, result)
  314. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  315. rrset.to_text())
  316. # Check the optional parameters are optional
  317. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  318. isc.dns.RRType.A)
  319. self.assertEqual(finder.SUCCESS, result)
  320. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  321. rrset.to_text())
  322. result, rrset, _ = finder.find(isc.dns.Name("www.sql1.example.com"),
  323. isc.dns.RRType.A,
  324. finder.FIND_DEFAULT)
  325. self.assertEqual(finder.DELEGATION, result)
  326. self.assertEqual("sql1.example.com. 3600 IN NS dns01.example.com.\n" +
  327. "sql1.example.com. 3600 IN NS dns02.example.com.\n" +
  328. "sql1.example.com. 3600 IN NS dns03.example.com.\n",
  329. rrset.to_text())
  330. result, rrset, _ = finder.find(isc.dns.Name("doesnotexist.example.com"),
  331. isc.dns.RRType.A,
  332. finder.FIND_DEFAULT)
  333. self.assertEqual(finder.NXDOMAIN, result)
  334. self.assertEqual(None, rrset)
  335. self.assertRaises(isc.datasrc.OutOfZone, finder.find,
  336. isc.dns.Name("www.some.other.domain"),
  337. isc.dns.RRType.A)
  338. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  339. isc.dns.RRType.TXT,
  340. finder.FIND_DEFAULT)
  341. self.assertEqual(finder.NXRRSET, result)
  342. self.assertEqual(None, rrset)
  343. result, rrset, _ = finder.find(isc.dns.Name("cname-ext.example.com"),
  344. isc.dns.RRType.A,
  345. finder.FIND_DEFAULT)
  346. self.assertEqual(finder.CNAME, result)
  347. self.assertEqual(
  348. "cname-ext.example.com. 3600 IN CNAME www.sql1.example.com.\n",
  349. rrset.to_text())
  350. result, rrset, flags = \
  351. finder.find(isc.dns.Name("foo.wild.example.com"),
  352. isc.dns.RRType.A, finder.FIND_DEFAULT)
  353. self.assertEqual(finder.SUCCESS, result)
  354. self.assertEqual(finder.RESULT_WILDCARD, flags)
  355. self.assertEqual("foo.wild.example.com. 3600 IN A 192.0.2.255\n",
  356. rrset.to_text())
  357. result, rrset, _ = finder.find(isc.dns.Name("foo.wild.example.com"),
  358. isc.dns.RRType.TXT,
  359. finder.FIND_DEFAULT)
  360. self.assertEqual(finder.NXRRSET, result)
  361. self.assertTrue(finder.RESULT_WILDCARD, flags)
  362. self.assertEqual(None, rrset)
  363. self.assertRaises(TypeError, finder.find,
  364. "foo",
  365. isc.dns.RRType.A,
  366. finder.FIND_DEFAULT)
  367. self.assertRaises(TypeError, finder.find,
  368. isc.dns.Name("cname-ext.example.com"),
  369. "foo",
  370. finder.FIND_DEFAULT)
  371. self.assertRaises(TypeError, finder.find,
  372. isc.dns.Name("cname-ext.example.com"),
  373. isc.dns.RRType.A,
  374. "foo")
  375. class DataSrcUpdater(unittest.TestCase):
  376. def setUp(self):
  377. # Make a fresh copy of the writable database with all original content
  378. shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
  379. def test_findall(self):
  380. """
  381. The same test as DataSrcClient.test_findall, but on an updater
  382. instead of a finder.
  383. """
  384. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  385. updater = dsc.get_updater(isc.dns.Name("example.com"), False)
  386. test_findall_common(self, updater)
  387. def test_construct(self):
  388. # can't construct directly
  389. self.assertRaises(TypeError, isc.datasrc.ZoneUpdater)
  390. def test_update_finder(self):
  391. # Check basic behavior of updater's finder
  392. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  393. updater = dsc.get_updater(isc.dns.Name("example.com"), False)
  394. result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
  395. isc.dns.RRType.A,
  396. ZoneFinder.FIND_DEFAULT)
  397. self.assertEqual(ZoneFinder.SUCCESS, result)
  398. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  399. rrset.to_text())
  400. # Omit optional parameters
  401. result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
  402. isc.dns.RRType.A)
  403. self.assertEqual(ZoneFinder.SUCCESS, result)
  404. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  405. rrset.to_text())
  406. def test_update_delete_commit(self):
  407. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  408. # first make sure, through a separate finder, that some record exists
  409. result, finder = dsc.find_zone(isc.dns.Name("example.com"))
  410. self.assertEqual(finder.SUCCESS, result)
  411. self.assertEqual(isc.dns.RRClass.IN, finder.get_class())
  412. self.assertEqual("example.com.", finder.get_origin().to_text())
  413. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  414. isc.dns.RRType.A,
  415. finder.FIND_DEFAULT)
  416. self.assertEqual(finder.SUCCESS, result)
  417. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  418. rrset.to_text())
  419. rrset_to_delete = rrset;
  420. rrset_to_delete.remove_rrsig()
  421. updater = dsc.get_updater(isc.dns.Name("example.com"), True)
  422. updater.delete_rrset(rrset_to_delete)
  423. # The record should be gone in the updater, but not in the original
  424. # finder (since we have not committed)
  425. result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
  426. isc.dns.RRType.A,
  427. finder.FIND_DEFAULT)
  428. self.assertEqual(finder.NXDOMAIN, result)
  429. self.assertEqual(None, rrset)
  430. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  431. isc.dns.RRType.A,
  432. finder.FIND_DEFAULT)
  433. self.assertEqual(finder.SUCCESS, result)
  434. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  435. rrset.to_text())
  436. updater.commit()
  437. # second commit should raise exception
  438. self.assertRaises(isc.datasrc.Error, updater.commit)
  439. # the record should be gone now in the 'real' finder as well
  440. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  441. isc.dns.RRType.A,
  442. finder.FIND_DEFAULT)
  443. self.assertEqual(finder.NXDOMAIN, result)
  444. self.assertEqual(None, rrset)
  445. # now add it again
  446. updater = dsc.get_updater(isc.dns.Name("example.com"), True)
  447. updater.add_rrset(rrset_to_delete)
  448. updater.commit()
  449. # second commit should throw
  450. self.assertRaises(isc.datasrc.Error, updater.commit)
  451. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  452. isc.dns.RRType.A,
  453. finder.FIND_DEFAULT)
  454. self.assertEqual(finder.SUCCESS, result)
  455. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  456. rrset.to_text())
  457. def test_updater_rrset_collection(self):
  458. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  459. updater = dsc.get_updater(isc.dns.Name("example.com"), False)
  460. updater_refs = sys.getrefcount(updater)
  461. # Get (internally create) updater's RRset collection
  462. rrsets = updater.get_rrset_collection()
  463. # From this point we cannot make further updates
  464. rrset = RRset(isc.dns.Name('www.example.com'), isc.dns.RRClass.IN,
  465. isc.dns.RRType.AAAA, isc.dns.RRTTL(10))
  466. rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.AAAA,
  467. isc.dns.RRClass.IN, '2001:db8::1'))
  468. self.assertRaises(isc.datasrc.Error, updater.add_rrset, rrset)
  469. # Checks basic API
  470. found = rrsets.find(isc.dns.Name("www.example.com"),
  471. isc.dns.RRClass.IN, isc.dns.RRType.A)
  472. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  473. found.to_text())
  474. self.assertEqual(None, rrsets.find(isc.dns.Name("www.example.com"),
  475. isc.dns.RRClass.IN,
  476. isc.dns.RRType.AAAA))
  477. # Once committed collection cannot be used any more.
  478. updater.commit()
  479. self.assertRaises(isc.dns.RRsetCollectionError,
  480. rrsets.find, isc.dns.Name("www.example.com"),
  481. isc.dns.RRClass.IN, isc.dns.RRType.A)
  482. # When we destroy the RRsetCollection it should release the refcount
  483. # to the updater.
  484. rrsets = None
  485. self.assertEqual(updater_refs, sys.getrefcount(updater))
  486. def test_two_modules(self):
  487. # load two modules, and check if they don't interfere; as the
  488. # memory datasource module no longer exists, we check the static
  489. # datasource instead (as that uses the memory datasource
  490. # anyway).
  491. dsc_static = isc.datasrc.DataSourceClient("static", STATIC_ZONE_CONFIG)
  492. dsc_sql = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
  493. # check if exceptions are working
  494. self.assertRaises(isc.datasrc.Error, isc.datasrc.DataSourceClient,
  495. "static", "\"\"")
  496. self.assertRaises(isc.datasrc.Error, isc.datasrc.DataSourceClient,
  497. "sqlite3", "{}")
  498. # see if a lookup succeeds in sqlite3 ds
  499. result, finder = dsc_sql.find_zone(isc.dns.Name("example.com"))
  500. self.assertEqual(finder.SUCCESS, result)
  501. self.assertEqual(isc.dns.RRClass.IN, finder.get_class())
  502. self.assertEqual("example.com.", finder.get_origin().to_text())
  503. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  504. isc.dns.RRType.A,
  505. finder.FIND_DEFAULT)
  506. self.assertEqual(finder.SUCCESS, result)
  507. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  508. rrset.to_text())
  509. # see if a lookup fails in static ds
  510. result, finder = dsc_static.find_zone(isc.dns.Name("example"))
  511. self.assertEqual(finder.NXDOMAIN, result)
  512. def test_update_delete_abort(self):
  513. # we don't do enything with this one, just making sure loading two
  514. # datasources
  515. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  516. # first make sure, through a separate finder, that some record exists
  517. result, finder = dsc.find_zone(isc.dns.Name("example.com"))
  518. self.assertEqual(finder.SUCCESS, result)
  519. self.assertEqual(isc.dns.RRClass.IN, finder.get_class())
  520. self.assertEqual("example.com.", finder.get_origin().to_text())
  521. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  522. isc.dns.RRType.A,
  523. finder.FIND_DEFAULT)
  524. self.assertEqual(finder.SUCCESS, result)
  525. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  526. rrset.to_text())
  527. rrset_to_delete = rrset;
  528. rrset_to_delete.remove_rrsig()
  529. updater = dsc.get_updater(isc.dns.Name("example.com"), True)
  530. updater.delete_rrset(rrset_to_delete)
  531. # The record should be gone in the updater, but not in the original
  532. # finder (since we have not committed)
  533. result, rrset, _ = updater.find(isc.dns.Name("www.example.com"),
  534. isc.dns.RRType.A,
  535. finder.FIND_DEFAULT)
  536. self.assertEqual(finder.NXDOMAIN, result)
  537. self.assertEqual(None, rrset)
  538. # destroy the updater, which should make it roll back
  539. updater = None
  540. # the record should still be available in the 'real' finder as well
  541. result, rrset, _ = finder.find(isc.dns.Name("www.example.com"),
  542. isc.dns.RRType.A,
  543. finder.FIND_DEFAULT)
  544. self.assertEqual(finder.SUCCESS, result)
  545. self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
  546. rrset.to_text())
  547. def test_update_for_no_zone(self):
  548. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  549. self.assertEqual(None,
  550. dsc.get_updater(isc.dns.Name("notexistent.example"),
  551. True))
  552. def test_client_reference(self):
  553. # Temporarily create various objects using factory methods of the
  554. # client. The created objects won't be stored anywhere and
  555. # immediately released. The creation shouldn't affect the reference
  556. # to the base client.
  557. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  558. orig_ref = sys.getrefcount(dsc)
  559. dsc.find_zone(isc.dns.Name("example.com"))
  560. self.assertEqual(orig_ref, sys.getrefcount(dsc))
  561. dsc.get_iterator(isc.dns.Name("example.com."))
  562. self.assertEqual(orig_ref, sys.getrefcount(dsc))
  563. dsc.get_updater(isc.dns.Name("example.com"), True)
  564. self.assertEqual(orig_ref, sys.getrefcount(dsc))
  565. def test_iterate_over_empty_zone(self):
  566. # empty the test zone first
  567. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  568. updater = dsc.get_updater(isc.dns.Name("example.com"), True)
  569. updater.commit()
  570. # Check the iterator behavior for the empty zone.
  571. iterator = dsc.get_iterator(isc.dns.Name("example.com."))
  572. self.assertEqual(None, iterator.get_soa())
  573. self.assertEqual(None, iterator.get_next_rrset())
  574. def test_create_or_delete_zone_args(self):
  575. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  576. for method in [dsc.create_zone, dsc.delete_zone]:
  577. self.assertRaises(TypeError, method)
  578. self.assertRaises(TypeError, method, 1)
  579. self.assertRaises(TypeError, method, None)
  580. self.assertRaises(TypeError, method, "foo.")
  581. self.assertRaises(TypeError, method, isc.dns.Name("example.org"),
  582. 1)
  583. def test_create_delete_zone(self):
  584. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  585. # Note, using example.org here, which should not exist
  586. zone_name = isc.dns.Name("example.org")
  587. self.assertIsNone(dsc.get_updater(zone_name, True))
  588. self.assertRaises(isc.datasrc.Error, dsc.get_iterator, zone_name)
  589. # delete_zone should return False, meaning it didn't exist.
  590. self.assertFalse(dsc.delete_zone(zone_name))
  591. self.assertTrue(dsc.create_zone(zone_name))
  592. # should exist now, we should be able to get an updater
  593. # and currently it should be empty
  594. self.assertIsNotNone(dsc.get_updater(zone_name, True))
  595. iterator = dsc.get_iterator(zone_name)
  596. self.assertEqual(None, iterator.get_soa())
  597. self.assertEqual(None, iterator.get_next_rrset())
  598. # Trying to create it again should return False
  599. self.assertFalse(dsc.create_zone(zone_name))
  600. # Now that it exists, delete_zone should return True, and it cannot
  601. # be found any more.
  602. self.assertTrue(dsc.delete_zone(zone_name))
  603. self.assertEqual(isc.datasrc.DataSourceClient.NOTFOUND,
  604. dsc.find_zone(zone_name)[0])
  605. def test_create_zone_locked(self):
  606. zone_name = isc.dns.Name("example.org")
  607. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  608. updater = dsc.get_updater(isc.dns.Name("example.com"), True)
  609. # Should fail since db is locked
  610. self.assertRaises(isc.datasrc.Error, dsc.create_zone, zone_name)
  611. self.assertRaises(isc.datasrc.Error, dsc.delete_zone,
  612. isc.dns.Name('example.com'))
  613. # Cancel updater, then create/delete should succeed
  614. updater = None
  615. self.assertTrue(dsc.create_zone(zone_name))
  616. self.assertTrue(dsc.delete_zone(zone_name))
  617. def test_create_zone_not_implemented(self):
  618. # As the memory datasource module no longer exists, we check the
  619. # static datasource instead (as that uses the memory datasource
  620. # anyway).
  621. dsc = isc.datasrc.DataSourceClient("static", STATIC_ZONE_CONFIG)
  622. self.assertRaises(isc.datasrc.NotImplemented, dsc.create_zone,
  623. isc.dns.Name("example.com"))
  624. class JournalWrite(unittest.TestCase):
  625. def setUp(self):
  626. # Make a fresh copy of the writable database with all original content
  627. shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
  628. self.dsc = isc.datasrc.DataSourceClient("sqlite3",
  629. WRITE_ZONE_DB_CONFIG)
  630. self.updater = self.dsc.get_updater(Name("example.com"), False, True)
  631. def tearDown(self):
  632. self.dsc = None
  633. self.updater = None
  634. def check_journal(self, expected_list):
  635. # This assumes sqlite3 DB and directly fetches stored data from
  636. # the DB file. It should be generalized using ZoneJournalReader
  637. # once it's supported.
  638. conn = sqlite3.connect(WRITE_ZONE_DB_FILE)
  639. cur = conn.cursor()
  640. cur.execute('SELECT name, rrtype, ttl, rdata FROM diffs ORDER BY id')
  641. actual_list = cur.fetchall()
  642. self.assertEqual(len(expected_list), len(actual_list))
  643. for (expected, actual) in zip(expected_list, actual_list):
  644. self.assertEqual(expected, actual)
  645. conn.close()
  646. def create_a(self, address):
  647. a_rr = RRset(Name('www.example.org'), RRClass.IN, RRType.A,
  648. RRTTL(3600))
  649. a_rr.add_rdata(Rdata(RRType.A, RRClass.IN, address))
  650. return (a_rr)
  651. def test_journal_write(self):
  652. # This is a straightforward port of the C++ 'journal' test
  653. # Note: we add/delete 'out of zone' data (example.org in the
  654. # example.com zone for convenience.
  655. self.updater.delete_rrset(create_soa(1234))
  656. self.updater.delete_rrset(self.create_a('192.0.2.2'))
  657. self.updater.add_rrset(create_soa(1235))
  658. self.updater.add_rrset(self.create_a('192.0.2.2'))
  659. self.updater.commit()
  660. expected = []
  661. expected.append(("example.org.", "SOA", 3600,
  662. "ns1.example.org. admin.example.org. " +
  663. "1234 3600 1800 2419200 7200"))
  664. expected.append(("www.example.org.", "A", 3600, "192.0.2.2"))
  665. expected.append(("example.org.", "SOA", 3600,
  666. "ns1.example.org. admin.example.org. " +
  667. "1235 3600 1800 2419200 7200"))
  668. expected.append(("www.example.org.", "A", 3600, "192.0.2.2"))
  669. self.check_journal(expected)
  670. def test_journal_write_multiple(self):
  671. # This is a straightforward port of the C++ 'journalMultiple' test
  672. expected = []
  673. for i in range(1, 100):
  674. self.updater.delete_rrset(create_soa(1234 + i - 1))
  675. expected.append(("example.org.", "SOA", 3600,
  676. "ns1.example.org. admin.example.org. " +
  677. str(1234 + i - 1) + " 3600 1800 2419200 7200"))
  678. self.updater.add_rrset(create_soa(1234 + i))
  679. expected.append(("example.org.", "SOA", 3600,
  680. "ns1.example.org. admin.example.org. " +
  681. str(1234 + i) + " 3600 1800 2419200 7200"))
  682. self.updater.commit()
  683. self.check_journal(expected)
  684. def test_journal_write_bad_sequence(self):
  685. # This is a straightforward port of the C++ 'journalBadSequence' test
  686. # Delete A before SOA
  687. self.assertRaises(isc.datasrc.Error, self.updater.delete_rrset,
  688. self.create_a('192.0.2.1'))
  689. # Add before delete
  690. self.updater = self.dsc.get_updater(Name("example.com"), False, True)
  691. self.assertRaises(isc.datasrc.Error, self.updater.add_rrset,
  692. create_soa(1234))
  693. # Add A before SOA
  694. self.updater = self.dsc.get_updater(Name("example.com"), False, True)
  695. self.updater.delete_rrset(create_soa(1234))
  696. self.assertRaises(isc.datasrc.Error, self.updater.add_rrset,
  697. self.create_a('192.0.2.1'))
  698. # Commit before add
  699. self.updater = self.dsc.get_updater(Name("example.com"), False, True)
  700. self.updater.delete_rrset(create_soa(1234))
  701. self.assertRaises(isc.datasrc.Error, self.updater.commit)
  702. # Delete two SOAs
  703. self.updater = self.dsc.get_updater(Name("example.com"), False, True)
  704. self.updater.delete_rrset(create_soa(1234))
  705. self.assertRaises(isc.datasrc.Error, self.updater.delete_rrset,
  706. create_soa(1235))
  707. # Add two SOAs
  708. self.updater = self.dsc.get_updater(Name("example.com"), False, True)
  709. self.updater.delete_rrset(create_soa(1234))
  710. self.updater.add_rrset(create_soa(1235))
  711. self.assertRaises(isc.datasrc.Error, self.updater.add_rrset,
  712. create_soa(1236))
  713. def test_journal_write_onerase(self):
  714. self.updater = None
  715. self.assertRaises(isc.datasrc.Error, self.dsc.get_updater,
  716. Name("example.com"), True, True)
  717. def test_journal_write_badparam(self):
  718. dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
  719. self.assertRaises(TypeError, dsc.get_updater, 0, False, True)
  720. self.assertRaises(TypeError, dsc.get_updater, Name('example.com'),
  721. False, 0)
  722. self.assertRaises(TypeError, dsc.get_updater, Name("example.com"),
  723. 1, True)
  724. class JournalRead(unittest.TestCase):
  725. def setUp(self):
  726. # Make a fresh copy of the writable database with all original content
  727. self.zname = Name('example.com')
  728. shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
  729. self.dsc = isc.datasrc.DataSourceClient("sqlite3",
  730. WRITE_ZONE_DB_CONFIG)
  731. self.reader = None
  732. def tearDown(self):
  733. # Some tests leave the reader in the middle of sequence, holding
  734. # the lock. Since the unittest framework keeps each test object
  735. # until the end of the entire tests, we need to make sure the reader
  736. # is released at the end of each test. The client shouldn't do harm
  737. # but we clean it up, too, just in case.
  738. self.dsc = None
  739. self.reader = None
  740. def make_simple_diff(self, begin_soa):
  741. updater = self.dsc.get_updater(self.zname, False, True)
  742. updater.delete_rrset(begin_soa)
  743. updater.add_rrset(create_soa(1235))
  744. updater.commit()
  745. def test_journal_reader(self):
  746. # This is a straightforward port of the C++ 'journalReader' test
  747. self.make_simple_diff(create_soa(1234))
  748. result, self.reader = self.dsc.get_journal_reader(self.zname, 1234,
  749. 1235)
  750. self.assertEqual(ZoneJournalReader.SUCCESS, result)
  751. self.assertNotEqual(None, self.reader)
  752. rrsets_equal(create_soa(1234), self.reader.get_next_diff())
  753. rrsets_equal(create_soa(1235), self.reader.get_next_diff())
  754. self.assertEqual(None, self.reader.get_next_diff())
  755. self.assertRaises(ValueError, self.reader.get_next_diff)
  756. def test_journal_reader_with_large_serial(self):
  757. # similar to the previous one, but use a very large serial to check
  758. # if the python wrapper code has unexpected integer overflow
  759. self.make_simple_diff(create_soa(4294967295))
  760. result, self.reader = self.dsc.get_journal_reader(self.zname,
  761. 4294967295, 1235)
  762. self.assertNotEqual(None, self.reader)
  763. # dump to text and compare them in case create_soa happens to have
  764. # an overflow bug
  765. self.assertEqual('example.org. 3600 IN SOA ns1.example.org. ' + \
  766. 'admin.example.org. 4294967295 3600 1800 ' + \
  767. '2419200 7200\n',
  768. self.reader.get_next_diff().to_text())
  769. def test_journal_reader_large_journal(self):
  770. # This is a straightforward port of the C++ 'readLargeJournal' test.
  771. # In this test we use the ZoneJournalReader object as a Python
  772. # iterator.
  773. updater = self.dsc.get_updater(self.zname, False, True)
  774. expected = []
  775. for i in range(0, 100):
  776. rrset = create_soa(1234 + i)
  777. updater.delete_rrset(rrset)
  778. expected.append(rrset)
  779. rrset = create_soa(1234 + i + 1)
  780. updater.add_rrset(rrset)
  781. expected.append(rrset)
  782. updater.commit()
  783. _, self.reader = self.dsc.get_journal_reader(self.zname, 1234, 1334)
  784. self.assertNotEqual(None, self.reader)
  785. i = 0
  786. for rr in self.reader:
  787. self.assertNotEqual(len(expected), i)
  788. rrsets_equal(expected[i], rr)
  789. i += 1
  790. self.assertEqual(len(expected), i)
  791. def test_journal_reader_no_range(self):
  792. # This is a straightforward port of the C++ 'readJournalForNoRange'
  793. # test
  794. self.make_simple_diff(create_soa(1234))
  795. result, self.reader = self.dsc.get_journal_reader(self.zname, 1200,
  796. 1235)
  797. self.assertEqual(ZoneJournalReader.NO_SUCH_VERSION, result)
  798. self.assertEqual(None, self.reader)
  799. def test_journal_reader_no_zone(self):
  800. # This is a straightforward port of the C++ 'journalReaderForNXZone'
  801. # test
  802. result, self.reader = self.dsc.get_journal_reader(Name('nosuchzone'),
  803. 0, 1)
  804. self.assertEqual(ZoneJournalReader.NO_SUCH_ZONE, result)
  805. self.assertEqual(None, self.reader)
  806. def test_journal_reader_bad_params(self):
  807. self.assertRaises(TypeError, self.dsc.get_journal_reader,
  808. 'example.com.', 0, 1)
  809. self.assertRaises(TypeError, self.dsc.get_journal_reader,
  810. self.zname, 'must be int', 1)
  811. self.assertRaises(TypeError, self.dsc.get_journal_reader,
  812. self.zname, 0, 'must be int')
  813. def test_journal_reader_direct_construct(self):
  814. # ZoneJournalReader can only be constructed via a factory
  815. self.assertRaises(TypeError, ZoneJournalReader)
  816. if __name__ == "__main__":
  817. isc.log.init("bind10")
  818. isc.log.resetUnitTestRootLogger()
  819. unittest.main()