BIND 10 master, updated. d365482e0e132c96812b7b99c465582a027035f2 [master] Merge branch 'trac2437'
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Jan 8 18:03:04 UTC 2013
The branch, master has been updated
via d365482e0e132c96812b7b99c465582a027035f2 (commit)
via 6e4caf9dd4f4239776d1e05efbfe13987e5bf9a5 (commit)
via fb10ec4e31a94ed4c98fa83c5ef3064f8e53a39e (commit)
via f1e9ef1f4906cfc43cf76308675d24a851739105 (commit)
via 96e05ecaa1e2c19c28ff5d1f50efdcff7e3e7d5c (commit)
via c6f04a39f70ad71571779b734e7a3593e6c2f69f (commit)
via c85c06e93473453547d4a378ebc3c12ef74ae68c (commit)
via 202d9dd4ef7d116f844029a34c36fbdb48921ba0 (commit)
via b0a3df2d384689dad61a47084b5dfa5a00918885 (commit)
via de2886172153001a862453ee4ed73bcd07db2ae0 (commit)
via 58c136da23483524f053d3caa20210941bfa13db (commit)
via cb50f4f6d306ebd40d7ff03b1494dc52b96b191d (commit)
via 7cee8494a729694402e9ce19d257d643888b7a4c (commit)
via d078381e41cf375a3504655ec921999828c284e2 (commit)
from bd9b58b2875c100b68e3f246a09975bde0a13e16 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit d365482e0e132c96812b7b99c465582a027035f2
Merge: bd9b58b 6e4caf9
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Tue Jan 8 10:01:11 2013 -0800
[master] Merge branch 'trac2437'
-----------------------------------------------------------------------
Summary of changes:
src/lib/dns/python/Makefile.am | 5 +
src/lib/dns/python/pydnspp.cc | 21 +-
src/lib/dns/python/rrset_collection_python.cc | 426 ++++++++++++++++++++
.../python/rrset_collection_python.h} | 40 +-
src/lib/dns/python/rrset_collection_python_inc.cc | 148 +++++++
src/lib/dns/python/tests/Makefile.am | 2 +
.../python/tests/rrset_collection_python_test.py | 140 +++++++
.../dns/python/tests/zone_checker_python_test.py | 179 ++++++++
src/lib/dns/python/zone_checker_python.cc | 224 ++++++++++
.../python/zone_checker_python.h} | 18 +-
src/lib/dns/python/zone_checker_python_inc.cc | 79 ++++
11 files changed, 1250 insertions(+), 32 deletions(-)
create mode 100644 src/lib/dns/python/rrset_collection_python.cc
copy src/lib/{python/isc/acl/dns_requestcontext_python.h => dns/python/rrset_collection_python.h} (55%)
create mode 100644 src/lib/dns/python/rrset_collection_python_inc.cc
create mode 100644 src/lib/dns/python/tests/rrset_collection_python_test.py
create mode 100644 src/lib/dns/python/tests/zone_checker_python_test.py
create mode 100644 src/lib/dns/python/zone_checker_python.cc
copy src/lib/{python/isc/util/cio/socketsession_python.h => dns/python/zone_checker_python.h} (74%)
create mode 100644 src/lib/dns/python/zone_checker_python_inc.cc
-----------------------------------------------------------------------
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 6dd94b6..a221bfe 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/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.
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 6d1bd89..c75c737 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/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);
}
diff --git a/src/lib/dns/python/rrset_collection_python.cc b/src/lib/dns/python/rrset_collection_python.cc
new file mode 100644
index 0000000..df313f7
--- /dev/null
+++ b/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
diff --git a/src/lib/dns/python/rrset_collection_python.h b/src/lib/dns/python/rrset_collection_python.h
new file mode 100644
index 0000000..98cb84b
--- /dev/null
+++ b/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:
diff --git a/src/lib/dns/python/rrset_collection_python_inc.cc b/src/lib/dns/python/rrset_collection_python_inc.cc
new file mode 100644
index 0000000..5c1e532
--- /dev/null
+++ b/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
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 4b0ea9f..de6b010 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/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
diff --git a/src/lib/dns/python/tests/rrset_collection_python_test.py b/src/lib/dns/python/tests/rrset_collection_python_test.py
new file mode 100644
index 0000000..2cf286e
--- /dev/null
+++ b/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()
diff --git a/src/lib/dns/python/tests/zone_checker_python_test.py b/src/lib/dns/python/tests/zone_checker_python_test.py
new file mode 100644
index 0000000..66b6c47
--- /dev/null
+++ b/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()
diff --git a/src/lib/dns/python/zone_checker_python.cc b/src/lib/dns/python/zone_checker_python.cc
new file mode 100644
index 0000000..e78be95
--- /dev/null
+++ b/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
diff --git a/src/lib/dns/python/zone_checker_python.h b/src/lib/dns/python/zone_checker_python.h
new file mode 100644
index 0000000..63168fd
--- /dev/null
+++ b/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:
diff --git a/src/lib/dns/python/zone_checker_python_inc.cc b/src/lib/dns/python/zone_checker_python_inc.cc
new file mode 100644
index 0000000..c99042e
--- /dev/null
+++ b/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
More information about the bind10-changes
mailing list