Michal 'vorner' Vaner 12 years ago
parent
commit
0044c1905b

+ 37 - 2
src/lib/datasrc/client_list.cc

@@ -167,6 +167,39 @@ ConfigurableClientList::configure(const Element& config, bool allow_cache) {
     }
 }
 
+namespace {
+
+class CacheKeeper : public ClientList::FindResult::LifeKeeper {
+public:
+    CacheKeeper(const boost::shared_ptr<InMemoryClient>& cache) :
+        cache_(cache)
+    {}
+private:
+    const boost::shared_ptr<InMemoryClient> cache_;
+};
+
+class ContainerKeeper : public ClientList::FindResult::LifeKeeper {
+public:
+    ContainerKeeper(const DataSourceClientContainerPtr& container) :
+        container_(container)
+    {}
+private:
+    const DataSourceClientContainerPtr container_;
+};
+
+boost::shared_ptr<ClientList::FindResult::LifeKeeper>
+genKeeper(const ConfigurableClientList::DataSourceInfo& info) {
+    if (info.cache_) {
+        return (boost::shared_ptr<ClientList::FindResult::LifeKeeper>(
+            new CacheKeeper(info.cache_)));
+    } else {
+        return (boost::shared_ptr<ClientList::FindResult::LifeKeeper>(
+            new ContainerKeeper(info.container_)));
+    }
+}
+
+}
+
 ClientList::FindResult
 ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
                             bool) const
@@ -185,10 +218,11 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
         ZoneFinderPtr finder;
         uint8_t matched_labels;
         bool matched;
+        boost::shared_ptr<FindResult::LifeKeeper> keeper;
         operator FindResult() const {
             // Conversion to the right result. If we return this, there was
             // a partial match at best.
-            return (FindResult(datasrc_client, finder, false));
+            return (FindResult(datasrc_client, finder, false, keeper));
         }
     } candidate;
 
@@ -206,7 +240,7 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
                 // TODO: In case we have only the datasource and not the finder
                 // and the need_updater parameter is true, get the zone there.
                 return (FindResult(client, result.zone_finder,
-                                   true));
+                                   true, genKeeper(info)));
             case result::PARTIALMATCH:
                 if (!want_exact_match) {
                     // In case we have a partial match, check if it is better
@@ -230,6 +264,7 @@ ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
                         candidate.finder = result.zone_finder;
                         candidate.matched_labels = labels;
                         candidate.matched = true;
+                        candidate.keeper = genKeeper(info);
                     }
                 }
                 break;

+ 23 - 4
src/lib/datasrc/client_list.h

@@ -65,15 +65,27 @@ public:
     /// Instead, all the member variables are defined as const and can be
     /// accessed directly.
     struct FindResult {
+        /// \brief Internal class for holding a reference.
+        ///
+        /// This is used to make sure the data source client isn't released
+        /// too soon.
+        ///
+        /// \see life_keeper_;
+        class LifeKeeper {
+        public:
+            virtual ~LifeKeeper() {};
+        };
         /// \brief Constructor.
         ///
         /// It simply fills in the member variables according to the
         /// parameters. See the member descriptions for their meaning.
         FindResult(DataSourceClient* dsrc_client, const ZoneFinderPtr& finder,
-                   bool exact_match) :
+                   bool exact_match,
+                   const boost::shared_ptr<LifeKeeper>& life_keeper) :
             dsrc_client_(dsrc_client),
             finder_(finder),
-            exact_match_(exact_match)
+            exact_match_(exact_match),
+            life_keeper_(life_keeper)
         {}
 
         /// \brief Negative answer constructor.
@@ -101,8 +113,9 @@ public:
         /// If no such data source exists, this is NULL pointer.
         ///
         /// Note that the pointer is valid only as long the ClientList which
-        /// returned the pointer is alive and was not reconfigured. The
-        /// ownership is preserved within the ClientList.
+        /// returned the pointer is alive and was not reconfigured or you hold
+        /// a reference to life_keeper_. The ownership is preserved within the
+        /// ClientList.
         DataSourceClient* const dsrc_client_;
 
         /// \brief The finder for the requested zone.
@@ -116,6 +129,12 @@ public:
 
         /// \brief If the result is an exact match.
         const bool exact_match_;
