]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#93,!51] Added options management in MySQL DHCPv4 config backend.
authorMarcin Siodelski <marcin@isc.org>
Tue, 2 Oct 2018 09:09:19 +0000 (11:09 +0200)
committerMarcin Siodelski <marcin@isc.org>
Mon, 8 Oct 2018 18:09:51 +0000 (20:09 +0200)
18 files changed:
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h
src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
src/hooks/dhcp/mysql_cb/mysql_cb_impl.h
src/hooks/dhcp/mysql_cb/tests/Makefile.am
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
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/testutils/Makefile.am
src/lib/dhcpsrv/testutils/generic_backend_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/testutils/generic_backend_unittest.h [new file with mode: 0644]
src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc
src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h
src/share/database/scripts/mysql/dhcpdb_create.mysql
src/share/database/scripts/mysql/upgrade_6.0_to_7.0.sh.in

index bb3790c335323c3a5cf21fcc28d2bd3dbdb8f50a..7fa569869bbeb79095ae1f52d1c19ce1af912aec 100644 (file)
 #include <database/db_exceptions.h>
 #include <dhcp/classify.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/option_data_types.h>
+#include <dhcp/option_space.h>
 #include <dhcpsrv/network.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/lease.h>
+#include <util/buffer.h>
 #include <mysql/mysql_connection.h>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/lexical_cast.hpp>
@@ -28,6 +31,7 @@
 using namespace isc::db;
 using namespace isc::data;
 using namespace isc::asiolink;
+using namespace isc::util;
 
 namespace isc {
 namespace dhcp {
@@ -53,13 +57,18 @@ public:
         GET_OPTION_DEF4_CODE_SPACE,
         GET_ALL_OPTION_DEFS4,
         GET_MODIFIED_OPTION_DEFS4,
+        GET_OPTION4_SUBNET_ID_CODE_SPACE,
+        GET_OPTION4_SHARED_NETWORK_CODE_SPACE,
         INSERT_SUBNET4,
         INSERT_POOL4,
         INSERT_SHARED_NETWORK4,
         INSERT_OPTION_DEF4,
+        INSERT_OPTION4,
         UPDATE_SUBNET4,
         UPDATE_SHARED_NETWORK4,
         UPDATE_OPTION_DEF4,
+        UPDATE_OPTION4_SUBNET_ID,
+        UPDATE_OPTION4_SHARED_NETWORK,
         DELETE_SUBNET4_ID,
         DELETE_SUBNET4_PREFIX,
         DELETE_ALL_SUBNETS4,
@@ -68,6 +77,10 @@ public:
         DELETE_ALL_SHARED_NETWORKS4,
         DELETE_OPTION_DEF4_CODE_NAME,
         DELETE_ALL_OPTION_DEFS4,
+        DELETE_OPTION4_SUBNET_ID,
+        DELETE_OPTION4_SHARED_NETWORK,
+        DELETE_OPTIONS4_SUBNET_ID,
+        DELETE_OPTIONS4_SHARED_NETWORK,
         NUM_STATEMENTS
     };
 
@@ -119,14 +132,27 @@ public:
             MySqlBinding::createInteger<uint32_t>(), // pool: start_address
             MySqlBinding::createInteger<uint32_t>(), // pool: end_address
             MySqlBinding::createInteger<uint32_t>(), // pool: subnet_id
-            MySqlBinding::createTimestamp() // pool: modification_ts
+            MySqlBinding::createTimestamp(), // pool: modification_ts
+            MySqlBinding::createInteger<uint64_t>(), // option: option_id
+            MySqlBinding::createInteger<uint8_t>(), // option: code
+            MySqlBinding::createBlob(65536), // option: value
+            MySqlBinding::createString(8192), // option: formatted_value
+            MySqlBinding::createString(128), // option: space
+            MySqlBinding::createInteger<uint8_t>(), // option: persistent
+            MySqlBinding::createInteger<uint32_t>(), // option: dhcp4_subnet_id
+            MySqlBinding::createInteger<uint8_t>(), // option: scope_id
+            MySqlBinding::createString(65536), // option: user_context
+            MySqlBinding::createString(128), // option: shared_network_name
+            MySqlBinding::createInteger<uint64_t>(), // option: pool_id
+            MySqlBinding::createTimestamp() //option: modification_ts
         };
 
         uint64_t last_pool_id = 0;
+        uint64_t last_option_id = 0;
 
         // Execute actual query.
         conn_.selectQuery(index, in_bindings, out_bindings,
-                          [&subnets, &last_pool_id]
+                          [this, &subnets, &last_pool_id, &last_option_id]
                           (MySqlBindingCollection& out_bindings) {
             // Get pointer to the last subnet in the collection.
             Subnet4Ptr last_subnet;
@@ -251,12 +277,25 @@ public:
                 (out_bindings[21]->getInteger<uint32_t>() != 0) &&
                 (out_bindings[22]->getInteger<uint32_t>() != 0) &&
                 ((last_pool_id == 0) ||
-                 (out_bindings[20]->getInteger<uint64_t>() != last_pool_id))) {
+                 (out_bindings[20]->getInteger<uint64_t>() > last_pool_id))) {
                 last_pool_id = out_bindings[20]->getInteger<uint64_t>();
                 Pool4Ptr pool(new Pool4(IOAddress(out_bindings[21]->getInteger<uint32_t>()),
                                         IOAddress(out_bindings[22]->getInteger<uint32_t>())));
                 last_subnet->addPool(pool);
             }
+
+            // Parse option.
+            if (!out_bindings[25]->amNull() &&
+                ((last_option_id == 0) ||
+                (last_option_id < out_bindings[25]->getInteger<uint64_t>()))) {
+                last_option_id = out_bindings[25]->getInteger<uint64_t>();
+
+                OptionDescriptorPtr desc = processOptionRow(Option::V4, out_bindings.begin() + 25);
+                if (desc) {
+                    last_subnet->getCfgOption()->add(*desc, desc->space_name_);
+                }
+            }
+
         });
     }
 
@@ -373,9 +412,8 @@ public:
                               in_bindings);
 
         } catch (const DuplicateEntry&) {
-            // Delete existing pools in case the updated subnet contains different
-            // set of pools.
             deletePools4(subnet);
+            deleteOptions4(existing_subnet);
 
             // Need to add one more binding for WHERE clause.
             in_bindings.push_back(MySqlBinding::createInteger<uint32_t>(subnet->getID()));
@@ -388,6 +426,17 @@ public:
             createPool4(boost::dynamic_pointer_cast<Pool4>(pool), subnet);
         }
 
+        // (Re)create options.
+        auto option_spaces = subnet->getCfgOption()->getOptionSpaceNames();
+        for (auto option_space : option_spaces) {
+            OptionContainerPtr options = subnet->getCfgOption()->getAll(option_space);
+            for (auto desc = options->begin(); desc != options->end(); ++desc) {
+                OptionDescriptorPtr desc_copy(new OptionDescriptor(*desc));
+                desc_copy->space_name_ = option_space;
+                createUpdateOption4(selector, subnet->getID(), desc_copy);
+            }
+        }
+
         transaction.commit();
     }
 
@@ -464,12 +513,26 @@ public:
             MySqlBinding::createString(65536), // require_client_classes
             MySqlBinding::createInteger<uint8_t>(), // reservation_mode
             MySqlBinding::createString(65536), // user_context
-            MySqlBinding::createInteger<uint32_t>() // valid_lifetime
+            MySqlBinding::createInteger<uint32_t>(), // valid_lifetime
+            MySqlBinding::createInteger<uint64_t>(), // option: option_id
+            MySqlBinding::createInteger<uint8_t>(), // option: code
+            MySqlBinding::createBlob(65536), // option: value
+            MySqlBinding::createString(8192), // option: formatted_value
+            MySqlBinding::createString(128), // option: space
+            MySqlBinding::createInteger<uint8_t>(), // option: persistent
+            MySqlBinding::createInteger<uint32_t>(), // option: dhcp4_subnet_id
+            MySqlBinding::createInteger<uint8_t>(), // option: scope_id
+            MySqlBinding::createString(65536), // option: user_context
+            MySqlBinding::createString(128), // option: shared_network_name
+            MySqlBinding::createInteger<uint64_t>(), // option: pool_id
+            MySqlBinding::createTimestamp() //option: modification_ts
         };
 
         uint64_t last_network_id = 0;
+        uint64_t last_option_id = 0;
+
         conn_.selectQuery(index, in_bindings, out_bindings,
-                          [&shared_networks, &last_network_id]
+                          [this, &shared_networks, &last_network_id, &last_option_id]
                           (MySqlBindingCollection& out_bindings) {
             SharedNetwork4Ptr last_network;
             if (!shared_networks.empty()) {
@@ -559,6 +622,18 @@ public:
 
                 shared_networks.push_back(last_network);
             }
+
+            // Parse option.
+            if (!out_bindings[13]->amNull() &&
+                ((last_option_id == 0) ||
+                (last_option_id < out_bindings[13]->getInteger<uint64_t>()))) {
+                last_option_id = out_bindings[13]->getInteger<uint64_t>();
+
+                OptionDescriptorPtr desc = processOptionRow(Option::V4, out_bindings.begin() + 13);
+                if (desc) {
+                    last_network->getCfgOption()->add(*desc, desc->space_name_);
+                }
+            }
         });
     }
 
@@ -612,27 +687,106 @@ public:
                               in_bindings);
 
         } catch (const DuplicateEntry&) {
+            deleteOptions4(shared_network);
+
             // Need to add one more binding for WHERE clause.
             in_bindings.push_back(MySqlBinding::createString(shared_network->getName()));
             conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_SHARED_NETWORK4,
                                     in_bindings);
         }
 
+        // (Re)create options.
+        auto option_spaces = shared_network->getCfgOption()->getOptionSpaceNames();
+        for (auto option_space : option_spaces) {
+            OptionContainerPtr options = shared_network->getCfgOption()->getAll(option_space);
+            for (auto desc = options->begin(); desc != options->end(); ++desc) {
+                OptionDescriptorPtr desc_copy(new OptionDescriptor(*desc));
+                desc_copy->space_name_ = option_space;
+                createUpdateOption4(selector, shared_network->getName(), desc_copy);
+            }
+        }
+
         transaction.commit();
     }
 
