zone_loader_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. # Copyright (C) 2012 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. import isc.log
  16. import isc.datasrc
  17. import isc.dns
  18. import os
  19. import unittest
  20. import shutil
  21. import sys
  22. # Constants and common data used in tests
  23. TESTDATA_PATH = os.environ['TESTDATA_PATH']
  24. TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH']
  25. ZONE_FILE = TESTDATA_PATH + '/example.com'
  26. STATIC_ZONE_FILE = '../../../../datasrc/static.zone'
  27. SOURCE_DB_FILE = TESTDATA_PATH + '/example.com.source.sqlite3'
  28. ORIG_DB_FILE = TESTDATA_PATH + '/example.com.sqlite3'
  29. DB_FILE = TESTDATA_WRITE_PATH + '/zoneloadertest.sqlite3'
  30. DB_CLIENT_CONFIG = '{ "database_file": "' + DB_FILE + '" }'
  31. DB_SOURCE_CLIENT_CONFIG = '{ "database_file": "' + SOURCE_DB_FILE + '" }'
  32. ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
  33. 'admin.example.com. 1234 3600 1800 2419200 7200\n'
  34. NEW_SOA_TXT = 'example.com. 1000 IN SOA a.dns.example.com. ' +\
  35. 'mail.example.com. 1 1 1 1 1\n'
  36. PROGRESS_UNKNOWN = isc.datasrc.ZoneLoader.PROGRESS_UNKNOWN
  37. class ZoneLoaderTests(unittest.TestCase):
  38. def setUp(self):
  39. self.test_name = isc.dns.Name("example.com")
  40. self.test_file = ZONE_FILE
  41. self.client = isc.datasrc.DataSourceClient("sqlite3", DB_CLIENT_CONFIG)
  42. # Make a fresh copy of the database
  43. shutil.copyfile(ORIG_DB_FILE, DB_FILE)
  44. # Some tests set source client; if so, check refcount in
  45. # tearDown, since most tests don't, set it to None by default.
  46. self.source_client = None
  47. self.loader = None
  48. self.assertEqual(2, sys.getrefcount(self.test_name))
  49. self.assertEqual(2, sys.getrefcount(self.client))
  50. def tearDown(self):
  51. # We can only create 1 loader at a time (it locks the db), and it
  52. # may not be destroyed immediately if there is an exception in a
  53. # test. So the tests that do create one should put it in self, and
  54. # we make sure to invalidate it here.
  55. # We can also use this to check reference counts; if a loader
  56. # exists, the client and source client (if any) should have
  57. # an increased reference count (but the name should not, this
  58. # is only used in the initializer)
  59. if self.loader is not None:
  60. self.assertEqual(2, sys.getrefcount(self.test_name))
  61. self.assertEqual(3, sys.getrefcount(self.client))
  62. if self.source_client is not None:
  63. self.assertEqual(3, sys.getrefcount(self.source_client))
  64. self.loader = None
  65. # Now that the loader has been destroyed, the refcounts
  66. # of its arguments should be back to their originals
  67. self.assertEqual(2, sys.getrefcount(self.test_name))
  68. self.assertEqual(2, sys.getrefcount(self.client))
  69. if self.source_client is not None:
  70. self.assertEqual(2, sys.getrefcount(self.source_client))
  71. def test_bad_constructor(self):
  72. self.assertRaises(TypeError, isc.datasrc.ZoneLoader)
  73. self.assertRaises(TypeError, isc.datasrc.ZoneLoader, 1)
  74. self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
  75. None, self.test_name, self.test_file)
  76. self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
  77. self.client, None, self.test_file)
  78. self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
  79. self.client, self.test_name, None)
  80. self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
  81. self.client, self.test_name, self.test_file, 1)
  82. def check_zone_soa(self, soa_txt):
  83. """
  84. Check that the given SOA RR exists and matches the expected string
  85. """
  86. result, finder = self.client.find_zone(self.test_name)
  87. self.assertEqual(self.client.SUCCESS, result)
  88. result, rrset, _ = finder.find(self.test_name, isc.dns.RRType.SOA)
  89. self.assertEqual(finder.SUCCESS, result)
  90. self.assertEqual(soa_txt, rrset.to_text())
  91. def check_load(self):
  92. self.check_zone_soa(ORIG_SOA_TXT)
  93. self.loader.load()
  94. self.check_zone_soa(NEW_SOA_TXT)
  95. # And after that, it should throw
  96. self.assertRaises(isc.dns.InvalidOperation, self.loader.load)
  97. def test_load_from_file(self):
  98. self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
  99. self.test_file)
  100. self.assertEqual(0, self.loader.get_rr_count())
  101. self.assertEqual(0, self.loader.get_progress())
  102. self.check_load()
  103. # Expected values are hardcoded, taken from the test zone file,
  104. # assuming it won't change too often. progress should reach 100% (=1).
  105. self.assertEqual(8, self.loader.get_rr_count())
  106. self.assertEqual(1, self.loader.get_progress())
  107. def test_load_from_client(self):
  108. self.source_client = isc.datasrc.DataSourceClient('sqlite3',
  109. DB_SOURCE_CLIENT_CONFIG)
  110. self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
  111. self.source_client)
  112. self.assertEqual(0, self.loader.get_rr_count())
  113. self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
  114. self.check_load()
  115. # In case of loading from another data source, progress is unknown.
  116. self.assertEqual(8, self.loader.get_rr_count())
  117. self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
  118. def check_load_incremental(self, from_file=True):
  119. # New zone has 8 RRs
  120. # After 5, it should return False
  121. self.assertFalse(self.loader.load_incremental(5))
  122. # New zone should not have been loaded yet
  123. self.check_zone_soa(ORIG_SOA_TXT)
  124. # In case it's from a zone file, get_progress should be in the middle
  125. # of (0, 1). expected value is taken from the test zone file
  126. # (total size = 422, current position = 288)
  127. if from_file:
  128. # To avoid any false positive due to rounding errors, we convert
  129. # them to near integers between 0 and 100.
  130. self.assertEqual(int((288 * 100) / 422),
  131. int(self.loader.get_progress() * 100))
  132. # Also check the return value has higher precision.
  133. self.assertNotEqual(int(288 * 100 / 422),
  134. 100 * self.loader.get_progress())
  135. # After 5 more, it should return True (only having read 3)
  136. self.assertTrue(self.loader.load_incremental(5))
  137. # New zone should now be loaded
  138. self.check_zone_soa(NEW_SOA_TXT)
  139. # And after that, it should throw
  140. self.assertRaises(isc.dns.InvalidOperation,
  141. self.loader.load_incremental, 5)
  142. def test_load_from_file_incremental(self):
  143. # Create loader and load the zone
  144. self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
  145. self.test_file)
  146. self.check_load_incremental()
  147. def test_load_from_client_incremental(self):
  148. self.source_client = isc.datasrc.DataSourceClient('sqlite3',
  149. DB_SOURCE_CLIENT_CONFIG)
  150. self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
  151. self.source_client)
  152. self.check_load_incremental(False)
  153. def test_bad_file(self):
  154. self.check_zone_soa(ORIG_SOA_TXT)
  155. self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
  156. 'no such file')
  157. self.assertRaises(isc.datasrc.MasterFileError, self.loader.load)
  158. self.check_zone_soa(ORIG_SOA_TXT)
  159. def test_bad_file_incremental(self):
  160. self.check_zone_soa(ORIG_SOA_TXT)
  161. self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
  162. 'no such file')
  163. self.assertRaises(isc.datasrc.MasterFileError,
  164. self.loader.load_incremental, 1)
  165. self.check_zone_soa(ORIG_SOA_TXT)
  166. def test_no_such_zone_in_target(self):
  167. self.assertRaises(isc.datasrc.Error, isc.datasrc.ZoneLoader,
  168. self.client, isc.dns.Name("unknownzone"),
  169. self.test_file)
  170. def test_no_such_zone_in_source(self):
  171. # Reuse a zone that exists in target but not in source
  172. zone_name = isc.dns.Name("sql1.example.com")
  173. self.source_client = isc.datasrc.DataSourceClient('sqlite3',
  174. DB_SOURCE_CLIENT_CONFIG)
  175. # make sure the zone exists in the target
  176. found, _ = self.client.find_zone(zone_name)
  177. self.assertEqual(self.client.SUCCESS, found)
  178. # And that it does not in the source
  179. found, _ = self.source_client.find_zone(zone_name)
  180. self.assertNotEqual(self.source_client.SUCCESS, found)
  181. self.assertRaises(isc.datasrc.Error, isc.datasrc.ZoneLoader,
  182. self.client, zone_name, self.source_client)
  183. def test_no_ds_load_support(self):
  184. # This may change in the future, but atm, the in-mem ds does
  185. # not support the API the zone loader uses (it has direct load calls)
  186. inmem_client = isc.datasrc.DataSourceClient('memory',
  187. '{ "type": "memory" }');
  188. self.assertRaises(isc.datasrc.NotImplemented,
  189. isc.datasrc.ZoneLoader,
  190. inmem_client, self.test_name, self.test_file)
  191. def test_wrong_class_from_file(self):
  192. # If the file has wrong class, it is not detected until load time
  193. self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
  194. self.test_file + '.ch')
  195. self.assertRaises(isc.datasrc.MasterFileError, self.loader.load)
  196. def test_wrong_class_from_client(self):
  197. # For ds->ds loading, wrong class is detected upon construction
  198. # Need a bit of the extended setup for CH source client
  199. clientlist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.CH)
  200. clientlist.configure('[ { "type": "static", "params": "' +
  201. STATIC_ZONE_FILE +'" } ]', False)
  202. self.source_client, _, _ = clientlist.find(isc.dns.Name("bind."),
  203. False, False)
  204. self.assertRaises(isc.dns.InvalidParameter, isc.datasrc.ZoneLoader,
  205. self.client, isc.dns.Name("bind."),
  206. self.source_client)
  207. def test_exception(self):
  208. # Just check if masterfileerror is subclass of datasrc.Error
  209. self.assertTrue(issubclass(isc.datasrc.MasterFileError,
  210. isc.datasrc.Error))
  211. if __name__ == "__main__":
  212. isc.log.init("bind10")
  213. isc.log.resetUnitTestRootLogger()
  214. unittest.main()