Browse Source

[master] Merge branch 'trac1206'

Conflicts:
	src/lib/datasrc/sqlite3_accessor.cc
Jelte Jansen 13 years ago
parent
commit
1378551aa7

+ 6 - 0
src/bin/auth/Makefile.am

@@ -50,6 +50,12 @@ b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += common.h common.cc
 b10_auth_SOURCES += common.h common.cc
 b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += main.cc
 b10_auth_SOURCES += main.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+b10_auth_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
 
 
 nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
 nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
 EXTRA_DIST += auth_messages.mes
 EXTRA_DIST += auth_messages.mes

+ 6 - 0
src/bin/auth/benchmarks/Makefile.am

@@ -13,6 +13,12 @@ query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
 query_bench_SOURCES += ../auth_config.h ../auth_config.cc
 query_bench_SOURCES += ../auth_config.h ../auth_config.cc
 query_bench_SOURCES += ../statistics.h ../statistics.cc
 query_bench_SOURCES += ../statistics.h ../statistics.cc
 query_bench_SOURCES += ../auth_log.h ../auth_log.cc
 query_bench_SOURCES += ../auth_log.h ../auth_log.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+query_bench_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
 
 
 nodist_query_bench_SOURCES = ../auth_messages.h ../auth_messages.cc
 nodist_query_bench_SOURCES = ../auth_messages.h ../auth_messages.cc
 
 

+ 7 - 0
src/bin/auth/tests/Makefile.am

@@ -37,6 +37,13 @@ run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+run_unittests_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
+
 
 
 nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
 nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
 
 

+ 9 - 3
src/lib/datasrc/Makefile.am

@@ -9,7 +9,7 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
 CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
 CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
 
 
-lib_LTLIBRARIES = libdatasrc.la
+lib_LTLIBRARIES = libdatasrc.la sqlite3_ds.la memory_ds.la
 libdatasrc_la_SOURCES = data_source.h data_source.cc
 libdatasrc_la_SOURCES = data_source.h data_source.cc
 libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
 libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
@@ -17,15 +17,21 @@ libdatasrc_la_SOURCES += query.h query.cc
 libdatasrc_la_SOURCES += cache.h cache.cc
 libdatasrc_la_SOURCES += cache.h cache.cc
 libdatasrc_la_SOURCES += rbtree.h
 libdatasrc_la_SOURCES += rbtree.h
 libdatasrc_la_SOURCES += zonetable.h zonetable.cc
 libdatasrc_la_SOURCES += zonetable.h zonetable.cc
-libdatasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
 libdatasrc_la_SOURCES += zone.h
 libdatasrc_la_SOURCES += zone.h
 libdatasrc_la_SOURCES += result.h
 libdatasrc_la_SOURCES += result.h
 libdatasrc_la_SOURCES += logger.h logger.cc
 libdatasrc_la_SOURCES += logger.h logger.cc
 libdatasrc_la_SOURCES += client.h iterator.h
 libdatasrc_la_SOURCES += client.h iterator.h
 libdatasrc_la_SOURCES += database.h database.cc
 libdatasrc_la_SOURCES += database.h database.cc
-libdatasrc_la_SOURCES += sqlite3_accessor.h sqlite3_accessor.cc
+#libdatasrc_la_SOURCES += sqlite3_accessor.h sqlite3_accessor.cc
+libdatasrc_la_SOURCES += factory.h factory.cc
 nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 
 
+sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
+sqlite3_ds_la_LDFLAGS = -module
+
+memory_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
+memory_ds_la_LDFLAGS = -module
+
 libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la

+ 44 - 0
src/lib/datasrc/client.h

@@ -22,6 +22,47 @@
 
 
 #include <datasrc/zone.h>
 #include <datasrc/zone.h>
 
 
