Browse Source

[master] Merge branch 'trac2437'

JINMEI Tatuya 12 years ago
parent
commit
d365482e0e

+ 5 - 0
src/lib/dns/python/Makefile.am

@@ -25,6 +25,9 @@ libb10_pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
 libb10_pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
 libb10_pydnspp_la_SOURCES += edns_python.cc edns_python.h
 libb10_pydnspp_la_SOURCES += message_python.cc message_python.h
+libb10_pydnspp_la_SOURCES += rrset_collection_python.cc
+libb10_pydnspp_la_SOURCES += rrset_collection_python.h
+libb10_pydnspp_la_SOURCES += zone_checker_python.cc zone_checker_python.h
 
 libb10_pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 libb10_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -43,6 +46,8 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 EXTRA_DIST = tsigerror_python_inc.cc
 EXTRA_DIST += message_python_inc.cc
 EXTRA_DIST += nsec3hash_python_inc.cc
+EXTRA_DIST += rrset_collection_python_inc.cc
+EXTRA_DIST += zone_checker_python_inc.cc
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.

+ 19 - 2
src/lib/dns/python/pydnspp.cc

@@ -50,12 +50,16 @@
 #include "rrset_python.h"
 #include "rrttl_python.h"
 #include "rrtype_python.h"
+#include "rrset_collection_python.h"
 #include "serial_python.h"
 #include "tsigerror_python.h"
 #include "tsigkey_python.h"
 #include "tsig_python.h"
 #include "tsig_rdata_python.h"
 #include "tsigrecord_python.h"
+#include "zone_checker_python.h"
+
+#include "zone_checker_python_inc.cc"
 
 using namespace isc::dns;
 using namespace isc::dns::python;
@@ -728,6 +732,11 @@ initModulePart_TSIGRecord(PyObject* mod) {
     return (true);
 }
 
+PyMethodDef methods[] = {
+    { "check_zone", internal::pyCheckZone, METH_VARARGS, dns_checkZone_doc },
+    { NULL, NULL, 0, NULL }
+};
+
 PyModuleDef pydnspp = {
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     "pydnspp",
@@ -737,13 +746,13 @@ PyModuleDef pydnspp = {
     "and OutputBuffer for instance), and others may be necessary, but "
     "were not up to now.",
     -1,
-    NULL,
+    methods,
     NULL,
     NULL,
     NULL,
     NULL
 };
-}
+} // unnamed namespace
 
 PyMODINIT_FUNC
 PyInit_pydnspp(void) {
@@ -864,5 +873,13 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_RRsetCollectionBase(mod)) {
+        return (NULL);
+    }
+
+    if (!initModulePart_RRsetCollection(mod)) {
+        return (NULL);
+    }
+
     return (mod);
 }

+ 426 - 0
src/lib/dns/python/rrset_collection_python.cc

