Browse Source

[master] Merge branch 'trac2438'

resolved Conflicts:
	src/lib/dns/python/Makefile.am
	src/lib/dns/python/rrset_collection_python.cc
	src/lib/dns/python/rrset_collection_python.h
	src/lib/dns/python/rrset_collection_python_inc.cc
JINMEI Tatuya 12 years ago
parent
commit
c4df99eac2

+ 4 - 1
configure.ac

@@ -282,7 +282,10 @@ AC_SUBST(PYTHON_LOGMSGPKG_DIR)
 
 
 # This is python package paths commonly used in python tests.  See
 # This is python package paths commonly used in python tests.  See
 # README of log_messages for why it's included.
 # README of log_messages for why it's included.
-COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python"
+# lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module.  #2145 should eliminate the need for it.
+COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python:\$(abs_top_builddir)/src/lib/dns/python/.libs"
 AC_SUBST(COMMON_PYTHON_PATH)
 AC_SUBST(COMMON_PYTHON_PATH)
 
 
 # Check for python development environments
 # Check for python development environments

+ 4 - 1
src/bin/bindctl/run_bindctl.sh.in

@@ -20,7 +20,10 @@ export PYTHON_EXEC
 
 
 BINDCTL_PATH=@abs_top_builddir@/src/bin/bindctl
 BINDCTL_PATH=@abs_top_builddir@/src/bin/bindctl
 
 
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module.  #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
 export PYTHONPATH
 export PYTHONPATH
 
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 4 - 1
src/bin/dbutil/run_dbutil.sh.in

@@ -20,7 +20,10 @@ export PYTHON_EXEC
 
 
 DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil
 DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil
 
 
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module.  #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
 export PYTHONPATH
 export PYTHONPATH
 
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 5 - 4
src/bin/sysinfo/run_sysinfo.sh.in

@@ -20,10 +20,11 @@ export PYTHON_EXEC
 
 
 SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
 SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
 
 
-# Note: we shouldn't need log_messages except for the seemingly necessary
-# dependency due to the automatic import in the isc package (its __init__.py
-# imports some other modules)
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages
+# Note: we shouldn't need log_messages and lib/dns except for the seemingly
+# necessary dependency due to the automatic import in the isc package (its
+# __init__.py imports some other modules)
+# #2145 should eliminate the need for them.
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/dns/python/.libs
 export PYTHONPATH
 export PYTHONPATH
 
 
 # Likewise, we need only because isc.log requires some loadable modules.
 # Likewise, we need only because isc.log requires some loadable modules.

+ 50 - 23
src/lib/dns/python/rrset_collection_python.cc

@@ -55,10 +55,23 @@ setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
 }
 }
 }
 }
 
 
+// RRsetCollectionBase: the base RRsetCollection class in Python.
 //
 //