+/// \file
+/// Datasource clients
+///
+/// The data source client API is specified in client.h, and provides the
+/// functionality to query and modify data in the data sources. There are
+/// multiple datasource implementations, and by subclassing DataSourceClient or
+/// DatabaseClient, more can be added.
+///
+/// All datasources are implemented as loadable modules, with a name of the
+/// form "<type>_ds.so". This has been chosen intentionally, to minimize
+/// confusion and potential mistakes.
+///
+/// In order to use a datasource client backend, the class
+/// DataSourceClientContainer is provided in factory.h; this will load the
+/// library, set up the instance, and clean everything up once it is destroyed.
+///
+/// Access to the actual instance is provided with the getInstance() method
+/// in DataSourceClientContainer
+///
+/// \note Depending on actual usage, we might consider making the container
+/// a transparent abstraction layer, so it can be used as a DataSourceClient
+/// directly. This has some other implications though so for now the only access
+/// provided is through getInstance()).
+///
+/// For datasource backends, we use a dynamically loaded library system (with
+/// dlopen()). This library must contain the following things;
+/// - A subclass of DataSourceClient or DatabaseClient (which itself is a
+///   subclass of DataSourceClient)
+/// - A creator function for an instance of that subclass, of the form:
+/// \code
+/// extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr cfg);
+/// \endcode
+/// - A destructor for said instance, of the form:
+/// \code
+/// extern "C" void destroyInstance(isc::data::DataSourceClient* instance);
+/// \endcode
+///
+/// See the documentation for the \link DataSourceClient \endlink class for
+/// more information on implementing subclasses of it.
+///
+
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
 
 
@@ -39,6 +80,9 @@ typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
 /// operations to other classes; in general methods of this class act as
 /// operations to other classes; in general methods of this class act as
 /// factories of these other classes.
 /// factories of these other classes.
 ///
 ///
+/// See \link datasrc/client.h datasrc/client.h \endlink for more information
+/// on adding datasource implementations.
+///
 /// The following derived classes are currently (expected to be) provided:
 /// The following derived classes are currently (expected to be) provided:
 /// - \c InMemoryClient: A client of a conceptual data source that stores
 /// - \c InMemoryClient: A client of a conceptual data source that stores
 /// all necessary data in memory for faster lookups
 /// all necessary data in memory for faster lookups

+ 78 - 0
src/lib/datasrc/factory.cc

@@ -0,0 +1,78 @@
+// 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.
+
+#include "factory.h"
+
+#include "data_source.h"
+#include "database.h"
+#include "sqlite3_accessor.h"
+#include "memory_datasrc.h"
+
+#include <datasrc/logger.h>
+
+#include <dlfcn.h>
+
+using namespace isc::data;
+using namespace isc::datasrc;
+
+namespace isc {
+namespace datasrc {
+
+LibraryContainer::LibraryContainer(const std::string& name) {
+    ds_lib_ = dlopen(name.c_str(), RTLD_NOW | RTLD_LOCAL);
+    if (ds_lib_ == NULL) {
+        isc_throw(DataSourceLibraryError, dlerror());
+    }
+}
+
+LibraryContainer::~LibraryContainer() {
+    dlclose(ds_lib_);
+}
+
+void*
+LibraryContainer::getSym(const char* name) {
+    // Since dlsym can return NULL on success, we check for errors by
+    // first clearing any existing errors with dlerror(), then calling dlsym,
+    // and finally checking for errors with dlerror()
+    dlerror();
+
+    void *sym = dlsym(ds_lib_, name);
+
+    const char* dlsym_error = dlerror();
+    if (dlsym_error != NULL) {
+        isc_throw(DataSourceLibrarySymbolError, dlsym_error);
+    }
+
+    return (sym);
+}
+
+DataSourceClientContainer::DataSourceClientContainer(const std::string& type,
+                                                     ConstElementPtr config)
+: ds_lib_(type + "_ds.so")
+{
+    ds_creator* ds_create =
+        reinterpret_cast<ds_creator*>(ds_lib_.getSym("createInstance"));
+    destructor_ =
+        reinterpret_cast<ds_destructor*>(ds_lib_.getSym("destroyInstance"));
+
+    instance_ = ds_create(config);
+}
+
+DataSourceClientContainer::~DataSourceClientContainer() {
+    destructor_(instance_);
+}
+
+} // end namespace datasrc
+} // end namespace isc
+

+ 178 - 0
src/lib/datasrc/factory.h

