]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
Merge branch 'master' into trac1514
authorhaikuo zhang <zhanghaikuo@cnnic.cn>
Mon, 11 Jun 2012 05:40:59 +0000 (13:40 +0800)
committerhaikuo zhang <zhanghaikuo@cnnic.cn>
Mon, 11 Jun 2012 05:40:59 +0000 (13:40 +0800)
[1514] merge master to trac1514
Conflicts:
src/lib/python/isc/ddns/session.py
src/lib/python/isc/ddns/tests/session_tests.py

1  2 
src/lib/python/isc/ddns/session.py
src/lib/python/isc/ddns/tests/session_tests.py

index e98381049b203766e38c6f35e79441da2597fd15,6cb2c98c110b41117cb37914b0e10cef24718eae..d36f6fde437db680858a014ccf9c437fa852ab96
@@@ -112,58 -97,34 +97,86 @@@ def convert_rrset_class(rrset, rrclass)
          new_rrset.add_rdata(isc.dns.Rdata(rrset.get_type(), rrclass, wire))
      return new_rrset
  
+ def collect_rrsets(collection, rrset):
+     '''
+     Helper function to collect similar rrsets.
+     Collect all rrsets with the same name, class, and type
+     collection is the currently collected list of RRsets,
+     rrset is the RRset to add;
+     if an RRset with the same name, class and type as the
+     given rrset exists in the collection, its rdata fields
+     are added to that RRset. Otherwise, the rrset is added
+     to the given collection.
+     TTL is ignored.
+     This method does not check rdata contents for duplicate
+     values.
+     The collection and its rrsets are modified in-place,
+     this method does not return anything.
+     '''
+     found = False
+     for existing_rrset in collection:
+         if existing_rrset.get_name() == rrset.get_name() and\
+            existing_rrset.get_class() == rrset.get_class() and\
+            existing_rrset.get_type() == rrset.get_type():
+             for rdata in rrset.get_rdata():
+                 existing_rrset.add_rdata(rdata)
+             found = True
+     if not found:
+         collection.append(rrset)
 +class DDNS_SOA:
 +    '''Class to handle the SOA in the DNS update '''
 +
 +    def __get_serial_internal(self, origin_soa):
 +        '''Get serial number from soa'''
 +        return Serial(int(origin_soa.get_rdata()[0].to_text().split()[2]))
 +
 +    def __write_soa_internal(self, origin_soa, soa_num):
 +        '''Write back serial number to soa'''
 +        new_soa = RRset(origin_soa.get_name(), origin_soa.get_class(),
 +                        RRType.SOA(), origin_soa.get_ttl())
 +        soa_rdata_parts = origin_soa.get_rdata()[0].to_text().split()
 +        soa_rdata_parts[2] = str(soa_num.get_value())
 +        new_soa.add_rdata(Rdata(origin_soa.get_type(), origin_soa.get_class(),
 +                                " ".join(soa_rdata_parts)))
 +        return new_soa
 +
 +    def soa_update_check(self, origin_soa, new_soa):
 +        '''Check whether the new soa is valid. If the serial number is bigger
 +        than the old one, it is valid, then return True, otherwise, return
 +        False. Make sure the origin_soa and new_soa parameters are not none
 +        before invoke soa_update_check.
 +        Parameters:
 +            origin_soa, old SOA resource record.
 +            new_soa, new SOA resource record.
 +        Output:
 +            if the serial number of new soa is bigger than the old one, return
 +            True, otherwise return False.
 +        '''
 +        old_serial = self.__get_serial_internal(origin_soa)
 +        new_serial = self.__get_serial_internal(new_soa)
 +        if(new_serial > old_serial):
 +            return True
 +        else:
 +            return False
 +
 +    def update_soa(self, origin_soa, inc_number = 1):
 +        ''' Update the soa number incrementally as RFC 2136. Please make sure
 +        that the origin_soa exists and not none before invoke this function.
 +        Parameters:
 +            origin_soa, the soa resource record which will be updated.
 +            inc_number, the number which will be added into the serial number of
 +            origin_soa, the default value is one.
 +        Output:
 +            The new origin soa whoes serial number has been updated.
 +        '''
 +        soa_num = self.__get_serial_internal(origin_soa)
 +        soa_num = soa_num + inc_number
 +        if soa_num.get_value() == 0:
 +            soa_num = soa_num + 1
 +        return self.__write_soa_internal(origin_soa, soa_num)
 +
  class UpdateSession:
      '''Protocol handling for a single dynamic update request.
  
          # serial magic and add the newly created one
  
          # get it from DS and to increment and stuff
 -        result, old_soa, _ = self.__diff.find(self.__zname, RRType.SOA())
 -
 -        if self.__added_soa is not None:
 -            new_soa = self.__added_soa
 -            # serial check goes here
 +        result, old_soa, _ = self.__finder.find(self.__zname, RRType.SOA(),
 +                                                ZoneFinder.NO_WILDCARD |
 +                                                ZoneFinder.FIND_GLUE_OK)
 +        # We may implement recovering from missing SOA data at some point, but
 +        # for now servfail on such a broken state
 +        if result != ZoneFinder.SUCCESS:
 +            raise UpdateError("Error finding SOA record in datasource.",
 +                    self.__zname, self.__zclass, Rcode.SERVFAIL())
 +        serial_operation = DDNS_SOA()
 +        if self.__added_soa is not None and\
 +        serial_operation.soa_update_check(old_soa, self.__added_soa):
 +                new_soa = self.__added_soa
          else:
 -            new_soa = old_soa
              # increment goes here
 +            new_soa = serial_operation.update_soa(old_soa)
  
-         diff.delete_data(old_soa)
-         diff.add_data(new_soa)
+         self.__diff.delete_data(old_soa)
+         self.__diff.add_data(new_soa)
  
      def __do_update(self):
          '''Scan, check, and execute the Update section in the
