Browse Source

[2437] Merge branch 'trac2438' into trac2437

JINMEI Tatuya 12 years ago
parent
commit
cb50f4f6d3

+ 4 - 0
src/lib/dns/Makefile.am

@@ -127,6 +127,8 @@ libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
 libb10_dns___la_SOURCES += character_string.h character_string.cc
 libb10_dns___la_SOURCES += master_loader_callbacks.h master_loader_callbacks.cc
 libb10_dns___la_SOURCES += master_loader.h
+libb10_dns___la_SOURCES += rrset_collection_base.h
+libb10_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
@@ -170,6 +172,8 @@ libdns___include_HEADERS = \
 	rdata.h \
 	rrparamregistry.h \
 	rrset.h \
+	rrset_collection_base.h \
+	rrset_collection.h \
 	rrttl.h \
 	tsigkey.h
 # Purposely not installing these headers:

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

+ 128 - 0
src/lib/dns/rrset_collection.cc

@@ -0,0 +1,128 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset_collection.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/master_loader.h>
+#include <dns/rrcollator.h>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/bind.hpp>
+
+using namespace isc;
+
+namespace isc {
+namespace dns {
+
+void
+RRsetCollection::loaderCallback(const std::string&, size_t, const std::string&)
+{
+     // We just ignore callbacks for errors and warnings.
+}
+
+void
+RRsetCollection::addRRset(RRsetPtr rrset) {
+    const CollectionKey key(rrset->getClass(), rrset->getType(),
+                            rrset->getName());
+    CollectionMap::const_iterator it = rrsets_.find(key);
+    if (it != rrsets_.end()) {
+        isc_throw(InvalidParameter,
+                  "RRset for " << rrset->getName() << "/" << rrset->getClass()
+                  << " with type " << rrset->getType() << " already exists");
+    }
+
+    rrsets_.insert(std::pair<CollectionKey, RRsetPtr>(key, rrset));
+}
+
+template<typename T>
+void
+RRsetCollection::constructHelper(T source, const isc::dns::Name& origin,
+                                 const isc::dns::RRClass& rrclass)
+{
+    RRCollator collator(boost::bind(&RRsetCollection::addRRset, this, _1));
+    MasterLoaderCallbacks callbacks
+        (boost::bind(&RRsetCollection::loaderCallback, this, _1, _2, _3),
+         boost::bind(&RRsetCollection::loaderCallback, this, _1, _2, _3));
+    MasterLoader loader(source, origin, rrclass, callbacks,
+                        collator.getCallback(),
+                        MasterLoader::DEFAULT);
+    loader.load();
+    collator.flush();
+}
+
+RRsetCollection::RRsetCollection(const char* filename, const Name& origin,
+                                 const RRClass& rrclass)
+{
+    constructHelper<const char*>(filename, origin, rrclass);
+}
+
+RRsetCollection::RRsetCollection(std::istream& input_stream, const Name& origin,
+                                 const RRClass& rrclass)
+{
+    constructHelper<std::istream&>(input_stream, origin, rrclass);
+}
+
+RRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+                      const RRType& rrtype) {
+    const CollectionKey key(rrclass, rrtype, name);
+    CollectionMap::iterator it = rrsets_.find(key);
+    if (it != rrsets_.end()) {
+        return (it->second);
+    }
+    return (RRsetPtr());
+}
+
+ConstRRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+                      const RRType& rrtype) const
+{
+    const CollectionKey key(rrclass, rrtype, name);
+    CollectionMap::const_iterator it = rrsets_.find(key);
+    if (it != rrsets_.end()) {
+        return (it->second);
+    }
+    return (ConstRRsetPtr());
+}
+
+bool
+RRsetCollection::removeRRset(const Name& name, const RRClass& rrclass,
+                             const RRType& rrtype)
+{
+    const CollectionKey key(rrclass, rrtype, name);
+
+    CollectionMap::iterator it = rrsets_.find(key);
+    if (it == rrsets_.end()) {
+        return (false);
+    }
+
+    rrsets_.erase(it);
+    return (true);
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getBeginning() {
+    CollectionMap::iterator it = rrsets_.begin();
+    return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getEnd() {
+    CollectionMap::iterator it = rrsets_.end();
+    return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+} // end of namespace dns
+} // end of namespace isc

+ 172 - 0
src/lib/dns/rrset_collection.h

@@ -0,0 +1,172 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_H
+#define RRSET_COLLECTION_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+
+#include <boost/tuple/tuple.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+
+#include <map>
+
+namespace isc {
+namespace dns {
+
+/// \brief libdns++ implementation of RRsetCollectionBase using an STL
+/// container.
+class RRsetCollection : public RRsetCollectionBase {
+public:
+    /// \brief Constructor.
+    ///
+    /// This constructor creates an empty collection without any data in
+    /// it. RRsets can be added to the collection with the \c addRRset()
+    /// method.
+    RRsetCollection() {}
+
+    /// \brief Constructor.
+    ///
+    /// The \c origin and \c rrclass arguments are required for the zone
+    /// loading, but \c RRsetCollection itself does not do any
+    /// validation, and the collection of RRsets does not have to form a
+    /// valid zone. The constructor throws MasterLoaderError if there is
+    /// an error during loading.
+    ///
+    /// \param filename Name of a file containing a collection of RRs in
+    /// the master file format (which may or may not form a valid zone).
+    /// \param origin The zone origin.
+    /// \param rrclass The zone class.
+    RRsetCollection(const char* filename, const isc::dns::Name& origin,
+                    const isc::dns::RRClass& rrclass);
+
+    /// \brief Constructor.
+    ///
+    /// This constructor is similar to the previous one, but instead of
+    /// taking a filename to load a zone from, it takes an input
+    /// stream. The constructor throws MasterLoaderError if there is an
+    /// error during loading.
+    ///
+    /// \param input_stream The input stream to load from.
+    /// \param origin The zone origin.
+    /// \param rrclass The zone class.
+    RRsetCollection(std::istream& input_stream, const isc::dns::Name& origin,
+                    const isc::dns::RRClass& rrclass);
+
+    /// \brief Destructor
+    virtual ~RRsetCollection() {}
+
+    /// \brief Add an RRset to the collection.
+    ///
+    /// Does not do any validation whether \c rrset belongs to a
+    /// particular zone or not. A reference to \c rrset is taken in an
+    /// internally managed \c shared_ptr, so even if the caller's
+    /// \c RRsetPtr is destroyed, the RRset it wrapped is still alive
+    /// and managed by the \c RRsetCollection. It throws an
+    /// \c isc::InvalidParameter exception if an rrset with the same
+    /// class, type and name already exists.
+    ///
+    /// Callers must not modify the RRset after adding it to the
+    /// collection, as the rrset is indexed internally by the
+    /// collection.
+    void addRRset(isc::dns::RRsetPtr rrset);
+
+    /// \brief Remove an RRset from the collection.
+    ///
+    /// RRset(s) matching the \c name, \c rrclass and \c rrtype are
+    /// removed from the collection.
+    ///
+    /// \returns \c true if a matching RRset was deleted, \c false if no
+    /// such RRset exists.
+    bool removeRRset(const isc::dns::Name& name,
+                     const isc::dns::RRClass& rrclass,
+                     const isc::dns::RRType& rrtype);
+
+    /// \brief Find a matching RRset in the collection.
+    ///
+    /// Returns the RRset in the collection that exactly matches the
+    /// given \c name, \c rrclass and \c rrtype.  If no matching RRset
+    /// is found, \c NULL is returned.
+    ///
+    /// \param name The name of the RRset to search for.
+    /// \param rrclass The class of the RRset to search for.
+    /// \param rrtype The type of the RRset to search for.
+    /// \returns The RRset if found, \c NULL otherwise.
+    virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+                                         const isc::dns::RRClass& rrclass,
+                                         const isc::dns::RRType& rrtype) const;
+
+    /// \brief Find a matching RRset in the collection (non-const
+    /// variant).
+    ///
+    /// See above for a description of the method and arguments.
+    isc::dns::RRsetPtr find(const isc::dns::Name& name,
+                            const isc::dns::RRClass& rrclass,
+                            const isc::dns::RRType& rrtype);
+
+private:
+    template<typename T>
+    void constructHelper(T source, const isc::dns::Name& origin,
+                         const isc::dns::RRClass& rrclass);
+    void loaderCallback(const std::string&, size_t, const std::string&);
+
+    typedef boost::tuple<isc::dns::RRClass, isc::dns::RRType, isc::dns::Name>
+        CollectionKey;
+    typedef std::map<CollectionKey, isc::dns::RRsetPtr> CollectionMap;
+
+    CollectionMap rrsets_;
+
+protected:
+    class DnsIter : public RRsetCollectionBase::Iter {
+    public:
+        DnsIter(CollectionMap::iterator& iter) :
+            iter_(iter)
+        {}
+
+        virtual const isc::dns::AbstractRRset& getValue() {
+            isc::dns::RRsetPtr& rrset = iter_->second;
+            return (*rrset);
+        }
+
+        virtual IterPtr getNext() {
+            CollectionMap::iterator it = iter_;
+            it++;
+            return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+        }
+
+        virtual bool equals(Iter& other) {
+            const DnsIter* other_real = dynamic_cast<DnsIter*>(&other);
+            if (other_real == NULL) {
+                return (false);
+            }
+            return (iter_ == other_real->iter_);
+        }
+
+    private:
+        CollectionMap::iterator iter_;
+    };
+
+    virtual RRsetCollectionBase::IterPtr getBeginning();
+    virtual RRsetCollectionBase::IterPtr getEnd();
+};
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif  // RRSET_COLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 163 - 0
src/lib/dns/rrset_collection_base.h

@@ -0,0 +1,163 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_BASE_H
+#define RRSET_COLLECTION_BASE_H 1
+
+#include <dns/rrset.h>
+#include <dns/name.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <iterator>
+
+namespace isc {
+namespace dns {
+
+/// \brief Generic class to represent a set of RRsets.
+///
+/// This is a generic container and the stored set of RRsets does not
+/// necessarily form a valid zone (e.g. there doesn't necessarily have
+/// to be an SOA at the "origin"). Instead, it will be used to represent
+/// a single zone for the purpose of zone loading/checking. It provides
+/// a simple find() method to find an RRset for the given name and type
+/// (and maybe class) and a way to iterate over all RRsets.
+///
+/// See \c RRsetCollection for a simple libdns++ implementation using an
+/// STL container. libdatasrc will have another implementation.
+class RRsetCollectionBase {
+public:
+    /// \brief Find a matching RRset in the collection.
+    ///
+    /// Returns the RRset in the collection that exactly matches the
+    /// given \c name, \c rrclass and \c rrtype.  If no matching RRset
+    /// is found, \c NULL is returned.
+    ///
+    /// \param name The name of the RRset to search for.
+    /// \param rrtype The type of the RRset to search for.
+    /// \param rrclass The class of the RRset to search for.
+    /// \returns The RRset if found, \c NULL otherwise.
+    virtual isc::dns::ConstRRsetPtr find
+        (const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+         const isc::dns::RRType& rrtype)
+        const = 0;
+
+    /// \brief Destructor
+    virtual ~RRsetCollectionBase() {}
+
+protected:
+    class Iter; // forward declaration
+
+    /// \brief Wraps Iter with a reference count.
+    typedef boost::shared_ptr<Iter> IterPtr;
+
+    /// \brief A helper iterator interface for \c RRsetCollectionBase.
+    ///
+    /// This is a protected iterator class that is a helper interface
+    /// used by the public iterator.  Derived classes of
+    /// \c RRsetCollectionBase are supposed to implement this class and
+    /// the \c getBeginning() and \c getEnd() methods, so that the
+    /// public interator interface can be provided. This is a forward
+    /// iterator only.
+    class Iter {
+    public:
+        /// \brief Returns the \c AbstractRRset currently pointed to by
+        /// the iterator.
+        virtual const isc::dns::AbstractRRset& getValue() = 0;
+
+        /// \brief Returns an \c IterPtr wrapping an Iter pointing to
+        /// the next \c AbstractRRset in sequence in the collection.
+        virtual IterPtr getNext() = 0;
+
+        /// \brief Check if another iterator is equal to this one.
+        ///
+        /// Returns \c true if this iterator is equal to \c other,
+        /// \c false otherwise. Note that if \c other is not the same
+        /// type as \c this, or cannot be compared meaningfully, the
+        /// method must return \c false.
+        ///
+        /// \param other The other iterator to compare against.
+        /// \returns \c true if equal, \c false otherwise.
+        virtual bool equals(Iter& other) = 0;
+    };
+
+    /// \brief Returns an \c IterPtr wrapping an Iter pointing to the
+    /// beginning of the collection.
+    virtual IterPtr getBeginning() = 0;
+
+    /// \brief Returns an \c IterPtr wrapping an Iter pointing past the
+    /// end of the collection.
+    virtual IterPtr getEnd() = 0;
+
+public:
+    /// \brief A forward \c std::iterator for \c RRsetCollectionBase.
+    ///
+    /// It behaves like a \c std::iterator forward iterator, so please
+    /// see its documentation for usage.
+    class Iterator : std::iterator<std::forward_iterator_tag,
+                                   const isc::dns::AbstractRRset>
+    {
+    public:
+        explicit Iterator(IterPtr iter) :
+            iter_(iter)
+        {}
+
+        reference operator*() {
+            return (iter_->getValue());
+        }
+
+        Iterator& operator++() {
+            iter_ = iter_->getNext();
+            return (*this);
+        }
+
+        Iterator operator++(int) {
+            Iterator tmp(iter_);
+            ++*this;
+            return (tmp);
+        }
+
+        bool operator==(const Iterator& other) const {
+            return (iter_->equals(*other.iter_));
+        }
+
+        bool operator!=(const Iterator& other) const {
+            return (!iter_->equals(*other.iter_));
+        }
+
+    private:
+        IterPtr iter_;
+    };
+
+    /// \brief Returns an iterator pointing to the beginning of the
+    /// collection.
+    Iterator begin() {
+      return Iterator(getBeginning());
+    }
+
+    /// \brief Returns an iterator pointing past the end of the
+    /// collection.
+    Iterator end() {
+      return Iterator(getEnd());
+    }
+};
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif  // RRSET_COLLECTION_BASE_H
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -75,6 +75,7 @@ run_unittests_SOURCES += tsigkey_unittest.cc
 run_unittests_SOURCES += tsigrecord_unittest.cc
 run_unittests_SOURCES += character_string_unittest.cc
 run_unittests_SOURCES += master_loader_callbacks_test.cc
+run_unittests_SOURCES += rrset_collection_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 # We shouldn't need to include BOTAN_LIBS here, but there

+ 249 - 0
src/lib/dns/tests/rrset_collection_unittest.cc

@@ -0,0 +1,249 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset_collection.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+#include <gtest/gtest.h>
+
+#include <list>
+#include <fstream>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace std;
+
+namespace {
+
+class RRsetCollectionTest : public ::testing::Test {
+public:
+    RRsetCollectionTest() :
+        rrclass("IN"),
+        origin("example.org"),
+        collection(TEST_DATA_SRCDIR "/example.org", origin, rrclass)
+    {}
+
+    const RRClass rrclass;
+    const Name origin;
+    RRsetCollection collection;
+};
+
+TEST_F(RRsetCollectionTest, istreamConstructor) {
+    std::ifstream fs(TEST_DATA_SRCDIR "/example.org");
+    RRsetCollection collection2(fs, origin, rrclass);
+
+    RRsetCollectionBase::Iterator iter = collection.begin();
+    RRsetCollectionBase::Iterator iter2 = collection2.begin();
+    while (iter != collection.end()) {
+         EXPECT_TRUE(iter2 != collection2.end());
+         EXPECT_EQ((*iter).toText(), (*iter2).toText());
+         ++iter;
+         ++iter2;
+    }
+    EXPECT_TRUE(iter2 == collection2.end());
+}
+
+template <typename T, typename TP>
+void doFind(T& collection, const RRClass& rrclass) {
+    // Test the find() that returns ConstRRsetPtr
+    TP rrset = collection.find(Name("www.example.org"), rrclass, RRType::A());
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+    EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+    EXPECT_EQ(RRClass("IN"), rrset->getClass());
+    EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+    // foo.example.org doesn't exist
+    rrset = collection.find(Name("foo.example.org"), rrclass, RRType::A());
+    EXPECT_FALSE(rrset);
+
+    // www.example.org exists, but not with MX
+    rrset = collection.find(Name("www.example.org"), rrclass, RRType::MX());
+    EXPECT_FALSE(rrset);
+
+    // www.example.org exists, with AAAA
+    rrset = collection.find(Name("www.example.org"), rrclass, RRType::AAAA());
+    EXPECT_TRUE(rrset);
+
+    // www.example.org with AAAA does not exist in RRClass::CH()
+    rrset = collection.find(Name("www.example.org"), RRClass::CH(),
+                            RRType::AAAA());
+    EXPECT_FALSE(rrset);
+}
+
+TEST_F(RRsetCollectionTest, findConst) {
+    // Test the find() that returns ConstRRsetPtr
+    const RRsetCollection& ccln = collection;
+    doFind<const RRsetCollection, ConstRRsetPtr>(ccln, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, find) {
+    // Test the find() that returns RRsetPtr
+    doFind<RRsetCollection, RRsetPtr>(collection, rrclass);
+}
+
+void
+doAddAndRemove(RRsetCollection& collection, const RRClass& rrclass) {
+    // foo.example.org/A doesn't exist
+    RRsetPtr rrset_found = collection.find(Name("foo.example.org"), rrclass,
+                                           RRType::A());
+    EXPECT_FALSE(rrset_found);
+
+    // Add foo.example.org/A
+    RRsetPtr rrset(new BasicRRset(Name("foo.example.org"), rrclass, RRType::A(),
+                                  RRTTL(7200)));
+    rrset->addRdata(in::A("192.0.2.1"));
+    collection.addRRset(rrset);
+
+    // foo.example.org/A should now exist
+    rrset_found = collection.find(Name("foo.example.org"), rrclass,
+                                  RRType::A());
+    EXPECT_TRUE(rrset_found);
+    EXPECT_EQ(RRType::A(), rrset_found->getType());
+    EXPECT_EQ(RRTTL(7200), rrset_found->getTTL());
+    EXPECT_EQ(RRClass("IN"), rrset_found->getClass());
+    EXPECT_EQ(Name("foo.example.org"), rrset_found->getName());
+
+    // The collection must not be empty.
+    EXPECT_TRUE(collection.end() != collection.begin());
+
+    // Adding a duplicate RRset must throw.
+    EXPECT_THROW({
+        collection.addRRset(rrset);
+    }, isc::InvalidParameter);
+
+    // Remove foo.example.org/A, which should pass
+    bool exists = collection.removeRRset(Name("foo.example.org"),
+                                         rrclass, RRType::A());
+    EXPECT_TRUE(exists);
+
+    // foo.example.org/A should not exist now
+    rrset_found = collection.find(Name("foo.example.org"), rrclass,
+                                  RRType::A());
+    EXPECT_FALSE(rrset_found);
+
+    // Removing foo.example.org/A should fail now
+    exists = collection.removeRRset(Name("foo.example.org"),
+                                    rrclass, RRType::A());
+    EXPECT_FALSE(exists);
+}
+
+TEST_F(RRsetCollectionTest, addAndRemove) {
+    doAddAndRemove(collection, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, empty) {
+    RRsetCollection cln;
+
+    // Here, cln is empty.
+    EXPECT_TRUE(cln.end() == cln.begin());
+
+    doAddAndRemove(cln, rrclass);
+
+    // cln should be empty again here, after the add and remove
+    // operations.
+    EXPECT_TRUE(cln.end() == cln.begin());
+}
+
+TEST_F(RRsetCollectionTest, iteratorTest) {
+    // The collection must not be empty.
+    EXPECT_TRUE(collection.end() != collection.begin());
+
+    // Here, we just count the records and do some basic tests on them.
+    size_t count = 0;
+    for (RRsetCollection::Iterator it = collection.begin();
+         it != collection.end(); ++it) {
+         ++count;
+         const AbstractRRset& rrset = *it;
+         EXPECT_EQ(rrclass, rrset.getClass());
+         EXPECT_EQ(RRTTL(3600), rrset.getTTL());
+    }
+
+    // example.org master file has SOA, NS, A, AAAA
+    EXPECT_EQ(4, count);
+}
+
+// This is a dummy class which is used in iteratorCompareDifferent test
+// to compare iterators from different RRsetCollectionBase
+// implementations.
+class MyRRsetCollection : public RRsetCollectionBase {
+public:
+    MyRRsetCollection()
+    {}
+
+    virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name&,
+                                         const isc::dns::RRClass&,
+                                         const isc::dns::RRType&) const {
+        return (ConstRRsetPtr());
+    }
+
+    typedef std::list<isc::dns::RRset> MyCollection;
+
+protected:
+    class MyIter : public RRsetCollectionBase::Iter {
+    public:
+        MyIter(MyCollection::iterator& iter) :
+            iter_(iter)
+        {}
+
+        virtual const isc::dns::AbstractRRset& getValue() {
+            return (*iter_);
+        }
+
+        virtual IterPtr getNext() {
+            MyCollection::iterator it = iter_;
+            it++;
+            return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+        }
+
+        virtual bool equals(Iter& other) {
+            const MyIter* other_real = dynamic_cast<MyIter*>(&other);
+            if (other_real == NULL) {
+                return (false);
+            }
+            return (iter_ == other_real->iter_);
+        }
+
+    private:
+        MyCollection::iterator iter_;
+    };
+
+    virtual RRsetCollectionBase::IterPtr getBeginning() {
+        MyCollection::iterator it = dummy_list_.begin();
+        return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+    }
+
+    virtual RRsetCollectionBase::IterPtr getEnd() {
+        MyCollection::iterator it = dummy_list_.end();
+        return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+    }
+
+private:
+    MyCollection dummy_list_;
+};
+
+TEST_F(RRsetCollectionTest, iteratorCompareDifferent) {
+    // Create objects of two different RRsetCollectionBase
+    // implementations.
+    RRsetCollection cln1;
+    MyRRsetCollection cln2;
+
+    // Comparing two iterators from different RRsetCollectionBase
+    // implementations must not throw.
+    EXPECT_TRUE(cln2.begin() != cln1.begin());
+    EXPECT_TRUE(cln1.end() != cln2.end());
+}
+
+} // namespace