@@ -0,0 +1,178 @@
+// 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 __DATA_SOURCE_FACTORY_H
+#define __DATA_SOURCE_FACTORY_H 1
+
+#include <boost/noncopyable.hpp>
+
+#include <datasrc/data_source.h>
+#include <datasrc/client.h>
+#include <exceptions/exceptions.h>
+
+#include <cc/data.h>
+
+namespace isc {
+namespace datasrc {
+
+
+/// \brief Raised if there is an error loading the datasource implementation
+///        library
+class DataSourceLibraryError : public DataSourceError {
+public:
+    DataSourceLibraryError(const char* file, size_t line, const char* what) :
+        DataSourceError(file, line, what) {}
+};
+
+/// \brief Raised if there is an error reading a symbol from the datasource
+///        implementation library
+class DataSourceLibrarySymbolError : public DataSourceError {
+public:
+    DataSourceLibrarySymbolError(const char* file, size_t line,
+                                 const char* what) :
+        DataSourceError(file, line, what) {}
+};
+
+/// \brief Raised if the given config contains bad data
+///
+/// Depending on the datasource type, the configuration may differ (for
+/// instance, the sqlite3 datasource needs a database file).
+class DataSourceConfigError : public DataSourceError {
+public:
+    DataSourceConfigError(const char* file, size_t line, const char* what) :
+        DataSourceError(file, line, what) {}
+};
+
+typedef DataSourceClient* ds_creator(isc::data::ConstElementPtr config);
+typedef void ds_destructor(DataSourceClient* instance);
+
+/// \brief Container class for dynamically loaded libraries
+///
+/// This class is used to dlopen() a library, provides access to dlsym(),
+/// and cleans up the dlopened library when the instance of this class is
+/// destroyed.
+///
+/// Its main function is to provide RAII-style access to dlopen'ed libraries.
+///
+/// \note Currently it is Datasource-backend specific. If we have need for this
+///       in other places than for dynamically loading datasources, then, apart
+///       from moving it to another location, we also need to make the
+///       exceptions raised more general.
+class LibraryContainer : boost::noncopyable {
+public:
+    /// \brief Constructor
+    ///
+    /// \param name The name of the library (.so) file. This file must be in
+    ///             the library path.
+    ///
+    /// \exception DataSourceLibraryError If the library cannot be found or
+    ///            cannot be loaded.
+    LibraryContainer(const std::string& name);
+
+    /// \brief Destructor
+    ///
+    /// Cleans up the library by calling dlclose()
+    ~LibraryContainer();
+
+    /// \brief Retrieve a symbol
+    ///
+    /// This retrieves a symbol from the loaded library.
+    ///
+    /// \exception DataSourceLibrarySymbolError if the symbol cannot be found,
+    ///            or if another error (as reported by dlerror() occurs.
+    ///
+    /// \param name The name of the symbol to retrieve
+    /// \return A pointer to the symbol. This may be NULL, and if so, indicates
+    ///         the symbol does indeed exist, but has the value NULL itself.
+    ///         If the symbol does not exist, a DataSourceLibrarySymbolError is
+    ///         raised.
+    ///
+    /// \note The argument is a const char* (and not a std::string like the
+    ///       argument in the constructor). This argument is always a fixed
+    ///       string in the code, while the other can be read from
+    ///       configuration, and needs modification
+    void* getSym(const char* name);
+private:
+    /// Pointer to the dynamically loaded library structure
+    void *ds_lib_;
+};
+
+
+/// \brief Container for a specific instance of a dynamically loaded
+///        DataSourceClient implementation
+///
+/// Given a datasource type and a type-specific set of configuration data,
+/// the corresponding dynamic library is loaded (if it hadn't been already),
+/// and an instance is created. This instance is stored within this structure,
+/// and can be accessed through getInstance(). Upon destruction of this
+/// container, the stored instance of the DataSourceClient is deleted with
+/// the destructor function provided by the loaded library.
+///
+/// The 'type' is actually the name of the library, minus the '_ds.so' postfix
+/// Datasource implementation libraries therefore have a fixed name, both for
+/// easy recognition and to reduce potential mistakes.
+/// For example, the sqlite3 implementation has the type 'sqlite3', and the
+/// derived filename 'sqlite3_ds.so'
+///
+/// There are of course some demands to an implementation, not all of which
+/// can be verified compile-time. It must provide a creator and destructor
+/// functions. The creator function must return an instance of a subclass of
+/// DataSourceClient. The prototypes of these functions are as follows:
+/// \code
+/// extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr cfg);
+///
+/// extern "C" void destroyInstance(isc::data::DataSourceClient* instance);
+/// \endcode
+class DataSourceClientContainer : boost::noncopyable {
+public:
+    /// \brief Constructor
+    ///
+    /// \exception DataSourceLibraryError if there is an error loading the
+    ///            backend library
+    /// \exception DataSourceLibrarySymbolError if the library does not have
+    ///            the needed symbols, or if there is an error reading them
+    /// \exception DataSourceConfigError if the given config is not correct
+    ///            for the given type
+    ///
+    /// \param type The type of the datasource client. Based on the value of
+    ///             type, a specific backend library is used, by appending the
+    ///             string '_ds.so' to the given type, and loading that as the
+    ///             implementation library
+    /// \param config Type-specific configuration data, see the documentation
+    ///               of the datasource backend type for information on what
+    ///               configuration data to pass.
+    DataSourceClientContainer(const std::string& type,
+                              isc::data::ConstElementPtr config);
+
+    /// \brief Destructor
+    ~DataSourceClientContainer();
+
+    /// \brief Accessor to the instance
+    ///
+    /// \return Reference to the DataSourceClient instance contained in this
+    ///         container.
+    DataSourceClient& getInstance() { return *instance_; }
+
+private:
+    DataSourceClient* instance_;
+    ds_destructor* destructor_;
+    LibraryContainer ds_lib_;
+};
+
+} // end namespace datasrc
+} // end namespace isc
+#endif  // DATA_SOURCE_FACTORY_H
+// Local Variables:
+// mode: c++
+// End:

