Parcourir la 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 il y a 12 ans
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
 # 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)
 
 # 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
 
-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
 
 # 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
 
-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
 
 # 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
 
-# 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
 
 # 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 {
 int
 RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
@@ -70,8 +83,13 @@ RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
 void
 RRsetCollectionBase_destroy(PyObject* 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);
 }
 
@@ -79,6 +97,9 @@ PyObject*
 RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
     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) {
         PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
                      "derived RRsetCollection class");
@@ -108,6 +129,9 @@ RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
             }
             Py_RETURN_NONE;
         }
+    } catch (const RRsetCollectionError& ex) {
+        PyErr_SetString(po_RRsetCollectionError, ex.what());
+        return (NULL);
     } catch (const std::exception& ex) {
         const string ex_what = "Unexpected failure in "
             "RRsetCollectionBase.find: " + string(ex.what());
@@ -137,6 +161,9 @@ PyMethodDef RRsetCollectionBase_methods[] = {
 namespace isc {
 namespace dns {
 namespace python {
+// Definition of class specific exception(s)
+PyObject* po_RRsetCollectionError;
+
 // This defines the complete type for reflection in python and
 // parsing of PyObject* to s_RRsetCollection
 // 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
 bool
 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);
     }
-    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);
     }
-    Py_INCREF(&rrset_collection_base_type);
 
     return (true);
 }
@@ -405,18 +441,9 @@ PyTypeObject rrset_collection_type = {
 // Module Initialization, all statics are initialized here
 bool
 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);
     }
-    Py_INCREF(&rrset_collection_type);
 
     return (true);
 }

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

@@ -23,10 +23,13 @@ class RRsetCollectionBase;
 
 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
 // 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 {
 public:
     s_RRsetCollection() : cppobj(NULL) {}
@@ -39,6 +42,9 @@ extern PyTypeObject rrset_collection_base_type;
 // Python type information for dns.RRsetCollection
 extern PyTypeObject rrset_collection_type;
 
+// Class specific exceptions
+extern PyObject* po_RRsetCollectionError;
+
 bool initModulePart_RRsetCollectionBase(PyObject* mod);
 bool initModulePart_RRsetCollection(PyObject* mod);
 

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

@@ -1,7 +1,7 @@
 namespace {
 // Modifications
 //   - libdns++ => isc.dns, libdatasrc => isc.datasrc
-//   - note about the constructor.
+//   - note about the direct construction.
 //   - add note about iteration
 const char* const RRsetCollectionBase_doc = "\
 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\
 such as isc.datasrc will have another implementation.\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\
 ";
 
@@ -79,18 +79,18 @@ RRsetCollection(filename, origin, rrclass)\n\
       origin     (isc.dns.Name) The zone origin.\n\
       rrclass    (isc.dns.RRClass) The zone class.\n\
 \n\
-RRsetCollection(input_stream, origin, rrclass)\n\
+RRsetCollection(input, origin, rrclass)\n\
 \n\
     Constructor.\n\
 \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\
     The constructor throws IscException if there is an error\n\
     during loading.\n\
 \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\
       rrclass    (isc.dns.RRClass) The zone class.\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 isc
 

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

@@ -218,23 +218,6 @@ initModulePart_ZoneLoader(PyObject* mod) {
 }
 
 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) {
     if (PyType_Ready(&journal_reader_type) < 0) {
         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",
                          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):
         # load two modules, and check if they don't interfere
         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\
 exception.\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\
 gain experiences:\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\
 exception.\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\
 gain experiences:\n\
 \n\
@@ -178,4 +186,32 @@ Exceptions:\n\
              error, or wrapper error\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

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

@@ -32,6 +32,7 @@
 #include <dns/python/rrset_python.h>
 #include <dns/python/rrclass_python.h>
 #include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_collection_python.h>
 
 #include "datasrc.h"
 #include "updater_python.h"
@@ -195,6 +196,107 @@ ZoneUpdater_find_all(PyObject* po_self, PyObject* 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
 // python. Each entry has
 // 1. Python method name
@@ -207,6 +309,8 @@ PyMethodDef ZoneUpdater_methods[] = {
     { "delete_rrset", ZoneUpdater_deleteRRset,
       METH_VARARGS, ZoneUpdater_deleteRRset_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
     // This is because ZoneFinder is non-copyable, and we should not create
     // a ZoneFinder object from a reference only (which is what is returned
@@ -292,6 +396,56 @@ createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
     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 datasrc
 } // 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* base_obj = NULL);
 
-
+bool initModulePart_ZoneUpdater(PyObject* mod);
 } // namespace python
 } // namespace datasrc
 } // namespace isc

+ 2 - 2
tests/system/ifconfig.sh

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