From: Marcin Siodelski Date: Tue, 28 Aug 2018 10:01:15 +0000 (+0200) Subject: [#28] Added libkea-cb library. X-Git-Tag: 134-bugs--xcode-10_base~26 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=417beb8b59c179342b6e4394d85633abfdb6d39e;p=thirdparty%2Fkea.git [#28] Added libkea-cb library. --- diff --git a/configure.ac b/configure.ac index e9f2b0b486..b784dfc19a 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 319c995490..211aafe810 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -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 index 0000000000..4e524d6dce --- /dev/null +++ b/src/lib/config_backend/Makefile.am @@ -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 index 0000000000..9ec6f6365e --- /dev/null +++ b/src/lib/config_backend/base_config_backend.h @@ -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 +#include + +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 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 index 0000000000..f727a9f1ea --- /dev/null +++ b/src/lib/config_backend/base_config_backend_mgr.h @@ -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 +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace cb { + +template +class BaseConfigBackendMgr { +public: + + typedef boost::shared_ptr ConfigBackendPoolPtr; + + typedef std::function 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 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 index 0000000000..34175fa6a3 --- /dev/null +++ b/src/lib/config_backend/base_config_backend_pool.cc @@ -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 +#include +#include +#include + +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::max())) { + isc_throw(BadValue, "'port' parameter must be a number in range from 0 " + "to " << std::numeric_limits::max()); + } + port_ = static_cast(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 index 0000000000..ea392ecf1f --- /dev/null +++ b/src/lib/config_backend/base_config_backend_pool.h @@ -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 +#include +#include +#include +#include +#include +#include + +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 +class BaseConfigBackendPool { +public: + + /// @brief Shared pointer to the Configuration Backend used. + typedef boost::shared_ptr 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 + /// (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 + 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 + /// (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 + 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 + /// (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 + 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 + /// (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 + 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 selectBackends(const BackendSelector& selector) const { + + std::list 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 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 index 0000000000..fc69ee8567 --- /dev/null +++ b/src/lib/config_backend/config_backend_mgr.h @@ -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 index 0000000000..c11da81999 --- /dev/null +++ b/src/lib/config_backend/tests/Makefile.am @@ -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 index 0000000000..57f4aad45c --- /dev/null +++ b/src/lib/config_backend/tests/config_backend_mgr_unittest.cc @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 > 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& 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 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 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 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 { +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 + (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 + (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 + (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& new_property, + const BackendSelector& selector) { + createUpdateDeleteProperty&, + &TestConfigBackend::createProperty> + (new_property, selector); + } + +}; + +using TestConfigBackendMgr = BaseConfigBackendMgr; + +/// @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 index 0000000000..0fded79d30 --- /dev/null +++ b/src/lib/config_backend/tests/config_backend_selector_unittest.cc @@ -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 +#include +#include +#include + +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 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 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 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 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 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 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 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 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 index 0000000000..c22007ba0d --- /dev/null +++ b/src/lib/config_backend/tests/libcb_unittests @@ -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 index 0000000000..6754d66eb2 --- /dev/null +++ b/src/lib/config_backend/tests/run_unittests.cc @@ -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 + +#include +#include + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + int result = RUN_ALL_TESTS(); + + return (result); +} diff --git a/src/lib/database/db_exceptions.h b/src/lib/database/db_exceptions.h index 60e10900d9..21dd8fa5a6 100644 --- a/src/lib/database/db_exceptions.h +++ b/src/lib/database/db_exceptions.h @@ -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