From 16ffb6d6ed7bfeaf3f4abf55f5908b628ea836af Mon Sep 17 00:00:00 2001 From: Thomas Markwalder Date: Mon, 7 Jul 2025 11:45:25 -0400 Subject: [PATCH] [#3770] Finished MySql v4, Expanded v4 UTs /src/hooks/dhcp/mysql/mysql_cb_dhcp4.* /src/hooks/dhcp/mysql/mysql_cb_impl.cc Added client classes to where clauses as needed /src/hooks/dhcp/mysql/tests/mysql_cb_dhcp4_unittest.cc TEST_F(MySqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest) TEST_F(MySqlConfigBackendDHCPv4Test, sharedNetworkOption4WithClientClassesTest) TEST_F(MySqlConfigBackendDHCPv4Test, subnetOption4WithClientClassesTest) TEST_F(MySqlConfigBackendDHCPv4Test, poolOption4WithClientClassesTest) - new tests /src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc TEST_F(PgSqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest) TEST_F(PgSqlConfigBackendDHCPv4Test, sharedNetworkOption4WithClientClassesTest) TEST_F(PgSqlConfigBackendDHCPv4Test, subnetOption4WithClientClassesTest) TEST_F(PgSqlConfigBackendDHCPv4Test, poolOption4WithClientClassesTest) - new tests /src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc New tests and functions --- src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc | 39 +- src/hooks/dhcp/mysql/mysql_cb_dhcp4.h | 15 +- src/hooks/dhcp/mysql/mysql_cb_impl.cc | 11 +- src/hooks/dhcp/mysql/mysql_cb_impl.h | 5 +- .../mysql/tests/mysql_cb_dhcp4_unittest.cc | 16 + src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc | 29 +- src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h | 15 +- src/hooks/dhcp/pgsql/pgsql_cb_impl.cc | 9 +- src/hooks/dhcp/pgsql/pgsql_cb_impl.h | 5 +- .../pgsql/tests/pgsql_cb_dhcp4_unittest.cc | 16 +- src/lib/dhcpsrv/cfg_option.h | 2 +- src/lib/dhcpsrv/config_backend_dhcp4.h | 5 +- src/lib/dhcpsrv/config_backend_pool_dhcp4.cc | 14 +- src/lib/dhcpsrv/config_backend_pool_dhcp4.h | 13 +- .../testutils/generic_cb_dhcp4_unittest.cc | 409 +++++++++++++----- .../testutils/generic_cb_dhcp4_unittest.h | 27 +- .../testutils/test_config_backend_dhcp4.cc | 8 +- .../testutils/test_config_backend_dhcp4.h | 7 +- 18 files changed, 466 insertions(+), 179 deletions(-) diff --git a/src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc b/src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc index 5491078256..1c7ca0a3dd 100644 --- a/src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc +++ b/src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc @@ -2241,7 +2241,7 @@ public: uint64_t deleteOption4(const ServerSelector& server_selector, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const ClientClassesPtr client_classes) { MySqlBindingCollection in_bindings = { MySqlBinding::createInteger(code), MySqlBinding::createString(space), @@ -2269,7 +2269,7 @@ public: const SubnetID& subnet_id, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const ClientClassesPtr client_classes) { MySqlBindingCollection in_bindings = { MySqlBinding::createInteger(static_cast(subnet_id)), MySqlBinding::createInteger(code), @@ -2299,13 +2299,13 @@ public: const IOAddress& pool_end_address, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const ClientClassesPtr client_classes) { MySqlBindingCollection in_bindings = { MySqlBinding::createInteger(code), MySqlBinding::createString(space), + createClientClassesForWhereClause(client_classes), MySqlBinding::createInteger(pool_start_address.toUint32()), - MySqlBinding::createInteger(pool_end_address.toUint32()), - createClientClassesForWhereClause(client_classes) + MySqlBinding::createInteger(pool_end_address.toUint32()) }; // Run DELETE. @@ -2329,7 +2329,7 @@ public: const std::string& shared_network_name, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const ClientClassesPtr client_classes) { MySqlBindingCollection in_bindings = { MySqlBinding::createString(shared_network_name), MySqlBinding::createInteger(code), @@ -3162,9 +3162,10 @@ TaggedStatementArray tagged_statements = { { MYSQL_GET_OPTION_DEF(dhcp4, AND d.modification_ts >= ?) }, - // Retrieves global option by code and space. + // Retrieves global option by code, space and client-classes. { MySqlConfigBackendDHCPv4Impl::GET_OPTION4_CODE_SPACE, - MYSQL_GET_OPTION4(AND o.scope_id = 0 AND o.code = ? AND o.space = ?) + MYSQL_GET_OPTION4(AND o.scope_id = 0 AND o.code = ? AND o.space = ? + AND o.client_classes LIKE ?) }, // Retrieves all global options. @@ -3677,18 +3678,21 @@ TaggedStatementArray tagged_statements = { { // Delete single option from a subnet. { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SUBNET_ID, MYSQL_DELETE_OPTION_NO_TAG(dhcp4, - WHERE o.scope_id = 1 AND o.dhcp4_subnet_id = ? AND o.code = ? AND o.space = ?) + WHERE (o.scope_id = 1 AND o.dhcp4_subnet_id = ? AND o.code = ? AND o.space = ? + AND o.client_classes LIKE ?)) }, // Delete single option from a pool. { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4_POOL_RANGE, - MYSQL_DELETE_OPTION_POOL_RANGE(dhcp4, o.scope_id = 5 AND o.code = ? AND o.space = ?) + MYSQL_DELETE_OPTION_POOL_RANGE(dhcp4, o.scope_id = 5 AND o.code = ? AND o.space = ? + AND o.client_classes LIKE ?) }, // Delete single option from a shared network. { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SHARED_NETWORK, MYSQL_DELETE_OPTION_NO_TAG(dhcp4, - WHERE o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?) + WHERE (o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ? + AND o.client_classes LIKE ?)) }, // Delete options belonging to a subnet. @@ -3897,11 +3901,12 @@ MySqlConfigBackendDHCPv4::getModifiedOptionDefs4(const ServerSelector& server_se OptionDescriptorPtr MySqlConfigBackendDHCPv4::getOption4(const ServerSelector& server_selector, const uint16_t code, - const std::string& space) const { + const std::string& space, + const ClientClassesPtr client_classes) const { LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_OPTION4) .arg(code).arg(space); return (impl_->getOption(MySqlConfigBackendDHCPv4Impl::GET_OPTION4_CODE_SPACE, - Option::V4, server_selector, code, space)); + Option::V4, server_selector, code, space, client_classes)); } OptionContainer @@ -4249,7 +4254,7 @@ uint64_t MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& server_selector, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const 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, client_classes); @@ -4263,7 +4268,7 @@ MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector const std::string& shared_network_name, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const 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. @@ -4281,7 +4286,7 @@ MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector const SubnetID& subnet_id, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const 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. @@ -4300,7 +4305,7 @@ MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector const asiolink::IOAddress& pool_end_address, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const 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. diff --git a/src/hooks/dhcp/mysql/mysql_cb_dhcp4.h b/src/hooks/dhcp/mysql/mysql_cb_dhcp4.h index 979322e30c..31e74f89b8 100644 --- a/src/hooks/dhcp/mysql/mysql_cb_dhcp4.h +++ b/src/hooks/dhcp/mysql/mysql_cb_dhcp4.h @@ -146,12 +146,17 @@ public: /// @brief Retrieves single option by code and space. /// /// @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 Pointer to the retrieved option descriptor or null if /// no option was found. /// @throw NotImplemented if server selector is "unassigned". virtual OptionDescriptorPtr getOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space) const; + const std::string& space, + const ClientClassesPtr client_classes = ClientClassesPtr()) const; /// @brief Retrieves all global options. /// @@ -471,7 +476,7 @@ public: virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes shared network level option. /// @@ -488,7 +493,7 @@ public: const std::string& shared_network_name, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes subnet level option. /// @@ -504,7 +509,7 @@ public: virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes pool level option. /// @@ -525,7 +530,7 @@ public: const asiolink::IOAddress& pool_end_address, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const 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 751cb00cb9..47b1bc75ab 100644 --- a/src/hooks/dhcp/mysql/mysql_cb_impl.cc +++ b/src/hooks/dhcp/mysql/mysql_cb_impl.cc @@ -561,7 +561,8 @@ MySqlConfigBackendImpl::getOption(const int index, const Option::Universe& universe, const ServerSelector& server_selector, const uint16_t code, - const std::string& space) { + const std::string& space, + const ClientClassesPtr client_classes) { if (server_selector.amUnassigned()) { isc_throw(NotImplemented, "managing configuration for no particular server" @@ -579,11 +580,17 @@ MySqlConfigBackendImpl::getOption(const int index, in_bindings.push_back(MySqlBinding::createInteger(code)); } in_bindings.push_back(MySqlBinding::createString(space)); + + /// @todo Remove the if when v6 is ready for this. + if (universe == Option::V4) { + in_bindings.push_back(createClientClassesForWhereClause(client_classes)); + } getOptions(index, in_bindings, universe, options); return (options.empty() ? OptionDescriptorPtr() : OptionDescriptor::create(*options.begin())); } + OptionContainer MySqlConfigBackendImpl::getAllOptions(const int index, const Option::Universe& universe, @@ -1115,7 +1122,7 @@ MySqlConfigBackendImpl::getPort() const { } db::MySqlBindingPtr -MySqlConfigBackendImpl::createClientClassesForWhereClause(ClientClassesPtr client_classes) { +MySqlConfigBackendImpl::createClientClassesForWhereClause(const ClientClassesPtr client_classes) { return (client_classes ? createInputClientClassesBinding(*client_classes) : MySqlBinding::createString("%")); } diff --git a/src/hooks/dhcp/mysql/mysql_cb_impl.h b/src/hooks/dhcp/mysql/mysql_cb_impl.h index 334a75cf5a..45135942fb 100644 --- a/src/hooks/dhcp/mysql/mysql_cb_impl.h +++ b/src/hooks/dhcp/mysql/mysql_cb_impl.h @@ -446,6 +446,8 @@ public: /// @param server_selector Server selector. /// @param code Option code. /// @param space Option space name. + /// @param client_classes ClientClasses collection containing the class names. + /// Defaults to an empty pointer. /// /// @return Pointer to the returned option or NULL if such option /// doesn't exist. @@ -453,7 +455,8 @@ public: const Option::Universe& universe, const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space); + const std::string& space, + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Sends query to retrieve all global options. /// diff --git a/src/hooks/dhcp/mysql/tests/mysql_cb_dhcp4_unittest.cc b/src/hooks/dhcp/mysql/tests/mysql_cb_dhcp4_unittest.cc index bb418b5296..e4f366d597 100644 --- a/src/hooks/dhcp/mysql/tests/mysql_cb_dhcp4_unittest.cc +++ b/src/hooks/dhcp/mysql/tests/mysql_cb_dhcp4_unittest.cc @@ -403,6 +403,22 @@ TEST_F(MySqlConfigBackendDHCPv4Test, multipleAuditEntriesTest) { multipleAuditEntriesTest(); } +TEST_F(MySqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest) { + globalOption4WithClientClassesTest(); +} + +TEST_F(MySqlConfigBackendDHCPv4Test, sharedNetworkOption4WithClientClassesTest) { + sharedNetworkOption4WithClientClassesTest(); +} + +TEST_F(MySqlConfigBackendDHCPv4Test, subnetOption4WithClientClassesTest) { + subnetOption4WithClientClassesTest(); +} + +TEST_F(MySqlConfigBackendDHCPv4Test, poolOption4WithClientClassesTest) { + poolOption4WithClientClassesTest(); +} + /// @brief Test fixture for verifying database connection loss-recovery /// behavior. class MySqlConfigBackendDHCPv4DbLostCallbackTest : public GenericConfigBackendDbLostCallbackTest { diff --git a/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc b/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc index 3272191e1d..9a46525327 100644 --- a/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc +++ b/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc @@ -2103,7 +2103,7 @@ public: uint64_t deleteOption4(const ServerSelector& server_selector, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const ClientClassesPtr client_classes) { PsqlBindArray in_bindings; in_bindings.add(code); in_bindings.add(space); @@ -2131,7 +2131,7 @@ public: const SubnetID& subnet_id, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const ClientClassesPtr client_classes) { PsqlBindArray in_bindings; in_bindings.add(subnet_id); in_bindings.add(code); @@ -2161,7 +2161,7 @@ public: const IOAddress& pool_end_address, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const ClientClassesPtr client_classes) { PsqlBindArray in_bindings; in_bindings.addInet4(pool_start_address); in_bindings.addInet4(pool_end_address); @@ -2191,7 +2191,7 @@ public: const std::string& shared_network_name, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const ClientClassesPtr client_classes) { PsqlBindArray in_bindings; in_bindings.add(shared_network_name); in_bindings.add(code); @@ -3186,14 +3186,16 @@ TaggedStatementArray tagged_statements = { { // Retrieves global option by code and space. { // PgSqlConfigBackendDHCPv4Impl::GET_OPTION4_CODE_SPACE, - 3, + 4, { OID_VARCHAR, // 1 server_tag OID_INT2, // 2 code - OID_VARCHAR // 3 space + OID_VARCHAR, // 3 space + OID_TEXT // 4 client_classes }, "GET_OPTION4_CODE_SPACE", - PGSQL_GET_OPTION4(AND o.scope_id = 0 AND o.code = $2 AND o.space = $3) + PGSQL_GET_OPTION4(AND o.scope_id = 0 AND o.code = $2 AND o.space = $3 + AND o.client_classes LIKE $4) }, // Retrieves all global options. @@ -4841,11 +4843,12 @@ PgSqlConfigBackendDHCPv4::getModifiedOptionDefs4(const ServerSelector& server_se OptionDescriptorPtr PgSqlConfigBackendDHCPv4::getOption4(const ServerSelector& server_selector, const uint16_t code, - const std::string& space) const { + const std::string& space, + const ClientClassesPtr client_classes) const { LOG_DEBUG(pgsql_cb_logger, DBGLVL_TRACE_BASIC, PGSQL_CB_GET_OPTION4) .arg(code).arg(space); return (impl_->getOption(PgSqlConfigBackendDHCPv4Impl::GET_OPTION4_CODE_SPACE, - Option::V4, server_selector, code, space)); + Option::V4, server_selector, code, space, client_classes)); } OptionContainer @@ -5194,7 +5197,7 @@ uint64_t PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& server_selector, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const 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, client_classes); @@ -5208,7 +5211,7 @@ PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector const std::string& shared_network_name, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const 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. @@ -5226,7 +5229,7 @@ PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector const SubnetID& subnet_id, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const 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. @@ -5245,7 +5248,7 @@ PgSqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector const asiolink::IOAddress& pool_end_address, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes) { + const 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. diff --git a/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h b/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h index 8fac877ad3..6c7d91b643 100644 --- a/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h +++ b/src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h @@ -146,12 +146,17 @@ public: /// @brief Retrieves single option by code and space. /// /// @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 Pointer to the retrieved option descriptor or null if /// no option was found. /// @throw NotImplemented if server selector is "unassigned". virtual OptionDescriptorPtr getOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space) const; + const std::string& space, + const ClientClassesPtr client_classes = ClientClassesPtr()) const; /// @brief Retrieves all global options. /// @@ -471,7 +476,7 @@ public: virtual uint64_t deleteOption4(const db::ServerSelector& server_selector, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes shared network level option. /// @@ -488,7 +493,7 @@ public: const std::string& shared_network_name, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes subnet level option. /// @@ -506,7 +511,7 @@ public: const SubnetID& subnet_id, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes pool level option. /// @@ -527,7 +532,7 @@ public: const asiolink::IOAddress& pool_end_address, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const 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 f909afbb3a..87cd19f688 100644 --- a/src/hooks/dhcp/pgsql/pgsql_cb_impl.cc +++ b/src/hooks/dhcp/pgsql/pgsql_cb_impl.cc @@ -547,8 +547,9 @@ PgSqlConfigBackendImpl::getOption(const int index, const Option::Universe& universe, const ServerSelector& server_selector, const uint16_t code, - const std::string& space) { - + const std::string& space, + const ClientClassesPtr client_classes + /* = ClientClassesPtr() */) { if (server_selector.amUnassigned()) { isc_throw(NotImplemented, "managing configuration for no particular server" " (unassigned) is unsupported at the moment"); @@ -561,6 +562,10 @@ PgSqlConfigBackendImpl::getOption(const int index, in_bindings.add(tag); in_bindings.add(code); in_bindings.add(space); + /// @todo TKM remove if when v6 is ready. + if (universe == Option::V4) { + addClientClassesForWhereClause(in_bindings, client_classes); + } getOptions(index, in_bindings, universe, options); return (options.empty() ? OptionDescriptorPtr() : diff --git a/src/hooks/dhcp/pgsql/pgsql_cb_impl.h b/src/hooks/dhcp/pgsql/pgsql_cb_impl.h index 3576c9e28c..eea15d5a2d 100644 --- a/src/hooks/dhcp/pgsql/pgsql_cb_impl.h +++ b/src/hooks/dhcp/pgsql/pgsql_cb_impl.h @@ -379,6 +379,8 @@ public: /// @param server_selector Server selector. /// @param code Option code. /// @param space Option space name. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// /// @return Pointer to the returned option or NULL if such option /// doesn't exist. @@ -386,7 +388,8 @@ public: const Option::Universe& universe, const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space); + const std::string& space, + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Sends query to retrieve all global options. /// 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 54b6a2ab69..8787aee0d5 100644 --- a/src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc +++ b/src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc @@ -401,8 +401,20 @@ TEST_F(PgSqlConfigBackendDHCPv4Test, multipleAuditEntriesTest) { multipleAuditEntriesTest(); } -TEST_F(PgSqlConfigBackendDHCPv4Test, subnetOption4WithClienClassesTest) { - subnetOption4WithClienClassesTest(); +TEST_F(PgSqlConfigBackendDHCPv4Test, globalOption4WithClientClassesTest) { + globalOption4WithClientClassesTest(); +} + +TEST_F(PgSqlConfigBackendDHCPv4Test, sharedNetworkOption4WithClientClassesTest) { + sharedNetworkOption4WithClientClassesTest(); +} + +TEST_F(PgSqlConfigBackendDHCPv4Test, subnetOption4WithClientClassesTest) { + subnetOption4WithClientClassesTest(); +} + +TEST_F(PgSqlConfigBackendDHCPv4Test, poolOption4WithClientClassesTest) { + poolOption4WithClientClassesTest(); } /// @brief Test fixture for verifying database connection loss-recovery diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index 53d7df9e2d..e5d0930f94 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -717,7 +717,7 @@ public: template OptionDescriptor get(const Selector& key, const uint16_t option_code, - ClientClasses& client_classes) const { + const ClientClasses& client_classes) const { // Check for presence of options. OptionContainerPtr options = getAll(key); diff --git a/src/lib/dhcpsrv/config_backend_dhcp4.h b/src/lib/dhcpsrv/config_backend_dhcp4.h index d98997ec08..6ca3fee441 100644 --- a/src/lib/dhcpsrv/config_backend_dhcp4.h +++ b/src/lib/dhcpsrv/config_backend_dhcp4.h @@ -229,11 +229,14 @@ public: /// @param server_selector Server selector. /// @param code Option code. /// @param space Option space. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Pointer to the retrieved option descriptor or null if /// no option was found. virtual OptionDescriptorPtr getOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space) const = 0; + const std::string& space, + const ClientClassesPtr client_classes = ClientClassesPtr()) const = 0; /// @brief Retrieves all global options. /// diff --git a/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc b/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc index 1c3ea3adc6..7dc8aab9e6 100644 --- a/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc +++ b/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc @@ -138,11 +138,13 @@ OptionDescriptorPtr ConfigBackendPoolDHCPv4::getOption4(const BackendSelector& backend_selector, const ServerSelector& server_selector, const uint16_t code, - const std::string& space) const { + const std::string& space, + const ClientClassesPtr client_classes + /* = ClientClassesPtr */) const { OptionDescriptorPtr option; getPropertyPtrConst (&ConfigBackendDHCPv4::getOption4, backend_selector, server_selector, - option, code, space); + option, code, space, client_classes); return (option); } @@ -432,7 +434,7 @@ ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector, const ServerSelector& server_selector, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes /* = ClientClassesPtr */) { + const ClientClassesPtr client_classes /* = ClientClassesPtr */) { return (createUpdateDeleteProperty (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector, @@ -445,7 +447,7 @@ ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector, const std::string& shared_network_name, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes /* = ClientClassesPtr */) { + const ClientClassesPtr client_classes /* = ClientClassesPtr */) { return (createUpdateDeleteProperty (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector, @@ -458,7 +460,7 @@ ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector, const SubnetID& subnet_id, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes /* = ClientClassesPtr */) { + const ClientClassesPtr client_classes /* = ClientClassesPtr */) { return (createUpdateDeleteProperty (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector, subnet_id, code, space, client_classes)); @@ -471,7 +473,7 @@ ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector, const asiolink::IOAddress& pool_end_address, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes /* = ClientClassesPtr */) { + const ClientClassesPtr client_classes /* = ClientClassesPtr */) { return (createUpdateDeleteProperty (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector, diff --git a/src/lib/dhcpsrv/config_backend_pool_dhcp4.h b/src/lib/dhcpsrv/config_backend_pool_dhcp4.h index eb935a08ef..72aa94a6fd 100644 --- a/src/lib/dhcpsrv/config_backend_pool_dhcp4.h +++ b/src/lib/dhcpsrv/config_backend_pool_dhcp4.h @@ -166,13 +166,16 @@ public: /// @param server_selector Server selector. /// @param code Option code. /// @param space Option space. + /// @param client_classes Optional client classes list of the option to be deleted. + /// Defaults to an empty pointer. /// @return Pointer to the retrieved option descriptor or null if /// no option was found. virtual OptionDescriptorPtr getOption4(const db::BackendSelector& backend_selector, const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space) const; + const std::string& space, + const ClientClassesPtr client_classes = ClientClassesPtr()) const; /// @brief Retrieves all global options. /// @@ -506,7 +509,7 @@ public: const db::ServerSelector& server_selector, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes shared network level option. /// @@ -524,7 +527,7 @@ public: const std::string& shared_network_name, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes subnet level option. /// @@ -542,7 +545,7 @@ public: const db::ServerSelector& server_selector, const SubnetID& subnet_id, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes pool level option. /// @@ -564,7 +567,7 @@ public: const asiolink::IOAddress& pool_end_address, const uint16_t code, const std::string& space, - ClientClassesPtr client_classes = ClientClassesPtr()); + const ClientClassesPtr client_classes = ClientClassesPtr()); /// @brief Deletes global parameter. /// diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc index 0acedfdc09..cbbe1f1e1b 100644 --- a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc @@ -4718,154 +4718,333 @@ GenericConfigBackendDHCPv4Test::multipleAuditEntriesTest() { } } +std::list +GenericConfigBackendDHCPv4Test::makeClassTaggedOptions() { + // Describes an option to create. + struct OptData { + uint16_t code_; + std::string value_; + std::string cclass_; + }; + + // List of options to create. + std::list opts_to_make = { + { DHO_TCODE, "T100", "cc-one" }, + { DHO_PCODE, "P100", "cc-one" }, + { DHO_PCODE, "P300", "" }, + { DHO_TCODE, "T200", "" }, + { DHO_PCODE, "P200", "cc-two" } + }; + + std::list tagged_options; + for ( auto const& opt_to_make : opts_to_make) { + OptionDescriptor desc = createOption(Option::V4, opt_to_make.code_, + true, false, false, opt_to_make.value_); + desc.space_name_ = DHCP4_OPTION_SPACE; + if (!opt_to_make.cclass_.empty()) { + desc.addClientClass(opt_to_make.cclass_); + } + + tagged_options.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + } + + return (tagged_options); +} + void -GenericConfigBackendDHCPv4Test::subnetOption4WithClienClassesTest() { +GenericConfigBackendDHCPv4Test::updateClassTaggedOptions( + std::list& options) { + for ( auto& desc : options) { + OptionStringPtr opt = boost::dynamic_pointer_cast(desc->option_); + ASSERT_TRUE(opt); + std::string new_value(opt->getValue() + std::string(".") + opt->getValue()); + opt->setValue(new_value); + } +} - Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, - 30, 40, 60, 1024)); +// Macro the make SCOPED_TRACE around equivalance functon more compact and helpful. +#define SCOPED_OPT_COMPARE(exp_opt,test_opt)\ +{\ + std::stringstream oss;\ + oss << "Options not equal:\n"\ + << " exp_opt: " << exp_opt.option_->toText() << "\n"\ + << " test_opt: " << (test_opt.option_ ? test_opt.option_->toText() : "") << "\n";\ + SCOPED_TRACE(oss.str());\ + testOptionsEquivalent(exp_opt,test_opt);\ +} - // 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); +// Verify that one can add multiple global instances of the same option code +// and that they can be distinguished via their client_classes. +void +GenericConfigBackendDHCPv4Test::globalOption4WithClientClassesTest() { + // Add the options to global scope. + auto ref_options = makeClassTaggedOptions(); + for (auto const& ref_option : ref_options) { + // Add option to the config back end. + cbptr_->createUpdateOption4(ServerSelector::ALL(), ref_option); + } + + // Make sure that we can find each option. + OptionDescriptorPtr found_option; + for (auto const& ref_option : ref_options) { + // Find the option by code and client_classes. + ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_)); + found_option = cbptr_->getOption4(ServerSelector::ALL(), + ref_option->option_->getType(), + DHCP4_OPTION_SPACE, + cclasses); + ASSERT_TRUE(found_option); + SCOPED_OPT_COMPARE((*ref_option), (*found_option)); + } + + // Update the option values. + updateClassTaggedOptions(ref_options); + + // Update each option in the backend. + for (auto const& ref_option : ref_options) { + ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_)); + + // Update option in the config back end. + cbptr_->createUpdateOption4(ServerSelector::ALL(), ref_option); + + // Fetch and verify the updated option. + found_option = cbptr_->getOption4(ServerSelector::ALL(), + ref_option->option_->getType(), + DHCP4_OPTION_SPACE, + cclasses); + ASSERT_TRUE(found_option); + SCOPED_OPT_COMPARE((*ref_option), (*found_option)); + } + + // Delete each option from the backend. + for (auto const& ref_option : ref_options) { + ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_)); + + // Delete the option by code and client_classes. + ASSERT_EQ(1, cbptr_->deleteOption4(ServerSelector::ALL(), + ref_option->option_->getType(), + DHCP4_OPTION_SPACE, + cclasses)); + + // Finding the option by code and client_classes should fail. + found_option = cbptr_->getOption4(ServerSelector::ALL(), + ref_option->option_->getType(), + DHCP4_OPTION_SPACE, + cclasses); + ASSERT_FALSE(found_option); + } +} - 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); +// Verify that one can add multiple instances of the same option code +// to a shared-network and that they can be distinguished via their client_classes. +void +GenericConfigBackendDHCPv4Test::sharedNetworkOption4WithClientClassesTest() { + // Make a network with options. + SharedNetwork4Ptr network(new SharedNetwork4("net1")); + auto ref_options = makeClassTaggedOptions(); + for ( auto const& ref_option : ref_options) { + network->getCfgOption()->add(*ref_option, ref_option->space_name_); + } - desc = createOption(Option::V4, DHO_BOOT_FILE_NAME, - true, false, false, "boot-file-three"); - desc.space_name_ = DHCP4_OPTION_SPACE; - options.push_back(desc); + // Add the network to config back end. + cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network); + + // Fetch the network. + SharedNetwork4Ptr returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(), + network->getName()); + ASSERT_TRUE(returned_network); + + // Make sure that CfgOption->get() with client_classes finds each ref option. + for (auto const& ref_option : ref_options) { + auto cfg_option = returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, + ref_option->option_->getType(), + ref_option->client_classes_); + SCOPED_OPT_COMPARE((*ref_option), cfg_option); + } + + // Now make sure that we can set the options individually. + updateClassTaggedOptions(ref_options); + for (auto const& ref_option : ref_options) { + cbptr_->createUpdateOption4(ServerSelector::ALL(), network->getName(), ref_option); + } + + // Re-fetch the network. + returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(), network->getName()); + ASSERT_TRUE(returned_network); + + // Make sure that CfgOption->get() with client_classes finds each ref option. + for (auto const& ref_option : ref_options) { + auto cfg_option = returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, + ref_option->option_->getType(), + ref_option->client_classes_); + SCOPED_OPT_COMPARE((*ref_option), cfg_option); + } + + // Now make sure that we can delete the options individually. + updateClassTaggedOptions(ref_options); + for (auto const& ref_option : ref_options) { + ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_)); + ASSERT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(), + network->getName(), + ref_option->option_->getType(), + DHCP4_OPTION_SPACE, + cclasses)) << "code:" << ref_option->option_->getType() + << " classes: " << cclasses->toText(); + } - 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_); + // Re-fetch the network. + returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(), network->getName()); + ASSERT_TRUE(returned_network); - auto found_opts = subnet->getCfgOption()->getList(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME); - ASSERT_EQ(3, found_opts.size()); + // Make sure that CfgOption is empty + auto cfg_option = returned_network->getCfgOption()->getAll(DHCP4_OPTION_SPACE); + EXPECT_TRUE(cfg_option->empty()); +} + +// Verify that one can add multiple instances of the same option code +// to a subnet and that they can be distinguished via their client_classes. +void +GenericConfigBackendDHCPv4Test::subnetOption4WithClientClassesTest() { + // Make a subnet with options. + Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, + 30, 40, 60, 1024)); + auto ref_options = makeClassTaggedOptions(); + for ( auto const& ref_option : ref_options) { + subnet->getCfgOption()->add(*ref_option, ref_option->space_name_); + } // 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()); + // Fetch the subnet. + 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"); + // Make sure that CfgOption->get() with client_classes finds each ref option. + for (auto const& ref_option : ref_options) { + auto cfg_option = returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, + ref_option->option_->getType(), + ref_option->client_classes_); + SCOPED_OPT_COMPARE((*ref_option), cfg_option); } - // 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); + // Now make sure that we can set the options individually. + updateClassTaggedOptions(ref_options); + for (auto const& ref_option : ref_options) { + cbptr_->createUpdateOption4(ServerSelector::ALL(), subnet->getID(), ref_option); + } - returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), - subnet->getID()); + // Re-fetch the subnet. + 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); + // Make sure that CfgOption->get() with client_classes finds each ref option. + for (auto const& ref_option : ref_options) { + auto cfg_option = returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, + ref_option->option_->getType(), + ref_option->client_classes_); + SCOPED_OPT_COMPARE((*ref_option), cfg_option); } - { - 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"); + // Now make sure that we can delete the options individually. + updateClassTaggedOptions(ref_options); + for (auto const& ref_option : ref_options) { + ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_)); + ASSERT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(), + subnet->getID(), + ref_option->option_->getType(), + DHCP4_OPTION_SPACE, + cclasses)); } - // We have added one option to the existing subnet. We should now have - // three options. - ASSERT_EQ(3, countRows("dhcp4_options")); + // Re-fetch the subnet. + returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), subnet->getID()); + ASSERT_TRUE(returned_subnet); - 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); + // Make sure that CfgOption is empty + auto cfg_option = returned_subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE); + EXPECT_TRUE(cfg_option->empty()); +} - 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_); +// Verify that one can add multiple instances of the same option code +// to a pool and that they can be distinguished via their client_classes. +void +GenericConfigBackendDHCPv4Test::poolOption4WithClientClassesTest() { + // Make subnet with a pool with options. + Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, + 30, 40, 60, 1024)); - { - SCOPED_TRACE("verify returned option with modified persistence"); - testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name); - } + Pool4Ptr pool(new Pool4(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"))); + subnet->addPool(pool); - { - SCOPED_TRACE("UPDATE audit entry for an updated subnet option"); - testNewAuditEntry("dhcp4_subnet", - AuditEntry::ModificationType::UPDATE, - "subnet specific option set"); + // Add the options to the pool. + auto ref_options = makeClassTaggedOptions(); + for ( auto const& ref_option : ref_options) { + pool->getCfgOption()->add(*ref_option, ref_option->space_name_); } - // 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")); + // Add the subnet to config back end. + cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet); - // 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_)); + // Fetch this subnet by subnet identifier. + Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); - returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), - subnet->getID()); + PoolPtr returned_pool = returned_subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.10")); + ASSERT_TRUE(returned_pool); + + // Make sure that CfgOption->get() with client_classes finds each ref option. + for (auto const& ref_option : ref_options) { + auto cfg_option = returned_pool->getCfgOption()->get(DHCP4_OPTION_SPACE, + ref_option->option_->getType(), + ref_option->client_classes_); + SCOPED_OPT_COMPARE((*ref_option), cfg_option); + } + + // Now make sure that we can set the options individually. + updateClassTaggedOptions(ref_options); + for (auto const& ref_option : ref_options) { + cbptr_->createUpdateOption4(ServerSelector::ALL(), + pool->getFirstAddress(), + pool->getLastAddress(), + ref_option); + } + + // Re-fetch the subnet. + 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_); + returned_pool = returned_subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.10")); + ASSERT_TRUE(returned_pool); - { - SCOPED_TRACE("UPDATE audit entry for a deleted subnet option"); - testNewAuditEntry("dhcp4_subnet", - AuditEntry::ModificationType::UPDATE, - "subnet specific option deleted"); + // Make sure that CfgOption->get() with client_classes finds each ref option. + for (auto const& ref_option : ref_options) { + auto cfg_option = returned_pool->getCfgOption()->get(DHCP4_OPTION_SPACE, + ref_option->option_->getType(), + ref_option->client_classes_); + SCOPED_OPT_COMPARE((*ref_option), cfg_option); } - // We should have only two options after deleting one of them. - ASSERT_EQ(2, countRows("dhcp4_options")); -#endif + // Now make sure that we can delete the options individually. + for (auto const& ref_option : ref_options) { + ClientClassesPtr cclasses(new ClientClasses(ref_option->client_classes_)); + ASSERT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(), + pool->getFirstAddress(), + pool->getLastAddress(), + ref_option->option_->getType(), + DHCP4_OPTION_SPACE, + cclasses)); + } + + // Re-fetch the subnet. + returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), subnet->getID()); + ASSERT_TRUE(returned_subnet); + + returned_pool = returned_subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.10")); + ASSERT_TRUE(returned_pool); + + // Make sure that CfgOption is empty + auto cfg_option = returned_pool->getCfgOption()->getAll(DHCP4_OPTION_SPACE); + EXPECT_TRUE(cfg_option->empty()); } diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h index 8956cae8d7..64fa801561 100644 --- a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h @@ -369,7 +369,32 @@ public: /// event and it does not matter). void multipleAuditEntriesTest(); - void subnetOption4WithClienClassesTest(); + /// @brief Creates a list of string options with and without client_class tags. + /// It creates 3 DHO_TCODE options and 2 DHO_PCODE options. + std::list makeClassTaggedOptions(); + + /// @brief Updates the value of each string option in the list. + void updateClassTaggedOptions(std::list& options); + + /// @brief This test verifies that multiple instances of an option can + /// be added to global scope and be distinguished from one another + /// by their client-classes content. + void globalOption4WithClientClassesTest(); + + /// @brief This test verifies that multiple instances of an option can + /// be added to a shared-network and be distinguished from one another + /// by their client-classes content. + void sharedNetworkOption4WithClientClassesTest(); + + /// @brief This test verifies that multiple instances of an option can + /// be added to a subnet and be distinguished from one another + /// by their client-classes content. + void subnetOption4WithClientClassesTest(); + + /// @brief This test verifies that multiple instances of an option can + /// be added to a pool and be distinguished from one another + /// by their client-classes content. + void poolOption4WithClientClassesTest(); /// @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 4658df7495..29d29cbb4d 100644 --- a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc +++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc @@ -371,7 +371,8 @@ TestConfigBackendDHCPv4::getModifiedOptionDefs4(const db::ServerSelector& server OptionDescriptorPtr TestConfigBackendDHCPv4::getOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space) const { + const std::string& space, + const ClientClassesPtr client_classes) const { auto tags = server_selector.getTags(); auto candidate = OptionDescriptorPtr(); auto const& index = options_.get<1>(); @@ -379,11 +380,16 @@ TestConfigBackendDHCPv4::getOption4(const db::ServerSelector& server_selector, BOOST_FOREACH(auto const& option_it, option_it_pair) { if (option_it.space_name_ == space) { + if (client_classes && (option_it.client_classes_ != *client_classes)) { + continue; + } + for (auto const& tag : tags) { if (option_it.hasServerTag(ServerTag(tag))) { return (OptionDescriptorPtr(new OptionDescriptor(option_it))); } } + if (option_it.hasAllServerTag()) { candidate = OptionDescriptorPtr(new OptionDescriptor(option_it)); } diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h index 9c25512c36..656f9cecdd 100644 --- a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h +++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h @@ -168,11 +168,16 @@ public: /// @brief Retrieves single option by code and space. /// /// @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 Pointer to the retrieved option descriptor or null if /// no option was found. virtual OptionDescriptorPtr getOption4(const db::ServerSelector& server_selector, const uint16_t code, - const std::string& space) const; + const std::string& space, + const ClientClassesPtr client_classes = ClientClassesPtr()) const; /// @brief Retrieves all global options. /// -- 2.47.2