+ 140 - 1
src/lib/datasrc/memory_datasrc.cc

@@ -16,6 +16,7 @@
 #include <cassert>
 #include <cassert>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
+#include <boost/foreach.hpp>
 
 
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -29,9 +30,13 @@
 #include <datasrc/logger.h>
 #include <datasrc/logger.h>
 #include <datasrc/iterator.h>
 #include <datasrc/iterator.h>
 #include <datasrc/data_source.h>
 #include <datasrc/data_source.h>
+#include <datasrc/factory.h>
+
+#include <cc/data.h>
 
 
 using namespace std;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::data;
 
 
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
@@ -805,5 +810,139 @@ ZoneUpdaterPtr
 InMemoryClient::getUpdater(const isc::dns::Name&, bool) const {
 InMemoryClient::getUpdater(const isc::dns::Name&, bool) const {
     isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
     isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
 }
 }
+
+
+namespace {
+// convencience function to add an error message to a list of those
+// (TODO: move functions like these to some util lib?)
+void
+addError(ElementPtr errors, const std::string& error) {
+    if (errors != ElementPtr() && errors->getType() == Element::list) {
+        errors->add(Element::create(error));
+    }
+}
+
+/// Check if the given element exists in the map, and if it is a string
+bool
+checkConfigElementString(ConstElementPtr config, const std::string& name,
+                         ElementPtr errors)
+{
+    if (!config->contains(name)) {
+        addError(errors,
+                 "Config for memory backend does not contain a '"
+                 "type"
+                 "' value");
+        return false;
+    } else if (!config->get(name) ||
+               config->get(name)->getType() != Element::string) {
+        addError(errors, "value of " + name +
+                 " in memory backend config is not a string");
+        return false;
+    } else {
+        return true;
+    }
+}
+
+bool
+checkZoneConfig(ConstElementPtr config, ElementPtr errors) {
+    bool result = true;
+    if (!config || config->getType() != Element::map) {
+        addError(errors, "Elements in memory backend's zone list must be maps");
+        result = false;
+    } else {
+        if (!checkConfigElementString(config, "origin", errors)) {
+            result = false;
+        }
+        if (!checkConfigElementString(config, "file", errors)) {
+            result = false;
+        }
+        // we could add some existence/readabilty/parsability checks here
+        // if we want
+    }
+    return result;
+}
+
+bool
+checkConfig(ConstElementPtr config, ElementPtr errors) {
+    /* Specific configuration is under discussion, right now this accepts
+     * the 'old' configuration, see [TODO]
+     * So for memory datasource, we get a structure like this:
+     * { "type": string ("memory"),
+     *   "class": string ("IN"/"CH"/etc),
+     *   "zones": list
+     * }
+     * Zones list is a list of maps:
+     * { "origin": string,
+     *     "file": string
+     * }
+     *
+     * At this moment we cannot be completely sure of the contents of the
+     * structure, so we have to do some more extensive tests than should
+     * strictly be necessary (e.g. existence and type of elements)
+     */
+    bool result = true;
+
+    if (!config || config->getType() != Element::map) {
+        addError(errors, "Base config for memory backend must be a map");
+        result = false;
+    } else {
+        if (!checkConfigElementString(config, "type", errors)) {
+            result = false;
+        } else {
+            if (config->get("type")->stringValue() != "memory") {
+                addError(errors,
+                         "Config for memory backend is not of type \"memory\"");
+                result = false;
+            }
+        }
+        if (!checkConfigElementString(config, "class", errors)) {
+            result = false;
+        } else {
+            try {
+                RRClass rrc(config->get("class")->stringValue());
+            } catch (const isc::Exception& rrce) {
+                addError(errors,
+                         "Error parsing class config for memory backend: " +
+                         std::string(rrce.what()));
+                result = false;
+            }
+        }
+        if (!config->contains("zones")) {
+            addError(errors, "No 'zones' element in memory backend config");
+            result = false;
+        } else if (!config->get("zones") ||
+                   config->get("zones")->getType() != Element::list) {
+            addError(errors, "'zones' element in memory backend config is not a list");
+            result = false;
+        } else {
+            BOOST_FOREACH(ConstElementPtr zone_config,
+                          config->get("zones")->listValue()) {
+                if (!checkZoneConfig(zone_config, errors)) {
+                    result = false;
+                }
+            }
+        }
+    }
+
+    return (result);
+    return true;
+}
+
+} // end anonymous namespace
+
+DataSourceClient *
+createInstance(isc::data::ConstElementPtr config) {
+    ElementPtr errors(Element::createList());
+    if (!checkConfig(config, errors)) {
+        isc_throw(DataSourceConfigError, errors->str());
+    }
+    return (new InMemoryClient());
+}
+
+void destroyInstance(DataSourceClient* instance) {
+    delete instance;
+}
+
+
 } // end of namespace datasrc
 } // end of namespace datasrc