index 33493755da021ce09b62cc811fb94408bfd5b1e0,53f198219c78587d5bce7a489c628049d1e1d05d..f191995dc086a31315f4992200b4171878846757
@@@ -79,100 -94,138 +94,197 @@@ def create_rrset(name, rrclass, rrtype
          add_rdata(rrset, rdata)
      return rrset
  
- def add_rdata(rrset, rdata):
-     '''
-     Helper function for easily adding Rdata fields to RRsets.
-     This function assumes the given rdata is of type string or bytes,
-     and corresponds to the given rrset
+ class SessionModuleTests(unittest.TestCase):
+     '''Tests for module-level functions in the session.py module'''
+     def test_foreach_rr_in_rrset(self):
+         rrset = create_rrset("www.example.org", TEST_RRCLASS,
+                              RRType.A(), 3600, [ "192.0.2.1" ])
+         l = []
+         for rr in foreach_rr(rrset):
+             l.append(str(rr))
+         self.assertEqual(["www.example.org. 3600 IN A 192.0.2.1\n"], l)
+         add_rdata(rrset, "192.0.2.2")
+         add_rdata(rrset, "192.0.2.3")
+         # but through the generator, there should be several 1-line entries
+         l = []
+         for rr in foreach_rr(rrset):
+             l.append(str(rr))
+         self.assertEqual(["www.example.org. 3600 IN A 192.0.2.1\n",
+                           "www.example.org. 3600 IN A 192.0.2.2\n",
+                           "www.example.org. 3600 IN A 192.0.2.3\n",
+                          ], l)
+     def test_convert_rrset_class(self):
+         # Converting an RRSET to a different class should work
+         # if the rdata types can be converted
+         rrset = create_rrset("www.example.org", RRClass.NONE(), RRType.A(),
+                              3600, [ b'\xc0\x00\x02\x01', b'\xc0\x00\x02\x02'])
+         rrset2 = convert_rrset_class(rrset, RRClass.IN())
+         self.assertEqual("www.example.org. 3600 IN A 192.0.2.1\n" +
+                          "www.example.org. 3600 IN A 192.0.2.2\n",
+                          str(rrset2))
+         rrset3 = convert_rrset_class(rrset2, RRClass.NONE())
+         self.assertEqual("www.example.org. 3600 CLASS254 A \\# 4 " +
+                          "c0000201\nwww.example.org. 3600 CLASS254 " +
+                          "A \\# 4 c0000202\n",
+                          str(rrset3))
+         # depending on what type of bad data is given, a number
+         # of different exceptions could be raised (TODO: i recall
+         # there was a ticket about making a better hierarchy for
+         # dns/parsing related exceptions)
+         self.assertRaises(InvalidRdataLength, convert_rrset_class,
+                           rrset, RRClass.CH())
+         add_rdata(rrset, b'\xc0\x00')
+         self.assertRaises(DNSMessageFORMERR, convert_rrset_class,
+                           rrset, RRClass.IN())
+     def test_collect_rrsets(self):
+         '''
+         Tests the 'rrset collector' method, which collects rrsets
+         with the same name and type
+         '''
+         collected = []
+         collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+                                                RRType.A(), 0, [ "192.0.2.1" ]))
+         # Same name and class, different type
+         collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+                                                RRType.TXT(), 0, [ "one" ]))
+         collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+                                                RRType.A(), 0, [ "192.0.2.2" ]))
+         collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+                                                RRType.TXT(), 0, [ "two" ]))
+         # Same class and type as an existing one, different name
+         collect_rrsets(collected, create_rrset("b.example.org", RRClass.IN(),
+                                                RRType.A(), 0, [ "192.0.2.3" ]))
+         # Same name and type as an existing one, different class
+         collect_rrsets(collected, create_rrset("a.example.org", RRClass.CH(),
+                                                RRType.TXT(), 0, [ "one" ]))
+         collect_rrsets(collected, create_rrset("b.example.org", RRClass.IN(),
+                                                RRType.A(), 0, [ "192.0.2.4" ]))
+         collect_rrsets(collected, create_rrset("a.example.org", RRClass.CH(),
+                                                RRType.TXT(), 0, [ "two" ]))
+         strings = [ rrset.to_text() for rrset in collected ]
+         # note + vs , in this list
+         expected = ['a.example.org. 0 IN A 192.0.2.1\n' +
+                     'a.example.org. 0 IN A 192.0.2.2\n',
+                     'a.example.org. 0 IN TXT "one"\n' +
+                     'a.example.org. 0 IN TXT "two"\n',
+                     'b.example.org. 0 IN A 192.0.2.3\n' +
+                     'b.example.org. 0 IN A 192.0.2.4\n',
+                     'a.example.org. 0 CH TXT "one"\n' +
+                     'a.example.org. 0 CH TXT "two"\n']
+         self.assertEqual(expected, strings)
+ class SessionTestBase(unittest.TestCase):
+     '''Base class for all sesion related tests.
+     It just initializes common test parameters in its setUp() and defines
+     some common utility method(s).
      '''
-     rrset.add_rdata(isc.dns.Rdata(rrset.get_type(),
-                                   rrset.get_class(),
-                                   rdata))
+     def setUp(self):
+         shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
+         self._datasrc_client = DataSourceClient("sqlite3",
+                                                 WRITE_ZONE_DB_CONFIG)
+         self._update_msg = create_update_msg()
+         self._acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS):
+                              REQUEST_LOADER.load([{"action": "ACCEPT"}])}
+         self._session = UpdateSession(self._update_msg, TEST_CLIENT4,
+                                       ZoneConfig([], TEST_RRCLASS,
+                                                  self._datasrc_client,
+                                                  self._acl_map))
+         self._session._get_update_zone()
+         self._session._create_diff()
+     def tearDown(self):
+         # With the Updater created in _get_update_zone, and tests
+         # doing all kinds of crazy stuff, one might get database locked
+         # errors if it doesn't clean up explicitely after each test
+         self._session = None
+     def check_response(self, msg, expected_rcode):
+         '''Perform common checks on update resposne message.'''
+         self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_QR))
+         # note: we convert opcode to text it'd be more helpful on failure.
+         self.assertEqual(Opcode.UPDATE().to_text(), msg.get_opcode().to_text())
+         self.assertEqual(expected_rcode.to_text(), msg.get_rcode().to_text())
+         # All sections should be cleared
+         self.assertEqual(0, msg.get_rr_count(SECTION_ZONE))
+         self.assertEqual(0, msg.get_rr_count(SECTION_PREREQUISITE))
+         self.assertEqual(0, msg.get_rr_count(SECTION_UPDATE))
+         self.assertEqual(0, msg.get_rr_count(Message.SECTION_ADDITIONAL))
  
- class SessionTest(unittest.TestCase):
-     '''Session tests'''
-     def setUp(self):
-         shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
-         self.__datasrc_client = DataSourceClient("sqlite3",
-                                                  WRITE_ZONE_DB_CONFIG)
-         self.__update_msgdata, self.__update_msg = create_update_msg()
-         self.__session = UpdateSession(self.__update_msg,
-                                        self.__update_msgdata, TEST_CLIENT4,
-                                        ZoneConfig([], TEST_RRCLASS,
-                                                   self.__datasrc_client))
-         self.__session._UpdateSession__get_update_zone()
-     def check_response(self, msg, expected_rcode):
-         '''Perform common checks on update resposne message.'''
-         self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_QR))
-         # note: we convert opcode to text it'd be more helpful on failure.
-         self.assertEqual(Opcode.UPDATE().to_text(), msg.get_opcode().to_text())
-         self.assertEqual(expected_rcode.to_text(), msg.get_rcode().to_text())
-         # All sections should be cleared
-         self.assertEqual(0, msg.get_rr_count(SECTION_ZONE))
-         self.assertEqual(0, msg.get_rr_count(SECTION_PREREQUISITE))
-         self.assertEqual(0, msg.get_rr_count(SECTION_UPDATE))
-         self.assertEqual(0, msg.get_rr_count(Message.SECTION_ADDITIONAL))
 +class TestDDNSSOA(unittest.TestCase):
 +    '''unittest for the DDNS_SOA'''
 +    def test_update_soa(self):
 +        '''unittest for update_soa function'''
 +        soa_update = DDNS_SOA()
 +        soa_rr = create_rrset("example.org", TEST_RRCLASS,
 +                              RRType.SOA(), 3600, ["ns1.example.org. " +
 +                              "admin.example.org. " +
 +                              "1233 3600 1800 2419200 7200"])
 +        expected_soa_rr = create_rrset("example.org", TEST_RRCLASS,
 +                                       RRType.SOA(), 3600, ["ns1.example.org. "
 +                                       + "admin.example.org. " +
 +                                       "1234 3600 1800 2419200 7200"])
 +        self.assertEqual(soa_update.update_soa(soa_rr).get_rdata()[0].to_text(),
 +                         expected_soa_rr.get_rdata()[0].to_text())
 +        max_serial = 2 ** 32 - 1
 +        soa_rdata = "%d %s"%(max_serial,"3600 1800 2419200 7200")
 +        soa_rr = create_rrset("example.org", TEST_RRCLASS, RRType.SOA(), 3600,
 +                              ["ns1.example.org. " + "admin.example.org. " +
 +                              soa_rdata])
 +        expected_soa_rr = create_rrset("example.org", TEST_RRCLASS,
 +                                       RRType.SOA(), 3600, ["ns1.example.org. "
 +                                       + "admin.example.org. " +
 +                                       "1 3600 1800 2419200 7200"])
 +        self.assertEqual(soa_update.update_soa(soa_rr).get_rdata()[0].to_text(),
 +                         expected_soa_rr.get_rdata()[0].to_text())
 +
 +    def test_soa_update_check(self):
 +        '''unittest for soa_update_check function'''
 +        small_soa_rr = create_rrset("example.org", TEST_RRCLASS, RRType.SOA(),
 +                                    3600, ["ns1.example.org. " +
 +                                    "admin.example.org. " +
 +                                    "1233 3600 1800 2419200 7200"])
 +        large_soa_rr = create_rrset("example.org", TEST_RRCLASS, RRType.SOA(),
 +                                    3600, ["ns1.example.org. " +
 +                                    "admin.example.org. " +
 +                                    "1234 3600 1800 2419200 7200"])
 +        soa_update = DDNS_SOA()
 +        # The case of (i1 < i2 and i2 - i1 < 2^(SERIAL_BITS - 1)) in rfc 1982
 +        self.assertTrue(soa_update.soa_update_check(small_soa_rr,
 +                                                    large_soa_rr))
 +        self.assertFalse(soa_update.soa_update_check(large_soa_rr,
 +                                                     small_soa_rr))
 +        small_serial = 1235 + 2 ** 31
 +        soa_rdata = "%d %s"%(small_serial,"3600 1800 2419200 7200")
 +        small_soa_rr = create_rrset("example.org", TEST_RRCLASS, RRType.SOA(),
 +                                    3600, ["ns1.example.org. " +
 +                                           "admin.example.org. " +
 +                                           soa_rdata])
 +        large_soa_rr = create_rrset("example.org", TEST_RRCLASS, RRType.SOA(),
 +                                    3600, ["ns1.example.org. " +
 +                                    "admin.example.org. " +
 +                                    "1234 3600 1800 2419200 7200"])
 +        # The case of (i1 > i2 and i1 - i2 > 2^(SERIAL_BITS - 1)) in rfc 1982
 +        self.assertTrue(soa_update.soa_update_check(small_soa_rr,
 +                                                    large_soa_rr))
 +        self.assertFalse(soa_update.soa_update_check(large_soa_rr,
 +                                                     small_soa_rr))
 +
+ class SessionTest(SessionTestBase):
+     '''Basic session tests'''
  
      def test_handle(self):
          '''Basic update case'''
          self.check_full_handle_result(Rcode.NOERROR(),
                                        [ self.rrset_update_del_soa_apex,
                                          self.rrset_update_soa_del ])
 -        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
 -                                 isc.dns.Name("example.org"),
 -                                 RRType.SOA(),
 -                                 orig_soa_rrset)
 +        self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
 +                               isc.dns.Name("example.org"),
 +                               RRType.SOA(),
 +                               incremented_soa_rrset_01)
  
          # If we delete everything at the apex, the SOA and NS rrsets should be
 -        # untouched
 +        # untouched (but serial will be incremented)
          self.check_full_handle_result(Rcode.NOERROR(),
                                        [ self.rrset_update_del_name_apex ])
 -        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
 -                                 isc.dns.Name("example.org"),
 -                                 RRType.SOA(),
 -                                 orig_soa_rrset)
 -        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
 -                                 isc.dns.Name("example.org"),
 -                                 RRType.NS(),
 -                                 orig_ns_rrset)
 +        self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
 +                               isc.dns.Name("example.org"),
 +                               RRType.SOA(),
 +                               incremented_soa_rrset_02)
 +        self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
 +                               isc.dns.Name("example.org"),
 +                               RRType.NS(),
 +                               orig_ns_rrset)
          # but the MX should be gone
-         self.check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
-                                isc.dns.Name("example.org"),
-                                RRType.MX())
+         self.__check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
+                                  isc.dns.Name("example.org"),
+                                  RRType.MX())
  
          # Deleting the NS rrset by name and type only, it should also be left
          # untouched