Browse Source

[master] Merge branch 'trac1179'

Conflicts:
	src/lib/python/isc/Makefile.am
	src/lib/python/isc/datasrc/tests/Makefile.am
Jelte Jansen 13 years ago
parent
commit
7d2b014816

+ 1 - 0
configure.ac

@@ -845,6 +845,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/util/tests/Makefile
                  src/lib/python/isc/datasrc/Makefile
                  src/lib/python/isc/datasrc/tests/Makefile
+                 src/lib/python/isc/dns/Makefile
                  src/lib/python/isc/cc/Makefile
                  src/lib/python/isc/cc/tests/Makefile
                  src/lib/python/isc/config/Makefile

+ 3 - 3
src/lib/Makefile.am

@@ -1,3 +1,3 @@
-SUBDIRS = exceptions util log cryptolink dns cc config acl python xfr \
-          bench asiolink asiodns nsas cache resolve testutils datasrc \
-          server_common
+SUBDIRS = exceptions util log cryptolink dns cc config acl xfr bench \
+          asiolink asiodns nsas cache resolve testutils datasrc \
+          server_common python

+ 1 - 1
src/lib/python/isc/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = datasrc cc config log net notify util testutils acl bind10
+SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
 SUBDIRS += log_messages
 
 python_PYTHON = __init__.py

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

@@ -1,10 +1,39 @@
 SUBDIRS = . tests
 
+# old data, should be removed in the near future once conversion is done
 python_PYTHON = __init__.py master.py sqlite3_ds.py
 
-pythondir = $(pyexecdir)/isc/datasrc
+#pythondir = $(pyexecdir)/isc/pydatasrc
+
+# new data
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(SQLITE_CFLAGS)
+
+pythondir = $(pyexecdir)/isc
+pyexec_LTLIBRARIES = datasrc.la
+datasrc_la_SOURCES = datasrc.cc datasrc.h
+datasrc_la_SOURCES += client_python.cc client_python.h
+datasrc_la_SOURCES += iterator_python.cc iterator_python.h
+datasrc_la_SOURCES += finder_python.cc finder_python.h
+datasrc_la_SOURCES += updater_python.cc updater_python.h
+
+datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
+datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
+datasrc_la_LDFLAGS = $(PYTHON_LDFLAGS)
+datasrc_la_LDFLAGS += -module
+datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
+datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la
+datasrc_la_LIBADD += $(PYTHON_LIB)
+datasrc_la_LIBADD += $(SQLITE_LIBS)
+
+EXTRA_DIST = client_inc.cc
+EXTRA_DIST += finder_inc.cc
+EXTRA_DIST += iterator_inc.cc
+EXTRA_DIST += updater_inc.cc
 
 CLEANDIRS = __pycache__
 
 clean-local:
 	rm -rf $(CLEANDIRS)
+

+ 7 - 0
src/lib/python/isc/datasrc/__init__.py

@@ -1,2 +1,9 @@
 from isc.datasrc.master import *
 from isc.datasrc.sqlite3_ds import *
+
+for base in sys.path[:]:
+    datasrc_libdir = os.path.join(base, 'isc/datasrc/.libs')
+    if os.path.exists(datasrc_libdir):
+        sys.path.insert(0, datasrc_libdir)
+
+from datasrc import *

+ 157 - 0
src/lib/python/isc/datasrc/client_inc.cc

@@ -0,0 +1,157 @@
+namespace {
+
+const char* const DataSourceClient_doc = "\
+The base class of data source clients.\n\
+\n\
+This is the python wrapper for the abstract base class that defines\n\
+the common interface for various types of data source clients. A data\n\
+source client is a top level access point to a data source, allowing \n\
+various operations on the data source such as lookups, traversing or \n\
+updates. The client class itself has limited focus and delegates \n\
+the responsibility for these specific operations to other (c++) classes;\n\
+in general methods of this class act as factories of these other classes.\n\
+\n\
+- InMemoryClient: A client of a conceptual data source that stores all\n\
+  necessary data in memory for faster lookups\n\
+- DatabaseClient: A client that uses a real database backend (such as\n\
+  an SQL database). It would internally hold a connection to the\n\
+  underlying database system.\n\
+\n\
+It is intentional that while the term these derived classes don't\n\
+contain \"DataSource\" unlike their base class. It's also noteworthy\n\
+that the naming of the base class is somewhat redundant because the\n\
+namespace datasrc would indicate that it's related to a data source.\n\
+The redundant naming comes from the observation that namespaces are\n\
+often omitted with using directives, in which case \"Client\" would be\n\
+too generic. On the other hand, concrete derived classes are generally\n\
+not expected to be referenced directly from other modules and\n\
+applications, so we'll give them more concise names such as\n\
+InMemoryClient. A single DataSourceClient object is expected to handle\n\
+only a single RR class even if the underlying data source contains\n\
+records for multiple RR classes. Likewise, (when we support views) a\n\
+DataSourceClient object is expected to handle only a single view.\n\
+\n\
+If the application uses multiple threads, each thread will need to\n\
+create and use a separate DataSourceClient. This is because some\n\
+database backend doesn't allow multiple threads to share the same\n\
+connection to the database.\n\
+\n\
+For a client using an in memory backend, this may result in having a\n\
+multiple copies of the same data in memory, increasing the memory\n\
+footprint substantially. Depending on how to support multiple CPU\n\
+cores for concurrent lookups on the same single data source (which is\n\
+not fully fixed yet, and for which multiple threads may be used), this\n\
+design may have to be revisited. This class (and therefore its derived\n\
+classes) are not copyable. This is because the derived classes would\n\
+generally contain attributes that are not easy to copy (such as a\n\
+large size of in memory data or a network connection to a database\n\
+server). In order to avoid a surprising disruption with a naive copy\n\
+it's prohibited explicitly. For the expected usage of the client\n\
+classes the restriction should be acceptable.\n\
+\n\
+Todo: This class is still not complete. It will need more factory\n\
+methods, e.g. for (re)loading a zone.\n\
+";
+
+const char* const DataSourceClient_findZone_doc = "\
+find_zone(name) -> (code, ZoneFinder)\n\
+\n\
+Returns a ZoneFinder for a zone that best matches the given name.\n\
+\n\
+code: The result code of the operation (integer).\n\
+- DataSourceClient.SUCCESS: A zone that gives an exact match is found\n\
+- DataSourceClient.PARTIALMATCH: A zone whose origin is a super domain of name\n\
+  is found (but there is no exact match)\n\
+- DataSourceClient.NOTFOUND: For all other cases.\n\
+ZoneFinder: ZoneFinder object for the found zone if one is found;\n\
+otherwise None.\n\
+\n\
+Any internal error will be raised as an isc.datasrc.Error exception\n\
+\n\
+Parameters:\n\
+  name       A domain name for which the search is performed.\n\
+\n\
+Return Value(s): A tuple containing a result value and a ZoneFinder object or\n\
+None\n\
+";
+
+const char* const DataSourceClient_getIterator_doc = "\
+get_iterator(name) -> ZoneIterator\n\
+\n\
+Returns an iterator to the given zone.\n\
+\n\
+This allows for traversing the whole zone. The returned object can\n\
+provide the RRsets one by one.\n\
+\n\
+This throws isc.datasrc.Error when the zone does not exist in the\n\
+datasource, or when an internal error occurs.\n\
+\n\
+The default implementation throws isc.datasrc.NotImplemented. This allows for\n\
+easy and fast deployment of minimal custom data sources, where the\n\
+user/implementator doesn't have to care about anything else but the\n\
+actual queries. Also, in some cases, it isn't possible to traverse the\n\
+zone from logic point of view (eg. dynamically generated zone data).\n\
+\n\
+It is not fixed if a concrete implementation of this method can throw\n\
+anything else.\n\
+\n\
+Parameters:\n\
+  isc.dns.Name The name of zone apex to be traversed. It doesn't do\n\
+               nearest match as find_zone.\n\
+\n\
+Return Value(s): Pointer to the iterator.\n\
+";
+
+const char* const DataSourceClient_getUpdater_doc = "\
+get_updater(name, replace) -> ZoneUpdater\n\
+\n\
+Return an updater to make updates to a specific zone.\n\
+\n\
+The RR class of the zone is the one that the client is expected to\n\
+handle (see the detailed description of this class).\n\
+\n\
+If the specified zone is not found via the client, a NULL pointer will\n\
+be returned; in other words a completely new zone cannot be created\n\
+using an updater. It must be created beforehand (even if it's an empty\n\
+placeholder) in a way specific to the underlying data source.\n\
+\n\
+Conceptually, the updater will trigger a separate transaction for\n\
+subsequent updates to the zone within the context of the updater (the\n\
+actual implementation of the \"transaction\" may vary for the specific\n\
+underlying data source). Until commit() is performed on the updater,\n\
+the intermediate updates won't affect the results of other methods\n\
+(and the result of the object's methods created by other factory\n\
+methods). Likewise, if the updater is destructed without performing\n\
+commit(), the intermediate updates will be effectively canceled and\n\
+will never affect other methods.\n\
+\n\
+If the underlying data source allows concurrent updates, this method\n\
+can be called multiple times while the previously returned updater(s)\n\
+are still active. In this case each updater triggers a different\n\
+\"transaction\". Normally it would be for different zones for such a\n\
+case as handling multiple incoming AXFR streams concurrently, but this\n\
+interface does not even prohibit an attempt of getting more than one\n\
+updater for the same zone, as long as the underlying data source\n\
+allows such an operation (and any conflict resolution is left to the\n\
+specific implementation).\n\
+\n\
+If replace is true, any existing RRs of the zone will be deleted on\n\
+successful completion of updates (after commit() on the updater); if\n\
+it's false, the existing RRs will be intact unless explicitly deleted\n\
+by delete_rrset() on the updater.\n\
+\n\
+A data source can be \"read only\" or can prohibit partial updates. In\n\
+such cases this method will result in an isc.datasrc.NotImplemented exception\n\
+unconditionally or when replace is false).\n\
+\n\
+Exceptions:\n\
+  isc.datasrc. NotImplemented The underlying data source does not support\n\
+               updates.\n\
+  isc.datasrc.Error Internal error in the underlying data source.\n\
+\n\
+Parameters:\n\
+  name       The zone name to be updated\n\
+  replace    Whether to delete existing RRs before making updates\n\
+\n\
+";
+} // unnamed namespace

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