@@ -0,0 +1,426 @@
+// Copyright (C) 2013  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 <exceptions/exceptions.h>
+
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/name_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrset_collection.h>
+
+#include <string>
+#include <sstream>
+#include <stdexcept>
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+// Import pydoc text
+#include "rrset_collection_python_inc.cc"
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+    PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+                 var_name, type_name, pobj->ob_type->tp_name);
+    return (NULL);
+}
+}
+
+//
+// RRsetCollectionBase
+//
+
+namespace {
+int
+RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
+    PyErr_SetString(PyExc_TypeError,
+                    "RRsetCollectionBase cannot be instantiated directly");
+    return (-1);
+}
+
+void
+RRsetCollectionBase_destroy(PyObject* po_self) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+
+    if (self->cppobj == NULL) {
+        PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
+                     "derived RRsetCollection class");
+        return (NULL);
+    }
+
+    try {
+        PyObject* po_name;
+        PyObject* po_rrclass;
+        PyObject* po_rrtype;
+
+        if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+            if (!PyName_Check(po_name)) {
+                return (setTypeError(po_name, "name", "Name"));
+            }
+            if (!PyRRClass_Check(po_rrclass)) {
+                return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+            }
+            if (!PyRRType_Check(po_rrtype)) {
+                return (setTypeError(po_rrtype, "rrtype", "RRType"));
+            }
+            ConstRRsetPtr found_rrset = self->cppobj->find(
+                PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+                PyRRType_ToRRType(po_rrtype));
+            if (found_rrset) {
+                return (createRRsetObject(*found_rrset));
+            }
+            Py_RETURN_NONE;
+        }
+    } catch (const std::exception& ex) {
+        const string ex_what = "Unexpected failure in "
+            "RRsetCollectionBase.find: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+
+    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 RRsetCollectionBase_methods[] = {
+    { "find", RRsetCollectionBase_find, METH_VARARGS,
+      RRsetCollectionBase_find_doc },
+    { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// 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.
+PyTypeObject rrset_collection_base_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "dns.RRsetCollectionBase",
+    sizeof(s_RRsetCollection),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    RRsetCollectionBase_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|Py_TPFLAGS_BASETYPE, // tp_flags (it's inheritable)
+    RRsetCollectionBase_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                 // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    RRsetCollectionBase_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
+    RRsetCollectionBase_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
+};
+
+// 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) {
+        return (false);
+    }
+    void* p = &rrset_collection_base_type;
+    if (PyModule_AddObject(mod, "RRsetCollectionBase",
+                           static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&rrset_collection_base_type);
+
+    return (true);
+}
+
+//
+// RRsetCollection
+//
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_RRsetCollection, RRsetCollection> RRsetCollectionContainer;
+
+int
+RRsetCollection_init(PyObject* po_self, PyObject* args, PyObject*) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    try {
+        const char* filename;
+        PyObject* po_name;
+        PyObject* po_rrclass;
+        Py_buffer py_buf;
+
+        if (PyArg_ParseTuple(args, "sO!O!", &filename, &name_type, &po_name,
+                             &rrclass_type, &po_rrclass)) {
+            self->cppobj =
+                new RRsetCollection(filename, PyName_ToName(po_name),
+                                    PyRRClass_ToRRClass(po_rrclass));
+            return (0);
+        } else if (PyArg_ParseTuple(args, "y*O!O!", &py_buf, &name_type,
+                                    &po_name,&rrclass_type, &po_rrclass)) {
+            PyErr_Clear();      // clear the error for the first ParseTuple
+            const char* const cp = static_cast<const char*>(py_buf.buf);
+            std::istringstream iss(string(cp, cp + py_buf.len));
+            self->cppobj =
+                new RRsetCollection(iss, PyName_ToName(po_name),
+                                    PyRRClass_ToRRClass(po_rrclass));
+            return (0);
+        } else if (PyArg_ParseTuple(args, "")) {
+            PyErr_Clear();      // clear the error for the second ParseTuple
+            self->cppobj = new RRsetCollection;
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct RRsetCollection object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (-1);
+    }
+
+    // Default error string isn't helpful when it takes multiple combinations
+    // of args.  We provide our own.
+    PyErr_SetString(PyExc_TypeError, "Invalid argument(s) to RRsetCollection "
+                    "constructor; see pydoc");
+
+    return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollection_addRRset(PyObject* po_self, PyObject* args) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    try {
+        PyObject* po_rrset;
+        if (PyArg_ParseTuple(args, "O", &po_rrset)) {
+            if (!PyRRset_Check(po_rrset)) {
+                return (setTypeError(po_rrset, "rrset", "RRset"));
+            }
+            static_cast<RRsetCollection*>(self->cppobj)->addRRset(
+                PyRRset_ToRRsetPtr(po_rrset));
+            Py_RETURN_NONE;
+        }
+    } catch (const InvalidParameter& ex) { // duplicate add
+        PyErr_SetString(PyExc_ValueError, ex.what());
+        return (NULL);
+    } catch (const std::exception& ex) {
+        const string ex_what = "Unexpected failure in "
+            "RRsetCollection.add_rrset: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+
+    return (NULL);
+}
+
+PyObject*
+RRsetCollection_removeRRset(PyObject* po_self, PyObject* args) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    try {
+        PyObject* po_name;
+        PyObject* po_rrclass;
+        PyObject* po_rrtype;
+
+        if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+            if (!PyName_Check(po_name)) {
+                return (setTypeError(po_name, "name", "Name"));
+            }
+            if (!PyRRClass_Check(po_rrclass)) {
+                return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+            }
+            if (!PyRRType_Check(po_rrtype)) {
+                return (setTypeError(po_rrtype, "rrtype", "RRType"));
+            }
+            const bool result =
+                static_cast<RRsetCollection*>(self->cppobj)->removeRRset(
+                    PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+                    PyRRType_ToRRType(po_rrtype));
+            if (result) {
+                Py_RETURN_TRUE;
+            } else {
+                Py_RETURN_FALSE;
+            }
+        }
+    } catch (...) {}
+
+    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 RRsetCollection_methods[] = {
+    { "add_rrset", RRsetCollection_addRRset, METH_VARARGS,
+      RRsetCollection_addRRset_doc },
+    { "remove_rrset", RRsetCollection_removeRRset, METH_VARARGS,
+      RRsetCollection_removeRRset_doc },
+    { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+// 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.
+PyTypeObject rrset_collection_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "dns.RRsetCollection",
+    sizeof(s_RRsetCollection),                 // 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
+    RRsetCollection_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    RRsetCollection_methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    &rrset_collection_base_type,        // tp_base
+    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
+};
+
+// 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) {
+        return (false);
+    }
+    Py_INCREF(&rrset_collection_type);
+
+    return (true);
+}
+
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 52 - 0
src/lib/dns/python/rrset_collection_python.h

