BIND 10 master, updated. 1b9d39ec504ce53be4a9e9ef731a7a4295669800 [1457] fix typo SesseionTestBase

BIND 10 source code commits bind10-changes at lists.isc.org
Fri Jun 1 14:31:00 UTC 2012


The branch, master has been updated
       via  1b9d39ec504ce53be4a9e9ef731a7a4295669800 (commit)
       via  5122d4095a0865413c3f80083ef78a54bc3f7ea6 (commit)
       via  edadf68c0de62bc2dbdce97ff8372f36fd8bec8d (commit)
       via  55330acd18cb29963fc23be6eb59d5a56ab64f9a (commit)
       via  11af4fd1af04140c9e64aff58efc5f6661a41cf0 (commit)
       via  bc9715b080230f432420a7796d8ec2c5029e889e (commit)
       via  0cdd1593225256d36486590cfca23ab35ae15879 (commit)
       via  6296009e6fe929f80676b6f21ed838361e49358c (commit)
       via  0beb60c748a95def56529ccda897fadaaaf926af (commit)
       via  61977ad7fa96c33c4b6d45b11b0e31530d5a491b (commit)
       via  0e48c8a489e035440dc9f37fab485f9ecf3da8ed (commit)
       via  0bfaa798f73e2519b9ce53576f01e4fac2cae842 (commit)
       via  57a94c94601e7b6598e2cb73c3c9d252bd675d57 (commit)
       via  b432bc4116df51b0356d9e3e88943e777ae4137c (commit)
       via  57dc3faeae674c5d332dc4929154ce41bf2de891 (commit)
       via  6c4729c0a6329a83faa095f10639c9f391bd33ba (commit)
       via  e5a8ffb5a895a5e51e2d5c3ea4ba00e035746d9f (commit)
       via  99e857e87975a05b97942a5b98efed4b425c1e66 (commit)
       via  0db71e6a18ea01c52c6fe9280c15ddfc7aebd163 (commit)
       via  1f66bb6dd40f0119b985b3a76a224bf6490e24c8 (commit)
       via  cbd1f113584000f6f6b12e9c155987130d95abf2 (commit)
       via  929f452fc947d38dd02f1a66d4ae0c588162fdfe (commit)
       via  1e0e7c3c3a0190c1700499671a70d242ad0e4f14 (commit)
      from  91164ce5585a7d659f73ba86da2ffcae6db2f5e6 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 1b9d39ec504ce53be4a9e9ef731a7a4295669800
Author: Jelte Jansen <jelte at isc.org>
Date:   Fri Jun 1 16:01:07 2012 +0200

    [1457] fix typo SesseionTestBase

commit 5122d4095a0865413c3f80083ef78a54bc3f7ea6
Merge: edadf68 91164ce
Author: Jelte Jansen <jelte at isc.org>
Date:   Fri Jun 1 15:46:15 2012 +0200

    [1457] Merge branch 'master' into merge_1457
    
    and updated tests to reflect changed initializer for UpdateSession
    Conflicts:
    	src/lib/python/isc/ddns/libddns_messages.mes
    	src/lib/python/isc/ddns/session.py
    	src/lib/python/isc/ddns/tests/session_tests.py

commit edadf68c0de62bc2dbdce97ff8372f36fd8bec8d
Merge: 55330ac 031eaf1
Author: Jelte Jansen <jelte at isc.org>
Date:   Fri Jun 1 15:17:09 2012 +0200

    [merge_1457] Merge branch 'master' into merge_1457

-----------------------------------------------------------------------

Summary of changes:
 src/lib/dns/python/rdata_python.cc             |    1 +
 src/lib/python/isc/ddns/libddns_messages.mes   |   65 +-
 src/lib/python/isc/ddns/session.py             |  530 ++++++++++--
 src/lib/python/isc/ddns/tests/session_tests.py | 1090 ++++++++++++++++++------
 src/lib/testutils/testdata/rwtest.sqlite3      |  Bin 17408 -> 17408 bytes
 5 files changed, 1376 insertions(+), 310 deletions(-)

