Browse Source

[2438] added Python wrapper for RRsetCollection (base and DNS derived ver.).

JINMEI Tatuya 12 years ago
parent
commit
7cee8494a7

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

@@ -25,6 +25,8 @@ 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_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 libb10_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)

+ 9 - 0
src/lib/dns/python/pydnspp.cc

@@ -50,6 +50,7 @@
 #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"
@@ -864,5 +865,13 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_RRsetCollectionBase(mod)) {
+        return (NULL);
+    }
+
+    if (!initModulePart_RRsetCollection(mod)) {
+        return (NULL);
+    }
+
     return (mod);
 }

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

@@ -0,0 +1,424 @@
+// 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)) {
+            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, "")) {
+            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 rrsetcollection_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(&rrsetcollection_type) < 0) {
+        return (false);
+    }
+    void* p = &rrsetcollection_type;
+    if (PyModule_AddObject(mod, "RRsetCollection",
+                           static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&rrsetcollection_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

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

@@ -12,6 +12,7 @@ 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

+ 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()