-    /// @brief Sends query to the database to retrieve multiple option
-    /// definitions.
+    /// @brief Sends query to insert or update DHCP option in a subnet.
     ///
-    /// Query should order option definitions by id.
+    /// @param selector Server selector.
+    /// @param subnet_id Identifier of the subnet the option belongs to.
+    /// @param option Pointer to the option descriptor encapsulating the option.
+    void createUpdateOption4(const ServerSelector& selector,
+                             const SubnetID& subnet_id,
+                             const OptionDescriptorPtr& option) {
+
+        MySqlBindingCollection in_bindings = {
+            MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
+            createOptionValueBinding(option),
+            MySqlBinding::condCreateString(option->formatted_value_),
+            MySqlBinding::condCreateString(option->space_name_),
+            MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(option->persistent_)),
+            MySqlBinding::createNull(),
+            MySqlBinding::createInteger<uint32_t>(static_cast<uint32_t>(subnet_id)),
+            MySqlBinding::createInteger<uint8_t>(1),
+            createInputContextBinding(option),
+            MySqlBinding::createNull(),
+            MySqlBinding::createNull(),
+            MySqlBinding::createTimestamp(option->getModificationTime())
+        };
+
+        OptionDescriptorPtr existing_option = getOption4(selector, subnet_id,
+                                                         option->option_->getType(),
+                                                         option->space_name_);
+        if (existing_option) {
+            in_bindings.push_back(MySqlBinding::createInteger<uint32_t>(static_cast<uint32_t>(subnet_id)));
+            in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(option->option_->getType()));
+            in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_));
+            conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SUBNET_ID,
+                                    in_bindings);
+
+        } else {
+            conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_OPTION4,
+                              in_bindings);
+        }
+    }
+
+    /// @brief Sends query to insert or update DHCP option in a shared network.
     ///
-    /// @param index Index of the query to be used.
-    /// @param in_bindings Input bindings specifying selection criteria. The
-    /// size of the bindings collection must match the number of placeholders
-    /// in the prepared statement. The input bindings collection must be empty
-    /// if the query contains no WHERE clause.
-    /// @param [out] option_defs Reference to the container where fetched
-    /// option definitions will be inserted.
+    /// @param selector Server selector.
+    /// @param shared_network_name Name of the shared network the option
+    /// belongs to.
+    /// @param option Pointer to the option descriptor encapsulating the option.
+    void createUpdateOption4(const ServerSelector& selector,
+                             const std::string& shared_network_name,
+                             const OptionDescriptorPtr& option) {
+        MySqlBindingCollection in_bindings = {
+            MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
+            createOptionValueBinding(option),
+            MySqlBinding::condCreateString(option->formatted_value_),
+            MySqlBinding::condCreateString(option->space_name_),
+            MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(option->persistent_)),
+            MySqlBinding::createNull(),
+            MySqlBinding::createNull(),
+            MySqlBinding::createInteger<uint8_t>(4),
+            createInputContextBinding(option),
+            MySqlBinding::createString(shared_network_name),
+            MySqlBinding::createNull(),
+            MySqlBinding::createTimestamp(option->getModificationTime())
+        };
+
+        OptionDescriptorPtr existing_option = getOption4(selector, shared_network_name,
+                                                         option->option_->getType(),
+                                                         option->space_name_);
+        if (existing_option) {
+            in_bindings.push_back(MySqlBinding::createString(shared_network_name));
+            in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(option->option_->getType()));
+            in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_));
+            conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::
+                                    UPDATE_OPTION4_SHARED_NETWORK,
+                                    in_bindings);
+        } else {
+            conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_OPTION4, in_bindings);
+        }
+    }
 
     /// @brief Sends query to retrieve single option definition by code and
     /// option space.
@@ -685,6 +839,58 @@ public:
         return (option_defs);
     }
 
+    /// @brief Sends query to retrieve single option by code and option space
+    /// for a giben subnet id.
+    ///
+    /// @param selector Server selector.
+    /// @param subnet_id Subnet identifier.
+    /// @param code Option code.
+    /// @param space Option space name.
+    ///
+    /// @return Pointer to the returned option descriptor or NULL if such
+    /// option doesn't exist.
+    OptionDescriptorPtr getOption4(const ServerSelector& /* selector */,
+                                   const SubnetID& subnet_id,
+                                   const uint16_t code,
+                                   const std::string& space) {
+        OptionContainer options;
+        MySqlBindingCollection in_bindings = {
+            MySqlBinding::createInteger<uint32_t>(static_cast<uint32_t>(subnet_id)),
+            MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(code)),
+            MySqlBinding::createString(space)
+        };
+        getOptions(GET_OPTION4_SUBNET_ID_CODE_SPACE, in_bindings, Option::V4,
+                   options);
+        return (options.empty() ? OptionDescriptorPtr() :
+                OptionDescriptorPtr(new OptionDescriptor(*options.begin())));
+    }
+
+    /// @brief Sends query to retrieve single option by code and option space
+    /// for a given shared network.
+    ///
+    /// @param selector Server selector.
+    /// @param shared_network_name Shared network name.
+    /// @param code Option code.
+    /// @param space Option space name.
+    ///
+    /// @return Pointer to the returned option descriptor or NULL if such
+    /// option doesn't exist.
+    OptionDescriptorPtr getOption4(const ServerSelector& /* selector */,
+                                   const std::string& shared_network_name,
+                                   const uint16_t code,
+                                   const std::string& space) {
+        OptionContainer options;
+        MySqlBindingCollection in_bindings = {
+            MySqlBinding::createString(shared_network_name),
+            MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(code)),
+            MySqlBinding::createString(space)
+        };
+        getOptions(GET_OPTION4_SHARED_NETWORK_CODE_SPACE, in_bindings, Option::V4,
+                   options);
+        return (options.empty() ? OptionDescriptorPtr() :
+                OptionDescriptorPtr(new OptionDescriptor(*options.begin())));
+    }
+
     /// @brief Sends query to insert or update option definition.
     ///
     /// @param server_selector Server selector.
@@ -759,6 +965,74 @@ public:
         // Run DELETE.
         return (conn_.updateDeleteQuery(DELETE_OPTION_DEF4_CODE_NAME, in_bindings));
     }
+
+    /// @brief Deletes subnet level option.
+    ///
+    /// @param selector Server selector.
+    /// @param subnet_id Identifier of the subnet to which deleted option
+    /// belongs.
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    void deleteOption4(const db::ServerSelector& /* selector */,
+                       const SubnetID& subnet_id,
+                       const uint16_t code,
+                       const std::string& space) {
+        MySqlBindingCollection in_bindings = {
+            MySqlBinding::createInteger<uint32_t>(static_cast<uint32_t>(subnet_id)),
+            MySqlBinding::createInteger<uint8_t>(code),
+            MySqlBinding::createString(space)
+        };
+
+        // Run DELETE.
+        conn_.updateDeleteQuery(DELETE_OPTION4_SUBNET_ID, in_bindings);
+    }
+
+    /// @brief Deletes shared network level option.
+    ///
+    /// @param selector Server selector.
+    /// @param shared_network_name Name of the shared network which deleted
+    /// option belongs to
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    void deleteOption4(const db::ServerSelector& /* selector */,
+                       const std::string& shared_network_name,
+                       const uint16_t code,
+                       const std::string& space) {
+        MySqlBindingCollection in_bindings = {
+            MySqlBinding::createString(shared_network_name),
+            MySqlBinding::createInteger<uint8_t>(code),
+            MySqlBinding::createString(space)
+        };
+
+        // Run DELETE.
+        conn_.updateDeleteQuery(DELETE_OPTION4_SHARED_NETWORK, in_bindings);
+    }
+
+    /// @brief Deletes options belonging to a subnet from the database.
+    ///
+    /// @param subnet Pointer to the subnet for which options should be
+    /// deleted.
+    void deleteOptions4(const Subnet4Ptr& subnet) {
+        MySqlBindingCollection in_bindings = {
+            MySqlBinding::createInteger<uint32_t>(subnet->getID())
+        };
+
+        // Run DELETE.
+        conn_.updateDeleteQuery(DELETE_OPTIONS4_SUBNET_ID, in_bindings);
+    }
+
+    /// @brief Deletes options belonging to a shared network from the database.
+    ///
+    /// @param subnet Pointer to the subnet for which options should be
+    /// deleted.
+    void deleteOptions4(const SharedNetwork4Ptr& shared_network) {
+        MySqlBindingCollection in_bindings = {
+            MySqlBinding::createString(shared_network->getName())
+        };
+
+        // Run DELETE.
+        conn_.updateDeleteQuery(DELETE_OPTIONS4_SHARED_NETWORK, in_bindings);
+    }
 };
 
 /// @brief Array of tagged statements.