+
+        /// \brief Something that holds the dsrc_client_ valid.
+        ///
+        /// As long as you hold the life_keeper_, the dsrc_client_ is
+        /// guaranteed to be valid.
+        const boost::shared_ptr<LifeKeeper> life_keeper_;
     };
 
     /// \brief Search for a zone through the data sources.

+ 9 - 0
src/lib/datasrc/tests/client_list_unittest.cc

@@ -245,6 +245,12 @@ public:
         ASSERT_NE(ZoneFinderPtr(), result.finder_);
         EXPECT_EQ(name, result.finder_->getOrigin());
         EXPECT_EQ(exact, result.exact_match_);
+        // If it is a positive result, there's something to keep
+        // alive, even when we don't know what it is.
+        // Any better idea how to test it actually keeps the thing
+        // alive?
+        EXPECT_NE(shared_ptr<ClientList::FindResult::LifeKeeper>(),
+                  result.life_keeper_);
         if (from_cache) {
             EXPECT_NE(shared_ptr<InMemoryZoneFinder>(),
                       dynamic_pointer_cast<InMemoryZoneFinder>(
@@ -315,6 +321,9 @@ TEST_F(ListTest, selfTest) {
     EXPECT_EQ(result::NOTFOUND, ds_[1]->findZone(Name("example.org")).code);
     EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code);
     EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code);
+    // Nothing to keep alive here.
+    EXPECT_EQ(shared_ptr<ClientList::FindResult::LifeKeeper>(),
+                  negativeResult_.life_keeper_);
 }
 
 // Test the list we create with empty configuration is, in fact, empty

+ 2 - 0
src/lib/python/isc/datasrc/Makefile.am

@@ -18,6 +18,8 @@ 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_SOURCES += journal_reader_python.cc journal_reader_python.h
+datasrc_la_SOURCES += configurableclientlist_python.cc
+datasrc_la_SOURCES += configurableclientlist_python.h
 
 datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)

+ 37 - 5
src/lib/python/isc/datasrc/client_python.cc

@@ -28,6 +28,7 @@
 #include <datasrc/data_source.h>
 #include <datasrc/sqlite3_accessor.h>
 #include <datasrc/iterator.h>
+#include <datasrc/client_list.h>
 
 #include <dns/python/name_python.h>
 #include <dns/python/rrset_python.h>