-----------------------------------------------------------------------
diff --git a/src/lib/dns/python/rdata_python.cc b/src/lib/dns/python/rdata_python.cc
index e4ff890..20f67c8 100644
--- a/src/lib/dns/python/rdata_python.cc
+++ b/src/lib/dns/python/rdata_python.cc
@@ -116,6 +116,7 @@ Rdata_init(PyObject* self_p, PyObject* args, PyObject*) {
             return (0);
         } else if (PyArg_ParseTuple(args, "O!O!y#", &rrtype_type, &rrtype,
                                     &rrclass_type, &rrclass, &data, &len)) {
+            PyErr_Clear();
             InputBuffer input_buffer(data, len);
             self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
                                        PyRRClass_ToRRClass(rrclass),
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
index 12c3a8c..dad516f 100644
--- a/src/lib/python/isc/ddns/libddns_messages.mes
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -58,7 +58,7 @@ specified NAME.  Note that this prerequisite IS satisfied by
 empty nonterminals.
 
 % LIBDDNS_PREREQ_NOTZONE update client %1 for zone %2: prerequisite not in zone (%3)
-A DNS UPDATE prerequisite has a name that does not appear to be inside
+A DDNS UPDATE prerequisite has a name that does not appear to be inside
 the zone specified in the Zone section of the UPDATE message.
 The specific prerequisite is shown. A NOTZONE error response is sent to
 the client.
@@ -93,10 +93,57 @@ RRset exists (value independent).  At least one RR with a
 specified NAME and TYPE (in the zone and class specified by
 the Zone Section) must exist.
 
+% LIBDDNS_UPDATE_ADD_BAD_TYPE update client %1 for zone %2: update addition RR bad type: %3
+The Update section of a DDNS update message contains a statement
+that tries to add a record of an invalid type. Most likely the
+record has an RRType that is considered a 'meta' type, which
+cannot be zone content data. The specific record is shown.
+A FORMERR response is sent back to the client.
+
 % LIBDDNS_UPDATE_APPROVED update client %1 for zone %2 approved
 Debug message.  An update request was approved in terms of the zone's
 update ACL.
 
+% LIBDDNS_UPDATE_BAD_CLASS update client %1 for zone %2: bad class in update RR: %3
+The Update section of a DDNS update message contains an RRset with
+a bad class. The class of the update RRset must be either the same
+as the class in the Zone Section, ANY, or NONE.
+A FORMERR response is sent back to the client.
+
+% LIBDDNS_UPDATE_DATASRC_ERROR error in datasource during DDNS update: %1
+An error occured while committing the DDNS update changes to the
+datasource. The specific error is printed. A SERVFAIL response is sent
+back to the client.
+
+% LIBDDNS_UPDATE_DELETE_BAD_TYPE update client %1 for zone %2: update deletion RR bad type: %3
+The Update section of a DDNS update message contains a statement
+that tries to delete an rrset of an invalid type. Most likely the
+record has an RRType that is considered a 'meta' type, which
+cannot be zone content data. The specific record is shown.
+A FORMERR response is sent back to the client.
+
+% LIBDDNS_UPDATE_DELETE_NONZERO_TTL update client %1 for zone %2: update deletion RR has non-zero TTL: %3
+The Update section of a DDNS update message contains a 'delete rrset'
+statement with a non-zero TTL. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
+% LIBDDNS_UPDATE_DELETE_RRSET_NOT_EMPTY update client %1 for zone %2: update deletion RR contains data %3
+The Update section of a DDNS update message contains a 'delete rrset'
+statement with a non-empty RRset. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
+% LIBDDNS_UPDATE_DELETE_RR_BAD_TYPE update client %1 for zone %2: update deletion RR bad type: %3
+The Update section of a DDNS update message contains a statement
+that tries to delete one or more rrs of an invalid type. Most
+likely the records have an RRType that is considered a 'meta'
+type, which cannot be zone content data. The specific record is
+shown. A FORMERR response is sent back to the client.
+
+% LIBDDNS_UPDATE_DELETE_RR_NONZERO_TTL update client %1 for zone %2: update deletion RR has non-zero TTL: %3
+The Update section of a DDNS update message contains a 'delete rrs'
+statement with a non-zero TTL. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
 % LIBDDNS_UPDATE_DENIED update client %1 for zone %2 denied
 Informational message.  An update request was denied because it was
 rejected by the zone's update ACL.  When this library is used by
@@ -134,9 +181,23 @@ configuration of those clients to suppress the requests.  As specified
 in Section 3.1 of RFC2136, the receiving server will return a response
 with an RCODE of NOTAUTH.
 
-% LIBDDNS_UPDATE_PREREQUISITE_FAILED prerequisite failed in update update client %1 for zone %2: result code %3
+% LIBDDNS_UPDATE_NOTZONE update client %1 for zone %2: update RR out of zone %3
+A DDNS UPDATE record has a name that does not appear to be inside
+the zone specified in the Zone section of the UPDATE message.
+The specific update record is shown. A NOTZONE error response is
+sent to the client.
+
+% LIBDDNS_UPDATE_PREREQUISITE_FAILED prerequisite failed in update client %1 for zone %2: result code %3
 The handling of the prerequisite section (RFC2136 Section 3.2) found
 that one of the prerequisites was not satisfied. The result code
 should give more information on what prerequisite type failed.
 If the result code is FORMERR, the prerequisite section was not well-formed.
 An error response with the given result code is sent back to the client.
+
+% LIBDDNS_UPDATE_UNCAUGHT_EXCEPTION update client %1 for zone %2: uncaught exception while processing update section: %1
+An uncaught exception was encountered while processing the Update
+section of a DDNS message. The specific exception is shown in the log message.
+To make sure DDNS service is not interrupted, this problem is caught instead
+of reraised; The update is aborted, and a SERVFAIL is sent back to the client.
+This is most probably a bug in the DDNS code, but *could* be caused by
+the data source.
diff --git a/src/lib/python/isc/ddns/session.py b/src/lib/python/isc/ddns/session.py
index 6a4079d..353af3f 100644
--- a/src/lib/python/isc/ddns/session.py
+++ b/src/lib/python/isc/ddns/session.py
@@ -19,6 +19,8 @@ from isc.log import *
 from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter,\
                             RRsetFormatter
 from isc.log_messages.libddns_messages import *
+from isc.datasrc import ZoneFinder
+import isc.xfrin.diff
 from isc.acl.acl import ACCEPT, REJECT, DROP
 import copy
 
@@ -59,6 +61,70 @@ class UpdateError(Exception):
         self.rcode = rcode
         self.nolog = nolog
 
+def foreach_rr(rrset):
+    '''
+    Generator that creates a new RRset with one RR from
+    the given RRset upon each iteration, usable in calls that
+    need to loop over an RRset and perform an action with each
+    of the individual RRs in it.
+    Example:
+    for rr in foreach_rr(rrset):
+        print(str(rr))
+    '''
+    for rdata in rrset.get_rdata():
+        rr = isc.dns.RRset(rrset.get_name(),
+                           rrset.get_class(),
+                           rrset.get_type(),
+                           rrset.get_ttl())
+        rr.add_rdata(rdata)
+        yield rr
+
+def convert_rrset_class(rrset, rrclass):
+    '''Returns a (new) rrset with the data from the given rrset,
+       but of the given class. Useful to convert from NONE and ANY to
+       a real class.
+       Note that the caller should be careful what to convert;
+       and DNS error that could happen during wire-format reading
+       could technically occur here, and is not caught by this helper.
+    '''
+    new_rrset = isc.dns.RRset(rrset.get_name(), rrclass,
+                              rrset.get_type(), rrset.get_ttl())
+    for rdata in rrset.get_rdata():
+        # Rdata class is nof modifiable, and must match rrset's
+        # class, so we need to to some ugly conversion here.
+        # And we cannot use to_text() (since the class may be unknown)
+        wire = rdata.to_wire(bytes())
+        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 UpdateSession:
     '''Protocol handling for a single dynamic update request.
 
@@ -89,6 +155,7 @@ class UpdateSession:
         self.__tsig = req_message.get_tsig_record()
         self.__client_addr = client_addr
         self.__zone_config = zone_config
+        self.__added_soa = None
 
     def get_message(self):
         '''Return the update message.
@@ -122,17 +189,19 @@ class UpdateSession:
 
         '''
         try:
-            datasrc_client, zname, zclass = self.__get_update_zone()
+            self.__get_update_zone()
             # conceptual code that would follow
-            prereq_result = self.__check_prerequisites(datasrc_client,
-                                                       zname, zclass)
+            prereq_result = self.__check_prerequisites()
             if prereq_result != Rcode.NOERROR():
                 self.__make_response(prereq_result)
-                return UPDATE_ERROR, zname, zclass
-            self.__check_update_acl(zname, zclass)
-            # self.__do_update()
-            # self.__make_response(Rcode.NOERROR())
-            return UPDATE_SUCCESS, zname, zclass
+                return UPDATE_ERROR, self.__zname, self.__zclass
+            self.__check_update_acl(self.__zname, self.__zclass)
+            update_result = self.__do_update()
+            if update_result != Rcode.NOERROR():
+                self.__make_response(update_result)
+                return UPDATE_ERROR, self.__zname, self.__zclass
+            self.__make_response(Rcode.NOERROR())
+            return UPDATE_SUCCESS, self.__zname, self.__zclass
         except UpdateError as e:
             if not e.nolog:
                 logger.debug(logger.DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_ERROR,
@@ -150,11 +219,13 @@ class UpdateSession:
         '''Parse the zone section and find the zone to be updated.
 
         If the zone section is valid and the specified zone is found in
-        the configuration, it returns a tuple of:
-        - A matching data source that contains the specified zone
-        - The zone name as a Name object
-        - The zone class as an RRClass object
-
+        the configuration, sets private member variables for this session:
+        __datasrc_client: A matching data source that contains the specified
+                          zone
+        __zname: The zone name as a Name object
+        __zclass: The zone class as an RRClass object
+        __finder: A ZoneFinder for this zone
+        If this method raises an exception, these members are not set
         '''
         # Validation: the zone section must contain exactly one question,
         # and it must be of type SOA.
@@ -172,7 +243,11 @@ class UpdateSession:
         zclass = zrecord.get_class()
         zone_type, datasrc_client = self.__zone_config.find_zone(zname, zclass)
         if zone_type == isc.ddns.zone_config.ZONE_PRIMARY:
-            return datasrc_client, zname, zclass
+            _, self.__finder = datasrc_client.find_zone(zname)
+            self.__zname = zname
+            self.__zclass = zclass
+            self.__datasrc_client = datasrc_client
+            return
         elif zone_type == isc.ddns.zone_config.ZONE_SECONDARY:
             # We are a secondary server; since we don't yet support update
             # forwarding, we return 'not implemented'.
@@ -217,7 +292,7 @@ class UpdateSession:
         self.__message.clear_section(SECTION_ZONE)
         self.__message.set_rcode(rcode)
 
-    def __prereq_rrset_exists(self, datasrc_client, rrset):
+    def __prereq_rrset_exists(self, rrset):
         '''Check whether an rrset with the given name and type exists. Class,
            TTL, and Rdata (if any) of the given RRset are ignored.
            RFC2136 Section 2.4.1.
@@ -229,22 +304,22 @@ class UpdateSession:
            only return what the result code would be (and not read/copy
            any actual data).
         '''
-        _, finder = datasrc_client.find_zone(rrset.get_name())
-        result, _, _ = finder.find(rrset.get_name(), rrset.get_type(),
-                                   finder.NO_WILDCARD | finder.FIND_GLUE_OK)
-        return result == finder.SUCCESS
+        result, _, _ = self.__finder.find(rrset.get_name(), rrset.get_type(),
+                                          ZoneFinder.NO_WILDCARD |
+                                          ZoneFinder.FIND_GLUE_OK)
+        return result == ZoneFinder.SUCCESS
 
-    def __prereq_rrset_exists_value(self, datasrc_client, rrset):
+    def __prereq_rrset_exists_value(self, rrset):
         '''Check whether an rrset that matches name, type, and rdata(s) of the
            given rrset exists.
            RFC2136 Section 2.4.2
            Returns True if the prerequisite is satisfied, False otherwise.
         '''
-        _, finder = datasrc_client.find_zone(rrset.get_name())
-        result, found_rrset, _ = finder.find(rrset.get_name(), rrset.get_type(),
-                                             finder.NO_WILDCARD |
-                                             finder.FIND_GLUE_OK)
-        if result == finder.SUCCESS and\
+        result, found_rrset, _ = self.__finder.find(rrset.get_name(),
+                                                    rrset.get_type(),
+                                                    ZoneFinder.NO_WILDCARD |
+                                                    ZoneFinder.FIND_GLUE_OK)
+        if result == ZoneFinder.SUCCESS and\
            rrset.get_name() == found_rrset.get_name() and\
            rrset.get_type() == found_rrset.get_type():
             # We need to match all actual RRs, unfortunately there is no
@@ -262,15 +337,15 @@ class UpdateSession:
             return len(found_rdata) == 0
         return False
 
-    def __prereq_rrset_does_not_exist(self, datasrc_client, rrset):
+    def __prereq_rrset_does_not_exist(self, rrset):
         '''Check whether no rrsets with the same name and type as the given
            rrset exist.
            RFC2136 Section 2.4.3.
            Returns True if the prerequisite is satisfied, False otherwise.
         '''
-        return not self.__prereq_rrset_exists(datasrc_client, rrset)
+        return not self.__prereq_rrset_exists(rrset)
 
-    def __prereq_name_in_use(self, datasrc_client, rrset):
+    def __prereq_name_in_use(self, rrset):
         '''Check whether the name of the given RRset is in use (i.e. has
            1 or more RRs).
            RFC2136 Section 2.4.4
@@ -282,37 +357,45 @@ class UpdateSession:
            to only return what the result code would be (and not read/copy
            any actual data).
         '''
-        _, finder = datasrc_client.find_zone(rrset.get_name())
-        result, rrsets, flags = finder.find_all(rrset.get_name(),
-                                                finder.NO_WILDCARD |
-                                                finder.FIND_GLUE_OK)
-        if result == finder.SUCCESS and\
-           (flags & finder.RESULT_WILDCARD == 0):
+        result, rrsets, flags = self.__finder.find_all(rrset.get_name(),
+                                                       ZoneFinder.NO_WILDCARD |
+                                                       ZoneFinder.FIND_GLUE_OK)
+        if result == ZoneFinder.SUCCESS and\
+           (flags & ZoneFinder.RESULT_WILDCARD == 0):
             return True
         return False
 
-    def __prereq_name_not_in_use(self, datasrc_client, rrset):
+    def __prereq_name_not_in_use(self, rrset):
         '''Check whether the name of the given RRset is not in use (i.e. does
            not exist at all, or is an empty nonterminal.
            RFC2136 Section 2.4.5.
            Returns True if the prerequisite is satisfied, False otherwise.
         '''
-        return not self.__prereq_name_in_use(datasrc_client, rrset)
+        return not self.__prereq_name_in_use(rrset)
 
-    def __check_prerequisites(self, datasrc_client, zname, zclass):
+    def __check_in_zone(self, rrset):
+        '''Returns true if the name of the given rrset is equal to
+           or a subdomain of the zname from the Zone Section.'''
+        relation = rrset.get_name().compare(self.__zname).get_relation()
+        return relation == NameComparisonResult.SUBDOMAIN or\
+               relation == NameComparisonResult.EQUAL
+
+    def __check_prerequisites(self):
         '''Check the prerequisites section of the UPDATE Message.
            RFC2136 Section 2.4.
            Returns a dns Rcode signaling either no error (Rcode.NOERROR())
            or that one of the prerequisites failed (any other Rcode).
         '''
+
+        # Temporary array to store exact-match RRsets
+        exact_match_rrsets = []
+
         for rrset in self.__message.get_section(SECTION_PREREQUISITE):
             # First check if the name is in the zone
-            relation = rrset.get_name().compare(zname).get_relation()
-            if relation != NameComparisonResult.SUBDOMAIN and\
-               relation != NameComparisonResult.EQUAL:
+            if not self.__check_in_zone(rrset):
                 logger.info(LIBDDNS_PREREQ_NOTZONE,
                             ClientFormatter(self.__client_addr),
-                            ZoneFormatter(zname, zclass),
+                            ZoneFormatter(self.__zname, self.__zclass),
                             RRsetFormatter(rrset))
                 return Rcode.NOTZONE()
 
@@ -322,24 +405,23 @@ class UpdateSession:
                    rrset.get_rdata_count() != 0:
                     logger.info(LIBDDNS_PREREQ_FORMERR_ANY,
                                 ClientFormatter(self.__client_addr),
-                                ZoneFormatter(zname, zclass),
+                                ZoneFormatter(self.__zname, self.__zclass),
                                 RRsetFormatter(rrset))
                     return Rcode.FORMERR()
                 elif rrset.get_type() == RRType.ANY():
-                    if not self.__prereq_name_in_use(datasrc_client,
-                                                     rrset):
+                    if not self.__prereq_name_in_use(rrset):
                         rcode = Rcode.NXDOMAIN()
                         logger.info(LIBDDNS_PREREQ_NAME_IN_USE_FAILED,
                                     ClientFormatter(self.__client_addr),
-                                    ZoneFormatter(zname, zclass),
+                                    ZoneFormatter(self.__zname, self.__zclass),
                                     RRsetFormatter(rrset), rcode)
                         return rcode
                 else:
-                    if not self.__prereq_rrset_exists(datasrc_client, rrset):
+                    if not self.__prereq_rrset_exists(rrset):
                         rcode = Rcode.NXRRSET()
                         logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_FAILED,
                                     ClientFormatter(self.__client_addr),
-                                    ZoneFormatter(zname, zclass),
+                                    ZoneFormatter(self.__zname, self.__zclass),
                                     RRsetFormatter(rrset), rcode)
                         return rcode
             elif rrset.get_class() == RRClass.NONE():
@@ -347,49 +429,367 @@ class UpdateSession:
                    rrset.get_rdata_count() != 0:
                     logger.info(LIBDDNS_PREREQ_FORMERR_NONE,
                                 ClientFormatter(self.__client_addr),
-                                ZoneFormatter(zname, zclass),
+                                ZoneFormatter(self.__zname, self.__zclass),
                                 RRsetFormatter(rrset))
                     return Rcode.FORMERR()
                 elif rrset.get_type() == RRType.ANY():
-                    if not self.__prereq_name_not_in_use(datasrc_client,
-                                                         rrset):
+                    if not self.__prereq_name_not_in_use(rrset):
                         rcode = Rcode.YXDOMAIN()
                         logger.info(LIBDDNS_PREREQ_NAME_NOT_IN_USE_FAILED,
                                     ClientFormatter(self.__client_addr),
-                                    ZoneFormatter(zname, zclass),
+                                    ZoneFormatter(self.__zname, self.__zclass),
                                     RRsetFormatter(rrset), rcode)
                         return rcode
                 else:
-                    if not self.__prereq_rrset_does_not_exist(datasrc_client,
-                                                              rrset):
+                    if not self.__prereq_rrset_does_not_exist(rrset):
                         rcode = Rcode.YXRRSET()
                         logger.info(LIBDDNS_PREREQ_RRSET_DOES_NOT_EXIST_FAILED,
                                     ClientFormatter(self.__client_addr),
-                                    ZoneFormatter(zname, zclass),
+                                    ZoneFormatter(self.__zname, self.__zclass),
                                     RRsetFormatter(rrset), rcode)
                         return rcode
-            elif rrset.get_class() == zclass:
+            elif rrset.get_class() == self.__zclass:
                 if rrset.get_ttl().get_value() != 0:
                     logger.info(LIBDDNS_PREREQ_FORMERR,
                                 ClientFormatter(self.__client_addr),
-                                ZoneFormatter(zname, zclass),
+                                ZoneFormatter(self.__zname, self.__zclass),
                                 RRsetFormatter(rrset))
                     return Rcode.FORMERR()
                 else:
-                    if not self.__prereq_rrset_exists_value(datasrc_client,
-                                                            rrset):
-                        rcode = Rcode.NXRRSET()
-                        logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED,
-                                    ClientFormatter(self.__client_addr),
-                                    ZoneFormatter(zname, zclass),
-                                    RRsetFormatter(rrset), rcode)
-                        return rcode
+                    collect_rrsets(exact_match_rrsets, rrset)
             else:
                 logger.info(LIBDDNS_PREREQ_FORMERR_CLASS,
                             ClientFormatter(self.__client_addr),
-                            ZoneFormatter(zname, zclass),
+                            ZoneFormatter(self.__zname, self.__zclass),
                             RRsetFormatter(rrset))
                 return Rcode.FORMERR()
 
+        for collected_rrset in exact_match_rrsets:
+            if not self.__prereq_rrset_exists_value(collected_rrset):
+                rcode = Rcode.NXRRSET()
+                logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED,
+                            ClientFormatter(self.__client_addr),
+                            ZoneFormatter(self.__zname, self.__zclass),
+                            RRsetFormatter(collected_rrset), rcode)
+                return rcode
+
         # All prerequisites are satisfied
         return Rcode.NOERROR()
+
+    def __set_soa_rrset(self, rrset):
+        '''Sets the given rrset to the member __added_soa (which
+           is used by __do_update for updating the SOA record'''
+        self.__added_soa = rrset
+
+    def __do_prescan(self):
+        '''Perform the prescan as defined in RFC2136 section 3.4.1.
+           This method has a side-effect; it sets self._new_soa if
+           it encounters the addition of a SOA record in the update
+           list (so serial can be checked by update later, etc.).
+           It puts the added SOA in self.__added_soa.
+        '''
+        for rrset in self.__message.get_section(SECTION_UPDATE):
+            if not self.__check_in_zone(rrset):
+                logger.info(LIBDDNS_UPDATE_NOTZONE,
+                            ClientFormatter(self.__client_addr),
+                            ZoneFormatter(self.__zname, self.__zclass),
+                            RRsetFormatter(rrset))
+                return Rcode.NOTZONE()
+            if rrset.get_class() == self.__zclass:
+                # In fact, all metatypes are in a specific range,
+                # so one check can test TKEY to ANY
+                # (some value check is needed anyway, since we do
+                # not have defined RRtypes for MAILA and MAILB)
+                if rrset.get_type().get_code() >=  249:
+                    logger.info(LIBDDNS_UPDATE_ADD_BAD_TYPE,
+                                ClientFormatter(self.__client_addr),
+                                ZoneFormatter(self.__zname, self.__zclass),
+                                RRsetFormatter(rrset))
+                    return Rcode.FORMERR()
+                if rrset.get_type() == RRType.SOA():
+                    # In case there's multiple soa records in the update
+                    # somehow, just take the last
+                    for rr in foreach_rr(rrset):
+                        self.__set_soa_rrset(rr)
+            elif rrset.get_class() == RRClass.ANY():
+                if rrset.get_ttl().get_value() != 0:
+                    logger.info(LIBDDNS_UPDATE_DELETE_NONZERO_TTL,
+                                ClientFormatter(self.__client_addr),
+                                ZoneFormatter(self.__zname, self.__zclass),
+                                RRsetFormatter(rrset))
+                    return Rcode.FORMERR()
+                if rrset.get_rdata_count() > 0:
+                    logger.info(LIBDDNS_UPDATE_DELETE_RRSET_NOT_EMPTY,
+                                ClientFormatter(self.__client_addr),
+                                ZoneFormatter(self.__zname, self.__zclass),
+                                RRsetFormatter(rrset))
+                    return Rcode.FORMERR()
+                if rrset.get_type().get_code() >= 249 and\
+                   rrset.get_type().get_code() <= 254:
+                    logger.info(LIBDDNS_UPDATE_DELETE_BAD_TYPE,
+                                ClientFormatter(self.__client_addr),
+                                ZoneFormatter(self.__zname, self.__zclass),
+                                RRsetFormatter(rrset))
+                    return Rcode.FORMERR()
+            elif rrset.get_class() == RRClass.NONE():
+                if rrset.get_ttl().get_value() != 0:
+                    logger.info(LIBDDNS_UPDATE_DELETE_RR_NONZERO_TTL,
+                                ClientFormatter(self.__client_addr),
+                                ZoneFormatter(self.__zname, self.__zclass),
+                                RRsetFormatter(rrset))
+                    return Rcode.FORMERR()
+                if rrset.get_type().get_code() >= 249:
+                    logger.info(LIBDDNS_UPDATE_DELETE_RR_BAD_TYPE,
+                                ClientFormatter(self.__client_addr),
+                                ZoneFormatter(self.__zname, self.__zclass),
+                                RRsetFormatter(rrset))
+                    return Rcode.FORMERR()
+            else:
+                logger.info(LIBDDNS_UPDATE_BAD_CLASS,
+                            ClientFormatter(self.__client_addr),
+                            ZoneFormatter(self.__zname, self.__zclass),
+                            RRsetFormatter(rrset))
+                return Rcode.FORMERR()
+        return Rcode.NOERROR()
+
+    def __do_update_add_single_rr(self, diff, rr, existing_rrset):
+        '''Helper for __do_update_add_rrs_to_rrset: only add the
+           rr if it is not present yet
+           (note that rr here should already be a single-rr rrset)
+        '''
+        if existing_rrset is None:
+            diff.add_data(rr)
+        else:
+            rr_rdata = rr.get_rdata()[0]
+            if not rr_rdata in existing_rrset.get_rdata():
+                diff.add_data(rr)
+
+    def __do_update_add_rrs_to_rrset(self, diff, rrset):
+        '''Add the rrs from the given rrset to the diff.
+           There is handling for a number of special cases mentioned
+           in RFC2136;
+           - If the addition is a CNAME, but existing data at its
+             name is not, the addition is ignored, and vice versa.
+           - If it is a CNAME, and existing data is too, it is
+             replaced (existing data is deleted)
+           An additional restriction is that SOA data is ignored as
+           well (it is handled separately by the __do_update method).
+
+           Note that in the (near) future, this method may have
+           addition special-cases processing.
+        '''
+        # For a number of cases, we may need to remove data in the zone
+        # (note; SOA is handled separately by __do_update, so that one
+        # is explicitely ignored here)
+        if rrset.get_type() == RRType.SOA():
+            return
+        result, orig_rrset, _ = self.__finder.find(rrset.get_name(),
+                                                   rrset.get_type(),
+                                                   ZoneFinder.NO_WILDCARD |
+                                                   ZoneFinder.FIND_GLUE_OK)
+        if result == self.__finder.CNAME:
+            # Ignore non-cname rrs that try to update CNAME records
+            # (if rrset itself is a CNAME, the finder result would be
+            # SUCCESS, see next case)
+            return
+        elif result == ZoneFinder.SUCCESS:
+            # if update is cname, and zone rr is not, ignore
+            if rrset.get_type() == RRType.CNAME():
+                # Remove original CNAME record (the new one
+                # is added below)
+                diff.delete_data(orig_rrset)
+            # We do not have WKS support at this time, but if there
+            # are special Update equality rules such as for WKS, and
+            # we do have support for the type, this is where the check
+            # (and potential delete) would go.
+        elif result == ZoneFinder.NXRRSET:
+            # There is data present, but not for this type.
+            # If this type is CNAME, ignore the update
+            if rrset.get_type() == RRType.CNAME():
+                return
+        for rr in foreach_rr(rrset):
+            self.__do_update_add_single_rr(diff, rr, orig_rrset)
+
+    def __do_update_delete_rrset(self, diff, rrset):
+        '''Deletes the rrset with the name and type of the given
+           rrset from the zone data (by putting all existing data
+           in the given diff as delete statements).
+           Special cases: if the delete statement is for the
+           zone's apex, and the type is either SOA or NS, it
+           is ignored.'''
+        result, to_delete, _ = self.__finder.find(rrset.get_name(),
+                                                  rrset.get_type(),
+                                                  ZoneFinder.NO_WILDCARD |
+                                                  ZoneFinder.FIND_GLUE_OK)
+        if result == ZoneFinder.SUCCESS:
+            if to_delete.get_name() == self.__zname and\
+               (to_delete.get_type() == RRType.SOA() or\
+                to_delete.get_type() == RRType.NS()):
+                # ignore
+                return
+            for rr in foreach_rr(to_delete):
+                diff.delete_data(rr)
+
+    def __ns_deleter_helper(self, diff, rrset):
+        '''Special case helper for deleting NS resource records
+           at the zone apex. In that scenario, the last NS record
+           may never be removed (and any action that would do so
+           should be ignored).
+        '''
+        # NOTE: This method is currently bad: it WILL delete all
+        # NS rrsets in a number of cases.
+        # We need an extension to our diff.py to handle this correctly
+        # (see ticket #2016)
+        # The related test is currently disabled. When this is fixed,
+        # enable that test again.
+        result, orig_rrset, _ = self.__finder.find(rrset.get_name(),
+                                                   rrset.get_type(),
+                                                   ZoneFinder.NO_WILDCARD |
+                                                   ZoneFinder.FIND_GLUE_OK)
+        # Even a real rrset comparison wouldn't help here...
+        # The goal is to make sure that after deletion of the
+        # given rrset, at least 1 NS record is left (at the apex).
+        # So we make a (shallow) copy of the existing rrset,
+        # and for each rdata in the to_delete set, we check if it wouldn't
+        # delete the last one. If it would, that specific one is ignored.
+        # If it would not, the rdata is removed from the temporary list
+        orig_rrset_rdata = copy.copy(orig_rrset.get_rdata())
+        for rdata in rrset.get_rdata():
+            if len(orig_rrset_rdata) == 1 and rdata == orig_rrset_rdata[0]:
+                # ignore
+                continue
+            else:
+                # create an individual RRset for deletion
+                to_delete = isc.dns.RRset(rrset.get_name(),
+                                          rrset.get_class(),
+                                          rrset.get_type(),
+                                          rrset.get_ttl())
+                to_delete.add_rdata(rdata)
+                orig_rrset_rdata.remove(rdata)
+                diff.delete_data(to_delete)
+
+    def __do_update_delete_name(self, diff, rrset):
+        '''Delete all data at the name of the given rrset,
+           by adding all data found by find_all as delete statements
+           to the given diff.
+           Special case: if the name is the zone's apex, SOA and
+           NS records are kept.
+        '''
+        result, rrsets, flags = self.__finder.find_all(rrset.get_name(),
+                                                       ZoneFinder.NO_WILDCARD |
+                                                       ZoneFinder.FIND_GLUE_OK)
+        if result == ZoneFinder.SUCCESS and\
+           (flags & ZoneFinder.RESULT_WILDCARD == 0):
+            for to_delete in rrsets:
+                # if name == self.__zname and type is soa or ns, don't delete!
+                if to_delete.get_name() == self.__zname and\
+                   (to_delete.get_type() == RRType.SOA() or
+                    to_delete.get_type() == RRType.NS()):
+                    continue
+                else:
+                    for rr in foreach_rr(to_delete):
+                        diff.delete_data(rr)
+
+    def __do_update_delete_rrs_from_rrset(self, diff, rrset):
+        '''Deletes all resource records in the given rrset from the
+           zone. Resource records that do not exist are ignored.
+           If the rrset if of type SOA, it is ignored.
+           Uses the __ns_deleter_helper if the rrset's name is the
+           zone's apex, and the type is NS.
+        '''
+        # Delete all rrs in the rrset, except if name=self.__zname and type=soa, or
+        # type = ns and there is only one left (...)
+
+        # The delete does not want class NONE, we would not have gotten here
+        # if it wasn't, but now is a good time to change it to the zclass.
+        to_delete = convert_rrset_class(rrset, self.__zclass)
+
+        if rrset.get_name() == self.__zname:
+            if rrset.get_type() == RRType.SOA():
+                # ignore
+                return
+            elif rrset.get_type() == RRType.NS():
+                # hmm. okay. annoying. There must be at least one left,
+                # delegate to helper method
+                self.__ns_deleter_helper(diff, to_delete)
+                return
+        for rr in foreach_rr(to_delete):
+            diff.delete_data(rr)
+
+    def __update_soa(self, diff):
+        '''Checks the member value __added_soa, and depending on
+           whether it has been set and what its value is, creates
+           a new SOA if necessary.
+           Then removes the original SOA and adds the new one,
+           by adding the needed operations to the given diff.'''
+        # Get the existing SOA
+        # if a new soa was specified, add that one, otherwise, do the
+        # serial magic and add the newly created one
+
+        # get it from DS and to increment and stuff
+        result, old_soa, _ = self.__finder.find(self.__zname, RRType.SOA(),
+                                                ZoneFinder.NO_WILDCARD |
+                                                ZoneFinder.FIND_GLUE_OK)
+
+        if self.__added_soa is not None:
+            new_soa = self.__added_soa
+            # serial check goes here
+        else:
+            new_soa = old_soa
+            # increment goes here
+
+        diff.delete_data(old_soa)
+        diff.add_data(new_soa)
+
+    def __do_update(self):
+        '''Scan, check, and execute the Update section in the
+           DDNS Update message.
+           Returns an Rcode to signal the result (NOERROR upon success,
+           any error result otherwise).
+        '''
+        # prescan
+        prescan_result = self.__do_prescan()
+        if prescan_result != Rcode.NOERROR():
+            return prescan_result
+
+        # update
+        try:
+            # create an ixfr-out-friendly diff structure to work on
+            diff = isc.xfrin.diff.Diff(self.__datasrc_client, self.__zname,
+                                       journaling=True, single_update_mode=True)
+
+            # Do special handling for SOA first
+            self.__update_soa(diff)
+
+            # Algorithm from RFC2136 Section 3.4
+            # Note that this works on full rrsets, not individual RRs.
+            # Some checks might be easier with individual RRs, but only if we
+            # would use the ZoneUpdater directly (so we can query the
+            # 'zone-as-it-would-be-so-far'. However, due to the current use
+            # of the Diff class, this is not the case, and therefore it
+            # is easier to work with full rrsets for the most parts
+            # (less lookups needed; conversion to individual rrs is
+            # the same effort whether it is done here or in the several
+            # do_update statements)
+            for rrset in self.__message.get_section(SECTION_UPDATE):
+                if rrset.get_class() == self.__zclass:
+                    self.__do_update_add_rrs_to_rrset(diff, rrset)
+                elif rrset.get_class() == RRClass.ANY():
+                    if rrset.get_type() == RRType.ANY():
+                        self.__do_update_delete_name(diff, rrset)
+                    else:
+                        self.__do_update_delete_rrset(diff, rrset)
+                elif rrset.get_class() == RRClass.NONE():
+                    self.__do_update_delete_rrs_from_rrset(diff, rrset)
+
+            diff.commit()
+            return Rcode.NOERROR()
+        except isc.datasrc.Error as dse:
+            logger.info(LIBDDNS_UPDATE_DATASRC_ERROR, dse)
+            return Rcode.SERVFAIL()
+        except Exception as uce:
+            logger.error(LIBDDNS_UPDATE_UNCAUGHT_EXCEPTION,
+                         ClientFormatter(self.__client_addr),
+                         ZoneFormatter(self.__zname, self.__zclass),
+                         uce)
+            return Rcode.SERVFAIL()
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
index 7f15480..87cd255 100644
--- a/src/lib/python/isc/ddns/tests/session_tests.py
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -39,7 +39,7 @@ TEST_CLIENT4 = ('192.0.2.1', 53)
 TEST_TSIG_KEY = TSIGKey("example.org:SFuWd/q99SzF8Yzd1QbB9g==")
 
 def create_update_msg(zones=[TEST_ZONE_RECORD], prerequisites=[],
-                      tsig_key=None):
+                      updates=[], tsig_key=None):
     msg = Message(Message.RENDER)
     msg.set_qid(5353)           # arbitrary chosen
     msg.set_opcode(Opcode.UPDATE())
@@ -48,6 +48,8 @@ def create_update_msg(zones=[TEST_ZONE_RECORD], prerequisites=[],
         msg.add_question(z)
     for p in prerequisites:
         msg.add_rrset(SECTION_PREREQUISITE, p)
+    for u in updates:
+        msg.add_rrset(SECTION_UPDATE, u)
 
     renderer = MessageRenderer()
     if tsig_key is not None:
@@ -57,11 +59,42 @@ def create_update_msg(zones=[TEST_ZONE_RECORD], prerequisites=[],
 
     # re-read the created data in the parse mode
     msg.clear(Message.PARSE)
-    msg.from_wire(renderer.get_data())
+    msg.from_wire(renderer.get_data(), Message.PRESERVE_ORDER)
 
     return msg
 
-class SesseionTestBase(unittest.TestCase):
+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
+    '''
+    rrset.add_rdata(isc.dns.Rdata(rrset.get_type(),
+                                  rrset.get_class(),
+                                  rdata))
+
+def create_rrset(name, rrclass, rrtype, ttl, rdatas = []):
+    '''
+    Helper method to easily create RRsets, auto-converts
+    name, rrclass, rrtype,  and ttl (if possibly through their
+    respective constructors)
+    rdatas is a list of rr data strings, or bytestrings, which
+    should match the RRType of the rrset to create
+    '''
+    if type(name) != Name:
+        name = Name(name)
+    if type(rrclass) != RRClass:
+        rrclass = RRClass(rrclass)
+    if type(rrtype) != RRType:
+        rrtype = RRType(rrtype)
+    if type(ttl) != RRTTL:
+        ttl = RRTTL(ttl)
+    rrset = isc.dns.RRset(name, rrclass, rrtype, ttl)
+    for rdata in rdatas:
+        add_rdata(rrset, rdata)
+    return rrset
+
+class SessionTestBase(unittest.TestCase):
     '''Base class for all sesion related tests.
 
     It just initializes common test parameters in its setUp() and defines
@@ -79,6 +112,7 @@ class SesseionTestBase(unittest.TestCase):
                                       ZoneConfig([], TEST_RRCLASS,
                                                  self._datasrc_client,
                                                  self._acl_map))
+        self._session._UpdateSession__get_update_zone()
 
     def check_response(self, msg, expected_rcode):
         '''Perform common checks on update resposne message.'''
@@ -92,7 +126,7 @@ class SesseionTestBase(unittest.TestCase):
         self.assertEqual(0, msg.get_rr_count(SECTION_UPDATE))
         self.assertEqual(0, msg.get_rr_count(Message.SECTION_ADDITIONAL))
 
-class SessionTest(SesseionTestBase):
+class SessionTest(SessionTestBase):
     '''Basic session tests'''
 
     def test_handle(self):
@@ -160,11 +194,99 @@ class SessionTest(SesseionTestBase):
         # zone class doesn't match
         self.check_notauth(Name('example.org'), RRClass.CH())
 
+    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)
+
     def __prereq_helper(self, method, expected, rrset):
         '''Calls the given method with self._datasrc_client
            and the given rrset, and compares the return value.
            Function does not do much but makes the code look nicer'''
-        self.assertEqual(expected, method(self._datasrc_client, rrset))
+        self.assertEqual(expected, method(rrset))
 
     def __check_prerequisite_exists_combined(self, method, rrclass, expected):
         '''shared code for the checks for the very similar (but reversed
@@ -175,156 +297,102 @@ class SessionTest(SesseionTestBase):
         '''
         # Basic existence checks
         # www.example.org should have an A, but not an MX
-        rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                              rrclass, isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("www.example.org", rrclass, RRType.A(), 0)
         self.__prereq_helper(method, expected, rrset)
-        rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                              rrclass, isc.dns.RRType.MX(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("www.example.org", rrclass, RRType.MX(), 0)
         self.__prereq_helper(method, not expected, rrset)
 
         # example.org should have an MX, but not an A
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              rrclass, isc.dns.RRType.MX(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("example.org", rrclass, RRType.MX(), 0)
         self.__prereq_helper(method, expected, rrset)
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              rrclass, isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("example.org", rrclass, RRType.A(), 0)
         self.__prereq_helper(method, not expected, rrset)
 
         # Also check the case where the name does not even exist
-        rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"),
-                              rrclass, isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("doesnotexist.example.org", rrclass, RRType.A(), 0)
         self.__prereq_helper(method, not expected, rrset)
 
         # Wildcard expansion should not be applied, but literal matches
         # should work
-        rrset = isc.dns.RRset(isc.dns.Name("foo.wildcard.example.org"),
-                              rrclass, isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("foo.wildcard.example.org", rrclass, RRType.A(), 0)
         self.__prereq_helper(method, not expected, rrset)
 
-        rrset = isc.dns.RRset(isc.dns.Name("*.wildcard.example.org"),
-                              rrclass, isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("*.wildcard.example.org", rrclass, RRType.A(), 0)
         self.__prereq_helper(method, expected, rrset)
 
         # Likewise, CNAME directly should match, but what it points to should
         # not
-        rrset = isc.dns.RRset(isc.dns.Name("cname.example.org"),
-                              rrclass, isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("cname.example.org", rrclass, RRType.A(), 0)
         self.__prereq_helper(method, not expected, rrset)
 
-        rrset = isc.dns.RRset(isc.dns.Name("cname.example.org"),
-                              rrclass, isc.dns.RRType.CNAME(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("cname.example.org", rrclass, RRType.CNAME(), 0)
         self.__prereq_helper(method, expected, rrset)
 
         # And also make sure a delegation (itself) is not treated as existing
         # data
-        rrset = isc.dns.RRset(isc.dns.Name("foo.sub.example.org"),
-                              rrclass, isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("foo.sub.example.org", rrclass, RRType.A(), 0)
         self.__prereq_helper(method, not expected, rrset)
         # But the delegation data itself should match
-        rrset = isc.dns.RRset(isc.dns.Name("sub.example.org"),
-                              rrclass, isc.dns.RRType.NS(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("sub.example.org", rrclass, RRType.NS(), 0)
         self.__prereq_helper(method, expected, rrset)
         # As should glue
-        rrset = isc.dns.RRset(isc.dns.Name("ns.sub.example.org"),
-                              rrclass, isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("ns.sub.example.org", rrclass, RRType.A(), 0)
         self.__prereq_helper(method, expected, rrset)
 
     def test_check_prerequisite_exists(self):
         method = self._session._UpdateSession__prereq_rrset_exists
         self.__check_prerequisite_exists_combined(method,
-                                                  isc.dns.RRClass.ANY(),
+                                                  RRClass.ANY(),
                                                   True)
 
     def test_check_prerequisite_does_not_exist(self):
         method = self._session._UpdateSession__prereq_rrset_does_not_exist
         self.__check_prerequisite_exists_combined(method,
-                                                  isc.dns.RRClass.NONE(),
+                                                  RRClass.NONE(),
                                                   False)
 
     def test_check_prerequisite_exists_value(self):
         method = self._session._UpdateSession__prereq_rrset_exists_value
 
-        rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                              isc.dns.RRClass.IN(), isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("www.example.org", RRClass.IN(), RRType.A(), 0)
         # empty one should not match
         self.__prereq_helper(method, False, rrset)
 
         # When the rdata is added, it should match
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
-                                      isc.dns.RRClass.IN(),
-                                      "192.0.2.1"))
+        add_rdata(rrset, "192.0.2.1")
         self.__prereq_helper(method, True, rrset)
 
         # But adding more should not
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
-                                      isc.dns.RRClass.IN(),
-                                      "192.0.2.2"))
+        add_rdata(rrset, "192.0.2.2")
         self.__prereq_helper(method, False, rrset)
 
         # Also test one with more than one RR
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              isc.dns.RRClass.IN(), isc.dns.RRType.NS(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("example.org", RRClass.IN(), RRType.NS(), 0)
         self.__prereq_helper(method, False, rrset)
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                      isc.dns.RRClass.IN(),
-                                      "ns1.example.org."))
+        add_rdata(rrset, "ns1.example.org.")
         self.__prereq_helper(method, False, rrset)
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                      isc.dns.RRClass.IN(),
-                                      "ns2.example.org."))
+        add_rdata(rrset, "ns2.example.org")
         self.__prereq_helper(method, False, rrset)
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                      isc.dns.RRClass.IN(),
-                                      "ns3.example.org."))
+        add_rdata(rrset, "ns3.example.org.")
         self.__prereq_helper(method, True, rrset)
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                      isc.dns.RRClass.IN(),
-                                      "ns4.example.org."))
+        add_rdata(rrset, "ns4.example.org.")
         self.__prereq_helper(method, False, rrset)
 
         # Repeat that, but try a different order of Rdata addition
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              isc.dns.RRClass.IN(), isc.dns.RRType.NS(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("example.org", RRClass.IN(), RRType.NS(), 0)
         self.__prereq_helper(method, False, rrset)
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                      isc.dns.RRClass.IN(),
-                                      "ns3.example.org."))
+        add_rdata(rrset, "ns3.example.org.")
         self.__prereq_helper(method, False, rrset)
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                      isc.dns.RRClass.IN(),
-                                      "ns2.example.org."))
+        add_rdata(rrset, "ns2.example.org.")
         self.__prereq_helper(method, False, rrset)
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                      isc.dns.RRClass.IN(),
-                                      "ns1.example.org."))
+        add_rdata(rrset, "ns1.example.org.")
         self.__prereq_helper(method, True, rrset)
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                      isc.dns.RRClass.IN(),
-                                      "ns4.example.org."))
+        add_rdata(rrset, "ns4.example.org.")
         self.__prereq_helper(method, False, rrset)
 
         # and test one where the name does not even exist
-        rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"),
-                              isc.dns.RRClass.IN(), isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
-                                      isc.dns.RRClass.IN(),
-                                      "192.0.2.1"))
+        rrset = create_rrset("doesnotexist.example.org", RRClass.IN(),
+                             RRType.A(), 0, [ "192.0.2.1" ])
         self.__prereq_helper(method, False, rrset)
 
     def __check_prerequisite_name_in_use_combined(self, method, rrclass,
@@ -333,51 +401,42 @@ class SessionTest(SesseionTestBase):
            in behaviour) methods __prereq_name_in_use and
            __prereq_name_not_in_use
         '''
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              rrclass, isc.dns.RRType.ANY(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("example.org", rrclass, RRType.ANY(), 0)
         self.__prereq_helper(method, expected, rrset)
 
-        rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                              rrclass, isc.dns.RRType.ANY(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("www.example.org", rrclass, RRType.ANY(), 0)
         self.__prereq_helper(method, expected, rrset)
 
-        rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"),
-                              rrclass, isc.dns.RRType.ANY(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("doesnotexist.example.org", rrclass,
+                             RRType.ANY(), 0)
         self.__prereq_helper(method, not expected, rrset)
 
-        rrset = isc.dns.RRset(isc.dns.Name("belowdelegation.sub.example.org"),
-                              rrclass, isc.dns.RRType.ANY(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("belowdelegation.sub.example.org", rrclass,
+                             RRType.ANY(), 0)
         self.__prereq_helper(method, not expected, rrset)
 
-        rrset = isc.dns.RRset(isc.dns.Name("foo.wildcard.example.org"),
-                              rrclass, isc.dns.RRType.ANY(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("foo.wildcard.example.org", rrclass,
+                             RRType.ANY(), 0)
         self.__prereq_helper(method, not expected, rrset)
 
         # empty nonterminal should not match
-        rrset = isc.dns.RRset(isc.dns.Name("nonterminal.example.org"),
-                              rrclass, isc.dns.RRType.ANY(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("nonterminal.example.org", rrclass,
+                             RRType.ANY(), 0)
         self.__prereq_helper(method, not expected, rrset)
-        rrset = isc.dns.RRset(isc.dns.Name("empty.nonterminal.example.org"),
-                              rrclass, isc.dns.RRType.ANY(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("empty.nonterminal.example.org", rrclass,
+                             RRType.ANY(), 0)
         self.__prereq_helper(method, expected, rrset)
 
     def test_check_prerequisite_name_in_use(self):
         method = self._session._UpdateSession__prereq_name_in_use
         self.__check_prerequisite_name_in_use_combined(method,
-                                                       isc.dns.RRClass.ANY(),
+                                                       RRClass.ANY(),
                                                        True)
 
     def test_check_prerequisite_name_not_in_use(self):
         method = self._session._UpdateSession__prereq_name_not_in_use
         self.__check_prerequisite_name_in_use_combined(method,
-                                                       isc.dns.RRClass.NONE(),
+                                                       RRClass.NONE(),
                                                        False)
 
     def check_prerequisite_result(self, expected, prerequisites):
@@ -389,23 +448,62 @@ class SessionTest(SesseionTestBase):
         zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
                              self._acl_map)
         session = UpdateSession(msg, TEST_CLIENT4, zconfig)
+        session._UpdateSession__get_update_zone()
+        # compare the to_text output of the rcodes (nicer error messages)
+        # This call itself should also be done by handle(),
+        # but just for better failures, it is first called on its own
+        self.assertEqual(expected.to_text(),
+            session._UpdateSession__check_prerequisites().to_text())
+        # Now see if handle finds the same result
+        (result, _, _) = session.handle()
+        self.assertEqual(expected.to_text(),
+                         session._UpdateSession__message.get_rcode().to_text())
+        # And that the result looks right
+        if expected == Rcode.NOERROR():
+            self.assertEqual(UPDATE_SUCCESS, result)
+        else:
+            self.assertEqual(UPDATE_ERROR, result)
+
+    def check_prescan_result(self, expected, updates, expected_soa = None):
+        '''Helper method for checking the result of a prerequisite check;
+           creates an update session, and fills it with the list of rrsets
+           from 'updates'. Then checks if __do_prescan()
+           returns the Rcode specified in 'expected'.'''
+        msg = create_update_msg([TEST_ZONE_RECORD], [], updates)
+        zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+                             self._acl_map)
+        session = UpdateSession(msg, TEST_CLIENT4, zconfig)
+        session._UpdateSession__get_update_zone()
         # compare the to_text output of the rcodes (nicer error messages)
         # This call itself should also be done by handle(),
         # but just for better failures, it is first called on its own
         self.assertEqual(expected.to_text(),
-            session._UpdateSession__check_prerequisites(self._datasrc_client,
-                                                        TEST_ZONE_NAME,
-                                                        TEST_RRCLASS).to_text())
+            session._UpdateSession__do_prescan().to_text())
+        # If there is an expected soa, check it
+        self.assertEqual(str(expected_soa),
+                         str(session._UpdateSession__added_soa))
+
+    def check_full_handle_result(self, expected, updates):
+        '''Helper method for checking the result of a full handle;
+           creates an update session, and fills it with the list of rrsets
+           from 'updates'. Then checks if __handle()
+           results in a response with rcode 'expected'.'''
+        msg = create_update_msg([TEST_ZONE_RECORD], [], updates)
+        zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+                             self._acl_map)
+        session = UpdateSession(msg, TEST_CLIENT4, zconfig)
+
         # Now see if handle finds the same result
         (result, _, _) = session.handle()
-        self.assertEqual(expected,
-                         session._UpdateSession__message.get_rcode())
+        self.assertEqual(expected.to_text(),
+                         session._UpdateSession__message.get_rcode().to_text())
         # And that the result looks right
         if expected == Rcode.NOERROR():
             self.assertEqual(UPDATE_SUCCESS, result)
         else:
             self.assertEqual(UPDATE_ERROR, result)
 
+
     def test_check_prerequisites(self):
         # This test checks if the actual prerequisite-type-specific
         # methods are called.
@@ -414,95 +512,47 @@ class SessionTest(SesseionTestBase):
         # in the specific prerequisite type tests)
 
         # Let's first define a number of prereq's that should succeed
-        rrset_exists_yes = isc.dns.RRset(isc.dns.Name("example.org"),
-                                         isc.dns.RRClass.ANY(),
-                                         isc.dns.RRType.SOA(),
-                                         isc.dns.RRTTL(0))
-
-        rrset_exists_value_yes = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                                               isc.dns.RRClass.IN(),
-                                               isc.dns.RRType.A(),
-                                               isc.dns.RRTTL(0))
-        rrset_exists_value_yes.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
-                                                       isc.dns.RRClass.IN(),
-                                                       "192.0.2.1"))
-
-        rrset_does_not_exist_yes = isc.dns.RRset(isc.dns.Name("foo.example.org"),
-                                                 isc.dns.RRClass.NONE(),
-                                                 isc.dns.RRType.SOA(),
-                                                 isc.dns.RRTTL(0))
-
-        name_in_use_yes = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                                        isc.dns.RRClass.ANY(),
-                                        isc.dns.RRType.ANY(),
-                                        isc.dns.RRTTL(0))
-
-        name_not_in_use_yes = isc.dns.RRset(isc.dns.Name("foo.example.org"),
-                                            isc.dns.RRClass.NONE(),
-                                            isc.dns.RRType.ANY(),
-                                            isc.dns.RRTTL(0))
-
-        rrset_exists_value_1 = isc.dns.RRset(isc.dns.Name("example.org"),
-                                             isc.dns.RRClass.IN(),
-                                             isc.dns.RRType.NS(),
-                                             isc.dns.RRTTL(0))
-        rrset_exists_value_1.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                                     isc.dns.RRClass.IN(),
-                                                     "ns1.example.org"))
-        rrset_exists_value_2 = isc.dns.RRset(isc.dns.Name("example.org"),
-                                             isc.dns.RRClass.IN(),
-                                             isc.dns.RRType.NS(),
-                                             isc.dns.RRTTL(0))
-        rrset_exists_value_2.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                                     isc.dns.RRClass.IN(),
-                                                     "ns2.example.org"))
-        rrset_exists_value_3 = isc.dns.RRset(isc.dns.Name("example.org"),
-                                             isc.dns.RRClass.IN(),
-                                             isc.dns.RRType.NS(),
-                                             isc.dns.RRTTL(0))
-        rrset_exists_value_3.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
-                                                     isc.dns.RRClass.IN(),
-                                                     "ns3.example.org"))
+        rrset_exists_yes = create_rrset("example.org", RRClass.ANY(),
+                                        RRType.SOA(), 0)
+
+        rrset_exists_value_yes = create_rrset("www.example.org", RRClass.IN(),
+                                              RRType.A(), 0, [ "192.0.2.1" ])
+
+        rrset_does_not_exist_yes = create_rrset("foo.example.org",
+                                                RRClass.NONE(), RRType.SOA(),
+                                                0)
+
+        name_in_use_yes = create_rrset("www.example.org", RRClass.ANY(),
+                                       RRType.ANY(), 0)
+
+        name_not_in_use_yes = create_rrset("foo.example.org", RRClass.NONE(),
+                                           RRType.ANY(), 0)
+
+        rrset_exists_value_1 = create_rrset("example.org", RRClass.IN(),
+                                            RRType.NS(), 0,
+                                            [ "ns1.example.org" ])
+        rrset_exists_value_2 = create_rrset("example.org", RRClass.IN(),
+                                            RRType.NS(), 0,
+                                            [ "ns2.example.org" ])
+        rrset_exists_value_3 = create_rrset("example.org", RRClass.IN(),
+                                            RRType.NS(), 0,
+                                            [ "ns3.example.org" ])
 
         # and a number that should not
-        rrset_exists_no = isc.dns.RRset(isc.dns.Name("foo.example.org"),
-                                        isc.dns.RRClass.ANY(),
-                                        isc.dns.RRType.SOA(),
-                                        isc.dns.RRTTL(0))
-
-
-        rrset_exists_value_no = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                                              isc.dns.RRClass.IN(),
-                                              isc.dns.RRType.A(),
-                                              isc.dns.RRTTL(0))
-        rrset_exists_value_no.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
-                                                      isc.dns.RRClass.IN(),
-                                                      "192.0.2.2"))
-
-        rrset_does_not_exist_no = isc.dns.RRset(isc.dns.Name("example.org"),
-                                                isc.dns.RRClass.NONE(),
-                                                isc.dns.RRType.SOA(),
-                                                isc.dns.RRTTL(0))
-
-        name_in_use_no = isc.dns.RRset(isc.dns.Name("foo.example.org"),
-                                       isc.dns.RRClass.ANY(),
-                                       isc.dns.RRType.ANY(),
-                                       isc.dns.RRTTL(0))
-
-        name_not_in_use_no = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                                           isc.dns.RRClass.NONE(),
-                                           isc.dns.RRType.ANY(),
-                                           isc.dns.RRTTL(0))
-
-        # Create an UPDATE with all 5 'yes' prereqs
-        create_update_msg([TEST_ZONE_RECORD],
-                          [rrset_exists_yes,
-                           rrset_does_not_exist_yes,
-                           name_in_use_yes,
-                           name_not_in_use_yes,
-                           rrset_exists_value_yes,
-                           ])
+        rrset_exists_no = create_rrset("foo.example.org", RRClass.ANY(),
+                                       RRType.SOA(), 0)
+
+        rrset_exists_value_no = create_rrset("www.example.org", RRClass.IN(),
+                                             RRType.A(), 0, [ "192.0.2.2" ])
+
+        rrset_does_not_exist_no = create_rrset("example.org", RRClass.NONE(),
+                                               RRType.SOA(), 0)
+
+        name_in_use_no = create_rrset("foo.example.org", RRClass.ANY(),
+                                      RRType.ANY(), 0)
 