-} // end of namespace dns
+} // end of namespace isc

+ 28 - 1
src/lib/datasrc/memory_datasrc.h

@@ -22,6 +22,8 @@
 #include <datasrc/zonetable.h>
 #include <datasrc/zonetable.h>
 #include <datasrc/client.h>
 #include <datasrc/client.h>
 
 
+#include <cc/data.h>
+
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 class Name;
 class Name;
@@ -219,7 +221,7 @@ private:
 /// while it wouldn't be safe to delete unnecessary zones inside the dedicated
 /// while it wouldn't be safe to delete unnecessary zones inside the dedicated
 /// backend.
 /// backend.
 ///
 ///
-/// The findZone() method takes a domain name and returns the best matching 
+/// The findZone() method takes a domain name and returns the best matching
 /// \c InMemoryZoneFinder in the form of (Boost) shared pointer, so that it can
 /// \c InMemoryZoneFinder in the form of (Boost) shared pointer, so that it can
 /// provide the general interface for all data sources.
 /// provide the general interface for all data sources.
 class InMemoryClient : public DataSourceClient {
 class InMemoryClient : public DataSourceClient {
@@ -289,6 +291,31 @@ private:
     class InMemoryClientImpl;
     class InMemoryClientImpl;
     InMemoryClientImpl* impl_;
     InMemoryClientImpl* impl_;
 };
 };
+
+/// \brief Creates an instance of the Memory datasource client
+///
+/// Currently the configuration passed here must be a MapElement, formed as
+/// follows:
+/// \code
+/// { "type": string ("memory"),
+///   "class": string ("IN"/"CH"/etc),
+///   "zones": list
+/// }
+/// Zones list is a list of maps:
+/// { "origin": string,
+///   "file": string
+/// }
+/// \endcode
+/// (i.e. the configuration that was used prior to the datasource refactor)
+///
+/// This configuration setup is currently under discussion and will change in
+/// the near future.
+extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config);
+
+/// \brief Destroy the instance created by createInstance()
+extern "C" void destroyInstance(DataSourceClient* instance);
+
+
 }
 }
 }
 }
 #endif  // __DATA_SOURCE_MEMORY_H
 #endif  // __DATA_SOURCE_MEMORY_H

+ 64 - 0
src/lib/datasrc/sqlite3_accessor.cc