@@ -51,8 +52,17 @@ namespace {
 // The s_* Class simply covers one instantiation of the object
 class s_DataSourceClient : public PyObject {
 public:
-    s_DataSourceClient() : cppobj(NULL) {};
+    s_DataSourceClient() :
+        cppobj(NULL),
+        client(NULL),
+        keeper(NULL)
+    {};
     DataSourceClientContainer* cppobj;
+    DataSourceClient* client;
+    // We can't rely on the constructor or destructor being
+    // called, so this is a pointer to shared pointer, so we
+    // can call the new and delete explicitly.
+    boost::shared_ptr<ClientList::FindResult::LifeKeeper>* keeper;
 };
 
 PyObject*
@@ -62,7 +72,7 @@ DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
     if (PyArg_ParseTuple(args, "O!", &name_type, &name)) {
         try {
             DataSourceClient::FindResult find_result(
-                self->cppobj->getInstance().findZone(PyName_ToName(name)));
+                self->client->findZone(PyName_ToName(name)));
 
             result::Result r = find_result.code;
             ZoneFinderPtr zfp = find_result.zone_finder;
@@ -103,7 +113,7 @@ DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
                 }
             }
             return (createZoneIteratorObject(
-                self->cppobj->getInstance().getIterator(PyName_ToName(name_obj),
+                self->client->getIterator(PyName_ToName(name_obj),
                                                         separate_rrs),
                 po_self));
         } catch (const isc::NotImplemented& ne) {
@@ -139,7 +149,7 @@ DataSourceClient_getUpdater(PyObject* po_self, PyObject* args) {
         const bool journaling = (journaling_obj == Py_True);
         try {
             ZoneUpdaterPtr updater =
-                self->cppobj->getInstance().getUpdater(PyName_ToName(name_obj),
+                self->client->getUpdater(PyName_ToName(name_obj),
                                                        replace, journaling);
             if (!updater) {
                 return (Py_None);
@@ -184,7 +194,7 @@ DataSourceClient_getJournalReader(PyObject* po_self, PyObject* args) {
                          &begin_obj, &end_obj)) {
         try {
             pair<ZoneJournalReader::Result, ZoneJournalReaderPtr> result =
-                self->cppobj->getInstance().getJournalReader(
+                self->client->getJournalReader(
                     PyName_ToName(name_obj), static_cast<uint32_t>(begin_obj),
                     static_cast<uint32_t>(end_obj));
             PyObject* po_reader;
@@ -245,6 +255,8 @@ DataSourceClient_init(PyObject* po_self, PyObject* args, PyObject*) {
                 isc::data::Element::fromJSON(ds_config_str);
             self->cppobj = new DataSourceClientContainer(ds_type_str,
                                                          ds_config);
+            self->client = &self->cppobj->getInstance();
+            self->keeper = NULL;
             return (0);
         } else {
             return (-1);
@@ -280,7 +292,10 @@ void
 DataSourceClient_destroy(PyObject* po_self) {
     s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
     delete self->cppobj;
+    delete self->keeper;
     self->cppobj = NULL;
+    self->client = NULL;
+    self->keeper = NULL;
     Py_TYPE(self)->tp_free(self);
 }
 
@@ -342,6 +357,23 @@ PyTypeObject datasourceclient_type = {
     0                                   // tp_version_tag
 };
 
+PyObject*
+wrapDataSourceClient(DataSourceClient* client,
+                     const boost::shared_ptr<ClientList::FindResult::
+                     LifeKeeper>& life_keeper)
+{
+    s_DataSourceClient *result =
+        static_cast<s_DataSourceClient*>(PyObject_New(s_DataSourceClient,
+                                                      &datasourceclient_type));
+    CPPPyObjectContainer<s_DataSourceClient, DataSourceClientContainer>
+        container(result);
+    result->cppobj = NULL;
+    result->keeper =
+        new boost::shared_ptr<ClientList::FindResult::LifeKeeper>(life_keeper);
+    result->client = client;
+    return (container.release());
+}
+
 } // namespace python
 } // namespace datasrc
 } // namespace isc

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

@@ -15,6 +15,8 @@
 #ifndef __PYTHON_DATASRC_CLIENT_H
 #define __PYTHON_DATASRC_CLIENT_H 1
 
+#include <datasrc/client_list.h>
+
 #include <Python.h>
 
 namespace isc {
@@ -25,6 +27,23 @@ namespace python {
 
 extern PyTypeObject datasourceclient_type;
 
+/// \brief Create a DataSourceClient python object
+///
+/// Unlike many similar functions, this one does not create a copied instance
+/// of the passed object. It wraps the given one. This is why the name is
+/// different than the usual.
+///
+/// \param client The client to wrap.
+/// \param life_keeper An optional object which keeps the client pointer valid.
+///     The object will be kept inside the wrapper too, making sure that the
+///     client is not destroyed sooner than the python object. The keeper thing
+///     is designed to acommodate the interface of the ClientList.
+PyObject*
+wrapDataSourceClient(DataSourceClient* client,
+                     const boost::shared_ptr<ClientList::FindResult::
+                     LifeKeeper>& life_keeper = boost::shared_ptr<ClientList::
+                     FindResult::LifeKeeper>());
+
 } // namespace python
 } // namespace datasrc
 } // namespace isc

+ 308 - 0
src/lib/python/isc/datasrc/configurableclientlist_python.cc

@@ -0,0 +1,308 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// 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 <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/python/rrclass_python.h>
+#include <dns/python/name_python.h>
+
+#include <datasrc/client_list.h>
+
+#include "configurableclientlist_python.h"
+#include "datasrc.h"
+#include "finder_python.h"
+#include "client_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+
+//
+// ConfigurableClientList
+//
+
+// Trivial constructor.
+s_ConfigurableClientList::s_ConfigurableClientList() : cppobj(NULL) {
+}
+
+namespace {
+
+int
+ConfigurableClientList_init(PyObject* po_self, PyObject* args, PyObject*) {
+    s_ConfigurableClientList* self =
+        static_cast<s_ConfigurableClientList*>(po_self);
+    try {
+        const PyObject* rrclass;
+        if (PyArg_ParseTuple(args, "O!", &isc::dns::python::rrclass_type,
+                             &rrclass)) {
+            self->cppobj =
+                new ConfigurableClientList(isc::dns::python::
+                                           PyRRClass_ToRRClass(rrclass));
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct ConfigurableClientList object: " +
+            string(ex.what());
+        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (-1);
+    }
+
+    return (-1);
+}
+
+void
+ConfigurableClientList_destroy(PyObject* po_self) {
+    s_ConfigurableClientList* self =
+        static_cast<s_ConfigurableClientList*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ConfigurableClientList_configure(PyObject* po_self, PyObject* args) {
+    s_ConfigurableClientList* self =
+        static_cast<s_ConfigurableClientList*>(po_self);
+    try {
+        const char* configuration;
+        int allow_cache;
+        if (PyArg_ParseTuple(args, "si", &configuration, &allow_cache)) {
+            const isc::data::ConstElementPtr
+                element(isc::data::Element::fromJSON(string(configuration)));
+            self->cppobj->configure(*element, allow_cache);
+            Py_RETURN_NONE;
+        } else {
+            return (NULL);
+        }
+    } catch (const isc::data::JSONError& jse) {
+        const string ex_what(std::string("JSON parse error in data source"
+                               " configuration: ") + jse.what());
+        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+        return (NULL);
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unknown C++ exception");
+        return (NULL);
+    }
+}
+
+PyObject*
+ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
+    s_ConfigurableClientList* self =
+        static_cast<s_ConfigurableClientList*>(po_self);
+    try {
+        PyObject* name_obj;
+        int want_exact_match = 0;
+        int want_finder = 1;
+        if (PyArg_ParseTuple(args, "O!|ii", &isc::dns::python::name_type,
+                             &name_obj, &want_exact_match, &want_finder)) {
+            const isc::dns::Name
+                name(isc::dns::python::PyName_ToName(name_obj));
+            const ClientList::FindResult
+                result(self->cppobj->find(name, want_exact_match,
+                                          want_finder));
+            PyObjectContainer dsrc;
+            if (result.dsrc_client_ == NULL) {
+                // Use the Py_BuildValue, as it takes care of the
+                // reference counts correctly.
+                dsrc.reset(Py_BuildValue(""));
+            } else {
+                // Make sure we have a keeper there too, so it doesn't
+                // die when the underlying client list dies or is
+                // reconfigured.
+                //
+                // However, as it is inside the C++ part, is there a
+                // reasonable way to test it?
+                dsrc.reset(wrapDataSourceClient(result.dsrc_client_,
+                                                result.life_keeper_));
+            }
+            PyObjectContainer finder;
+            if (result.finder_ == NULL) {
+                finder.reset(Py_BuildValue(""));
+            } else {
+                // Make sure it keeps the data source client alive.
+                finder.reset(createZoneFinderObject(result.finder_,
+                                                    dsrc.get()));
+            }
+            PyObjectContainer exact(PyBool_FromLong(result.exact_match_));
+
+            return (Py_BuildValue("OOO", dsrc.get(), finder.get(),
+                                  exact.get()));
+        } else {
+            return (NULL);
+        }
+    } catch (const std::exception& exc) {
+        PyErr_SetString(getDataSourceException("Error"), exc.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(getDataSourceException("Error"),
+                        "Unknown C++ exception");
+        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 ConfigurableClientList_methods[] = {
+    { "configure", ConfigurableClientList_configure, METH_VARARGS,
+        "configure(configuration, allow_cache) -> None\n\
+\n\
+Wrapper around C++ ConfigurableClientList::configure\n\
+\n\
+This sets the active configuration. It fills the ConfigurableClientList with\
+corresponding data source clients.\n\
+\n\
+If any error is detected, an exception is raised and the previous\
+configuration preserved.\n\
+\n\
+Parameters:\n\
+  configuration     The configuration, as a JSON encoded string.\
+  allow_cache       If caching is allowed." },
+    { "find", ConfigurableClientList_find, METH_VARARGS,
+"find(zone, want_exact_match=False, want_finder=True) -> datasrc_client,\
+zone_finder, exact_match\n\
+\n\
+Look for a data source containing the given zone.\n\
+\n\
+It searches through the contained data sources and returns a data source\
+containing the zone, the zone finder of the zone and a boolean if the answer\
+is an exact match.\n\
+\n\
+The first parameter is isc.dns.Name object of a name in the zone. If the\
+want_exact_match is True, only zone with this exact origin is returned.\
+If it is False, the best matching zone is returned.\n\
+\n\
+If the want_finder is False, the returned zone_finder might be None even\
+if the data source is identified (in such case, the datasrc_client is not\
+None). Setting it to false allows the client list some optimisations, if\
+you don't need it, but if you do need it, it is better to set it to True\
+instead of getting it from the datasrc_client later.\n\
+\n\
+If no answer is found, the datasrc_client and zone_finder are None." },
+    { NULL, NULL, 0, NULL }
+};
+
+const char* const ConfigurableClientList_doc = "\
+The list of data source clients\n\
+\n\
+The purpose is to have several data source clients of the same class\
+and then be able to search through them to identify the one containing\
+a given zone.\n\
+\n\
+Unlike the C++ version, we don't have the abstract base class. Abstract\
+classes are not needed due to the duck typing nature of python.\
+";
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_ConfigurableClientList
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject configurableclientlist_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "datasrc.ConfigurableClientList",
+    sizeof(s_ConfigurableClientList),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    ConfigurableClientList_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
+    ConfigurableClientList_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    ConfigurableClientList_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
+    ConfigurableClientList_init,                    // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_ConfigurableClientList(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(&configurableclientlist_type) < 0) {
+        return (false);
+    }
+    void* p = &configurableclientlist_type;
+    if (PyModule_AddObject(mod, "ConfigurableClientList",
+                           static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&configurableclientlist_type);
+
+    return (true);
+}
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc

+ 44 - 0
src/lib/python/isc/datasrc/configurableclientlist_python.h

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

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

@@ -28,6 +28,7 @@
 #include "iterator_python.h"
 #include "updater_python.h"
 #include "journal_reader_python.h"
+#include "configurableclientlist_python.h"
 
 #include <util/python/pycppwrapper_util.h>
 #include <dns/python/pydnspp_common.h>
@@ -284,6 +285,11 @@ PyInit_datasrc(void) {
         return (NULL);
     }
 
+    if (!initModulePart_ConfigurableClientList(mod)) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
     try {
         po_DataSourceError = PyErr_NewException("isc.datasrc.Error", NULL,
                                                 NULL);

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

@@ -1,7 +1,7 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 # old tests, TODO remove or change to use new API?
 #PYTESTS = master_test.py
-PYTESTS =  datasrc_test.py sqlite3_ds_test.py
+PYTESTS =  datasrc_test.py sqlite3_ds_test.py clientlist_test.py
 EXTRA_DIST = $(PYTESTS)
 
 EXTRA_DIST += testdata/brokendb.sqlite3
@@ -36,6 +36,7 @@ endif
 	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) \
+	GLOBAL_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
 	B10_FROM_BUILD=$(abs_top_builddir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

+ 146 - 0
src/lib/python/isc/datasrc/tests/clientlist_test.py

@@ -0,0 +1,146 @@
+# Copyright (C) 2012  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 sys
+
+TESTDATA_PATH = os.environ['GLOBAL_TESTDATA_PATH'] + os.sep
+
+class ClientListTest(unittest.TestCase):
+    """
+    Test cases for the client lists. Currently, the python wrappers
+    contain the ConfigurableClientList only.
+    """
+
+    def test_constructors(self):
+        """
+        Test the constructor. It should accept an RRClass. Check it
+        reject invalid inputs.
+        """
+        isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN())
+        isc.datasrc.ConfigurableClientList(isc.dns.RRClass.CH())
+        # Not enough arguments
+        self.assertRaises(TypeError, isc.datasrc.ConfigurableClientList)
+        # Bad types of arguments
+        self.assertRaises(TypeError, isc.datasrc.ConfigurableClientList, 0)
+        self.assertRaises(TypeError, isc.datasrc.ConfigurableClientList, "IN")
+        # Too many arguments
+        self.assertRaises(TypeError, isc.datasrc.ConfigurableClientList,
+                         isc.dns.RRClass.IN(), isc.dns.RRClass.IN())
+
+    def test_configure(self):
+        """
+        Test we can configure the client list. This tests if the valid
+        ones are acceptend and invalid rejected. We check the changes
+        have effect.
+        """
+        clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN())
+        # This should be NOP now
+        clist.configure("[]", True)
+        # Check the zone is not there yet
+        dsrc, finder, exact = clist.find(isc.dns.Name("example.org"))
+        self.assertIsNone(dsrc)
+        self.assertIsNone(finder)
+        self.assertFalse(exact)
+        # We can use this type, as it is not loaded dynamically.
+        clist.configure('''[{
+            "type": "MasterFiles",
+            "params": {
+                "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
+            },
+            "cache-enable": true
+        }]''', True)
+        # Check the zone is there now. Proper tests of find are in other
+        # test methods.
+        dsrc, finder, exact = clist.find(isc.dns.Name("example.org"))
+        self.assertIsNotNone(dsrc)
+        self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
+        self.assertIsNotNone(finder)
+        self.assertTrue(isinstance(finder, isc.datasrc.ZoneFinder))
+        self.assertTrue(exact)
+        self.assertRaises(isc.datasrc.Error, clist.configure, '"bad type"',
+                          True)
+        self.assertRaises(isc.datasrc.Error, clist.configure, '''[{
+            "type": "bad type"
+        }]''', True)
+        self.assertRaises(isc.datasrc.Error, clist.configure, '''[{
+            bad JSON,
+        }]''', True)
+        self.assertRaises(TypeError, clist.configure, [], True)
+        self.assertRaises(TypeError, clist.configure, "[]")
+        self.assertRaises(TypeError, clist.configure, "[]", "true")
+
+    def test_find(self):
+        """
+        Test the find accepts the right arguments, some of them can be omitted,
+        etc.
+        """
+        clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN())
+        clist.configure('''[{
+            "type": "MasterFiles",
+            "params": {
+                "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
+            },
+            "cache-enable": true
+        }]''', True)
+        dsrc, finder, exact = clist.find(isc.dns.Name("sub.example.org"))
+        self.assertIsNotNone(dsrc)
+        self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
+        self.assertIsNotNone(finder)
+        self.assertTrue(isinstance(finder, isc.datasrc.ZoneFinder))
+        # Check the finder holds a reference to the data source
+        # Note that one reference is kept in the parameter list
+        # of getrefcount
+        self.assertEqual(3, sys.getrefcount(dsrc))
+        finder = None
+        self.assertEqual(2, sys.getrefcount(dsrc))
+        # We check an exact match in test_configure already
+        self.assertFalse(exact)
+        dsrc, finder, exact = clist.find(isc.dns.Name("sub.example.org"),
+                                         False)
+        self.assertIsNotNone(dsrc)
+        self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
+        self.assertIsNotNone(finder)
+        self.assertTrue(isinstance(finder, isc.datasrc.ZoneFinder))
+        self.assertFalse(exact)
+        dsrc, finder, exact = clist.find(isc.dns.Name("sub.example.org"),
+                                         True)
+        self.assertIsNone(dsrc)
+        self.assertIsNone(finder)
+        self.assertFalse(exact)
+        dsrc, finder, exact = clist.find(isc.dns.Name("sub.example.org"),
+                                         False, False)
+        self.assertIsNotNone(dsrc)
+        self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
+        self.assertIsNotNone(finder)
+        self.assertTrue(isinstance(finder, isc.datasrc.ZoneFinder))
+        self.assertFalse(exact)
+        dsrc, finder, exact = clist.find(isc.dns.Name("sub.example.org"),
+                                         True, False)
+        self.assertIsNone(dsrc)
+        self.assertIsNone(finder)
+        self.assertFalse(exact)
+        # Some invalid inputs
+        self.assertRaises(TypeError, clist.find, "example.org")
+        self.assertRaises(TypeError, clist.find)
+
+if __name__ == "__main__":
+    isc.log.init("bind10")
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()