@@ -0,0 +1,52 @@
+// Copyright (C) 2013  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_RRSETCOLLECTION_H
+#define PYTHON_RRSETCOLLECTION_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class RRsetCollectionBase;
+
+namespace python {
+
+// 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.
+class s_RRsetCollection : public PyObject {
+public:
+    s_RRsetCollection() : cppobj(NULL) {}
+    RRsetCollectionBase* cppobj;
+};
+
+// Python type information for RRsetCollectionBase
+extern PyTypeObject rrset_collection_base_type;
+
+// Python type information for dns.RRsetCollection
+extern PyTypeObject rrset_collection_type;
+
+bool initModulePart_RRsetCollectionBase(PyObject* mod);
+bool initModulePart_RRsetCollection(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_RRSETCOLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 148 - 0
src/lib/dns/python/rrset_collection_python_inc.cc

@@ -0,0 +1,148 @@
+namespace {
+// Modifications
+//   - libdns++ => isc.dns, libdatasrc => isc.datasrc
+//   - note about the constructor.
+//   - add note about iteration
+const char* const RRsetCollectionBase_doc = "\
+Generic class to represent a set of RRsets.\n\
+\n\
+This is a generic container and the stored set of RRsets does not\n\
+necessarily form a valid zone (e.g. there doesn't necessarily have to\n\
+be an SOA at the \"origin\"). Instead, it will be used to represent a\n\
+single zone for the purpose of zone loading/checking. It provides a\n\
+simple find() method to find an RRset for the given name and type (and\n\
+maybe class) and a way to iterate over all RRsets.\n\
+\n\
+    Note: in the initial version, iteration is not yet supported.\n\
+\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\
+\n\
+";
+
+// Modifications
+//   - ConstRRset => RRset
+//   - NULL => None
+//   - added types of params
+const char* const RRsetCollectionBase_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, None is\n\
+returned.\n\
+\n\
+Parameters:\n\
+  name       (isc.dns.Name) The name of the RRset to search for.\n\
+  rrtype     (isc.dns.RRType) The type of the RRset to search for.\n\
+  rrclass    (isc.dns.RRClass) The class of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, None otherwise.\n\
+";
+
+
+// Modifications
+//   - libdns++ => isc.dns
+//   - remove STL
+//   - MasterLoaderError => IscException
+//   - added types of params
+//  - input_stream => input, stream => bytes
+const char* const RRsetCollection_doc = "\
+Derived class implementation of RRsetCollectionBase for isc.dns module.\n\
+\n\
+RRsetCollection()\n\
+\n\
+    Constructor.\n\
+\n\
+    This constructor creates an empty collection without any data in\n\
+    it. RRsets can be added to the collection with the add_rrset()\n\
+    method.\n\
+\n\
+RRsetCollection(filename, origin, rrclass)\n\
+\n\
+    Constructor.\n\
+\n\
+    The origin and rrclass arguments are required for the zone\n\
+    loading, but RRsetCollection itself does not do any validation,\n\
+    and the collection of RRsets does not have to form a valid zone.\n\
+    The constructor throws IscException if there is an error\n\
+    during loading.\n\
+\n\
+    Parameters:\n\
+      filename   (str) Name of a file containing a collection of RRs in the\n\
+                 master file format (which may or may not form a valid\n\
+                 zone).\n\
+      origin     (isc.dns.Name) The zone origin.\n\
+      rrclass    (isc.dns.RRClass) The zone class.\n\
+\n\
+RRsetCollection(input_stream, 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\
+    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\
+      origin     (isc.dns.Name) The zone origin.\n\
+      rrclass    (isc.dns.RRClass) The zone class.\n\
+\n\
+";
+
+// Modifications
+//   - void => None
+//   - InvalidParameter => ValueError
+//   - remove ownership related points (doesn't apply here)
+const char* const RRsetCollection_addRRset_doc = "\
+add_rrset(rrset) -> None\n\
+\n\
+Add an RRset to the collection.\n\
+\n\
+Does not do any validation whether rrset belongs to a particular zone\n\
+or not.\n\
+\n\
+It throws a ValueError exception if an rrset with the same\n\
+class, type and name already exists.\n\
+\n\
+";
+
+// Modifications
+//   - ConstRRset => RRset
+const char* const RRsetCollection_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, NULL is\n\
+returned.\n\
+\n\
+Parameters:\n\
+  name       The name of the RRset to search for.\n\
+  rrclass    The class of the RRset to search for.\n\
+  rrtype     The type of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, NULL otherwise.\n\
+";
+
+// Modifications
+//   //   - true/false => True/False
+const char* const RRsetCollection_removeRRset_doc = "\
+remove_rrset(name, rrclass, rrtype) -> bool\n\
+\n\
+Remove an RRset from the collection.\n\
+\n\
+RRset(s) matching the name, rrclass and rrtype are removed from the\n\
+collection.\n\
+\n\
+True if a matching RRset was deleted, False if no such RRset exists.\n\
+\n\
+";
+} // unnamed namespace

+ 2 - 0
src/lib/dns/python/tests/Makefile.am

@@ -12,12 +12,14 @@ PYTESTS += rrclass_python_test.py
 PYTESTS += rrset_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrtype_python_test.py
+PYTESTS += rrset_collection_python_test.py
 PYTESTS += serial_python_test.py
 PYTESTS += tsig_python_test.py
 PYTESTS += tsig_rdata_python_test.py
 PYTESTS += tsigerror_python_test.py
 PYTESTS += tsigkey_python_test.py
 PYTESTS += tsigrecord_python_test.py
+PYTESTS += zone_checker_python_test.py
 
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += testutil.py

+ 140 - 0
src/lib/dns/python/tests/rrset_collection_python_test.py

@@ -0,0 +1,140 @@
+# Copyright (C) 2013  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 os
+import unittest
+from pydnspp import *
+
+# This should refer to the testdata diretory for the C++ tests.
+TESTDATA_DIR = os.environ["TESTDATA_PATH"].split(':')[0]
+
+class RRsetCollectionBaseTest(unittest.TestCase):
+    def test_init(self):
+        # direct instantiation of the base class is prohibited.
+        self.assertRaises(TypeError, RRsetCollectionBase)
+
+class RRsetCollectionTest(unittest.TestCase):
+    def test_init_fail(self):
+        # check various failure cases on construction (other normal cases are
+        # covered as part of other tests)
+
+        # bad args
+        self.assertRaises(TypeError, RRsetCollection, 1)
+        self.assertRaises(TypeError, RRsetCollection, # extra arg
+                          b'example. 0 A 192.0.2.1',
+                          Name('example'), RRClass.IN(), 1)
+        self.assertRaises(TypeError, RRsetCollection, # incorrect order
+                          b'example. 0 A 192.0.2.1', RRClass.IN(),
+                          Name('example'))
+
+        # constructor will result in C++ exception.
+        self.assertRaises(IscException, RRsetCollection,
+                          TESTDATA_DIR + '/no_such_file', Name('example.org'),
+                          RRClass.IN())
+
+    def check_find_result(self, rrsets):
+        # Commonly used check pattern
+        found = rrsets.find(Name('www.example.org'), RRClass.IN(), RRType.A())
+        self.assertNotEqual(None, found)
+        self.assertEqual(Name('www.example.org'), found.get_name())
+        self.assertEqual(RRClass.IN(), found.get_class())
+        self.assertEqual(RRType.A(), found.get_type())
+        self.assertEqual('192.0.2.1', found.get_rdata()[0].to_text())
+
+    def test_find(self):
+        # Checking the underlying find() is called as intended, both for
+        # success and failure cases, and with two different constructors.
+        rrsets = RRsetCollection(TESTDATA_DIR + '/example.org',
+                                 Name('example.org'), RRClass.IN())
+        self.check_find_result(rrsets)
+        self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+                                           RRType.A()))
+
+        rrsets = RRsetCollection(b'www.example.org. 3600 IN A 192.0.2.1',
+                                 Name('example.org'), RRClass.IN())
+        self.check_find_result(rrsets)
+        self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+                                           RRType.A()))
+
+    def test_find_badargs(self):
+        rrsets = RRsetCollection()
+
+        # Check bad arguments: bad types
+        self.assertRaises(TypeError, rrsets.find, 1, RRClass.IN(), RRType.A())
+        self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+                          RRType.A())
+        self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+                          RRType.A())
+        self.assertRaises(TypeError, rrsets.find, Name('example'),
+                          RRClass.IN(), 1)
+        self.assertRaises(TypeError, rrsets.find, Name('example'), RRType.A(),
+                          RRClass.IN())
+
+        # Check bad arguments: too many/few arguments
+        self.assertRaises(TypeError, rrsets.find, Name('example'),
+                          RRClass.IN(), RRType.A(), 0)
+        self.assertRaises(TypeError, rrsets.find, Name('example'),
+                          RRClass.IN())
+
+    def test_add_remove_rrset(self):
+        name = Name('www.example.org')
+        rrclass = RRClass.IN()
+        rrtype = RRType.A()
+
+        # Create a collection with no RRsets
+        rrsets = RRsetCollection()
+        self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+        # add the record, then it should be found
+        rrset = RRset(name, rrclass, rrtype, RRTTL(60))
+        rrset.add_rdata(Rdata(rrtype, rrclass, '192.0.2.1'))
+        self.assertEqual(None, rrsets.add_rrset(rrset))
+        self.check_find_result(rrsets)
+
+        # duplicate add is (at least currently) rejected
+        self.assertRaises(ValueError, rrsets.add_rrset, rrset)
+
+        # remove it, then we cannot find it any more.
+        self.assertTrue(rrsets.remove_rrset(name, rrclass, rrtype))
+        self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+        # duplicate remove (specified RRset doesn't exist) reulsts in False
+        self.assertFalse(rrsets.remove_rrset(name, rrclass, rrtype))
+
+        # Checking bad args
+        self.assertRaises(TypeError, rrsets.add_rrset, 1)
+        self.assertRaises(TypeError, rrsets.add_rrset, rrset, 1)
+        self.assertRaises(TypeError, rrsets.add_rrset)
+
+        self.assertRaises(TypeError, rrsets.remove_rrset, 1, rrclass, rrtype)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, 1, rrtype)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass, 1)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, rrtype,
+                          rrclass)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass,
+                          rrtype, 1)
+
+    def test_empty_class(self):
+        # A user defined collection class shouldn't cause disruption.
+        class EmptyRRsetCollection(RRsetCollectionBase):
+            def __init__(self):
+                pass
+        rrsets = EmptyRRsetCollection()
+        self.assertRaises(TypeError, rrsets.find, Name('www.example.org'),
+                          RRClass.IN(), RRType.A())
+
+if __name__ == '__main__':
+    unittest.main()