+        name_not_in_use_no = create_rrset("www.example.org", RRClass.NONE(),
+                                          RRType.ANY(), 0)
         # check 'no' result codes
         self.check_prerequisite_result(Rcode.NXRRSET(),
                                        [ rrset_exists_no ])
@@ -516,6 +566,23 @@ class SessionTest(SesseionTestBase):
                                        [ name_not_in_use_no ])
 
         # the 'yes' codes should result in ok
+        # individually
+        self.check_prerequisite_result(Rcode.NOERROR(),
+                                       [ rrset_exists_yes ] )
+        self.check_prerequisite_result(Rcode.NOERROR(),
+                                       [ rrset_exists_value_yes ])
+        self.check_prerequisite_result(Rcode.NOERROR(),
+                                       [ rrset_does_not_exist_yes ])
+        self.check_prerequisite_result(Rcode.NOERROR(),
+                                       [ name_in_use_yes ])
+        self.check_prerequisite_result(Rcode.NOERROR(),
+                                       [ name_not_in_use_yes ])
+        self.check_prerequisite_result(Rcode.NOERROR(),
+                                       [ rrset_exists_value_1,
+                                         rrset_exists_value_2,
+                                         rrset_exists_value_3])
+
+        # and together
         self.check_prerequisite_result(Rcode.NOERROR(),
                                        [ rrset_exists_yes,
                                          rrset_exists_value_yes,
@@ -546,70 +613,607 @@ class SessionTest(SesseionTestBase):
                                          rrset_exists_value_1])
 
     def test_prerequisite_notzone(self):
