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
SUBDIRS += cql
endif
-SUBDIRS += testutils hooks dhcp config stats
+SUBDIRS += config_backend testutils hooks dhcp config stats
if HAVE_SYSREPO
SUBDIRS += yang
--- /dev/null
+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 =
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+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)
--- /dev/null
+// 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);
+}
+
+}
+
--- /dev/null
+// 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));
+}
+
+
+}
+
--- /dev/null
+#! /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
--- /dev/null
+// 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);
+}
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