-// RRsetCollectionBase
-//
-
+// Any derived RRsetCollection class is supposed to be inherited from this
+// class:
+// - If the derived class is implemented via a C++ wrapper (associated with
+//   a C++ implementation of RRsetCollection), its PyTypeObject should
+//   specify rrset_collection_base_type for tp_base.  Its C/C++-representation
+//   of objects should be compatible with s_RRsetCollection, and the wrapper
+//   should set its cppobj member to point to the corresponding C++
+//   RRsetCollection object.  Normally it doesn't have to provide Python
+//   wrapper of find(); the Python interpreter will then call find() on
+//   the base class, which ensures that the corresponding C++ version of
+//   find() will be used.
+// - If the derived class is implemented purely in Python, it must implement
+//   find() in Python within the class.  As explained in the first bullet,
+//   the base class method is generally expected to be used only for C++
+//   wrapper of RRsetCollection derived class.
 namespace {
 namespace {
 int
 int
 RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
 RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
@@ -70,8 +83,13 @@ RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
 void
 void
 RRsetCollectionBase_destroy(PyObject* po_self) {
 RRsetCollectionBase_destroy(PyObject* po_self) {
     s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
     s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
-    delete self->cppobj;
-    self->cppobj = NULL;
+
+    // Any C++-wrapper of derived RRsetCollection class should have its own
+    // destroy function (as it may manage cppobj in its own way);
+    // Python-only derived classes shouldn't set cppobj (which is
+    // 0-initialized).  So this assertion must hold.
+    assert(self->cppobj == NULL);
+
     Py_TYPE(self)->tp_free(self);
     Py_TYPE(self)->tp_free(self);
 }
 }
 
 
@@ -79,6 +97,9 @@ PyObject*
 RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
 RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
     s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
     s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
 
 
+    // If this function is called with cppobj being NULL, this means
+    // a pure-Python derived class skips implementing its own find().
+    // This is an error (see general description above).
     if (self->cppobj == NULL) {
     if (self->cppobj == NULL) {
         PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
         PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
                      "derived RRsetCollection class");
                      "derived RRsetCollection class");
@@ -108,6 +129,9 @@ RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
             }
             }
             Py_RETURN_NONE;
             Py_RETURN_NONE;
         }
         }