-        rrset = isc.dns.RRset(isc.dns.Name("some.other.zone."),
-                              isc.dns.RRClass.ANY(),
-                              isc.dns.RRType.SOA(),
-                              isc.dns.RRTTL(0))
+        rrset = create_rrset("some.other.zone.", RRClass.ANY(), RRType.SOA(), 0)
         self.check_prerequisite_result(Rcode.NOTZONE(), [ rrset ])
 
     def test_prerequisites_formerr(self):
         # test for form errors in the prerequisite section
 
         # Class ANY, non-zero TTL
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              isc.dns.RRClass.ANY(),
-                              isc.dns.RRType.SOA(),
-                              isc.dns.RRTTL(1))
+        rrset = create_rrset("example.org", RRClass.ANY(), RRType.SOA(), 1)
         self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
 
         # Class ANY, but with rdata
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              isc.dns.RRClass.ANY(),
-                              isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
-                                      isc.dns.RRClass.ANY(),
-                                      "\# 04 00 00 00 00"))
+        rrset = create_rrset("example.org", RRClass.ANY(), RRType.A(), 0,
+                             [ b'\x00\x00\x00\x00' ])
         self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
 
         # Class NONE, non-zero TTL
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              isc.dns.RRClass.NONE(),
-                              isc.dns.RRType.SOA(),
-                              isc.dns.RRTTL(1))
+        rrset = create_rrset("example.org", RRClass.NONE(), RRType.SOA(), 1)
         self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
 
         # Class NONE, but with rdata
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              isc.dns.RRClass.NONE(),
-                              isc.dns.RRType.A(),
-                              isc.dns.RRTTL(0))
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
-                                      isc.dns.RRClass.NONE(),
-                                      "\# 04 00 00 00 00"))
+        rrset = create_rrset("example.org", RRClass.NONE(), RRType.A(), 0,
+                             [ b'\x00\x00\x00\x00' ])
         self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
 
         # Matching class and type, but non-zero TTL
