From: Marcin Siodelski Date: Wed, 5 Sep 2018 15:43:13 +0000 (+0200) Subject: [#28,!20] Added server selector to the config_backend. X-Git-Tag: 134-bugs--xcode-10_base~19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e45157fc4b5995637d39ac6389e20c85035a053f;p=thirdparty%2Fkea.git [#28,!20] Added server selector to the config_backend. Also, modfied config backend pool functions to support variable number of parameters. --- diff --git a/src/lib/config_backend/base_config_backend.h b/src/lib/config_backend/base_config_backend.h index b92d2079be..d35734eb1d 100644 --- a/src/lib/config_backend/base_config_backend.h +++ b/src/lib/config_backend/base_config_backend.h @@ -9,11 +9,118 @@ #include #include +#include #include namespace isc { namespace cb { +/// @brief Server selector for associating objects in a database with +/// specific servers. +/// +/// Configuration information stored in the configuration backends can be +/// associated with selected servers, all servers or no particular server. +/// For example: a particular subnet definition in the database may be +/// associated with one server or can be shared by multiple servers. +/// In the latter case, a subnet may be associated with a subset of +/// servers or all servers. An administrator may also add the +/// configuration data into the database and do not associate this data +/// with any patrticular server. +/// +/// When fetching the configuration data from a databse or when storing +/// data in the database there is a need to specify which servers this +/// data is associated with. The @c ServerSelector class represents +/// such associations. +/// +/// It includes three modes of selection: UNASSIGNED, ALL and SUBSET and +/// several factory functions making associations described above. +/// +/// The @c ServerSelector class should be used in objects derived from +/// @c BaseConfigBackendPool and in objects derived from +/// @c BaseConfigBackend to indicate which servers the specific calls +/// exposed by these objects refer to. +class ServerSelector { +public: + + /// @brief Type of the server selection. + enum class Type { + UNASSIGNED, + ALL, + SUBSET + }; + + /// @brief Factory returning "unassigned" server selector. + static ServerSelector& UNASSIGNED() { + static ServerSelector selector(Type::UNASSIGNED); + return (selector); + } + + /// @brief Factory returning "all servers" selector. + static ServerSelector& ALL() { + static ServerSelector selector(Type::ALL); + return (selector); + } + + /// @brief Factory returning selector of one server. + /// + /// @param server_tag tag of the single server to be selected. + static ServerSelector& ONE(const std::string& server_tag) { + static ServerSelector selector(server_tag); + return (selector); + } + + /// @brief Factory returning "multiple servers" selector. + /// + /// @param server_tags set of server tags to be selected. + static ServerSelector& MULTIPLE(const std::set& server_tags) { + static ServerSelector selector(server_tags); + return (selector); + } + + /// @brief Returns type of the selector. + Type getType() const { + return (type_); + } + + /// @brief Returns tags associated with the selector. + /// + /// @return server tags for mutliple selections and for one server, + /// empty set for all servers and and unassigned. + std::set getTags() const { + return (tags_); + } + +private: + + /// @brief Constructor used for "unassigned" and "all" slection types. + /// + /// @param type selector type. + explicit ServerSelector(const Type& type) + : type_(type), tags_() { + } + + /// @brief Constructor used for selecting a single server. + /// + /// @param server_tag tag of the server to be selected. + explicit ServerSelector(const std::string& server_tag) + : type_(Type::SUBSET), tags_() { + tags_.insert(server_tag); + } + + /// @brief Constructor used for selecting multiple servers. + /// + /// @param server_tags set of server tags. + explicit ServerSelector(const std::set& server_tags) + : type_(Type::SUBSET), tags_(server_tags) { + } + + /// @brief Selection type used. + Type type_; + + /// @brief Holds tags of explicitly selected servers. + std::set tags_; +}; + /// @brief Base class for server specific configuration backends. class BaseConfigBackend { public: diff --git a/src/lib/config_backend/base_config_backend_pool.h b/src/lib/config_backend/base_config_backend_pool.h index ea392ecf1f..2dd5a9d03f 100644 --- a/src/lib/config_backend/base_config_backend_pool.h +++ b/src/lib/config_backend/base_config_backend_pool.h @@ -216,7 +216,7 @@ private: /// 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 +/// It implements mechanisms for managing multiple backends and for forwarding /// API calls to one or many database backends depending on the selections /// made via @c BackendSelector class. /// @@ -250,17 +250,18 @@ protected: /// @brief Retrieve a single configuration property from the pool. /// - /// This is a common method for retrieving a single configuration property + /// This is 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 { + /// const BackendSelector& selector, + /// const ServerSelector& server_selector) const { /// Subnet4Ptr subnet; /// getPropertyPtrConst - /// (subnet, subnet_id, selector); + /// (&ConfigBackendDHCPv4::getSubnet4, selector, subnet, subnet_id); /// return (subnet); /// } /// @endcode @@ -268,7 +269,7 @@ protected: /// where @c ConfigBackendDHCPv4::getSubnet4 has the following signature: /// /// @code - /// Subnet4Ptr getSubnet4(const SubnetID& subnet_id) const; + /// Subnet4Ptr getSubnet4(const ServerSelector&, const SubnetID&) const; /// @endcode /// /// If the backend selector is set to "unspecified", this method will iterate @@ -279,28 +280,30 @@ protected: /// 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. + /// @tparam InputType Type of the objects used as input to the backend call. /// + /// @param MethodPointer Pointer to the backend method to be called. + /// @param selector Backend selector. + /// @param server_selector Server selector. /// @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. + /// @param input Values to be used as input to the backend call. /// /// @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 { + template + void getPropertyPtrConst(PropertyType (ConfigBackendType::*MethodPointer) + (const ServerSelector&, InputType...) const, + const BackendSelector& selector, + const ServerSelector& server_selector, + PropertyType& property, + InputType... input) 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); + property = ((*backend).*MethodPointer)(server_selector, input...); if (property) { break; } @@ -311,7 +314,7 @@ protected: auto backends = selectBackends(selector); if (!backends.empty()) { for (auto backend : backends) { - property = ((*backend).*MethodPointer)(input); + property = ((*backend).*MethodPointer)(server_selector, input...); if (property) { break; } @@ -333,20 +336,21 @@ protected: /// @c getSubnets6 method: /// /// @code - /// Subnet6Collection getModifiedSubnets6(const ptime& modification_time, - /// const BackendSelector& selector) const { + /// Subnet6Collection getModifiedSubnets6(const BackendSelector& selector, + /// const ServerSelector& server_selector, + /// const ptime& modification_time) const { /// Subnet6Collection subnets; - /// getMultiplePropertiesConst - /// (subnets, modification_time, selector); - /// return (subnets); + /// getMultiplePropertiesConst + /// (&ConfigBackendDHCPv6::getSubnets6, selector, subnets, + /// modification_time); + /// return (subnets); /// } /// @endcode /// /// where @c ConfigBackendDHCPv6::getSubnets6 has the following signature: /// /// @code - /// Subnet6Collection getSubnets6(const ptime& modification_time) const; + /// Subnet6Collection getSubnets6(const ServerSelector&, const ptime&) const; /// @endcode /// /// If the backend selector is set to "unspecified", this method will iterate @@ -356,25 +360,26 @@ protected: /// /// @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. + /// @tparam InputType Type of the objects used as input to the backend call. /// + /// @param MethodPointer Pointer to the backend method to be called. + /// @param selector Backend selector. + /// @param server_selector Server selector. /// @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. + /// @param input Values to be used as input to the backend call. /// /// @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 { + template + void getMultiplePropertiesConst(PropertyCollectionType (ConfigBackendType::*MethodPointer) + (const ServerSelector&, InputType...) const, + const BackendSelector& selector, + const ServerSelector& server_selector, + PropertyCollectionType& properties, + InputType... input) const { if (selector.amUnspecified()) { for (auto backend : backends_) { - properties = ((*backend).*MethodPointer)(input); + properties = ((*backend).*MethodPointer)(server_selector, input...); if (!properties.empty()) { break; } @@ -384,7 +389,7 @@ protected: auto backends = selectBackends(selector); if (!backends.empty()) { for (auto backend : backends) { - properties = ((*backend).*MethodPointer)(input); + properties = ((*backend).*MethodPointer)(server_selector, input...); if (!properties.empty()) { break; } @@ -406,10 +411,11 @@ protected: /// @c getAllSubnets4 method: /// /// @code - /// Subnet4Collection getAllSubnets4(const BackendSelector& selector) const { + /// Subnet4Collection getAllSubnets4(const BackendSelector&, const ServerSelector&) const { /// Subnet4Collection subnets; - /// getAllPropertiesConst - /// (subnets, selector); + /// getAllPropertiesConst + /// (&ConfigBackendDHCPv4::getAllSubnets4, subnets, selector, + /// server_selector); /// return (subnets); /// } /// @endcode @@ -417,7 +423,7 @@ protected: /// where @c ConfigBackendDHCPv4::getAllSubnets4 has the following signature: /// /// @code - /// Subnet4Collection getAllSubnets4() const; + /// Subnet4Collection getAllSubnets4(const ServerSelector&) const; /// @endcode /// /// If the backend selector is set to "unspecified", this method will iterate @@ -427,22 +433,23 @@ protected: /// /// @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 MethodPointer Pointer to the backend method to be called. + /// @param selector Backend selector. + /// @param server_selector Server selector. /// @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 { + template + void getAllPropertiesConst(PropertyCollectionType (ConfigBackendType::*MethodPointer) + (const ServerSelector&) const, + const BackendSelector& selector, + const ServerSelector& server_selector, + PropertyCollectionType& properties) const { if (selector.amUnspecified()) { for (auto backend : backends_) { - properties = ((*backend).*MethodPointer)(); + properties = ((*backend).*MethodPointer)(server_selector); if (!properties.empty()) { break; } @@ -452,7 +459,7 @@ protected: auto backends = selectBackends(selector); if (!backends.empty()) { for (auto backend : backends) { - properties = ((*backend).*MethodPointer)(); + properties = ((*backend).*MethodPointer)(server_selector); if (!properties.empty()) { break; } @@ -476,10 +483,11 @@ protected: /// /// @code /// void createUpdateSubnet6(const Subnet6Ptr& subnet, - /// const BackendSelector& selector) { - /// createUpdateDeleteProperty - /// (subnet, selector); + /// const BackendSelector& selector, + /// const ServerSelector& server_selector) { + /// createUpdateDeleteProperty + /// (&ConfigBackendDHCPv6::createUpdateSubnet6, selector, + /// server_selector, subnet, selector); /// } /// @endcode /// @@ -487,29 +495,32 @@ protected: /// signature: /// /// @code - /// void createUpdateSubnet6(const Subnet6Ptr& subnet); + /// void createUpdateSubnet6(const ServerSelector&, const Subnet6Ptr&); /// @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. + /// @tparam InputType Type of the objects being used as arguments of the + /// backend method, e.g. new property to be added, updated or deleted. /// - /// @param input Object being a new property to be added or updated, or an - /// identifier of the object to be deleted. + /// @param MethodPointer Pointer to the backend method to be called. /// @param selector Backend selector. + /// @param server_selector Server selector. + /// @param input Objects used as arguments to the backend method to be + /// called. /// /// @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) { + template + void createUpdateDeleteProperty(void (ConfigBackendType::*MethodPointer) + (const ServerSelector&, InputType...), + const BackendSelector& selector, + const ServerSelector& server_selector, + InputType... input) { auto backends = selectBackends(selector); if (backends.empty()) { isc_throw(db::NoSuchDatabase, "no database found for selector: " @@ -520,7 +531,7 @@ protected: "selector: " << selector.toText()); } - (*(*(backends.begin())).*MethodPointer)(input); + (*(*(backends.begin())).*MethodPointer)(server_selector, input...); } /// @brief Selects existing backends matching the selector. diff --git a/src/lib/config_backend/tests/Makefile.am b/src/lib/config_backend/tests/Makefile.am index 29472adfa4..1c2b76d80f 100644 --- a/src/lib/config_backend/tests/Makefile.am +++ b/src/lib/config_backend/tests/Makefile.am @@ -22,6 +22,7 @@ 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_SOURCES += server_selector_unittest.cc libcb_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) libcb_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) diff --git a/src/lib/config_backend/tests/config_backend_mgr_unittest.cc b/src/lib/config_backend/tests/config_backend_mgr_unittest.cc index 57f4aad45c..a2568b6293 100644 --- a/src/lib/config_backend/tests/config_backend_mgr_unittest.cc +++ b/src/lib/config_backend/tests/config_backend_mgr_unittest.cc @@ -54,7 +54,8 @@ public: /// /// @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 { + virtual int getProperty(const ServerSelector&, + const std::string& property_name) const { for (auto property : properties_) { if (property.first == property_name) { return (property.second); @@ -63,12 +64,30 @@ public: return (0); } + /// @brief Retrieves first property matching the name and value. + /// + /// @param property_name Name of the property to be retrieved. + /// @param property_value Value of the property to be retrieved. + /// @return Value of the property or 0 if the property doesn't exist. + virtual int getProperty(const ServerSelector&, + const std::string& property_name, + const int property_value) const { + for (auto property : properties_) { + if ((property.first == property_name) && + (property.second == property_value)) { + 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 { + virtual PropertiesList getProperties(const ServerSelector&, + const std::string& property_name) const { PropertiesList properties; for (auto property : properties_) { if (property.first == property_name) { @@ -81,14 +100,15 @@ public: /// @brief Retrieves all properties. /// /// @return List of all properties held in the backend. - virtual PropertiesList getAllProperties() const { + virtual PropertiesList getAllProperties(const ServerSelector&) 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) { + virtual void createProperty(const ServerSelector&, + const std::pair& new_property) { properties_.push_back(new_property); } @@ -188,8 +208,13 @@ public: /// @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. + /// @param server_selector Server selector. The default value is set to @c ALL, + /// which means that the property for all servers will be returned. virtual int getProperty(const std::string& property_name, - const BackendSelector& selector = BackendSelector::UNSPEC()) const { + const BackendSelector& selector = + BackendSelector::UNSPEC(), + const ServerSelector& server_selector = + ServerSelector::ALL()) const { int property; // If the selector is specified, this method will pick the appropriate @@ -201,22 +226,48 @@ public: // 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); + getPropertyPtrConst + (&TestConfigBackend::getProperty, selector, server_selector, property, + property_name); return (property); } + /// @brief Retrieves value of the property. + /// + /// @param property_name Name of the property which value should be returned. + /// @param property_value Value of the property to be retrieved. + /// @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. + /// @param server_selector Server selector. The default value is set to @c ALL, + /// which means that the property for all servers will be returned. + virtual int getProperty(const std::string& property_name, + const int property_value, + const BackendSelector& selector = + BackendSelector::UNSPEC(), + const ServerSelector& server_selector = + ServerSelector::ALL()) const { + int property; + getPropertyPtrConst + (&TestConfigBackend::getProperty, selector, server_selector, property, + property_name, property_value); + 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. + /// @param server_selector Server selector. The default value is set to @c ALL, + /// which means that the properties for all servers will be returned. virtual PropertiesList getProperties(const std::string& property_name, const BackendSelector& selector = - BackendSelector::UNSPEC()) const { + BackendSelector::UNSPEC(), + const ServerSelector& server_selector = + ServerSelector::ALL()) const { PropertiesList properties; // If the selector is specified, this method will pick the appropriate @@ -226,9 +277,9 @@ public: // 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); + getMultiplePropertiesConst + (&TestConfigBackend::getProperties, selector, server_selector, + properties, property_name); return (properties); } @@ -237,14 +288,19 @@ public: /// @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. + /// @param server_selector Server selector. The default value is set to @c ALL, + /// which means that the properties for all servers will be returned. virtual PropertiesList getAllProperties(const BackendSelector& selector = - BackendSelector::UNSPEC()) const { + BackendSelector::UNSPEC(), + const ServerSelector& server_selector = + ServerSelector::ALL()) const { PropertiesList properties; // This method is similar to getMultiplePropertiesConst but it lacks // an argument and it simply returns all properties. - getAllPropertiesConst - (properties, selector); + getAllPropertiesConst + (&TestConfigBackend::getAllProperties, selector, server_selector, + properties); return (properties); } @@ -252,13 +308,16 @@ public: /// /// @param new_property New property to be added to a backend. /// @param selector Backend selector. It has no default value. + /// @param server_selector The default value is @c ALL which means that + /// new property is going to be shared by all servers. virtual void createProperty(const std::pair& new_property, - const BackendSelector& selector) { - createUpdateDeleteProperty&, - &TestConfigBackend::createProperty> - (new_property, selector); + const BackendSelector& selector, + const ServerSelector& server_selector = + ServerSelector::ALL()) { + createUpdateDeleteProperty&> + (&TestConfigBackend::createProperty, selector, server_selector, + new_property); } - }; using TestConfigBackendMgr = BaseConfigBackendMgr; @@ -399,6 +458,15 @@ TEST_F(ConfigBackendMgrTest, getSingleProperty) { EXPECT_EQ(2, config_mgr_.getPool()->getProperty("cats", BackendSelector(BackendSelector::Type::PGSQL))); + // Also make sure that the variant of getProperty function taking two arguments + // would return the value. + EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs", 1, + BackendSelector(BackendSelector::Type::MYSQL))); + + // If the value is not matching it should return 0. + EXPECT_EQ(0, config_mgr_.getPool()->getProperty("dogs", 2, + BackendSelector(BackendSelector::Type::MYSQL))); + // Try to use the backend that is not present. EXPECT_THROW(config_mgr_.getPool()->getProperty("cats", BackendSelector(BackendSelector::Type::CQL)), diff --git a/src/lib/config_backend/tests/server_selector_unittest.cc b/src/lib/config_backend/tests/server_selector_unittest.cc new file mode 100644 index 0000000000..bf532771b9 --- /dev/null +++ b/src/lib/config_backend/tests/server_selector_unittest.cc @@ -0,0 +1,51 @@ +// 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 + +using namespace isc::cb; + +namespace { + +// Check that server selector can be set to UNASSIGNED. +TEST(ServerSelectorTest, unassigned) { + ServerSelector selector = ServerSelector::UNASSIGNED(); + EXPECT_EQ(ServerSelector::Type::UNASSIGNED, selector.getType()); + EXPECT_TRUE(selector.getTags().empty()); +} + +// Check that server selector can be set to ALL. +TEST(ServerSelectorTest, all) { + ServerSelector selector = ServerSelector::ALL(); + EXPECT_EQ(ServerSelector::Type::ALL, selector.getType()); + EXPECT_TRUE(selector.getTags().empty()); +} + +// Check that a single server can be selected. +TEST(ServerSelectorTest, one) { + ServerSelector selector = ServerSelector::ONE("some-tag"); + EXPECT_EQ(ServerSelector::Type::SUBSET, selector.getType()); + + std::set tags = selector.getTags(); + ASSERT_EQ(1, tags.size()); + EXPECT_EQ(1, tags.count("some-tag")); +} + +// Check that multiple servers can be selected. +TEST(ServerSelectorTest, multiple) { + ServerSelector selector = ServerSelector::MULTIPLE({ "tag1", "tag2", "tag3" }); + EXPECT_EQ(ServerSelector::Type::SUBSET, selector.getType()); + + std::set tags = selector.getTags(); + ASSERT_EQ(3, tags.size()); + EXPECT_EQ(1, tags.count("tag1")); + EXPECT_EQ(1, tags.count("tag2")); + EXPECT_EQ(1, tags.count("tag3")); +} + +}