From 2fed7e62a4c868dfa1ce6a17cc2f631a29eb3d2b Mon Sep 17 00:00:00 2001 From: Thomas Markwalder Date: Tue, 1 Jul 2025 10:00:32 -0400 Subject: [PATCH] [#3770] CfgOption and CB v4 use client-classes v4 CfgOption and CB internals support client-classes as part of key for updating and delete options. CB parsing does NOT yet support it as argument. /src/lib/dhcpsrv/cfg_option.* Added composite key index type + client_classes to OptionContainer OptionDescriptor::equals() - add comparision of client_classes_ CfgOption::replace() - Use new type + client_classes index CfgOption::del(const std::string& option_space, const uint16_t option_code, const ClientClasses& client_classes) - new function CfgOption::get(const Selector& key, const uint16_t option_code, ClientClasses& client_classes) - new function CfgOption::del(const std::string& option_space, const uint16_t option_code, const ClientClasses& client_classes); /src/hooks/dhcp/mysql/mysql_cb_dhcp4.* Added client-classes to createUpdate and delete option SQL statements and functions /src/hooks/dhcp/mysql/mysql_cb_impl.* MySqlConfigBackendImpl::createClientClassesForWhereClause() - new function /src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.* Added client-classes to createUpdate and delete option SQL statements and functions /src/hooks/dhcp/pgsql/pgsql_cb_impl.cc PgSqlConfigBackendImpl::addClientClassesForWhereClause() - new function /src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc /src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc TEST_F(PgSqlConfigBackendDHCPv4Test, subnetOption4WithClienClassesTest) - new test /src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h Added typedef boost::shared_ptr ClientClassesPtr; /src/lib/dhcpsrv/config_backend_dhcp4.h /src/lib/dhcpsrv/config_backend_pool_dhcp4.* Udpated with client_classes parameter where needed /src/lib/dhcpsrv/tests/cfg_option_unittest.cc TEST_F(CfgOptionTest, optionsWithClientClasses) TEST_F(CfgOptionTest, replaceWithClientClasses) TEST_F(CfgOptionTest, deleteWithClientClasses) - new tests /src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.* GenericConfigBackendDHCPv4Test::subnetOption4WithClienClassesTest() - new test /src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc Update functions as needed --- src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc | 101 +++++---- src/hooks/dhcp/mysql/mysql_cb_dhcp4.h | 20 +- src/hooks/dhcp/mysql/mysql_cb_impl.cc | 10 +- src/hooks/dhcp/mysql/mysql_cb_impl.h | 7 + src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc | 119 +++++++---- src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h | 24 ++- src/hooks/dhcp/pgsql/pgsql_cb_impl.cc | 12 ++ src/hooks/dhcp/pgsql/pgsql_cb_impl.h | 15 +- .../pgsql/tests/pgsql_cb_dhcp4_unittest.cc | 4 + src/lib/dhcp/classify.h | 3 + src/lib/dhcpsrv/cfg_option.cc | 55 ++++- src/lib/dhcpsrv/cfg_option.h | 90 +++++++- src/lib/dhcpsrv/config_backend_dhcp4.h | 20 +- src/lib/dhcpsrv/config_backend_pool_dhcp4.cc | 21 +- src/lib/dhcpsrv/config_backend_pool_dhcp4.h | 20 +- src/lib/dhcpsrv/tests/cfg_option_unittest.cc | 197 ++++++++++++++++++ .../testutils/generic_backend_unittest.cc | 2 +- .../testutils/generic_cb_dhcp4_unittest.cc | 152 ++++++++++++++ .../testutils/generic_cb_dhcp4_unittest.h | 2 + .../testutils/test_config_backend_dhcp4.cc | 29 ++- .../testutils/test_config_backend_dhcp4.h | 23 +- 21 files changed, 794 insertions(+), 132 deletions(-) diff --git a/src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc b/src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc index 2a4d81f00f..5491078256 100644 --- a/src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc +++ b/src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc @@ -1863,7 +1863,8 @@ public: cc_binding, MySqlBinding::createString(tag), MySqlBinding::createInteger(option->option_->getType()), - MySqlBinding::condCreateString(option->space_name_) + MySqlBinding::condCreateString(option->space_name_), + cc_binding }; MySqlTransaction transaction(conn_); @@ -1877,8 +1878,8 @@ public: if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4, in_bindings) == 0) { - // Remove the 3 bindings used only in case of update. - in_bindings.resize(in_bindings.size() - 3); + // Remove the 4 bindings used only in case of update. + in_bindings.resize(in_bindings.size() - 4); insertOption4(server_selector, in_bindings); } @@ -1920,7 +1921,8 @@ public: cc_binding, MySqlBinding::createInteger(static_cast(subnet_id)), MySqlBinding::createInteger(option->option_->getType()), - MySqlBinding::condCreateString(option->space_name_) + MySqlBinding::condCreateString(option->space_name_), + cc_binding }; boost::scoped_ptr transaction; @@ -1941,8 +1943,8 @@ public: if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SUBNET_ID, in_bindings) == 0) { - // Remove the 3 bindings used only in case of update. - in_bindings.resize(in_bindings.size() - 3); + // Remove the 4 bindings used only in case of update. + in_bindings.resize(in_bindings.size() - 4); insertOption4(server_selector, in_bindings); } @@ -2008,7 +2010,8 @@ public: cc_binding, MySqlBinding::createInteger(pool_id), MySqlBinding::createInteger(option->option_->getType()), - MySqlBinding::condCreateString(option->space_name_) + MySqlBinding::condCreateString(option->space_name_), + cc_binding }; MySqlTransaction transaction(conn_); @@ -2023,8 +2026,8 @@ public: if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_POOL_ID, in_bindings) == 0) { - // Remove the 3 bindings used only in case of update. - in_bindings.resize(in_bindings.size() - 3); + // Remove the 4 bindings used only in case of update. + in_bindings.resize(in_bindings.size() - 4); insertOption4(server_selector, in_bindings); } @@ -2067,7 +2070,8 @@ public: cc_binding, MySqlBinding::createString(shared_network_name), MySqlBinding::createInteger(option->option_->getType()), - MySqlBinding::condCreateString(option->space_name_) + MySqlBinding::condCreateString(option->space_name_), + cc_binding }; boost::scoped_ptr transaction; @@ -2089,8 +2093,8 @@ public: if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: UPDATE_OPTION4_SHARED_NETWORK, in_bindings) == 0) { - // Remove the 3 bindings used only in case of update. - in_bindings.resize(in_bindings.size() - 3); + // Remove the 4 bindings used only in case of update. + in_bindings.resize(in_bindings.size() - 4); insertOption4(server_selector, in_bindings); } @@ -2131,7 +2135,8 @@ public: cc_binding, MySqlBinding::createString(client_class->getName()), MySqlBinding::createInteger(option->option_->getType()), - MySqlBinding::condCreateString(option->space_name_) + MySqlBinding::condCreateString(option->space_name_), + cc_binding }; // Create scoped audit revision. As long as this instance exists @@ -2145,8 +2150,8 @@ public: if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: UPDATE_OPTION4_CLIENT_CLASS, in_bindings) == 0) { - // Remove the 3 bindings used only in case of update. - in_bindings.resize(in_bindings.size() - 3); + // Remove the 4 bindings used only in case of update. + in_bindings.resize(in_bindings.size() - 4); insertOption4(server_selector, in_bindings); } } @@ -2231,13 +2236,16 @@ public: /// @param server_selector Server selector. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. /// @return Number of deleted options. uint64_t deleteOption4(const ServerSelector& server_selector, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { MySqlBindingCollection in_bindings = { MySqlBinding::createInteger(code), - MySqlBinding::createString(space) + MySqlBinding::createString(space), + createClientClassesForWhereClause(client_classes) }; // Run DELETE. @@ -2255,15 +2263,18 @@ public: /// belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. /// @return Number of deleted options. uint64_t deleteOption4(const ServerSelector& server_selector, const SubnetID& subnet_id, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { MySqlBindingCollection in_bindings = { MySqlBinding::createInteger(static_cast(subnet_id)), MySqlBinding::createInteger(code), - MySqlBinding::createString(space) + MySqlBinding::createString(space), + createClientClassesForWhereClause(client_classes) }; // Run DELETE. @@ -2281,17 +2292,20 @@ public: /// @param pool_end_address Upper bound pool address. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. /// @return Number of deleted options. uint64_t deleteOption4(const db::ServerSelector& server_selector, const IOAddress& pool_start_address, const IOAddress& pool_end_address, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { MySqlBindingCollection in_bindings = { MySqlBinding::createInteger(code), MySqlBinding::createString(space), MySqlBinding::createInteger(pool_start_address.toUint32()), - MySqlBinding::createInteger(pool_end_address.toUint32()) + MySqlBinding::createInteger(pool_end_address.toUint32()), + createClientClassesForWhereClause(client_classes) }; // Run DELETE. @@ -2309,15 +2323,18 @@ public: /// option belongs to /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. /// @return Number of deleted options. uint64_t deleteOption4(const db::ServerSelector& server_selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { MySqlBindingCollection in_bindings = { MySqlBinding::createString(shared_network_name), MySqlBinding::createInteger(code), - MySqlBinding::createString(space) + MySqlBinding::createString(space), + createClientClassesForWhereClause(client_classes) }; // Run DELETE. @@ -3499,27 +3516,31 @@ TaggedStatementArray tagged_statements = { { // Update existing global option. { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4, - MYSQL_UPDATE_OPTION4_WITH_TAG(AND o.scope_id = 0 AND o.code = ? AND o.space = ?) + MYSQL_UPDATE_OPTION4_WITH_TAG(AND o.scope_id = 0 AND o.code = ? AND o.space = ? AND o.client_classes = ?) }, // Update existing subnet level option. { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SUBNET_ID, - MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 1 AND o.dhcp4_subnet_id = ? AND o.code = ? AND o.space = ?) + MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 1 AND o.dhcp4_subnet_id = ? AND o.code = ? AND o.space = ? + AND o.client_classes = ?) }, // Update existing pool level option. { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_POOL_ID, - MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 5 AND o.pool_id = ? AND o.code = ? AND o.space = ?) + MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 5 AND o.pool_id = ? AND o.code = ? AND o.space = ? + AND o.client_classes = ?) }, // Update existing shared network level option. { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SHARED_NETWORK, - MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?) + MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ? + AND o.client_classes = ?) }, // Update existing client class level option. { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_CLIENT_CLASS, - MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 2 AND o.dhcp_client_class = ? AND o.code = ? AND o.space = ?) + MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 2 AND o.dhcp_client_class = ? AND o.code = ? AND o.space = ? + AND o.client_classes = ?) }, // Update existing client class with specifying its position. @@ -3644,7 +3665,8 @@ TaggedStatementArray tagged_statements = { { // Delete single global option. { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4, - MYSQL_DELETE_OPTION_WITH_TAG(dhcp4, AND o.scope_id = 0 AND o.code = ? AND o.space = ?) + MYSQL_DELETE_OPTION_WITH_TAG(dhcp4, AND o.scope_id = 0 AND o.code = ? AND o.space = ? + AND o.client_classes LIKE ?) }, // Delete all global options which are unassigned to any servers. @@ -4226,10 +4248,11 @@ MySqlConfigBackendDHCPv4::deleteAllOptionDefs4(const ServerSelector& server_sele uint64_t MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& server_selector, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_OPTION4) .arg(code).arg(space); - uint64_t result = impl_->deleteOption4(server_selector, code, space); + uint64_t result = impl_->deleteOption4(server_selector, code, space, client_classes); LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_OPTION4_RESULT) .arg(result); return (result); @@ -4239,14 +4262,15 @@ uint64_t MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector */, const std::string& shared_network_name, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { /// @todo In the future we might use the server selector to make sure that the /// option is only deleted if the pool belongs to a given server. For now, we /// just delete it when there is a match with the parent object. LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_SHARED_NETWORK_OPTION4) .arg(shared_network_name).arg(code).arg(space); uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), shared_network_name, - code, space); + code, space, client_classes); LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_SHARED_NETWORK_OPTION4_RESULT) .arg(result); return (result); @@ -4256,13 +4280,15 @@ uint64_t MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector */, const SubnetID& subnet_id, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { /// @todo In the future we might use the server selector to make sure that the /// option is only deleted if the pool belongs to a given server. For now, we /// just delete it when there is a match with the parent object. LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_BY_SUBNET_ID_OPTION4) .arg(subnet_id).arg(code).arg(space); - uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), subnet_id, code, space); + uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), subnet_id, code, space, + client_classes); LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_BY_SUBNET_ID_OPTION4_RESULT) .arg(result); return (result); @@ -4273,14 +4299,15 @@ MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { /// @todo In the future we might use the server selector to make sure that the /// option is only deleted if the pool belongs to a given server. For now, we /// just delete it when there is a match with the parent object. LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_BY_POOL_OPTION4) .arg(pool_start_address.toText()).arg(pool_end_address.toText()).arg(code).arg(space); uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), pool_start_address, - pool_end_address, code, space); + pool_end_address, code, space, client_classes); LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_BY_POOL_OPTION4_RESULT) .arg(result); return (result); diff --git a/src/hooks/dhcp/mysql/mysql_cb_dhcp4.h b/src/hooks/dhcp/mysql/mysql_cb_dhcp4.h index 782b367150..979322e30c 100644 --- a/src/hooks/dhcp/mysql/mysql_cb_dhcp4.h +++ b/src/hooks/dhcp/mysql/mysql_cb_dhcp4.h @@ -464,11 +464,14 @@ public: /// @param server_selector Server selector. /// @param code Code of the option to be deleted. /// @param space Option space of the option to be deleted. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. /// @throw NotImplemented if server selector is "unassigned". virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes shared network level option. /// @@ -477,12 +480,15 @@ public: /// option belongs to /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @throw NotImplemented if server selector is "unassigned". virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes subnet level option. /// @@ -491,11 +497,14 @@ public: /// belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. /// @throw NotImplemented if server selector is "unassigned". virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id, - const uint16_t code, const std::string& space); + const uint16_t code, const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes pool level option. /// @@ -506,6 +515,8 @@ public: /// deleted option belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. /// @throw NotImplemented if server selector is "unassigned". virtual uint64_t @@ -513,7 +524,8 @@ public: const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes global parameter. /// diff --git a/src/hooks/dhcp/mysql/mysql_cb_impl.cc b/src/hooks/dhcp/mysql/mysql_cb_impl.cc index e0f30e3f6d..751cb00cb9 100644 --- a/src/hooks/dhcp/mysql/mysql_cb_impl.cc +++ b/src/hooks/dhcp/mysql/mysql_cb_impl.cc @@ -1115,11 +1115,13 @@ MySqlConfigBackendImpl::getPort() const { } db::MySqlBindingPtr -MySqlConfigBackendImpl::createInputClientClassesBinding(const ClientClasses& client_classes) { - if (client_classes.empty()) { - return(db::MySqlBinding::createNull()); - } +MySqlConfigBackendImpl::createClientClassesForWhereClause(ClientClassesPtr client_classes) { + return (client_classes ? createInputClientClassesBinding(*client_classes) + : MySqlBinding::createString("%")); +} +db::MySqlBindingPtr +MySqlConfigBackendImpl::createInputClientClassesBinding(const ClientClasses& client_classes) { // Create JSON list of client classes. data::ElementPtr client_classes_element = data::Element::createList(); for (auto const& client_class : client_classes) { diff --git a/src/hooks/dhcp/mysql/mysql_cb_impl.h b/src/hooks/dhcp/mysql/mysql_cb_impl.h index ec458c5a49..334a75cf5a 100644 --- a/src/hooks/dhcp/mysql/mysql_cb_impl.h +++ b/src/hooks/dhcp/mysql/mysql_cb_impl.h @@ -622,6 +622,13 @@ public: /// relay addresses specified). db::MySqlBindingPtr createInputRelayBinding(const NetworkPtr& network); + /// @brief Creates input binding from a list of client classes + /// + /// @param client_classes ClientClasses collection containing the class names + /// @return Pointer to the binding (possibly null binding if there are no + /// classes specified). + db::MySqlBindingPtr createClientClassesForWhereClause(ClientClassesPtr client_classes); + /// @brief Creates input binding from a list of client classes /// /// @param client_classes ClientClasses collection containing the class names diff --git a/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc b/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc index 3d5a9d0aab..3272191e1d 100644 --- a/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc +++ b/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc @@ -1692,6 +1692,7 @@ public: in_bindings.add(tag); in_bindings.add(option->option_->getType()); in_bindings.addOptional(option->space_name_); + addClientClassesBinding(in_bindings, option->client_classes_); // Start transaction. PgSqlTransaction transaction(conn_); @@ -1761,6 +1762,7 @@ public: in_bindings.add(subnet_id); in_bindings.add(option->option_->getType()); in_bindings.addOptional(option->space_name_); + addClientClassesBinding(in_bindings, option->client_classes_); // Start transaction. PgSqlTransaction transaction(conn_); @@ -1853,6 +1855,7 @@ public: in_bindings.add(pool_id); in_bindings.add(option->option_->getType()); in_bindings.addOptional(option->space_name_); + addClientClassesBinding(in_bindings, option->client_classes_); // Start transaction. PgSqlTransaction transaction(conn_); @@ -1924,6 +1927,7 @@ public: in_bindings.add(shared_network_name); in_bindings.add(option->option_->getType()); in_bindings.addOptional(option->space_name_); + addClientClassesBinding(in_bindings, option->client_classes_); // Start transaction. PgSqlTransaction transaction(conn_); @@ -1991,6 +1995,7 @@ public: in_bindings.add(class_name); in_bindings.add(option->option_->getType()); in_bindings.addOptional(option->space_name_); + addClientClassesBinding(in_bindings, option->client_classes_); // Create scoped audit revision. As long as this instance exists // no new audit revisions are created in any subsequent calls. @@ -2093,13 +2098,16 @@ public: /// @param server_selector Server selector. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. /// @return Number of deleted options. uint64_t deleteOption4(const ServerSelector& server_selector, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { PsqlBindArray in_bindings; in_bindings.add(code); in_bindings.add(space); + addClientClassesForWhereClause(in_bindings, client_classes); // Run DELETE. return (deleteTransactional(PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4, @@ -2117,15 +2125,18 @@ public: /// belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. /// @return Number of deleted options. uint64_t deleteOption4(const ServerSelector& server_selector, const SubnetID& subnet_id, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { PsqlBindArray in_bindings; in_bindings.add(subnet_id); in_bindings.add(code); in_bindings.add(space); + addClientClassesForWhereClause(in_bindings, client_classes); // Run DELETE. return (deleteTransactional(PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SUBNET_ID, @@ -2143,17 +2154,20 @@ public: /// @param pool_end_address Upper bound pool address. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. /// @return Number of deleted options. uint64_t deleteOption4(const db::ServerSelector& server_selector, const IOAddress& pool_start_address, const IOAddress& pool_end_address, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { PsqlBindArray in_bindings; in_bindings.addInet4(pool_start_address); in_bindings.addInet4(pool_end_address); in_bindings.add(code); in_bindings.add(space); + addClientClassesForWhereClause(in_bindings, client_classes); // Run DELETE. return (deleteTransactional(PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_POOL_RANGE, @@ -2171,15 +2185,18 @@ public: /// option belongs to /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. /// @return Number of deleted options. uint64_t deleteOption4(const db::ServerSelector& server_selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { PsqlBindArray in_bindings; in_bindings.add(shared_network_name); in_bindings.add(code); in_bindings.add(space); + addClientClassesForWhereClause(in_bindings, client_classes); // Run DELETE. return (deleteTransactional(PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SHARED_NETWORK, @@ -4013,7 +4030,7 @@ TaggedStatementArray tagged_statements = { { // Update existing global option. { // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4, - 17, + 18, { OID_INT2, // 1 code OID_BYTEA, // 2 value @@ -4032,15 +4049,16 @@ TaggedStatementArray tagged_statements = { { OID_VARCHAR, // 15 server_tag OID_INT2, // 16 code (of option to update) OID_VARCHAR, // 17 space (of option to update) + OID_VARCHAR // 18 client_classes (of option to update) }, "UPDATE_OPTION4", - PGSQL_UPDATE_OPTION4_WITH_TAG(AND o.scope_id = 0 AND o.code = $16 AND o.space = $17) + PGSQL_UPDATE_OPTION4_WITH_TAG(AND o.scope_id = 0 AND o.code = $16 AND o.space = $17 AND o.client_classes = $18) }, // Update existing subnet level option. { // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SUBNET_ID, - 17, + 18, { OID_INT2, // 1 code OID_BYTEA, // 2 value @@ -4058,16 +4076,17 @@ TaggedStatementArray tagged_statements = { { OID_TEXT, // 14 client_classes OID_INT8, // 15 subnet_id (of option to update) OID_INT2, // 16 code (of option to update) - OID_VARCHAR // 17 space (of option to update) + OID_VARCHAR, // 17 space (of option to update) + OID_VARCHAR // 18 client_classes (of option to update) }, "UPDATE_OPTION4_SUBNET_ID", - PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 1 AND o.dhcp4_subnet_id = $15 AND o.code = $16 AND o.space = $17) + PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 1 AND o.dhcp4_subnet_id = $15 AND o.code = $16 AND o.space = $17 AND o.client_classes = $18) }, // Update existing pool level option. { // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_POOL_ID, - 17, + 18, { OID_INT2, // 1 code OID_BYTEA, // 2 value @@ -4085,16 +4104,17 @@ TaggedStatementArray tagged_statements = { { OID_TEXT, // 14 client_classes OID_INT8, // 15 pool_id (of option to update) OID_INT2, // 16 code (of option to update) - OID_VARCHAR // 17 space (of option to update) + OID_VARCHAR, // 17 space (of option to update) + OID_VARCHAR // 18 client_classes (of option to update) }, "UPDATE_OPTION4_POOL_ID", - PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 5 AND o.pool_id = $15 AND o.code = $16 AND o.space = $17) + PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 5 AND o.pool_id = $15 AND o.code = $16 AND o.space = $17 AND o.client_classes = $18) }, // Update existing shared network level option. { // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SHARED_NETWORK, - 17, + 18, { OID_INT2, // 1 code OID_BYTEA, // 2 value @@ -4112,16 +4132,17 @@ TaggedStatementArray tagged_statements = { { OID_TEXT, // 14 client_classes OID_VARCHAR, // 15 shared_network_name (of option to update) OID_INT2, // 16 code (of option to update) - OID_VARCHAR // 17 space (of option to update) + OID_VARCHAR, // 17 space (of option to update) + OID_VARCHAR // 18 client_classes (of option to update) }, "UPDATE_OPTION4_SHARED_NETWORK", - PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 4 AND o.shared_network_name = $15 AND o.code = $16 AND o.space = $17) + PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 4 AND o.shared_network_name = $15 AND o.code = $16 AND o.space = $17 AND o.client_classes = $18) }, // Update existing client class level option. { // PgSqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_CLIENT_CLASS, - 17, + 18, { OID_INT2, // 1 code OID_BYTEA, // 2 value @@ -4140,9 +4161,10 @@ TaggedStatementArray tagged_statements = { { OID_VARCHAR, // 15 dhcp_client_class (of option to update) OID_INT2, // 16 code (of option to update) OID_VARCHAR, // 17 space (of option to update) + OID_VARCHAR // 18 client_classes (of option to update) }, "UPDATE_OPTION4_CLIENT_CLASS", - PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 2 AND o.dhcp_client_class = $15 AND o.code = $16 AND o.space = $17) + PGSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 2 AND o.dhcp_client_class = $15 AND o.code = $16 AND o.space = $17 AND o.client_classes = $18) }, // Update existing client class with specifying its position. @@ -4450,14 +4472,16 @@ TaggedStatementArray tagged_statements = { { // Delete single global option. { // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4, - 3, + 4, { OID_VARCHAR, // 1 server_tag OID_INT2, // 2 code - OID_VARCHAR // 3 space + OID_VARCHAR, // 3 space + OID_TEXT // 4 client_classes }, "DELETE_OPTION4", - PGSQL_DELETE_OPTION_WITH_TAG(dhcp4, AND o.scope_id = 0 AND o.code = $2 AND o.space = $3) + PGSQL_DELETE_OPTION_WITH_TAG(dhcp4, AND o.scope_id = 0 AND o.code = $2 AND o.space = $3 + AND o.client_classes LIKE $4) }, // Delete all global options which are unassigned to any servers. @@ -4474,43 +4498,49 @@ TaggedStatementArray tagged_statements = { { // Delete single option from a subnet. { // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SUBNET_ID, - 3, + 4, { - OID_INT8, // 1 subnet_id - OID_INT2, // 2 code - OID_VARCHAR // 3 space + OID_INT8, // 1 subnet_id + OID_INT2, // 2 code + OID_VARCHAR, // 3 space + OID_TEXT // 4 client_classes }, "DELETE_OPTION4_SUBNET_ID", PGSQL_DELETE_OPTION_NO_TAG(dhcp4, - WHERE o.scope_id = 1 AND o.dhcp4_subnet_id = $1 AND o.code = $2 AND o.space = $3) + WHERE o.scope_id = 1 AND o.dhcp4_subnet_id = $1 AND o.code = $2 AND o.space = $3 + AND o.client_classes LIKE $4) }, // Delete single option from a pool. { // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_POOL_RANGE, - 4, + 5, { - OID_TEXT, // 1 start_address - cast as inet - OID_TEXT, // 2 start_address - cast as inet - OID_INT2, // 3 code - OID_VARCHAR // 4 space + OID_TEXT, // 1 start_address - cast as inet + OID_TEXT, // 2 start_address - cast as inet + OID_INT2, // 3 code + OID_VARCHAR, // 4 space + OID_TEXT // 5 client_classes }, "DELETE_OPTION4_POOL_RANGE", - PGSQL_DELETE_OPTION_POOL_RANGE(dhcp4, o.scope_id = 5 AND o.code = $3 AND o.space = $4) + PGSQL_DELETE_OPTION_POOL_RANGE(dhcp4, o.scope_id = 5 AND o.code = $3 AND o.space = $4 + AND o.client_classes LIKE $5) }, // Delete single option from a shared network. { // PgSqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SHARED_NETWORK, - 3, + 4, { OID_VARCHAR, // 1 shared_network_name OID_INT2, // 2 code - OID_VARCHAR // 3 space + OID_VARCHAR, // 3 space + OID_TEXT // 4 client_classes }, "DELETE_OPTION4_SHARED_NETWORK", PGSQL_DELETE_OPTION_NO_TAG(dhcp4, - WHERE o.scope_id = 4 AND o.shared_network_name = $1 AND o.code = $2 AND o.space = $3) + WHERE o.scope_id = 4 AND o.shared_network_name = $1 AND o.code = $2 AND o.space = $3 + AND o.client_classes LIKE $4) }, // Delete options belonging to a subnet. @@ -5163,10 +5193,11 @@ PgSqlConfigBackendDHCPv4::deleteAllOptionDefs4(const ServerSelector& server_sele uint64_t PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& server_selector, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_OPTION4) .arg(code).arg(space); - uint64_t result = impl_->deleteOption4(server_selector, code, space); + uint64_t result = impl_->deleteOption4(server_selector, code, space, client_classes); LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_OPTION4_RESULT) .arg(result); return (result); @@ -5176,14 +5207,15 @@ uint64_t PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector */, const std::string& shared_network_name, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { /// @todo In the future we might use the server selector to make sure that the /// option is only deleted if the pool belongs to a given server. For now, we /// just delete it when there is a match with the parent object. LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SHARED_NETWORK_OPTION4) .arg(shared_network_name).arg(code).arg(space); uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), shared_network_name, - code, space); + code, space, client_classes); LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_SHARED_NETWORK_OPTION4_RESULT) .arg(result); return (result); @@ -5193,13 +5225,15 @@ uint64_t PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector */, const SubnetID& subnet_id, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { /// @todo In the future we might use the server selector to make sure that the /// option is only deleted if the pool belongs to a given server. For now, we /// just delete it when there is a match with the parent object. LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_SUBNET_ID_OPTION4) .arg(subnet_id).arg(code).arg(space); - uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), subnet_id, code, space); + uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), subnet_id, code, space, + client_classes); LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_SUBNET_ID_OPTION4_RESULT) .arg(result); return (result); @@ -5210,14 +5244,15 @@ PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { /// @todo In the future we might use the server selector to make sure that the /// option is only deleted if the pool belongs to a given server. For now, we /// just delete it when there is a match with the parent object. LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_POOL_OPTION4) .arg(pool_start_address.toText()).arg(pool_end_address.toText()).arg(code).arg(space); uint64_t result = impl_->deleteOption4(ServerSelector::ANY(), pool_start_address, - pool_end_address, code, space); + pool_end_address, code, space, client_classes); LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_DELETE_BY_POOL_OPTION4_RESULT) .arg(result); return (result); diff --git a/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h b/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h index 7e0104604a..8fac877ad3 100644 --- a/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h +++ b/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h @@ -464,11 +464,14 @@ public: /// @param server_selector Server selector. /// @param code Code of the option to be deleted. /// @param space Option space of the option to be deleted. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. /// @throw NotImplemented if server selector is "unassigned". virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes shared network level option. /// @@ -477,12 +480,15 @@ public: /// option belongs to /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @throw NotImplemented if server selector is "unassigned". virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes subnet level option. /// @@ -491,11 +497,16 @@ public: /// belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. /// @throw NotImplemented if server selector is "unassigned". virtual uint64_t - deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id, - const uint16_t code, const std::string& space); + deleteOption4(const db::ServerSelector& server_selector, + const SubnetID& subnet_id, + const uint16_t code, + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes pool level option. /// @@ -506,6 +517,8 @@ public: /// deleted option belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. /// @throw NotImplemented if server selector is "unassigned". virtual uint64_t @@ -513,7 +526,8 @@ public: const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes global parameter. /// diff --git a/src/hooks/dhcp/pgsql/pgsql_cb_impl.cc b/src/hooks/dhcp/pgsql/pgsql_cb_impl.cc index 40f6aa2705..f909afbb3a 100644 --- a/src/hooks/dhcp/pgsql/pgsql_cb_impl.cc +++ b/src/hooks/dhcp/pgsql/pgsql_cb_impl.cc @@ -1160,5 +1160,17 @@ PgSqlConfigBackendImpl::addClientClassesBinding(db::PsqlBindArray& bindings, bindings.add(client_classes_element); } +void +PgSqlConfigBackendImpl::addClientClassesForWhereClause(db::PsqlBindArray& bindings, + const ClientClassesPtr client_classes + /* = ClientClassePtr() */) { + if (client_classes) { + addClientClassesBinding(bindings, *client_classes); + } else { + // Wildcard for LIKE expression. + bindings.add("%"); + } +} + } // end of namespace isc::dhcp } // end of namespace isc diff --git a/src/hooks/dhcp/pgsql/pgsql_cb_impl.h b/src/hooks/dhcp/pgsql/pgsql_cb_impl.h index 4e495b587d..3576c9e28c 100644 --- a/src/hooks/dhcp/pgsql/pgsql_cb_impl.h +++ b/src/hooks/dhcp/pgsql/pgsql_cb_impl.h @@ -628,10 +628,23 @@ public: /// Creates an Element tree of client class names and adds that to the end /// of the given bind array. /// + /// @param bindings PsqlBindArray to which the option value should be added. /// @param client_classes ClientClasses collection containing the class names - void addClientClassesBinding(db::PsqlBindArray& bindings, + void addClientClassesBinding(db::PsqlBindArray& bindings, const ClientClasses& client_classes); + /// @brief Adds 'client-classes' to a bind array as a where clause argument. + /// + /// If client_classes parameter is not an empty pointer, it creates an Element + /// tree of client class names and adds that to the end of the given bind array. + /// Otherwise it adds a the LIKE operator wild-card string value, "%". + /// + /// @param bindings PsqlBindArray to which the option value should be added. + /// @param client_classes ClientClasses collection containing the class names. + void addClientClassesForWhereClause(db::PsqlBindArray& bindings, + const ClientClassesPtr client_classes + = ClientClassesPtr()); + /// @brief Iterates over the class names in a JSON list element at a /// given column, adding each name to the given ClientClasses instance. /// diff --git a/src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc b/src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc index f8ba885fd5..54b6a2ab69 100644 --- a/src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc +++ b/src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc @@ -401,6 +401,10 @@ TEST_F(PgSqlConfigBackendDHCPv4Test, multipleAuditEntriesTest) { multipleAuditEntriesTest(); } +TEST_F(PgSqlConfigBackendDHCPv4Test, subnetOption4WithClienClassesTest) { + subnetOption4WithClienClassesTest(); +} + /// @brief Test fixture for verifying database connection loss-recovery /// behavior. class PgSqlConfigBackendDHCPv4DbLostCallbackTest : public GenericConfigBackendDbLostCallbackTest { diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h index 8571cf9909..9e60e24393 100644 --- a/src/lib/dhcp/classify.h +++ b/src/lib/dhcp/classify.h @@ -255,6 +255,9 @@ private: ClientClassContainer container_; }; +/// @brief Smart pointer to ClientClasses object. +typedef boost::shared_ptr ClientClassesPtr; + } } diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc index 5fbb2dee5b..8f18c135e8 100644 --- a/src/lib/dhcpsrv/cfg_option.cc +++ b/src/lib/dhcpsrv/cfg_option.cc @@ -48,7 +48,9 @@ OptionDescriptor::equals(const OptionDescriptor& other) const { (cancelled_ == other.cancelled_) && (formatted_value_ == other.formatted_value_) && (space_name_ == other.space_name_) && - option_->equals(other.option_)); + ((option_ && other.option_) && + option_->equals(other.option_)) && + (client_classes_ == other.client_classes_)); } void @@ -128,15 +130,16 @@ CfgOption::replace(const OptionDescriptor& desc, const std::string& option_space } // Find the option we want to replace. - OptionContainerTypeIndex& idx = options->get<1>(); - auto const& od_itr = idx.find(desc.option_->getType()); - if (od_itr == idx.end()) { + auto& idx6 = options->get<6>(); + auto const& od_itr = idx6.find(boost::make_tuple(desc.option_->getType(), desc.client_classes_)); + if (od_itr == idx6.end()) { isc_throw(isc::BadValue, "cannot replace option: " << option_space << ":" << desc.option_->getType() + << " , client-classes: " << desc.client_classes_.toText() << ", it does not exist"); } - idx.replace(od_itr, desc); + idx6.replace(od_itr, desc); } std::list @@ -409,6 +412,48 @@ CfgOption::del(const std::string& option_space, const uint16_t option_code) { return (idx.erase(option_code)); } +size_t +CfgOption::del(const std::string& option_space, const uint16_t option_code, + const ClientClasses& client_classes) { + // Check for presence of options. + OptionContainerPtr options = getAll(option_space); + if (!options || options->empty()) { + // There are no options, so there is nothing to do. + return (0); + } + + // If this is not top level option we may also need to delete the + // option instance from options encapsulating the particular option + // space. + if ((option_space != DHCP4_OPTION_SPACE) && + (option_space != DHCP6_OPTION_SPACE)) { + // For each option space name iterate over the existing options. + auto option_space_names = getOptionSpaceNames(); + for (auto const& option_space_from_list : option_space_names) { + // Get all options within the particular option space. + auto const& options_in_space = getAll(option_space_from_list); + for (auto const& option_it : *options_in_space) { + + // Check if the option encapsulates our option space and + // it does, try to delete our option. + if (option_it.option_ && + (option_it.option_->getEncapsulatedSpace() == option_space)) { + option_it.option_->delOption(option_code); + } + } + } + } + + auto& idx6 = options->get<6>(); + auto range = idx6.equal_range(boost::make_tuple(option_code, client_classes)); + auto count = std::distance(range.first, range.second); + if (count) { + idx6.erase(range.first, range.second); + } + + return(count); +} + size_t CfgOption::del(const uint32_t vendor_id, const uint16_t option_code) { // Check for presence of options. diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index 5c3c1958d2..53d7df9e2d 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -315,6 +316,27 @@ typedef boost::multi_index_container< bool, &OptionDescriptor::cancelled_ > + >, + // Start definition of index #6. + boost::multi_index::hashed_non_unique< + boost::multi_index::composite_key< + OptionDescriptor, + KeyFromKeyExtractor< + boost::multi_index::const_mem_fun< + Option, + uint16_t, + &Option::getType + >, + boost::multi_index::member< + OptionDescriptor, + OptionPtr, + &OptionDescriptor::option_ + > + >, + boost::multi_index::member + > > > > OptionContainer; @@ -343,6 +365,11 @@ typedef OptionContainer::nth_index<5>::type OptionContainerCancelIndex; typedef std::pair OptionContainerCancelRange; +/// Type of the index #6 - option type + client_classes. +typedef OptionContainer::nth_index<6>::type OptionContainerTypeClassesIndex; +typedef std::pair OptionContainerTypeClassesRange; + /// @brief Represents option data configuration for the DHCP server. /// /// This class holds a collection of options to be sent to a DHCP client. @@ -674,6 +701,41 @@ public: return (list); } + /// @brief Returns option for the specified key, option code and client classes tag. + /// + /// The key should be a string, in which case it specifies an option space + /// name, or an uint32_t value, in which case it specifies a vendor + /// identifier. + /// + /// @param key Option space name or vendor identifier. + /// @param option_code Code of the option to be returned. + /// @param client_classes client classes tag of the option to be returned. + /// @tparam Selector one of: @c std::string or @c uint32_t + /// + /// @return Descriptor of the option. If option hasn't been found, the + /// descriptor holds null option. + template + OptionDescriptor get(const Selector& key, + const uint16_t option_code, + ClientClasses& client_classes) const { + + // Check for presence of options. + OptionContainerPtr options = getAll(key); + if (!options || options->empty()) { + return (OptionDescriptor(false, false)); + } + + // Some options present, locate the one we are interested in + // using code + client_classes index. + auto& idx6 = options->get<6>(); + auto const& od_itr6 = idx6.find(boost::make_tuple(option_code, client_classes)); + if (od_itr6 == idx6.end()) { + return (OptionDescriptor(false, false)); + } + + return (*od_itr6); + } + /// @brief Fetches an option for a given code if it is allowed for the given list /// of client classes. /// @@ -696,7 +758,7 @@ public: // We treat the empty client-classes case (if present) as a default. // If we encounter it before we reach the end of the list of options - // remember it but keep checking the list for an actual match. We do + // remember it but keep checking the list for an actual match. We do // it this way to avoid expecting the entries in any particular order. auto & index = options->get<1>(); auto range = index.equal_range(option_code); @@ -708,8 +770,8 @@ public: return (*range.first); } break; - default: - { + default: + { auto default_opt = index.end(); auto otr = range.first; while (otr != range.second) { @@ -717,18 +779,18 @@ public: if (!(*otr).client_classes_.empty()) { return (*otr); } - + default_opt = otr; } - + ++otr; } - + // If we have a default return it. if (default_opt != index.end()) { return (*default_opt); } - }} + }} // None allowed. return (OptionDescriptor(false, false)); @@ -746,6 +808,20 @@ public: /// @return Number of deleted options. size_t del(const std::string& option_space, const uint16_t option_code); + /// @brief Deletes option for the specified option space and option code. + /// + /// If the option is encapsulated within some non top level option space, + /// it is also deleted from all option instances encapsulating this + /// option space. + /// + /// @param option_space Option space name. + /// @param option_code Code of the option to be returned. + /// @param client_classes client classes tag of the option to be deleted. + /// + /// @return Number of deleted options. + size_t del(const std::string& option_space, const uint16_t option_code, + const ClientClasses& client_classes); + /// @brief Deletes vendor option for the specified vendor id. /// /// @param vendor_id Vendor identifier. diff --git a/src/lib/dhcpsrv/config_backend_dhcp4.h b/src/lib/dhcpsrv/config_backend_dhcp4.h index a235a4b7b0..d98997ec08 100644 --- a/src/lib/dhcpsrv/config_backend_dhcp4.h +++ b/src/lib/dhcpsrv/config_backend_dhcp4.h @@ -570,11 +570,14 @@ public: /// @param server_selector Server selector. /// @param code Code of the option to be deleted. /// @param space Option space of the option to be deleted. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space) = 0; + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()) = 0; /// @brief Deletes shared network level option. /// @@ -585,12 +588,15 @@ public: /// @param shared_network_name Name of the shared network which option /// belongs to. /// @param code Code of the option to be deleted. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @param space Option space of the option to be deleted. virtual uint64_t deleteOption4(const db::ServerSelector& selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space) = 0; + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()) = 0; /// @brief Deletes subnet level option. /// @@ -602,12 +608,15 @@ public: /// belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id, const uint16_t code, - const std::string& space) = 0; + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()) = 0; /// @brief Deletes pool level option. /// @@ -621,13 +630,16 @@ public: /// deleted option belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space) = 0; + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()) = 0; /// @brief Deletes global parameter. /// diff --git a/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc b/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc index 1eaa2730ac..1c3ea3adc6 100644 --- a/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc +++ b/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc @@ -431,10 +431,12 @@ uint64_t ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector, const ServerSelector& server_selector, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes /* = ClientClassesPtr */) { + return (createUpdateDeleteProperty (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector, - code, space)); + code, space, client_classes)); } uint64_t @@ -442,11 +444,12 @@ ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector, const ServerSelector& server_selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes /* = ClientClassesPtr */) { return (createUpdateDeleteProperty (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector, - shared_network_name, code, space)); + shared_network_name, code, space, client_classes)); } uint64_t @@ -454,10 +457,11 @@ ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector, const ServerSelector& server_selector, const SubnetID& subnet_id, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes /* = ClientClassesPtr */) { return (createUpdateDeleteProperty (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector, - subnet_id, code, space)); + subnet_id, code, space, client_classes)); } uint64_t @@ -466,11 +470,12 @@ ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector, const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes /* = ClientClassesPtr */) { return (createUpdateDeleteProperty (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector, - pool_start_address, pool_end_address, code, space)); + pool_start_address, pool_end_address, code, space, client_classes)); } uint64_t diff --git a/src/lib/dhcpsrv/config_backend_pool_dhcp4.h b/src/lib/dhcpsrv/config_backend_pool_dhcp4.h index 1810d5f05f..eb935a08ef 100644 --- a/src/lib/dhcpsrv/config_backend_pool_dhcp4.h +++ b/src/lib/dhcpsrv/config_backend_pool_dhcp4.h @@ -498,12 +498,15 @@ public: /// @param server_selector Server selector. /// @param code Code of the option to be deleted. /// @param space Option space of the option to be deleted. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t deleteOption4(const db::BackendSelector& backend_selector, const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes shared network level option. /// @@ -513,12 +516,15 @@ public: /// belongs to. /// @param code Code of the option to be deleted. /// @param space Option space of the option to be deleted. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. virtual uint64_t deleteOption4(const db::BackendSelector& backend_selector, const db::ServerSelector& server_selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes subnet level option. /// @@ -528,12 +534,15 @@ public: /// belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t deleteOption4(const db::BackendSelector& backend_selector, const db::ServerSelector& server_selector, const SubnetID& subnet_id, - const uint16_t code, const std::string& space); + const uint16_t code, const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes pool level option. /// @@ -545,6 +554,8 @@ public: /// deleted option belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t deleteOption4(const db::BackendSelector& backend_selector, @@ -552,7 +563,8 @@ public: const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes global parameter. /// diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc index 8bdfcc623f..d52622e47b 100644 --- a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc @@ -1454,4 +1454,201 @@ TEST_F(CfgOptionTest, allowedForClientClasses) { } } +TEST_F(CfgOptionTest, optionsWithClientClasses) { + // Describes an option to create. + struct OptData { + uint16_t code_; + uint16_t value_; + std::string cclass_; + }; + + // List of options to create. + std::list opts_to_make = { + { 777, 1, "cc-one" }, + { 777, 3, "" }, + { 777, 2, "cc-two" } + }; + + // Populate a CfgOption with test options. + CfgOption cfg; + OptionDescriptorList reference_options; + for (const auto& opt_to_make : opts_to_make) { + OptionUint16Ptr opt(new OptionUint16(Option::V6, opt_to_make.code_, + opt_to_make.value_)); + OptionDescriptor desc(opt, false, false); + desc.space_name_ = DHCP6_OPTION_SPACE; + if (!opt_to_make.cclass_.empty()) { + desc.addClientClass(opt_to_make.cclass_); + } + + ASSERT_NO_THROW(cfg.add(desc, desc.space_name_)); + reference_options.push_back(desc); + } + + // Verify we have the expected option counts. + auto options = cfg.getAll(DHCP6_OPTION_SPACE); + ASSERT_EQ(options->size(), reference_options.size()); + + // Access them by sequence index (i.e order of insertion). + auto reference_desc = reference_options.begin(); + auto const& idx = options->get<0>(); + for (auto& returned_desc : idx) { + ASSERT_EQ(*reference_desc, returned_desc); + ++reference_desc; + } + + // Verify that CfgOption::get() (code only) returns the + // last one inserted. + OptionDescriptor found_desc = cfg.get(DHCP6_OPTION_SPACE, 777); + ASSERT_EQ(options->size(), reference_options.size()); + ASSERT_TRUE(found_desc.option_); + ASSERT_EQ(found_desc, *(reference_options.rbegin())); + + // Verify that CfgOption::getList() returns all three. + // Note this returns them in reverse sequence order. + OptionDescriptorList list_options = cfg.getList(DHCP6_OPTION_SPACE, 777); + ASSERT_EQ(options->size(), reference_options.size()); + auto reference_rdesc = reference_options.rbegin(); + for (auto &returned_desc : list_options ){ + ASSERT_EQ(*reference_rdesc, returned_desc); + ++reference_rdesc; + } + + // Verify that CfgOption::get() with client classes returns + // each one correctly. + for (auto &reference_desc : reference_options ){ + OptionDescriptor found_desc = cfg.get(DHCP6_OPTION_SPACE, 777, + reference_desc.client_classes_); + ASSERT_TRUE(found_desc.option_); + ASSERT_EQ(found_desc, reference_desc); + } +} + +TEST_F(CfgOptionTest, replaceWithClientClasses) { + // Describes an option to create. + struct OptData { + uint16_t code_; + uint16_t value_; + std::string cclass_; + }; + + // List of options to create. + std::list opts_to_make = { + { 777, 1, "cc-one" }, + { 777, 3, "" }, + { 777, 2, "cc-two" } + }; + + // Populate a CfgOption with test options. + CfgOption cfg; + OptionDescriptorList reference_options; + for (const auto& opt_to_make : opts_to_make) { + OptionUint16Ptr opt(new OptionUint16(Option::V6, opt_to_make.code_, + opt_to_make.value_)); + + OptionDescriptor desc(opt, false, false); + desc.space_name_ = DHCP6_OPTION_SPACE; + if (!opt_to_make.cclass_.empty()) { + desc.addClientClass(opt_to_make.cclass_); + } + + ASSERT_NO_THROW(cfg.add(desc, desc.space_name_)); + reference_options.push_back(desc); + } + + // Replace the first reference option. + auto& replacement = reference_options[0]; + (boost::dynamic_pointer_cast(replacement.option_))->setValue(100); + ASSERT_NO_THROW(cfg.replace(replacement, DHCP6_OPTION_SPACE)); + + // Make sure we can the updated option. + OptionDescriptor found_desc = cfg.get(DHCP6_OPTION_SPACE, 777, + replacement.client_classes_); + ASSERT_TRUE(found_desc.option_); + ASSERT_EQ(found_desc, replacement); + + // Replace the second reference option. + auto& replacement2 = reference_options[1]; + (boost::dynamic_pointer_cast(replacement2.option_))->setValue(300); + ASSERT_NO_THROW(cfg.replace(replacement2, DHCP6_OPTION_SPACE)); + + // Make sure we can the updated option. + found_desc = cfg.get(DHCP6_OPTION_SPACE, 777, replacement2.client_classes_); + ASSERT_TRUE(found_desc.option_); + ASSERT_EQ(found_desc, replacement2); + + // Verify we still have all the expected options in sequence order. + auto options = cfg.getAll(DHCP6_OPTION_SPACE); + ASSERT_EQ(options->size(), reference_options.size()); + + // Access them by sequence index (i.e order of insertion). + auto reference_desc = reference_options.begin(); + auto const& idx = options->get<0>(); + for (auto& returned_desc : idx) { + ASSERT_EQ(*reference_desc, returned_desc); + ++reference_desc; + } +} + +TEST_F(CfgOptionTest, deleteWithClientClasses) { + // Describes an option to create. + struct OptData { + uint16_t code_; + uint16_t value_; + std::string cclass_; + }; + + // List of options to create. + std::list opts_to_make = { + { 777, 1, "cc-one" }, + { 777, 3, "" }, + { 777, 2, "cc-two" } + }; + + // Populate a CfgOption with test options. + CfgOption cfg; + OptionDescriptorList reference_options; + for (const auto& opt_to_make : opts_to_make) { + OptionUint16Ptr opt(new OptionUint16(Option::V6, opt_to_make.code_, + opt_to_make.value_)); + + OptionDescriptor desc(opt, false, false); + desc.space_name_ = DHCP6_OPTION_SPACE; + if (!opt_to_make.cclass_.empty()) { + desc.addClientClass(opt_to_make.cclass_); + } + + ASSERT_NO_THROW(cfg.add(desc, desc.space_name_)); + reference_options.push_back(desc); + } + + // Delete the third reference option. + ASSERT_NO_THROW(cfg.del(DHCP6_OPTION_SPACE, 777, reference_options[2].client_classes_)); + + // Make sure we can no longer find the deleted option. + OptionDescriptor found_desc = cfg.get(DHCP6_OPTION_SPACE, 777, + reference_options[2].client_classes_); + ASSERT_FALSE(found_desc.option_); + reference_options.pop_back(); + + // Delete the secon reference option. + ASSERT_NO_THROW(cfg.del(DHCP6_OPTION_SPACE, 777, reference_options[1].client_classes_)); + + // Make sure we can no longer find the deleted option. + found_desc = cfg.get(DHCP6_OPTION_SPACE, 777, reference_options[1].client_classes_); + ASSERT_FALSE(found_desc.option_); + reference_options.pop_back(); + + // Verify we still have one option. + auto options = cfg.getAll(DHCP6_OPTION_SPACE); + ASSERT_EQ(options->size(), reference_options.size()); + auto reference_desc = reference_options.begin(); + auto const& idx = options->get<0>(); + for (auto& returned_desc : idx) { + ASSERT_EQ(*reference_desc, returned_desc); + ++reference_desc; + } +} + + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc index ead2c5ea26..f66105b844 100644 --- a/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2025 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 diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc index 2ea1411f70..0acedfdc09 100644 --- a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc @@ -4717,3 +4717,155 @@ GenericConfigBackendDHCPv4Test::multipleAuditEntriesTest() { distance--; } } + +void +GenericConfigBackendDHCPv4Test::subnetOption4WithClienClassesTest() { + + Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, + 30, 40, 60, 1024)); + + // Add several options to the subnet. + std::vector options; + OptionDescriptor desc = createOption(Option::V4, DHO_BOOT_FILE_NAME, + true, false, false, "boot-file-one"); + desc.space_name_ = DHCP4_OPTION_SPACE; + desc.addClientClass("class3"); + options.push_back(desc); + + desc = createOption(Option::V4, DHO_BOOT_FILE_NAME, + true, false, false, "boot-file-two"); + desc.space_name_ = DHCP4_OPTION_SPACE; + desc.addClientClass("class1"); + desc.addClientClass("class2"); + options.push_back(desc); + + desc = createOption(Option::V4, DHO_BOOT_FILE_NAME, + true, false, false, "boot-file-three"); + desc.space_name_ = DHCP4_OPTION_SPACE; + options.push_back(desc); + + subnet->getCfgOption()->add(options[2], options[0].space_name_); + subnet->getCfgOption()->add(options[0], options[1].space_name_); + subnet->getCfgOption()->add(options[1], options[2].space_name_); + + auto found_opts = subnet->getCfgOption()->getList(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME); + ASSERT_EQ(3, found_opts.size()); + + // Add the subnet to config back end. + cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet); + + // Fetch this subnet by subnet identifier. + Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + { + SCOPED_TRACE("CREATE audit entry for a new subnet"); + testNewAuditEntry("dhcp4_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + // The inserted subnet contains three options. + ASSERT_EQ(3, countRows("dhcp4_options")); +#if 0 + auto first_opt = subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME); + EXPECT_EQ(options[0].option_->toText(), first_opt.option_->toText()); +#endif + + found_opts = returned_subnet->getCfgOption()->getList(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME); + ASSERT_EQ(3, found_opts.size()); + for (auto fo : found_opts) { + std::cout << "FO cclient_classes: " << fo.client_classes_.toText() + << ", opt: " << fo.option_->toText() << std::endl; + } +#if 0 + EXPECT_EQ(options[0].option_->toText(), found_opts[0].option_->toText()); + EXPECT_EQ(options[1].option_->toText(), found_opts[1].option_->toText()); + EXPECT_EQ(options[2].option_->toText(), found_opts[2].option_->toText()); +#endif + +#if 0 + OptionDescriptorPtr opt_boot_file_name = test_options_[0]; + cbptr_->createUpdateOption4(ServerSelector::ANY(), subnet->getID(), + opt_boot_file_name); + + returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + OptionDescriptor returned_opt_boot_file_name = + returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME); + ASSERT_TRUE(returned_opt_boot_file_name.option_); + + { + SCOPED_TRACE("verify returned option"); + testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name); + EXPECT_GT(returned_opt_boot_file_name.getId(), 0); + } + + { + SCOPED_TRACE("UPDATE audit entry for an added subnet option"); + // Instead of adding an audit entry for an option we add an audit + // entry for the entire subnet so as the server refreshes the + // subnet with the new option. Note that the server doesn't + // have means to retrieve only the newly added option. + testNewAuditEntry("dhcp4_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option set"); + } + + // We have added one option to the existing subnet. We should now have + // three options. + ASSERT_EQ(3, countRows("dhcp4_options")); + + opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_; + opt_boot_file_name->cancelled_ = !opt_boot_file_name->cancelled_; + cbptr_->createUpdateOption4(ServerSelector::ANY(), subnet->getID(), + opt_boot_file_name); + + returned_subnet = cbptr_->getSubnet4(ServerSelector::ANY(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + returned_opt_boot_file_name = + returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME); + ASSERT_TRUE(returned_opt_boot_file_name.option_); + + { + SCOPED_TRACE("verify returned option with modified persistence"); + testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name); + } + + { + SCOPED_TRACE("UPDATE audit entry for an updated subnet option"); + testNewAuditEntry("dhcp4_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option set"); + } + + // Updating the option should replace the existing instance with the new + // instance. Therefore, we should still have three options. + ASSERT_EQ(3, countRows("dhcp4_options")); + + // It should succeed for any server. + EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(), subnet->getID(), + opt_boot_file_name->option_->getType(), + opt_boot_file_name->space_name_)); + + returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_); + + { + SCOPED_TRACE("UPDATE audit entry for a deleted subnet option"); + testNewAuditEntry("dhcp4_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option deleted"); + } + + // We should have only two options after deleting one of them. + ASSERT_EQ(2, countRows("dhcp4_options")); +#endif +} diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h index 4375cb426d..8956cae8d7 100644 --- a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h @@ -369,6 +369,8 @@ public: /// event and it does not matter). void multipleAuditEntriesTest(); + void subnetOption4WithClienClassesTest(); + /// @brief Holds pointers to subnets used in tests. std::vector test_subnets_; diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc index e0a7f0a0bf..4658df7495 100644 --- a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc +++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc @@ -1175,13 +1175,15 @@ TestConfigBackendDHCPv4::deleteAllOptionDefs4(const db::ServerSelector& server_s uint64_t TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { auto tag = getServerTag(server_selector); uint64_t erased = 0; for (auto option_it = options_.begin(); option_it != options_.end(); ) { if ((option_it->option_->getType() == code) && (option_it->space_name_ == space) && - (option_it->hasServerTag(ServerTag(tag)))) { + (option_it->hasServerTag(ServerTag(tag))) && + (!client_classes || (option_it->client_classes_ == *client_classes))) { option_it = options_.erase(option_it); ++erased; } else { @@ -1195,7 +1197,8 @@ uint64_t TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { auto& index = shared_networks_.get(); auto network_it = index.find(shared_network_name); @@ -1223,12 +1226,18 @@ TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector } } } + + if (!found) { isc_throw(BadValue, "attempted to delete option in a " "shared network " << shared_network_name << " not present in a selected server"); } + if (client_classes) { + return (shared_network->getCfgOption()->del(space, code, *client_classes)); + } + return (shared_network->getCfgOption()->del(space, code)); } @@ -1236,7 +1245,8 @@ uint64_t TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { auto& index = subnets_.get(); auto subnet_it = index.find(subnet_id); @@ -1270,6 +1280,10 @@ TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector << " not present in a selected server"); } + if (client_classes) { + return (subnet->getCfgOption()->del(space, code, *client_classes)); + } + return (subnet->getCfgOption()->del(space, code)); } @@ -1278,7 +1292,8 @@ TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space) { + const std::string& space, + ClientClassesPtr client_classes) { auto not_in_selected_servers = false; for (auto const& subnet : subnets_) { // Get the pool: if it is not here we can directly go to the next subnet. @@ -1310,6 +1325,10 @@ TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector } } + if (client_classes) { + return (pool->getCfgOption()->del(space, code, *client_classes)); + } + return (pool->getCfgOption()->del(space, code)); } diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h index 7791a0f241..9c25512c36 100644 --- a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h +++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h @@ -435,10 +435,14 @@ public: /// @param server_selector Server selector. /// @param code Code of the option to be deleted. /// @param space Option space of the option to be deleted. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t - deleteOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space); + deleteOption4(const db::ServerSelector& server_selector, + const uint16_t code, + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes shared network level option. /// @@ -447,11 +451,14 @@ public: /// belongs to. /// @param code Code of the option to be deleted. /// @param space Option space of the option to be deleted. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const std::string& shared_network_name, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes subnet level option. /// @@ -460,10 +467,13 @@ public: /// belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id, - const uint16_t code, const std::string& space); + const uint16_t code, const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes pool level option. /// @@ -474,13 +484,16 @@ public: /// deleted option belongs. /// @param code Code of the deleted option. /// @param space Option space of the deleted option. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Number of deleted options. virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const asiolink::IOAddress& pool_start_address, const asiolink::IOAddress& pool_end_address, const uint16_t code, - const std::string& space); + const std::string& space, + ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes global parameter. /// -- 2.47.2