@@ -795,11 +1069,24 @@ TaggedStatementArray tagged_statements = { {
       "  p.start_address,"
       "  p.end_address,"
       "  p.subnet_id,"
-      "  p.modification_ts "
+      "  p.modification_ts,"
+      "  o.option_id,"
+      "  o.code,"
+      "  o.value,"
+      "  o.formatted_value,"
+      "  o.space,"
+      "  o.persistent,"
+      "  o.dhcp4_subnet_id,"
+      "  o.scope_id,"
+      "  o.user_context,"
+      "  o.shared_network_name,"
+      "  o.pool_id,"
+      "  o.modification_ts "
       "FROM dhcp4_subnet AS s "
       "LEFT JOIN dhcp4_pool AS p ON s.subnet_id = p.subnet_id "
+      "LEFT JOIN dhcp4_options AS o ON o.scope_id = 1 AND s.subnet_id = o.dhcp4_subnet_id "
       "WHERE s.subnet_id = ? "
-      "ORDER BY s.subnet_id, p.id" },
+      "ORDER BY s.subnet_id, p.id, o.option_id" },
 
     // Select subnet by prefix.
     { MySqlConfigBackendDHCPv4Impl::GET_SUBNET4_PREFIX,
@@ -828,11 +1115,24 @@ TaggedStatementArray tagged_statements = { {
       "  p.start_address,"
       "  p.end_address,"
       "  p.subnet_id,"
-      "  p.modification_ts "
+      "  p.modification_ts,"
+      "  o.option_id,"
+      "  o.code,"
+      "  o.value,"
+      "  o.formatted_value,"
+      "  o.space,"
+      "  o.persistent,"
+      "  o.dhcp4_subnet_id,"
+      "  o.scope_id,"
+      "  o.user_context,"
+      "  o.shared_network_name,"
+      "  o.pool_id,"
+      "  o.modification_ts "
       "FROM dhcp4_subnet AS s "
       "LEFT JOIN dhcp4_pool AS p ON s.subnet_id = p.subnet_id "
+      "LEFT JOIN dhcp4_options AS o ON o.scope_id = 1 AND s.subnet_id = o.dhcp4_subnet_id "
       "WHERE s.subnet_prefix = ? "
-      "ORDER BY s.subnet_id, p.id" },
+      "ORDER BY s.subnet_id, p.id, o.option_id" },
 
     // Select all subnets.
     { MySqlConfigBackendDHCPv4Impl::GET_ALL_SUBNETS4,
@@ -861,10 +1161,23 @@ TaggedStatementArray tagged_statements = { {
       "  p.start_address,"
       "  p.end_address,"
       "  p.subnet_id,"
-      "  p.modification_ts "
+      "  p.modification_ts,"
+      "  o.option_id,"
+      "  o.code,"
+      "  o.value,"
+      "  o.formatted_value,"
+      "  o.space,"
+      "  o.persistent,"
+      "  o.dhcp4_subnet_id,"
+      "  o.scope_id,"
+      "  o.user_context,"
+      "  o.shared_network_name,"
+      "  o.pool_id,"
+      "  o.modification_ts "
       "FROM dhcp4_subnet AS s "
       "LEFT JOIN dhcp4_pool AS p ON s.subnet_id = p.subnet_id "
-      "ORDER BY s.subnet_id" },
+      "LEFT JOIN dhcp4_options AS o ON o.scope_id = 1 AND s.subnet_id = o.dhcp4_subnet_id "
+      "ORDER BY s.subnet_id, p.id, o.option_id" },
 
     // Select subnets having modification time later than X.
     { MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_SUBNETS4,
@@ -893,11 +1206,24 @@ TaggedStatementArray tagged_statements = { {
       "  p.start_address,"
       "  p.end_address,"
       "  p.subnet_id,"
-      "  p.modification_ts "
+      "  p.modification_ts,"
+      "  o.option_id,"
+      "  o.code,"
+      "  o.value,"
+      "  o.formatted_value,"
+      "  o.space,"
+      "  o.persistent,"
+      "  o.dhcp4_subnet_id,"
+      "  o.scope_id,"
+      "  o.user_context,"
+      "  o.shared_network_name,"
+      "  o.pool_id,"
+      "  o.modification_ts "
       "FROM dhcp4_subnet AS s "
       "LEFT JOIN dhcp4_pool AS p ON s.subnet_id = p.subnet_id "
+      "LEFT JOIN dhcp4_options AS o ON o.scope_id = 1 AND s.subnet_id = o.dhcp4_subnet_id "
       "WHERE s.modification_ts > ? "
-      "ORDER BY s.subnet_id" },
+      "ORDER BY s.subnet_id, p.id, o.option_id" },
 
     // Select shared network by name.
     { MySqlConfigBackendDHCPv4Impl::GET_SHARED_NETWORK4_NAME,
@@ -914,10 +1240,23 @@ TaggedStatementArray tagged_statements = { {
       "  n.require_client_classes,"
       "  n.reservation_mode,"
       "  n.user_context,"
-      "  n.valid_lifetime "
+      "  n.valid_lifetime,"
+      "  o.option_id,"
+      "  o.code,"
+      "  o.value,"
+      "  o.formatted_value,"
+      "  o.space,"
+      "  o.persistent,"
+      "  o.dhcp4_subnet_id,"
+      "  o.scope_id,"
+      "  o.user_context,"
+      "  o.shared_network_name,"
+      "  o.pool_id,"
+      "  o.modification_ts "
       "FROM dhcp4_shared_network AS n "
+      "LEFT JOIN dhcp4_options AS o ON o.scope_id = 4 AND n.name = o.shared_network_name "
       "WHERE n.name = ? "
-      "ORDER BY n.id" },
+      "ORDER BY n.id, o.option_id" },
 
     // Select all shared networks.
     { MySqlConfigBackendDHCPv4Impl::GET_ALL_SHARED_NETWORKS4,
@@ -934,8 +1273,21 @@ TaggedStatementArray tagged_statements = { {
       "  n.require_client_classes,"
       "  n.reservation_mode,"
       "  n.user_context,"
-      "  n.valid_lifetime "
+      "  n.valid_lifetime,"
+      "  o.option_id,"
+      "  o.code,"
+      "  o.value,"
+      "  o.formatted_value,"
+      "  o.space,"
+      "  o.persistent,"
+      "  o.dhcp4_subnet_id,"
+      "  o.scope_id,"
+      "  o.user_context,"
+      "  o.shared_network_name,"
+      "  o.pool_id,"
+      "  o.modification_ts "
       "FROM dhcp4_shared_network AS n "
+      "LEFT JOIN dhcp4_options AS o ON o.scope_id = 4 AND n.name = o.shared_network_name "
       "ORDER BY n.id" },
 
     // Select modified shared networks.
@@ -953,8 +1305,21 @@ TaggedStatementArray tagged_statements = { {
       "  n.require_client_classes,"
       "  n.reservation_mode,"
       "  n.user_context,"
-      "  n.valid_lifetime "
+      "  n.valid_lifetime,"
+      "  o.option_id,"
+      "  o.code,"
+      "  o.value,"
+      "  o.formatted_value,"
+      "  o.space,"
+      "  o.persistent,"
+      "  o.dhcp4_subnet_id,"
+      "  o.scope_id,"
+      "  o.user_context,"
+      "  o.shared_network_name,"
+      "  o.pool_id,"
+      "  o.modification_ts "
       "FROM dhcp4_shared_network AS n "
+      "LEFT JOIN dhcp4_options AS o ON o.scope_id = 4 AND n.name = o.shared_network_name "
       "WHERE n.modification_ts > ? "
       "ORDER BY n.id" },
 
@@ -1008,6 +1373,46 @@ TaggedStatementArray tagged_statements = { {
       "WHERE modification_ts > ? "
       "ORDER BY d.id" },
 
+    // Retrieves an option for a given subnet, option code and space.
+    { MySqlConfigBackendDHCPv4Impl::GET_OPTION4_SUBNET_ID_CODE_SPACE,
+      "SELECT"
+      "  option_id,"
+      "  code,"
+      "  value,"
+      "  formatted_value,"
+      "  space,"
+      "  persistent,"
+      "  dhcp4_subnet_id,"
+      "  scope_id,"
+      "  user_context,"
+      "  shared_network_name,"
+      "  pool_id,"
+      "  modification_ts "
+      "FROM dhcp4_options "
+      "WHERE scope_id = 1 AND dhcp4_subnet_id = ? AND"
+      "  code = ? AND space = ? "
+      "ORDER BY option_id" },
+
+    // Retrieves an option for a given shared network, option code and space.
+    { MySqlConfigBackendDHCPv4Impl::GET_OPTION4_SHARED_NETWORK_CODE_SPACE,
+      "SELECT"
+      "  option_id,"
+      "  code,"
+      "  value,"
+      "  formatted_value,"
+      "  space,"
+      "  persistent,"
+      "  dhcp4_subnet_id,"
+      "  scope_id,"
+      "  user_context,"
+      "  shared_network_name,"
+      "  pool_id,"
+      "  modification_ts "
+      "FROM dhcp4_options "
+      "WHERE scope_id = 4 AND shared_network_name = ? AND"
+      "  code = ? AND space = ? "
+      "ORDER BY option_id" },
+
     // Insert a subnet.
     { MySqlConfigBackendDHCPv4Impl::INSERT_SUBNET4,
       "INSERT INTO dhcp4_subnet("
@@ -1074,6 +1479,23 @@ TaggedStatementArray tagged_statements = { {
       "user_context"
       ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" },
 
+    // Insert subnet specific option.
+    { MySqlConfigBackendDHCPv4Impl::INSERT_OPTION4,
+      "INSERT INTO dhcp4_options ("
+      "code,"
+      "value,"
+      "formatted_value,"
+      "space,"
+      "persistent,"
+      "dhcp_client_class,"
+      "dhcp4_subnet_id,"
+      "scope_id,"
+      "user_context,"
+      "shared_network_name,"
+      "pool_id,"
+      "modification_ts"
+      ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" },
+
     // Update existing subnet.
     { MySqlConfigBackendDHCPv4Impl::UPDATE_SUBNET4,
       "UPDATE dhcp4_subnet SET"
@@ -1130,6 +1552,42 @@ TaggedStatementArray tagged_statements = { {
       "  user_context = ? "
       "WHERE code = ? AND space = ?" },
 
+    // Update existing subnet level option.
+    { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SUBNET_ID,
+      "UPDATE dhcp4_options SET"
+      "  code = ?,"
+      "  value = ?,"
+      "  formatted_value = ?,"
+      "  space = ?,"
+      "  persistent = ?,"
+      "  dhcp_client_class = ?,"
+      "  dhcp4_subnet_id = ?,"
+      "  scope_id = ?,"
+      "  user_context = ?,"
+      "  shared_network_name = ?,"
+      "  pool_id = ?,"
+      "  modification_ts = ? "
+      "WHERE scope_id = 1 AND dhcp4_subnet_id = ? AND code = ? AND space = ?"
+    },
+
+    // Update existing shared network level option.
+    { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_SHARED_NETWORK,
+      "UPDATE dhcp4_options SET"
+      "  code = ?,"
+      "  value = ?,"
+      "  formatted_value = ?,"
+      "  space = ?,"
+      "  persistent = ?,"
+      "  dhcp_client_class = ?,"
+      "  dhcp4_subnet_id = ?,"
+      "  scope_id = ?,"
+      "  user_context = ?,"
+      "  shared_network_name = ?,"
+      "  pool_id = ?,"
+      "  modification_ts = ? "
+      "WHERE scope_id = 4 AND shared_network_name = ? AND code = ? AND space = ?"
+    },
+
     // Delete subnet by id.
     { MySqlConfigBackendDHCPv4Impl::DELETE_SUBNET4_ID,
       "DELETE FROM dhcp4_subnet "
@@ -1165,7 +1623,29 @@ TaggedStatementArray tagged_statements = { {
 
     // Delete all option definitions.
     { MySqlConfigBackendDHCPv4Impl::DELETE_ALL_OPTION_DEFS4,
-      "DELETE FROM dhcp4_option_def" }
+      "DELETE FROM dhcp4_option_def" },
+
+    // Delete single option from a subnet.
+    { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SUBNET_ID,
+      "DELETE FROM dhcp4_options "
+      "WHERE scope_id = 1 AND dhcp4_subnet_id = ?"
+      "  AND code = ? AND space = ?" },
+
+    // Delete single option from a shared network.
+    { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SHARED_NETWORK,
+      "DELETE FROM dhcp4_options "
+      "WHERE scope_id = 4 AND shared_network_name = ?"
+      "  AND code = ? AND space = ?" },
+
+    // Delete options belonging to a subnet.
+    { MySqlConfigBackendDHCPv4Impl::DELETE_OPTIONS4_SUBNET_ID,
+      "DELETE FROM dhcp4_options "
+      "WHERE scope_id = 1 AND dhcp4_subnet_id = ?" },
+
+    // Delete options belonging to a shared_network.
+    { MySqlConfigBackendDHCPv4Impl::DELETE_OPTIONS4_SHARED_NETWORK,
+      "DELETE FROM dhcp4_options "
+      "WHERE scope_id = 4 AND shared_network_name = ?" }
 }
 };
 
@@ -1302,20 +1782,28 @@ MySqlConfigBackendDHCPv4::createUpdateOptionDef4(const ServerSelector& server_se
 
 void
 MySqlConfigBackendDHCPv4::createUpdateOption4(const ServerSelector& /* server_selector */,
-                                              const OptionPtr& /* option */) {
+                                              const OptionDescriptorPtr& /* option */) {
 }
 
 void
-MySqlConfigBackendDHCPv4::createUpdateOption4(const ServerSelector& /* server_selector */,
-                                              const SubnetID& /* subnet_id */,
-                                              const OptionPtr& /* option */) {
+MySqlConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+                                              const std::string& shared_network_name,
+                                              const OptionDescriptorPtr& option) {
+    impl_->createUpdateOption4(selector, shared_network_name, option);
+}
+
+void
+MySqlConfigBackendDHCPv4::createUpdateOption4(const ServerSelector& selector,
+                                              const SubnetID& subnet_id,
+                                              const OptionDescriptorPtr& option) {
+    impl_->createUpdateOption4(selector, subnet_id, option);
 }
 
 void
 MySqlConfigBackendDHCPv4::createUpdateOption4(const ServerSelector& /* server_selector */,
                                               const asiolink::IOAddress& /* pool_start_address */,
                                               const asiolink::IOAddress& /* pool_end_address */,
-                                              const OptionPtr& /* option */) {
+                                              const OptionDescriptorPtr& /* option */) {
 }
 
 void
@@ -1380,11 +1868,19 @@ MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector
 }
 
 uint64_t
-MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& /* server_selector */,
-                                        const SubnetID& /* subnet_id */,
-                                        const uint16_t /* code */,
-                                        const std::string& /* space */) {
-    return (0);
+MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector& server_selector,
+                                        const std::string& shared_network_name,
+                                        const uint16_t code,
+                                        const std::string& space) {
+    impl_->deleteOption4(selector, shared_network_name, code, space);
+}
+
+void
+MySqlConfigBackendDHCPv4::deleteOption4(const ServerSelector&  selector,
+                                        const SubnetID& subnet_id,
+                                        const uint16_t code,
+                                        const std::string& space) {
+    impl_->deleteOption4(selector, subnet_id, code, space);
 }
 
 uint64_t
index 52d0539099c8b0792da40a838d794fe1bc69a9a5..09ee3d48d497d29a0455a0f1c82f160532ce2e76 100644 (file)
@@ -169,7 +169,18 @@ public:
     /// @param option Option to be added or updated.
     virtual void
     createUpdateOption4(const db::ServerSelector& server_selector,
-                        const OptionPtr& option);
+                        const OptionDescriptorPtr& option);
+
+    /// @brief Creates or updates shared network level option.
+    ///
+    /// @param selector Server selector.
+    /// @param shared_network_name Name of a shared network to which option
+    /// belongs.
+    /// @param option Option to be added or updated.
+    virtual void
+    createUpdateOption4(const db::ServerSelector& selector,
+                        const std::string& shared_network_name,
+                        const OptionDescriptorPtr& option);
 
     /// @brief Creates or updates subnet level option.
     ///
@@ -179,7 +190,7 @@ public:
     virtual void
     createUpdateOption4(const db::ServerSelector& server_selector,
                         const SubnetID& subnet_id,
-                        const OptionPtr& option);
+                        const OptionDescriptorPtr& option);
 
     /// @brief Creates or updates pool level option.
     ///
@@ -193,7 +204,7 @@ public:
     createUpdateOption4(const db::ServerSelector& server_selector,
                         const asiolink::IOAddress& pool_start_address,
                         const asiolink::IOAddress& pool_end_address,
-                        const OptionPtr& option);
+                        const OptionDescriptorPtr& option);
 
     /// @brief Creates or updates global string parameter.
     ///
@@ -282,6 +293,19 @@ public:
     deleteOption4(const db::ServerSelector& server_selector, const uint16_t code,
                   const std::string& space);
 
+    /// @brief Deletes shared network level option.
+    ///
+    /// @param selector Server selector.
+    /// @param shared_network_name Name of the shared network which deleted
+    /// option belongs to
+    /// @param code Code of the deleted option.
+    /// @param space Option space of the deleted option.
+    virtual void
+    deleteOption4(const db::ServerSelector& selector,
+                  const std::string& shared_network_name,
+                  const uint16_t code,
+                  const std::string& space);
+
     /// @brief Deletes subnet level option.
     ///
     /// @param server_selector Server selector.
index 51e8f5990f44371cf1c5be71583bf6ec0183a3f2..0d953956e66296974350e28634df675923b9475b 100644 (file)
@@ -6,6 +6,11 @@
 
 #include <mysql_cb_impl.h>
 #include <asiolink/io_address.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_space.h>
+#include <util/buffer.h>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
 #include <mysql.h>
 #include <mysqld_error.h>
 #include <cstdint>
@@ -13,6 +18,7 @@
 
 using namespace isc::data;
 using namespace isc::db;
+using namespace isc::util;
 
 namespace isc {
 namespace dhcp {
@@ -162,6 +168,100 @@ MySqlConfigBackendImpl::getOptionDefs(const int index,
     });
 }
 
+void
+MySqlConfigBackendImpl::getOptions(const int index,
+                                   const db::MySqlBindingCollection& in_bindings,
+                                   const Option::Universe& universe,
+                                   OptionContainer& options) {
+    // Create output bindings. The order must match that in the prepared
+    // statement.
+    MySqlBindingCollection out_bindings = {
+        MySqlBinding::createInteger<uint64_t>(), // option_id
+        MySqlBinding::createInteger<uint8_t>(), // code
+        MySqlBinding::createBlob(65536), // value
+        MySqlBinding::createString(8192), // formatted_value
+            MySqlBinding::createString(128), // space
+        MySqlBinding::createInteger<uint8_t>(), // persistent
+        MySqlBinding::createInteger<uint32_t>(), // dhcp4_subnet_id
+        MySqlBinding::createInteger<uint8_t>(), // scope_id
+        MySqlBinding::createString(65536), // user_context
+        MySqlBinding::createString(128), // shared_network_name
+        MySqlBinding::createInteger<uint64_t>(), // pool_id
+        MySqlBinding::createTimestamp() //modification_ts
+    };
+
+    uint64_t last_option_id = 0;
+
+    conn_.selectQuery(index, in_bindings, out_bindings,
+                      [this, universe, &options, &last_option_id]
+                      (MySqlBindingCollection& out_bindings) {
+        // Parse option.
+        if (!out_bindings[0]->amNull() &&
+            ((last_option_id == 0) ||
+             (last_option_id < out_bindings[0]->getInteger<uint64_t>()))) {
+            last_option_id = out_bindings[0]->getInteger<uint64_t>();
+
+            OptionDescriptorPtr desc = processOptionRow(universe, out_bindings.begin());
+            if (desc) {
+                options.push_back(*desc);
+            }
+        }
+    });
+}
+
+OptionDescriptorPtr
+MySqlConfigBackendImpl::processOptionRow(const Option::Universe& universe,
+                                         MySqlBindingCollection::iterator first_binding) {
+    std::string space = (*(first_binding + 4))->getStringOrDefault(DHCP4_OPTION_SPACE);
+    uint16_t code = (*(first_binding + 1))->getInteger<uint8_t>();
+
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
+    if (!def && (space != DHCP4_OPTION_SPACE) && (space != DHCP6_OPTION_SPACE)) {
+        uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
+        if (vendor_id > 0) {
+            def = LibDHCP::getVendorOptionDef(universe, vendor_id, code);
+        }
+    }
+
+    if (!def) {
+        def = LibDHCP::getRuntimeOptionDef(space, code);
+    }
+
+    std::vector<uint8_t> blob;
+    if (!(*(first_binding + 2))->amNull()) {
+        blob = (*(first_binding + 2))->getBlob();
+    }
+    OptionBuffer buf(blob.begin(), blob.end());
+
+    std::string formatted_value = (*(first_binding + 3))->getStringOrDefault("");
+
+    OptionPtr option;
+    if (!def) {
+        option.reset(new Option(universe, code, buf.begin(), buf.end()));
+
+    } else {
+        if (formatted_value.empty()) {
+            option = def->optionFactory(universe, code, buf.begin(),
+                                        buf.end());
+        } else {
+            // Spit the value specified in comma separated values
+            // format.
+            std::vector<std::string> split_vec;
+            boost::split(split_vec, formatted_value, boost::is_any_of(","));
+            option = def->optionFactory(universe, code, split_vec);
+        }
+    }
+
+    bool persistent = static_cast<bool>((*(first_binding + 5))->getIntegerOrDefault<uint8_t>(0));
+
+    OptionDescriptorPtr desc(new OptionDescriptor(option, persistent, formatted_value));
+    desc->space_name_ = space;
+
+    desc->setModificationTime((*(first_binding + 11))->getTimestamp());
+
+    return (desc);
+}
+
 MySqlBindingPtr
 MySqlConfigBackendImpl::createInputRelayBinding(const NetworkPtr& network) {
     ElementPtr relay_element = Element::createList();
@@ -192,5 +292,22 @@ MySqlConfigBackendImpl::createInputRequiredClassesBinding(const NetworkPtr& netw
             MySqlBinding::createNull());
 }
 
+MySqlBindingPtr
+MySqlConfigBackendImpl::createOptionValueBinding(const OptionDescriptorPtr& option) {
+    OptionPtr opt = option->option_;
+    if (option->formatted_value_.empty() && (opt->len() > opt->getHeaderLen())) {
+        OutputBuffer buf(opt->len());
+        opt->pack(buf);
+        const char* buf_ptr = static_cast<const char*>(buf.getData());
+        std::vector<uint8_t> blob(buf_ptr + opt->getHeaderLen(),
+                                  buf_ptr + buf.getLength());
+        return (MySqlBinding::createBlob(blob.begin(), blob.end()));
+
+    }
+
+    return (MySqlBinding::createNull());
+}
+
+
 } // end of namespace isc::dhcp
 } // end of namespace isc
index ffb9c9c887a1681ee5a4ecabb2e5c3974541be58..dff9de82820f33bb9777a374655124b818de876f 100644 (file)
@@ -8,7 +8,9 @@
 #define MYSQL_CONFIG_BACKEND_IMPL_H
 
 #include <database/database_connection.h>
+#include <dhcp/option.h>
 #include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/network.h>
 #include <mysql/mysql_binding.h>
 #include <mysql/mysql_connection.h>
@@ -64,6 +66,47 @@ public:
                        const db::MySqlBindingCollection& in_bindings,
                        OptionDefContainer& option_defs);
 
+    /// @brief Sends query to the database to retrieve multiple options.
+    ///
+    /// Query should order by option_id.
+    ///
+    /// @param index Index of the query to be used.
+    /// @param in_bindings Input bindings specifying selection criteria. The
+    /// size of the bindings collection must match the number of placeholders
+    /// in the prepared statement. The input bindings collection must be empty
+    /// if the query contains no WHERE clause.
+    /// @param universe Option universe, i.e. V4 or V6.
+    /// @param [out] options Reference to the container where fetched options
+    /// will be inserted.
+    void getOptions(const int index,
+                    const db::MySqlBindingCollection& in_bindings,
+                    const Option::Universe& universe,
+                    OptionContainer& options);
+
+    /// @brief Returns DHCP option instance from output bindings.
+    ///
+    /// The following is the expected order of columns specified in the SELECT
+    /// query:
+    /// - option_id,
+    /// - code,
+    /// - value,
+    /// - formatted_value,
+    /// - space,
+    /// - persistent,
+    /// - dhcp4_subnet_id,
+    /// - scope_id,
+    /// - user_context,
+    /// - shared_network_name,
+    /// - pool_id,
+    /// - modification_ts
+    ///
+    /// @param universe V4 or V6.
+    /// @param first_binding Iterator of the output binding containing
+    /// option_id.
+    OptionDescriptorPtr
+    processOptionRow(const Option::Universe& universe,
+                     db::MySqlBindingCollection::iterator first_binding);
+
     /// @brief Creates input binding for relay addresses.
     ///
     /// @param network Pointer to a shared network or subnet for which binding
@@ -95,6 +138,13 @@ public:
                 db::MySqlBinding::createNull());
     }
 
+    /// @brief Creates input binding for option value parameter.
+    ///
+    /// @param option Option descriptor holding option for which binding is to
+    /// be created.
+    /// @return Pointer to the binding (possibly null binding if formatted
+    /// value is non-empty.
+    db::MySqlBindingPtr createOptionValueBinding(const OptionDescriptorPtr& option);
 
     /// @brief Represents connection to the MySQL database.
     db::MySqlConnection conn_;
index 747d2c3ad0b1cf2a8ce38e1c8db7b75433c677e5..4bc925598d7cf11384df090618a7728feff88a3b 100644 (file)
@@ -32,7 +32,8 @@ mysql_cb_unittests_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
 
 mysql_cb_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 
-mysql_cb_unittests_LDADD  = $(top_builddir)/src/hooks/dhcp/mysql_cb/libmysqlcb.la
+mysql_cb_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
+mysql_cb_unittests_LDADD += $(top_builddir)/src/hooks/dhcp/mysql_cb/libmysqlcb.la
 mysql_cb_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
 mysql_cb_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
 mysql_cb_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
index cd13eaffbe612854fec444e8974b722335ae0281..7ccb133840ad9e6aa1299a23173fe2f7ee2b45a2 100644 (file)
@@ -5,10 +5,16 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+#include <mysql_cb_dhcp4.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
-#include <mysql_cb_dhcp4.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
 #include <mysql/testutils/mysql_schema.h>
 #include <boost/shared_ptr.hpp>
 #include <gtest/gtest.h>
@@ -19,11 +25,12 @@ using namespace isc::db;
 using namespace isc::db::test;
 using namespace isc::data;
 using namespace isc::dhcp;
+using namespace isc::dhcp::test;
 
 namespace {
 
 /// @brief Test fixture class for @c MySqlConfigBackendDHCPv4.
-class MySqlConfigBackendDHCPv4Test : public ::testing::Test {
+class MySqlConfigBackendDHCPv4Test : public GenericBackendTest {
 public:
 
     /// @brief Constructor.
@@ -49,6 +56,7 @@ public:
         }
 
         // Create test data.
+        initTestOptions();
         initTestSubnets();
         initTestSharedNetworks();
         initTestOptionDefs();
@@ -96,6 +104,19 @@ public:
         Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.50"), IOAddress("192.0.2.60")));
         subnet->addPool(pool2);
 
+        // Add several options to the subnet.
+        subnet->getCfgOption()->add(test_options_[0]->option_,
+                                    test_options_[0]->persistent_,
+                                    test_options_[0]->space_name_);
+
+        subnet->getCfgOption()->add(test_options_[1]->option_,
+                                    test_options_[1]->persistent_,
+                                    test_options_[1]->space_name_);
+
+        subnet->getCfgOption()->add(test_options_[2]->option_,
+                                    test_options_[2]->persistent_,
+                                    test_options_[2]->space_name_);
+
         test_subnets_.push_back(subnet);
 
         // Adding another subnet with the same subnet id to test
@@ -137,6 +158,19 @@ public:
         shared_network->setContext(user_context);
         shared_network->setValid(5555);
 
+        // Add several options to the shared network.
+        shared_network->getCfgOption()->add(test_options_[2]->option_,
+                                            test_options_[2]->persistent_,
+                                            test_options_[2]->space_name_);
+
+        shared_network->getCfgOption()->add(test_options_[3]->option_,
+                                            test_options_[3]->persistent_,
+                                            test_options_[3]->space_name_);
+
+        shared_network->getCfgOption()->add(test_options_[4]->option_,
+                                            test_options_[4]->persistent_,
+                                            test_options_[4]->space_name_);
+
         test_networks_.push_back(shared_network);
 
         // Adding another shared network called "level1" to test
@@ -178,6 +212,58 @@ public:
         test_option_defs_.push_back(option_def);
     }
 
+    /// @brief Creates several DHCP options used in tests.
+    void initTestOptions() {
+        ElementPtr user_context = Element::createMap();
+        user_context->set("foo", Element::create("bar"));
+
+        OptionDefSpaceContainer defs;
+
+        OptionDescriptor desc =
+            createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+                                       true, false, "my-boot-file");
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        desc.setContext(user_context);
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+                                         false, true, 64);
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createOption<OptionUint32>(Option::V4, 1, false, false, 312131),
+        desc.space_name_ = "vendor-encapsulated-options";
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createAddressOption<Option4AddrLst>(254, true, true,
+                                                   "192.0.2.3");
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createEmptyOption(Option::V4, 1, true);
+        desc.space_name_ = "isc";
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createAddressOption<Option4AddrLst>(2, false, true, "10.0.0.5",
+                                                   "10.0.0.3", "10.0.3.4");
+        desc.space_name_ = "isc";
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        // Add definitions for DHCPv4 non-standard options.
+        defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+                         "vendor-encapsulated-1", 1, "uint32")),
+                     "vendor-encapsulated-options");
+        defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+                         "option-254", 254, "ipv4-address", true)),
+                     DHCP4_OPTION_SPACE);
+        defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "empty")), "isc");
+        defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "ipv4-address", true)),
+                     "isc");
+
+        // Register option definitions.
+        LibDHCP::setRuntimeOptionDefs(defs);
+    }
+
     /// @brief Initialize posix time values used in tests.
     void initTimestamps() {
         // Current time minus 1 hour to make sure it is in the past.
@@ -198,6 +284,9 @@ public:
     /// @brief Holds pointers to option definitions used in tests.
     std::vector<OptionDefinitionPtr> test_option_defs_;
 
+    /// @brief Holds pointers to options used in tests.
+    std::vector<OptionDescriptorPtr> test_options_;
+
     /// @brief Holds timestamp values used in tests.
     std::map<std::string, boost::posix_time::ptime> timestamps_;
 
@@ -565,6 +654,104 @@ TEST_F(MySqlConfigBackendDHCPv4Test, getModifiedOptionDefinitions4) {
     ASSERT_TRUE(option_defs.empty());
 }
 
+// This test verifies that subnet level option can be added, updated and
+// deleted.
+TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSubnetOption4) {
+    // Insert new subnet.
+    Subnet4Ptr subnet = test_subnets_[1];
+    cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(), subnet);
+
+    // Fetch this subnet by subnet identifier.
+    Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+                                                    subnet->getID());
+    ASSERT_TRUE(returned_subnet);
+
+    OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+    cbptr_->createUpdateOption4(ServerSelector::UNASSIGNED(), subnet->getID(),
+                                opt_boot_file_name);
+
+    returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+                                         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_);
+    EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
+
+    opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+    cbptr_->createUpdateOption4(ServerSelector::UNASSIGNED(), subnet->getID(),
+                                opt_boot_file_name);
+
+    returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+                                         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_);
+    EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
+
+    cbptr_->deleteOption4(ServerSelector::UNASSIGNED(), subnet->getID(),
+                          opt_boot_file_name->option_->getType(),
+                          opt_boot_file_name->space_name_);
+
+    returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+                                         subnet->getID());
+    ASSERT_TRUE(returned_subnet);
+
+    EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
+}
+
+// This test verifies that shared network level option can be added,
+// updated and deleted.
+TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSharedNetworkOption4) {
+    // Insert new shared network.
+    SharedNetwork4Ptr shared_network = test_networks_[1];
+    cbptr_->createUpdateSharedNetwork4(ServerSelector::UNASSIGNED(),
+                                       shared_network);
+
+    // Fetch this shared network by name.
+    SharedNetwork4Ptr returned_network =
+        cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
+                                  shared_network->getName());
+    ASSERT_TRUE(returned_network);
+
+    OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+    cbptr_->createUpdateOption4(ServerSelector::UNASSIGNED(),
+                                shared_network->getName(),
+                                opt_boot_file_name);
+
+    returned_network = cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
+                                                shared_network->getName());
+    ASSERT_TRUE(returned_network);
+
+    OptionDescriptor returned_opt_boot_file_name =
+        returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+    ASSERT_TRUE(returned_opt_boot_file_name.option_);
+    EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
+
+    opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+    cbptr_->createUpdateOption4(ServerSelector::UNASSIGNED(),
+                                shared_network->getName(),
+                                opt_boot_file_name);
+
+    returned_network = cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
+                                                 shared_network->getName());
+    ASSERT_TRUE(returned_network);
+    returned_opt_boot_file_name =
+        returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+    ASSERT_TRUE(returned_opt_boot_file_name.option_);
+    EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
+
+    cbptr_->deleteOption4(ServerSelector::UNASSIGNED(),
+                          shared_network->getName(),
+                          opt_boot_file_name->option_->getType(),
+                          opt_boot_file_name->space_name_);
+    returned_network = cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
+                                                 shared_network->getName());
+    ASSERT_TRUE(returned_network);
+    EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
+}
 
 
 }