+    } catch (const RRsetCollectionError& ex) {
+        PyErr_SetString(po_RRsetCollectionError, ex.what());
+        return (NULL);
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
         const string ex_what = "Unexpected failure in "
         const string ex_what = "Unexpected failure in "
             "RRsetCollectionBase.find: " + string(ex.what());
             "RRsetCollectionBase.find: " + string(ex.what());
@@ -137,6 +161,9 @@ PyMethodDef RRsetCollectionBase_methods[] = {
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 namespace python {
 namespace python {
+// Definition of class specific exception(s)
+PyObject* po_RRsetCollectionError;
+
 // This defines the complete type for reflection in python and
 // This defines the complete type for reflection in python and
 // parsing of PyObject* to s_RRsetCollection
 // parsing of PyObject* to s_RRsetCollection
 // Most of the functions are not actually implemented and NULL here.
 // Most of the functions are not actually implemented and NULL here.
@@ -193,18 +220,27 @@ PyTypeObject rrset_collection_base_type = {
 // Module Initialization, all statics are initialized here
 // Module Initialization, all statics are initialized here
 bool
 bool
 initModulePart_RRsetCollectionBase(PyObject* mod) {
 initModulePart_RRsetCollectionBase(PyObject* mod) {
-    // We initialize the static description object with PyType_Ready(),
-    // then add it to the module. This is not just a check! (leaving
-    // this out results in segmentation faults)
-    if (PyType_Ready(&rrset_collection_base_type) < 0) {
+    if (!initClass(rrset_collection_base_type, "RRsetCollectionBase", mod)) {
         return (false);
         return (false);
     }
     }
-    void* p = &rrset_collection_base_type;
-    if (PyModule_AddObject(mod, "RRsetCollectionBase",
-                           static_cast<PyObject*>(p)) < 0) {
+
+    try {
+        po_RRsetCollectionError =
+            PyErr_NewException("dns.RRsetCollectionError",
+                               po_IscException, NULL);
+        PyObjectContainer(po_RRsetCollectionError).installToModule(
+            mod, "RRsetCollectionError");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in RRsetCollectionBase initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "RRsetCollectionBase initialization");
         return (false);
         return (false);
     }
     }
-    Py_INCREF(&rrset_collection_base_type);
 
 
     return (true);
     return (true);
 }
 }
@@ -405,18 +441,9 @@ PyTypeObject rrset_collection_type = {
 // Module Initialization, all statics are initialized here
 // Module Initialization, all statics are initialized here
 bool
 bool
 initModulePart_RRsetCollection(PyObject* mod) {
 initModulePart_RRsetCollection(PyObject* mod) {
-    // We initialize the static description object with PyType_Ready(),
-    // then add it to the module. This is not just a check! (leaving
-    // this out results in segmentation faults)
-    if (PyType_Ready(&rrset_collection_type) < 0) {
-        return (false);
-    }
-    void* p = &rrset_collection_type;
-    if (PyModule_AddObject(mod, "RRsetCollection",
-                           static_cast<PyObject*>(p)) < 0) {
+    if (!initClass(rrset_collection_type, "RRsetCollection", mod)) {
         return (false);
         return (false);
     }
     }
-    Py_INCREF(&rrset_collection_type);
 
 
     return (true);
     return (true);
 }
 }

+ 8 - 2
src/lib/dns/python/rrset_collection_python.h

@@ -23,10 +23,13 @@ class RRsetCollectionBase;
 
 
 namespace python {
 namespace python {
 
 
-// The s_* Class simply covers one instantiation of the object
+// The s_* Class simply covers one instantiation of the object.
 // This structure will be commonly used for all derived classes of
 // This structure will be commonly used for all derived classes of
 // RRsetCollectionBase.  cppobj will point to an instance of the specific
 // RRsetCollectionBase.  cppobj will point to an instance of the specific
-// derived class.
+// derived class of (C++) RRsetCollectionBase.
+//
+// A C++ wrapper for Python version of RRsetCollection should set this
+// variable, and skip the implementation of C++ wrapper of find() method.
 class s_RRsetCollection : public PyObject {
 class s_RRsetCollection : public PyObject {
 public:
 public:
     s_RRsetCollection() : cppobj(NULL) {}
     s_RRsetCollection() : cppobj(NULL) {}
@@ -39,6 +42,9 @@ extern PyTypeObject rrset_collection_base_type;
 // Python type information for dns.RRsetCollection
 // Python type information for dns.RRsetCollection
 extern PyTypeObject rrset_collection_type;
 extern PyTypeObject rrset_collection_type;
 
 
+// Class specific exceptions
+extern PyObject* po_RRsetCollectionError;
+
 bool initModulePart_RRsetCollectionBase(PyObject* mod);
 bool initModulePart_RRsetCollectionBase(PyObject* mod);
 bool initModulePart_RRsetCollection(PyObject* mod);
 bool initModulePart_RRsetCollection(PyObject* mod);
 
 

+ 6 - 6
src/lib/dns/python/rrset_collection_python_inc.cc

@@ -1,7 +1,7 @@
 namespace {
 namespace {
 // Modifications
 // Modifications
 //   - libdns++ => isc.dns, libdatasrc => isc.datasrc
 //   - libdns++ => isc.dns, libdatasrc => isc.datasrc
-//   - note about the constructor.
+//   - note about the direct construction.
 //   - add note about iteration
 //   - add note about iteration
 const char* const RRsetCollectionBase_doc = "\
 const char* const RRsetCollectionBase_doc = "\
 Generic class to represent a set of RRsets.\n\
 Generic class to represent a set of RRsets.\n\
@@ -18,8 +18,8 @@ maybe class) and a way to iterate over all RRsets.\n\
 See RRsetCollection for a simple isc.dns implementation. Other modules\n\
 See RRsetCollection for a simple isc.dns implementation. Other modules\n\
 such as isc.datasrc will have another implementation.\n\
 such as isc.datasrc will have another implementation.\n\
 \n\
 \n\
-This base class cannot be directly instantiated, so no constructor is\n\
-defined.\n\
+This base class cannot be directly instantiated.  Such an attempt will\n\
+result in a TypeError exception.\n\
 \n\
 \n\
 ";
 ";
 
 
@@ -79,18 +79,18 @@ RRsetCollection(filename, origin, rrclass)\n\
       origin     (isc.dns.Name) The zone origin.\n\
       origin     (isc.dns.Name) The zone origin.\n\
       rrclass    (isc.dns.RRClass) The zone class.\n\
       rrclass    (isc.dns.RRClass) The zone class.\n\
 \n\
 \n\
-RRsetCollection(input_stream, origin, rrclass)\n\
+RRsetCollection(input, origin, rrclass)\n\
 \n\
 \n\
     Constructor.\n\
     Constructor.\n\
 \n\
 \n\
     This constructor is similar to the previous one, but instead of\n\
     This constructor is similar to the previous one, but instead of\n\
-    taking a filename to load a zone from, it takes a byte object,\n\
+    taking a filename to load a zone from, it takes a bytes object,\n\
     representing the zone contents in text.\n\
     representing the zone contents in text.\n\
     The constructor throws IscException if there is an error\n\
     The constructor throws IscException if there is an error\n\
     during loading.\n\
     during loading.\n\
 \n\
 \n\
     Parameters:\n\
     Parameters:\n\
-      input      (byte) Textual representation of the zone.\n\
+      input      (bytes) Textual representation of the zone.\n\
       origin     (isc.dns.Name) The zone origin.\n\
       origin     (isc.dns.Name) The zone origin.\n\
       rrclass    (isc.dns.RRClass) The zone class.\n\
       rrclass    (isc.dns.RRClass) The zone class.\n\
 \n\
 \n\

+ 2 - 0
src/lib/dns/rrset_collection_base.h

@@ -185,6 +185,8 @@ public:
     }
     }
 };
 };
 
 
+typedef boost::shared_ptr<RRsetCollectionBase> RRsetCollectionPtr;
+
 } // end of namespace dns
 } // end of namespace dns
 } // end of namespace isc
 } // end of namespace isc
 
 

+ 0 - 17
src/lib/python/isc/datasrc/datasrc.cc

@@ -218,23 +218,6 @@ initModulePart_ZoneLoader(PyObject* mod) {
 }
 }
 
 
 bool
 bool
-initModulePart_ZoneUpdater(PyObject* mod) {
-    // We initialize the static description object with PyType_Ready(),
-    // then add it to the module. This is not just a check! (leaving
-    // this out results in segmentation faults)
-    if (PyType_Ready(&zoneupdater_type) < 0) {
-        return (false);
-    }
-    void* zip = &zoneupdater_type;
-    if (PyModule_AddObject(mod, "ZoneUpdater", static_cast<PyObject*>(zip)) < 0) {
-        return (false);
-    }
-    Py_INCREF(&zoneupdater_type);
-
-    return (true);
-}
-
-bool
 initModulePart_ZoneJournalReader(PyObject* mod) {
 initModulePart_ZoneJournalReader(PyObject* mod) {
     if (PyType_Ready(&journal_reader_type) < 0) {
     if (PyType_Ready(&journal_reader_type) < 0) {
         return (false);
         return (false);

+ 35 - 0
src/lib/python/isc/datasrc/tests/datasrc_test.py

@@ -528,6 +528,41 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
                          rrset.to_text())
 
 
+    def test_updater_rrset_collection(self):
+        dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+        updater = dsc.get_updater(isc.dns.Name("example.com"), False)
+        updater_refs = sys.getrefcount(updater)
+
+        # Get (internally create) updater's RRset collection
+        rrsets = updater.get_rrset_collection()
+
+        # From this point we cannot make further updates
+        rrset = RRset(isc.dns.Name('www.example.com'), isc.dns.RRClass.IN(),
+                      isc.dns.RRType.AAAA(), isc.dns.RRTTL(10))
+        rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.AAAA(),
+                                      isc.dns.RRClass.IN(), '2001:db8::1'))
+        self.assertRaises(isc.datasrc.Error, updater.add_rrset, rrset)
+
+        # Checks basic API
+        found = rrsets.find(isc.dns.Name("www.example.com"),
+                            isc.dns.RRClass.IN(), isc.dns.RRType.A())
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         found.to_text())
+        self.assertEqual(None, rrsets.find(isc.dns.Name("www.example.com"),
+                                           isc.dns.RRClass.IN(),
+                                           isc.dns.RRType.AAAA()))
+
+        # Once committed collection cannot be used any more.
+        updater.commit()
+        self.assertRaises(isc.dns.RRsetCollectionError,
+                          rrsets.find, isc.dns.Name("www.example.com"),
+                          isc.dns.RRClass.IN(), isc.dns.RRType.A())
+
+        # When we destroy the RRsetCollection it should release the refcount
+        # to the updater.
+        rrsets = None
+        self.assertEqual(updater_refs, sys.getrefcount(updater))
+
     def test_two_modules(self):
     def test_two_modules(self):
         # load two modules, and check if they don't interfere
         # load two modules, and check if they don't interfere
         mem_cfg = { "type": "memory", "class": "IN", "zones": [] };
         mem_cfg = { "type": "memory", "class": "IN", "zones": [] };

+ 36 - 0
src/lib/python/isc/datasrc/updater_inc.cc

@@ -76,6 +76,10 @@ This method must not be called once commit() is performed. If it calls\n\
 after commit() the implementation must throw a isc.datasrc.Error\n\
 after commit() the implementation must throw a isc.datasrc.Error\n\
 exception.\n\
 exception.\n\
 \n\
 \n\
+Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
+after get_rrset_collection() is called. In this case, implementations\n\
+throw an InvalidOperation exception.\n\
+\n\
 Todo As noted above we may have to revisit the design details as we\n\
 Todo As noted above we may have to revisit the design details as we\n\
 gain experiences:\n\
 gain experiences:\n\
 \n\
 \n\
@@ -133,6 +137,10 @@ This method must not be called once commit() is performed. If it calls\n\
 after commit() the implementation must throw a isc.datasrc.Error\n\
 after commit() the implementation must throw a isc.datasrc.Error\n\
 exception.\n\
 exception.\n\
 \n\
 \n\
+Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
+after get_rrset_collection() is called. In this case, implementations\n\
+throw an InvalidOperation exception.\n\
+\n\
 Todo: As noted above we may have to revisit the design details as we\n\
 Todo: As noted above we may have to revisit the design details as we\n\
 gain experiences:\n\
 gain experiences:\n\
 \n\
 \n\
@@ -178,4 +186,32 @@ Exceptions:\n\
              error, or wrapper error\n\\n\
              error, or wrapper error\n\\n\
 \n\
 \n\
 ";
 ";
+
+// Modifications
+// - isc.datasrc.RRsetCollectionBase => isc.dns.RRsetCollectionBase
+//   (in the Python wrapper, the former is completely invisible)
+// - remove other reference to isc.datasrc.RRsetCollectionBase
+const char* const ZoneUpdater_getRRsetCollection_doc = "\
+get_rrset_collection() -> isc.dns.RRsetCollectionBase \n\
+\n\
+Return an RRsetCollection for the updater.\n\
+\n\
+This method returns an RRsetCollection for the updater, implementing\n\
+the isc.dns.RRsetCollectionBase interface. Typically, the returned\n\
+RRsetCollection is a singleton for its ZoneUpdater. The returned\n\
+RRsetCollection object must not be used after its corresponding\n\
+ZoneUpdater has been destroyed. The returned RRsetCollection object\n\
+may be used to search RRsets from the ZoneUpdater. The actual\n\
+RRsetCollection returned has a behavior dependent on the ZoneUpdater\n\
+implementation.\n\
+\n\
+The behavior of the RRsetCollection is similar to the behavior of the\n\
+Zonefinder returned by get_finder(). Implementations of ZoneUpdater\n\
+may not allow adding or deleting RRsets after get_rrset_collection()\n\
+is called. Implementations of ZoneUpdater may disable a previously\n\
+returned RRsetCollection after commit() is called. If an\n\
+RRsetCollection is disabled, using methods such as find() and using\n\
+its iterator would cause an exception to be thrown.\n\
+\n\
+";
 } // unnamed namespace
 } // unnamed namespace