-        rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
-                              isc.dns.RRClass.IN(),
-                              isc.dns.RRType.A(),
-                              isc.dns.RRTTL(1))
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
-                                      isc.dns.RRClass.IN(),
-                                      "192.0.2.1"))
+        rrset = create_rrset("www.example.org", RRClass.IN(), RRType.A(), 1,
+                             [ "192.0.2.1" ])
         self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
 
         # Completely different class
-        rrset = isc.dns.RRset(isc.dns.Name("example.org"),
-                              isc.dns.RRClass.CH(),
-                              isc.dns.RRType.TXT(),
-                              isc.dns.RRTTL(0))
-        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.TXT(),
-                                      isc.dns.RRClass.CH(),
-                                      "foo"))
+        rrset = create_rrset("example.org", RRClass.CH(), RRType.TXT(), 0,
+                             [ "foo" ])
         self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
 
-class SessionACLTest(SesseionTestBase):
+    def __prereq_helper(self, method, expected, rrset):
+        '''Calls the given method with self._datasrc_client
+           and the given rrset, and compares the return value.
+           Function does not do much but makes the code look nicer'''
+        self.assertEqual(expected, method(rrset))
+
+    def __initialize_update_rrsets(self):
+        '''Prepare a number of RRsets to be used in several update tests
+           The rrsets are stored in self'''
+        orig_a_rrset = create_rrset("www.example.org", TEST_RRCLASS,
+                                    RRType.A(), 3600, [ "192.0.2.1" ])
+        self.orig_a_rrset = orig_a_rrset
+
+        rrset_update_a = create_rrset("www.example.org", TEST_RRCLASS,
+                                      RRType.A(), 3600,
+                                      [ "192.0.2.2", "192.0.2.3" ])
+        self.rrset_update_a = rrset_update_a
+
+        rrset_update_soa = create_rrset("example.org", TEST_RRCLASS,
+                                        RRType.SOA(), 3600,
+                                        [ "ns1.example.org. " +
+                                          "admin.example.org. " +
+                                          "1233 3600 1800 2419200 7200" ])
+        self.rrset_update_soa = rrset_update_soa
+
+        rrset_update_soa_del = create_rrset("example.org", RRClass.NONE(),
+                                            RRType.SOA(), 0,
+                                            [ "ns1.example.org. " +
+                                              "admin.example.org. " +
+                                              "1233 3600 1800 2419200 7200" ])
+        self.rrset_update_soa_del = rrset_update_soa_del
+
+
+        rrset_update_soa2 = create_rrset("example.org", TEST_RRCLASS,
+                                         RRType.SOA(), 3600,
+                                         [ "ns1.example.org. " +
+                                           "admin.example.org. " +
+                                           "4000 3600 1800 2419200 7200" ])
+        self.rrset_update_soa2 = rrset_update_soa2
+
+        rrset_update_del_name = create_rrset("www.example.org", RRClass.ANY(),
+                                             RRType.ANY(), 0)
+        self.rrset_update_del_name = rrset_update_del_name
+
+        rrset_update_del_name_apex = create_rrset("example.org", RRClass.ANY(),
+                                                  RRType.ANY(), 0)
+        self.rrset_update_del_name_apex = rrset_update_del_name_apex
+
+        rrset_update_del_rrset = create_rrset("www.example.org", RRClass.ANY(),
+                                              RRType.A(), 0)
+        self.rrset_update_del_rrset = rrset_update_del_rrset
+
+        rrset_update_del_mx_apex = create_rrset("example.org", RRClass.ANY(),
+                                                RRType.MX(), 0)
+        self.rrset_update_del_mx_apex = rrset_update_del_mx_apex
+
+        rrset_update_del_soa_apex = create_rrset("example.org", RRClass.ANY(),
+                                                 RRType.SOA(), 0)
+        self.rrset_update_del_soa_apex = rrset_update_del_soa_apex
+
+        rrset_update_del_ns_apex = create_rrset("example.org", RRClass.ANY(),
+                                                RRType.NS(), 0)
+        self.rrset_update_del_ns_apex = rrset_update_del_ns_apex
+
+        rrset_update_del_rrset_part = create_rrset("www.example.org",
+                                                   RRClass.NONE(), RRType.A(),
+                                                   0,
+                                                   [ b'\xc0\x00\x02\x02',
+                                                     b'\xc0\x00\x02\x03' ])
+        self.rrset_update_del_rrset_part = rrset_update_del_rrset_part
+
+        rrset_update_del_rrset_ns = create_rrset("example.org", RRClass.NONE(),
+                                        RRType.NS(), 0,
+                                        [ b'\x03ns1\x07example\x03org\x00',
+                                          b'\x03ns2\x07example\x03org\x00',
+                                          b'\x03ns3\x07example\x03org\x00' ])
+        self.rrset_update_del_rrset_ns = rrset_update_del_rrset_ns
+
+        rrset_update_del_rrset_mx = create_rrset("example.org", RRClass.NONE(),
+                                RRType.MX(), 0,
+                                [ b'\x00\x0a\x04mail\x07example\x03org\x00' ])
+        self.rrset_update_del_rrset_mx = rrset_update_del_rrset_mx
+
+    def test_prescan(self):
+        '''Test whether the prescan succeeds on data that is ok, and whether
+           if notices the SOA if present'''
+        # prepare a set of correct update statements
+        self.__initialize_update_rrsets()
+
+        self.check_prescan_result(Rcode.NOERROR(), [ self.rrset_update_a ])
+
+        # check if soa is noticed
+        self.check_prescan_result(Rcode.NOERROR(), [ self.rrset_update_soa ],
+                                  self.rrset_update_soa)
+
+        # Other types of succesful prechecks
+        self.check_prescan_result(Rcode.NOERROR(), [ self.rrset_update_soa2 ],
+                                  self.rrset_update_soa2)
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [ self.rrset_update_del_name ])
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [ self.rrset_update_del_name_apex ])
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [ self.rrset_update_del_rrset ])
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [ self.rrset_update_del_mx_apex ])
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [ self.rrset_update_del_rrset_part ])
+
+        # and check a few permutations of the above
+        # all of them (with one of the soas)
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [
+                                    self.rrset_update_a,
+                                    self.rrset_update_soa,
+                                    self.rrset_update_del_name,
+                                    self.rrset_update_del_name_apex,
+                                    self.rrset_update_del_rrset,
+                                    self.rrset_update_del_mx_apex,
+                                    self.rrset_update_del_rrset_part
+                                  ],
+                                  self.rrset_update_soa)
+
+        # Two soas. Should we reject or simply use the last?
+        # (RFC is not really explicit on this, but between the lines I read
+        # use the last)
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [ self.rrset_update_soa,
+                                    self.rrset_update_soa2 ],
+                                    self.rrset_update_soa2)
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [ self.rrset_update_soa2,
+                                    self.rrset_update_soa ],
+                                  self.rrset_update_soa)
+
+        self.check_prescan_result(Rcode.NOERROR(),
+                                  [
+                                    self.rrset_update_del_mx_apex,
+                                    self.rrset_update_del_name,
+                                    self.rrset_update_del_name_apex,
+                                    self.rrset_update_del_rrset_part,
+                                    self.rrset_update_a,
+                                    self.rrset_update_del_rrset,
+                                    self.rrset_update_soa
+                                  ],
+                                  self.rrset_update_soa)
+
+    def test_prescan_failures(self):
+        '''Test whether prescan fails on bad data'''
+        # out of zone data
+        rrset = create_rrset("different.zone", RRClass.ANY(), RRType.TXT(), 0)
+        self.check_prescan_result(Rcode.NOTZONE(), [ rrset ])
+
+
+        # forbidden type, zone class
+        rrset = create_rrset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.ANY(), 0,
+                             [ b'\x00' ])
+        self.check_prescan_result(Rcode.FORMERR(), [ rrset ])
+
+        # non-zero TTL, class ANY
+        rrset = create_rrset(TEST_ZONE_NAME, RRClass.ANY(), RRType.TXT(), 1)
+        self.check_prescan_result(Rcode.FORMERR(), [ rrset ])
+
+        # non-zero Rdata, class ANY
+        rrset = create_rrset(TEST_ZONE_NAME, RRClass.ANY(), RRType.TXT(), 0,
+                             [ "foo" ])
+        self.check_prescan_result(Rcode.FORMERR(), [ rrset ])
+
+        # forbidden type, class ANY
+        rrset = create_rrset(TEST_ZONE_NAME, RRClass.ANY(), RRType.AXFR(), 0,
+                             [ b'\x00' ])
+        self.check_prescan_result(Rcode.FORMERR(), [ rrset ])
+
+        # non-zero TTL, class NONE
+        rrset = create_rrset(TEST_ZONE_NAME, RRClass.NONE(), RRType.TXT(), 1)
+        self.check_prescan_result(Rcode.FORMERR(), [ rrset ])
+
+        # forbidden type, class NONE
+        rrset = create_rrset(TEST_ZONE_NAME, RRClass.NONE(), RRType.AXFR(), 0,
+                             [ b'\x00' ])
+        self.check_prescan_result(Rcode.FORMERR(), [ rrset ])
+
+    def __check_inzone_data(self, expected_result, name, rrtype,
+                            expected_rrset = None):
+        '''Does a find on TEST_ZONE for the given rrset's name and type,
+           then checks if the result matches the expected result.
+           If so, and if expected_rrset is given, they are compared as
+           well.'''
+        _, finder = self._datasrc_client.find_zone(TEST_ZONE_NAME)
+        result, found_rrset, _ = finder.find(name, rrtype,
+                                             finder.NO_WILDCARD |
+                                             finder.FIND_GLUE_OK)
+        self.assertEqual(expected_result, result)
+        # Sigh. Need rrsets.compare() again.
+        # To be sure, compare name, class, type, and ttl
+        if expected_rrset is not None:
+            self.assertEqual(expected_rrset.get_name(), found_rrset.get_name())
+            self.assertEqual(expected_rrset.get_class(), found_rrset.get_class())
+            self.assertEqual(expected_rrset.get_type(), found_rrset.get_type())
+            self.assertEqual(expected_rrset.get_ttl().to_text(),
+                             found_rrset.get_ttl().to_text())
+            expected_rdata =\
+                [ rdata.to_text() for rdata in expected_rrset.get_rdata() ]
+            found_rdata =\
+                [ rdata.to_text() for rdata in found_rrset.get_rdata() ]
+            expected_rdata.sort()
+            found_rdata.sort()
+            self.assertEqual(expected_rdata, found_rdata)
+
+    def test_update_add_delete_rrset(self):
+        '''
+        Tests a sequence of related add and delete updates. Some other
+        cases are tested by later tests.
+        '''
+        self.__initialize_update_rrsets()
+
+        # initially, the www should only contain one rr
+        # (set to self.orig_a_rrset)
+
+        # during this test, we will extend it at some point
+        extended_a_rrset = create_rrset("www.example.org", TEST_RRCLASS,
+                                        RRType.A(), 3600,
+                                        [ "192.0.2.1",
+                                          "192.0.2.2",
+                                          "192.0.2.3" ])
+
+        # Sanity check, make sure original data is really there before updates
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A(),
+                                 self.orig_a_rrset)
+
+        # Add two rrs
+        self.check_full_handle_result(Rcode.NOERROR(), [ self.rrset_update_a ])
+
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A(),
+                                 extended_a_rrset)
+
+        # Adding the same RRsets should not make a difference.
+        self.check_full_handle_result(Rcode.NOERROR(), [ self.rrset_update_a ])
+
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A(),
+                                 extended_a_rrset)
+
+        # Now delete those two, and we should end up with the original RRset
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_del_rrset_part ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A(),
+                                 self.orig_a_rrset)
+
+        # 'Deleting' them again should make no difference
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_del_rrset_part ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A(),
+                                 self.orig_a_rrset)
+
+        # But deleting the entire rrset, independent of its contents, should
+        # work
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_del_rrset ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A())
+
+        # Check that if we update the SOA, it is updated to our value
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_soa2 ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("example.org"),
+                                 RRType.SOA(),
+                                 self.rrset_update_soa2)
+
+    def test_glue_deletions(self):
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("sub.example.org."),
+                                 RRType.NS())
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("ns.sub.example.org."),
+                                 RRType.A())
+
+        # See that we can delete glue
+        rrset_delete_glue = create_rrset("ns.sub.example.org.",
+                                         RRClass.ANY(),
+                                         RRType.A(),
+                                         0)
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ rrset_delete_glue ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("sub.example.org."),
+                                 RRType.NS())
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+                                 isc.dns.Name("ns.sub.example.org."),
+                                 RRType.A())
+
+        # Check that we don't accidentally delete a delegation if we
+        # try to delete non-existent glue
+        rrset_delete_nonexistent_glue = create_rrset("foo.sub.example.org.",
+                                                     RRClass.ANY(),
+                                                     RRType.A(),
+                                                     0)
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ rrset_delete_nonexistent_glue ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("sub.example.org."),
+                                 RRType.NS())
+
+    def test_update_add_new_data(self):
+        '''
+        This tests adds data where none is present
+        '''
+        # Add data at a completely new name
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+                                 isc.dns.Name("new.example.org"),
+                                 RRType.A())
+        rrset = create_rrset("new.example.org", TEST_RRCLASS, RRType.A(),
+                             3600, [ "192.0.2.1", "192.0.2.2" ])
+        self.check_full_handle_result(Rcode.NOERROR(), [ rrset ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("new.example.org"),
+                                 RRType.A(),
+                                 rrset)
+
+        # Also try a name where data is present, but none of this
+        # specific type
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
+                                 isc.dns.Name("new.example.org"),
+                                 RRType.TXT())
+        rrset = create_rrset("new.example.org", TEST_RRCLASS, RRType.TXT(),
+                             3600, [ "foo" ])
+        self.check_full_handle_result(Rcode.NOERROR(), [ rrset ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("new.example.org"),
+                                 RRType.TXT(),
+                                 rrset)
+
+    def test_update_add_new_data_interspersed(self):
+        '''
+        This tests adds data where none is present, similar to
+        test_update_add_new_data, but this time the second RRset
+        is put into the record between the two RRs of the first
+        RRset.
+        '''
+        # Add data at a completely new name
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+                                 isc.dns.Name("new_a.example.org"),
+                                 RRType.A())
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+                                 isc.dns.Name("new_txt.example.org"),
+                                 RRType.TXT())
+
+        rrset1 = create_rrset("new_a.example.org", TEST_RRCLASS, RRType.A(),
+                              3600, [ "192.0.2.1" ])
+
+        rrset2 = create_rrset("new_txt.example.org", TEST_RRCLASS, RRType.TXT(),
+                              3600, [ "foo" ])
+
+        rrset3 = create_rrset("new_a.example.org", TEST_RRCLASS, RRType.A(),
+                              3600, [ "192.0.2.2" ])
+
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ rrset1, rrset2, rrset3 ])
+
+        # The update should have merged rrset1 and rrset3
+        rrset_merged = create_rrset("new_a.example.org", TEST_RRCLASS,
+                                    RRType.A(), 3600,
+                                    [ "192.0.2.1", "192.0.2.2" ])
+
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("new_a.example.org"),
+                                 RRType.A(),
+                                 rrset_merged)
+
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("new_txt.example.org"),
+                                 RRType.TXT(),
+                                 rrset2)
+
+    def test_update_delete_name(self):
+        self.__initialize_update_rrsets()
+
+        # First check it is there
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A())
+
+        # Delete the entire name
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_del_name ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A())
+
+        # Should still be gone after pointless second delete
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_del_name ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A())
+
+    def test_update_apex_special_cases(self):
+        '''
+        Tests a few special cases when deleting data from the apex
+        '''
+        self.__initialize_update_rrsets()
+
+        # the original SOA
+        orig_soa_rrset = create_rrset("example.org", TEST_RRCLASS,
+                                      RRType.SOA(), 3600,
+                                      [ "ns1.example.org. " +
+                                        "admin.example.org. " +
+                                        "1234 3600 1800 2419200 7200" ])
+
+        # We will delete some of the NS records
+        orig_ns_rrset = create_rrset("example.org", TEST_RRCLASS,
+                                      RRType.NS(), 3600,
+                                      [ "ns1.example.org.",
+                                        "ns2.example.org.",
+                                        "ns3.example.org." ])
+
+        # Sanity check, make sure original data is really there before updates
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("example.org"),
+                                 RRType.NS(),
+                                 orig_ns_rrset)
+        # We will delete the MX record later in this test, so let's make
+        # sure that it exists (we do not care about its value)
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("example.org"),
+                                 RRType.MX())
+
+        # Check that we cannot delete the SOA record by direction deletion
+        # both by name+type and by full rrset
+        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)
+
+        # If we delete everything at the apex, the SOA and NS rrsets should be
+        # untouched
+        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)
+        # but the MX should be gone
+        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
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_del_ns_apex ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("example.org"),
+                                 RRType.NS(),
+                                 orig_ns_rrset)
+
+    def DISABLED_test_update_apex_special_case_ns_rrset(self):
+        # If we delete the NS at the apex specifically, it should still
+        # keep one record
+        self.__initialize_update_rrsets()
+        # When we are done, we should have a reduced NS rrset
+        short_ns_rrset = create_rrset("example.org", TEST_RRCLASS,
+                                      RRType.NS(), 3600,
+                                      [ "ns3.example.org." ])
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_del_rrset_ns ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("example.org"),
+                                 RRType.NS(),
+                                 short_ns_rrset)
+
+    def test_update_delete_normal_rrset_at_apex(self):
+        '''
+        Tests a number of 'normal rrset' deletes at the apex
+        '''
+
+        # MX should simply be deleted
+        self.__initialize_update_rrsets()
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("example.org"),
+                                 RRType.MX())
+        self.check_full_handle_result(Rcode.NOERROR(),
+                                      [ self.rrset_update_del_rrset_mx ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
+                                 isc.dns.Name("example.org"),
+                                 RRType.MX())
+
+    def test_update_cname_special_cases(self):
+        self.__initialize_update_rrsets()
+
+        # Sanity check
+        orig_cname_rrset = create_rrset("cname.example.org", TEST_RRCLASS,
+                                        RRType.CNAME(), 3600,
+                                        [ "www.example.org." ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
+                                 isc.dns.Name("cname.example.org"),
+                                 RRType.A(),
+                                 orig_cname_rrset)
+
+        # If we try to add data where a cname is preset
+        rrset = create_rrset("cname.example.org", TEST_RRCLASS, RRType.A(),
+                             3600, [ "192.0.2.1" ])
+
+        self.check_full_handle_result(Rcode.NOERROR(), [ rrset ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
+                                 isc.dns.Name("cname.example.org"),
+                                 RRType.A(),
+                                 orig_cname_rrset)
+
+        # But updating the cname itself should work
+        new_cname_rrset = create_rrset("cname.example.org", TEST_RRCLASS,
+                                       RRType.CNAME(), 3600,
+                                       [ "mail.example.org." ])
+        self.check_full_handle_result(Rcode.NOERROR(), [ new_cname_rrset ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
+                                 isc.dns.Name("cname.example.org"),
+                                 RRType.A(),
+                                 new_cname_rrset)
+
+        self.__initialize_update_rrsets()
+
+        # Likewise, adding a cname where other data is
+        # present should do nothing either
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A(),
+                                 self.orig_a_rrset)
+        new_cname_rrset = create_rrset("www.example.org", TEST_RRCLASS,
+                                       RRType.CNAME(), 3600,
+                                       [ "mail.example.org." ])
+        self.check_full_handle_result(Rcode.NOERROR(), [ new_cname_rrset ])
+        self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+                                 isc.dns.Name("www.example.org"),
+                                 RRType.A(),
+                                 self.orig_a_rrset)
+
+    def test_update_bad_class(self):
+        rrset = create_rrset("example.org.", RRClass.CH(), RRType.TXT(), 0,
+                             [ "foo" ])
+        self.check_full_handle_result(Rcode.FORMERR(), [ rrset ])
+
+    def test_uncaught_exception(self):
+        def my_exc():
+            raise Exception("foo")
+        self._session._UpdateSession__update_soa = my_exc
+        self.assertEqual(Rcode.SERVFAIL().to_text(),
+                         self._session._UpdateSession__do_update().to_text())
+
+class SessionACLTest(SessionTestBase):
     '''ACL related tests for update session.'''
     def test_update_acl_check(self):
         '''Test for various ACL checks.
diff --git a/src/lib/testutils/testdata/rwtest.sqlite3 b/src/lib/testutils/testdata/rwtest.sqlite3
index 24afc2c..558bc3f 100644
Binary files a/src/lib/testutils/testdata/rwtest.sqlite3 and b/src/lib/testutils/testdata/rwtest.sqlite3 differ



More information about the bind10-changes mailing list