]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3770] Finished MySql v4, Expanded v4 UTs
authorThomas Markwalder <tmark@isc.org>
Mon, 7 Jul 2025 15:45:25 +0000 (11:45 -0400)
committerThomas Markwalder <tmark@isc.org>
Tue, 15 Jul 2025 14:02:03 +0000 (14:02 +0000)
/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

18 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/mysql/tests/mysql_cb_dhcp4_unittest.cc
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/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/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 5491078256e911bc2f02213ebad4b7796f8e7c20..1c7ca0a3dd10077a19f851cf028c468f7173a0ce 100644 (file)
@@ -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<uint8_t>(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<uint32_t>(static_cast<uint32_t>(subnet_id)),
             MySqlBinding::createInteger<uint8_t>(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<uint8_t>(code),
             MySqlBinding::createString(space),
+            createClientClassesForWhereClause(client_classes),
             MySqlBinding::createInteger<uint32_t>(pool_start_address.toUint32()),
-            MySqlBinding::createInteger<uint32_t>(pool_end_address.toUint32()),
-            createClientClassesForWhereClause(client_classes)
+            MySqlBinding::createInteger<uint32_t>(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<uint8_t>(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.
index 979322e30c48eebe07ca048854c30ce7b41affd4..31e74f89b893ad08b4884e59e452453acb364af0 100644 (file)
@@ -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.
     ///
index 751cb00cb9c2a6966c883e6774d64f73a694b286..47b1bc75ab1498e042866416d3505898fded6cde 100644 (file)
@@ -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<uint16_t>(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("%"));
 }
index 334a75cf5a054a291e3f0825c9322438d6e9a853..45135942fb8a60e541f1ae00ef6b2a40254319e5 100644 (file)
@@ -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.
     ///
index bb418b529610372293e352fbde070fc05fe6e19a..e4f366d5971a3d2929ba1a648a8c7f03140f7a89 100644 (file)
@@ -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 {
index 3272191e1d9a11eaa849cd103441b9495487d33c..9a465253271f42f4e1748c78b1ec4e4b163bdcf8 100644 (file)
@@ -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.
index 8fac877ad343f566d5b85918e2055dc396231b4b..6c7d91b6435a3b8fa317ad6739b9ae8234009abb 100644 (file)
@@ -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.
     ///
index f909afbb3aadd4e8ed9ac6512242dceeafe36f07..87cd19f68858e853fe0806291340396a7fd8e5f0 100644 (file)
@@ -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() :
index 3576c9e28ca743fa8bd6b18c49a8050478254ec4..eea15d5a2dacfa25da47efe0d5be2b86b7f271ef 100644 (file)
@@ -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.
     ///
index 54b6a2ab69498c2ef0a7446e5c39096a7a1cf839..8787aee0d51c08f9552d6bbcd7d0a35934941870 100644 (file)
@@ -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
index 53d7df9e2da3dc4393b86a9c59cd66f5bab202a4..e5d0930f947f7f66a302ba5270c3084e9984ce55 100644 (file)
@@ -717,7 +717,7 @@ public:
     template<typename Selector>
     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);
index d98997ec0863c14b8ba6ee171603c0e88e0c159f..6ca3fee44180899ff144db9f4a11f34995f6063a 100644 (file)
@@ -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.
     ///
index 1c3ea3adc65e219c23b549acf451d1cfe777119f..7dc8aab9e6ff331ceda0e00088701baa39d7bbf5 100644 (file)
@@ -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<OptionDescriptorPtr, uint16_t, const std::string&>
         (&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<uint64_t, uint16_t, const std::string&>
             (&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<uint64_t, const std::string&, uint16_t,
                                        const std::string&>
             (&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<uint64_t, const SubnetID&, uint16_t, const std::string&>
             (&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<uint64_t, const IOAddress&, const IOAddress&,
                                        uint16_t, const std::string&>
             (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector,
index eb935a08ef319d8f8d7ca734a840c09e0442ac9f..72aa94a6fd4f132a8864d1714a5a26febefde866 100644 (file)
@@ -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.
     ///
index 0acedfdc0943516c1bbb9111f0a892fb4d2465a9..cbbe1f1e1b18d3b31bfafc8290ef0bd4f67306f3 100644 (file)
@@ -4718,154 +4718,333 @@ GenericConfigBackendDHCPv4Test::multipleAuditEntriesTest() {
     }
 }
 
+std::list<OptionDescriptorPtr>
+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<OptData> 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<OptionDescriptorPtr> tagged_options;
+    for ( auto const& opt_to_make : opts_to_make) {
+        OptionDescriptor desc = createOption<OptionString>(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<OptionDescriptorPtr>& options) {
+    for ( auto& desc : options) {
+        OptionStringPtr opt = boost::dynamic_pointer_cast<OptionString>(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() : "<null>") << "\n";\
+    SCOPED_TRACE(oss.str());\
+    testOptionsEquivalent(exp_opt,test_opt);\
+}
 
-    // 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);
+// 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<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);
+// 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<OptionString>(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());
 }
index 8956cae8d7edf5476deb4077636f1749cdb962e3..64fa80156103557f9ccc22410a22a255fc435ccd 100644 (file)
@@ -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<OptionDescriptorPtr> makeClassTaggedOptions();
+
+    /// @brief Updates the value of each string option in the list.
+    void updateClassTaggedOptions(std::list<OptionDescriptorPtr>& 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<Subnet4Ptr> test_subnets_;
index 4658df7495ec63f0cb41796fdaa864cc3d25801b..29d29cbb4dd8448ce4bbd397ab44568372786187 100644 (file)
@@ -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));
             }
index 9c25512c36df0d0f36f0c6e35ed45d7559d1160d..656f9cecdd752487dc41e07ddaf5327d1844fc55 100644 (file)
@@ -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.
     ///