]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#28] Added libkea-cb library.
authorMarcin Siodelski <marcin@isc.org>
Tue, 28 Aug 2018 10:01:15 +0000 (12:01 +0200)
committerMarcin Siodelski <marcin@isc.org>
Tue, 18 Sep 2018 05:06:26 +0000 (07:06 +0200)
14 files changed:
configure.ac
src/lib/Makefile.am
src/lib/config_backend/Makefile.am [new file with mode: 0644]
src/lib/config_backend/base_config_backend.h [new file with mode: 0644]
src/lib/config_backend/base_config_backend_mgr.h [new file with mode: 0644]
src/lib/config_backend/base_config_backend_pool.cc [new file with mode: 0644]
src/lib/config_backend/base_config_backend_pool.h [new file with mode: 0644]
src/lib/config_backend/config_backend_mgr.h [new file with mode: 0644]
src/lib/config_backend/tests/Makefile.am [new file with mode: 0644]
src/lib/config_backend/tests/config_backend_mgr_unittest.cc [new file with mode: 0644]
src/lib/config_backend/tests/config_backend_selector_unittest.cc [new file with mode: 0644]
src/lib/config_backend/tests/libcb_unittests [new file with mode: 0755]
src/lib/config_backend/tests/run_unittests.cc [new file with mode: 0644]
src/lib/database/db_exceptions.h

index e9f2b0b486a725f7262edfa1fbe06ca2bb179d1e..b784dfc19a970b5b0ea1dd7cd33d160bb6f886fc 100644 (file)
@@ -1534,6 +1534,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/config/tests/Makefile
                  src/lib/config/tests/data_def_unittests_config.h
                  src/lib/config/tests/testdata/Makefile
+                 src/lib/config_backend/Makefile
+                 src/lib/config_backend/tests/Makefile
                  src/lib/cryptolink/Makefile
                  src/lib/cryptolink/tests/Makefile
                  src/lib/database/Makefile
index 319c99549062345c26e13faa1a294bb3d777abe4..211aafe810839806ae6bb763bd5a28e144643e1f 100644 (file)
@@ -13,7 +13,7 @@ if HAVE_CQL
 SUBDIRS += cql
 endif
 
-SUBDIRS += testutils hooks dhcp config stats
+SUBDIRS += config_backend testutils hooks dhcp config stats
 
 if HAVE_SYSREPO
 SUBDIRS += yang
