Browse Source

[1288] disable adjust_ttl in get_iterator to preserve the original TTLs
of the same name/type when they are different (unusual for modern zones
but possible). added test case for it, and for that purpose added sqlite3
test data and refactored the test framework a bit.

JINMEI Tatuya 13 years ago
parent
commit
045c30f0df

+ 5 - 0
src/bin/xfrout/tests/Makefile.am

@@ -2,6 +2,10 @@ PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = xfrout_test.py
 noinst_SCRIPTS = $(PYTESTS)
 
+EXTRA_DIST = testdata/test.sqlite3
+# This one is actually not necessary, but added for reference
+EXTRA_DIST += testdata/example.com
+
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
 LIBRARY_PATH_PLACEHOLDER =
@@ -24,5 +28,6 @@ endif
 	B10_FROM_BUILD=$(abs_top_builddir) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/xfrout:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
+	TESTDATASRCDIR=$(abs_srcdir)/testdata/ \
 	$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 	done

+ 6 - 0
src/bin/xfrout/tests/testdata/example.com

@@ -0,0 +1,6 @@
+;; This is the source of a zone stored in test.sqlite3.  It's provided
+;; for reference purposes only.
+example.com.         3600  IN  SOA a.dns.example.com. mail.example.com. 1 1 1 1 1
+example.com.         3600  IN  NS  a.dns.example.com.
+a.dns.example.com.   3600  IN  A    192.0.2.1
+a.dns.example.com.   7200  IN  A    192.0.2.2

BIN
src/bin/xfrout/tests/testdata/test.sqlite3


+ 44 - 3
src/bin/xfrout/tests/xfrout_test.py.in

@@ -27,6 +27,7 @@ import xfrout
 import isc.log
 import isc.acl.dns
 
+TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
 TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
 
 # our fake socket, where we can read and insert messages
@@ -55,10 +56,10 @@ class MySocket():
         self.sendqueue = self.sendqueue[size:]
         return result
 
-    def read_msg(self):
+    def read_msg(self, parse_options=Message.PARSE_DEFAULT):
         sent_data = self.readsent()
         get_msg = Message(Message.PARSE)
-        get_msg.from_wire(bytes(sent_data[2:]))
+        get_msg.from_wire(bytes(sent_data[2:]), parse_options)
         return get_msg
 
     def clear_send(self):
@@ -135,7 +136,13 @@ class Dbserver:
     def decrease_transfers_counter(self):
         self.transfer_counter -= 1
 
-class TestXfroutSession(unittest.TestCase):
+class TestXfroutSessionBase(unittest.TestCase):
+    '''Base classs for tests related to xfrout sessions
+
+    This class defines common setup/teadown and utility methods.  Actual
+    tests are delegated to subclasses.
+
+    '''
     def getmsg(self):
         msg = Message(Message.PARSE)
         msg.from_wire(self.mdata)
@@ -188,12 +195,17 @@ class TestXfroutSession(unittest.TestCase):
                                        'master.Example.com. ' +
                                        'admin.exAmple.com. ' +
                                        '1234 3600 1800 2419200 7200'))
+        # some test replaces a module-wide function.  We should ensure the
+        # original is used elsewhere.
+        self.orig_get_rrset_len = xfrout.get_rrset_len
 
     def tearDown(self):
+        xfrout.get_rrset_len = self.orig_get_rrset_len
         # transfer_counter must be always be reset no matter happens within
         # the XfroutSession object.  We check the condition here.
         self.assertEqual(0, self.xfrsess._server.transfer_counter)
 
+class TestXfroutSession(TestXfroutSessionBase):
     def test_quota_error(self):
         '''Emulating the server being too busy.
 
@@ -702,6 +714,35 @@ class TestXfroutSession(unittest.TestCase):
         # and it should not have sent anything else
         self.assertEqual(0, len(self.sock.sendqueue))
 
+
+class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
+    '''Tests for XFR-out sessions using an SQLite3 DB.
+
+    These are provided mainly to confirm the implementation actually works
+    in an environment closer to actual operational environments.  So we
+    only check a few common cases; other details are tested using mock
+    data sources.
+
+    '''
+    def setUp(self):
+        super().setUp()
+        self.xfrsess._request_data = self.mdata
+        self.xfrsess._server.get_db_file = lambda : TESTDATA_SRCDIR + \
+            'test.sqlite3'
+
+    def test_axfr_normal_session(self):
+        XfroutSession._handle(self.xfrsess)
+        response = self.sock.read_msg(Message.PRESERVE_ORDER);
+        self.assertEqual(Rcode.NOERROR(), response.get_rcode())
+        # This zone contains two A RRs for the same name with different TTLs.
+        # These TTLs should be preseved in the AXFR stream.
+        actual_ttls = []
+        for rr in response.get_section(Message.SECTION_ANSWER):
+            if rr.get_type() == RRType.A() and \
+                    not rr.get_ttl() in actual_ttls:
+                actual_ttls.append(rr.get_ttl().get_value())
+        self.assertEqual([3600, 7200], sorted(actual_ttls))
+
 class MyUnixSockServer(UnixSockServer):
     def __init__(self):
         self._shutdown_event = threading.Event()

+ 5 - 1
src/bin/xfrout/xfrout.py.in

@@ -316,7 +316,11 @@ class XfroutSession():
             self._server.get_db_file() + '"}'
         self._datasrc_client = self.ClientClass('sqlite3', datasrc_config)
         try:
-            self._iterator = self._datasrc_client.get_iterator(zone_name)
+            # Note that we disable 'adjust_ttl'.  In xfr-out we need to
+            # preserve as many things as possible (even if it's half broken)
+            # stored in the zone.
+            self._iterator = self._datasrc_client.get_iterator(zone_name,
+                                                               False)
         except isc.datasrc.Error:
             # If the current name server does not have authority for the
             # zone, xfrout can't serve for it, return rcode NOTAUTH.