@@ -22,12 +22,16 @@
 #include <datasrc/sqlite3_accessor.h>
 #include <datasrc/sqlite3_accessor.h>
 #include <datasrc/logger.h>
 #include <datasrc/logger.h>
 #include <datasrc/data_source.h>
 #include <datasrc/data_source.h>
+#include <datasrc/factory.h>
 #include <util/filename.h>
 #include <util/filename.h>
 
 
 using namespace std;
 using namespace std;
+using namespace isc::data;
 
 
 #define SQLITE_SCHEMA_VERSION 1
 #define SQLITE_SCHEMA_VERSION 1
 
 
+#define CONFIG_ITEM_DATABASE_FILE "database_file"
+
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
 
 
@@ -711,5 +715,65 @@ SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
     return (result);
     return (result);
 }
 }
 
 
+namespace {
+void
+addError(ElementPtr errors, const std::string& error) {
+    if (errors != ElementPtr() && errors->getType() == Element::list) {
+        errors->add(Element::create(error));
+    }
 }
 }
+
+bool
+checkConfig(ConstElementPtr config, ElementPtr errors) {
+    /* Specific configuration is under discussion, right now this accepts
+     * the 'old' configuration, see header file
+     */
+    bool result = true;
+
+    if (!config || config->getType() != Element::map) {
+        addError(errors, "Base config for SQlite3 backend must be a map");
+        result = false;
+    } else {
+        if (!config->contains(CONFIG_ITEM_DATABASE_FILE)) {
+            addError(errors,
+                     "Config for SQlite3 backend does not contain a '"
+                     CONFIG_ITEM_DATABASE_FILE
+                     "' value");
+            result = false;
+        } else if (!config->get(CONFIG_ITEM_DATABASE_FILE) ||
+                   config->get(CONFIG_ITEM_DATABASE_FILE)->getType() !=
+                   Element::string) {
+            addError(errors, "value of " CONFIG_ITEM_DATABASE_FILE
+                     " in SQLite3 backend is not a string");
+            result = false;
+        } else if (config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue() ==
+                   "") {
+            addError(errors, "value of " CONFIG_ITEM_DATABASE_FILE
+                     " in SQLite3 backend is empty");
+            result = false;
+        }
+    }
+
+    return (result);
 }
 }
+
+} // end anonymous namespace
+
+DataSourceClient *
+createInstance(isc::data::ConstElementPtr config) {
+    ElementPtr errors(Element::createList());
+    if (!checkConfig(config, errors)) {
+        isc_throw(DataSourceConfigError, errors->str());
+    }
+    std::string dbfile = config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue();
+    boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
+        new SQLite3Accessor(dbfile, isc::dns::RRClass::IN()));
+    return (new DatabaseClient(isc::dns::RRClass::IN(), sqlite3_accessor));
+}
+
+void destroyInstance(DataSourceClient* instance) {
+    delete instance;
+}
+
+} // end of namespace datasrc
+} // end of namespace isc

+ 14 - 0
src/lib/datasrc/sqlite3_accessor.h

@@ -24,6 +24,8 @@
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <string>
 #include <string>
 
 
+#include <cc/data.h>
+
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 class RRClass;
 class RRClass;
@@ -191,6 +193,18 @@ private:
     const std::string database_name_;
     const std::string database_name_;
 };
 };
 
 
+/// \brief Creates an instance of the SQlite3 datasource client
+///
+/// Currently the configuration passed here must be a MapElement, containing
+/// one item called "database_file", whose value is a string
+///
+/// This configuration setup is currently under discussion and will change in
+/// the near future.
+extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config);
+
+/// \brief Destroy the instance created by createInstance()
+extern "C" void destroyInstance(DataSourceClient* instance);
+
 }
 }
 }
 }
 
 

+ 7 - 2
src/lib/datasrc/tests/Makefile.am

@@ -29,12 +29,17 @@ run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += cache_unittest.cc
 run_unittests_SOURCES += cache_unittest.cc
 run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
 run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
 run_unittests_SOURCES += rbtree_unittest.cc
 run_unittests_SOURCES += rbtree_unittest.cc
