RRTTL(3600))
self.ns_rrset.add_rdata(Rdata(RRType.NS(), TEST_RRCLASS,
'ns.example.com'))
+ self.a_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.A(),
+ RRTTL(3600))
+ self.a_rrset.add_rdata(Rdata(RRType.A(), TEST_RRCLASS, '192.0.2.1'))
+
self.conn._datasrc_client = MockDataSourceClient()
- self.conn._diff = Diff(MockDataSourceClient(), TEST_ZONE_NAME)
+ self.conn._diff = Diff(self.conn._datasrc_client, TEST_ZONE_NAME)
+class TestXfrinStateBase(TestXfrinState):
+ def setUp(self):
+ super().setUp()
+
+ def test_handle_rr_on_base(self):
+ # The base version of handle_rr() isn't supposed to be called
+ # directly (the argument doesn't matter in this test)
+ self.assertRaises(XfrinException, XfrinState().handle_rr, None)
+
class TestXfrinInitialSOA(TestXfrinState):
def setUp(self):
super().setUp()
# the initial SOA. Should switch to AXFR.
self.assertFalse(self.state.handle_rr(self.conn, self.ns_rrset))
self.assertEqual(type(XfrinAXFR()), type(self.conn.get_xfrstate()))
+ # The Diff for AXFR should be created at this point
+ self.assertNotEqual(None, self.conn._diff)
def test_handle_ixfr_to_axfr_by_different_soa(self):
- # Response contains two consecutive SOA but the serial of the second
- # does not match the requested one. The only possible interpretation
- # at this point is that it's an AXFR-compatible IXFR that only
- # consists of the SOA RR. It will result in broken zone and should
- # be rejected anyway, but at this point we should switch to AXFR.
+ # An unusual case: Response contains two consecutive SOA but the
+ # serial of the second does not match the requested one. See
+ # the documentation for XfrinFirstData.handle_rr().
self.assertFalse(self.state.handle_rr(self.conn, soa_rrset))
self.assertEqual(type(XfrinAXFR()), type(self.conn.get_xfrstate()))
+ self.assertNotEqual(None, self.conn._diff)
def test_finish_message(self):
self.assertTrue(self.state.finish_message(self.conn))
self.conn._handle_xfrin_responses()
self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
self.assertEqual([], self.conn._datasrc_client.diffs)
- self.check_diffs([[('delete', begin_soa_rrset), ('add', soa_rrset)]],
- self.conn._datasrc_client.committed_diffs)
+ check_diffs(self.assertEqual,
- [[('remove', begin_soa_rrset), ('add', soa_rrset)]],
++ [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
def test_ixfr_response_multi_sequences(self):
'''Similar to the previous case, but with multiple diff seqs.
self.conn._handle_xfrin_responses()
self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
self.assertEqual([], self.conn._datasrc_client.diffs)
- self.check_diffs([[('delete', begin_soa_rrset),
- ('delete', self._create_a('192.0.2.1')),
- ('add', self._create_soa('1231')),
- ('add', self._create_a('192.0.2.2'))],
- [('delete', self._create_soa('1231')),
- ('delete', self._create_a('192.0.2.3')),
- ('add', self._create_soa('1232')),
- ('add', self._create_a('192.0.2.4'))],
- [('delete', self._create_soa('1232')),
- ('delete', self._create_a('192.0.2.5')),
- ('add', soa_rrset),
- ('add', self._create_a('192.0.2.6'))]],
- self.conn._datasrc_client.committed_diffs)
+ check_diffs(self.assertEqual,
- [[('remove', begin_soa_rrset),
- ('remove', self._create_a('192.0.2.1')),
++ [[('delete', begin_soa_rrset),
++ ('delete', self._create_a('192.0.2.1')),
+ ('add', self._create_soa('1231')),
+ ('add', self._create_a('192.0.2.2'))],
- [('remove', self._create_soa('1231')),
- ('remove', self._create_a('192.0.2.3')),
++ [('delete', self._create_soa('1231')),
++ ('delete', self._create_a('192.0.2.3')),
+ ('add', self._create_soa('1232')),
+ ('add', self._create_a('192.0.2.4'))],
- [('remove', self._create_soa('1232')),
- ('remove', self._create_a('192.0.2.5')),
++ [('delete', self._create_soa('1232')),
++ ('delete', self._create_a('192.0.2.5')),
+ ('add', soa_rrset),
+ ('add', self._create_a('192.0.2.6'))]],
+ self.conn._datasrc_client.committed_diffs)
def test_ixfr_response_multi_messages(self):
'''Similar to the first case, but RRs span over multiple messages.
answers=[soa_rrset])
self.conn._handle_xfrin_responses()
self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
- self.check_diffs([[('delete', begin_soa_rrset), ('add', soa_rrset)]],
- self.conn._datasrc_client.committed_diffs)
+ check_diffs(self.assertEqual,
- [[('remove', begin_soa_rrset), ('add', soa_rrset)]],
++ [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
def test_ixfr_response_broken(self):
'''Test with a broken response.
self._create_a('192.0.2.1')])
self.assertRaises(XfrinProtocolError,
self.conn._handle_xfrin_responses)
- self.check_diffs([[('delete', begin_soa_rrset), ('add', soa_rrset)]],
- self.conn._datasrc_client.committed_diffs)
+ check_diffs(self.assertEqual,
- [[('remove', begin_soa_rrset), ('add', soa_rrset)]],
++ [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_to_axfr_response(self):
+ '''AXFR-style IXFR response.
+
+ It simply updates the zone's SOA one time.
+
+ '''
+ ns_rr = self._create_ns()
+ a_rr = self._create_a('192.0.2.1')
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, ns_rr, a_rr, soa_rrset])
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.diffs)
+ # The SOA should be added exactly once, and in our implementation
+ # it should be added at the end of the sequence.
+ check_diffs(self.assertEqual,
+ [[('add', ns_rr), ('add', a_rr), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_to_axfr_response_mismatch_soa(self):
+ '''AXFR-style IXFR response, but the two SOA are not the same.
+
+ In the current implementation, we accept it and use the second SOA.
+
+ '''
+ ns_rr = self._create_ns()
+ a_rr = self._create_a('192.0.2.1')
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, ns_rr, a_rr, begin_soa_rrset])
+ self.conn._handle_xfrin_responses()
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.diffs)
+ check_diffs(self.assertEqual,
+ [[('add', ns_rr), ('add', a_rr),
+ ('add', begin_soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
+
+ def test_ixfr_to_axfr_response_extra(self):
+ '''Test with an extra RR after the end of AXFR-style IXFR session.
+
+ The session should be rejected, and nothing should be committed.
+
+ '''
+ ns_rr = self._create_ns()
+ a_rr = self._create_a('192.0.2.1')
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.IXFR())],
+ answers=[soa_rrset, ns_rr, a_rr, soa_rrset, a_rr])
+ self.assertRaises(XfrinProtocolError,
+ self.conn._handle_xfrin_responses)
+ self.assertEqual(type(XfrinAXFREnd()), type(self.conn.get_xfrstate()))
+ self.assertEqual([], self.conn._datasrc_client.committed_diffs)
class TestIXFRSession(TestXfrinConnection):
'''Tests for a full IXFR session (query and response).
# Check some details of the IXFR protocol processing
self.assertEqual(type(XfrinIXFREnd()), type(self.conn.get_xfrstate()))
- self.check_diffs([[('delete', begin_soa_rrset), ('add', soa_rrset)]],
- self.conn._datasrc_client.committed_diffs)
+ check_diffs(self.assertEqual,
- [[('remove', begin_soa_rrset), ('add', soa_rrset)]],
++ [[('delete', begin_soa_rrset), ('add', soa_rrset)]],
+ self.conn._datasrc_client.committed_diffs)
# Check if the query was IXFR.
qdata = self.conn.query_data[2:]
self.__data_operations = []
self.__apply_called = False
self.__commit_called = False
+ self.__broken_called = False
+ self.__warn_called = False
+ self.__should_replace = False
# Some common values
self.__rrclass = RRClass.IN()
self.__type = RRType.A()
diff.compact()
check()
+ def test_wrong_class(self):
+ """
+ Test a wrong class of rrset is rejected.
+ """
+ diff = Diff(self, Name('example.org.'))
+ rrset = RRset(Name('a.example.org.'), RRClass.CH(), RRType.NS(),
+ self.__ttl)
+ rrset.add_rdata(Rdata(RRType.NS(), RRClass.CH(), 'ns.example.org.'))
+ self.assertRaises(ValueError, diff.add_data, rrset)
+ self.assertRaises(ValueError, diff.delete_data, rrset)
+
+ def __do_raise_test(self):
+ """
+ Do a raise test. Expects that one of the operations is exchanged for
+ broken version.
+ """
+ diff = Diff(self, Name('example.org.'))
+ diff.add_data(self.__rrset1)
+ diff.delete_data(self.__rrset2)
+ self.assertRaises(TestError, diff.commit)
+ self.assertTrue(self.__broken_called)
+ self.assertRaises(ValueError, diff.add_data, self.__rrset1)
+ self.assertRaises(ValueError, diff.delete_data, self.__rrset2)
+ self.assertRaises(ValueError, diff.commit)
+ self.assertRaises(ValueError, diff.apply)
+
+ def test_raise_add(self):
+ """
+ Test the exception from add_rrset is propagated and the diff can't be
+ used afterwards.
+ """
+ self.add_rrset = self.__broken_operation
+ self.__do_raise_test()
+
+ def test_raise_delete(self):
+ """
+ Test the exception from delete_rrset is propagated and the diff can't be
+ used afterwards.
+ """
+ self.delete_rrset = self.__broken_operation
+ self.__do_raise_test()
+
+ def test_raise_commit(self):
+ """
+ Test the exception from updater's commit gets propagated and it can't be
+ used afterwards.
+ """
+ self.commit = self.__broken_operation
+ self.__do_raise_test()
+
+ def test_ttl(self):
+ """
+ Test the TTL handling. A warn function should have been called if they
+ differ, but that's all, it should not crash or raise.
+ """
+ orig_logger = isc.xfrin.diff.logger
+ try:
+ isc.xfrin.diff.logger = self
+ diff = Diff(self, Name('example.org.'))
+ diff.add_data(self.__rrset1)
+ rrset2 = RRset(Name('a.example.org.'), self.__rrclass,
+ self.__type, RRTTL(120))
+ rrset2.add_rdata(Rdata(self.__type, self.__rrclass, '192.10.2.2'))
+ diff.add_data(rrset2)
+ rrset2 = RRset(Name('a.example.org.'), self.__rrclass,
+ self.__type, RRTTL(6000))
+ rrset2.add_rdata(Rdata(self.__type, self.__rrclass, '192.10.2.3'))
+ diff.add_data(rrset2)
+ # They should get compacted together and complain.
+ diff.compact()
+ self.assertEqual(1, len(diff.get_buffer()))
+ # The TTL stays on the first value, no matter if smaller or bigger
+ # ones come later.
+ self.assertEqual(self.__ttl, diff.get_buffer()[0][1].get_ttl())
+ self.assertTrue(self.__warn_called)
+ finally:
+ isc.xfrin.diff.logger = orig_logger
+
+ def test_relpace(self):
+ """
+ Test that when we want to replace the whole zone, it is propagated.
+ """
+ self.__should_replace = True
+ diff = Diff(self, "example.org.", True)
+ self.assertTrue(self.__updater_requested)
+
if __name__ == "__main__":
isc.log.init("bind10")
unittest.main()