index 49e4fe9d35e1fbc69cddfa1b5ec67f5f47f50162..1ef4d9061ac806ace2991d1d45462b7aa78e88ae 100644 (file)
@@ -22,8 +22,9 @@ namespace dhcp {
 
 bool
 OptionDescriptor::equals(const OptionDescriptor& other) const {
-    return (persistent_ == other.persistent_ &&
-            formatted_value_ == other.formatted_value_ &&
+    return ((persistent_ == other.persistent_) &&
+            (formatted_value_ == other.formatted_value_) &&
+            (space_name_ == other.space_name_) &&
             option_->equals(other.option_));
 }
 
index d29914f888f0bbb314bfa345678431146e5c0140..a3fa37066aee70e9860b21330659bc0e37068c83 100644 (file)
@@ -10,6 +10,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_space_container.h>
 #include <cc/cfg_to_element.h>
+#include <cc/stamped_element.h>
 #include <cc/user_context.h>
 #include <dhcpsrv/key_from_key.h>
 #include <boost/multi_index_container.hpp>
@@ -31,7 +32,7 @@ namespace dhcp {
 /// for this option. This information comprises whether this option is sent
 /// to DHCP client only on request (persistent = false) or always
 /// (persistent = true).
-class OptionDescriptor : public data::UserContext {
+class OptionDescriptor : public data::StampedElement, public data::UserContext {
 public:
     /// @brief Option instance.
     OptionPtr option_;
@@ -57,6 +58,17 @@ public:
     /// for the option which carries IPv6 address, a number and a text.
     std::string formatted_value_;
 
+    /// @brief Option space name.
+    ///
+    /// Options are associated with option spaces. Typically, such association
+    /// is made when the option is stored in the @c OptionContainer. However,
+    /// in some cases it is also required to associate option with the particular
+    /// option space outside of the container. In particular, when the option
+    /// is fetched from a database. The database configuration backend will
+    /// set option space upon return of the option. In other cases this value
+    /// won't be set.
+    std::string space_name_;
+
     /// @brief Constructor.
     ///
     /// @param opt option
@@ -68,7 +80,8 @@ public:
                      const std::string& formatted_value = "",
                      data::ConstElementPtr user_context = data::ConstElementPtr())
         : option_(opt), persistent_(persist),
-          formatted_value_(formatted_value) {
+          formatted_value_(formatted_value),
+          space_name_() {
         setContext(user_context);
     };
 
@@ -77,14 +90,15 @@ public:
     /// @param persist if true option is always sent.
     OptionDescriptor(bool persist)
         : option_(OptionPtr()), persistent_(persist),
-          formatted_value_() {};
+          formatted_value_(), space_name_() {};
 
     /// @brief Constructor.
     ///
     /// @param desc descriptor
     OptionDescriptor(const OptionDescriptor& desc)
         : option_(desc.option_), persistent_(desc.persistent_),
-          formatted_value_(desc.formatted_value_) {
+          formatted_value_(desc.formatted_value_),
+          space_name_(desc.space_name_) {
         setContext(desc.getContext());
     };
 
index af318d0dcf8d92bb14ad0185e6e6382834f60bff..69632fa63055cab213f2329ff11cc3864f2bc9f0 100644 (file)
@@ -11,6 +11,7 @@
 #include <database/server_selector.h>
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <util/optional_value.h>
@@ -170,7 +171,18 @@ public:
     /// @param option Option to be added or updated.
     virtual void
     createUpdateOption4(const db::ServerSelector& server_selector,
-                        const OptionPtr& option) = 0;
+                        const OptionDescriptorPtr& option) = 0;
+
+    /// @brief Creates or updates shared network level option.
+    ///
+    /// @param selector Server selector.
+    /// @param shared_network_name Name of a shared network to which option
+    /// belongs.
+    /// @param option Option to be added or updated.
+    virtual void
+    createUpdateOption4(const db::ServerSelector& selector,
+                        const std::string& shared_network_name,
+                        const OptionDescriptorPtr& option) = 0;
 
     /// @brief Creates or updates subnet level option.
     ///
@@ -180,7 +192,7 @@ public:
     virtual void
     createUpdateOption4(const db::ServerSelector& server_selector,
                         const SubnetID& subnet_id,
-                        const OptionPtr& option) = 0;
+                        const OptionDescriptorPtr& option) = 0;
 
     /// @brief Creates or updates pool level option.
     ///
@@ -194,7 +206,7 @@ public:
     createUpdateOption4(const db::ServerSelector& server_selector,
                         const asiolink::IOAddress& pool_start_address,
                         const asiolink::IOAddress& pool_end_address,
-                        const OptionPtr& option) = 0;
+                        const OptionDescriptorPtr& option) = 0;
 
     /// @brief Creates or updates global string parameter.
     ///
@@ -283,6 +295,19 @@ public:
     deleteOption4(const db::ServerSelector& server_selector, const uint16_t code,
                   const std::string& space) = 0;
 
+    /// @brief Deletes shared network level option.
+    ///
+    /// @param selector Server selector.
+    /// @param shared_network_name Name of the shared network which option
+    /// belongs to.
+    /// @param code Code of the option to be deleted.
+    /// @param space Option space of the option to be deleted.
+    virtual void
+    deleteOption4(const db::ServerSelector& selector,
+                  const std::string& shared_network_name,
+                  const uint16_t code,
+                  const std::string& space) = 0;
+
     /// @brief Deletes subnet level option.
     ///
     /// @param server_selector Server selector.
index 79a928763408b0ea274db487e13e6ce15d1b94e1..cd89e99ad649437ab93bc193c0c933ebabb1e588 100644 (file)
@@ -185,17 +185,28 @@ ConfigBackendPoolDHCPv4::createUpdateOptionDef4(const db::BackendSelector& backe
 void
 ConfigBackendPoolDHCPv4::createUpdateOption4(const db::BackendSelector& backend_selector,
                                              const db::ServerSelector& server_selector,
-                                             const OptionPtr& option) {
+                                             const OptionDescriptorPtr& option) {
     createUpdateDeleteProperty<void, const OptionPtr&>
         (&ConfigBackendDHCPv4::createUpdateOption4, backend_selector,
          server_selector, option);
 }
 
+void
+ConfigBackendPoolDHCPv4::createUpdateOption4(const db::BackendSelector& backend_selector,
+                                             const db::ServerSelector& server_selector,
+                                             const std::string& shared_network_name,
+                                             const OptionDescriptorPtr& option) {
+    createUpdateDeleteProperty<const std::string&, const OptionDescriptorPtr&>
+        (&ConfigBackendDHCPv4::createUpdateOption4, backend_selector,
+         server_selector, shared_network_name, option);
+}
+
+
 void
 ConfigBackendPoolDHCPv4::createUpdateOption4(const db::BackendSelector& backend_selector,
                                              const db::ServerSelector& server_selector,
                                              const SubnetID& subnet_id,
-                                             const OptionPtr& option) {
+                                             const OptionDescriptorPtr& option) {
     createUpdateDeleteProperty<void, const SubnetID&, const OptionPtr&>
         (&ConfigBackendDHCPv4::createUpdateOption4, backend_selector,
          server_selector, subnet_id, option);
@@ -206,8 +217,9 @@ ConfigBackendPoolDHCPv4::createUpdateOption4(const db::BackendSelector& backend_
                                              const db::ServerSelector& server_selector,
                                              const IOAddress& pool_start_address,
                                              const IOAddress& pool_end_address,
-                                             const OptionPtr& option) {
+                                             const OptionDescriptorPtr& option) {
     createUpdateDeleteProperty<void, const IOAddress&, const IOAddress&, const OptionPtr&>
+                               const OptionDescriptorPtr&>
         (&ConfigBackendDHCPv4::createUpdateOption4, backend_selector,
          server_selector, pool_start_address, pool_end_address, option);
 }
@@ -300,6 +312,17 @@ ConfigBackendPoolDHCPv4::deleteOption4(const db::BackendSelector& backend_select
              code, space));
 }
 
+void
+ConfigBackendPoolDHCPv4::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) {
+    createUpdateDeleteProperty<const std::string&, uint16_t, const std::string&>
+        (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector,
+         shared_network_name, code, space);
+}
+
 uint64_t
 ConfigBackendPoolDHCPv4::deleteOption4(const db::BackendSelector& backend_selector,
                                        const db::ServerSelector& server_selector,
index 3ac592dfeb5f708bd222b64567a05d315d537366..21bc3504d650c1931d175e8b83228e4849d3a054 100644 (file)
@@ -12,6 +12,7 @@
 #include <database/server_selector.h>
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/config_backend_dhcp4.h>
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
@@ -204,7 +205,20 @@ public:
     virtual void
     createUpdateOption4(const db::BackendSelector& backend_selector,
                         const db::ServerSelector& server_selector,
-                        const OptionPtr& option);
+                        const OptionDescriptorPtr& option);
+
+    /// @brief Creates or updates shared network level option.
+    ///
+    /// @param backend_selector Backend selector.
+    /// @param server_selector Server selector.
+    /// @param shared_network_name Name of a shared network to which option
+    /// belongs.
+    /// @param option Option to be added or updated.
+    virtual void
+    createUpdateOption4(const db::BackendSelector& backend_selector,
+                        const db::ServerSelector& server_selector,
+                        const std::string& shared_network_name,
+                        const OptionDescriptorPtr& option);
 
     /// @brief Creates or updates subnet level option.
     ///
@@ -216,7 +230,7 @@ public:
     createUpdateOption4(const db::BackendSelector& backend_selector,
                         const db::ServerSelector& server_selector,
                         const SubnetID& subnet_id,
-                        const OptionPtr& option);
+                        const OptionDescriptorPtr& option);
 
     /// @brief Creates or updates pool level option.
     ///
@@ -232,7 +246,7 @@ public:
                         const db::ServerSelector& server_selector,
                         const asiolink::IOAddress& pool_start_address,
                         const asiolink::IOAddress& pool_end_address,
-                        const OptionPtr& option);
+                        const OptionDescriptorPtr& option);
 
     /// @brief Creates or updates global string parameter.
     ///