+ 179 - 0
src/lib/dns/python/tests/zone_checker_python_test.py

@@ -0,0 +1,179 @@
+# Copyright (C) 2013  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 unittest
+import sys
+from pydnspp import *
+
+# A separate exception class raised from some tests to see if it's propagated.
+class FakeException(Exception):
+    pass
+
+class ZoneCheckerTest(unittest.TestCase):
+    def __callback(self, reason, reasons):
+        # Issue callback for check_zone().  It simply records the given reason
+        # string in the given list.
+        reasons.append(reason)
+
+    def test_check(self):
+        errors = []
+        warns = []
+
+        # A successful case with no warning.
+        rrsets = RRsetCollection(b'example.org. 0 SOA . . 0 0 0 0 0\n' +
+                                 b'example.org. 0 NS ns.example.org.\n' +
+                                 b'ns.example.org. 0 A 192.0.2.1\n',
+                                 Name('example.org'), RRClass.IN())
+        self.assertTrue(check_zone(Name('example.org'), RRClass.IN(),
+                                   rrsets,
+                                   (lambda r: self.__callback(r, errors),
+                                    lambda r: self.__callback(r, warns))))
+        self.assertEqual([], errors)
+        self.assertEqual([], warns)
+
+        # Check fails and one additional warning.
+        rrsets = RRsetCollection(b'example.org. 0 NS ns.example.org.',
+                                 Name('example.org'), RRClass.IN())
+        self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+                                    (lambda r: self.__callback(r, errors),
+                                     lambda r: self.__callback(r, warns))))
+        self.assertEqual(['zone example.org/IN: has 0 SOA records'], errors)
+        self.assertEqual(['zone example.org/IN: NS has no address records ' +
+                          '(A or AAAA)'], warns)
+
+        # Same RRset collection, suppressing callbacks
+        errors = []
+        warns = []
+        self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+                                    (None, None)))
+        self.assertEqual([], errors)
+        self.assertEqual([], warns)
+
+    def test_check_badarg(self):
+        rrsets = RRsetCollection()
+        # Bad types
+        self.assertRaises(TypeError, check_zone, 1, RRClass.IN(), rrsets,
+                          (None, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), 1, rrsets,
+                          (None, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          1, (None, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, 1)
+
+        # Bad callbacks
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (None, None, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (1, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (None, 1))
+
+        # Extra/missing args
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (None, None), 1)
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets)
+        check_zone(Name('example'), RRClass.IN(), rrsets, (None, None))
+
+    def test_check_callback_fail(self):
+        # Let the call raise a Python exception.  It should be propagated to
+        # the top level.
+        def __bad_callback(reason):
+            raise FakeException('error in callback')
+
+        # Using an empty collection, triggering an error callback.
+        self.assertRaises(FakeException, check_zone, Name('example.org'),
+                          RRClass.IN(), RRsetCollection(),
+                          (__bad_callback, None))
+
+        # An unusual case: the callback is expected to return None, but if it
+        # returns an actual object it shouldn't cause leak inside the callback.
+        class RefChecker:
+            pass
+        def __callback(reason, checker):
+            return checker
+
+        ref_checker = RefChecker()
+        orig_refcnt = sys.getrefcount(ref_checker)
+        check_zone(Name('example.org'), RRClass.IN(), RRsetCollection(),
+                   (lambda r: __callback(r, ref_checker), None))
+        self.assertEqual(orig_refcnt, sys.getrefcount(ref_checker))
+
+    def test_check_custom_collection(self):
+        # Test if check_zone() works with pure-Python RRsetCollection.
+
+        class FakeRRsetCollection(RRsetCollectionBase):
+            # This is the Python-only collection class.  Its find() makes
+            # the check pass by default, by returning hardcoded RRsets.
+            # If raise_on_find is set to True, find() raises an exception.
+            # If find_result is set to something other than 'use_default'
+            # (as a string), find() returns that specified value (note that
+            # it can be None).
+
+            def __init__(self, raise_on_find=False, find_result='use_default'):
+                self.__raise_on_find = raise_on_find
+                self.__find_result = find_result
+
+            def find(self, name, rrclass, rrtype):
+                if self.__raise_on_find:
+                    raise FakeException('find error')
+                if self.__find_result is not 'use_default':
+                    return self.__find_result
+                if rrtype == RRType.SOA():
+                    soa = RRset(Name('example'), RRClass.IN(), rrtype,
+                                RRTTL(0))
+                    soa.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
+                                        '. . 0 0 0 0 0'))
+                    return soa
+                if rrtype == RRType.NS():
+                    ns = RRset(Name('example'), RRClass.IN(), rrtype,
+                               RRTTL(0))
+                    ns.add_rdata(Rdata(RRType.NS(), RRClass.IN(),
+                                       'example.org'))
+                    return ns
+                return None
+
+        # A successful case.  Just checking it works in that case.
+        rrsets = FakeRRsetCollection()
+        self.assertTrue(check_zone(Name('example'), RRClass.IN(), rrsets,
+                                   (None, None)))
+
+        # Likewise, normal case but zone check fails.
+        rrsets = FakeRRsetCollection(False, None)
+        self.assertFalse(check_zone(Name('example'), RRClass.IN(), rrsets,
+                                    (None, None)))
+
+        # Our find() returns a bad type of result.
+        rrsets = FakeRRsetCollection(False, 1)
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (None, None))
+
+        # Our find() returns an empty SOA RRset.  C++ zone checker code
+        # throws, which results in IscException.
+        rrsets = FakeRRsetCollection(False, RRset(Name('example'),
+                                                  RRClass.IN(),
+                                                  RRType.SOA(), RRTTL(0)))
+        self.assertRaises(IscException, check_zone, Name('example'),
+                          RRClass.IN(), rrsets, (None, None))
+
+        # Our find() raises an exception.  That exception is propagated to
+        # the top level.
+        rrsets = FakeRRsetCollection(True)
+        self.assertRaises(FakeException, check_zone, Name('example'),
+                          RRClass.IN(), rrsets, (None, None))
+
+if __name__ == '__main__':
+    unittest.main()

