Browse Source

[master]Merge branch 'master' of ssh://git.bind10.isc.org/var/bind10/git/bind10

Jeremy C. Reed 12 years ago
parent
commit
214e040921

+ 193 - 192
src/lib/dns/python/pydnspp.cc

@@ -65,21 +65,13 @@ namespace {
 
 bool
 initModulePart_EDNS(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)
-    //
     // After the type has been initialized, we initialize any exceptions
     // that are defined in the wrapper for this class, and add constants
     // to the type, if any
 
-    if (PyType_Ready(&edns_type) < 0) {
+    if (!initClass(edns_type, "EDNS", mod)) {
         return (false);
     }
-    Py_INCREF(&edns_type);
-    void* p = &edns_type;
-    PyModule_AddObject(mod, "EDNS", static_cast<PyObject*>(p));
-
     addClassVariable(edns_type, "SUPPORTED_VERSION",
                      Py_BuildValue("B", EDNS::SUPPORTED_VERSION));
 
@@ -88,14 +80,9 @@ initModulePart_EDNS(PyObject* mod) {
 
 bool
 initModulePart_Message(PyObject* mod) {
-    if (PyType_Ready(&message_type) < 0) {
-        return (false);
-    }
-    void* p = &message_type;
-    if (PyModule_AddObject(mod, "Message", static_cast<PyObject*>(p)) < 0) {
+    if (!initClass(message_type, "Message", mod)) {
         return (false);
     }
-    Py_INCREF(&message_type);
 
     try {
         //
@@ -186,32 +173,25 @@ initModulePart_Message(PyObject* mod) {
 
 bool
 initModulePart_MessageRenderer(PyObject* mod) {
-    if (PyType_Ready(&messagerenderer_type) < 0) {
+    if (!initClass(messagerenderer_type, "MessageRenderer", mod)) {
         return (false);
     }
-    Py_INCREF(&messagerenderer_type);
 
     addClassVariable(messagerenderer_type, "CASE_INSENSITIVE",
                      Py_BuildValue("I", MessageRenderer::CASE_INSENSITIVE));
     addClassVariable(messagerenderer_type, "CASE_SENSITIVE",
                      Py_BuildValue("I", MessageRenderer::CASE_SENSITIVE));
 
-    PyModule_AddObject(mod, "MessageRenderer",
-                       reinterpret_cast<PyObject*>(&messagerenderer_type));
 
     return (true);
 }
 
 bool
-initModulePart_Name(PyObject* mod) {
-
-    //
-    // NameComparisonResult
-    //
-    if (PyType_Ready(&name_comparison_result_type) < 0) {
+initModulePart_NameComparisonResult(PyObject* mod) {
+    if (!initClass(name_comparison_result_type,
+                   "NameComparisonResult", mod)) {
         return (false);
     }
-    Py_INCREF(&name_comparison_result_type);
 
     // Add the enums to the module
     po_NameRelation = Py_BuildValue("{i:s,i:s,i:s,i:s}",
@@ -231,17 +211,14 @@ initModulePart_Name(PyObject* mod) {
     addClassVariable(name_comparison_result_type, "COMMONANCESTOR",
                      Py_BuildValue("I", NameComparisonResult::COMMONANCESTOR));
 
-    PyModule_AddObject(mod, "NameComparisonResult",
-        reinterpret_cast<PyObject*>(&name_comparison_result_type));
-
-    //
-    // Name
-    //
+    return (true);
+}
 
-    if (PyType_Ready(&name_type) < 0) {
+bool
+initModulePart_Name(PyObject* mod) {
+    if (!initClass(name_type, "Name", mod)) {
         return (false);
     }
-    Py_INCREF(&name_type);
 
     // Add the constants to the module
     addClassVariable(name_type, "MAX_WIRE",
@@ -260,51 +237,56 @@ initModulePart_Name(PyObject* mod) {
     addClassVariable(name_type, "ROOT_NAME",
                      createNameObject(Name::ROOT_NAME()));
 
-    PyModule_AddObject(mod, "Name",
-                       reinterpret_cast<PyObject*>(&name_type));
-
-
     // Add the exceptions to the module
-    po_EmptyLabel = PyErr_NewException("pydnspp.EmptyLabel", NULL, NULL);
-    PyModule_AddObject(mod, "EmptyLabel", po_EmptyLabel);
+    try {
+        po_EmptyLabel = PyErr_NewException("pydnspp.EmptyLabel", NULL, NULL);
+        PyObjectContainer(po_EmptyLabel).installToModule(mod, "EmptyLabel");
 
-    po_TooLongName = PyErr_NewException("pydnspp.TooLongName", NULL, NULL);
-    PyModule_AddObject(mod, "TooLongName", po_TooLongName);
+        po_TooLongName = PyErr_NewException("pydnspp.TooLongName", NULL, NULL);
+        PyObjectContainer(po_TooLongName).installToModule(mod, "TooLongName");
 
-    po_TooLongLabel = PyErr_NewException("pydnspp.TooLongLabel", NULL, NULL);
-    PyModule_AddObject(mod, "TooLongLabel", po_TooLongLabel);
+        po_TooLongLabel = PyErr_NewException("pydnspp.TooLongLabel", NULL, NULL);
+        PyObjectContainer(po_TooLongLabel).installToModule(mod, "TooLongLabel");
 
-    po_BadLabelType = PyErr_NewException("pydnspp.BadLabelType", NULL, NULL);
-    PyModule_AddObject(mod, "BadLabelType", po_BadLabelType);
+        po_BadLabelType = PyErr_NewException("pydnspp.BadLabelType", NULL, NULL);
+        PyObjectContainer(po_BadLabelType).installToModule(mod, "BadLabelType");
 
-    po_BadEscape = PyErr_NewException("pydnspp.BadEscape", NULL, NULL);
-    PyModule_AddObject(mod, "BadEscape", po_BadEscape);
+        po_BadEscape = PyErr_NewException("pydnspp.BadEscape", NULL, NULL);
+        PyObjectContainer(po_BadEscape).installToModule(mod, "BadEscape");
 
-    po_IncompleteName = PyErr_NewException("pydnspp.IncompleteName", NULL, NULL);
-    PyModule_AddObject(mod, "IncompleteName", po_IncompleteName);
+        po_IncompleteName = PyErr_NewException("pydnspp.IncompleteName", NULL,
+                                               NULL);
+        PyObjectContainer(po_IncompleteName).installToModule(mod, "IncompleteName");
 
-    po_InvalidBufferPosition =
-        PyErr_NewException("pydnspp.InvalidBufferPosition", NULL, NULL);
-    PyModule_AddObject(mod, "InvalidBufferPosition", po_InvalidBufferPosition);
+        po_InvalidBufferPosition =
+            PyErr_NewException("pydnspp.InvalidBufferPosition", NULL, NULL);
+        PyObjectContainer(po_InvalidBufferPosition).installToModule(
+            mod, "InvalidBufferPosition");
 
-    // This one could have gone into the message_python.cc file, but is
-    // already needed here.
-    po_DNSMessageFORMERR = PyErr_NewException("pydnspp.DNSMessageFORMERR",
-                                              NULL, NULL);
-    PyModule_AddObject(mod, "DNSMessageFORMERR", po_DNSMessageFORMERR);
+        // This one could have gone into the message_python.cc file, but is
+        // already needed here.
+        po_DNSMessageFORMERR = PyErr_NewException("pydnspp.DNSMessageFORMERR",
+                                                  NULL, NULL);
+        PyObjectContainer(po_DNSMessageFORMERR).installToModule(
+            mod, "DNSMessageFORMERR");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in Name initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in Name initialization");
+        return (false);
+    }
 
     return (true);
 }
 
 bool
 initModulePart_Opcode(PyObject* mod) {
-    if (PyType_Ready(&opcode_type) < 0) {
-        return (false);
-    }
-    Py_INCREF(&opcode_type);
-    void* p = &opcode_type;
-    if (PyModule_AddObject(mod, "Opcode", static_cast<PyObject*>(p)) != 0) {
-        Py_DECREF(&opcode_type);
+    if (!initClass(opcode_type, "Opcode", mod)) {
         return (false);
     }
 
@@ -346,25 +328,12 @@ initModulePart_Opcode(PyObject* mod) {
 
 bool
 initModulePart_Question(PyObject* mod) {
-    if (PyType_Ready(&question_type) < 0) {
-        return (false);
-    }
-    Py_INCREF(&question_type);
-    PyModule_AddObject(mod, "Question",
-                       reinterpret_cast<PyObject*>(&question_type));
-
-    return (true);
+    return (initClass(question_type, "Question", mod));
 }
 
 bool
 initModulePart_Rcode(PyObject* mod) {
-    if (PyType_Ready(&rcode_type) < 0) {
-        return (false);
-    }
-    Py_INCREF(&rcode_type);
-    void* p = &rcode_type;
-    if (PyModule_AddObject(mod, "Rcode", static_cast<PyObject*>(p)) != 0) {
-        Py_DECREF(&rcode_type);
+    if (!initClass(rcode_type, "Rcode", mod)) {
         return (false);
     }
 
@@ -408,126 +377,168 @@ initModulePart_Rcode(PyObject* mod) {
 
 bool
 initModulePart_Rdata(PyObject* mod) {
-    if (PyType_Ready(&rdata_type) < 0) {
+    if (!initClass(rdata_type, "Rdata", mod)) {
         return (false);
     }
-    Py_INCREF(&rdata_type);
-    PyModule_AddObject(mod, "Rdata",
-                       reinterpret_cast<PyObject*>(&rdata_type));
 
     // Add the exceptions to the class
-    po_InvalidRdataLength = PyErr_NewException("pydnspp.InvalidRdataLength",
-                                               NULL, NULL);
-    PyModule_AddObject(mod, "InvalidRdataLength", po_InvalidRdataLength);
-
-    po_InvalidRdataText = PyErr_NewException("pydnspp.InvalidRdataText",
-                                             NULL, NULL);
-    PyModule_AddObject(mod, "InvalidRdataText", po_InvalidRdataText);
-
-    po_CharStringTooLong = PyErr_NewException("pydnspp.CharStringTooLong",
-                                              NULL, NULL);
-    PyModule_AddObject(mod, "CharStringTooLong", po_CharStringTooLong);
-
+    try {
+        po_InvalidRdataLength =
+            PyErr_NewException("pydnspp.InvalidRdataLength", NULL, NULL);
+        PyObjectContainer(po_InvalidRdataLength).installToModule(
+            mod, "InvalidRdataLength");
+
+        po_InvalidRdataText = PyErr_NewException("pydnspp.InvalidRdataText",
+                                                 NULL, NULL);
+        PyObjectContainer(po_InvalidRdataText).installToModule(
+            mod, "InvalidRdataText");
+
+        po_CharStringTooLong = PyErr_NewException("pydnspp.CharStringTooLong",
+                                                  NULL, NULL);
+        PyObjectContainer(po_CharStringTooLong).installToModule(
+            mod, "CharStringTooLong");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in Rdata initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in Rdata initialization");
+        return (false);
+    }
 
     return (true);
 }
 
 bool
 initModulePart_RRClass(PyObject* mod) {
-    po_InvalidRRClass = PyErr_NewException("pydnspp.InvalidRRClass",
-                                           NULL, NULL);
-    Py_INCREF(po_InvalidRRClass);
-    PyModule_AddObject(mod, "InvalidRRClass", po_InvalidRRClass);
-    po_IncompleteRRClass = PyErr_NewException("pydnspp.IncompleteRRClass",
-                                              NULL, NULL);
-    Py_INCREF(po_IncompleteRRClass);
-    PyModule_AddObject(mod, "IncompleteRRClass", po_IncompleteRRClass);
+    if (!initClass(rrclass_type, "RRClass", mod)) {
+        return (false);
+    }
 
-    if (PyType_Ready(&rrclass_type) < 0) {
+    try {
+        po_InvalidRRClass = PyErr_NewException("pydnspp.InvalidRRClass",
+                                               NULL, NULL);
+        PyObjectContainer(po_InvalidRRClass).installToModule(
+            mod, "InvalidRRClass");
+
+        po_IncompleteRRClass = PyErr_NewException("pydnspp.IncompleteRRClass",
+                                                  NULL, NULL);
+        PyObjectContainer(po_IncompleteRRClass).installToModule(
+            mod, "IncompleteRRClass");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in RRClass initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in RRClass initialization");
         return (false);
     }
-    Py_INCREF(&rrclass_type);
-    PyModule_AddObject(mod, "RRClass",
-                       reinterpret_cast<PyObject*>(&rrclass_type));
 
     return (true);
 }
 
 bool
 initModulePart_RRset(PyObject* mod) {
-    po_EmptyRRset = PyErr_NewException("pydnspp.EmptyRRset", NULL, NULL);
-    PyModule_AddObject(mod, "EmptyRRset", po_EmptyRRset);
+    if (!initClass(rrset_type, "RRset", mod)) {
+        return (false);
+    }
 
-    // NameComparisonResult
-    if (PyType_Ready(&rrset_type) < 0) {
+    try {
+        po_EmptyRRset = PyErr_NewException("pydnspp.EmptyRRset", NULL, NULL);
+        PyObjectContainer(po_EmptyRRset).installToModule(mod, "EmptyRRset");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in RRset initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in RRset initialization");
         return (false);
     }
-    Py_INCREF(&rrset_type);
-    PyModule_AddObject(mod, "RRset",
-                       reinterpret_cast<PyObject*>(&rrset_type));
 
     return (true);
 }
 
 bool
 initModulePart_RRTTL(PyObject* mod) {
-    po_InvalidRRTTL = PyErr_NewException("pydnspp.InvalidRRTTL", NULL, NULL);
-    PyModule_AddObject(mod, "InvalidRRTTL", po_InvalidRRTTL);
-    po_IncompleteRRTTL = PyErr_NewException("pydnspp.IncompleteRRTTL",
-                                            NULL, NULL);
-    PyModule_AddObject(mod, "IncompleteRRTTL", po_IncompleteRRTTL);
+    if (!initClass(rrttl_type, "RRTTL", mod)) {
+        return (false);
+    }
+
+    try {
+        po_InvalidRRTTL = PyErr_NewException("pydnspp.InvalidRRTTL",
+                                             NULL, NULL);
+        PyObjectContainer(po_InvalidRRTTL).installToModule(mod,
+                                                           "InvalidRRTTL");
 
-    if (PyType_Ready(&rrttl_type) < 0) {
+        po_IncompleteRRTTL = PyErr_NewException("pydnspp.IncompleteRRTTL",
+                                                NULL, NULL);
+        PyObjectContainer(po_IncompleteRRTTL).installToModule(
+            mod, "IncompleteRRTTL");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in RRTTL initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in RRTTL initialization");
         return (false);
     }
-    Py_INCREF(&rrttl_type);
-    PyModule_AddObject(mod, "RRTTL",
-                       reinterpret_cast<PyObject*>(&rrttl_type));
 
     return (true);
 }
 
 bool
 initModulePart_RRType(PyObject* mod) {
-    // Add the exceptions to the module
-    po_InvalidRRType = PyErr_NewException("pydnspp.InvalidRRType", NULL, NULL);
-    PyModule_AddObject(mod, "InvalidRRType", po_InvalidRRType);
-    po_IncompleteRRType = PyErr_NewException("pydnspp.IncompleteRRType",
-                                             NULL, NULL);
-    PyModule_AddObject(mod, "IncompleteRRType", po_IncompleteRRType);
+    if (!initClass(rrtype_type, "RRType", mod)) {
+        return (false);
+    }
+
+    try {
+        po_InvalidRRType = PyErr_NewException("pydnspp.InvalidRRType",
+                                              NULL, NULL);
+        PyObjectContainer(po_InvalidRRType).installToModule(mod,
+                                                            "InvalidRRType");
 
-    if (PyType_Ready(&rrtype_type) < 0) {
+        po_IncompleteRRType = PyErr_NewException("pydnspp.IncompleteRRType",
+                                                 NULL, NULL);
+        PyObjectContainer(po_IncompleteRRType).installToModule(
+            mod, "IncompleteRRType");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in RRType initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in RRType initialization");
         return (false);
     }
-    Py_INCREF(&rrtype_type);
-    PyModule_AddObject(mod, "RRType",
-                       reinterpret_cast<PyObject*>(&rrtype_type));
 
     return (true);
 }
 
 bool
 initModulePart_Serial(PyObject* mod) {
-    if (PyType_Ready(&serial_type) < 0) {
-        return (false);
-    }
-    Py_INCREF(&serial_type);
-    PyModule_AddObject(mod, "Serial",
-                       reinterpret_cast<PyObject*>(&serial_type));
-
-    return (true);
+    return (initClass(serial_type, "Serial", mod));
 }
 
 bool
 initModulePart_TSIGError(PyObject* mod) {
-    if (PyType_Ready(&tsigerror_type) < 0) {
-        return (false);
-    }
-    void* p = &tsigerror_type;
-    if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
+    if (!initClass(tsigerror_type, "TSIGError", mod)) {
         return (false);
     }
-    Py_INCREF(&tsigerror_type);
 
     try {
         // Constant class variables
@@ -595,14 +606,9 @@ initModulePart_TSIGError(PyObject* mod) {
 
 bool
 initModulePart_TSIGKey(PyObject* mod) {
-    if (PyType_Ready(&tsigkey_type) < 0) {
-        return (false);
-    }
-    void* p = &tsigkey_type;
-    if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
+    if (!initClass(tsigkey_type, "TSIGKey", mod)) {
         return (false);
     }
-    Py_INCREF(&tsigkey_type);
 
     try {
         // Constant class variables
@@ -635,14 +641,7 @@ initModulePart_TSIGKey(PyObject* mod) {
 
 bool
 initModulePart_TSIGKeyRing(PyObject* mod) {
-    if (PyType_Ready(&tsigkeyring_type) < 0) {
-        return (false);
-    }
-    Py_INCREF(&tsigkeyring_type);
-    void* p = &tsigkeyring_type;
-    if (PyModule_AddObject(mod, "TSIGKeyRing",
-                           static_cast<PyObject*>(p)) != 0) {
-        Py_DECREF(&tsigkeyring_type);
+    if (!initClass(tsigkeyring_type, "TSIGKeyRing", mod)) {
         return (false);
     }
 
@@ -658,15 +657,9 @@ initModulePart_TSIGKeyRing(PyObject* mod) {
 
 bool
 initModulePart_TSIGContext(PyObject* mod) {
-    if (PyType_Ready(&tsigcontext_type) < 0) {
-        return (false);
-    }
-    void* p = &tsigcontext_type;
-    if (PyModule_AddObject(mod, "TSIGContext",
-                           static_cast<PyObject*>(p)) < 0) {
+    if (!initClass(tsigcontext_type, "TSIGContext", mod)) {
         return (false);
     }
-    Py_INCREF(&tsigcontext_type);
 
     try {
         // Class specific exceptions
@@ -707,28 +700,14 @@ initModulePart_TSIGContext(PyObject* mod) {
 
 bool
 initModulePart_TSIG(PyObject* mod) {
-    if (PyType_Ready(&tsig_type) < 0) {
-        return (false);
-    }
-    void* p = &tsig_type;
-    if (PyModule_AddObject(mod, "TSIG", static_cast<PyObject*>(p)) < 0) {
-        return (false);
-    }
-    Py_INCREF(&tsig_type);
-
-    return (true);
+    return (initClass(tsig_type, "TSIG", mod));
 }
 
 bool
 initModulePart_TSIGRecord(PyObject* mod) {
-    if (PyType_Ready(&tsigrecord_type) < 0) {
-        return (false);
-    }
-    void* p = &tsigrecord_type;
-    if (PyModule_AddObject(mod, "TSIGRecord", static_cast<PyObject*>(p)) < 0) {
+    if (!initClass(tsigrecord_type, "TSIGRecord", mod)) {
         return (false);
     }
-    Py_INCREF(&tsigrecord_type);
 
     try {
         // Constant class variables
@@ -773,16 +752,38 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
-    // Add the exceptions to the class
-    po_IscException = PyErr_NewException("pydnspp.IscException", NULL, NULL);
-    PyModule_AddObject(mod, "IscException", po_IscException);
-
-    po_InvalidParameter = PyErr_NewException("pydnspp.InvalidParameter",
-                                             NULL, NULL);
-    PyModule_AddObject(mod, "InvalidParameter", po_InvalidParameter);
+    try {
+        // Add the exceptions to the class
+        po_IscException = PyErr_NewException("pydnspp.IscException", NULL, NULL);
+        PyObjectContainer(po_IscException).installToModule(mod, "IscException");
+
+        po_InvalidOperation = PyErr_NewException("pydnspp.InvalidOperation",
+                                                 NULL, NULL);
+        PyObjectContainer(po_InvalidOperation).installToModule(
+            mod, "InvalidOperation");
+
+        po_InvalidParameter = PyErr_NewException("pydnspp.InvalidParameter",
+                                                 NULL, NULL);
+        PyObjectContainer(po_InvalidParameter).installToModule(
+            mod, "InvalidParameter");
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in pydnspp initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in pydnspp initialization");
+        return (NULL);
+    }
 
     // for each part included above, we call its specific initializer
 
+    if (!initModulePart_NameComparisonResult(mod)) {
+        return (NULL);
+    }
+
     if (!initModulePart_Name(mod)) {
         return (NULL);
     }

+ 17 - 0
src/lib/dns/python/pydnspp_common.cc

@@ -47,6 +47,7 @@ namespace dns {
 namespace python {
 // For our 'general' isc::Exceptions
 PyObject* po_IscException;
+PyObject* po_InvalidOperation;
 PyObject* po_InvalidParameter;
 
 // For our own isc::dns::Exception
@@ -91,6 +92,22 @@ addClassVariable(PyTypeObject& c, const char* name, PyObject* obj) {
     }
     return (PyDict_SetItemString(c.tp_dict, name, obj));
 }
+
+bool
+initClass(PyTypeObject& type, const char* name, 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)
+    //
+    void* p = &type;
+    if (PyType_Ready(&type) < 0 ||
+        PyModule_AddObject(mod, name, static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&type);
+    return (true);
+}
+
 }
 }
 }

+ 13 - 0
src/lib/dns/python/pydnspp_common.h

@@ -28,6 +28,7 @@ namespace dns {
 namespace python {
 // For our 'general' isc::Exceptions
 extern PyObject* po_IscException;
+extern PyObject* po_InvalidOperation;
 extern PyObject* po_InvalidParameter;
 
 // For our own isc::dns::Exception
@@ -47,6 +48,18 @@ int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
 
 int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
 
+/// \brief Initialize a wrapped class type, and add to module
+///
+/// The type object is initalized, and its refcount is increased after
+/// successful addition to the module.
+///
+/// \param type The type object to initialize
+/// \param name The python name of the class to add
+/// \param mod The python module to add the class to
+///
+/// \return true on success, false on failure
+bool initClass(PyTypeObject& type, const char* name, PyObject* mod);
+
 // Short term workaround for unifying the return type of tp_hash
 #if PY_MINOR_VERSION < 2
 typedef long Py_hash_t;

+ 2 - 0
src/lib/python/isc/datasrc/Makefile.am

@@ -20,6 +20,7 @@ datasrc_la_SOURCES += updater_python.cc updater_python.h
 datasrc_la_SOURCES += journal_reader_python.cc journal_reader_python.h
 datasrc_la_SOURCES += configurableclientlist_python.cc
 datasrc_la_SOURCES += configurableclientlist_python.h
+datasrc_la_SOURCES += zone_loader_python.cc zone_loader_python.h
 
 datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -35,6 +36,7 @@ EXTRA_DIST += finder_inc.cc
 EXTRA_DIST += iterator_inc.cc
 EXTRA_DIST += updater_inc.cc
 EXTRA_DIST += journal_reader_inc.cc
+EXTRA_DIST += zone_loader_inc.cc
 
 CLEANDIRS = __pycache__
 

+ 1 - 2
src/lib/python/isc/datasrc/__init__.py

@@ -2,7 +2,7 @@ import sys
 import os
 
 # The datasource factory loader uses dlopen, as does python
-# for its modules. Some dynamic linkers do not play nice if 
+# for its modules. Some dynamic linkers do not play nice if
 # modules are not loaded with RTLD_GLOBAL, a symptom of which
 # is that exceptions are not recognized by type. So to make
 # sure this doesn't happen, we temporarily set RTLD_GLOBAL
@@ -31,5 +31,4 @@ else:
 sys.setdlopenflags(flags)
 
 from isc.datasrc.sqlite3_ds import *
-from isc.datasrc.master import *
 

+ 10 - 0
src/lib/python/isc/datasrc/client_python.cc

@@ -407,6 +407,16 @@ wrapDataSourceClient(DataSourceClient* client,
     return (container.release());
 }
 
+DataSourceClient&
+PyDataSourceClient_ToDataSourceClient(PyObject* client_obj) {
+    if (client_obj == NULL) {
+        isc_throw(PyCPPWrapperException,
+                  "argument NULL in DataSourceClient PyObject conversion");
+    }
+    s_DataSourceClient* client = static_cast<s_DataSourceClient*>(client_obj);
+    return (*client->client);
+}
+
 } // namespace python
 } // namespace datasrc
 } // namespace isc

+ 11 - 0
src/lib/python/isc/datasrc/client_python.h

@@ -44,6 +44,17 @@ wrapDataSourceClient(DataSourceClient* client,
                      LifeKeeper>& life_keeper = boost::shared_ptr<ClientList::
                      FindResult::LifeKeeper>());
 
+/// \brief Returns a reference to the DataSourceClient object contained
+///        in the given Python object.
+///
+/// \note The given object MUST be of type DataSourceClient; this can be
+///       checked with the right call to ParseTuple("O!")
+///
+/// \param client_obj Python object holding the DataSourceClient
+/// \return reference to the DataSourceClient object
+DataSourceClient&
+PyDataSourceClient_ToDataSourceClient(PyObject* client_obj);
+
 } // namespace python
 } // namespace datasrc
 } // namespace isc

+ 41 - 12
src/lib/python/isc/datasrc/datasrc.cc

@@ -29,6 +29,7 @@
 #include "updater_python.h"
 #include "journal_reader_python.h"
 #include "configurableclientlist_python.h"
+#include "zone_loader_python.h"
 
 #include <util/python/pycppwrapper_util.h>
 #include <dns/python/pydnspp_common.h>
@@ -181,6 +182,23 @@ initModulePart_ZoneIterator(PyObject* mod) {
 }
 
 bool
+initModulePart_ZoneLoader(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(&zone_loader_type) < 0) {
+        return (false);
+    }
+    void* p = &zone_loader_type;
+    if (PyModule_AddObject(mod, "ZoneLoader", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&zone_loader_type);
+
+    return (true);
+}
+
+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
@@ -234,8 +252,9 @@ initModulePart_ZoneJournalReader(PyObject* mod) {
 }
 
 PyObject* po_DataSourceError;
-PyObject* po_OutOfZone;
+PyObject* po_MasterFileError;
 PyObject* po_NotImplemented;
+PyObject* po_OutOfZone;
 
 PyModuleDef iscDataSrc = {
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
@@ -260,6 +279,26 @@ PyInit_datasrc(void) {
         return (NULL);
     }
 
+    try {
+        po_DataSourceError = PyErr_NewException("isc.datasrc.Error", NULL,
+                                                NULL);
+        PyObjectContainer(po_DataSourceError).installToModule(mod, "Error");
+        po_MasterFileError = PyErr_NewException("isc.datasrc.MasterFileError",
+                                                po_DataSourceError, NULL);
+        PyObjectContainer(po_MasterFileError).
+            installToModule(mod, "MasterFileError");
+        po_OutOfZone = PyErr_NewException("isc.datasrc.OutOfZone", NULL, NULL);
+        PyObjectContainer(po_OutOfZone).installToModule(mod, "OutOfZone");
+        po_NotImplemented = PyErr_NewException("isc.datasrc.NotImplemented",
+                                               NULL, NULL);
+        PyObjectContainer(po_NotImplemented).installToModule(mod,
+                                                             "NotImplemented");
+
+    } catch (...) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
     if (!initModulePart_DataSourceClient(mod)) {
         Py_DECREF(mod);
         return (NULL);
@@ -290,17 +329,7 @@ PyInit_datasrc(void) {
         return (NULL);
     }
 
-    try {
-        po_DataSourceError = PyErr_NewException("isc.datasrc.Error", NULL,
-                                                NULL);
-        PyObjectContainer(po_DataSourceError).installToModule(mod, "Error");
-        po_OutOfZone = PyErr_NewException("isc.datasrc.OutOfZone", NULL, NULL);
-        PyObjectContainer(po_OutOfZone).installToModule(mod, "OutOfZone");
-        po_NotImplemented = PyErr_NewException("isc.datasrc.NotImplemented",
-                                               NULL, NULL);
-        PyObjectContainer(po_NotImplemented).installToModule(mod,
-                                                             "NotImplemented");
-    } catch (...) {
+    if (!initModulePart_ZoneLoader(mod)) {
         Py_DECREF(mod);
         return (NULL);
     }

+ 1 - 1
src/lib/python/isc/datasrc/master.py

@@ -182,7 +182,7 @@ def records(input):
 
         if paren == 1 or not record:
             continue
-        
+
         ret = ' '.join(record)
         record = []
         oldsize = size

+ 6 - 1
src/lib/python/isc/datasrc/tests/Makefile.am

@@ -1,15 +1,20 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 # old tests, TODO remove or change to use new API?
 #PYTESTS = master_test.py
-PYTESTS =  datasrc_test.py sqlite3_ds_test.py clientlist_test.py
+PYTESTS =  datasrc_test.py sqlite3_ds_test.py
+PYTESTS += clientlist_test.py zone_loader_test.py
 EXTRA_DIST = $(PYTESTS)
 
 EXTRA_DIST += testdata/brokendb.sqlite3
 EXTRA_DIST += testdata/example.com.sqlite3
+EXTRA_DIST += testdata/example.com.source.sqlite3
 EXTRA_DIST += testdata/newschema.sqlite3
 EXTRA_DIST += testdata/oldschema.sqlite3
 EXTRA_DIST += testdata/new_minor_schema.sqlite3
+EXTRA_DIST += testdata/example.com
+EXTRA_DIST += testdata/example.com.ch
 CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
+CLEANFILES += $(abs_builddir)/zoneloadertest.sqlite3
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.

+ 8 - 0
src/lib/python/isc/datasrc/tests/testdata/example.com

@@ -0,0 +1,8 @@
+example.com.         1000  IN  SOA a.dns.example.com. mail.example.com. 1 1 1 1 1
+example.com.         1000  IN  NS  a.dns.example.com.
+example.com.         1000  IN  NS  b.dns.example.com.
+example.com.         1000  IN  NS  c.dns.example.com.
+a.dns.example.com.   1000  IN  A    1.1.1.1
+b.dns.example.com.   1000  IN  A    3.3.3.3
+b.dns.example.com.   1000  IN  AAAA 4:4::4:4
+b.dns.example.com.   1000  IN  AAAA 5:5::5:5

+ 8 - 0
src/lib/python/isc/datasrc/tests/testdata/example.com.ch

@@ -0,0 +1,8 @@
+example.com.         1000  CH  SOA a.dns.example.com. mail.example.com. 1 1 1 1 1
+example.com.         1000  CH  NS  a.dns.example.com.
+example.com.         1000  CH  NS  b.dns.example.com.
+example.com.         1000  CH  NS  c.dns.example.com.
+a.dns.example.com.   1000  CH  A    1.1.1.1
+b.dns.example.com.   1000  CH  A    3.3.3.3
+b.dns.example.com.   1000  CH  AAAA 4:4::4:4
+b.dns.example.com.   1000  CH  AAAA 5:5::5:5

BIN
src/lib/python/isc/datasrc/tests/testdata/example.com.source.sqlite3


BIN
src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3


+ 223 - 0
src/lib/python/isc/datasrc/tests/zone_loader_test.py

@@ -0,0 +1,223 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import isc.datasrc
+import isc.dns
+
+import os
+import unittest
+import shutil
+import sys
+
+# Constants and common data used in tests
+
+TESTDATA_PATH = os.environ['TESTDATA_PATH']
+TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH']
+
+ZONE_FILE = TESTDATA_PATH + '/example.com'
+STATIC_ZONE_FILE = '../../../../datasrc/static.zone'
+SOURCE_DB_FILE = TESTDATA_PATH + '/example.com.source.sqlite3'
+ORIG_DB_FILE = TESTDATA_PATH + '/example.com.sqlite3'
+DB_FILE = TESTDATA_WRITE_PATH + '/zoneloadertest.sqlite3'
+DB_CLIENT_CONFIG = '{ "database_file": "' + DB_FILE + '" }'
+DB_SOURCE_CLIENT_CONFIG = '{ "database_file": "' + SOURCE_DB_FILE + '" }'
+
+ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
+               'admin.example.com. 1234 3600 1800 2419200 7200\n'
+NEW_SOA_TXT = 'example.com. 1000 IN SOA a.dns.example.com. ' +\
+              'mail.example.com. 1 1 1 1 1\n'
+
+
+class ZoneLoaderTests(unittest.TestCase):
+    def setUp(self):
+        self.test_name = isc.dns.Name("example.com")
+        self.test_file = ZONE_FILE
+        self.client = isc.datasrc.DataSourceClient("sqlite3", DB_CLIENT_CONFIG)
+        # Make a fresh copy of the database
+        shutil.copyfile(ORIG_DB_FILE, DB_FILE)
+        # Some tests set source client; if so, check refcount in
+        # tearDown, since most tests don't, set it to None by default.
+        self.source_client = None
+        self.loader = None
+        self.assertEqual(2, sys.getrefcount(self.test_name))
+        self.assertEqual(2, sys.getrefcount(self.client))
+
+    def tearDown(self):
+        # We can only create 1 loader at a time (it locks the db), and it
+        # may not be destroyed immediately if there is an exception in a
+        # test. So the tests that do create one should put it in self, and
+        # we make sure to invalidate it here.
+
+        # We can also use this to check reference counts; if a loader
+        # exists, the client and source client (if any) should have
+        # an increased reference count (but the name should not, this
+        # is only used in the initializer)
+        if self.loader is not None:
+            self.assertEqual(2, sys.getrefcount(self.test_name))
+            self.assertEqual(3, sys.getrefcount(self.client))
+            if self.source_client is not None:
+                self.assertEqual(3, sys.getrefcount(self.source_client))
+        self.loader = None
+
+        # Now that the loader has been destroyed, the refcounts
+        # of its arguments should be back to their originals
+        self.assertEqual(2, sys.getrefcount(self.test_name))
+        self.assertEqual(2, sys.getrefcount(self.client))
+        if self.source_client is not None:
+            self.assertEqual(2, sys.getrefcount(self.source_client))
+
+    def test_bad_constructor(self):
+        self.assertRaises(TypeError, isc.datasrc.ZoneLoader)
+        self.assertRaises(TypeError, isc.datasrc.ZoneLoader, 1)
+        self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
+                          None, self.test_name, self.test_file)
+        self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
+                          self.client, None, self.test_file)
+        self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
+                          self.client, self.test_name, None)
+        self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
+                          self.client, self.test_name, self.test_file, 1)
+
+    def check_zone_soa(self, soa_txt):
+        """
+        Check that the given SOA RR exists and matches the expected string
+        """
+        result, finder = self.client.find_zone(self.test_name)
+        self.assertEqual(self.client.SUCCESS, result)
+        result, rrset, _ = finder.find(self.test_name, isc.dns.RRType.SOA())
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual(soa_txt, rrset.to_text())
+
+    def check_load(self):
+        self.check_zone_soa(ORIG_SOA_TXT)
+        self.loader.load()
+        self.check_zone_soa(NEW_SOA_TXT)
+
+        # And after that, it should throw
+        self.assertRaises(isc.dns.InvalidOperation, self.loader.load)
+
+    def test_load_from_file(self):
+        self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+                                             self.test_file)
+        self.check_load()
+
+    def test_load_from_client(self):
+        self.source_client = isc.datasrc.DataSourceClient('sqlite3',
+                                    DB_SOURCE_CLIENT_CONFIG)
+        self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+                                             self.source_client)
+        self.check_load()
+
+    def check_load_incremental(self):
+        # New zone has 8 RRs
+        # After 5, it should return False
+        self.assertFalse(self.loader.load_incremental(5))
+        # New zone should not have been loaded yet
+        self.check_zone_soa(ORIG_SOA_TXT)
+
+        # After 5 more, it should return True (only having read 3)
+        self.assertTrue(self.loader.load_incremental(5))
+        # New zone should now be loaded
+        self.check_zone_soa(NEW_SOA_TXT)
+
+        # And after that, it should throw
+        self.assertRaises(isc.dns.InvalidOperation,
+                          self.loader.load_incremental, 5)
+
+    def test_load_from_file_incremental(self):
+        # Create loader and load the zone
+        self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+                                             self.test_file)
+        self.check_load_incremental()
+
+    def test_load_from_client_incremental(self):
+        self.source_client = isc.datasrc.DataSourceClient('sqlite3',
+                                            DB_SOURCE_CLIENT_CONFIG)
+        self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+                                             self.source_client)
+        self.check_load_incremental()
+
+    def test_bad_file(self):
+        self.check_zone_soa(ORIG_SOA_TXT)
+        self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+                                             'no such file')
+        self.assertRaises(isc.datasrc.MasterFileError, self.loader.load)
+        self.check_zone_soa(ORIG_SOA_TXT)
+
+    def test_bad_file_incremental(self):
+        self.check_zone_soa(ORIG_SOA_TXT)
+        self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+                                             'no such file')
+        self.assertRaises(isc.datasrc.MasterFileError,
+                          self.loader.load_incremental, 1)
+        self.check_zone_soa(ORIG_SOA_TXT)
+
+    def test_no_such_zone_in_target(self):
+        self.assertRaises(isc.datasrc.Error, isc.datasrc.ZoneLoader,
+                          self.client, isc.dns.Name("unknownzone"),
+                          self.test_file)
+
+    def test_no_such_zone_in_source(self):
+        # Reuse a zone that exists in target but not in source
+        zone_name = isc.dns.Name("sql1.example.com")
+        self.source_client = isc.datasrc.DataSourceClient('sqlite3',
+                                            DB_SOURCE_CLIENT_CONFIG)
+
+        # make sure the zone exists in the target
+        found, _ = self.client.find_zone(zone_name)
+        self.assertEqual(self.client.SUCCESS, found)
+        # And that it does not in the source
+        found, _ = self.source_client.find_zone(zone_name)
+        self.assertNotEqual(self.source_client.SUCCESS, found)
+
+        self.assertRaises(isc.datasrc.Error, isc.datasrc.ZoneLoader,
+                          self.client, zone_name, self.source_client)
+
+    def test_no_ds_load_support(self):
+        # This may change in the future, but atm, the in-mem ds does
+        # not support the API the zone loader uses (it has direct load calls)
+        inmem_client = isc.datasrc.DataSourceClient('memory',
+                                                    '{ "type": "memory" }');
+        self.assertRaises(isc.datasrc.NotImplemented,
+                          isc.datasrc.ZoneLoader,
+                          inmem_client, self.test_name, self.test_file)
+
+    def test_wrong_class_from_file(self):
+        # If the file has wrong class, it is not detected until load time
+        self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+                                             self.test_file + '.ch')
+        self.assertRaises(isc.datasrc.MasterFileError, self.loader.load)
+
+    def test_wrong_class_from_client(self):
+        # For ds->ds loading, wrong class is detected upon construction
+        # Need a bit of the extended setup for CH source client
+        clientlist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.CH())
+        clientlist.configure('[ { "type": "static", "params": "' +
+                             STATIC_ZONE_FILE +'" } ]', False)
+        self.source_client, _, _ = clientlist.find(isc.dns.Name("bind."),
+                                                   False, False)
+        self.assertRaises(isc.dns.InvalidParameter, isc.datasrc.ZoneLoader,
+                          self.client, isc.dns.Name("bind."),
+                          self.source_client)
+
+    def test_exception(self):
+        # Just check if masterfileerror is subclass of datasrc.Error
+        self.assertTrue(issubclass(isc.datasrc.MasterFileError,
+                                   isc.datasrc.Error))
+
+if __name__ == "__main__":
+    isc.log.init("bind10")
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()

+ 116 - 0
src/lib/python/isc/datasrc/zone_loader_inc.cc

@@ -0,0 +1,116 @@
+namespace {
+const char* const ZoneLoader_doc = "\
+Class to load data into a data source client.\n\
+\n\
+This is a small wrapper class that is able to load data into a data\n\
+source. It can load either from another data source or from a master\n\
+file. The purpose of the class is only to hold the state for\n\
+incremental loading.\n\
+\n\
+The old content of zone is discarded and no journal is stored.\n\
+\n\
+ZoneLoader(destination, zone_name, master_file)\n\
+\n\
+    Constructor from master file.\n\
+\n\
+    This initializes the zone loader to load from a master file.\n\
+\n\
+    Exceptions:\n\
+      DataSourceError in case the zone does not exist in destination.\n\
+                 This class does not support creating brand new zones,\n\
+                 only loading data into them. In case a new zone is\n\
+                 needed, it must be created beforehand.\n\
+      DataSourceError in case of other (possibly low-level) errors,\n\
+                 such as read-only data source or database error.\n\
+\n\
+    Parameters:\n\
+      destination (isc.datasrc.DataSourceClient) The data source into\n\
+                  which the loaded data should go.\n\
+      zone_name   (isc.dns.Name) The origin of the zone. The class is\n\
+                  implicit in the destination.\n\
+      master_file (string) Path to the master file to read data from.\n\
+\n\
+ZoneLoader(destination, zone_name, source)\n\
+\n\
+    Constructor from another data source.\n\
+\n\
+    Parameters:\n\
+      destination (isc.datasrc.DataSourceClient) The data source into\n\
+                  which the loaded data should go.\n\
+      zone_name   (isc.dns.Name) The origin of the zone. The class is\n\
+                  implicit in the destination.\n\
+      source      (isc.datasrc.DataSourceClient) The data source from\n\
+                  which the data would be read.\n\
+\n\
+    Exceptions:\n\
+      InvalidParameter in case the class of destination and source\n\
+                 differs.\n\
+      NotImplemented in case the source data source client doesn't\n\
+                 provide an iterator.\n\
+      DataSourceError in case the zone does not exist in destination.\n\
+                 This class does not support creating brand new zones,\n\
+                 only loading data into them. In case a new zone is\n\
+                 needed, it must be created beforehand.\n\
+      DataSourceError in case the zone does not exist in the source.\n\
+      DataSourceError in case of other (possibly low-level) errors,\n\
+                 such as read-only data source or database error.\n\
+\n\
+    Parameters:\n\
+      destination The data source into which the loaded data should\n\
+                 go.\n\
+      zone_name  The origin of the zone.\n\
+      source     The data source from which the data would be read.\n\
+\n\
+";
+
+const char* const ZoneLoader_load_doc = "\
+load() -> None\n\
+\n\
+Perform the whole load.\n\
+\n\
+This performs the whole loading operation. It may take a long time.\n\
+\n\
+Exceptions:\n\
+  InvalidOperation in case the loading was already completed before\n\
+             this call.\n\
+  DataSourceError in case some error (possibly low-level) happens.\n\
+  MasterFileError when the master_file is badly formatted or some\n\
+             similar problem is found when loading the master file.\n\
+\n\
+";
+
+const char* const ZoneLoader_loadIncremental_doc = "\
+load_incremental(limit) -> bool\n\
+\n\
+Load up to limit RRs.\n\
+\n\
+This performs a part of the loading. In case there's enough data in\n\
+the source, it copies limit RRs. It can copy less RRs during the final\n\
+call (when there's less than limit left).\n\
+\n\
+This can be called repeatedly until the whole zone is loaded, having\n\
+pauses in the loading for some purposes (for example reporting\n\
+progress).\n\
+\n\
+Exceptions:\n\
+  InvalidOperation in case the loading was already completed before\n\
+             this call (by load() or by a load_incremental that\n\
+             returned true).\n\
+  DataSourceError in case some error (possibly low-level) happens.\n\
+  MasterFileError when the master_file is badly formatted or some\n\
+             similar problem is found when loading the master file.\n\
+\n\
+Parameters:\n\
+  limit      (integer) The maximum allowed number of RRs to be\n\
+             loaded during this call.\n\
+\n\
+Return Value(s): True in case the loading is completed, false if\n\
+there's more to load.\n\
+\n\
+Note that if the limit is exactly the number of RRs available to be\n\
+loaded, the method will still return False, and True will be returned\n\
+on the next call (which will load 0 RRs). This is because the end of\n\
+iterator or master file is detected when reading past the end, not\n\
+when the last one is read.\n\
+";
+} // unnamed namespace

+ 262 - 0
src/lib/python/isc/datasrc/zone_loader_python.cc

@@ -0,0 +1,262 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <datasrc/zone_loader.h>
+#include <dns/python/name_python.h>
+#include <dns/python/pydnspp_common.h>
+#include <exceptions/exceptions.h>
+
+#include "client_python.h"
+#include "datasrc.h"
+#include "zone_loader_inc.cc"
+
+using namespace std;
+using namespace isc::dns::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+using namespace isc::util::python;
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneLoader : public PyObject {
+public:
+    s_ZoneLoader() : cppobj(NULL), target_client(NULL), source_client(NULL)
+        {};
+    ZoneLoader* cppobj;
+    // a zoneloader should not survive its associated client(s),
+    // so add a ref to it at init
+    PyObject* target_client;
+    PyObject* source_client;
+};
+
+// General creation and destruction
+int
+ZoneLoader_init(PyObject* po_self, PyObject* args, PyObject*) {
+    s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+    PyObject *po_target_client = NULL;
+    PyObject *po_source_client = NULL;
+    PyObject *po_name = NULL;
+    char* master_file;
+    if (!PyArg_ParseTuple(args, "O!O!s", &datasourceclient_type,
+                          &po_target_client, &name_type, &po_name,
+                          &master_file) &&
+        !PyArg_ParseTuple(args, "O!O!O!", &datasourceclient_type,
+                          &po_target_client, &name_type, &po_name,
+                          &datasourceclient_type, &po_source_client)
+       ) {
+        PyErr_SetString(PyExc_TypeError,
+                        "Invalid arguments to ZoneLoader constructor, "
+                        "expects isc.datasrc.DataSourceClient, isc.dns.Name, "
+                        "and either a string or another DataSourceClient");
+        return (-1);
+    }
+    PyErr_Clear();
+    try {
+        // The associated objects must be alive during the lifetime
+        // of this instance, so incref them (through a container in case
+        // of exceptions in this method)
+        Py_INCREF(po_target_client);
+        PyObjectContainer target_client(po_target_client);
+        if (po_source_client != NULL) {
+            // See above
+            Py_INCREF(po_source_client);
+            PyObjectContainer source_client(po_source_client);
+            self->cppobj = new ZoneLoader(
+                PyDataSourceClient_ToDataSourceClient(po_target_client),
+                PyName_ToName(po_name),
+                PyDataSourceClient_ToDataSourceClient(po_source_client));
+            self->source_client = source_client.release();
+        } else {
+            self->cppobj = new ZoneLoader(
+                PyDataSourceClient_ToDataSourceClient(po_target_client),
+                PyName_ToName(po_name),
+                master_file);
+        }
+        self->target_client = target_client.release();
+        return (0);
+    } catch (const isc::InvalidParameter& ivp) {
+        PyErr_SetString(po_InvalidParameter, ivp.what());
+    } catch (const isc::datasrc::DataSourceError& dse) {
+        PyErr_SetString(getDataSourceException("Error"), dse.what());
+    } catch (const isc::NotImplemented& ni) {
+        PyErr_SetString(getDataSourceException("NotImplemented"), ni.what());
+    } catch (const std::exception& stde) {
+        PyErr_SetString(getDataSourceException("Error"), stde.what());
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unexpected exception");
+    }
+    return (-1);
+}
+
+void
+ZoneLoader_destroy(PyObject* po_self) {
+    s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    if (self->target_client != NULL) {
+        Py_DECREF(self->target_client);
+    }
+    if (self->source_client != NULL) {
+        Py_DECREF(self->source_client);
+    }
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ZoneLoader_load(PyObject* po_self, PyObject*) {
+    s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+    try {
+        self->cppobj->load();
+        Py_RETURN_NONE;
+    } catch (const isc::InvalidOperation& ivo) {
+        PyErr_SetString(po_InvalidOperation, ivo.what());
+        return (NULL);
+    } catch (const isc::datasrc::MasterFileError& mfe) {
+        PyErr_SetString(getDataSourceException("MasterFileError"), mfe.what());
+        return (NULL);
+    } catch (const isc::datasrc::DataSourceError& dse) {
+        PyErr_SetString(getDataSourceException("Error"), dse.what());
+        return (NULL);
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unexpected exception");
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneLoader_loadIncremental(PyObject* po_self, PyObject* args) {
+    s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+
+    int limit;
+    if (!PyArg_ParseTuple(args, "i", &limit)) {
+        return (NULL);
+    }
+    if (limit < 0) {
+        PyErr_SetString(PyExc_ValueError,
+                        "load_incremental argument must be positive");
+        return (NULL);
+    }
+    try {
+        if (self->cppobj->loadIncremental(limit)) {
+            Py_RETURN_TRUE;
+        } else {
+            Py_RETURN_FALSE;
+        }
+    } catch (const isc::InvalidOperation& ivo) {
+        PyErr_SetString(po_InvalidOperation, ivo.what());
+        return (NULL);
+    } catch (const isc::datasrc::MasterFileError& mfe) {
+        PyErr_SetString(getDataSourceException("MasterFileError"), mfe.what());
+        return (NULL);
+    } catch (const isc::datasrc::DataSourceError& dse) {
+        PyErr_SetString(getDataSourceException("Error"), dse.what());
+        return (NULL);
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unexpected exception");
+        return (NULL);
+    }
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef ZoneLoader_methods[] = {
+    { "load", ZoneLoader_load, METH_NOARGS, ZoneLoader_load_doc },
+    { "load_incremental", ZoneLoader_loadIncremental, METH_VARARGS,
+      ZoneLoader_loadIncremental_doc },
+    { NULL, NULL, 0, NULL }
+};
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+
+PyTypeObject zone_loader_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datasrc.ZoneLoader",
+    sizeof(s_ZoneLoader),               // tp_basicsize
+    0,                                  // tp_itemsize
+    ZoneLoader_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
+    ZoneLoader_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    ZoneLoader_methods,                 // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    ZoneLoader_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
+};
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+

+ 34 - 0
src/lib/python/isc/datasrc/zone_loader_python.h

@@ -0,0 +1,34 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PYTHON_DATASRC_ZONE_LOADER_H
+#define PYTHON_DATASRC_ZONE_LOADER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace datasrc {
+
+namespace python {
+
+extern PyTypeObject zone_loader_type;
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+#endif // PYTHON_DATASRC_ZONE_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End: