]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3770] CfgOption and CB v4 use client-classes
authorThomas Markwalder <tmark@isc.org>
Tue, 1 Jul 2025 14:00:32 +0000 (10:00 -0400)
committerThomas Markwalder <tmark@isc.org>
Tue, 15 Jul 2025 14:02:03 +0000 (14:02 +0000)
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<ClientClasses> 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

21 files changed:
src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc
src/hooks/dhcp/mysql/mysql_cb_dhcp4.h
src/hooks/dhcp/mysql/mysql_cb_impl.cc
src/hooks/dhcp/mysql/mysql_cb_impl.h
src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.cc
src/hooks/dhcp/pgsql/pgsql_cb_dhcp4.h
src/hooks/dhcp/pgsql/pgsql_cb_impl.cc
src/hooks/dhcp/pgsql/pgsql_cb_impl.h
src/hooks/dhcp/pgsql/tests/pgsql_cb_dhcp4_unittest.cc
src/lib/dhcp/classify.h
src/lib/dhcpsrv/cfg_option.cc
src/lib/dhcpsrv/cfg_option.h
src/lib/dhcpsrv/config_backend_dhcp4.h
src/lib/dhcpsrv/config_backend_pool_dhcp4.cc
src/lib/dhcpsrv/config_backend_pool_dhcp4.h
src/lib/dhcpsrv/tests/cfg_option_unittest.cc
src/lib/dhcpsrv/testutils/generic_backend_unittest.cc
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h
src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc
src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h