@@ -344,6 +358,21 @@ public:
                   const uint16_t code,
                   const std::string& space);
 
+    /// @brief Deletes shared network level option.
+    ///
+    /// @param backend_selector Backend selector.
+    /// @param selector Server selector.
+    /// @param shared_network_name Name of the shared network which option
+    /// belongs to.
+    /// @param code Code of the option to be deleted.
+    /// @param space Option space of the option to be deleted.
+    virtual void
+    deleteOption4(const db::BackendSelector& backend_selector,
+                  const db::ServerSelector& selector,
+                  const std::string& shared_network_name,
+                  const uint16_t code,
+                  const std::string& space) = 0;
+
     /// @brief Deletes subnet level option.
     ///
     /// @param backend_selector Backend selector.
index a56e0e6eb021a6eb9c0bd1e5daca5066ce0895f5..f5252365319dd56a94d101c4e3f449bbb23e5f23 100644 (file)
@@ -16,6 +16,7 @@ libdhcpsrvtest_la_SOURCES  = config_result_check.cc config_result_check.h
 libdhcpsrvtest_la_SOURCES += dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h
 libdhcpsrvtest_la_SOURCES += host_data_source_utils.cc host_data_source_utils.h
 libdhcpsrvtest_la_SOURCES += memory_host_data_source.cc memory_host_data_source.h
