diff_tests.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  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 unittest
  17. from isc.datasrc import ZoneFinder
  18. from isc.dns import Name, RRset, RRClass, RRType, RRTTL, Rdata
  19. from isc.xfrin.diff import Diff, NoSuchZone
  20. class TestError(Exception):
  21. """
  22. Just to have something to be raised during the tests.
  23. Not used outside.
  24. """
  25. pass
  26. class DiffTest(unittest.TestCase):
  27. """
  28. Tests for the isc.xfrin.diff.Diff class.
  29. It also plays role of a data source and an updater, so it can manipulate
  30. some test variables while being called.
  31. """
  32. def setUp(self):
  33. """
  34. This sets internal variables so we can see nothing was called yet.
  35. It also creates some variables used in multiple tests.
  36. """
  37. # Track what was called already
  38. self.__updater_requested = False
  39. self.__compact_called = False
  40. self.__data_operations = []
  41. self.__apply_called = False
  42. self.__commit_called = False
  43. self.__broken_called = False
  44. self.__warn_called = False
  45. self.__should_replace = False
  46. self.__find_called = False
  47. self.__find_name = None
  48. self.__find_type = None
  49. self.__find_options = None
  50. self.__find_all_called = False
  51. self.__find_all_name = None
  52. self.__find_all_options = None
  53. # Some common values
  54. self.__rrclass = RRClass.IN()
  55. self.__type = RRType.A()
  56. self.__ttl = RRTTL(3600)
  57. # And RRsets
  58. # Create two valid rrsets
  59. self.__rrset1 = RRset(Name('a.example.org.'), self.__rrclass,
  60. self.__type, self.__ttl)
  61. self.__rdata = Rdata(self.__type, self.__rrclass, '192.0.2.1')
  62. self.__rrset1.add_rdata(self.__rdata)
  63. self.__rrset2 = RRset(Name('b.example.org.'), self.__rrclass,
  64. self.__type, self.__ttl)
  65. self.__rrset2.add_rdata(self.__rdata)
  66. # And two invalid
  67. self.__rrset_empty = RRset(Name('empty.example.org.'), self.__rrclass,
  68. self.__type, self.__ttl)
  69. self.__rrset_multi = RRset(Name('multi.example.org.'), self.__rrclass,
  70. self.__type, self.__ttl)
  71. self.__rrset_multi.add_rdata(self.__rdata)
  72. self.__rrset_multi.add_rdata(Rdata(self.__type, self.__rrclass,
  73. '192.0.2.2'))
  74. # Also create a few other (valid) rrsets
  75. # A SOA record
  76. self.__rrset_soa = RRset(Name('example.org.'), self.__rrclass,
  77. RRType.SOA(), RRTTL(3600))
  78. self.__rrset_soa.add_rdata(Rdata(RRType.SOA(), self.__rrclass,
  79. "ns1.example.org. " +
  80. "admin.example.org. " +
  81. "1233 3600 1800 2419200 7200"))
  82. # A few single-rr rrsets that together would for a multi-rr rrset
  83. self.__rrset3 = RRset(Name('c.example.org.'), self.__rrclass,
  84. RRType.TXT(), self.__ttl)
  85. self.__rrset3.add_rdata(Rdata(RRType.TXT(), self.__rrclass, "one"))
  86. self.__rrset4 = RRset(Name('c.example.org.'), self.__rrclass,
  87. RRType.TXT(), self.__ttl)
  88. self.__rrset4.add_rdata(Rdata(RRType.TXT(), self.__rrclass, "two"))
  89. self.__rrset5 = RRset(Name('c.example.org.'), self.__rrclass,
  90. RRType.TXT(), self.__ttl)
  91. self.__rrset5.add_rdata(Rdata(RRType.TXT(), self.__rrclass, "three"))
  92. self.__rrset6 = RRset(Name('d.example.org.'), self.__rrclass,
  93. RRType.A(), self.__ttl)
  94. self.__rrset6.add_rdata(Rdata(RRType.A(), self.__rrclass, "192.0.2.1"))
  95. self.__rrset7 = RRset(Name('d.example.org.'), self.__rrclass,
  96. RRType.A(), self.__ttl)
  97. self.__rrset7.add_rdata(Rdata(RRType.A(), self.__rrclass, "192.0.2.2"))
  98. def __mock_compact(self):
  99. """
  100. This can be put into the diff to hook into its compact method and see
  101. if it gets called.
  102. """
  103. self.__compact_called = True
  104. def __mock_apply(self):
  105. """
  106. This can be put into the diff to hook into its apply method and see
  107. it gets called.
  108. """
  109. self.__apply_called = True
  110. def __broken_operation(self, *args):
  111. """
  112. This can be used whenever an operation should fail. It raises TestError.
  113. It should take whatever amount of parameters needed, so it can be put
  114. quite anywhere.
  115. """
  116. self.__broken_called = True
  117. raise TestError("Test error")
  118. def warn(self, *args):
  119. """
  120. This is for checking the warn function was called, we replace the logger
  121. in the tested module.
  122. """
  123. self.__warn_called = True
  124. # Also log the message so we can check the log format (manually)
  125. self.orig_logger.warn(*args)
  126. def commit(self):
  127. """
  128. This is part of pretending to be a zone updater. This notes the commit
  129. was called.
  130. """
  131. self.__commit_called = True
  132. def add_rrset(self, rrset):
  133. """
  134. This one is part of pretending to be a zone updater. It writes down
  135. addition of an rrset was requested.
  136. """
  137. self.__data_operations.append(('add', rrset))
  138. def delete_rrset(self, rrset):
  139. """
  140. This one is part of pretending to be a zone updater. It writes down
  141. removal of an rrset was requested.
  142. """
  143. self.__data_operations.append(('delete', rrset))
  144. def get_class(self):
  145. """
  146. This one is part of pretending to be a zone updater. It returns
  147. the IN class.
  148. """
  149. return self.__rrclass
  150. def get_updater(self, zone_name, replace, journaling=False):
  151. """
  152. This one pretends this is the data source client and serves
  153. getting an updater.
  154. If zone_name is 'none.example.org.', it returns None, otherwise
  155. it returns self.
  156. """
  157. # The diff should not delete the old data.
  158. self.assertEqual(self.__should_replace, replace)
  159. self.__updater_requested = True
  160. if zone_name == Name('none.example.org.'):
  161. # Pretend this zone doesn't exist
  162. return None
  163. # If journaling is enabled, record the fact; for a special zone
  164. # pretend that we don't support journaling.
  165. if journaling:
  166. if zone_name == Name('nodiff.example.org'):
  167. raise isc.datasrc.NotImplemented('journaling not supported')
  168. self.__journaling_enabled = True
  169. else:
  170. self.__journaling_enabled = False
  171. return self
  172. def find(self, name, rrtype, options=None):
  173. self.__find_called = True
  174. self.__find_name = name
  175. self.__find_type = rrtype
  176. self.__find_options = options
  177. # Doesn't really matter what is returned, as long
  178. # as the test can check that it's passed along
  179. return "find_return"
  180. def find_all(self, name, options=None):
  181. self.__find_all_called = True
  182. self.__find_all_name = name
  183. self.__find_all_options = options
  184. # Doesn't really matter what is returned, as long
  185. # as the test can check that it's passed along
  186. return "find_all_return"
  187. def test_create(self):
  188. """
  189. This test the case when the diff is successfuly created. It just
  190. tries it does not throw and gets the updater.
  191. """
  192. diff = Diff(self, Name('example.org.'))
  193. self.assertTrue(self.__updater_requested)
  194. self.assertEqual([], diff.get_buffer())
  195. # By default journaling is disabled
  196. self.assertFalse(self.__journaling_enabled)
  197. def test_create_nonexist(self):
  198. """
  199. Try to create a diff on a zone that doesn't exist. This should
  200. raise a correct exception.
  201. """
  202. self.assertRaises(NoSuchZone, Diff, self, Name('none.example.org.'))
  203. self.assertTrue(self.__updater_requested)
  204. def test_create_withjournal(self):
  205. Diff(self, Name('example.org'), False, True)
  206. self.assertTrue(self.__journaling_enabled)
  207. def test_create_nojournal(self):
  208. Diff(self, Name('nodiff.example.org'), False, True)
  209. self.assertFalse(self.__journaling_enabled)
  210. def __data_common(self, diff, method, operation):
  211. """
  212. Common part of test for test_add and test_delte.
  213. """
  214. # Try putting there the bad data first
  215. self.assertRaises(ValueError, method, self.__rrset_empty)
  216. self.assertRaises(ValueError, method, self.__rrset_multi)
  217. # They were not added
  218. self.assertEqual([], diff.get_buffer())
  219. # Put some proper data into the diff
  220. method(self.__rrset1)
  221. method(self.__rrset2)
  222. dlist = [(operation, self.__rrset1), (operation, self.__rrset2)]
  223. self.assertEqual(dlist, diff.get_buffer())
  224. # Check the data are not destroyed by raising an exception because of
  225. # bad data
  226. self.assertRaises(ValueError, method, self.__rrset_empty)
  227. self.assertEqual(dlist, diff.get_buffer())
  228. def test_add(self):
  229. """
  230. Try to add few items into the diff and see they are stored in there.
  231. Also try passing an rrset that has differnt amount of RRs than 1.
  232. """
  233. diff = Diff(self, Name('example.org.'))
  234. self.__data_common(diff, diff.add_data, 'add')
  235. def test_delete(self):
  236. """
  237. Try scheduling removal of few items into the diff and see they are
  238. stored in there.
  239. Also try passing an rrset that has different amount of RRs than 1.
  240. """
  241. diff = Diff(self, Name('example.org.'))
  242. self.__data_common(diff, diff.delete_data, 'delete')
  243. def test_apply(self):
  244. """
  245. Schedule few additions and check the apply works by passing the
  246. data into the updater.
  247. """
  248. # Prepare the diff
  249. diff = Diff(self, Name('example.org.'))
  250. diff.add_data(self.__rrset1)
  251. diff.delete_data(self.__rrset2)
  252. dlist = [('add', self.__rrset1), ('delete', self.__rrset2)]
  253. self.assertEqual(dlist, diff.get_buffer())
  254. # Do the apply, hook the compact method
  255. diff.compact = self.__mock_compact
  256. diff.apply()
  257. # It should call the compact
  258. self.assertTrue(self.__compact_called)
  259. # And pass the data. Our local history of what happened is the same
  260. # format, so we can check the same way
  261. self.assertEqual(dlist, self.__data_operations)
  262. # And the buffer in diff should become empty, as everything
  263. # got inside.
  264. self.assertEqual([], diff.get_buffer())
  265. def test_commit(self):
  266. """
  267. If we call a commit, it should first apply whatever changes are
  268. left (we hook into that instead of checking the effect) and then
  269. the commit on the updater should have been called.
  270. Then we check it raises value error for whatever operation we try.
  271. """
  272. diff = Diff(self, Name('example.org.'))
  273. diff.add_data(self.__rrset1)
  274. orig_apply = diff.apply
  275. diff.apply = self.__mock_apply
  276. diff.commit()
  277. self.assertTrue(self.__apply_called)
  278. self.assertTrue(self.__commit_called)
  279. # The data should be handled by apply which we replaced.
  280. self.assertEqual([], self.__data_operations)
  281. # Now check all range of other methods raise ValueError
  282. self.assertRaises(ValueError, diff.commit)
  283. self.assertRaises(ValueError, diff.add_data, self.__rrset2)
  284. self.assertRaises(ValueError, diff.delete_data, self.__rrset1)
  285. self.assertRaises(ValueError, diff.find, Name('foo.example.org.'),
  286. RRType.A())
  287. self.assertRaises(ValueError, diff.find_all, Name('foo.example.org.'))
  288. diff.apply = orig_apply
  289. self.assertRaises(ValueError, diff.apply)
  290. # This one does not state it should raise, so check it doesn't
  291. # But it is NOP in this situation anyway
  292. diff.compact()
  293. def test_autoapply(self):
  294. """
  295. Test the apply is called all by itself after 100 tasks are added.
  296. """
  297. diff = Diff(self, Name('example.org.'))
  298. # A method to check the apply is called _after_ the 100th element
  299. # is added. We don't use it anywhere else, so we define it locally
  300. # as lambda function
  301. def check():
  302. self.assertEqual(100, len(diff.get_buffer()))
  303. self.__mock_apply()
  304. orig_apply = diff.apply
  305. diff.apply = check
  306. # If we put 99, nothing happens yet
  307. for i in range(0, 99):
  308. diff.add_data(self.__rrset1)
  309. expected = [('add', self.__rrset1)] * 99
  310. self.assertEqual(expected, diff.get_buffer())
  311. self.assertFalse(self.__apply_called)
  312. # Now we push the 100th and it should call the apply method
  313. # This will _not_ flush the data yet, as we replaced the method.
  314. # It, however, would in the real life.
  315. diff.add_data(self.__rrset1)
  316. # Now the apply method (which is replaced by our check) should
  317. # have been called. If it wasn't, this is false. If it was, but
  318. # still with 99 elements, the check would complain
  319. self.assertTrue(self.__apply_called)
  320. # Reset the buffer by calling the original apply.
  321. orig_apply()
  322. self.assertEqual([], diff.get_buffer())
  323. # Similar with delete
  324. self.__apply_called = False
  325. for i in range(0, 99):
  326. diff.delete_data(self.__rrset2)
  327. expected = [('delete', self.__rrset2)] * 99
  328. self.assertEqual(expected, diff.get_buffer())
  329. self.assertFalse(self.__apply_called)
  330. diff.delete_data(self.__rrset2)
  331. self.assertTrue(self.__apply_called)
  332. def test_compact(self):
  333. """
  334. Test the compaction works as expected, eg. it compacts only consecutive
  335. changes of the same operation and on the same domain/type.
  336. The test case checks that it does merge them, but also puts some
  337. different operations "in the middle", changes the type and name and
  338. places the same kind of change further away of each other to see they
  339. are not merged in that case.
  340. """
  341. diff = Diff(self, Name('example.org.'))
  342. # Check we can do a compact on empty data, it shouldn't break
  343. diff.compact()
  344. self.assertEqual([], diff.get_buffer())
  345. # This data is the way it should look like after the compact
  346. # ('operation', 'domain.prefix', 'type', ['rdata', 'rdata'])
  347. # The notes say why the each of consecutive can't be merged
  348. data = [
  349. ('add', 'a', 'A', ['192.0.2.1', '192.0.2.2']),
  350. # Different type.
  351. ('add', 'a', 'AAAA', ['2001:db8::1', '2001:db8::2']),
  352. # Different operation
  353. ('delete', 'a', 'AAAA', ['2001:db8::3']),
  354. # Different domain
  355. ('delete', 'b', 'AAAA', ['2001:db8::4']),
  356. # This does not get merged with the first, even if logically
  357. # possible. We just don't do this.
  358. ('add', 'a', 'A', ['192.0.2.3'])
  359. ]
  360. # Now, fill the data into the diff, in a "flat" way, one by one
  361. for (op, nprefix, rrtype, rdata) in data:
  362. name = Name(nprefix + '.example.org.')
  363. rrtype_obj = RRType(rrtype)
  364. for rdatum in rdata:
  365. rrset = RRset(name, self.__rrclass, rrtype_obj, self.__ttl)
  366. rrset.add_rdata(Rdata(rrtype_obj, self.__rrclass, rdatum))
  367. if op == 'add':
  368. diff.add_data(rrset)
  369. else:
  370. diff.delete_data(rrset)
  371. # Compact it
  372. diff.compact()
  373. # Now check they got compacted. They should be in the same order as
  374. # pushed inside. So it should be the same as data modulo being in
  375. # the rrsets and isc.dns objects.
  376. def check():
  377. buf = diff.get_buffer()
  378. self.assertEqual(len(data), len(buf))
  379. for (expected, received) in zip(data, buf):
  380. (eop, ename, etype, edata) = expected
  381. (rop, rrrset) = received
  382. self.assertEqual(eop, rop)
  383. ename_obj = Name(ename + '.example.org.')
  384. self.assertEqual(ename_obj, rrrset.get_name())
  385. # We check on names to make sure they are printed nicely
  386. self.assertEqual(etype, str(rrrset.get_type()))
  387. rdata = rrrset.get_rdata()
  388. self.assertEqual(len(edata), len(rdata))
  389. # It should also preserve the order
  390. for (edatum, rdatum) in zip(edata, rdata):
  391. self.assertEqual(edatum, str(rdatum))
  392. check()
  393. # Try another compact does nothing, but survives
  394. diff.compact()
  395. check()
  396. def test_wrong_class(self):
  397. """
  398. Test a wrong class of rrset is rejected.
  399. """
  400. diff = Diff(self, Name('example.org.'))
  401. rrset = RRset(Name('a.example.org.'), RRClass.CH(), RRType.NS(),
  402. self.__ttl)
  403. rrset.add_rdata(Rdata(RRType.NS(), RRClass.CH(), 'ns.example.org.'))
  404. self.assertRaises(ValueError, diff.add_data, rrset)
  405. self.assertRaises(ValueError, diff.delete_data, rrset)
  406. def __do_raise_test(self):
  407. """
  408. Do a raise test. Expects that one of the operations is exchanged for
  409. broken version.
  410. """
  411. diff = Diff(self, Name('example.org.'))
  412. diff.add_data(self.__rrset1)
  413. diff.delete_data(self.__rrset2)
  414. self.assertRaises(TestError, diff.commit)
  415. self.assertTrue(self.__broken_called)
  416. self.assertRaises(ValueError, diff.add_data, self.__rrset1)
  417. self.assertRaises(ValueError, diff.delete_data, self.__rrset2)
  418. self.assertRaises(ValueError, diff.commit)
  419. self.assertRaises(ValueError, diff.apply)
  420. def test_raise_add(self):
  421. """
  422. Test the exception from add_rrset is propagated and the diff can't be
  423. used afterwards.
  424. """
  425. self.add_rrset = self.__broken_operation
  426. self.__do_raise_test()
  427. def test_raise_delete(self):
  428. """
  429. Test the exception from delete_rrset is propagated and the diff can't be
  430. used afterwards.
  431. """
  432. self.delete_rrset = self.__broken_operation
  433. self.__do_raise_test()
  434. def test_raise_commit(self):
  435. """
  436. Test the exception from updater's commit gets propagated and it can't be
  437. used afterwards.
  438. """
  439. self.commit = self.__broken_operation
  440. self.__do_raise_test()
  441. def test_ttl(self):
  442. """
  443. Test the TTL handling. A warn function should have been called if they
  444. differ, but that's all, it should not crash or raise.
  445. """
  446. self.orig_logger = isc.xfrin.diff.logger
  447. try:
  448. isc.xfrin.diff.logger = self
  449. diff = Diff(self, Name('example.org.'))
  450. diff.add_data(self.__rrset1)
  451. rrset2 = RRset(Name('a.example.org.'), self.__rrclass,
  452. self.__type, RRTTL(120))
  453. rrset2.add_rdata(Rdata(self.__type, self.__rrclass, '192.10.2.2'))
  454. diff.add_data(rrset2)
  455. rrset2 = RRset(Name('a.example.org.'), self.__rrclass,
  456. self.__type, RRTTL(6000))
  457. rrset2.add_rdata(Rdata(self.__type, self.__rrclass, '192.10.2.3'))
  458. diff.add_data(rrset2)
  459. # They should get compacted together and complain.
  460. diff.compact()
  461. self.assertEqual(1, len(diff.get_buffer()))
  462. # The TTL stays on the first value, no matter if smaller or bigger
  463. # ones come later.
  464. self.assertEqual(self.__ttl, diff.get_buffer()[0][1].get_ttl())
  465. self.assertTrue(self.__warn_called)
  466. finally:
  467. isc.xfrin.diff.logger = self.orig_logger
  468. def test_rrsig_ttl(self):
  469. '''Similar to the previous test, but for RRSIGs of different covered
  470. types.
  471. They shouldn't be compacted.
  472. '''
  473. diff = Diff(self, Name('example.org.'))
  474. rrsig1 = RRset(Name('example.org'), self.__rrclass,
  475. RRType.RRSIG(), RRTTL(3600))
  476. rrsig1.add_rdata(Rdata(RRType.RRSIG(), self.__rrclass,
  477. 'A 5 3 3600 20000101000000 20000201000000 ' +
  478. '0 example.org. FAKEFAKEFAKE'))
  479. diff.add_data(rrsig1)
  480. rrsig2 = RRset(Name('example.org'), self.__rrclass,
  481. RRType.RRSIG(), RRTTL(1800))
  482. rrsig2.add_rdata(Rdata(RRType.RRSIG(), self.__rrclass,
  483. 'AAAA 5 3 3600 20000101000000 20000201000000 ' +
  484. '1 example.org. FAKEFAKEFAKE'))
  485. diff.add_data(rrsig2)
  486. diff.compact()
  487. self.assertEqual(2, len(diff.get_buffer()))
  488. def test_replace(self):
  489. '''
  490. Test that when we want to replace the whole zone, it is propagated.
  491. '''
  492. self.__should_replace = True
  493. diff = Diff(self, "example.org.", True)
  494. self.assertTrue(self.__updater_requested)
  495. def test_get_buffer(self):
  496. '''
  497. Test that the getters raise when used in the wrong mode
  498. '''
  499. diff_multi = Diff(self, Name('example.org.'), single_update_mode=False)
  500. self.assertRaises(ValueError, diff_multi.get_single_update_buffers)
  501. self.assertEqual([], diff_multi.get_buffer())
  502. diff_single = Diff(self, Name('example.org.'), single_update_mode=True)
  503. self.assertRaises(ValueError, diff_single.get_buffer)
  504. self.assertEqual(([], []), diff_single.get_single_update_buffers())
  505. def test_finds_single(self):
  506. '''
  507. Test that find_updated() and find_all_updated() can only be used
  508. in single-update-mode.
  509. '''
  510. diff_multi = Diff(self, Name('example.org.'), single_update_mode=False)
  511. self.assertRaises(ValueError, diff_multi.find_updated,
  512. Name('example.org.'), RRType.A())
  513. self.assertRaises(ValueError, diff_multi.find_all_updated,
  514. Name('example.org.'))
  515. def test_single_update_mode(self):
  516. '''
  517. Test single-update mode. In this mode, updates and deletes can
  518. be done in any order, but there may only be one changeset.
  519. For both updates and deletes, exactly one SOA rr must be given,
  520. and it must be the first change.
  521. '''
  522. # full rrset for A (to check compact())
  523. txt = RRset(Name('c.example.org.'), self.__rrclass, RRType.TXT(),
  524. RRTTL(3600))
  525. txt.add_rdata(Rdata(txt.get_type(), txt.get_class(), "one"))
  526. txt.add_rdata(Rdata(txt.get_type(), txt.get_class(), "two"))
  527. txt.add_rdata(Rdata(txt.get_type(), txt.get_class(), "three"))
  528. a = RRset(Name('d.example.org.'), self.__rrclass, RRType.A(),
  529. RRTTL(3600))
  530. a.add_rdata(Rdata(a.get_type(), a.get_class(), "192.0.2.1"))
  531. a.add_rdata(Rdata(a.get_type(), a.get_class(), "192.0.2.2"))
  532. diff = Diff(self, Name('example.org.'), single_update_mode=True)
  533. # adding a first should fail
  534. self.assertRaises(ValueError, diff.add_data, a)
  535. # But soa should work
  536. diff.add_data(self.__rrset_soa)
  537. # And then A should as well
  538. diff.add_data(self.__rrset3)
  539. diff.add_data(self.__rrset4)
  540. diff.add_data(self.__rrset5)
  541. # But another SOA should fail again
  542. self.assertRaises(ValueError, diff.add_data, self.__rrset_soa)
  543. # Same for delete
  544. self.assertRaises(ValueError, diff.delete_data, self.__rrset6)
  545. diff.delete_data(self.__rrset_soa)
  546. diff.delete_data(self.__rrset6)
  547. diff.delete_data(self.__rrset7)
  548. self.assertRaises(ValueError, diff.delete_data, self.__rrset_soa)
  549. # Not compacted yet, so the buffers should be as we
  550. # filled them
  551. (delbuf, addbuf) = diff.get_single_update_buffers()
  552. self.assertEqual([('delete', self.__rrset_soa),
  553. ('delete', self.__rrset6),
  554. ('delete', self.__rrset7)], delbuf)
  555. self.assertEqual([('add', self.__rrset_soa),
  556. ('add', self.__rrset3),
  557. ('add', self.__rrset4),
  558. ('add', self.__rrset5)], addbuf)
  559. # Compact should compact the A records in both buffers
  560. diff.compact()
  561. (delbuf, addbuf) = diff.get_single_update_buffers()
  562. # need rrset equality again :/
  563. self.assertEqual(2, len(delbuf))
  564. self.assertEqual(2, len(delbuf[0]))
  565. self.assertEqual('delete', delbuf[0][0])
  566. self.assertEqual(self.__rrset_soa.to_text(), delbuf[0][1].to_text())
  567. self.assertEqual(2, len(delbuf[1]))
  568. self.assertEqual('delete', delbuf[1][0])
  569. self.assertEqual(a.to_text(), delbuf[1][1].to_text())
  570. self.assertEqual(2, len(addbuf))
  571. self.assertEqual(2, len(addbuf[0]))
  572. self.assertEqual('add', addbuf[0][0])
  573. self.assertEqual(self.__rrset_soa.to_text(), addbuf[0][1].to_text())
  574. self.assertEqual(2, len(addbuf[1]))
  575. self.assertEqual('add', addbuf[1][0])
  576. self.assertEqual(txt.to_text(), addbuf[1][1].to_text())
  577. # Apply should reset the buffers
  578. diff.apply()
  579. (delbuf, addbuf) = diff.get_single_update_buffers()
  580. self.assertEqual([], delbuf)
  581. self.assertEqual([], addbuf)
  582. # Now the change has been applied, and the buffers are cleared,
  583. # Adding non-SOA records should fail again.
  584. self.assertRaises(ValueError, diff.add_data, a)
  585. self.assertRaises(ValueError, diff.delete_data, a)
  586. def test_add_delete_same(self):
  587. '''
  588. Test that if a record is added, then deleted, it is not added to
  589. both buffers, but remove from the addition, and vice versa
  590. '''
  591. diff = Diff(self, Name('example.org.'), single_update_mode=True)
  592. # Need SOA records first
  593. diff.delete_data(self.__rrset_soa)
  594. diff.add_data(self.__rrset_soa)
  595. deletions, additions = diff.get_single_update_buffers()
  596. self.assertEqual(1, len(deletions))
  597. self.assertEqual(1, len(additions))
  598. diff.add_data(self.__rrset1)
  599. deletions, additions = diff.get_single_update_buffers()
  600. self.assertEqual(1, len(deletions))
  601. self.assertEqual(2, len(additions))
  602. diff.delete_data(self.__rrset1)
  603. deletions, additions = diff.get_single_update_buffers()
  604. self.assertEqual(1, len(deletions))
  605. self.assertEqual(1, len(additions))
  606. diff.delete_data(self.__rrset2)
  607. deletions, additions = diff.get_single_update_buffers()
  608. self.assertEqual(2, len(deletions))
  609. self.assertEqual(1, len(additions))
  610. diff.add_data(self.__rrset2)
  611. deletions, additions = diff.get_single_update_buffers()
  612. self.assertEqual(1, len(deletions))
  613. self.assertEqual(1, len(additions))
  614. def test_find(self):
  615. diff = Diff(self, Name('example.org.'))
  616. name = Name('www.example.org.')
  617. rrtype = RRType.A()
  618. self.assertFalse(self.__find_called)
  619. self.assertEqual(None, self.__find_name)
  620. self.assertEqual(None, self.__find_type)
  621. self.assertEqual(None, self.__find_options)
  622. self.assertEqual("find_return", diff.find(name, rrtype))
  623. self.assertTrue(self.__find_called)
  624. self.assertEqual(name, self.__find_name)
  625. self.assertEqual(rrtype, self.__find_type)
  626. self.assertEqual(ZoneFinder.NO_WILDCARD | ZoneFinder.FIND_GLUE_OK,
  627. self.__find_options)
  628. def test_find_options(self):
  629. diff = Diff(self, Name('example.org.'))
  630. name = Name('foo.example.org.')
  631. rrtype = RRType.TXT()
  632. options = ZoneFinder.NO_WILDCARD
  633. self.assertEqual("find_return", diff.find(name, rrtype, options))
  634. self.assertTrue(self.__find_called)
  635. self.assertEqual(name, self.__find_name)
  636. self.assertEqual(rrtype, self.__find_type)
  637. self.assertEqual(options, self.__find_options)
  638. def test_find_all(self):
  639. diff = Diff(self, Name('example.org.'))
  640. name = Name('www.example.org.')
  641. self.assertFalse(self.__find_all_called)
  642. self.assertEqual(None, self.__find_all_name)
  643. self.assertEqual(None, self.__find_all_options)
  644. self.assertEqual("find_all_return", diff.find_all(name))
  645. self.assertTrue(self.__find_all_called)
  646. self.assertEqual(name, self.__find_all_name)
  647. self.assertEqual(ZoneFinder.NO_WILDCARD | ZoneFinder.FIND_GLUE_OK,
  648. self.__find_all_options)
  649. def test_find_all_options(self):
  650. diff = Diff(self, Name('example.org.'))
  651. name = Name('www.example.org.')
  652. options = isc.datasrc.ZoneFinder.NO_WILDCARD
  653. self.assertFalse(self.__find_all_called)
  654. self.assertEqual(None, self.__find_all_name)
  655. self.assertEqual(None, self.__find_all_options)
  656. self.assertEqual("find_all_return", diff.find_all(name, options))
  657. self.assertTrue(self.__find_all_called)
  658. self.assertEqual(name, self.__find_all_name)
  659. self.assertEqual(options, self.__find_all_options)
  660. def __common_remove_rr_from_buffer(self, diff, add_method, remove_method,
  661. op_str, buf_nr):
  662. add_method(self.__rrset_soa)
  663. add_method(self.__rrset2)
  664. add_method(self.__rrset3)
  665. add_method(self.__rrset4)
  666. # sanity check
  667. buf = diff.get_single_update_buffers()[buf_nr]
  668. expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
  669. self.__rrset2,
  670. self.__rrset3,
  671. self.__rrset4 ] ]
  672. result = [ (op, str(rr)) for (op, rr) in buf ]
  673. self.assertEqual(expected, result)
  674. # remove one
  675. self.assertTrue(remove_method(self.__rrset2))
  676. buf = diff.get_single_update_buffers()[buf_nr]
  677. expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
  678. self.__rrset3,
  679. self.__rrset4 ] ]
  680. result = [ (op, str(rr)) for (op, rr) in buf ]
  681. self.assertEqual(expected, result)
  682. # SOA should not be removed
  683. self.assertFalse(remove_method(self.__rrset_soa))
  684. buf = diff.get_single_update_buffers()[buf_nr]
  685. expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
  686. self.__rrset3,
  687. self.__rrset4 ] ]
  688. result = [ (op, str(rr)) for (op, rr) in buf ]
  689. self.assertEqual(expected, result)
  690. # remove another
  691. self.assertTrue(remove_method(self.__rrset4))
  692. buf = diff.get_single_update_buffers()[buf_nr]
  693. expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
  694. self.__rrset3 ] ]
  695. result = [ (op, str(rr)) for (op, rr) in buf ]
  696. self.assertEqual(expected, result)
  697. # remove nonexistent should return False
  698. self.assertFalse(remove_method(self.__rrset4))
  699. buf = diff.get_single_update_buffers()[buf_nr]
  700. expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
  701. self.__rrset3 ] ]
  702. result = [ (op, str(rr)) for (op, rr) in buf ]
  703. self.assertEqual(expected, result)
  704. def test_remove_rr_from_additions(self):
  705. diff = Diff(self, Name('example.org'), single_update_mode=True)
  706. self.__common_remove_rr_from_buffer(diff, diff.add_data,
  707. diff._remove_rr_from_additions,
  708. 'add', 1)
  709. def test_remove_rr_from_deletions(self):
  710. diff = Diff(self, Name('example.org'), single_update_mode=True)
  711. self.__common_remove_rr_from_buffer(diff, diff.delete_data,
  712. diff._remove_rr_from_deletions,
  713. 'delete', 0)
  714. def __create_find(self, result, rrset, flags):
  715. '''
  716. Overwrites the local find() method with a method that returns
  717. the tuple (result, rrset, flags)
  718. '''
  719. def new_find(name, rrtype, fflags):
  720. return (result, rrset, flags)
  721. self.find = new_find
  722. def __create_find_all(self, result, rrsets, flags):
  723. '''
  724. Overwrites the local find() method with a method that returns
  725. the tuple (result, rrsets, flags)
  726. '''
  727. def new_find_all(name, fflags):
  728. return (result, rrsets, flags)
  729. self.find_all = new_find_all
  730. def __check_find_call(self, method, query_rrset, expected_rcode,
  731. expected_rdatas=None):
  732. '''
  733. Helper for find tests; calls the given method with the name and
  734. type of the given rrset. Checks for the given rcode.
  735. If expected_rdatas is not none, the result name, and type are
  736. checked to match the given rrset ones, and the rdatas are checked
  737. to be equal.
  738. The given method must have the same arguments and return type
  739. as find()
  740. '''
  741. result, rrset, _ = method(query_rrset.get_name(),
  742. query_rrset.get_type())
  743. self.assertEqual(expected_rcode, result)
  744. if expected_rdatas is not None:
  745. self.assertEqual(query_rrset.get_name(), rrset.get_name())
  746. self.assertEqual(query_rrset.get_type(), rrset.get_type())
  747. if expected_rdatas is not None:
  748. self.assertEqual(expected_rdatas, rrset.get_rdata())
  749. else:
  750. self.assertEqual(None, rrset)
  751. def __check_find_all_call(self, method, query_rrset, expected_rcode,
  752. expected_rrs=[]):
  753. '''
  754. Helper for find tests; calls the given method with the name and
  755. type of the given rrset. Checks for the given rcode.
  756. If expected_rdatas is not none, the result name, and type are
  757. checked to match the given rrset ones, and the rdatas are checked
  758. to be equal.
  759. The given method must have the same arguments and return type
  760. as find()
  761. '''
  762. result, rrsets, _ = method(query_rrset.get_name())
  763. self.assertEqual(expected_rcode, result)
  764. # We have no real equality function for rrsets, but since
  765. # the rrsets in question are themselves returns, pointer equality
  766. # works as well
  767. self.assertEqual(expected_rrs, rrsets)
  768. def test_find_updated_existing_data(self):
  769. '''
  770. Tests whether existent data is updated with the additions and
  771. deletions from the Diff
  772. '''
  773. diff = Diff(self, Name('example.org'), single_update_mode=True)
  774. diff.add_data(self.__rrset_soa)
  775. diff.delete_data(self.__rrset_soa)
  776. # override the actual find method
  777. self.__create_find(ZoneFinder.SUCCESS, self.__rrset3, 0)
  778. # sanity check
  779. self.__check_find_call(diff.find_updated, self.__rrset3,
  780. ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
  781. # check that normal find also returns the original data
  782. self.__check_find_call(diff.find, self.__rrset3,
  783. ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
  784. # Adding another should have it returned in the find_updated
  785. diff.add_data(self.__rrset4)
  786. self.__check_find_call(diff.find_updated, self.__rrset3,
  787. ZoneFinder.SUCCESS, self.__rrset3.get_rdata() +
  788. self.__rrset4.get_rdata())
  789. # check that normal find still returns the original data
  790. self.__check_find_call(diff.find, self.__rrset3,
  791. ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
  792. # Adding a different type should have no effect
  793. diff.add_data(self.__rrset2)
  794. self.__check_find_call(diff.find_updated, self.__rrset3,
  795. ZoneFinder.SUCCESS, self.__rrset3.get_rdata() +
  796. self.__rrset4.get_rdata())
  797. # check that normal find still returns the original data
  798. self.__check_find_call(diff.find, self.__rrset3,
  799. ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
  800. # Deleting 3 now should result in only 4 being updated
  801. diff.delete_data(self.__rrset3)
  802. self.__check_find_call(diff.find_updated, self.__rrset3,
  803. ZoneFinder.SUCCESS, self.__rrset4.get_rdata())
  804. # check that normal find still returns the original data
  805. self.__check_find_call(diff.find, self.__rrset3,
  806. ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
  807. # Deleting 4 now should result in empty rrset
  808. diff.delete_data(self.__rrset4)
  809. self.__check_find_call(diff.find_updated, self.__rrset3,
  810. ZoneFinder.NXRRSET)
  811. # check that normal find still returns the original data
  812. self.__check_find_call(diff.find, self.__rrset3,
  813. ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
  814. def test_find_updated_nonexistent_data(self):
  815. '''
  816. Test whether added data for a query that would originally result
  817. in NXDOMAIN works
  818. '''
  819. diff = Diff(self, Name('example.org'), single_update_mode=True)
  820. diff.add_data(self.__rrset_soa)
  821. diff.delete_data(self.__rrset_soa)
  822. # override the actual find method
  823. self.__create_find(ZoneFinder.NXDOMAIN, None, 0)
  824. # Sanity check
  825. self.__check_find_call(diff.find_updated, self.__rrset3,
  826. ZoneFinder.NXDOMAIN)
  827. self.__check_find_call(diff.find, self.__rrset3,
  828. ZoneFinder.NXDOMAIN)
  829. # Add data and see it is returned
  830. diff.add_data(self.__rrset3)
  831. self.__check_find_call(diff.find_updated, self.__rrset3,
  832. ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
  833. self.__check_find_call(diff.find, self.__rrset3,
  834. ZoneFinder.NXDOMAIN)
  835. # Add unrelated data, result should be the same
  836. diff.add_data(self.__rrset2)
  837. self.__check_find_call(diff.find_updated, self.__rrset3,
  838. ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
  839. self.__check_find_call(diff.find, self.__rrset3,
  840. ZoneFinder.NXDOMAIN)
  841. # Remove, result should now be NXDOMAIN again
  842. diff.delete_data(self.__rrset3)
  843. result, rrset, _ = diff.find_updated(self.__rrset3.get_name(),
  844. self.__rrset3.get_type())
  845. self.__check_find_call(diff.find_updated, self.__rrset3,
  846. ZoneFinder.NXDOMAIN)
  847. self.__check_find_call(diff.find, self.__rrset3,
  848. ZoneFinder.NXDOMAIN)
  849. def test_find_updated_other(self):
  850. '''
  851. Test that any other ZoneFinder.result code is directly
  852. passed on.
  853. '''
  854. diff = Diff(self, Name('example.org'), single_update_mode=True)
  855. # Add and delete some data to make sure it's not used
  856. diff.add_data(self.__rrset_soa)
  857. diff.add_data(self.__rrset3)
  858. diff.delete_data(self.__rrset_soa)
  859. diff.delete_data(self.__rrset2)
  860. for rcode in [ ZoneFinder.DELEGATION,
  861. ZoneFinder.CNAME,
  862. ZoneFinder.DNAME ]:
  863. # override the actual find method
  864. self.__create_find(rcode, None, 0)
  865. self.__check_find_call(diff.find, self.__rrset3, rcode)
  866. self.__check_find_call(diff.find_updated, self.__rrset3, rcode)
  867. def test_find_all_existing_data(self):
  868. diff = Diff(self, Name('example.org'), single_update_mode=True)
  869. diff.add_data(self.__rrset_soa)
  870. diff.delete_data(self.__rrset_soa)
  871. # override the actual find method
  872. self.__create_find_all(ZoneFinder.SUCCESS, [ self.__rrset3 ], 0)
  873. # Sanity check
  874. result, rrsets, _ = diff.find_all_updated(self.__rrset3.get_name())
  875. self.assertEqual(ZoneFinder.SUCCESS, result)
  876. self.assertEqual([self.__rrset3], rrsets)
  877. self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
  878. ZoneFinder.SUCCESS, [self.__rrset3])
  879. self.__check_find_all_call(diff.find_all, self.__rrset3,
  880. ZoneFinder.SUCCESS, [self.__rrset3])
  881. # Add a second rr with different type at same name
  882. add_rrset = RRset(self.__rrset3.get_name(), self.__rrclass,
  883. RRType.A(), self.__ttl)
  884. add_rrset.add_rdata(Rdata(RRType.A(), self.__rrclass, "192.0.2.2"))
  885. diff.add_data(add_rrset)
  886. self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
  887. ZoneFinder.SUCCESS,
  888. [self.__rrset3, add_rrset])
  889. self.__check_find_all_call(diff.find_all, self.__rrset3,
  890. ZoneFinder.SUCCESS, [self.__rrset3])
  891. # Remove original one
  892. diff.delete_data(self.__rrset3)
  893. self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
  894. ZoneFinder.SUCCESS, [add_rrset])
  895. self.__check_find_all_call(diff.find_all, self.__rrset3,
  896. ZoneFinder.SUCCESS, [self.__rrset3])
  897. # And remove new one, result should then become NXDOMAIN
  898. diff.delete_data(add_rrset)
  899. result, rrsets, _ = diff.find_all_updated(self.__rrset3.get_name())
  900. self.assertEqual(ZoneFinder.NXDOMAIN, result)
  901. self.assertEqual([ ], rrsets)
  902. self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
  903. ZoneFinder.NXDOMAIN)
  904. self.__check_find_all_call(diff.find_all, self.__rrset3,
  905. ZoneFinder.SUCCESS, [self.__rrset3])
  906. def test_find_all_nonexistent_data(self):
  907. diff = Diff(self, Name('example.org'), single_update_mode=True)
  908. diff.add_data(self.__rrset_soa)
  909. diff.delete_data(self.__rrset_soa)
  910. self.__create_find_all(ZoneFinder.NXDOMAIN, [], 0)
  911. # Sanity check
  912. self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
  913. ZoneFinder.NXDOMAIN)
  914. self.__check_find_all_call(diff.find_all, self.__rrset2,
  915. ZoneFinder.NXDOMAIN)
  916. # Adding data should change the result
  917. diff.add_data(self.__rrset2)
  918. self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
  919. ZoneFinder.SUCCESS, [ self.__rrset2 ])
  920. self.__check_find_all_call(diff.find_all, self.__rrset2,
  921. ZoneFinder.NXDOMAIN)
  922. # Adding data at other name should not
  923. diff.add_data(self.__rrset3)
  924. self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
  925. ZoneFinder.SUCCESS, [ self.__rrset2 ])
  926. self.__check_find_all_call(diff.find_all, self.__rrset2,
  927. ZoneFinder.NXDOMAIN)
  928. # Deleting it should revert to original
  929. diff.delete_data(self.__rrset2)
  930. self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
  931. ZoneFinder.NXDOMAIN)
  932. self.__check_find_all_call(diff.find_all, self.__rrset2,
  933. ZoneFinder.NXDOMAIN)
  934. def test_find_all_other_results(self):
  935. '''
  936. Any result code other than SUCCESS and NXDOMAIN should cause
  937. the results to be passed on directly
  938. '''
  939. diff = Diff(self, Name('example.org'), single_update_mode=True)
  940. # Add and delete some data to make sure it's not used
  941. diff.add_data(self.__rrset_soa)
  942. diff.add_data(self.__rrset3)
  943. diff.delete_data(self.__rrset_soa)
  944. diff.delete_data(self.__rrset2)
  945. for rcode in [ ZoneFinder.NXRRSET,
  946. ZoneFinder.DELEGATION,
  947. ZoneFinder.CNAME,
  948. ZoneFinder.DNAME ]:
  949. # override the actual find method
  950. self.__create_find_all(rcode, [], 0)
  951. self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
  952. rcode)
  953. self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
  954. rcode)
  955. self.__check_find_all_call(diff.find_all, self.__rrset2,
  956. rcode)
  957. self.__check_find_all_call(diff.find_all, self.__rrset3,
  958. rcode)
  959. if __name__ == "__main__":
  960. isc.log.init("bind10")
  961. isc.log.resetUnitTestRootLogger()
  962. unittest.main()