index 2a4d81f00f780ace04fddaa3de7fad90e8bd49cc..5491078256e911bc2f02213ebad4b7796f8e7c20 100644 (file)
@@ -1863,7 +1863,8 @@ public:
             cc_binding,
             MySqlBinding::createString(tag),
             MySqlBinding::createInteger<uint8_t>(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<uint32_t>(static_cast<uint32_t>(subnet_id)),
             MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
-            MySqlBinding::condCreateString(option->space_name_)
+            MySqlBinding::condCreateString(option->space_name_),
+            cc_binding
         };
 
         boost::scoped_ptr<MySqlTransaction> 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<uint64_t>(pool_id),
             MySqlBinding::createInteger<uint8_t>(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<uint8_t>(option->option_->getType()),
-            MySqlBinding::condCreateString(option->space_name_)
+            MySqlBinding::condCreateString(option->space_name_),
+            cc_binding
         };
 
         boost::scoped_ptr<MySqlTransaction> 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<uint8_t>(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<uint8_t>(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<uint32_t>(static_cast<uint32_t>(subnet_id)),
             MySqlBinding::createInteger<uint8_t>(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<uint8_t>(code),
             MySqlBinding::createString(space),
             MySqlBinding::createInteger<uint32_t>(pool_start_address.toUint32()),
-            MySqlBinding::createInteger<uint32_t>(pool_end_address.toUint32())
+            MySqlBinding::createInteger<uint32_t>(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<uint8_t>(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);
index 782b3671508d18b34afb67f4457764e43ba83a3b..979322e30c48eebe07ca048854c30ce7b41affd4 100644 (file)
@@ -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.
     ///
index e0f30e3f6d9d95909d7cc74d272d396f5d9024d1..751cb00cb9c2a6966c883e6774d64f73a694b286 100644 (file)
@@ -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) {
index ec458c5a49bc8bbc50d8139e03cb6bae8add2f06..334a75cf5a054a291e3f0825c9322438d6e9a853 100644 (file)
@@ -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
index 3d5a9d0aab3a2f628b12bc3fba57a7de60a7da85..3272191e1d9a11eaa849cd103441b9495487d33c 100644 (file)
@@ -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);
index 7e0104604a238beb10fcda2961a071937fa5d90a..8fac877ad343f566d5b85918e2055dc396231b4b 100644 (file)
@@ -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.
     ///
index 40f6aa2705ccaaccc13ab2fbe59d2fabc330c29a..f909afbb3aadd4e8ed9ac6512242dceeafe36f07 100644 (file)
@@ -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
index 4e495b587d043452f8f124360a95389624ba1f9d..3576c9e28ca743fa8bd6b18c49a8050478254ec4 100644 (file)
@@ -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.
     ///
index f8ba885fd56ba1d46eae269936b5fb23c89a4a7e..54b6a2ab69498c2ef0a7446e5c39096a7a1cf839 100644 (file)
@@ -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 {
index 8571cf990949f37e8c008217a8ffdeb9af0ba3ac..9e60e24393c684c18998632dfc83f26cfe06a818 100644 (file)
@@ -255,6 +255,9 @@ private:
     ClientClassContainer container_;
 };
 
+/// @brief Smart pointer to ClientClasses object.
+typedef boost::shared_ptr<ClientClasses> ClientClassesPtr;
+
 }
 }
 
index 5fbb2dee5bbafa961d9fa0c32510da15c761a891..8f18c135e86aa8f58e6a8f80a723f505f46efa96 100644 (file)
@@ -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<std::string>
@@ -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.
index 5c3c1958d2001c9afc91e86f6b4183377fd5f168..53d7df9e2da3dc4393b86a9c59cd66f5bab202a4 100644 (file)
@@ -22,6 +22,7 @@
 #include <boost/multi_index/sequenced_index.hpp>
 #include <boost/multi_index/mem_fun.hpp>
 #include <boost/multi_index/member.hpp>
+#include <boost/multi_index/composite_key.hpp>
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 #include <list>
@@ -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<OptionDescriptor,
+                                           ClientClasses,
+                                           &OptionDescriptor::client_classes_>
+            >
         >
     >
 > OptionContainer;
@@ -343,6 +365,11 @@ typedef OptionContainer::nth_index<5>::type OptionContainerCancelIndex;
 typedef std::pair<OptionContainerCancelIndex::const_iterator,
                   OptionContainerCancelIndex::const_iterator> OptionContainerCancelRange;
 
+/// Type of the index #6 - option type + client_classes.
+typedef OptionContainer::nth_index<6>::type OptionContainerTypeClassesIndex;
+typedef std::pair<OptionContainerTypeClassesIndex::const_iterator,
+                  OptionContainerTypeClassesIndex::const_iterator> 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<typename Selector>
+    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.
index a235a4b7b05253a36d772ef56a9990c62774b2c0..d98997ec0863c14b8ba6ee171603c0e88e0c159f 100644 (file)
@@ -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.
     ///
index 1eaa2730ac98858d735217a42ba694024d8d00b8..1c3ea3adc65e219c23b549acf451d1cfe777119f 100644 (file)
@@ -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<uint64_t, uint16_t, const std::string&>
             (&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<uint64_t, const std::string&, uint16_t,
                                        const std::string&>
             (&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<uint64_t, const SubnetID&, uint16_t, const std::string&>
             (&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<uint64_t, const IOAddress&, const IOAddress&,
                                        uint16_t, const std::string&>
             (&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
index 1810d5f05fee669c861b175658e98a031a5c481b..eb935a08ef319d8f8d7ca734a840c09e0442ac9f 100644 (file)
@@ -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.
     ///
index 8bdfcc623f7d9936f4cd6aea72d893af6b329280..d52622e47ba7991cae0a8e2b99ecf7dbaf157f09 100644 (file)
@@ -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<OptData> 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<OptData> 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<OptionUint16>(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<OptionUint16>(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<OptData> 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
index ead2c5ea26c60a79c26291e8aa9319642ed26e0a..f66105b844238828dc337813c1b101f5588b8e23 100644 (file)
@@ -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
index 2ea1411f70f51c4ae95ffc9d13470a9d93177d51..0acedfdc0943516c1bbb9111f0a892fb4d2465a9 100644 (file)
@@ -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<OptionDescriptor> options;
+    OptionDescriptor desc = createOption<OptionString>(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<OptionString>(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<OptionString>(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
+}
index 4375cb426db05c9ad59fa9db0f8e2e14c90bd994..8956cae8d7edf5476deb4077636f1749cdb962e3 100644 (file)
@@ -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<Subnet4Ptr> test_subnets_;
 
index e0a7f0a0bfb06a76fc46009b7fe01d0ac215e3c5..4658df7495ec63f0cb41796fdaa864cc3d25801b 100644 (file)
@@ -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<SharedNetworkNameIndexTag>();
     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<SubnetSubnetIdIndexTag>();
     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));
     }
 
index 7791a0f24161c7056010794a3a52f52ca98674bc..9c25512c36df0d0f36f0c6e35ed45d7559d1160d 100644 (file)
@@ -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.
     ///