diff --git a/src/lib/config_backend/Makefile.am b/src/lib/config_backend/Makefile.am
new file mode 100644 (file)
index 0000000..4e524d6
--- /dev/null
@@ -0,0 +1,29 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-cb.la
+libkea_cb_la_SOURCES  = base_config_backend.h
+libkea_cb_la_SOURCES += base_config_backend_mgr.h
+libkea_cb_la_SOURCES += base_config_backend_pool.cc base_config_backend_pool.h
+
+
+libkea_cb_la_LIBADD  = $(top_builddir)/src/lib/database/libkea-database.la
+libkea_cb_la_LIBADD  = $(top_builddir)/src/lib/util/libkea-util.la
+libkea_cb_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_cb_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_cb_la_LIBADD += $(BOOST_LIBS)
+
+libkea_cb_la_LDFLAGS = -no-undefined -version-info 0:0:0
+
+# The message file should be in the distribution.
+#EXTRA_DIST = config_backend.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+#libkea_cb_includedir = $(pkgincludedir)/config
+#libkea_cb_include_HEADERS = 
diff --git a/src/lib/config_backend/base_config_backend.h b/src/lib/config_backend/base_config_backend.h
new file mode 100644 (file)
index 0000000..9ec6f63
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BASE_CONFIG_BACKEND_H
+#define BASE_CONFIG_BACKEND_H
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace cb {
+
+class BaseConfigBackend {
+public:
+
+
+    virtual ~BaseConfigBackend() {
+    }
+
+    virtual std::string getType() const = 0;
+
+    virtual std::string getHost() const = 0;
+
+    virtual uint16_t getPort() const = 0;
+};
+
+typedef boost::shared_ptr<BaseConfigBackend> BaseConfigBackendPtr;
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif // BASE_CONFIG_BACKEND_H
diff --git a/src/lib/config_backend/base_config_backend_mgr.h b/src/lib/config_backend/base_config_backend_mgr.h
new file mode 100644 (file)
index 0000000..f727a9f
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BASE_CONFIG_BACKEND_MGR_H
+#define BASE_CONFIG_BACKEND_MGR_H
+
+#include <database/database_connection.h>
+#include <config_backend/base_config_backend.h>
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace cb {
+
+template<typename ConfigBackendPoolType>
+class BaseConfigBackendMgr {
+public:
+
+    typedef boost::shared_ptr<ConfigBackendPoolType> ConfigBackendPoolPtr;
+
+    typedef std::function<typename ConfigBackendPoolType::ConfigBackendTypePtr
+                          (const db::DatabaseConnection::ParameterMap&)> Factory;
+
+    BaseConfigBackendMgr()
+        : factories_(), backends_(new ConfigBackendPoolType()) {
+    }
+
+    bool registerBackendFactory(const std::string& db_type,
+                                const Factory& factory) {
+        if (factories_.count(db_type)) {
+            return (false);
+        }
+
+        factories_.insert(std::make_pair(db_type, factory));
+
+        return (true);
+    }
+
+    void addBackend(const std::string& dbaccess) {
+        // Parse the access string and create a redacted string for logging.
+        db::DatabaseConnection::ParameterMap parameters =
+            db::DatabaseConnection::parse(dbaccess);
+
+        // Get the database type and open the corresponding database
+        db::DatabaseConnection::ParameterMap::iterator it = parameters.find("type");
+        if (it == parameters.end()) {
+            isc_throw(InvalidParameter, "Host database configuration does not "
+                      "contain the 'type' keyword");
+        }
+
+        std::string db_type = it->second;
+        auto index = factories_.find(db_type);
+
+        // No match?
+        if (index == factories_.end()) {
+            isc_throw(db::InvalidType, "The type of host backend: '" <<
+                      db_type << "' is not currently supported");
+    }
+
+        // Call the factory and push the pointer on sources.
+        auto backend = index->second(parameters);
+        if (!backend) {
+            isc_throw(Unexpected, "Config database " << db_type <<
+                      " factory returned NULL");
+        }
+
+        backends_->addBackend(backend);
+    }
+
+    void delAllBackends() {
+        backends_->delAllBackends();
+    }
+
+    ConfigBackendPoolPtr getPool() const {
+        return (backends_);
+    }
+
+protected:
+
+    std::map<std::string, Factory> factories_;
+
+    ConfigBackendPoolPtr backends_;
+
+};
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif // BASE_CONFIG_BACKEND_MGR_H
diff --git a/src/lib/config_backend/base_config_backend_pool.cc b/src/lib/config_backend/base_config_backend_pool.cc
new file mode 100644 (file)
index 0000000..34175fa
--- /dev/null
@@ -0,0 +1,153 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config_backend/base_config_backend_pool.h>
+#include <exceptions/exceptions.h>
+#include <climits>
+#include <sstream>
+
+using namespace isc::data;
+
+namespace isc {
+namespace cb {
+
+BackendSelector::BackendSelector()
+    : backend_type_(BackendSelector::Type::UNSPEC),
+      host_(), port_(0) {
+}
+
+BackendSelector::BackendSelector(const Type& backend_type)
+    : backend_type_(backend_type),
+      host_(), port_(0) {
+}
+
+BackendSelector::BackendSelector(const std::string& host,
+                                             const uint16_t port)
+    : backend_type_(BackendSelector::Type::UNSPEC),
+      host_(host), port_(port) {
+    validate();
+}
+
+BackendSelector::BackendSelector(const data::ConstElementPtr& access_map)
+    : backend_type_(BackendSelector::Type::UNSPEC),
+      host_(), port_(0) {
+    if (access_map->getType() != Element::map) {
+        isc_throw(BadValue, "database access information must be a map");
+    }
+
+    ConstElementPtr t = access_map->get("type");
+    if (t) {
+        if (t->getType() != Element::string) {
+            isc_throw(BadValue, "'type' parameter must be a string");
+        }
+        backend_type_ = stringToBackendType(t->stringValue());
+    }
+
+    ConstElementPtr h = access_map->get("host");
+    if (h) {
+        if (h->getType() != Element::string) {
+            isc_throw(BadValue, "'host' parameter must be a string");
+        }
+        host_ = h->stringValue();
+    }
+
+    ConstElementPtr p = access_map->get("port");
+    if (p) {
+        if ((p->getType() != Element::integer) ||
+            (p->intValue() < 0) ||
+            (p->intValue() > std::numeric_limits<uint16_t>::max())) {
+            isc_throw(BadValue, "'port' parameter must be a number in range from 0 "
+                      "to " << std::numeric_limits<uint16_t>::max());
+        }
+        port_ = static_cast<uint16_t>(p->intValue());
+    }
+
+    validate();
+}
+
+const BackendSelector&
+BackendSelector::BackendSelector::UNSPEC() {
+    static BackendSelector selector;
+    return (selector);
+}
+
+bool
+BackendSelector::amUnspecified() const {
+    return ((backend_type_ == BackendSelector::Type::UNSPEC) &&
+            (host_.empty()) &&
+            (port_ == 0));
+}
+
+std::string
+BackendSelector::toText() const {
+    std::ostringstream s;
+    if (amUnspecified()) {
+        s << "unspecified";
+
+    } else {
+        if (backend_type_ != BackendSelector::Type::UNSPEC) {
+            s << "type=" << backendTypeToString(backend_type_) << ",";
+        }
+
+        if (!host_.empty()) {
+            s << "host=" << host_ << ",";
+
+            if (port_ > 0) {
+                s << "port=" << port_ << ",";
+            }
+        }
+    }
+
+    std::string text = s.str();
+    if ((!text.empty() && (text.back() == ','))) {
+        text.pop_back();
+    }
+
+    return (text);
+}
+
+BackendSelector::Type
+BackendSelector::stringToBackendType(const std::string& type) {
+    if (type == "mysql") {
+        return (BackendSelector::Type::MYSQL);
+
+    } else if (type == "pgsql") {
+        return (BackendSelector::Type::PGSQL);
+
+    } else if (type == "cql") {
+        return (BackendSelector::Type::CQL);
+
+    } else {
+        isc_throw(BadValue, "unsupported configuration backend type '" << type << "'");
+    }
+}
+
+std::string
+BackendSelector::backendTypeToString(const BackendSelector::Type& type) {
+    switch (type) {
+    case BackendSelector::Type::MYSQL:
+        return ("mysql");
+    case BackendSelector::Type::PGSQL:
+        return ("pgsql");
+    case BackendSelector::Type::CQL:
+        return ("cql");
+    default:
+        ;
+    }
+
+    return (std::string());
+}
+
+void
+BackendSelector::validate() const {
+    if ((port_ != 0) && (host_.empty())) {
+        isc_throw(BadValue, "'host' must be specified along with 'port' parameter");
+    }
+}
+
+
+} // end of namespace isc::cb
+} // end of namespace isc
diff --git a/src/lib/config_backend/base_config_backend_pool.h b/src/lib/config_backend/base_config_backend_pool.h
new file mode 100644 (file)
index 0000000..ea392ec
--- /dev/null
@@ -0,0 +1,586 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BASE_CONFIG_BACKEND_POOL_H
+#define BASE_CONFIG_BACKEND_POOL_H
+
+#include <cc/data.h>
+#include <config_backend/base_config_backend.h>
+#include <database/db_exceptions.h>
+#include <cstdint>
+#include <functional>
+#include <list>
+#include <string>
+
+namespace isc {
+namespace cb {
+
+/// @brief Config Backend selector.
+///
+/// Each Kea server using database as a configuration respository
+/// may use multiple configuration backends simultaneously. The most
+/// typical case is to use a single configuration backend per server,
+/// but there are use cases when configuration information is distributed
+/// accross multiple database instances. In the future, there may be
+/// also caching mechanisms implemented, which will allow for storing
+/// results of certain database queries in memory.
+///
+/// From the server perspective, the most common use of the configuration
+/// backend is to fetch entire configuration information from the
+/// databases (upon startup) or fetch the latest updates to the
+/// configuration, e.g. new subnet added, DHCP option modified etc.
+/// In those cases, it is not so important from the server which backend
+/// this data come from. Therefore, the server would fetch this information
+/// from all available backends.
+///
+/// When the server administrator wants to insert some new data into
+/// the database, modify existing data or simply wants to check the
+/// contents of one of the database instance, he would specify which
+/// database backend he wants to direct queries to.
+///
+/// The @c BackendSelector class provides means to specify whether
+/// the queries should be directed to any backend (see server case
+/// above) or to a specific backend (data insertion case above).
+/// In addition, the @c BackendSelector allows for using various
+/// criteria for selecting a backend to use. Currently those criteria
+/// are: database type (e.g. mysql), database host and database port.
+/// In order to use a specific port, the database host must also be
+/// specified. Note that in a general case multiple backends of the
+/// same type can be used simultaneously, e.g. multiple MySQL backends.
+/// In that case, it may be necessary to specify host (and port) to
+/// issue a query to the right one.
+///
+/// The @c BackendSelector class may be extended in the future to provide
+/// additional backend selection criteria.
+class BackendSelector {
+public:
+
+    /// @brief Supported database types.
+    ///
+    /// The @c UNSPEC indicates that the database type is not specified
+    /// as selection criteria.
+    enum class Type {
+        MYSQL,
+        PGSQL,
+        CQL,
+        UNSPEC
+    };
+
+    /// @brief Default constructor.
+    ///
+    /// It sets the selector to "unspecified". When this selector is used
+    /// the backend pool will use "any" backend. This has a different meaning
+    /// for each type of query. See the @c BaseConfigBackendPool for details.
+    explicit BackendSelector();
+
+    /// @brief Constructor specifying backend type as a selection criteria.
+    ///
+    /// @param backend_type Type of the backend to be selected.
+    explicit BackendSelector(const Type& backend_type);
+
+    /// @brief Constructor for specifying host and optionally port as a
+    /// selection criteria.
+    ///
+    /// @param host Hostname to be used for selecting a backend.
+    /// @param port Port number to be used for selecting a backend. This value
+    /// is optional and is ignored when set to 0. It must be used on conjuction
+    /// with hostname.
+    explicit BackendSelector(const std::string& host, const uint16_t port = 0);
+
+    /// @brief Constructor for selecting a backend using JSON access map.
+    ///
+    /// The provided access map must have the same structure as an element
+    /// of the "config-databases" configuration parameter. However, it merely
+    /// takes into account: "type", "host" and "port" parameters. In addition,
+    /// these parameters are optional. The following are valid combinations:
+    ///
+    /// @code
+    /// {
+    ///     "type": "mysql"
+    /// }
+    /// @endcode
+    ///
+    /// @code
+    /// {
+    ///     "host": "somehost.example.org"
+    /// }
+    /// @endcode
+    ///
+    /// @code
+    /// {
+    ///     "host": "somehost.example.org",
+    ///     "port": 1234
+    /// }
+    /// @endcode
+    ///
+    /// @code
+    /// {
+    ///     "type": "mysql"
+    ///     "host": "somehost.example.org",
+    /// }
+    /// @endcode
+    ///
+    /// @code
+    /// {
+    ///     "type": "mysql"
+    ///     "host": "somehost.example.org",
+    ///     "port": 1234
+    /// }
+    /// @endcode
+    ///
+    /// where "type" can be any of the supported backend types.
+    ///
+    /// This constructor is useful for creating backend selectors from the
+    /// received control commands.
+    ///
+    /// @param access_map Access map as provided above.
+    explicit BackendSelector(const data::ConstElementPtr& access_map);
+
+    /// @brief Returns instance of the "unspecified" backend selector.
+    static const BackendSelector& UNSPEC();
+
+    /// @brief Checks if selector is "unspecified".
+    ///
+    /// @return true if backend type is @c UNSPEC, hostname is empty and
+    /// port number 0, false otherwise.
+    bool amUnspecified() const;
+
+    /// @brief Returns backend type selected.
+    Type getBackendType() const {
+        return (backend_type_);
+    }
+
+    /// @brief Returns host selected.
+    ///
+    /// @return host if specified or empty string if host is not
+    /// specified.
+    std::string getBackendHost() const {
+        return (host_);
+    }
+
+    /// @brief Returns port selected.
+    ///
+    /// @return port number of the selected backend or 0 if port number
+    /// is not specified.
+    uint16_t getBackendPort() const {
+        return (port_);
+    }
+
+    /// @brief Returns selections as text.
+    ///
+    /// @return Collection of comma separated selections, e.g.
+    /// "type=mysql,host=somehost.example.org,port=1234".
+    std::string toText() const;
+
+    /// @brief Converts string to backend type.
+    ///
+    /// @param type Backend type as string.
+    static Type stringToBackendType(const std::string& type);
+
+    /// @brief Converts backend type to string.
+    ///
+    /// @param type Backend type to be converted.
+    static std::string backendTypeToString(const Type& type);
+
+
+private:
+
+    /// @brief Checks if the specified selector is valid.
+    ///
+    /// It checks if the port number is specified in conjuction with
+    /// host.
+    /// @throw BadValue if selector validation fails.
+    void validate() const;
+
+    /// @brief Backend type selected.
+    Type backend_type_;
+
+    /// @brief Host selected.
+    std::string host_;
+
+    /// @brief Port number selected.
+    uint16_t port_;
+};
+
+/// @brief Base class for configuration backend pools.
+///
+/// Each Kea server supporting databases as a configuration repository can
+/// use multiple database instances simultaneously. A pool is a collection
+/// of database backends used by a particular server. Different Kea servers
+/// use different pools because they store and fetch different configuration
+/// information. For example: DHCPv4 server stores and fetches IPv4 subnets,
+/// and DHCPv6 server stores and fetches IPv6 subnets. Therefore, each pool
+/// type will expose a different API calls.
+///
+/// This template class is a base class for all pools used by various servers.
+/// It implements mechanisms for managing multiple backends and for routing
+/// API calls to one or many database backends depending on the selections
+/// made via @c BackendSelector class.
+///
+/// @tparam ConfigBackendType Type of the configuration backend. This must
+/// be a class deriving from @c BaseConfigBackend class. It is a class
+/// dedicated to a particular server type, e.g. DHCPv4 server, and from
+/// which database specific backends derive.
+template<typename ConfigBackendType>
+class BaseConfigBackendPool {
+public:
+
+    /// @brief Shared pointer to the Configuration Backend used.
+    typedef boost::shared_ptr<ConfigBackendType> ConfigBackendTypePtr;
+
+    /// @brief Virtual destructor.
+    virtual ~BaseConfigBackendPool() { }
+
+    /// @brief Adds a backend to the pool.
+    ///
+    /// @param backend Pointer to a backend to be added.
+    void addBackend(ConfigBackendTypePtr backend) {
+        backends_.push_back(backend);
+    }
+
+    /// @brief Deletes all backends from the pool.
+    void delAllBackends() {
+        backends_.clear();
+    }
+
+protected:
+
+    /// @brief Retrieve a single configuration property from the pool.
+    ///
+    /// This is a common method for retrieving a single configuration property
+    /// from the databases. The server specific backends call this method to
+    /// retrieve a single object. For example, the DHCPv4 configuration backend
+    /// pool may use this function to implement a @c getSubnet4 method:
+    ///
+    /// @code
+    /// Subnet4Ptr getSubnet4(const SubnetID& subnet_id,
+    ///                       const BackendSelector& selector) const {
+    ///     Subnet4Ptr subnet;
+    ///     getPropertyPtrConst<Subnet4Ptr, const SubnetID&, ConfigBackendDHCPv4::getSubnet4>
+    ///         (subnet, subnet_id, selector);
+    ///     return (subnet);
+    /// }
+    /// @endcode
+    ///
+    /// where @c ConfigBackendDHCPv4::getSubnet4 has the following signature:
+    ///
+    /// @code
+    /// Subnet4Ptr getSubnet4(const SubnetID& subnet_id) const;
+    /// @endcode
+    ///
+    /// If the backend selector is set to "unspecified", this method will iterate
+    /// over the existing backends and call the @c MethodPointer method on each
+    /// backend. It will return the first non-null (or non-zero) value returned
+    /// by this call. For example: if the first backend returns non-null value,
+    /// this value is returned via @c property argument and the calls for the
+    /// rest of the backends are skipped.
+    ///
+    /// @tparam PropertyType Type of the object returned by the backend call.
+    /// @tparam InputType Type of the object used as input to the backend call.
+    /// @tparam MethodPointer Type of the pointer to the backend method to be
+    /// called.
+    ///
+    /// @param [out] property Reference to the shared pointer where retrieved
+    /// property should be assigned.
+    /// @param input Value to be used as input to the backend call.
+    /// @param selector Backend selector. By default it is unspecified.
+    ///
+    /// @throw db::NoSuchDatabase if no database matching the given selector
+    /// was found.
+    template<typename PropertyType, typename InputType,
+             PropertyType (ConfigBackendType::*MethodPointer)(InputType) const>
+    void getPropertyPtrConst(PropertyType& property, InputType input,
+                             const BackendSelector& selector =
+                             BackendSelector::UNSPEC()) const {
+
+        // If no particular backend is selected, call each backend and return
+        // the first non-null (non zero) value.
+        if (selector.amUnspecified()) {
+            for (auto backend : backends_) {
+                property = ((*backend).*MethodPointer)(input);
+                if (property) {
+                    break;
+                }
+            }
+
+        } else {
+            // Backend selected, find the one that matches selection.
+            auto backends = selectBackends(selector);
+            if (!backends.empty()) {
+                for (auto backend : backends) {
+                    property = ((*backend).*MethodPointer)(input);
+                    if (property) {
+                        break;
+                    }
+                }
+
+            } else {
+                isc_throw(db::NoSuchDatabase, "no such database found for selector: "
+                          << selector.toText());
+            }
+        }
+    }
+
+    /// @brief Retrieve multiple configuration properties from the pool.
+    ///
+    /// This is a common method for retrieving multiple configuration properties
+    /// from the databases. The server specific backends call this method to
+    /// retrieve multiple objects of the same type. For example, the DHCPv6
+    /// configuration backend pool may use this function to implement a
+    /// @c getSubnets6 method:
+    ///
+    /// @code
+    /// Subnet6Collection getModifiedSubnets6(const ptime& modification_time,
+    ///                                       const BackendSelector& selector) const {
+    ///     Subnet6Collection subnets;
+    ///     getMultiplePropertiesConst<Subnet6Collection, const ptime&,
+    ///                                ConfigBackendDHCPv6::getSubnets6>
+    ///         (subnets, modification_time, selector);
+    ///     return (subnets); 
+    /// }
+    /// @endcode
+    ///
+    /// where @c ConfigBackendDHCPv6::getSubnets6 has the following signature:
+    ///
+    /// @code
+    /// Subnet6Collection getSubnets6(const ptime& modification_time) const;
+    /// @endcode
+    ///
+    /// If the backend selector is set to "unspecified", this method will iterate
+    /// over existing backends and call the @c MethodPointer method on each
+    /// backend. It will return the first non-empty list returned by one of the
+    /// backends.
+    ///
+    /// @tparam PropertyCollectionType Type of the container into which the
+    /// properties are stored.
+    /// @tparam InputType type of the object used as input to the backend call.
+    /// @tparam MethodPointer Type of the pointer to the backend method to be
+    /// called.
+    ///
+    /// @param [out] properties Reference to the collection of retrieved properties.
+    /// @param inputValue to be used as input to the backend call.
+    /// @param selector Backend selector. By default it is unspecified.
+    ///
+    /// @throw db::NoSuchDatabase if no database matching the given selector
+    /// was found.
+    template<typename PropertyCollectionType, typename InputType,
+             PropertyCollectionType (ConfigBackendType::*MethodPointer)(InputType) const>
+    void getMultiplePropertiesConst(PropertyCollectionType& properties,
+                                    InputType input,
+                                    const BackendSelector& selector =
+                                    BackendSelector::UNSPEC()) const {
+        if (selector.amUnspecified()) {
+            for (auto backend : backends_) {
+                properties = ((*backend).*MethodPointer)(input);
+                if (!properties.empty()) {
+                    break;
+                }
+            }
+
+        } else {
+            auto backends = selectBackends(selector);
+            if (!backends.empty()) {
+                for (auto backend : backends) {
+                    properties = ((*backend).*MethodPointer)(input);
+                    if (!properties.empty()) {
+                        break;
+                    }
+                }
+
+            } else {
+                isc_throw(db::NoSuchDatabase, "no database found for selector: "
+                          << selector.toText());
+            }
+        }
+    }
+
+    /// @brief Retrieve all configuration properties from the pool.
+    ///
+    /// This is a common method for retrieving all configuration properties
+    /// from the databases. The server specific backends call this method
+    /// to retrieve all objects of the same type. For example, the DHCPv4
+    /// configuration backend pool may use this function to implement a
+    /// @c getAllSubnets4 method:
+    ///
+    /// @code
+    /// Subnet4Collection getAllSubnets4(const BackendSelector& selector) const {
+    ///     Subnet4Collection subnets;
+    ///     getAllPropertiesConst<Subnet6Collection, ConfigBackendDHCPv4::getAllSubnets4>
+    ///         (subnets, selector);
+    ///     return (subnets);
+    /// }
+    /// @endcode
+    ///
+    /// where @c ConfigBackendDHCPv4::getAllSubnets4 has the following signature:
+    ///
+    /// @code
+    /// Subnet4Collection getAllSubnets4() const;
+    /// @endcode
+    ///
+    /// If the backend selector is set to "unspecified", this method will iterate
+    /// over existing backends and call the @c MethodPointer method on each
+    /// backend. It will return the first non-empty list returned by one of the
+    /// backends.
+    ///
+    /// @tparam PropertyCollectionType Type of the container into which the
+    /// properties are stored.
+    /// @tparam MethodPointer Type of the pointer to the backend method to be
+    /// called.
+    ///
+    /// @param [out] properties Reference to the collection of retrieved properties.
+    /// @param selector Backend selector. By default it is unspecified.
+    ///
+    /// @throw db::NoSuchDatabase if no database matching the given selector
+    /// was found.
+    template<typename PropertyCollectionType,
+             PropertyCollectionType (ConfigBackendType::*MethodPointer)() const>
+    void getAllPropertiesConst(PropertyCollectionType& properties,
+                               const BackendSelector& selector =
+                               BackendSelector::UNSPEC()) const {
+        if (selector.amUnspecified()) {
+            for (auto backend : backends_) {
+                properties = ((*backend).*MethodPointer)();
+                if (!properties.empty()) {
+                    break;
+                }
+            }
+
+        } else {
+            auto backends = selectBackends(selector);
+            if (!backends.empty()) {
+                for (auto backend : backends) {
+                    properties = ((*backend).*MethodPointer)();
+                    if (!properties.empty()) {
+                        break;
+                    }
+                }
+
+            } else {
+                isc_throw(db::NoSuchDatabase, "no database found for selector: "
+                          << selector.toText());
+            }
+        }
+    }
+
+
+    /// @brief Add, update or delete property from the backend.
+    ///
+    /// This is a common method for storing a single configuration property in
+    /// a database, updating an existing property or deleting the property.
+    /// The server specific backends call this method. For example,
+    /// the DHCPv6 configuration backend pool may use this function to implement
+    /// a @c createUpdateSubnet6 method:
+    ///
+    /// @code
+    /// void createUpdateSubnet6(const Subnet6Ptr& subnet,
+    ///                          const BackendSelector& selector) {
+    ///     createUpdateDeleteProperty<const Subnet6Ptr&,
+    ///                                ConfigBackendDHCPv6::createUpdateSubnet6>
+    ///         (subnet, selector);
+    /// }
+    /// @endcode
+    ///
+    /// where @c ConfigBackendDHCPv6::createUpdateSubnet6 has the following
+    /// signature:
+    ///
+    /// @code
+    /// void createUpdateSubnet6(const Subnet6Ptr& subnet);
+    /// @endcode
+    ///
+    /// The backend selector must point to exactly one backend. If more than one
+    /// backend is selected, an exception is thrown. If no backend is selected
+    /// an exception is thrown either.
+    ///
+    /// @tparam InputType Type of the object being a new property to be added
+    /// or updated, or an identifier of the object to be deleted.
+    /// @tparam MethodPointer Type of the pointer to the backend method to be
+    /// called.
+    ///
+    /// @param input Object being a new property to be added or updated, or an
+    /// identifier of the object to be deleted.
+    /// @param selector Backend selector.
+    ///
+    /// @throw db::NoSuchDatabase if no database matching the given selector
+    /// was found.
+    /// @throw db::AmbiguousDatabase if multiple databases matching the selector
+    /// were found.
+    template<typename InputType,
+             void (ConfigBackendType::*MethodPointer)(InputType)>
+    void createUpdateDeleteProperty(InputType input, const BackendSelector& selector) {
+        auto backends = selectBackends(selector);
+        if (backends.empty()) {
+            isc_throw(db::NoSuchDatabase, "no database found for selector: "
+                      << selector.toText());
+
+        } else if (backends.size() > 1) {
+            isc_throw(db::AmbiguousDatabase, "more than 1 database found for "
+                      "selector: " << selector.toText());
+        }
+
+        (*(*(backends.begin())).*MethodPointer)(input);
+    }
+
+    /// @brief Selects existing backends matching the selector.
+    ///
+    /// This method selects backends matching the selector. If the selector is
+    /// "unspecified" or there is no backend in the pool, an empty list is returned.
+    ///
+    /// @param selector Selector for which matching backends should be selected.
+    std::list<ConfigBackendTypePtr> selectBackends(const BackendSelector& selector) const {
+
+        std::list<ConfigBackendTypePtr> selected;
+
+        // In case there is only one backend, it is allowed to not select the
+        // database backend.
+        if ((backends_.size() == 1) && selector.amUnspecified()) {
+            selected.push_back(*backends_.begin());
+            return (selected);
+        }
+
+        // For other cases we return empty list.
+        if (backends_.empty() || selector.amUnspecified()) {
+            return (selected);
+        }
+
+        // Go over all backends.
+        for (auto backend : backends_) {
+            // If backend type is specified and it is not matching,
+            // do not select this backend.
+            if ((selector.getBackendType() != BackendSelector::Type::UNSPEC) &&
+                (selector.getBackendType() !=
+                 BackendSelector::stringToBackendType(backend->getType()))) {
+                continue;
+            }
+
+            // If the host has been specified by the backend's host is not
+            // matching, do not select this backend.
+            if ((!selector.getBackendHost().empty()) &&
+                (selector.getBackendHost() != backend->getHost())) {
+                continue;
+            }
+
+            // If the port has been specified by the backend's port is not
+            // matching, do not select this backend.
+            if ((selector.getBackendPort() != 0) &&
+                (selector.getBackendPort() != backend->getPort())) {
+                continue;
+            }
+
+            // Passed all checks, so the backend is matching. Add it to the list.
+            selected.push_back(backend);
+        }
+
+        return (selected);
+    }
+
+    /// @brief Holds configuration backends belonging to the pool.
+    std::list<ConfigBackendTypePtr> backends_;
+};
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif // BASE_CONFIG_BACKEND_POOL_H
diff --git a/src/lib/config_backend/config_backend_mgr.h b/src/lib/config_backend/config_backend_mgr.h
new file mode 100644 (file)
index 0000000..fc69ee8
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+#ifndef CONFIG_BACKEND_MGR_H
+#define CONFIG_BACKEND_MGR_H
+
+namespace isc {
+namespace cb {
+
+class ConfigBackendMgr {
+public:
+
+
+};
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif // CONFIG_BACKEND_MGR_H
diff --git a/src/lib/config_backend/tests/Makefile.am b/src/lib/config_backend/tests/Makefile.am
new file mode 100644 (file)
index 0000000..c11da81
--- /dev/null
@@ -0,0 +1,38 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+       $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += libcb_unittests
+
+libcb_unittests_SOURCES  = config_backend_mgr_unittest.cc
+libcb_unittests_SOURCES += config_backend_selector_unittest.cc
+libcb_unittests_SOURCES += run_unittests.cc
+
+libcb_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libcb_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+libcb_unittests_LDADD  = $(top_builddir)/src/lib/database/libkea-database.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/config_backend/libkea-cb.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libcb_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/config_backend/tests/config_backend_mgr_unittest.cc b/src/lib/config_backend/tests/config_backend_mgr_unittest.cc
new file mode 100644 (file)
index 0000000..57f4aad
--- /dev/null
@@ -0,0 +1,461 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <config_backend/base_config_backend_mgr.h>
+#include <config_backend/base_config_backend_pool.h>
+#include <config_backend/base_config_backend.h>
+#include <database/database_connection.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::cb;
+using namespace isc::db;
+
+namespace {
+
+/// @brief Defines list of properties retrieved by @c TestConfigBackend.
+///
+/// A single property is a name/value pair, where value is an integer.
+typedef std::list<std::pair<std::string, int> > PropertiesList;
+
+/// @brief Implements configuration backend used in tests.
+///
+/// @c BaseConfigBackend is an abstract class that must be implemented
+/// in order to allow us to test mechanisms implemented in
+/// @c BaseConfigBackendMgr and @c BaseConfigBackendPool.
+///
+/// Normally, a class derived directly from the @c BaseConfigBackend
+/// will merely provide an interface for server specific operations,
+/// e.g. DHCPv4 specific operations, and the database specific classes
+/// will implement this interface. However, for the test purposes it
+/// is convenient to implement them here and let the derivations
+/// only provide the implementations of the @c getType, @c getHost
+/// and @c getPort functions. That way, the logic for adding and
+/// retrieving the data from the backend is implemented only once and
+/// is common accross all test backends.
+///
+/// This class provides a logic for managing test data being a collection
+/// of name/value pairs, i.e. "properties". It contains a list of
+/// properties and the functions for adding the properties, retrieving
+/// a single property and retrieving a collection of properties.
+class TestConfigBackend : public BaseConfigBackend {
+public:
+
+    /// @brief Retrieves first property having a given name.
+    ///
+    /// @param property_name Name of the property to be retrieved.
+    /// @return Value of the property or 0 if property doesn't exist.
+    virtual int getProperty(const std::string& property_name) const {
+        for (auto property : properties_) {
+            if (property.first == property_name) {
+                return (property.second);
+            }
+        }
+        return (0);
+    }
+
+    /// @brief Retrieves all properties having a given name.
+    ///
+    /// @param property_name Name of the properties to be retrieved.
+    /// @return List of the properties having a given name. This list is
+    /// empty if no property was found.
+    virtual PropertiesList getProperties(const std::string& property_name) const {
+        PropertiesList properties;
+        for (auto property : properties_) {
+            if (property.first == property_name) {
+                properties.push_back(property);
+            }
+        }
+        return (properties);
+    }
+
+    /// @brief Retrieves all properties.
+    ///
+    /// @return List of all properties held in the backend.
+    virtual PropertiesList getAllProperties() const {
+        return (properties_);
+    }
+
+    /// @brief Creates new property.
+    ///
+    /// @param new_property Property to be added to the backend.
+    virtual void createProperty(const std::pair<std::string, int>& new_property) {
+        properties_.push_back(new_property);
+    }
+
+protected:
+
+    /// @brief Holds list of properties (simulates database).
+    PropertiesList properties_;
+
+};
+
+/// @brief Shared pointer to the @c TestConfigBackend.
+typedef boost::shared_ptr<TestConfigBackend> TestConfigBackendPtr;
+
+/// @brief First implementation of the test config backend.
+///
+/// It simulates being a MySQL backend installed on the
+/// "mysql-host" host and running on port 2345.
+class TestConfigBackendImpl1 : public TestConfigBackend {
+public:
+
+    /// @brief Returns backend type.
+    ///
+    /// @return "mysql".
+    virtual std::string getType() const {
+        return (std::string("mysql"));
+    }
+
+    /// @brief Returns backend host.
+    ///
+    /// @return "mysql-host".
+    virtual std::string getHost() const {
+        return (std::string("mysql-host"));
+    }
+
+    /// @brief Returns backend port.
+    ///
+    /// @return Port number 2345.
+    virtual uint16_t getPort() const {
+        return (2345);
+    }
+
+};
+
+/// @brief Shared pointer to the @c TestConfigBackendImpl1.
+typedef boost::shared_ptr<TestConfigBackendImpl1> TestConfigBackendImpl1Ptr;
+
+/// @brief Second implementation of the test config backend.
+///
+/// It simulates being a Postgres backend installed on the
+/// "pgsql-host" host and running on port 1234.
+class TestConfigBackendImpl2 : public TestConfigBackend {
+public:
+
+    /// @brief Returns backend type.
+    ///
+    /// @return "pgsql".
+    virtual std::string getType() const {
+        return (std::string("pgsql"));
+    }
+
+    /// @brief Returns backend host.
+    ///
+    /// @return "pgsql-host".
+    virtual std::string getHost() const {
+        return (std::string("pgsql-host"));
+    }
+
+    /// @brief Returns backend port.
+    ///
+    /// @return Port number 1234.
+    virtual uint16_t getPort() const {
+        return (1234);
+    }
+};
+
+/// @brief Shared pointer to the @c TestConfigBackendImpl2.
+typedef boost::shared_ptr<TestConfigBackendImpl2> TestConfigBackendImpl2Ptr;
+
+/// @brief Implements test pool of configuration backends.
+///
+/// @c BaseConfigBackendPool template provides mechanics for managing the data
+/// stored in multiple backends. Server specific pools must extend this class
+/// with methods for managing the data appropriate for the server types.
+/// This class provides an example pool implementation for managing the
+/// "properties" being name/value pairs. It extends the base class with
+/// new methods to retrieve a single property and multiple properties. It
+/// also adds a method to create new property. Those methods correspond to
+/// the ones implemented in the @c TestConfigBackend, but also each of
+/// them includes a "database selector" used to indicate the backend to
+/// be used.
+class TestConfigBackendPool : public BaseConfigBackendPool<TestConfigBackend> {
+public:
+
+    /// @brief Retrieves a value of the property.
+    ///
+    /// @param property_name Name of the property which value should be returned.
+    /// @param selector Backend selector. The default value of the selector
+    /// is @c UNSPEC which means that the property will be searched in all backends
+    /// and the first value found will be returned.
+    virtual int getProperty(const std::string& property_name,
+                            const BackendSelector& selector = BackendSelector::UNSPEC()) const {
+        int property;
+
+        // If the selector is specified, this method will pick the appropriate
+        // backend and will call getProperty method on this backend. If the
+        // selector is not specified, this method will iterate over existing
+        // backends and call getProperty on them. It will return after finding
+        // the first non-zero value of the property. For example, if the first
+        // backend contains a non-zero value this value will be returned and
+        // the value held in the second backend (if any) won't be fetched.
+        // The template arguments specify the returned value type and the
+        // argument of the getProperty method.
+        getPropertyPtrConst<int,
+                            const std::string&,
+                            &TestConfigBackend::getProperty>
+            (property, property_name, selector);
+        return (property);
+    }
+
+    /// @brief Retrieves multiple properties.
+    ///
+    /// @param property_name Name of the properties which should be retrieved.
+    /// @param selector Backend selector. The default value of the selector
+    /// is @c UNSPEC which means that the properties will be searched in all
+    /// backends and the first non-empty list will be returned.
+    virtual PropertiesList getProperties(const std::string& property_name,
+                                         const BackendSelector& selector =
+                                         BackendSelector::UNSPEC()) const {
+        PropertiesList properties;
+
+        // If the selector is specified, this method will pick the appropriate
+        // backend and will call getProperties method on this backend. If the
+        // selector is not specified, this method will iterate over existing
+        // backends and call getProperties on them. It will return after finding
+        // the first non-empty list of properties in one of the backends.
+        // The template arguments specify the type of the list of properties
+        // and the argument of the getProperties method.
+        getMultiplePropertiesConst<PropertiesList, const std::string&,
+                                   &TestConfigBackend::getProperties>
+            (properties, property_name, selector);
+        return (properties);
+    }
+
+    /// @brief Retrieves all properties.
+    ///
+    /// @param selector Backend selector. The default value of the selector
+    /// is @c UNSPEC which means that the properties will be searched in all
+    /// backends and the first non-empty list will be returned.
+    virtual PropertiesList getAllProperties(const BackendSelector& selector =
+                                            BackendSelector::UNSPEC()) const {
+        PropertiesList properties;
+
+        // This method is similar to getMultiplePropertiesConst but it lacks
+        // an argument and it simply returns all properties.
+        getAllPropertiesConst<PropertiesList, &TestConfigBackend::getAllProperties>
+            (properties, selector);
+        return (properties);
+    }
+
+    /// @brief Creates new property in a selected backend.
+    ///
+    /// @param new_property New property to be added to a backend.
+    /// @param selector Backend selector. It has no default value.
+    virtual void createProperty(const std::pair<std::string, int>& new_property,
+                                const BackendSelector& selector) {
+        createUpdateDeleteProperty<const std::pair<std::string, int>&,
+                                   &TestConfigBackend::createProperty>
+            (new_property, selector);
+    }
+
+};
+
+using TestConfigBackendMgr = BaseConfigBackendMgr<TestConfigBackendPool>;
+
+/// @brief Test fixture class for testing @c ConfigBackendMgr.
+class ConfigBackendMgrTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ConfigBackendMgrTest()
+        : config_mgr_() {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Removes backend instances.
+    ~ConfigBackendMgrTest() {
+        config_mgr_.delAllBackends();
+    }
+
+    /// @brief Creates example database backend called "mysql".
+    ///
+    /// It uses @c TestConfigBackendImpl1.
+    void addTestMySQLBackend() {
+        config_mgr_.registerBackendFactory("mysql", [](const DatabaseConnection::ParameterMap&)
+                                          -> TestConfigBackendPtr {
+            return (TestConfigBackendImpl1Ptr(new TestConfigBackendImpl1()));
+        });
+
+        config_mgr_.addBackend("type=mysql");
+    }
+
+    /// @brief Creates example database backend called "pgsql".
+    ///
+    /// It uses @c TestConfigBackendImpl2.
+    void addTestPgSQLBackend() {
+        config_mgr_.registerBackendFactory("pgsql", [](const DatabaseConnection::ParameterMap&)
+                                          -> TestConfigBackendPtr {
+            return (TestConfigBackendImpl2Ptr(new TestConfigBackendImpl2()));
+        });
+
+        // Actually create the backends.
+        config_mgr_.addBackend("type=pgsql");
+    }
+
+    /// @brief Creates two example database backends called "mysql" and "pgsql".
+    ///
+    /// It uses @c TestConfigBackendImpl1 and @c TestConfigBackendImpl2.
+    void addTestBackends() {
+        addTestMySQLBackend();
+        addTestPgSQLBackend();
+    }
+
+    /// @brief Adds test data into the backends.
+    void addTestData() {
+        // Add two properties with different names into the first backend.
+        config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
+                                             BackendSelector(BackendSelector::Type::MYSQL));
+        config_mgr_.getPool()->createProperty(std::make_pair("wolves", 3),
+                                             BackendSelector(BackendSelector::Type::MYSQL));
+
+        // Add two properties into the second backend. Both properties share the
+        // name so as we can test retrieving multiple records from the same backend.
+        config_mgr_.getPool()->createProperty(std::make_pair("cats", 2),
+                                             BackendSelector(BackendSelector::Type::PGSQL));
+        config_mgr_.getPool()->createProperty(std::make_pair("cats", 4),
+                                             BackendSelector(BackendSelector::Type::PGSQL));
+    }
+
+    /// Instance of the test configuration manager.
+    TestConfigBackendMgr config_mgr_;
+};
+
+// Test that selector can be left "unspecified" if there is only one backend,
+// when manipulating the data.
+TEST_F(ConfigBackendMgrTest, createPropertySingleBackendUnspec) {
+    addTestMySQLBackend();
+
+    ASSERT_NO_THROW(
+        config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
+                                              BackendSelector(BackendSelector::Type::UNSPEC))
+    );
+
+    // We should be able to retrieve stored value without having to specify
+    // the backend.
+    EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs"));
+}
+
+// Test that selector must be specified if there is more than one backend,
+// when manipulating the data.
+TEST_F(ConfigBackendMgrTest, createPropertyTwoBackendsUnspec) {
+    addTestBackends();
+
+    // Backend must be selected if there is more than one backend present.
+    EXPECT_THROW(
+        config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
+                                              BackendSelector(BackendSelector::Type::UNSPEC)),
+        NoSuchDatabase
+    );
+}
+
+// Test that exception is thrown when multiple backends are selected.
+TEST_F(ConfigBackendMgrTest, createPropertyAmbiguousSelection) {
+    addTestBackends();
+
+    // Add another MySQL backend to cause the selection to give ambiguous
+    // result.
+    config_mgr_.addBackend("type=mysql");
+
+    EXPECT_THROW(
+        config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
+                                              BackendSelector(BackendSelector::Type::MYSQL)),
+        AmbiguousDatabase
+    );
+}
+
+// Test that a single property can be retrieved from a selected backend.
+TEST_F(ConfigBackendMgrTest, getSingleProperty) {
+
+    addTestBackends();
+    addTestData();
+
+    // Backend is not specified, so it should find the dogs in first one and
+    // cats in the second one.
+    EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs"));
+    EXPECT_EQ(2, config_mgr_.getPool()->getProperty("cats"));
+
+    // No dogs in the pgsql backend and no cats in mysql backend.
+    EXPECT_EQ(0, config_mgr_.getPool()->getProperty("dogs",
+                                                   BackendSelector(BackendSelector::Type::PGSQL)));
+    EXPECT_EQ(0, config_mgr_.getPool()->getProperty("cats",
+                                                   BackendSelector(BackendSelector::Type::MYSQL)));
+
+    // If the selectors are pointing to the right databases, the dogs and cats
+    // should be returned properly.
+    EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs",
+                                                   BackendSelector(BackendSelector::Type::MYSQL)));
+    EXPECT_EQ(2, config_mgr_.getPool()->getProperty("cats",
+                                                   BackendSelector(BackendSelector::Type::PGSQL)));
+
+    // Try to use the backend that is not present.
+    EXPECT_THROW(config_mgr_.getPool()->getProperty("cats",
+                                                    BackendSelector(BackendSelector::Type::CQL)),
+                 NoSuchDatabase);
+}
+
+// Test that multiple properties can be retrieved with filtering.
+TEST_F(ConfigBackendMgrTest, getMultipleProperties) {
+
+    addTestBackends();
+    addTestData();
+
+    // There is one dogs entry in mysql.
+    PropertiesList mysql_list =
+        config_mgr_.getPool()->getProperties("dogs",
+                                            BackendSelector(BackendSelector::Type::MYSQL));
+    ASSERT_EQ(1, mysql_list.size());
+
+    // There is also one wolves entry in mysql.
+    mysql_list = config_mgr_.getPool()->getProperties("wolves",
+                                                     BackendSelector(BackendSelector::Type::MYSQL));
+    ASSERT_EQ(1, mysql_list.size());
+
+    // There are two cats entries in pgsql.
+    PropertiesList pgsql_list =
+        config_mgr_.getPool()->getProperties("cats",
+                                            BackendSelector(BackendSelector::Type::PGSQL));
+    ASSERT_EQ(2, pgsql_list.size());
+
+    // Try to use the backend that is not present.
+    EXPECT_THROW(config_mgr_.getPool()->getProperties("cats",
+                                                      BackendSelector(BackendSelector::Type::CQL)),
+                 NoSuchDatabase);
+
+}
+
+// Test that all properties can be retrieved from each backend.
+TEST_F(ConfigBackendMgrTest, getAllProperties) {
+
+    addTestBackends();
+    addTestData();
+
+    // The mysql backend holds two entries.
+    PropertiesList mysql_list =
+        config_mgr_.getPool()->getAllProperties(BackendSelector(BackendSelector::Type::MYSQL));
+    ASSERT_EQ(2, mysql_list.size());
+
+    // The pgsql backends also holds two entries.
+    PropertiesList pgsql_list =
+        config_mgr_.getPool()->getAllProperties(BackendSelector(BackendSelector::Type::PGSQL));
+    ASSERT_EQ(2, pgsql_list.size());
+
+    // Try to use the backend that is not present.
+    EXPECT_THROW(config_mgr_.getPool()->getProperties("cats",
+                                                      BackendSelector(BackendSelector::Type::CQL)),
+                 NoSuchDatabase);
+}
+
+}
+
diff --git a/src/lib/config_backend/tests/config_backend_selector_unittest.cc b/src/lib/config_backend/tests/config_backend_selector_unittest.cc
new file mode 100644 (file)
index 0000000..0fded79
--- /dev/null
@@ -0,0 +1,159 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+#include <config.h>
+#include <config_backend/base_config_backend_pool.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::cb;
+using namespace isc::data;
+
+namespace {
+
+TEST(BackendSelectorTest, defaults) {
+    BackendSelector sel;
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, sel.getBackendType());
+    EXPECT_TRUE(sel.getBackendHost().empty());
+    EXPECT_EQ(0, sel.getBackendPort());
+    EXPECT_TRUE(sel.amUnspecified());
+    EXPECT_EQ("unspecified", sel.toText());
+}
+
+TEST(BackendSelectorTest, unspec) {
+    BackendSelector sel = BackendSelector::UNSPEC();
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, sel.getBackendType());
+    EXPECT_TRUE(sel.getBackendHost().empty());
+    EXPECT_EQ(0, sel.getBackendPort());
+    EXPECT_TRUE(sel.amUnspecified());
+    EXPECT_EQ("unspecified", sel.toText());
+}
+
+TEST(BackendSelectorTest, backendTypeSpec) {
+    boost::scoped_ptr<BackendSelector> sel;
+    ASSERT_NO_THROW(
+        sel.reset(new BackendSelector(BackendSelector::Type::MYSQL))
+    );
+    EXPECT_EQ(BackendSelector::Type::MYSQL, sel->getBackendType());
+    EXPECT_TRUE(sel->getBackendHost().empty());
+    EXPECT_EQ(0, sel->getBackendPort());
+    EXPECT_FALSE(sel->amUnspecified());
+    EXPECT_EQ("type=mysql", sel->toText());
+}
+
+TEST(BackendSelectorTest, backendHostPortSpec) {
+    boost::scoped_ptr<BackendSelector> sel;
+    ASSERT_NO_THROW(
+        sel.reset(new BackendSelector("myhost", 1234))
+    );
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+    EXPECT_EQ("myhost", sel->getBackendHost());
+    EXPECT_EQ(1234, sel->getBackendPort());
+    EXPECT_FALSE(sel->amUnspecified());
+    EXPECT_EQ("host=myhost,port=1234", sel->toText());
+}
+
+TEST(BackendSelectorTest, backendHostSpec) {
+    boost::scoped_ptr<BackendSelector> sel;
+    ASSERT_NO_THROW(
+        sel.reset(new BackendSelector("otherhost"))
+    );
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+    EXPECT_EQ("otherhost", sel->getBackendHost());
+    EXPECT_EQ(0, sel->getBackendPort());
+    EXPECT_FALSE(sel->amUnspecified());
+    EXPECT_EQ("host=otherhost", sel->toText());
+}
+
+TEST(BackendSelectorTest, accessMapTypeUnSpec) {
+    ElementPtr access_map = Element::createMap();
+    boost::scoped_ptr<BackendSelector> sel;
+    ASSERT_NO_THROW(
+        sel.reset(new BackendSelector(access_map))
+    );
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+    EXPECT_TRUE(sel->getBackendHost().empty());
+    EXPECT_EQ(0, sel->getBackendPort());
+    EXPECT_TRUE(sel->amUnspecified());
+    EXPECT_EQ("unspecified", sel->toText());
+}
+
+TEST(BackendSelectorTest, accessMapTypeSpec) {
+    ElementPtr access_map = Element::createMap();
+    access_map->set("type", Element::create("mysql"));
+    boost::scoped_ptr<BackendSelector> sel;
+    ASSERT_NO_THROW(
+        sel.reset(new BackendSelector(access_map))
+    );
+    EXPECT_EQ(BackendSelector::Type::MYSQL, sel->getBackendType());
+    EXPECT_TRUE(sel->getBackendHost().empty());
+    EXPECT_EQ(0, sel->getBackendPort());
+    EXPECT_FALSE(sel->amUnspecified());
+    EXPECT_EQ("type=mysql", sel->toText());
+}
+
+TEST(BackendSelectorTest, accessMapHostPortSpec) {
+    ElementPtr access_map = Element::createMap();
+    access_map->set("host", Element::create("myhost"));
+    access_map->set("port", Element::create(int64_t(1234)));
+    boost::scoped_ptr<BackendSelector> sel;
+    ASSERT_NO_THROW(
+        sel.reset(new BackendSelector(access_map))
+    );
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+    EXPECT_EQ("myhost", sel->getBackendHost());
+    EXPECT_EQ(1234, sel->getBackendPort());
+    EXPECT_FALSE(sel->amUnspecified());
+    EXPECT_EQ("host=myhost,port=1234", sel->toText());
+}
+
+TEST(BackendSelectorTest, accessMapHostSpec) {
+    ElementPtr access_map = Element::createMap();
+    access_map->set("host", Element::create("myhost"));
+    boost::scoped_ptr<BackendSelector> sel;
+    ASSERT_NO_THROW(
+        sel.reset(new BackendSelector(access_map))
+    );
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+    EXPECT_EQ("myhost", sel->getBackendHost());
+    EXPECT_EQ(0, sel->getBackendPort());
+    EXPECT_FALSE(sel->amUnspecified());
+    EXPECT_EQ("host=myhost", sel->toText());
+}
+
+TEST(BackendSelectorTest, accessMapPortSpec) {
+    ElementPtr access_map = Element::createMap();
+    access_map->set("port", Element::create(int64_t(1234)));
+    boost::scoped_ptr<BackendSelector> sel;
+    EXPECT_THROW(sel.reset(new BackendSelector(access_map)),
+                 BadValue);
+}
+
+TEST(BackendSelectorTest, stringToBackendType) {
+    EXPECT_EQ(BackendSelector::Type::MYSQL,
+              BackendSelector::stringToBackendType("mysql"));
+    EXPECT_EQ(BackendSelector::Type::PGSQL,
+              BackendSelector::stringToBackendType("pgsql"));
+    EXPECT_EQ(BackendSelector::Type::CQL,
+              BackendSelector::stringToBackendType("cql"));
+    EXPECT_THROW(BackendSelector::stringToBackendType("unsupported"),
+                 BadValue);
+}
+
+TEST(BackendSelectorTest, backendTypeToString) {
+    EXPECT_EQ("mysql",
+              BackendSelector::backendTypeToString(BackendSelector::Type::MYSQL));
+    EXPECT_EQ("pgsql",
+              BackendSelector::backendTypeToString(BackendSelector::Type::PGSQL));
+    EXPECT_EQ("cql",
+              BackendSelector::backendTypeToString(BackendSelector::Type::CQL));
+}
+
+
+}
+
diff --git a/src/lib/config_backend/tests/libcb_unittests b/src/lib/config_backend/tests/libcb_unittests
new file mode 100755 (executable)
index 0000000..c22007b
--- /dev/null
@@ -0,0 +1,210 @@
+#! /bin/sh
+
+# libcb_unittests - temporary wrapper script for .libs/libcb_unittests
+# Generated by libtool (GNU libtool) 2.4.6
+#
+# The libcb_unittests program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting.  It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+  # install mode needs the following variables:
+  generated_by_libtool_version='2.4.6'
+  notinst_deplibs=' ../../../../src/lib/database/libkea-database.la /Users/marcin/devel/kea/src/lib/cc/libkea-cc.la /Users/marcin/devel/kea/src/lib/log/libkea-log.la /Users/marcin/devel/kea/src/lib/util/threads/libkea-threads.la ../../../../src/lib/config_backend/libkea-cb.la /Users/marcin/devel/kea/src/lib/util/libkea-util.la /Users/marcin/devel/kea/src/lib/asiolink/libkea-asiolink.la ../../../../src/lib/util/libkea-util.la ../../../../src/lib/asiolink/libkea-asiolink.la /Users/marcin/devel/kea/src/lib/exceptions/libkea-exceptions.la ../../../../src/lib/exceptions/libkea-exceptions.la'
+else
+  # When we are sourced in execute mode, $file and $ECHO are already set.
+  if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+    file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+    ECHO="printf %s\\n"
+  fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+  lt_script_arg0=$0
+  shift
+  for lt_opt
+  do
+    case "$lt_opt" in
+    --lt-debug) lt_option_debug=1 ;;
+    --lt-dump-script)
+        lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+        test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+        lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+        cat "$lt_dump_D/$lt_dump_F"
+        exit 0
+      ;;
+    --lt-*)
+        $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+        exit 1
+      ;;
+    esac
+  done
+
+  # Print the debug banner immediately:
+  if test -n "$lt_option_debug"; then
+    echo "libcb_unittests:libcb_unittests:$LINENO: libtool wrapper (GNU libtool) 2.4.6" 1>&2
+  fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+  lt_dump_args_N=1;
+  for lt_arg
+  do
+    $ECHO "libcb_unittests:libcb_unittests:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+    lt_dump_args_N=`expr $lt_dump_args_N + 1`
+  done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+      if test -n "$lt_option_debug"; then
+        $ECHO "libcb_unittests:libcb_unittests:$LINENO: newargv[0]: $progdir/$program" 1>&2
+        func_lt_dump_args ${1+"$@"} 1>&2
+      fi
+      exec "$progdir/$program" ${1+"$@"}
+
+      $ECHO "$0: cannot exec $program $*" 1>&2
+      exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+  case " $* " in
+  *\ --lt-*)
+    for lt_wr_arg
+    do
+      case $lt_wr_arg in
+      --lt-*) ;;
+      *) set x "$@" "$lt_wr_arg"; shift;;
+      esac
+      shift
+    done ;;
+  esac
+  func_exec_program_core ${1+"$@"}
+}
+
+  # Parse options
+  func_parse_lt_options "$0" ${1+"$@"}
+
+  # Find the directory that this script lives in.
+  thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+  test "x$thisdir" = "x$file" && thisdir=.
+
+  # Follow symbolic links until we get to the real thisdir.
+  file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+  while test -n "$file"; do
+    destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+    # If there was a directory component, then change thisdir.
+    if test "x$destdir" != "x$file"; then
+      case "$destdir" in
+      [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+      *) thisdir="$thisdir/$destdir" ;;
+      esac
+    fi
+
+    file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+    file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+  done
+
+  # Usually 'no', except on cygwin/mingw when embedded into
+  # the cwrapper.
+  WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+  if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+    # special case for '.'
+    if test "$thisdir" = "."; then
+      thisdir=`pwd`
+    fi
+    # remove .libs from thisdir
+    case "$thisdir" in
+    *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+    .libs )   thisdir=. ;;
+    esac
+  fi
+
+  # Try to get the absolute directory name.
+  absdir=`cd "$thisdir" && pwd`
+  test -n "$absdir" && thisdir="$absdir"
+
+  program='libcb_unittests'
+  progdir="$thisdir/.libs"
+
+
+  if test -f "$progdir/$program"; then
+    # Add our own library path to DYLD_LIBRARY_PATH
+    DYLD_LIBRARY_PATH="/Users/marcin/devel/kea/src/lib/database/.libs:/Users/marcin/devel/kea/src/lib/cc/.libs:/Users/marcin/devel/kea/src/lib/log/.libs:/Users/marcin/devel/kea/src/lib/util/threads/.libs:/Users/marcin/devel/kea/src/lib/config_backend/.libs:/Users/marcin/devel/kea/src/lib/util/.libs:/Users/marcin/devel/kea/src/lib/asiolink/.libs:/Users/marcin/devel/kea/src/lib/exceptions/.libs:$DYLD_LIBRARY_PATH"
+
+    # Some systems cannot cope with colon-terminated DYLD_LIBRARY_PATH
+    # The second colon is a workaround for a bug in BeOS R4 sed
+    DYLD_LIBRARY_PATH=`$ECHO "$DYLD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+    export DYLD_LIBRARY_PATH
+
+    if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+      # Run the actual program with our arguments.
+      func_exec_program ${1+"$@"}
+    fi
+  else
+    # The program doesn't exist.
+    $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+    $ECHO "This script is just a wrapper for $program." 1>&2
+    $ECHO "See the libtool documentation for more information." 1>&2
+    exit 1
+  fi
+fi
diff --git a/src/lib/config_backend/tests/run_unittests.cc b/src/lib/config_backend/tests/run_unittests.cc
new file mode 100644 (file)
index 0000000..6754d66
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    int result = RUN_ALL_TESTS();
+
+    return (result);
+}
index 60e10900d9057e57bb3867e44cbc427b66f4a2fc..21dd8fa5a6dc7ff0fdedbb523a8119b435453b1c 100644 (file)
@@ -74,6 +74,22 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+/// @brief Error when specified database could not be found in the server
+/// configuration.
+class NoSuchDatabase :  public Exception {
+public:
+    NoSuchDatabase(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Specification of the database backend to be used yields
+/// multiple results.
+class AmbiguousDatabase :  public Exception {
+public:
+    AmbiguousDatabase(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 }  // namespace isc
 }  // namespace db