-run_unittests_SOURCES += zonetable_unittest.cc
-run_unittests_SOURCES += memory_datasrc_unittest.cc
+#run_unittests_SOURCES += zonetable_unittest.cc
+#run_unittests_SOURCES += memory_datasrc_unittest.cc
 run_unittests_SOURCES += logger_unittest.cc
 run_unittests_SOURCES += logger_unittest.cc
 run_unittests_SOURCES += database_unittest.cc
 run_unittests_SOURCES += database_unittest.cc
 run_unittests_SOURCES += client_unittest.cc
 run_unittests_SOURCES += client_unittest.cc
 run_unittests_SOURCES += sqlite3_accessor_unittest.cc
 run_unittests_SOURCES += sqlite3_accessor_unittest.cc
+run_unittests_SOURCES += factory_unittest.cc
+# for the dlopened types we have tests for, we also need to include the
+# sources
+run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/sqlite3_accessor.cc
+#run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/memory_datasrc.cc
 
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)

+ 175 - 0
src/lib/datasrc/tests/factory_unittest.cc

@@ -0,0 +1,175 @@
+// 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.
+
+#include <boost/scoped_ptr.hpp>
+
+#include <datasrc/factory.h>
+#include <datasrc/data_source.h>
+#include <datasrc/sqlite3_accessor.h>
+
+#include <dns/rrclass.h>
+#include <cc/data.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::datasrc;
+using namespace isc::data;
+
+std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
+
+namespace {
+
+TEST(FactoryTest, sqlite3ClientBadConfig) {
+    // We start out by building the configuration data bit by bit,
+    // testing each form of 'bad config', until we have a good one.
+    // Then we do some very basic operation on the client (detailed
+    // tests are left to the implementation-specific backends)
+    ElementPtr config;
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config = Element::create("asdf");
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config = Element::createMap();
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config->set("class", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config->set("class", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config->set("class", Element::create("FOO"));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config->set("class", Element::create("IN"));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config->set("database_file", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config->set("database_file", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceConfigError);
+
+    config->set("database_file", Element::create("/foo/bar/doesnotexist"));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 SQLite3Error);
+
+    config->set("database_file", Element::create(SQLITE_DBFILE_EXAMPLE_ORG));
+    DataSourceClientContainer dsc("sqlite3", config);
+
+    DataSourceClient::FindResult result1(
+        dsc.getInstance().findZone(isc::dns::Name("example.org.")));
+    ASSERT_EQ(result::SUCCESS, result1.code);
+
+    DataSourceClient::FindResult result2(
+        dsc.getInstance().findZone(isc::dns::Name("no.such.zone.")));
+    ASSERT_EQ(result::NOTFOUND, result2.code);
+
+    ZoneIteratorPtr iterator(dsc.getInstance().getIterator(
+        isc::dns::Name("example.org.")));
+
+    ZoneUpdaterPtr updater(dsc.getInstance().getUpdater(
+        isc::dns::Name("example.org."), false));
+}
+
+TEST(FactoryTest, memoryClient) {
+    // We start out by building the configuration data bit by bit,
+    // testing each form of 'bad config', until we have a good one.
+    // Then we do some very basic operation on the client (detailed
+    // tests are left to the implementation-specific backends)
+    ElementPtr config;
+    ASSERT_THROW(DataSourceClientContainer client("memory", config),
+                 DataSourceConfigError);
+
+    config = Element::create("asdf");
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config = Element::createMap();
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("type", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("type", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("type", Element::create("FOO"));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("type", Element::create("memory"));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("class", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("class", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("class", Element::create("FOO"));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("class", Element::create("IN"));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("zones", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("zones", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceConfigError);
+
+    config->set("zones", Element::createList());
+    DataSourceClientContainer dsc("memory", config);
+
+    // Once it is able to load some zones, we should add a few tests
+    // here to see that it does.
+    DataSourceClient::FindResult result(
+        dsc.getInstance().findZone(isc::dns::Name("no.such.zone.")));
+    ASSERT_EQ(result::NOTFOUND, result.code);
+
+    ASSERT_THROW(dsc.getInstance().getIterator(isc::dns::Name("example.org.")),
+                 DataSourceError);
+
+    ASSERT_THROW(dsc.getInstance().getUpdater(isc::dns::Name("no.such.zone."),
+                                              false), isc::NotImplemented);
+}
+
+TEST(FactoryTest, badType) {
+    ASSERT_THROW(DataSourceClientContainer("foo", ElementPtr()),
+                                           DataSourceError);
+}
+
+} // end anonymous namespace
+