+ 154 - 0
src/lib/python/isc/datasrc/updater_python.cc

@@ -32,6 +32,7 @@
 #include <dns/python/rrset_python.h>
 #include <dns/python/rrset_python.h>
 #include <dns/python/rrclass_python.h>
 #include <dns/python/rrclass_python.h>
 #include <dns/python/rrtype_python.h>
 #include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_collection_python.h>
 
 
 #include "datasrc.h"
 #include "datasrc.h"
 #include "updater_python.h"
 #include "updater_python.h"
@@ -195,6 +196,107 @@ ZoneUpdater_find_all(PyObject* po_self, PyObject* args) {
         &self->cppobj->getFinder(), args));
         &self->cppobj->getFinder(), args));
 }
 }
 
 
+namespace {
+// Below we define Python RRsetCollection class generated by the updater.
+// It's never expected to be instantiated directly from Python code, so
+// everything is hidden here, and tp_init always fails.
+
+class s_UpdaterRRsetCollection : public s_RRsetCollection {
+public:
+    s_UpdaterRRsetCollection() : s_RRsetCollection() {}
+    PyObject* base_obj_;
+};
+
+int
+RRsetCollection_init(PyObject*, PyObject*, PyObject*) {
+    // can't be called directly; actually, the constructor shouldn't even be
+    // called, but we catch the case just in case.
+    PyErr_SetString(PyExc_TypeError,
+                    "datasrc.RRsetCollection cannot be constructed directly");
+
+    return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+    s_UpdaterRRsetCollection* const self =
+        static_cast<s_UpdaterRRsetCollection*>(po_self);
+
+    // We don't own the C++ collection object; we shouldn't delete it here.
+
+    // Note: we need to check if this is NULL; it remains NULL in case of
+    // direct instantiation (which fails).
+    if (self->base_obj_ != NULL) {
+        Py_DECREF(self->base_obj_);
+    }
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyTypeObject updater_rrset_collection_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datasrc.UpdaterRRsetCollection",
+    sizeof(s_UpdaterRRsetCollection),   // tp_basicsize
+    0,                                  // tp_itemsize
+    RRsetCollection_destroy,            // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash
+    NULL,                               // tp_call
+    NULL,                               // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    NULL,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    NULL,                               // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,   // tp_base (rrset_collection_base_type, set at run time)
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    RRsetCollection_init,               // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+} // unnamed namespace
+
+PyObject*
+ZoneUpdater_getRRsetCollection(PyObject* po_self, PyObject*) {
+    s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+
+    s_UpdaterRRsetCollection* collection =
+        static_cast<s_UpdaterRRsetCollection*>(
+            PyObject_New(s_RRsetCollection, &updater_rrset_collection_type));
+    collection->cppobj = &self->cppobj->getRRsetCollection();
+    collection->base_obj_ = po_self;;
+    Py_INCREF(collection->base_obj_);
+
+    return (collection);
+}
+
 // This list contains the actual set of functions we have in
 // This list contains the actual set of functions we have in
 // python. Each entry has
 // python. Each entry has
 // 1. Python method name
 // 1. Python method name
@@ -207,6 +309,8 @@ PyMethodDef ZoneUpdater_methods[] = {
     { "delete_rrset", ZoneUpdater_deleteRRset,
     { "delete_rrset", ZoneUpdater_deleteRRset,
       METH_VARARGS, ZoneUpdater_deleteRRset_doc },
       METH_VARARGS, ZoneUpdater_deleteRRset_doc },
     { "commit", ZoneUpdater_commit, METH_NOARGS, ZoneUpdater_commit_doc },
     { "commit", ZoneUpdater_commit, METH_NOARGS, ZoneUpdater_commit_doc },
+    { "get_rrset_collection", ZoneUpdater_getRRsetCollection,
+      METH_NOARGS, ZoneUpdater_getRRsetCollection_doc },
     // Instead of a getFinder, we implement the finder functionality directly
     // Instead of a getFinder, we implement the finder functionality directly
     // This is because ZoneFinder is non-copyable, and we should not create
     // This is because ZoneFinder is non-copyable, and we should not create
     // a ZoneFinder object from a reference only (which is what is returned
     // a ZoneFinder object from a reference only (which is what is returned
@@ -292,6 +396,56 @@ createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
     return (py_zu);
     return (py_zu);
 }
 }
 
 
+bool
+initModulePart_ZoneUpdater(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&zoneupdater_type) < 0) {
+        return (false);
+    }
+    void* zip = &zoneupdater_type;
+    if (PyModule_AddObject(mod, "ZoneUpdater",
+                           static_cast<PyObject*>(zip)) < 0)
+    {
+        return (false);
+    }
+    Py_INCREF(&zoneupdater_type);
+
+    // get_rrset_collection() needs the base class type information.  Since
+    // it's defined in a separate loadable module, we retrieve its C object
+    // via the Python interpreter.  Directly referring to
+    // isc::dns::python::rrset_collection_base_type might work depending on
+    // the runtime environment (and in fact it does for some), but that would
+    // be less portable.
+    try {
+        if (updater_rrset_collection_type.tp_base == NULL) {
+            PyObjectContainer dns_module(PyImport_ImportModule("isc.dns"));
+            PyObjectContainer dns_dict(PyModule_GetDict(dns_module.get()));
+            // GetDict doesn't acquire a reference, so we need to get it to
+            // meet the container's requirement.
+            Py_INCREF(dns_dict.get());
+            PyObjectContainer base(
+                PyDict_GetItemString(dns_dict.get(), "RRsetCollectionBase"));
+            PyTypeObject* pt_rrset_collection_base =
+                static_cast<PyTypeObject*>(static_cast<void*>(base.get()));
+            updater_rrset_collection_type.tp_base = pt_rrset_collection_base;
+            if (PyType_Ready(&updater_rrset_collection_type) < 0) {
+                isc_throw(Unexpected, "failed to import isc.dns module");
+            }
+
+            // Make sure the base type won't suddenly disappear.  Note that we
+            // are effectively leaking it; it's intentional.
+            Py_INCREF(base.get());
+        }
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in ZoneUpdater initialization");
+        return (false);
+    }
+
+    return (true);
+}
 } // namespace python
 } // namespace python
 } // namespace datasrc
 } // namespace datasrc
 } // namespace isc
 } // namespace isc

+ 1 - 1
src/lib/python/isc/datasrc/updater_python.h

@@ -36,7 +36,7 @@ extern PyTypeObject zoneupdater_type;
 PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
 PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
                                   PyObject* base_obj = NULL);
                                   PyObject* base_obj = NULL);
 
 
-
+bool initModulePart_ZoneUpdater(PyObject* mod);
 } // namespace python
 } // namespace python
 } // namespace datasrc
 } // namespace datasrc
 } // namespace isc
 } // namespace isc

+ 2 - 2
tests/system/ifconfig.sh

@@ -63,7 +63,7 @@ esac
 case "$1" in
 case "$1" in
 
 
     start|up)
     start|up)
-	for ns in 1 2 3 4 5 6 7
+	for ns in 1 2 3 4 5 6 7 8
 	do
 	do
 		if test -n "$base"
 		if test -n "$base"
 		then
 		then
@@ -145,7 +145,7 @@ case "$1" in
 	;;
 	;;
 
 
     stop|down)
     stop|down)
-	for ns in 7 6 5 4 3 2 1
+	for ns in 8 7 6 5 4 3 2 1
 	do
 	do
 		if test -n "$base"
 		if test -n "$base"
 		then
 		then