+libdhcpsrvtest_la_SOURCES += generic_backend_unittest.cc generic_backend_unittest.h
 libdhcpsrvtest_la_SOURCES += generic_host_data_source_unittest.cc generic_host_data_source_unittest.h
 libdhcpsrvtest_la_SOURCES += lease_file_io.cc lease_file_io.h
 
diff --git a/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc
new file mode 100644 (file)
index 0000000..bbb5363
--- /dev/null
@@ -0,0 +1,55 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_vendor.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+GenericBackendTest::GenericBackendTest() {
+    LibDHCP::clearRuntimeOptionDefs();
+}
+
+GenericBackendTest::~GenericBackendTest() {
+    LibDHCP::clearRuntimeOptionDefs();
+}
+
+OptionDescriptor
+GenericBackendTest::createEmptyOption(const Option::Universe& universe,
+                                      const uint16_t option_type,
+                                      const bool persist) const {
+    OptionPtr option(new Option(universe, option_type));
+    OptionDescriptor desc(option, persist);
+    return (desc);
+}
+
+OptionDescriptor
+GenericBackendTest::createVendorOption(const Option::Universe& universe,
+                                       const bool persist,
+                                       const bool formatted,
+                                       const uint32_t vendor_id) const {
+    OptionVendorPtr option(new OptionVendor(universe, vendor_id));
+
+    std::ostringstream s;
+    if (formatted) {
+        // Vendor id comprises vendor-id field, for which we need to
+        // assign a value in the textual (formatted) format.
+        s << vendor_id;
+    }
+
+    OptionDescriptor desc(option, persist, s.str());
+    return (desc);
+}
+
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/generic_backend_unittest.h
new file mode 100644 (file)
index 0000000..34a1b57
--- /dev/null
@@ -0,0 +1,199 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef GENERIC_BACKEND_UNITTEST_H
+#define GENERIC_BACKEND_UNITTEST_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/cfg_option.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <cstdint>
+#include <sstream>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class with utility functions for
+/// testing database backends.
+class GenericBackendTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    GenericBackendTest();
+
+    /// @brief Virtual destructor.
+    virtual ~GenericBackendTest();
+
+    /// @brief Creates an option descriptor holding an empty option.
+    ///
+    /// @param universe V4 or V6.
+    /// @param option_type Option type.
+    /// @param persist A boolean flag indicating if the option is always
+    /// returned to the client or only when requested.
+    ///
+    /// @return Descriptor holding an empty option.
+    OptionDescriptor createEmptyOption(const Option::Universe& universe,
+                                       const uint16_t option_type,
+                                       const bool persist) const;
+
+    /// @brief Creates an instance of the option for which it is possible to
+    /// specify universe, option type, persistence flag  and value in
+    /// the constructor.
+    ///
+    /// Examples of options that can be created using this function are:
+    /// - @ref OptionString
+    /// - different variants of @ref OptionInt.
+    ///
+    /// @param universe V4 or V6.
+    /// @param option_type Option type.
+    /// @param persist A boolean flag indicating if the option is always
+    /// returned to the client or only when requested.
+    /// @param formatted A boolean value selecting if the formatted option
+    /// value should be used (if true), or binary value (if false).
+    /// @param value Option value to be assigned to the option.
+    /// @tparam OptionType Class encapsulating the option.
+    /// @tparam DataType Option value data type.
+    ///
+    /// @return Descriptor holding an instance of the option created.
+    template<typename OptionType, typename DataType>
+    OptionDescriptor createOption(const Option::Universe& universe,
+                                  const uint16_t option_type,
+                                  const bool persist,
+                                  const bool formatted,
+                                  const DataType& value) const {
+        boost::shared_ptr<OptionType> option(new OptionType(universe, option_type,
+                                                            value));
+        std::ostringstream s;
+        if (formatted) {
+            // Using formatted option value. Convert option value to a
+            // textual format.
+            s << value;
+        }
+        OptionDescriptor desc(option, persist, s.str());
+        return (desc);
+    }
+
+    /// @brief Creates an instance of the option for which it is possible to
+    /// specify option type, persistence flag  and value in the constructor.
+    ///
+    /// Examples of options that can be created using this function are:
+    /// - @ref Option4AddrLst
+    /// - @ref Option6AddrLst
+    ///
+    /// @param option_type Option type.
+    /// @param persist A boolean flag indicating if the option is always
+    /// returned to the client or only when requested.
+    /// @param formatted A boolean value selecting if the formatted option
+    /// value should be used (if true), or binary value (if false).
+    /// @param value Option value to be assigned to the option.
+    /// @tparam OptionType Class encapsulating the option.
+    /// @tparam DataType Option value data type.
+    ///
+    /// @return Descriptor holding an instance of the option created.
+    template<typename OptionType, typename DataType>
+    OptionDescriptor createOption(const uint16_t option_type,
+                                  const bool persist,
+                                  const bool formatted,
+                                  const DataType& value) const {
+        boost::shared_ptr<OptionType> option(new OptionType(option_type, value));
+
+        std::ostringstream s;
+        if (formatted) {
+            // Using formatted option value. Convert option value to a
+            // textual format.
+            s << value;
+        }
+
+        OptionDescriptor desc(option, persist, s.str());
+        return (desc);
+    }
+
+    /// @brief Creates an instance of the option holding list of IP addresses.
+    ///
+    /// @param option_type Option type.
+    /// @param persist A boolean flag indicating if the option is always
+    /// returned to the client or only when requested.
+    /// @param formatted A boolean value selecting if the formatted option
+    /// value should be used (if true), or binary value (if false).
+    /// @param address1 First address to be included. If address is empty, it is
+    /// not included.
+    /// @param address2 Second address to be included. If address is empty, it
+    /// is not included.
+    /// @param address3 Third address to be included. If address is empty, it
+    /// is not included.
+    /// @tparam OptionType Class encapsulating the option.
+    ///
+    /// @return Descriptor holding an instance of the option created.
+    template<typename OptionType>
+    OptionDescriptor
+    createAddressOption(const uint16_t option_type,
+                        const bool persist,
+                        const bool formatted,
+                        const std::string& address1 = "",
+                        const std::string& address2 = "",
+                        const std::string& address3 = "") const {
+        std::ostringstream s;
+        // First address.
+        typename OptionType::AddressContainer addresses;
+        if (!address1.empty()) {
+            addresses.push_back(asiolink::IOAddress(address1));
+            if (formatted) {
+                s << address1;
+            }
+        }
+        // Second address.
+        if (!address2.empty()) {
+            addresses.push_back(asiolink::IOAddress(address2));
+            if (formatted) {
+                if (s.tellp() != std::streampos(0)) {
+                    s << ",";
+                }
+                s << address2;
+            }
+        }
+        // Third address.
+        if (!address3.empty()) {
+            addresses.push_back(asiolink::IOAddress(address3));
+            if (formatted) {
+                if (s.tellp() != std::streampos(0)) {
+                    s << ",";
+                }
+                s << address3;
+            }
+        }
+
+        boost::shared_ptr<OptionType> option(new OptionType(option_type,
+                                                            addresses));
+        OptionDescriptor desc(option, persist, s.str());
+        return (desc);
+    }
+
+    /// @brief Creates an instance of the vendor option.
+    ///
+    /// @param universe V4 or V6.
+    /// @param persist A boolean flag indicating if the option is always
+    /// returned to the client or only when requested.
+    /// @param formatted A boolean value selecting if the formatted option
+    /// value should be used (if true), or binary value (if false).
+    /// @param vendor_id Vendor identifier.
+    ///
+    /// @return Descriptor holding an instance of the option created.
+    OptionDescriptor createVendorOption(const Option::Universe& universe,
+                                        const bool persist,
+                                        const bool formatted,
+                                        const uint32_t vendor_id) const;
+};
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif
+
index ce14d9192f30ab550f9ea25a02cb486328535c29..b0549451994221716e279951773ba03d65a0319f 100644 (file)
@@ -43,12 +43,11 @@ namespace isc {
 namespace dhcp {
 namespace test {
 
-GenericHostDataSourceTest::GenericHostDataSourceTest() : hdsptr_() {
-    LibDHCP::clearRuntimeOptionDefs();
+GenericHostDataSourceTest::GenericHostDataSourceTest()
+    : GenericBackendTest(), hdsptr_() {
 }
 
 GenericHostDataSourceTest::~GenericHostDataSourceTest() {
-    LibDHCP::clearRuntimeOptionDefs();
     hdsptr_.reset();
 }
 
@@ -88,34 +87,6 @@ GenericHostDataSourceTest::DuidToHWAddr(const DuidPtr& duid) {
     return (HWAddrPtr(new HWAddr(duid->getDuid(), HTYPE_ETHER)));
 }
 
-
-OptionDescriptor
-GenericHostDataSourceTest::createEmptyOption(const Option::Universe& universe,
-                                             const uint16_t option_type,
-                                             const bool persist) const {
-    OptionPtr option(new Option(universe, option_type));
-    OptionDescriptor desc(option, persist);
-    return (desc);
-}
-
-OptionDescriptor
-GenericHostDataSourceTest::createVendorOption(const Option::Universe& universe,
-                                              const bool persist,
-                                              const bool formatted,
-                                              const uint32_t vendor_id) const {
-    OptionVendorPtr option(new OptionVendor(universe, vendor_id));
-
-    std::ostringstream s;
-    if (formatted) {
-        // Vendor id comprises vendor-id field, for which we need to
-        // assign a value in the textual (formatted) format.
-        s << vendor_id;
-    }
-
-    OptionDescriptor desc(option, persist, s.str());
-    return (desc);
-}
-
 void
 GenericHostDataSourceTest::addTestOptions(const HostPtr& host,
                                           const bool formatted,
index 46653bb38222156f63ef828935a8b7fec7f93c4e..935b8922c34d1c15db795ad3c6f512eac586523d 100644 (file)
@@ -10,6 +10,7 @@
 #include <asiolink/io_address.h>
 #include <dhcpsrv/base_host_data_source.h>
 #include <dhcpsrv/host.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
 #include <dhcp/classify.h>
 #include <dhcp/option.h>
 #include <boost/algorithm/string/join.hpp>
@@ -26,7 +27,7 @@ namespace test {
 ///
 /// It contains utility functions for test purposes.
 /// All concrete HostDataSource test classes should be derived from it.
-class GenericHostDataSourceTest : public ::testing::Test {
+class GenericHostDataSourceTest : public GenericBackendTest {
 public:
 
     /// @brief Universe (V4 or V6).
@@ -63,150 +64,6 @@ public:
     static bool compareHostsForSort6(const ConstHostPtr& host1,
                                      const ConstHostPtr& host2);
 
-    /// @brief Creates an option descriptor holding an empty option.
-    ///
-    /// @param universe V4 or V6.
-    /// @param option_type Option type.
-    /// @param persist A boolean flag indicating if the option is always
-    /// returned to the client or only when requested.
-    ///
-    /// @return Descriptor holding an empty option.
-    OptionDescriptor createEmptyOption(const Option::Universe& universe,
-                                       const uint16_t option_type,
-                                       const bool persist) const;
-
-    /// @brief Creates an instance of the option for which it is possible to
-    /// specify universe, option type, persistence flag  and value in
-    /// the constructor.
-    ///
-    /// Examples of options that can be created using this function are:
-    /// - @ref OptionString
-    /// - different variants of @ref OptionInt.
-    ///
-    /// @param universe V4 or V6.
-    /// @param option_type Option type.
-    /// @param persist A boolean flag indicating if the option is always
-    /// returned to the client or only when requested.
-    /// @param formatted A boolean value selecting if the formatted option
-    /// value should be used (if true), or binary value (if false).
-    /// @param value Option value to be assigned to the option.
-    /// @tparam OptionType Class encapsulating the option.
-    /// @tparam DataType Option value data type.
-    ///
-    /// @return Descriptor holding an instance of the option created.
-    template<typename OptionType, typename DataType>
-    OptionDescriptor createOption(const Option::Universe& universe,
-                                  const uint16_t option_type,
-                                  const bool persist,
-                                  const bool formatted,
-                                  const DataType& value) const {
-        boost::shared_ptr<OptionType> option(new OptionType(universe, option_type,
-                                                            value));
-        std::ostringstream s;
-        if (formatted) {
-            // Using formatted option value. Convert option value to a
-            // textual format.
-            s << value;
-        }
-        OptionDescriptor desc(option, persist, s.str());
-        return (desc);
-    }
-
-    /// @brief Creates an instance of the option for which it is possible to
-    /// specify option type, persistence flag  and value in the constructor.
-    ///
-    /// Examples of options that can be created using this function are:
-    /// - @ref Option4AddrLst
-    /// - @ref Option6AddrLst
-    ///
-    /// @param option_type Option type.
-    /// @param persist A boolean flag indicating if the option is always
-    /// returned to the client or only when requested.
-    /// @param formatted A boolean value selecting if the formatted option
-    /// value should be used (if true), or binary value (if false).
-    /// @param value Option value to be assigned to the option.
-    /// @tparam OptionType Class encapsulating the option.
-    /// @tparam DataType Option value data type.
-    ///
-    /// @return Descriptor holding an instance of the option created.
-    template<typename OptionType, typename DataType>
-    OptionDescriptor createOption(const uint16_t option_type,
-                                  const bool persist,
-                                  const bool formatted,
-                                  const DataType& value) const {
-        boost::shared_ptr<OptionType> option(new OptionType(option_type, value));
-
-        std::ostringstream s;
-        if (formatted) {
-            // Using formatted option value. Convert option value to a
-            // textual format.
-            s << value;
-        }
-
-        OptionDescriptor desc(option, persist, s.str());
-        return (desc);
-    }
-
-    /// @brief Creates an instance of the option holding list of IP addresses.
-    ///
-    /// @param option_type Option type.
-    /// @param persist A boolean flag indicating if the option is always
-    /// returned to the client or only when requested.
-    /// @param formatted A boolean value selecting if the formatted option
-    /// value should be used (if true), or binary value (if false).
-    /// @param address1 First address to be included. If address is empty, it is
-    /// not included.
-    /// @param address2 Second address to be included. If address is empty, it
-    /// is not included.
-    /// @param address3 Third address to be included. If address is empty, it
-    /// is not included.
-    /// @tparam OptionType Class encapsulating the option.
-    ///
-    /// @return Descriptor holding an instance of the option created.
-    template<typename OptionType>
-    OptionDescriptor
-    createAddressOption(const uint16_t option_type,
-                        const bool persist,
-                        const bool formatted,
-                        const std::string& address1 = "",
-                        const std::string& address2 = "",
-                        const std::string& address3 = "") const {
-        std::ostringstream s;
-        // First address.
-        typename OptionType::AddressContainer addresses;
-        if (!address1.empty()) {
-            addresses.push_back(asiolink::IOAddress(address1));
-            if (formatted) {
-                s << address1;
-            }
-        }
-        // Second address.
-        if (!address2.empty()) {
-            addresses.push_back(asiolink::IOAddress(address2));
-            if (formatted) {
-                if (s.tellp() != std::streampos(0)) {
-                    s << ",";
-                }
-                s << address2;
-            }
-        }
-        // Third address.
-        if (!address3.empty()) {
-            addresses.push_back(asiolink::IOAddress(address3));
-            if (formatted) {
-                if (s.tellp() != std::streampos(0)) {
-                    s << ",";
-                }
-                s << address3;
-            }
-        }
-
-        boost::shared_ptr<OptionType> option(new OptionType(option_type,
-                                                            addresses));
-        OptionDescriptor desc(option, persist, s.str());
-        return (desc);
-    }
-
     /// @brief Returns number of entries in the v4 options table.
     ///
     /// This utility method is expected to be implemented by specific backends.
@@ -240,21 +97,6 @@ public:
         return (-1);
     }
 
-    /// @brief Creates an instance of the vendor option.
-    ///
-    /// @param universe V4 or V6.
-    /// @param persist A boolean flag indicating if the option is always
-    /// returned to the client or only when requested.
-    /// @param formatted A boolean value selecting if the formatted option
-    /// value should be used (if true), or binary value (if false).
-    /// @param vendor_id Vendor identifier.
-    ///
-    /// @return Descriptor holding an instance of the option created.
-    OptionDescriptor createVendorOption(const Option::Universe& universe,
-                                        const bool persist,
-                                        const bool formatted,
-                                        const uint32_t vendor_id) const;
-
     /// @brief Adds multiple options into the host.
     ///
     /// This method creates the following options into the host object:
index 2d325a49efc80391a6a3a8300d366e1db621b4ba..b4046f8dc7ae30de18012412a0bfe81ceb2740f4 100644 (file)
@@ -768,6 +768,11 @@ CREATE INDEX timestamp_index ON logs (timestamp);
 ALTER TABLE hosts
     ADD COLUMN auth_key VARCHAR(16) NULL;
 
+
+# Add scope for shared network specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+    VALUES(4, "shared-network");
+
 -- -----------------------------------------------------
 -- Table `modification`
 -- -----------------------------------------------------
index 5c3105ffdd5d13e8858830fd3f542b6bde27d97d..89f8896b830856c67d25b3c8265a4a50c6e0c697 100644 (file)
@@ -112,6 +112,10 @@ UPDATE dhcp4_options SET dhcp4_subnet_id = NULL WHERE dhcp4_subnet_id = 0;
 UPDATE hosts SET dhcp6_subnet_id = NULL WHERE dhcp6_subnet_id = 0;
 UPDATE dhcp6_options SET dhcp6_subnet_id = NULL WHERE dhcp6_subnet_id = 0;
 
+# Add scope for shared network specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+    VALUES(4, "shared-network");
+
 # Create table modification
 CREATE TABLE IF NOT EXISTS modification (
   id TINYINT(3) NOT NULL,