@@ -0,0 +1,264 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <datasrc/client.h>
+#include <datasrc/database.h>
+#include <datasrc/data_source.h>
+#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/iterator.h>
+
+#include <dns/python/name_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include "datasrc.h"
+#include "client_python.h"
+#include "finder_python.h"
+#include "iterator_python.h"
+#include "updater_python.h"
+#include "client_inc.cc"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_DataSourceClient : public PyObject {
+public:
+    s_DataSourceClient() : cppobj(NULL) {};
+    DataSourceClient* cppobj;
+};
+
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_DataSourceClient, DataSourceClient>
+    DataSourceClientContainer;
+
+PyObject*
+DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
+    s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
+    PyObject *name;
+    if (PyArg_ParseTuple(args, "O!", &name_type, &name)) {
+        try {
+            DataSourceClient::FindResult find_result(
+                self->cppobj->findZone(PyName_ToName(name)));
+
+            result::Result r = find_result.code;
+            ZoneFinderPtr zfp = find_result.zone_finder;
+            // Use N instead of O so refcount isn't increased twice
+            return (Py_BuildValue("IN", r, createZoneFinderObject(zfp)));
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        } catch (...) {
+            PyErr_SetString(getDataSourceException("Error"),
+                            "Unexpected exception");
+            return (NULL);
+        }
+    } else {
+        return (NULL);
+    }
+}
+
+PyObject*
+DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
+    s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
+    PyObject *name_obj;
+    if (PyArg_ParseTuple(args, "O!", &name_type, &name_obj)) {
+        try {
+            return (createZoneIteratorObject(
+                        self->cppobj->getIterator(PyName_ToName(name_obj))));
+        } catch (const isc::NotImplemented& ne) {
+            PyErr_SetString(getDataSourceException("NotImplemented"),
+                            ne.what());
+            return (NULL);
+        } catch (const DataSourceError& dse) {
+            PyErr_SetString(getDataSourceException("Error"), dse.what());
+            return (NULL);
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        } catch (...) {
+            PyErr_SetString(getDataSourceException("Error"),
+                            "Unexpected exception");
+            return (NULL);
+        }
+    } else {
+        return (NULL);
+    }
+}
+
+PyObject*
+DataSourceClient_getUpdater(PyObject* po_self, PyObject* args) {
+    s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
+    PyObject *name_obj;
+    PyObject *replace_obj;
+    if (PyArg_ParseTuple(args, "O!O", &name_type, &name_obj, &replace_obj) &&
+        PyBool_Check(replace_obj)) {
+        bool replace = (replace_obj != Py_False);
+        try {
+            return (createZoneUpdaterObject(
+                        self->cppobj->getUpdater(PyName_ToName(name_obj),
+                                                 replace)));
+        } catch (const isc::NotImplemented& ne) {
+            PyErr_SetString(getDataSourceException("NotImplemented"),
+                            ne.what());
+            return (NULL);
+        } catch (const DataSourceError& dse) {
+            PyErr_SetString(getDataSourceException("Error"), dse.what());
+            return (NULL);
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        } catch (...) {
+            PyErr_SetString(getDataSourceException("Error"),
+                            "Unexpected exception");
+            return (NULL);
+        }
+    } else {
+        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 DataSourceClient_methods[] = {
+    { "find_zone", reinterpret_cast<PyCFunction>(DataSourceClient_findZone),
+      METH_VARARGS, DataSourceClient_findZone_doc },
+    { "get_iterator",
+      reinterpret_cast<PyCFunction>(DataSourceClient_getIterator), METH_VARARGS,
+      DataSourceClient_getIterator_doc },
+    { "get_updater", reinterpret_cast<PyCFunction>(DataSourceClient_getUpdater),
+      METH_VARARGS, DataSourceClient_getUpdater_doc },
+    { NULL, NULL, 0, NULL }
+};
+
+int
+DataSourceClient_init(s_DataSourceClient* self, PyObject* args) {
+    // TODO: we should use the factory function which hasn't been written
+    // yet. For now we hardcode the sqlite3 initialization, and pass it one
+    // string for the database file. (similar to how the 'old direct'
+    // sqlite3_ds code works)
+    try {
+        char* db_file_name;
+        if (PyArg_ParseTuple(args, "s", &db_file_name)) {
+            boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
+                new SQLite3Accessor(db_file_name, isc::dns::RRClass::IN()));
+            self->cppobj = new DatabaseClient(isc::dns::RRClass::IN(),
+                                              sqlite3_accessor);
+            return (0);
+        } else {
+            return (-1);
+        }
+
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct DataSourceClient object: " +
+            string(ex.what());
+        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(PyExc_RuntimeError,
+            "Unexpected exception in constructing DataSourceClient");
+        return (-1);
+    }
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to DataSourceClient constructor");
+
+    return (-1);
+}
+
+void
+DataSourceClient_destroy(s_DataSourceClient* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+} // end anonymous namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_DataSourceClient
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject datasourceclient_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datasrc.DataSourceClient",
+    sizeof(s_DataSourceClient),         // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(DataSourceClient_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
+    DataSourceClient_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    DataSourceClient_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
+    reinterpret_cast<initproc>(DataSourceClient_init),// tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc

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

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

+ 225 - 0
src/lib/python/isc/datasrc/datasrc.cc

@@ -0,0 +1,225 @@
+// Copyright (C) 2011  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.
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <structmember.h>
+
+#include <config.h>
+
+#include <datasrc/client.h>
+#include <datasrc/database.h>
+#include <datasrc/sqlite3_accessor.h>
+
+#include "datasrc.h"
+#include "client_python.h"
+#include "finder_python.h"
+#include "iterator_python.h"
+#include "updater_python.h"
+
+#include <util/python/pycppwrapper_util.h>
+#include <dns/python/pydnspp_common.h>
+
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+using namespace isc::util::python;
+using namespace isc::dns::python;
+
+namespace isc {
+namespace datasrc {
+namespace python {
+PyObject*
+getDataSourceException(const char* ex_name) {
+    PyObject* ex_obj = NULL;
+
+    PyObject* datasrc_module = PyImport_AddModule("isc.datasrc");
+    if (datasrc_module != NULL) {
+        PyObject* datasrc_dict = PyModule_GetDict(datasrc_module);
+        if (datasrc_dict != NULL) {
+            ex_obj = PyDict_GetItemString(datasrc_dict, ex_name);
+        }
+    }
+
+    if (ex_obj == NULL) {
+        ex_obj = PyExc_RuntimeError;
+    }
+    return (ex_obj);
+}
+
+} // end namespace python
+} // end namespace datasrc
+} // end namespace isc
+
+namespace {
+
+bool
+initModulePart_DataSourceClient(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(&datasourceclient_type) < 0) {
+        return (false);
+    }
+    void* dscp = &datasourceclient_type;
+    if (PyModule_AddObject(mod, "DataSourceClient", static_cast<PyObject*>(dscp)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&datasourceclient_type);
+
+    addClassVariable(datasourceclient_type, "SUCCESS",
+                     Py_BuildValue("I", result::SUCCESS));
+    addClassVariable(datasourceclient_type, "EXIST",
+                     Py_BuildValue("I", result::EXIST));
+    addClassVariable(datasourceclient_type, "NOTFOUND",
+                     Py_BuildValue("I", result::NOTFOUND));
+    addClassVariable(datasourceclient_type, "PARTIALMATCH",
+                     Py_BuildValue("I", result::PARTIALMATCH));
+
+    return (true);
+}
+
+bool
+initModulePart_ZoneFinder(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(&zonefinder_type) < 0) {
+        return (false);
+    }
+    void* zip = &zonefinder_type;
+    if (PyModule_AddObject(mod, "ZoneFinder", static_cast<PyObject*>(zip)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&zonefinder_type);
+
+    addClassVariable(zonefinder_type, "SUCCESS",
+                     Py_BuildValue("I", ZoneFinder::SUCCESS));
+    addClassVariable(zonefinder_type, "DELEGATION",
+                     Py_BuildValue("I", ZoneFinder::DELEGATION));
+    addClassVariable(zonefinder_type, "NXDOMAIN",
+                     Py_BuildValue("I", ZoneFinder::NXDOMAIN));
+    addClassVariable(zonefinder_type, "NXRRSET",
+                     Py_BuildValue("I", ZoneFinder::NXRRSET));
+    addClassVariable(zonefinder_type, "CNAME",
+                     Py_BuildValue("I", ZoneFinder::CNAME));
+    addClassVariable(zonefinder_type, "DNAME",
+                     Py_BuildValue("I", ZoneFinder::DNAME));
+
+    addClassVariable(zonefinder_type, "FIND_DEFAULT",
+                     Py_BuildValue("I", ZoneFinder::FIND_DEFAULT));
+    addClassVariable(zonefinder_type, "FIND_GLUE_OK",
+                     Py_BuildValue("I", ZoneFinder::FIND_GLUE_OK));
+    addClassVariable(zonefinder_type, "FIND_DNSSEC",
+                     Py_BuildValue("I", ZoneFinder::FIND_DNSSEC));
+
+
+    return (true);
+}
+
+bool
+initModulePart_ZoneIterator(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(&zoneiterator_type) < 0) {
+        return (false);
+    }
+    void* zip = &zoneiterator_type;
+    if (PyModule_AddObject(mod, "ZoneIterator", static_cast<PyObject*>(zip)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&zoneiterator_type);
+
+    return (true);
+}
+
+bool
+initModulePart_ZoneUpdater(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&zoneupdater_type) < 0) {
+        return (false);
+    }
+    void* zip = &zoneupdater_type;
+    if (PyModule_AddObject(mod, "ZoneUpdater", static_cast<PyObject*>(zip)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&zoneupdater_type);
+
+    return (true);
+}
+
+
+PyObject* po_DataSourceError;
+PyObject* po_NotImplemented;
+
+PyModuleDef iscDataSrc = {
+    { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
+    "datasrc",
+    "Python bindings for the classes in the isc::datasrc namespace.\n\n"
+    "These bindings are close match to the C++ API, but they are not complete "
+    "(some parts are not needed) and some are done in more python-like ways.",
+    -1,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL
+};
+
+} // end anonymous namespace
+
+PyMODINIT_FUNC
+PyInit_datasrc(void) {
+    PyObject* mod = PyModule_Create(&iscDataSrc);
+    if (mod == NULL) {
+        return (NULL);
+    }
+
+    if (!initModulePart_DataSourceClient(mod)) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
+    if (!initModulePart_ZoneFinder(mod)) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
+    if (!initModulePart_ZoneIterator(mod)) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
+    if (!initModulePart_ZoneUpdater(mod)) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
+    try {
+        po_DataSourceError = PyErr_NewException("isc.datasrc.Error", NULL,
+                                                NULL);
+        PyObjectContainer(po_DataSourceError).installToModule(mod, "Error");
+        po_NotImplemented = PyErr_NewException("isc.datasrc.NotImplemented",
+                                               NULL, NULL);
+        PyObjectContainer(po_NotImplemented).installToModule(mod,
+                                                             "NotImplemented");
+    } catch (...) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
+    return (mod);
+}

+ 50 - 0
src/lib/python/isc/datasrc/datasrc.h

@@ -0,0 +1,50 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_DATASRC_H
+#define __PYTHON_DATASRC_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace datasrc {
+namespace python {
+
+// Return a Python exception object of the given name (ex_name) defined in
+// the isc.datasrc.datasrc loadable module.
+//
+// Since the datasrc module is a different binary image and is loaded separately
+// from the dns module, it would be very tricky to directly access to
+// C/C++ symbols defined in that module.  So we get access to these object
+// using the Python interpretor through this wrapper function.
+//
+// The __init__.py file should ensure isc.datasrc has been loaded by the time
+// whenever this function is called, and there shouldn't be any operation
+// within this function that can fail (such as dynamic memory allocation),
+// so this function should always succeed.  Yet there may be an overlooked
+// failure mode, perhaps due to a bug in the binding implementation, or
+// due to invalid usage.  As a last resort for such cases, this function
+// returns PyExc_RuntimeError (a C binding of Python's RuntimeError) should
+// it encounters an unexpected failure.
+extern PyObject* getDataSourceException(const char* ex_name);
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+
+#endif // __PYTHON_ACL_DNS_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 96 - 0
src/lib/python/isc/datasrc/finder_inc.cc

@@ -0,0 +1,96 @@
+namespace {
+const char* const ZoneFinder_doc = "\
+The base class to search a zone for RRsets.\n\
+\n\
+The ZoneFinder class is a wrapper for the c++ base class for representing an\n\
+object that performs DNS lookups in a specific zone accessible via a\n\
+data source. In general, different types of data sources (in-memory,\n\
+database-based, etc) define their own derived c++ classes of ZoneFinder,\n\
+implementing ways to retrieve the required data through the common\n\
+interfaces declared in the base class. Each concrete ZoneFinder object\n\
+is therefore (conceptually) associated with a specific zone of one\n\
+specific data source instance.\n\
+\n\
+The origin name and the RR class of the associated zone are available\n\
+via the get_origin() and get_class() methods, respectively.\n\
+\n\
+The most important method of this class is find(), which performs the\n\
+lookup for a given domain and type. See the description of the method\n\
+for details.\n\
+\n\
+It's not clear whether we should request that a zone finder form a\n\
+\"transaction\", that is, whether to ensure the finder is not\n\
+susceptible to changes made by someone else than the creator of the\n\
+finder. If we don't request that, for example, two different lookup\n\
+results for the same name and type can be different if other threads\n\
+or programs make updates to the zone between the lookups. We should\n\
+revisit this point as we gain more experiences.\n\
+\n\
+";
+
+const char* const ZoneFinder_getOrigin_doc = "\
+get_origin() -> isc.dns.Name\n\
+\n\
+Return the origin name of the zone.\n\
+\n\
+";
+
+const char* const ZoneFinder_getClass_doc = "\
+get_class() -> isc.dns.RRClass\n\
+\n\
+Return the RR class of the zone.\n\
+\n\
+";
+
+const char* const ZoneFinder_find_doc = "\
+find(name, type, target=NULL, options=FIND_DEFAULT) -> (code, FindResult)\n\
+\n\
+Search the zone for a given pair of domain name and RR type.\n\
+\n\
+- If the search name belongs under a zone cut, it returns the code of\n\
+  DELEGATION and the NS RRset at the zone cut.\n\
+- If there is no matching name, it returns the code of NXDOMAIN, and,\n\
+  if DNSSEC is requested, the NSEC RRset that proves the non-\n\
+  existence.\n\
+- If there is a matching name but no RRset of the search type, it\n\
+  returns the code of NXRRSET, and, if DNSSEC is required, the NSEC\n\
+  RRset for that name.\n\
+- If there is a CNAME RR of the searched name but there is no RR of\n\
+  the searched type of the name (so this type is different from\n\
+  CNAME), it returns the code of CNAME and that CNAME RR. Note that if\n\
+  the searched RR type is CNAME, it is considered a successful match,\n\
+  and the code of SUCCESS will be returned.\n\
+- If the search name matches a delegation point of DNAME, it returns\n\
+  the code of DNAME and that DNAME RR.\n\
+- If the target is a list, all RRsets under the domain are inserted\n\
+  there and SUCCESS (or NXDOMAIN, in case of empty domain) is returned\n\
+  instead of normall processing. This is intended to handle ANY query.\n\
+  : this behavior is controversial as we discussed in\n\
+  https://lists.isc.org/pipermail/bind10-dev/2011-January/001918.html\n\
+  We should revisit the interface before we heavily rely on it. The\n\
+  options parameter specifies customized behavior of the search. Their\n\
+  semantics is as follows:\n\
+  (This feature is disable at this time)\n\
+- GLUE_OK Allow search under a zone cut. By default the search will\n\
+  stop once it encounters a zone cut. If this option is specified it\n\
+  remembers information about the highest zone cut and continues the\n\
+  search until it finds an exact match for the given name or it\n\
+  detects there is no exact match. If an exact match is found, RRsets\n\
+  for that name are searched just like the normal case; otherwise, if\n\
+  the search has encountered a zone cut, DELEGATION with the\n\
+  information of the highest zone cut will be returned.\n\
+\n\
+This method raises an isc.datasrc.Error exception if there is an internal\n\
+error in the datasource.\n\
+\n\
+Parameters:\n\
+  name       The domain name to be searched for.\n\
+  type       The RR type to be searched for.\n\
+  target     If target is not NULL, insert all RRs under the domain\n\
+             into it.\n\
+  options    The search options.\n\
+\n\
+Return Value(s): A tuple of a result code an a FindResult object enclosing\n\
+the search result (see above).\n\
+";
+} // unnamed namespace

+ 248 - 0
src/lib/python/isc/datasrc/finder_python.cc

@@ -0,0 +1,248 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <datasrc/client.h>
+#include <datasrc/database.h>
+#include <datasrc/data_source.h>
+#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/iterator.h>
+#include <datasrc/zone.h>
+
+#include <dns/python/name_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include "datasrc.h"
+#include "finder_python.h"
+#include "finder_inc.cc"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+
+namespace isc_datasrc_internal {
+// This is the shared code for the find() call in the finder and the updater
+// Is is intentionally not available through any header, nor at our standard
+// namespace, as it is not supposed to be called anywhere but from finder and
+// updater
+PyObject* ZoneFinder_helper(ZoneFinder* finder, PyObject* args) {
+    if (finder == NULL) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Internal error in find() wrapper; finder object NULL");
+        return (NULL);
+    }
+    PyObject *name;
+    PyObject *rrtype;
+    PyObject *target;
+    int options_int;
+    if (PyArg_ParseTuple(args, "O!O!OI", &name_type, &name,
+                                         &rrtype_type, &rrtype,
+                                         &target, &options_int)) {
+        try {
+            ZoneFinder::FindOptions options =
+                static_cast<ZoneFinder::FindOptions>(options_int);
+            ZoneFinder::FindResult find_result(
+                finder->find(PyName_ToName(name),
+                                   PyRRType_ToRRType(rrtype),
+                                   NULL,
+                                   options
+                                   ));
+            ZoneFinder::Result r = find_result.code;
+            isc::dns::ConstRRsetPtr rrsp = find_result.rrset;
+            if (rrsp) {
+                // Use N instead of O so the refcount isn't increased twice
+                return (Py_BuildValue("IN", r, createRRsetObject(*rrsp)));
+            } else {
+                return (Py_BuildValue("IO", r, Py_None));
+            }
+        } catch (const DataSourceError& dse) {
+            PyErr_SetString(getDataSourceException("Error"), dse.what());
+            return (NULL);
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        } catch (...) {
+            PyErr_SetString(getDataSourceException("Error"),
+                            "Unexpected exception");
+            return (NULL);
+        }
+    } else {
+        return (NULL);
+    }
+    return Py_BuildValue("I", 1);
+}
+
+} // end namespace internal
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneFinder : public PyObject {
+public:
+    s_ZoneFinder() : cppobj(ZoneFinderPtr()) {};
+    ZoneFinderPtr cppobj;
+};
+
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_ZoneFinder, ZoneFinder> ZoneFinderContainer;
+
+// General creation and destruction
+int
+ZoneFinder_init(s_ZoneFinder* self, PyObject* args) {
+    // can't be called directly
+    PyErr_SetString(PyExc_TypeError,
+                    "ZoneFinder cannot be constructed directly");
+
+    return (-1);
+}
+
+void
+ZoneFinder_destroy(s_ZoneFinder* const self) {
+    // cppobj is a shared ptr, but to make sure things are not destroyed in
+    // the wrong order, we reset it here.
+    self->cppobj.reset();
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ZoneFinder_getClass(PyObject* po_self, PyObject*) {
+    s_ZoneFinder* self = static_cast<s_ZoneFinder*>(po_self);
+    try {
+        return (createRRClassObject(self->cppobj->getClass()));
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneFinder_getOrigin(PyObject* po_self, PyObject*) {
+    s_ZoneFinder* self = static_cast<s_ZoneFinder*>(po_self);
+    try {
+        return (createNameObject(self->cppobj->getOrigin()));
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unexpected exception");
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneFinder_find(PyObject* po_self, PyObject* args) {
+    s_ZoneFinder* const self = static_cast<s_ZoneFinder*>(po_self);
+    return (isc_datasrc_internal::ZoneFinder_helper(self->cppobj.get(), args));
+}
+
+// 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 ZoneFinder_methods[] = {
+    { "get_origin", reinterpret_cast<PyCFunction>(ZoneFinder_getOrigin),
+      METH_NOARGS, ZoneFinder_getOrigin_doc },
+    { "get_class", reinterpret_cast<PyCFunction>(ZoneFinder_getClass),
+      METH_NOARGS, ZoneFinder_getClass_doc },
+    { "find", reinterpret_cast<PyCFunction>(ZoneFinder_find), METH_VARARGS,
+      ZoneFinder_find_doc },
+    { NULL, NULL, 0, NULL }
+};
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+
+PyTypeObject zonefinder_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datasrc.ZoneFinder",
+    sizeof(s_ZoneFinder),               // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(ZoneFinder_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
+    ZoneFinder_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    ZoneFinder_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
+    reinterpret_cast<initproc>(ZoneFinder_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
+};
+
+PyObject*
+createZoneFinderObject(isc::datasrc::ZoneFinderPtr source) {
+    s_ZoneFinder* py_zi = static_cast<s_ZoneFinder*>(
+        zonefinder_type.tp_alloc(&zonefinder_type, 0));
+    if (py_zi != NULL) {
+        py_zi->cppobj = source;
+    }
+    return (py_zi);
+}
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+

+ 36 - 0
src/lib/python/isc/datasrc/finder_python.h

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

+ 34 - 0
src/lib/python/isc/datasrc/iterator_inc.cc

@@ -0,0 +1,34 @@
+namespace {
+
+const char* const ZoneIterator_doc = "\
+Read-only iterator to a zone.\n\
+\n\
+You can get an instance of the ZoneIterator from\n\
+DataSourceClient.get_iterator() method. The actual concrete\n\
+c++ implementation will be different depending on the actual data source\n\
+used. This is the abstract interface.\n\
+\n\
+There's no way to start iterating from the beginning again or return.\n\
+\n\
+The ZoneIterator is a python iterator, and can be iterated over directly.\n\
+";
+
+const char* const ZoneIterator_getNextRRset_doc = "\
+get_next_rrset() -> isc.dns.RRset\n\
+\n\
+Get next RRset from the zone.\n\
+\n\
+This returns the next RRset in the zone.\n\
+\n\
+Any special order is not guaranteed.\n\
+\n\
+While this can potentially throw anything (including standard\n\
+allocation errors), it should be rare.\n\
+\n\
+Pointer to the next RRset or None pointer when the iteration gets to\n\
+the end of the zone.\n\
+\n\
+Raises an isc.datasrc.Error exception if it is called again after returning\n\
+None\n\
+";
+} // unnamed namespace

+ 202 - 0
src/lib/python/isc/datasrc/iterator_python.cc

@@ -0,0 +1,202 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <datasrc/client.h>
+#include <datasrc/database.h>
+#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/iterator.h>
+
+#include <dns/python/name_python.h>
+#include <dns/python/rrset_python.h>
+
+#include "datasrc.h"
+#include "iterator_python.h"
+
+#include "iterator_inc.cc"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneIterator : public PyObject {
+public:
+    s_ZoneIterator() : cppobj(ZoneIteratorPtr()) {};
+    ZoneIteratorPtr cppobj;
+};
+
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_ZoneIterator, ZoneIterator>
+    ZoneIteratorContainer;
+
+// General creation and destruction
+int
+ZoneIterator_init(s_ZoneIterator* self, PyObject* args) {
+    // can't be called directly
+    PyErr_SetString(PyExc_TypeError,
+                    "ZoneIterator cannot be constructed directly");
+
+    return (-1);
+}
+
+void
+ZoneIterator_destroy(s_ZoneIterator* const self) {
+    // cppobj is a shared ptr, but to make sure things are not destroyed in
+    // the wrong order, we reset it here.
+    self->cppobj.reset();
+    Py_TYPE(self)->tp_free(self);
+}
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+PyObject*
+ZoneIterator_getNextRRset(PyObject* po_self, PyObject*) {
+    s_ZoneIterator* self = static_cast<s_ZoneIterator*>(po_self);
+    if (!self->cppobj) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "get_next_rrset() called past end of iterator");
+        return (NULL);
+    }
+    try {
+        isc::dns::ConstRRsetPtr rrset = self->cppobj->getNextRRset();
+        if (!rrset) {
+            Py_RETURN_NONE;
+        }
+        return (createRRsetObject(*rrset));
+    } catch (const isc::Exception& isce) {
+        // isc::Unexpected is thrown when we call getNextRRset() when we are
+        // already done iterating ('iterating past end')
+        // We could also simply return None again
+        PyErr_SetString(getDataSourceException("Error"), isce.what());
+        return (NULL);
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unexpected exception");
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneIterator_iter(PyObject *self) {
+    Py_INCREF(self);
+    return (self);
+}
+
+PyObject*
+ZoneIterator_next(PyObject* self) {
+    PyObject *result = ZoneIterator_getNextRRset(self, NULL);
+    // iter_next must return NULL without error instead of Py_None
+    if (result == Py_None) {
+        Py_DECREF(result);
+        return (NULL);
+    } else {
+        return (result);
+    }
+}
+
+PyMethodDef ZoneIterator_methods[] = {
+    { "get_next_rrset",
+      reinterpret_cast<PyCFunction>(ZoneIterator_getNextRRset), METH_NOARGS,
+      ZoneIterator_getNextRRset_doc },
+    { NULL, NULL, 0, NULL }
+};
+
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+PyTypeObject zoneiterator_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datasrc.ZoneIterator",
+    sizeof(s_ZoneIterator),             // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(ZoneIterator_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
+    ZoneIterator_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    ZoneIterator_iter,                  // tp_iter
+    ZoneIterator_next,                  // tp_iternext
+    ZoneIterator_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
+    reinterpret_cast<initproc>(ZoneIterator_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
+};
+
+PyObject*
+createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source) {
+    s_ZoneIterator* py_zi = static_cast<s_ZoneIterator*>(
+        zoneiterator_type.tp_alloc(&zoneiterator_type, 0));
+    if (py_zi != NULL) {
+        py_zi->cppobj = source;
+    }
+    return (py_zi);
+}
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+

+ 38 - 0
src/lib/python/isc/datasrc/iterator_python.h

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

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

@@ -1,16 +1,18 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = master_test.py sqlite3_ds_test.py
+# old tests, TODO remove or change to use new API?
+#PYTESTS = master_test.py sqlite3_ds_test.py
+PYTESTS =  datasrc_test.py
 EXTRA_DIST = $(PYTESTS)
 
 EXTRA_DIST += testdata/brokendb.sqlite3
 EXTRA_DIST += testdata/example.com.sqlite3
-CLEANFILES = $(abs_builddir)/example.com.out.sqlite3
+CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS
@@ -23,7 +25,7 @@ endif
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	PYTHONPATH=:$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log \
+	PYTHONPATH=:$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/python/isc/datasrc/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs \
 	TESTDATA_PATH=$(abs_srcdir)/testdata \
 	TESTDATA_WRITE_PATH=$(abs_builddir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \

+ 389 - 0
src/lib/python/isc/datasrc/tests/datasrc_test.py

@@ -0,0 +1,389 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import isc.log
+import isc.datasrc
+import isc.dns
+import unittest
+import os
+import shutil
+
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
+
+READ_ZONE_DB_FILE = TESTDATA_PATH + "example.com.sqlite3"
+BROKEN_DB_FILE = TESTDATA_PATH + "brokendb.sqlite3"
+WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
+NEW_DB_FILE = TESTDATA_WRITE_PATH + "new_db.sqlite3"
+
+def add_rrset(rrset_list, name, rrclass, rrtype, ttl, rdatas):
+    rrset_to_add = isc.dns.RRset(name, rrclass, rrtype, ttl)
+    if rdatas is not None:
+        for rdata in rdatas:
+            rrset_to_add.add_rdata(isc.dns.Rdata(rrtype, rrclass, rdata))
+    rrset_list.append(rrset_to_add)
+
+# helper function, we have no direct rrset comparison atm
+def rrsets_equal(a, b):
+    # no accessor for sigs either (so this only checks name, class, type, ttl,
+    # and rdata)
+    # also, because of the fake data in rrsigs, if the type is rrsig, the
+    # rdata is not checked
+    return a.get_name() == b.get_name() and\
+           a.get_class() == b.get_class() and\
+           a.get_type() == b.get_type() and \
+           a.get_ttl() == b.get_ttl() and\
+           (a.get_type() == isc.dns.RRType.RRSIG() or
+            sorted(a.get_rdata()) == sorted(b.get_rdata()))
+
+# returns true if rrset is in expected_rrsets
+# will remove the rrset from expected_rrsets if found
+def check_for_rrset(expected_rrsets, rrset):
+    for cur_rrset in expected_rrsets[:]:
+        if rrsets_equal(cur_rrset, rrset):
+            expected_rrsets.remove(cur_rrset)
+            return True
+    return False
+
+class DataSrcClient(unittest.TestCase):
+
+    def test_construct(self):
+        # can't construct directly
+        self.assertRaises(TypeError, isc.datasrc.ZoneIterator)
+
+
+    def test_iterate(self):
+        dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+
+        # for RRSIGS, the TTL's are currently modified. This test should
+        # start failing when we fix that.
+        rrs = dsc.get_iterator(isc.dns.Name("sql1.example.com."))
+
+        # we do not know the order in which they are returned by the iterator
+        # but we do want to check them, so we put all records into one list
+        # sort it (doesn't matter which way it is sorted, as long as it is
+        # sorted)
+
+        # RRset is (atm) an unorderable type, and within an rrset, the
+        # rdatas and rrsigs may also be in random order. In theory the
+        # rrsets themselves can be returned in any order.
+        #
+        # So we create a second list with all rrsets we expect, and for each
+        # rrset we get from the iterator, see if it is in that list, and
+        # remove it.
+        #
+        # When the iterator is empty, we check no rrsets are left in the
+        # list of expected ones
+        expected_rrset_list = []
+
+        name = isc.dns.Name("sql1.example.com")
+        rrclass = isc.dns.RRClass.IN()
+        add_rrset(expected_rrset_list, name, rrclass,
+                  isc.dns.RRType.DNSKEY(), isc.dns.RRTTL(3600),
+                  [
+                     "256 3 5 AwEAAdYdRhBAEY67R/8G1N5AjGF6asIiNh/pNGeQ8xDQP13J"+
+                     "N2lo+sNqWcmpYNhuVqRbLB+mamsU1XcCICSBvAlSmfz/ZUdafX23knAr"+
+                     "TlALxMmspcfdpqun3Yr3YYnztuj06rV7RqmveYckWvAUXVYMSMQZfJ30"+
+                     "5fs0dE/xLztL/CzZ",
+                     "257 3 5 AwEAAbaKDSa9XEFTsjSYpUTHRotTS9Tz3krfDucugW5UokGQ"+
+                     "KC26QlyHXlPTZkC+aRFUs/dicJX2kopndLcnlNAPWiKnKtrsFSCnIJDB"+
+                     "ZIyvcKq+9RXmV3HK3bUdHnQZ88IZWBRmWKfZ6wnzHo53kdYKAemTErkz"+
+                     "taX3lRRPLYWpxRcDPEjysXT3Lh0vfL5D+CIO1yKw/q7C+v6+/kYAxc2l"+
+                     "fbNE3HpklSuF+dyX4nXxWgzbcFuLz5Bwfq6ZJ9RYe/kNkA0uMWNa1KkG"+
+                     "eRh8gg22kgD/KT5hPTnpezUWLvoY5Qc7IB3T0y4n2JIwiF2ZrZYVrWgD"+
+                     "jRWAzGsxJiJyjd6w2k0="
+                  ])
+        add_rrset(expected_rrset_list, name, rrclass,
+                  isc.dns.RRType.NS(), isc.dns.RRTTL(3600),
+                  [
+                    "dns01.example.com.",
+                    "dns02.example.com.",
+                    "dns03.example.com."
+                  ])
+        add_rrset(expected_rrset_list, name, rrclass,
+                  isc.dns.RRType.NSEC(), isc.dns.RRTTL(7200),
+                  [
+                     "www.sql1.example.com. NS SOA RRSIG NSEC DNSKEY"
+                  ])
+        # For RRSIGS, we can't add the fake data through the API, so we
+        # simply pass no rdata at all (which is skipped by the check later)
+        add_rrset(expected_rrset_list, name, rrclass,
+                  isc.dns.RRType.RRSIG(), isc.dns.RRTTL(3600), None)
+        add_rrset(expected_rrset_list, name, rrclass,
+                  isc.dns.RRType.SOA(), isc.dns.RRTTL(3600),
+                  [
+                     "master.example.com. admin.example.com. 678 3600 1800 2419200 7200"
+                  ])
+        name = isc.dns.Name("www.sql1.example.com.")
+        add_rrset(expected_rrset_list, name, rrclass,
+                  isc.dns.RRType.A(), isc.dns.RRTTL(3600),
+                  [
+                     "192.0.2.100"
+                  ])
+        name = isc.dns.Name("www.sql1.example.com.")
+        add_rrset(expected_rrset_list, name, rrclass,
+                  isc.dns.RRType.NSEC(), isc.dns.RRTTL(7200),
+                  [
+                     "sql1.example.com. A RRSIG NSEC"
+                  ])
+        add_rrset(expected_rrset_list, name, rrclass,
+                  isc.dns.RRType.RRSIG(), isc.dns.RRTTL(3600), None)
+
+        # rrs is an iterator, but also has direct get_next_rrset(), use
+        # the latter one here
+        rrset_to_check = rrs.get_next_rrset()
+        while (rrset_to_check != None):
+            self.assertTrue(check_for_rrset(expected_rrset_list,
+                                            rrset_to_check),
+                            "Unexpected rrset returned by iterator:\n" +
+                            rrset_to_check.to_text())
+            rrset_to_check = rrs.get_next_rrset()
+
+        # Now check there are none left
+        self.assertEqual(0, len(expected_rrset_list),
+                         "RRset(s) not returned by iterator: " +
+                         str([rrset.to_text() for rrset in expected_rrset_list ]
+                        ))
+
+        # TODO should we catch this (iterating past end) and just return None
+        # instead of failing?
+        self.assertRaises(isc.datasrc.Error, rrs.get_next_rrset)
+
+        rrets = dsc.get_iterator(isc.dns.Name("example.com"))
+        # there are more than 80 RRs in this zone... let's just count them
+        # (already did a full check of the smaller zone above)
+        self.assertEqual(55, len(list(rrets)))
+        # TODO should we catch this (iterating past end) and just return None
+        # instead of failing?
+        self.assertRaises(isc.datasrc.Error, rrs.get_next_rrset)
+
+        self.assertRaises(TypeError, dsc.get_iterator, "asdf")
+
+    def test_construct(self):
+        # can't construct directly
+        self.assertRaises(TypeError, isc.datasrc.ZoneFinder)
+
+    def test_find(self):
+        dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+
+        result, finder = dsc.find_zone(isc.dns.Name("example.com"))
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
+        self.assertEqual("example.com.", finder.get_origin().to_text())
+
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         rrset.to_text())
+
+        result, rrset = finder.find(isc.dns.Name("www.sql1.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.DELEGATION, result)
+        self.assertEqual("sql1.example.com. 3600 IN NS dns01.example.com.\n" +
+                         "sql1.example.com. 3600 IN NS dns02.example.com.\n" +
+                         "sql1.example.com. 3600 IN NS dns03.example.com.\n",
+                         rrset.to_text())
+
+        result, rrset = finder.find(isc.dns.Name("doesnotexist.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.NXDOMAIN, result)
+        self.assertEqual(None, rrset)
+
+        result, rrset = finder.find(isc.dns.Name("www.some.other.domain"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.NXDOMAIN, result)
+        self.assertEqual(None, rrset)
+
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.TXT(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.NXRRSET, result)
+        self.assertEqual(None, rrset)
+
+        result, rrset = finder.find(isc.dns.Name("cname-ext.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.CNAME, result)
+        self.assertEqual(
+            "cname-ext.example.com. 3600 IN CNAME www.sql1.example.com.\n",
+            rrset.to_text())
+
+        self.assertRaises(TypeError, finder.find,
+                          "foo",
+                          isc.dns.RRType.A(),
+                          None,
+                          finder.FIND_DEFAULT)
+        self.assertRaises(TypeError, finder.find,
+                          isc.dns.Name("cname-ext.example.com"),
+                          "foo",
+                          None,
+                          finder.FIND_DEFAULT)
+        self.assertRaises(TypeError, finder.find,
+                          isc.dns.Name("cname-ext.example.com"),
+                          isc.dns.RRType.A(),
+                          None,
+                          "foo")
+
+
+class DataSrcUpdater(unittest.TestCase):
+
+    def setUp(self):
+        # Make a fresh copy of the writable database with all original content
+        shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
+
+    def test_construct(self):
+        # can't construct directly
+        self.assertRaises(TypeError, isc.datasrc.ZoneUpdater)
+
+    def test_update_delete_commit(self):
+
+        dsc = isc.datasrc.DataSourceClient(WRITE_ZONE_DB_FILE)
+
+        # first make sure, through a separate finder, that some record exists
+        result, finder = dsc.find_zone(isc.dns.Name("example.com"))
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
+        self.assertEqual("example.com.", finder.get_origin().to_text())
+
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         rrset.to_text())
+
+        rrset_to_delete = rrset;
+
+        # can't delete rrset with associated sig. Abuse that to force an
+        # exception first, then remove the sig, then delete the record
+        updater = dsc.get_updater(isc.dns.Name("example.com"), True)
+        self.assertRaises(isc.datasrc.Error, updater.delete_rrset,
+                          rrset_to_delete)
+
+        rrset_to_delete.remove_rrsig()
+
+        updater.delete_rrset(rrset_to_delete)
+
+        # The record should be gone in the updater, but not in the original
+        # finder (since we have not committed)
+        result, rrset = updater.find(isc.dns.Name("www.example.com"),
+                                     isc.dns.RRType.A(),
+                                     None,
+                                     finder.FIND_DEFAULT)
+        self.assertEqual(finder.NXDOMAIN, result)
+        self.assertEqual(None, rrset)
+
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         rrset.to_text())
+
+        updater.commit()
+        # second commit should raise exception
+        self.assertRaises(isc.datasrc.Error, updater.commit)
+
+        # the record should be gone now in the 'real' finder as well
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.NXDOMAIN, result)
+        self.assertEqual(None, rrset)
+
+        # now add it again
+        updater = dsc.get_updater(isc.dns.Name("example.com"), True)
+        updater.add_rrset(rrset_to_delete)
+        updater.commit()
+
+        # second commit should throw
+        self.assertRaises(isc.datasrc.Error, updater.commit)
+
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         rrset.to_text())
+
+    def test_update_delete_abort(self):
+        dsc = isc.datasrc.DataSourceClient(WRITE_ZONE_DB_FILE)
+
+        # first make sure, through a separate finder, that some record exists
+        result, finder = dsc.find_zone(isc.dns.Name("example.com"))
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
+        self.assertEqual("example.com.", finder.get_origin().to_text())
+
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         rrset.to_text())
+
+        rrset_to_delete = rrset;
+
+        # can't delete rrset with associated sig. Abuse that to force an
+        # exception first, then remove the sig, then delete the record
+        updater = dsc.get_updater(isc.dns.Name("example.com"), True)
+        self.assertRaises(isc.datasrc.Error, updater.delete_rrset,
+                          rrset_to_delete)
+
+        rrset_to_delete.remove_rrsig()
+
+        updater.delete_rrset(rrset_to_delete)
+
+        # The record should be gone in the updater, but not in the original
+        # finder (since we have not committed)
+        result, rrset = updater.find(isc.dns.Name("www.example.com"),
+                                     isc.dns.RRType.A(),
+                                     None,
+                                     finder.FIND_DEFAULT)
+        self.assertEqual(finder.NXDOMAIN, result)
+        self.assertEqual(None, rrset)
+
+        # destroy the updater, which should make it roll back
+        updater = None
+
+        # the record should still be available in the 'real' finder as well
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         rrset.to_text())
+
+
+if __name__ == "__main__":
+    isc.log.init("bind10")
+    unittest.main()

+ 181 - 0
src/lib/python/isc/datasrc/updater_inc.cc

@@ -0,0 +1,181 @@
+namespace {
+
+const char* const ZoneUpdater_doc = "\
+The base class to make updates to a single zone.\n\
+\n\
+On construction, each derived class object will start a\n\
+\"transaction\" for making updates to a specific zone (this means a\n\
+constructor of a derived class would normally take parameters to\n\
+identify the zone to be updated). The underlying realization of a\n\
+\"transaction\" will differ for different derived classes; if it uses\n\
+a general purpose database as a backend, it will involve performing\n\
+some form of \"begin transaction\" statement for the database.\n\
+\n\
+Updates (adding or deleting RRs) are made via add_rrset() and\n\
+delete_rrset() methods. Until the commit() method is called the\n\
+changes are local to the updater object. For example, they won't be\n\
+visible via a ZoneFinder object, but only by the updater's own find()\n\
+method. The commit() completes the transaction and makes the changes\n\
+visible to others.\n\
+\n\
+This class does not provide an explicit \"rollback\" interface. If\n\
+something wrong or unexpected happens during the updates and the\n\
+caller wants to cancel the intermediate updates, the caller should\n\
+simply destroy the updater object without calling commit(). The\n\
+destructor is supposed to perform the \"rollback\" operation,\n\
+depending on the internal details of the derived class.\n\
+\n\
+This initial implementation provides a quite simple interface of\n\
+adding and deleting RRs (see the description of the related methods).\n\
+It may be revisited as we gain more experiences.\n\
+\n\
+";
+
+const char* const ZoneUpdater_addRRset_doc = "\
+add_rrset(rrset) -> No return value\n\
+\n\
+Add an RRset to a zone via the updater.\n\
+It performs a few basic checks:\n\
+- Whether the RR class is identical to that for the zone to be updated\n\
+- Whether the RRset is not empty, i.e., it has at least one RDATA\n\
+- Whether the RRset is not associated with an RRSIG, i.e., whether\n\
+  get_rrsig() on the RRset returns a NULL pointer.\n\
+\n\
+and otherwise does not check any oddity. For example, it doesn't check\n\
+whether the owner name of the specified RRset is a subdomain of the\n\
+zone's origin; it doesn't care whether or not there is already an\n\
+RRset of the same name and RR type in the zone, and if there is,\n\
+whether any of the existing RRs have duplicate RDATA with the added\n\
+ones. If these conditions matter the calling application must examine\n\
+the existing data beforehand using the ZoneFinder returned by\n\
+get_finder().\n\
+\n\
+The validation requirement on the associated RRSIG is temporary. If we\n\
+find it more reasonable and useful to allow adding a pair of RRset and\n\
+its RRSIG RRset as we gain experiences with the interface, we may\n\
+remove this restriction. Until then we explicitly check it to prevent\n\
+accidental misuse.\n\
+\n\
+Conceptually, on successful call to this method, the zone will have\n\
+the specified RRset, and if there is already an RRset of the same name\n\
+and RR type, these two sets will be \"merged\". \"Merged\" means that\n\
+a subsequent call to ZoneFinder.find() for the name and type will\n\
+result in success and the returned RRset will contain all previously\n\
+existing and newly added RDATAs with the TTL being the minimum of the\n\
+two RRsets. The underlying representation of the \"merged\" RRsets may\n\
+vary depending on the characteristic of the underlying data source.\n\
+For example, if it uses a general purpose database that stores each RR\n\
+of the same RRset separately, it may simply be a larger sets of RRs\n\
+based on both the existing and added RRsets; the TTLs of the RRs may\n\
+be different within the database, and there may even be duplicate RRs\n\
+in different database rows. As long as the RRset returned via\n\
+ZoneFinder.find() conforms to the concept of \"merge\", the actual\n\
+internal representation is up to the implementation.\n\
+\n\
+This method must not be called once commit() is performed. If it calls\n\
+after commit() the implementation must throw a isc.datasrc.Error\n\
+exception.\n\
+\n\
+Todo As noted above we may have to revisit the design details as we\n\
+gain experiences:\n\
+\n\
+- we may want to check (and maybe reject) if there is already a\n\
+  duplicate RR (that has the same RDATA).\n\
+- we may want to check (and maybe reject) if there is already an RRset\n\
+  of the same name and RR type with different TTL\n\
+- we may even want to check if there is already any RRset of the same\n\
+  name and RR type.\n\
+- we may want to add an \"options\" parameter that can control the\n\
+  above points\n\
+- we may want to have this method return a value containing the\n\
+  information on whether there's a duplicate, etc.\n\
+\n\
+Exceptions:\n\
+  isc.datasrc.Error Called after commit(), RRset is invalid (see above),\n\
+                    internal data source error, or wrapper error\n\
+\n\
+Parameters:\n\
+  rrset      The RRset to be added\n\
+\n\
+";
+
+const char* const ZoneUpdater_deleteRRset_doc = "\
+delete_rrset(rrset) -> No return value\n\
+\n\
+Delete an RRset from a zone via the updater.\n\
+\n\
+Like add_rrset(), the detailed semantics and behavior of this method\n\
+may have to be revisited in a future version. The following are based\n\
+on the initial implementation decisions.\n\
+\n\
+- Existing RRs that don't match any of the specified RDATAs will\n\
+  remain in the zone.\n\
+- Any RRs of the specified RRset that doesn't exist in the zone will\n\
+  simply be ignored; the implementation of this method is not supposed\n\
+  to check that condition.\n\
+- The TTL of the RRset is ignored; matching is only performed by the\n\
+  owner name, RR type and RDATA\n\
+\n\
+Ignoring the TTL may not look sensible, but it's based on the\n\
+observation that it will result in more intuitive result, especially\n\
+when the underlying data source is a general purpose database. See\n\
+also the c++ documentation of DatabaseAccessor::DeleteRecordInZone()\n\
+on this point. It also matches the dynamic update protocol (RFC2136),\n\
+where TTLs are ignored when deleting RRs.\n\
+\n\
+This method performs a limited level of validation on the specified\n\
+RRset:\n\
+- Whether the RR class is identical to that for the zone to be updated\n\
+- Whether the RRset is not empty, i.e., it has at least one RDATA\n\
+- Whether the RRset is not associated with an RRSIG\n\
+\n\
+This method must not be called once commit() is performed. If it calls\n\
+after commit() the implementation must throw a isc.datasrc.Error\n\
+exception.\n\
+\n\
+Todo: As noted above we may have to revisit the design details as we\n\
+gain experiences:\n\
+\n\
+- we may want to check (and maybe reject) if some or all of the RRs\n\
+  for the specified RRset don't exist in the zone\n\
+- we may want to allow an option to \"delete everything\" for\n\
+  specified name and/or specified name + RR type.\n\
+- as mentioned above, we may want to include the TTL in matching the\n\
+  deleted RRs\n\
+- we may want to add an \"options\" parameter that can control the\n\
+  above points\n\
+- we may want to have this method return a value containing the\n\
+  information on whether there's any RRs that are specified but don't\n\
+  exit, the number of actually deleted RRs, etc.\n\
+\n\
+Exceptions:\n\
+  isc.datasrc.Error Called after commit(), RRset is invalid (see above),\n\
+             internal data source error\n\
+  std.bad_alloc Resource allocation failure\n\
+\n\
+Parameters:\n\
+  rrset      The RRset to be deleted\n\
+\n\
+";
+
+const char* const ZoneUpdater_commit_doc = "\
+commit() -> void\n\
+\n\
+Commit the updates made in the updater to the zone.\n\
+\n\
+This method completes the \"transaction\" started at the creation of\n\
+the updater. After successful completion of this method, the updates\n\
+will be visible outside the scope of the updater. The actual internal\n\
+behavior will defer for different derived classes. For a derived class\n\
+with a general purpose database as a backend, for example, this method\n\
+would perform a \"commit\" statement for the database.\n\
+\n\
+This operation can only be performed at most once. A duplicate call\n\
+must result in a isc.datasrc.Error exception.\n\
+\n\
+Exceptions:\n\
+  isc.datasrc.Error Duplicate call of the method, internal data source\n\
+             error, or wrapper error\n\\n\
+\n\
+";
+} // unnamed namespace

+ 318 - 0
src/lib/python/isc/datasrc/updater_python.cc

@@ -0,0 +1,318 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <datasrc/client.h>
+#include <datasrc/database.h>
+#include <datasrc/data_source.h>
+#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/zone.h>
+
+#include <dns/python/name_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrtype_python.h>
+
+#include "datasrc.h"
+#include "updater_python.h"
+
+#include "updater_inc.cc"
+#include "finder_inc.cc"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+
+namespace isc_datasrc_internal {
+// See finder_python.cc
+PyObject* ZoneFinder_helper(ZoneFinder* finder, PyObject* args);
+}
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneUpdater : public PyObject {
+public:
+    s_ZoneUpdater() : cppobj(ZoneUpdaterPtr()) {};
+    ZoneUpdaterPtr cppobj;
+};
+
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_ZoneUpdater, ZoneUpdater> ZoneUpdaterContainer;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int
+ZoneUpdater_init(s_ZoneUpdater* self, PyObject* args) {
+    // can't be called directly
+    PyErr_SetString(PyExc_TypeError,
+                    "ZoneUpdater cannot be constructed directly");
+
+    return (-1);
+}
+
+void
+ZoneUpdater_destroy(s_ZoneUpdater* const self) {
+    // cppobj is a shared ptr, but to make sure things are not destroyed in
+    // the wrong order, we reset it here.
+    self->cppobj.reset();
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ZoneUpdater_addRRset(PyObject* po_self, PyObject* args) {
+    s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+    PyObject* rrset_obj;
+    if (PyArg_ParseTuple(args, "O!", &rrset_type, &rrset_obj)) {
+        try {
+            self->cppobj->addRRset(PyRRset_ToRRset(rrset_obj));
+            Py_RETURN_NONE;
+        } catch (const DataSourceError& dse) {
+            PyErr_SetString(getDataSourceException("Error"), dse.what());
+            return (NULL);
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        }
+    } else {
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneUpdater_deleteRRset(PyObject* po_self, PyObject* args) {
+    s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+    PyObject* rrset_obj;
+    if (PyArg_ParseTuple(args, "O!", &rrset_type, &rrset_obj)) {
+        try {
+            self->cppobj->deleteRRset(PyRRset_ToRRset(rrset_obj));
+            Py_RETURN_NONE;
+        } catch (const DataSourceError& dse) {
+            PyErr_SetString(getDataSourceException("Error"), dse.what());
+            return (NULL);
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        }
+    } else {
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneUpdater_commit(PyObject* po_self, PyObject*) {
+    s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+    try {
+        self->cppobj->commit();
+        Py_RETURN_NONE;
+    } catch (const DataSourceError& dse) {
+        PyErr_SetString(getDataSourceException("Error"), dse.what());
+        return (NULL);
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneUpdater_getClass(PyObject* po_self, PyObject*) {
+    s_ZoneUpdater* self = static_cast<s_ZoneUpdater*>(po_self);
+    try {
+        return (createRRClassObject(self->cppobj->getFinder().getClass()));
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unexpected exception");
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneUpdater_getOrigin(PyObject* po_self, PyObject*) {
+    s_ZoneUpdater* self = static_cast<s_ZoneUpdater*>(po_self);
+    try {
+        return (createNameObject(self->cppobj->getFinder().getOrigin()));
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unexpected exception");
+        return (NULL);
+    }
+}
+
+PyObject*
+ZoneUpdater_find(PyObject* po_self, PyObject* args) {
+    s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+    return (isc_datasrc_internal::ZoneFinder_helper(&self->cppobj->getFinder(),
+                                                    args));
+}
+
+PyObject*
+AZoneUpdater_find(PyObject* po_self, PyObject* args) {
+    s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+    PyObject *name;
+    PyObject *rrtype;
+    PyObject *target;
+    int options_int;
+    if (PyArg_ParseTuple(args, "O!O!OI", &name_type, &name,
+                                         &rrtype_type, &rrtype,
+                                         &target, &options_int)) {
+        try {
+            ZoneFinder::FindOptions options =
+                static_cast<ZoneFinder::FindOptions>(options_int);
+            ZoneFinder::FindResult find_result(
+                self->cppobj->getFinder().find(PyName_ToName(name),
+                                   PyRRType_ToRRType(rrtype),
+                                   NULL,
+                                   options
+                                   ));
+            ZoneFinder::Result r = find_result.code;
+            isc::dns::ConstRRsetPtr rrsp = find_result.rrset;
+            if (rrsp) {
+                // Use N instead of O so the refcount isn't increased twice
+                return Py_BuildValue("IN", r, createRRsetObject(*rrsp));
+            } else {
+                return Py_BuildValue("IO", r, Py_None);
+            }
+        } catch (const DataSourceError& dse) {
+            PyErr_SetString(getDataSourceException("Error"), dse.what());
+            return (NULL);
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        } catch (...) {
+            PyErr_SetString(getDataSourceException("Error"),
+                            "Unexpected exception");
+            return (NULL);
+        }
+    } else {
+        return (NULL);
+    }
+    return Py_BuildValue("I", 1);
+}
+
+
+// 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 ZoneUpdater_methods[] = {
+    { "add_rrset", reinterpret_cast<PyCFunction>(ZoneUpdater_addRRset),
+      METH_VARARGS, ZoneUpdater_addRRset_doc },
+    { "delete_rrset", reinterpret_cast<PyCFunction>(ZoneUpdater_deleteRRset),
+      METH_VARARGS, ZoneUpdater_deleteRRset_doc },
+    { "commit", reinterpret_cast<PyCFunction>(ZoneUpdater_commit), METH_NOARGS,
+      ZoneUpdater_commit_doc },
+    // Instead of a getFinder, we implement the finder functionality directly
+    // This is because ZoneFinder is non-copyable, and we should not create
+    // a ZoneFinder object from a reference only (which is what is returned
+    // by getFinder(). Apart from that
+    { "get_origin", reinterpret_cast<PyCFunction>(ZoneUpdater_getOrigin),
+      METH_NOARGS, ZoneFinder_getOrigin_doc },
+    { "get_class", reinterpret_cast<PyCFunction>(ZoneUpdater_getClass),
+      METH_NOARGS, ZoneFinder_getClass_doc },
+    { "find", reinterpret_cast<PyCFunction>(ZoneUpdater_find), METH_VARARGS,
+      ZoneFinder_find_doc },
+    { NULL, NULL, 0, NULL }
+};
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+PyTypeObject zoneupdater_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datasrc.ZoneUpdater",
+    sizeof(s_ZoneUpdater),              // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(ZoneUpdater_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
+    ZoneUpdater_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    ZoneUpdater_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
+    reinterpret_cast<initproc>(ZoneUpdater_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
+};
+
+PyObject*
+createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source) {
+    s_ZoneUpdater* py_zi = static_cast<s_ZoneUpdater*>(
+        zoneupdater_type.tp_alloc(&zoneupdater_type, 0));
+    if (py_zi != NULL) {
+        py_zi->cppobj = source;
+    }
+    return (py_zi);
+}
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+

+ 39 - 0
src/lib/python/isc/datasrc/updater_python.h

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

+ 7 - 0
src/lib/python/isc/dns/Makefile.am

@@ -0,0 +1,7 @@
+python_PYTHON = __init__.py
+
+CLEANDIRS = __pycache__
+
+clean-local:
+	rm -rf $(CLEANDIRS)
+