Browse Source

[2438] added python wrapper for ZoneUpdater::getRRsetCollection().

JINMEI Tatuya 12 years ago
parent
commit
738540ce3a

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

@@ -199,23 +199,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