+ 224 - 0
src/lib/dns/python/zone_checker_python.cc

@@ -0,0 +1,224 @@
+// Copyright (C) 2013  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 <dns/python/name_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/zone_checker_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+#include <dns/zone_checker.h>
+
+#include <boost/bind.hpp>
+
+#include <cstring>
+#include <string>
+#include <stdexcept>
+
+using std::string;
+using isc::util::python::PyObjectContainer;
+using namespace isc::dns;
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+    PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+                 var_name, type_name, pobj->ob_type->tp_name);
+    return (NULL);
+}
+}
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+namespace {
+// This is used to abort check_zone() and go back to the top level.
+// We use a separate exception so it won't be caught in the middle.
+class InternalException : public std::exception {
+};
+
+// This is a "wrapper" RRsetCollection subclass.  It's constructed with
+// a Python RRsetCollection object, and its find() calls the Python version
+// of RRsetCollection.find().  This way, the check_zone() wrapper will work
+// for pure-Python RRsetCollection classes, too.
+class PyRRsetCollection : public RRsetCollectionBase {
+public:
+    PyRRsetCollection(PyObject* po_rrsets) : po_rrsets_(po_rrsets) {}
+
+    virtual ConstRRsetPtr find(const Name& name, const RRClass& rrclass,
+                               const RRType& rrtype) const {
+        try {
+            // Convert C++ args to Python objects, and builds argument tuple
+            // to the Python method.  This should basically succeed.
+            PyObjectContainer poc_name(createNameObject(name));
+            PyObjectContainer poc_rrclass(createRRClassObject(rrclass));
+            PyObjectContainer poc_rrtype(createRRTypeObject(rrtype));
+            PyObjectContainer poc_args(Py_BuildValue("(OOOO)",
+                                                     po_rrsets_,
+                                                     poc_name.get(),
+                                                     poc_rrclass.get(),
+                                                     poc_rrtype.get()));
+
+            // Call the Python method.
+            // PyObject_CallMethod is dirty and requires mutable C-string for
+            // method name and arguments.  While it's unlikely for these to
+            // be modified, we err on the side of caution and make copies.
+            char method_name[sizeof("find")];
+            char method_args[sizeof("(OOO)")];
+            std::strcpy(method_name, "find");
+            std::strcpy(method_args, "(OOO)");
+            PyObjectContainer poc_result(
+                PyObject_CallMethod(po_rrsets_, method_name, method_args,
+                                    poc_name.get(), poc_rrclass.get(),
+                                    poc_rrtype.get()));
+            PyObject* const po_result = poc_result.get();
+            if (po_result == Py_None) {
+                return (ConstRRsetPtr());
+            } else if (PyRRset_Check(po_result)) {
+                return (PyRRset_ToRRsetPtr(po_result));
+            } else {
+                PyErr_SetString(PyExc_TypeError, "invalid type for "
+                                "RRsetCollection.find(): must be None "
+                                "or RRset");
+                throw InternalException();
+            }
+        } catch (const isc::util::python::PyCPPWrapperException& ex) {
+            // This normally means the method call fails.  Propagate the
+            // already-set Python error to the top level.  Other C++ exceptions
+            // are really unexpected, so we also (implicitly) propagate it
+            // to the top level and recognize it as "unexpected failure".
+            throw InternalException();
+        }
+    }
+
+    virtual IterPtr getBeginning() {
+        isc_throw(NotImplemented, "iterator support is not yet available");
+    }
+    virtual IterPtr getEnd() {
+        isc_throw(NotImplemented, "iterator support is not yet available");
+    }
+
+private:
+    PyObject* const po_rrsets_;
+};
+
+void
+callback(const string& reason, PyObject* obj) {
+    PyObjectContainer poc_args(Py_BuildValue("(s#)", reason.c_str(),
+                                             reason.size()));
+    PyObject* po_result = PyObject_CallObject(obj, poc_args.get());
+    if (po_result == NULL) {
+        throw InternalException();
+    }
+    Py_DECREF(po_result);
+}
+
+ZoneCheckerCallbacks::IssueCallback
+PyCallable_ToCallback(PyObject* obj) {
+    if (obj == Py_None) {
+        return (NULL);
+    }
+    return (boost::bind(callback, _1, obj));
+}
+
+}
+
+PyObject*
+pyCheckZone(PyObject*, PyObject* args) {
+    try {
+        PyObject* po_name;
+        PyObject* po_rrclass;
+        PyObject* po_rrsets;
+        PyObject* po_error;
+        PyObject* po_warn;
+
+        if (PyArg_ParseTuple(args, "OOO(OO)", &po_name, &po_rrclass,
+                             &po_rrsets, &po_error, &po_warn)) {
+            if (!PyName_Check(po_name)) {
+                return (setTypeError(po_name, "zone_name", "Name"));
+            }
+            if (!PyRRClass_Check(po_rrclass)) {
+                return (setTypeError(po_rrclass, "zone_rrclass", "RRClass"));
+            }
+            if (!PyObject_TypeCheck(po_rrsets, &rrset_collection_base_type)) {
+                return (setTypeError(po_rrsets, "zone_rrsets",
+                                     "RRsetCollectionBase"));
+            }
+            if (po_error != Py_None && PyCallable_Check(po_error) == 0) {
+                return (setTypeError(po_error, "error", "callable or None"));
+            }
+            if (po_warn != Py_None && PyCallable_Check(po_warn) == 0) {
+                return (setTypeError(po_warn, "warn", "callable or None"));
+            }
+
+            PyRRsetCollection py_rrsets(po_rrsets);
+            if (checkZone(PyName_ToName(po_name),
+                          PyRRClass_ToRRClass(po_rrclass), py_rrsets,
+                          ZoneCheckerCallbacks(
+                              PyCallable_ToCallback(po_error),
+                              PyCallable_ToCallback(po_warn)))) {
+                Py_RETURN_TRUE;
+            } else {
+                Py_RETURN_FALSE;
+            }
+        }
+    } catch (const InternalException& ex) {
+        // Normally, error string should have been set already.  For some
+        // rare cases such as memory allocation failure, we set the last-resort
+        // error string.
+        if (PyErr_Occurred() == NULL) {
+            PyErr_SetString(PyExc_SystemError,
+                            "Unexpected failure in check_zone()");
+        }
+        return (NULL);
+    } catch (const std::exception& ex) {
+        const string ex_what = "Unexpected failure in check_zone(): " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+
+    return (NULL);
+}
+
+} // namespace internal
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 35 - 0
src/lib/dns/python/zone_checker_python.h

@@ -0,0 +1,35 @@
+// Copyright (C) 2013  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_ZONE_CHECKER_H
+#define PYTHON_ZONE_CHECKER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+PyObject* pyCheckZone(PyObject* self, PyObject* args);
+
+} // namespace python
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 79 - 0
src/lib/dns/python/zone_checker_python_inc.cc

@@ -0,0 +1,79 @@
+namespace {
+// Modifications
+//   - callbacks => (error, warn)
+//   - recover paragraph before itemization (it's a bug of convert script)
+//   - correct broken format for nested items (another bug of script)
+//   - true/false => True/False
+//   - removed Exception section (for simplicity)
+const char* const dns_checkZone_doc = "\
+check_zone(zone_name, zone_class, zone_rrsets, (error, warn)) -> bool\n\
+\n\
+Perform basic integrity checks on zone RRsets.\n\
+\n\
+This function performs some lightweight checks on zone's SOA and\n\
+(apex) NS records. Here, lightweight means it doesn't require\n\
+traversing the entire zone, and should be expected to complete\n\
+reasonably quickly regardless of the size of the zone.\n\
+\n\
+It distinguishes \"critical\" errors and other undesirable issues: the\n\
+former should be interpreted as the resulting zone shouldn't be used\n\
+further, e.g, by an authoritative server implementation; the latter\n\
+means the issues are better to be addressed but are not necessarily\n\
+considered to make the zone invalid. Critical errors are reported via\n\
+the error() function, and non critical issues are reported via warn().\n\
+\n\
+Specific checks performed by this function is as follows.  Failure of\n\
+a check is considered a critical error unless noted otherwise:\n\
+\n\
+- There is exactly one SOA RR at the zone apex.\n\
+- There is at least one NS RR at the zone apex.\n\
+- For each apex NS record, if the NS name (the RDATA of the record) is\n\
+  in the zone (i.e., it's a subdomain of the zone origin and above any\n\
+  zone cut due to delegation), check the following:\n\
+  - the NS name should have an address record (AAAA or A). Failure of\n\
+    this check is considered a non critical issue.\n\
+  - the NS name does not have a CNAME. This is prohibited by Section\n\
+    10.3 of RFC 2181.\n\
+  - the NS name is not subject to DNAME substitution. This is prohibited\n\
+    by Section 4 of RFC 6672.\n\
+\n\
+In addition, when the check is completed without any\n\
+critical error, this function guarantees that RRsets for the SOA and\n\
+(apex) NS stored in the passed RRset collection have the expected\n\
+type of Rdata objects, i.e., generic.SOA and generic.NS,\n\
+respectively. (This is normally expected to be the case, but not\n\
+guaranteed by the API).\n\
+\n\
+As for the check on the existence of AAAA or A records for NS names,\n\
+it should be noted that BIND 9 treats this as a critical error. It's\n\
+not clear whether it's an implementation dependent behavior or based\n\
+on the protocol standard (it looks like the former), but to make it\n\
+sure we need to confirm there is even no wildcard match for the names.\n\
+This should be a very rare configuration, and more expensive to\n\
+detect, so we do not check this condition, and treat this case as a\n\
+non critical issue.\n\
+\n\
+This function indicates the result of the checks (whether there is a\n\
+critical error) via the return value: It returns True if there is no\n\
+critical error and returns False otherwise. It doesn't throw an\n\
+exception on encountering an error so that it can report as many\n\
+errors as possible in a single call. If an exception is a better way\n\
+to signal the error, the caller can pass a callable object as error()\n\
+that throws.\n\
+\n\
+This function can still throw an exception if it finds a really bogus\n\
+condition that is most likely to be an implementation bug of the\n\
+caller. Such cases include when an RRset contained in the RRset\n\
+collection is empty.\n\
+\n\
+Parameters:\n\
+  zone_name  The name of the zone to be checked\n\
+  zone_class The RR class of the zone to be checked\n\
+  zone_rrsets The collection of RRsets of the zone\n\
+  error      Callable object used to report errors\n\
+  warn       Callable object used to report non-critical issues\n\
+\n\
+Return Value(s): True if no critical errors are found; False\n\
+otherwise.\n\
+";
+} // unnamed namespace