]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[4281] Extended MySQL host data source with DHCPv4 and DHCPv6 options.
authorMarcin Siodelski <marcin@isc.org>
Mon, 2 May 2016 08:24:30 +0000 (10:24 +0200)
committerMarcin Siodelski <marcin@isc.org>
Thu, 12 May 2016 16:31:07 +0000 (18:31 +0200)
Missing things:
- proper commentary
- formatted values for options
- rollback host insertion when failing to add reservation or option

12 files changed:
src/lib/dhcp/libdhcp++.cc
src/lib/dhcp/libdhcp++.h
src/lib/dhcpsrv/cfg_option.cc
src/lib/dhcpsrv/cfg_option.h
src/lib/dhcpsrv/host.cc
src/lib/dhcpsrv/mysql_host_data_source.cc
src/lib/dhcpsrv/mysql_host_data_source.h
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/tests/cfg_option_unittest.cc
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc

index 700b734bc58336801cc96d5638c5e769ca9611a0..fdbc1437c18da7ebeddb6a88d353e6533268838b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 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
 #include <util/buffer.h>
 #include <dhcp/option_definition.h>
 
+#include <boost/lexical_cast.hpp>
 #include <boost/shared_array.hpp>
 #include <boost/shared_ptr.hpp>
 
+#include <limits>
 #include <list>
 
 using namespace std;
@@ -844,6 +846,45 @@ LibDHCP::initVendorOptsIsc6() {
     initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_DEFS, ISC_V6_DEFS_SIZE);
 }
 
+uint32_t
+LibDHCP::optionSpaceToVendorId(const std::string& option_space) {
+    if (option_space.size() < 8) {
+        // 8 is a minimal length of "vendor-X" format
+        return (0);
+    }
+    if (option_space.substr(0,7) != "vendor-") {
+        return (0);
+    }
+
+    // text after "vendor-", supposedly numbers only
+    std::string x = option_space.substr(7);
+
+    int64_t check;
+    try {
+        check = boost::lexical_cast<int64_t>(x);
+    } catch (const boost::bad_lexical_cast &) {
+        /// @todo: Should we throw here?
+        // isc_throw(BadValue, "Failed to parse vendor-X value (" << x
+        //           << ") as unsigned 32-bit integer.");
+        return (0);
+    }
+    if (check > std::numeric_limits<uint32_t>::max()) {
+        /// @todo: Should we throw here?
+        //isc_throw(BadValue, "Value " << x << "is too large"
+        //          << " for unsigned 32-bit integer.");
+        return (0);
+    }
+    if (check < 0) {
+        /// @todo: Should we throw here?
+        // isc_throw(BadValue, "Value " << x << "is negative."
+        //       << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+        return (0);
+    }
+
+    // value is small enough to fit
+    return (static_cast<uint32_t>(check));
+}
+
 void initOptionSpace(OptionDefContainer& defs,
                      const OptionDefParams* params,
                      size_t params_size) {
index 51b68e44321c3538e6b45737ef786a08790e1a69..b310b1a8b4858a37adacaa0a2f02bb4d0ca74435 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 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
@@ -14,6 +14,7 @@
 #include <util/staged_value.h>
 
 #include <iostream>
+#include <stdint.h>
 #include <string>
 
 namespace isc {
@@ -321,6 +322,21 @@ public:
     /// @brief Commits runtime option definitions.
     static void commitRuntimeOptionDefs();
 
+    /// @brief Converts option space name to vendor id.
+    ///
+    /// If the option space name is specified in the following format:
+    /// "vendor-X" where X is an uint32_t number, it is assumed to be
+    /// a vendor space and the uint32_t number is returned by this function.
+    /// If the option space name is invalid this method will return 0, which
+    /// is not a valid vendor-id, to signal an error.
+    ///
+    /// @todo remove this function once when the conversion is dealt by the
+    /// appropriate functions returning options by option space names.
+    ///
+    /// @param option_space Option space name.
+    /// @return vendor id.
+    static uint32_t optionSpaceToVendorId(const std::string& option_space);
+
 private:
 
     /// Initialize standard DHCPv4 option definitions.
index aac7702f49a696ac566a4f716e4df1179d343057..5b13ebae41bb971d691e4149b2c572d69d9a2cfd 100644 (file)
@@ -1,14 +1,13 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 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 <dhcp/libdhcp++.h>
 #include <dhcp/option_space.h>
 #include <dhcpsrv/cfg_option.h>
-#include <boost/lexical_cast.hpp>
 #include <dhcp/dhcp6.h>
-#include <limits>
 #include <string>
 
 namespace isc {
@@ -37,7 +36,12 @@ CfgOption::equals(const CfgOption& other) const {
 void
 CfgOption::add(const OptionPtr& option, const bool persistent,
                const std::string& option_space) {
-    if (!option) {
+    add(OptionDescriptor(option, persistent), option_space);
+}
+
+void
+CfgOption::add(const OptionDescriptor& desc, const std::string& option_space) {
+    if (!desc.option_) {
         isc_throw(isc::BadValue, "option being configured must not be NULL");
 
     } else  if (!OptionSpace::validateName(option_space)) {
@@ -45,13 +49,25 @@ CfgOption::add(const OptionPtr& option, const bool persistent,
                   << option_space << "'");
     }
 
-    const uint32_t vendor_id = optionSpaceToVendorId(option_space);
+    const uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
     if (vendor_id) {
-        vendor_options_.addItem(OptionDescriptor(option, persistent),
-                                vendor_id);
+        vendor_options_.addItem(desc, vendor_id);
     } else {
-        options_.addItem(OptionDescriptor(option, persistent), option_space);
+        options_.addItem(desc, option_space);
+    }
+}
+
+std::list<std::string>
+CfgOption::getVendorIdsSpaceNames() const {
+    std::list<uint32_t> ids = getVendorIds();
+    std::list<std::string> names;
+    for (std::list<uint32_t>::const_iterator id = ids.begin();
+         id != ids.end(); ++id) {
+        std::ostringstream s;
+        s << "vendor-" << *id;
+        names.push_back(s.str());
     }
+    return (names);
 }
 
 void
@@ -152,44 +168,5 @@ CfgOption::getAll(const uint32_t vendor_id) const {
     return (vendor_options_.getItems(vendor_id));
 }
 
-uint32_t
-CfgOption::optionSpaceToVendorId(const std::string& option_space) {
-    if (option_space.size() < 8) {
-        // 8 is a minimal length of "vendor-X" format
-        return (0);
-    }
-    if (option_space.substr(0,7) != "vendor-") {
-        return (0);
-    }
-
-    // text after "vendor-", supposedly numbers only
-    std::string x = option_space.substr(7);
-
-    int64_t check;
-    try {
-        check = boost::lexical_cast<int64_t>(x);
-    } catch (const boost::bad_lexical_cast &) {
-        /// @todo: Should we throw here?
-        // isc_throw(BadValue, "Failed to parse vendor-X value (" << x
-        //           << ") as unsigned 32-bit integer.");
-        return (0);
-    }
-    if (check > std::numeric_limits<uint32_t>::max()) {
-        /// @todo: Should we throw here?
-        //isc_throw(BadValue, "Value " << x << "is too large"
-        //          << " for unsigned 32-bit integer.");
-        return (0);
-    }
-    if (check < 0) {
-        /// @todo: Should we throw here?
-        // isc_throw(BadValue, "Value " << x << "is negative."
-        //       << " Only 0 or larger are allowed for unsigned 32-bit integer.");
-        return (0);
-    }
-
-    // value is small enough to fit
-    return (static_cast<uint32_t>(check));
-}
-
 } // end of namespace isc::dhcp
 } // end of namespace isc
index a5830506fe0df1d243e75223a87fc0d6bc8e7e30..a8d4400a7a5801f4d3a61c12a53e4d023caab7ce 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 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
@@ -254,6 +254,19 @@ public:
     void add(const OptionPtr& option, const bool persistent,
              const std::string& option_space);
 
+    /// @brief A variant of the method which takes option descriptor as an
+    /// argument.
+    ///
+    /// This method works exactly the same as the other variant, but it takes
+    /// option descriptor as an argument.
+    ///
+    /// @param desc Option descriptor holding option instance and other
+    /// parameters pertaining to this option.
+    /// @param option_space Option space name.
+    ///
+    /// @throw isc::BadValue if the option space is invalid.
+    void add(const OptionDescriptor& desc, const std::string& option_space);
+
     /// @brief Merges this configuration to another configuration.
     ///
     /// This method iterates over the configuration items held in this
@@ -337,20 +350,21 @@ public:
         return (*od_itr);
     }
 
-    /// @brief Converts option space name to vendor id.
-    ///
-    /// If the option space name is specified in the following format:
-    /// "vendor-X" where X is an uint32_t number, it is assumed to be
-    /// a vendor space and the uint32_t number is returned by this function.
-    /// If the option space name is invalid this method will return 0, which
-    /// is not a valid vendor-id, to signal an error.
-    ///
-    /// @todo remove this function once when the conversion is dealt by the
-    /// appropriate functions returning options by option space names.
+    /// @brief Returns a list of all configured option space names.
+    std::list<std::string> getOptionSpaceNames() const {
+        return (options_.getOptionSpaceNames());
+    }
+
+    /// @brief Returns a list of all configured  vendor identifiers.
+    std::list<uint32_t> getVendorIds() const {
+        return (vendor_options_.getOptionSpaceNames());
+    }
+
+    /// @brief Returns a list of option space names for configured vendor ids.
     ///
-    /// @param option_space Option space name.
-    /// @return vendor id.
-    static uint32_t optionSpaceToVendorId(const std::string& option_space);
+    /// For each vendor-id the option space name returned is constructed
+    /// as "vendor-<vendor-id>".
+    std::list<std::string> getVendorIdsSpaceNames() const;
 
 private:
 
index c387d057ff0f1b4af6428740a439e667023f86c4..ac5b9f7660c9de1eb1e28a22ecc737bcaaa2300c 100644 (file)
@@ -81,7 +81,7 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len,
       ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
       hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
       dhcp6_client_classes_(dhcp6_client_classes), host_id_(0),
-      cfg_option4_(), cfg_option6_() {
+      cfg_option4_(new CfgOption()), cfg_option6_(new CfgOption()) {
 
     // Initialize host identifier.
     setIdentifier(identifier, identifier_len, identifier_type);
index dfef3373c0902e3742b46503e4038d3ba0255c86..48d5eb09f6e5fab56df4915b30dbd9ad65b84b4f 100644 (file)
@@ -6,9 +6,16 @@
 
 #include <config.h>
 
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/mysql_host_data_source.h>
 #include <dhcpsrv/db_exceptions.h>
+#include <util/buffer.h>
+#include <util/optional_value.h>
 
 #include <boost/pointer_cast.hpp>
 #include <boost/static_assert.hpp>
@@ -20,8 +27,9 @@
 #include <string>
 
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
 using namespace std;
 
 namespace {
@@ -42,6 +50,15 @@ const size_t CLIENT_CLASSES_MAX_LEN = 255;
 /// in the Client FQDN %Option (see RFC4702 and RFC4704).
 const size_t HOSTNAME_MAX_LEN = 255;
 
+/// @brief Maximum length of option value.
+const size_t OPTION_VALUE_MAX_LEN = 4096;
+
+/// @brief Maximum length of option value specified in textual format.
+const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;
+
+/// @brief Maximum length of option space name.
+const size_t OPTION_SPACE_MAX_LEN = 128;
+
 /// @brief Numeric value representing last supported identifier.
 ///
 /// This value is used to validate whether the identifier type stored in
@@ -64,40 +81,72 @@ TaggedStatement tagged_statements[] = {
             "dhcp6_iaid, host_id) "
          "VALUES (?,?,?,?,?)"},
 
+    // Inserts a single DHCPv4 option into 'dhcp4_options' table.
+    {MySqlHostDataSource::INSERT_V4_OPTION,
+         "INSERT INTO dhcp4_options(option_id, code, value, space, persistent, "
+            "dhcp_client_class, dhcp4_subnet_id, host_id) "
+         " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"},
+
+    // Inserts a single DHCPv6 option into 'dhcp6_options' table.
+    {MySqlHostDataSource::INSERT_V6_OPTION,
+         "INSERT INTO dhcp6_options(option_id, code, value, space, persistent, "
+            "dhcp_client_class, dhcp6_subnet_id, host_id) "
+         " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"},
+
     // Retrieves host information along with IPv6 reservations associated
     // with this host. If the host exists in multiple subnets, all hosts
     // having a specified identifier will be returned from those subnets.
     // Because LEFT JOIN clause is used, the number of rows returned for
     // a single host depends on the number of reservations.
     {MySqlHostDataSource::GET_HOST_DHCPID,
-            "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+            "SELECT DISTINCT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
                 "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
                 "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
-                "r.address, r.prefix_len, r.type, r.dhcp6_iaid "
+                "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
+                "o4.persistent, "
+                "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
+                "o6.persistent, "
+                "r.reservation_id, r.address, r.prefix_len, r.type, "
+                "r.dhcp6_iaid "
             "FROM hosts AS h "
+            "LEFT JOIN dhcp4_options AS o4 "
+                "ON h.host_id = o4.host_id "
+            "LEFT JOIN dhcp6_options AS o6 "
+                "ON h.host_id = o6.host_id "
             "LEFT JOIN ipv6_reservations AS r "
                 "ON h.host_id = r.host_id "
-            "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ?"},
+            "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ? "
+            "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
 
     // Retrieves host information by IPv4 address. This should typically
     // return a single host, but if we ever allow for defining subnets
     // with overlapping address pools, multiple hosts may be returned.
     {MySqlHostDataSource::GET_HOST_ADDR,
-            "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
-                "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
-                "dhcp4_client_classes, dhcp6_client_classes "
-            "FROM hosts "
-            "WHERE ipv4_address = ?"},
+            "SELECT DISTINCT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+                "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent "
+            "FROM hosts AS h "
+            "LEFT JOIN dhcp4_options AS o "
+                "ON h.host_id = o.host_id "
+            "WHERE ipv4_address = ? "
+            "ORDER BY h.host_id, o.option_id"},
 
     // Retrieves host information by subnet identifier and unique
     // identifier of a client. This is expected to return a single host.
     {MySqlHostDataSource::GET_HOST_SUBID4_DHCPID,
-            "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
-                "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
-                "dhcp4_client_classes, dhcp6_client_classes "
-            "FROM hosts "
-            "WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type = ? "
-            "   AND dhcp_identifier = ?"},
+            "SELECT DISTINCT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+                "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent "
+            "FROM hosts AS h "
+            "LEFT JOIN dhcp4_options AS o "
+                "ON h.host_id = o.host_id "
+            "WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? "
+            "   AND h.dhcp_identifier = ? "
+            "ORDER BY h.host_id, o.option_id"},
 
     // Retrieves host information by subnet identifier and unique
     // identifier of a client. This query should return information
@@ -110,23 +159,33 @@ TaggedStatement tagged_statements[] = {
                 "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
                 "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
                 "h.dhcp4_client_classes, h.dhcp6_client_classes, "
-                "r.address, r.prefix_len, r.type, r.dhcp6_iaid "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent, "
+                "r.reservation_id, r.address, r.prefix_len, r.type, "
+                "r.dhcp6_iaid "
             "FROM hosts AS h "
+            "LEFT JOIN dhcp6_options AS o "
+                "ON h.host_id = o.host_id "
             "LEFT JOIN ipv6_reservations AS r "
                 "ON h.host_id = r.host_id "
-            "WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type = ? "
-                "AND dhcp_identifier = ? "
-            "ORDER BY h.host_id, r.prefix_len, r.address"},
+            "WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? "
+                "AND h.dhcp_identifier = ? "
+            "ORDER BY h.host_id, o.option_id, r.reservation_id"},
 
     // Retrieves host information using subnet identifier and the
     // IPv4 address reservation. This should return inforamation for
     // a single host.
     {MySqlHostDataSource::GET_HOST_SUBID_ADDR,
-            "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
-                "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
-                "dhcp4_client_classes, dhcp6_client_classes "
-            "FROM hosts "
-            "WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
+            "SELECT DISTINCT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+                "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent "
+            "FROM hosts AS h "
+            "LEFT JOIN dhcp4_options AS o "
+                "ON h.host_id = o.host_id "
+            "WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? "
+            "ORDER BY h.host_id, o.option_id"},
 
     // Retrieves host information using IPv6 prefix and prefix length
     // or IPv6 address. This query returns host information for a
@@ -139,14 +198,19 @@ TaggedStatement tagged_statements[] = {
                 "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
                 "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
                 "h.dhcp4_client_classes, h.dhcp6_client_classes, "
-                "r.address, r.prefix_len, r.type, r.dhcp6_iaid "
+                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+                "o.persistent, "
+                "r.reservation_id, r.address, r.prefix_len, r.type, "
+                "r.dhcp6_iaid "
             "FROM hosts AS h "
+            "LEFT JOIN dhcp6_options AS o "
+                "ON h.host_id = o.host_id "
             "LEFT JOIN ipv6_reservations AS r "
                 "ON h.host_id = r.host_id "
             "WHERE h.host_id = "
                 "(SELECT host_id FROM ipv6_reservations "
                  "WHERE address = ? AND prefix_len = ?) "
-            "ORDER BY h.host_id, r.prefix_len, r.address"},
+            "ORDER BY h.host_id, o.option_id, r.reservation_id"},
 
     // Retrieves MySQL schema version.
     {MySqlHostDataSource::GET_VERSION,
@@ -178,15 +242,22 @@ public:
     ///
     /// The initialization of the variables here is only to satisfy cppcheck -
     /// all variables are initialized/set in the methods before they are used.
-    MySqlHostExchange()
-        : bind_(HOST_COLUMNS), columns_(HOST_COLUMNS),
-          error_(HOST_COLUMNS, MLM_FALSE), host_id_(0),
+    ///
+    /// @param additional_columns_num Additional number of columns to standard
+    /// set of columns used by this class, for which resources should be
+    /// allocated.
+    MySqlHostExchange(const size_t additional_columns_num = 0)
+        : columns_num_(HOST_COLUMNS + additional_columns_num),
+          bind_(columns_num_), columns_(columns_num_),
+          error_(columns_num_, MLM_FALSE), host_id_(0),
           dhcp_identifier_length_(0), dhcp_identifier_type_(0),
           dhcp4_subnet_id_(0), dhcp6_subnet_id_(0), ipv4_address_(0),
           hostname_length_(0), dhcp4_client_classes_length_(0),
-          dhcp6_client_classes_length_(0), dhcp4_subnet_id_null_(MLM_FALSE),
-          dhcp6_subnet_id_null_(MLM_FALSE), ipv4_address_null_(MLM_FALSE),
-          hostname_null_(MLM_FALSE), dhcp4_client_classes_null_(MLM_FALSE),
+          dhcp6_client_classes_length_(0),
+          dhcp4_subnet_id_null_(MLM_FALSE),
+          dhcp6_subnet_id_null_(MLM_FALSE),
+          ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
+          dhcp4_client_classes_null_(MLM_FALSE),
           dhcp6_client_classes_null_(MLM_FALSE) {
 
         // Fill arrays with 0 so as they don't include any garbage.
@@ -213,6 +284,12 @@ public:
     virtual ~MySqlHostExchange() {
     }
 
+    size_t findAvailColumn() const {
+        std::vector<std::string>::const_iterator empty_column =
+            std::find(columns_.begin(), columns_.end(), std::string());
+        return (std::distance(columns_.begin(), empty_column));
+    }
+
     /// @brief Returns value of host id.
     ///
     /// This method is used by derived classes.
@@ -378,7 +455,7 @@ public:
 
         // Add the data to the vector.  Note the end element is one after the
         // end of the array.
-        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[HOST_COLUMNS]));
+        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[columns_num_]));
     };
 
     /// @brief Create BIND array to receive Host data.
@@ -484,29 +561,34 @@ public:
             isc_throw(BadValue, "invalid dhcp identifier type returned: "
                       << static_cast<int>(dhcp_identifier_type_));
         }
-        // Set the dhcp identifier type in a variable of the appropriate data type.
+        // Set the dhcp identifier type in a variable of the appropriate
+        // data type.
         Host::IdentifierType type =
             static_cast<Host::IdentifierType>(dhcp_identifier_type_);
 
-        // Set DHCPv4 subnet ID to the value returned. If NULL returned, set to 0.
+        // Set DHCPv4 subnet ID to the value returned. If NULL returned,
+        // set to 0.
         SubnetID ipv4_subnet_id(0);
         if (dhcp4_subnet_id_null_ == MLM_FALSE) {
             ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
         }
 
-        // Set DHCPv6 subnet ID to the value returned. If NULL returned, set to 0.
+        // Set DHCPv6 subnet ID to the value returned. If NULL returned,
+        // set to 0.
         SubnetID ipv6_subnet_id(0);
         if (dhcp6_subnet_id_null_ == MLM_FALSE) {
             ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
         }
 
-        // Set IPv4 address reservation if it was given, if not, set IPv4 zero address
+        // Set IPv4 address reservation if it was given, if not, set IPv4 zero
+        // address
         asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
         if (ipv4_address_null_ == MLM_FALSE) {
             ipv4_reservation = asiolink::IOAddress(ipv4_address_);
         }
 
-        // Set hostname if non NULL value returned. Otherwise, leave an empty string.
+        // Set hostname if non NULL value returned. Otherwise, leave an
+        // empty string.
         std::string hostname;
         if (hostname_null_ == MLM_FALSE) {
             hostname = std::string(hostname_, hostname_length_);
@@ -577,6 +659,9 @@ public:
 
 protected:
 
+    /// Number of columns returned in queries.
+    size_t columns_num_;
+
     /// Vector of MySQL bindings.
     std::vector<MYSQL_BIND> bind_;
 
@@ -662,7 +747,319 @@ private:
 
 };
 
-/// @brief This class provides mechanisms for sending and retrieving
+class MySqlHostExchangeOpts : public MySqlHostExchange {
+private:
+
+    /// @brief Number of columns holding option information.
+    static const size_t OPTION_COLUMNS = 6;
+
+    class OptionProcessor {
+    public:
+        OptionProcessor(const Option::Universe& universe,
+                        const size_t start_column)
+        : universe_(universe), start_column_(start_column), option_id_(0),
+          code_(0), value_length_(0), formatted_value_length_(0),
+          space_length_(0), persistent_(false), option_id_null_(MLM_FALSE),
+          code_null_(MLM_FALSE), value_null_(MLM_FALSE),
+          formatted_value_null_(MLM_FALSE), space_null_(MLM_FALSE),
+          option_id_index_(start_column), code_index_(start_column_ + 1),
+          value_index_(start_column_ + 2),
+          formatted_value_index_(start_column_ + 3),
+          space_index_(start_column_ + 4),
+          persistent_index_(start_column_ + 5),
+          most_recent_option_id_(0) {
+
+            memset(value_, 0, sizeof(value_));
+            memset(formatted_value_, 0, sizeof(formatted_value_));
+            memset(space_, 0, sizeof(space_));
+        }
+
+        uint64_t getOptionId() const {
+            if (option_id_null_ == MLM_FALSE) {
+                return (option_id_);
+            }
+            return (0);
+        }
+
+        void retrieveOption(const CfgOptionPtr& cfg) {
+            if (option_id_ == 0) {
+                return;
+            }
+
+            if (most_recent_option_id_ < option_id_) {
+                most_recent_option_id_ = option_id_;
+
+                space_[space_length_] = '\0';
+                std::string space(space_);
+
+                OptionDefinitionPtr def;
+                if ((space == DHCP4_OPTION_SPACE) || (space == DHCP6_OPTION_SPACE)) {
+                    def = LibDHCP::getOptionDef(universe_, 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_);
+                }
+
+                OptionPtr option;
+                OptionBuffer buf(value_, value_ + value_length_);
+                if (def) {
+                    option = def->optionFactory(universe_, code_, buf.begin(),
+                                                buf.end());
+                } else {
+                    option.reset(new Option(universe_, code_, buf.begin(),
+                                            buf.end()));
+                }
+
+                OptionDescriptor desc(option, persistent_);
+                cfg->add(desc, space);
+            }
+        }
+
+        void setColumnNames(std::vector<std::string>& columns) {
+            columns[option_id_index_] = "option_id";
+            columns[code_index_] = "code";
+            columns[value_index_] = "value";
+            columns[formatted_value_index_] = "formatted_value";
+            columns[space_index_] = "space";
+            columns[persistent_index_] = "persistent";
+        }
+
+        void setBindFields(std::vector<MYSQL_BIND>& bind) {
+            most_recent_option_id_ = 0;
+
+            // option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT,
+            bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG;
+            bind[option_id_index_].buffer = reinterpret_cast<char*>(&option_id_);
+            bind[option_id_index_].is_unsigned = MLM_TRUE;
+
+            // code : TINYINT OR SHORT UNSIGNED NOT NULL
+            bind[code_index_].buffer_type = MYSQL_TYPE_SHORT;
+            bind[code_index_].buffer = reinterpret_cast<char*>(&code_);
+            bind[code_index_].is_unsigned = MLM_TRUE;
+            bind[code_index_].is_null = &code_null_;
+
+            // value : BLOB NULL
+            value_length_ = sizeof(value_);
+            bind[value_index_].buffer_type = MYSQL_TYPE_BLOB;
+            bind[value_index_].buffer = reinterpret_cast<char*>(value_);
+            bind[value_index_].buffer_length = value_length_;
+            bind[value_index_].length = &value_length_;
+            bind[value_index_].is_null = &value_null_;
+
+            // formatted_value : TEXT NULL
+            formatted_value_length_ = sizeof(formatted_value_);
+            bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING;
+            bind[formatted_value_index_].buffer = reinterpret_cast<char*>(formatted_value_);
+            bind[formatted_value_index_].buffer_length = formatted_value_length_;
+            bind[formatted_value_index_].length = &formatted_value_length_;
+            bind[formatted_value_index_].is_null = &formatted_value_null_;
+
+            // space : VARCHAR(128) NULL
+            space_length_ = sizeof(space_);
+            bind[space_index_].buffer_type = MYSQL_TYPE_STRING;
+            bind[space_index_].buffer = reinterpret_cast<char*>(space_);
+            bind[space_index_].buffer_length = space_length_;
+            bind[space_index_].length = &space_length_;
+            bind[space_index_].is_null = &space_null_;
+
+            // persistent : TINYINT(1) NOT NULL DEFAULT 0
+            bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY;
+            bind[persistent_index_].buffer = reinterpret_cast<char*>(&persistent_);
+            bind[persistent_index_].is_unsigned = MLM_TRUE;
+        }
+
+    private:
+
+        Option::Universe universe_;
+
+        size_t start_column_;
+
+        /// Option id.
+        uint64_t option_id_;
+
+        /// Option code.
+        uint16_t code_;
+
+        /// Buffer holding binary value of an option.
+        uint8_t value_[OPTION_VALUE_MAX_LEN];
+
+        /// Option value length.
+        unsigned long value_length_;
+
+        /// Buffer holding textual value of an option.
+        char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN];
+
+        /// Formatted option value length.
+        unsigned long formatted_value_length_;
+
+        /// Buffer holding option space name.
+        char space_[OPTION_SPACE_MAX_LEN];
+
+        /// Option space length.
+        unsigned long space_length_;
+
+        /// Flag indicating if option is always sent or only if requested.
+        bool persistent_;
+
+        /// @name Boolean values indicating if values of specific columns in
+        /// the database are NULL.
+        //@{
+        /// Boolean flag indicating if the DHCPv4 option id is NULL.
+        my_bool option_id_null_;
+
+        /// Boolean flag indicating if the DHCPv4 option code is NULL.
+        my_bool code_null_;
+
+        /// Boolean flag indicating if the DHCPv4 option value is NULL.
+        my_bool value_null_;
+
+        /// Boolean flag indicating if the DHCPv4 formatted option value
+        /// is NULL.
+        my_bool formatted_value_null_;
+
+        /// Boolean flag indicating if the DHCPv4 option space is NULL.
+        my_bool space_null_;
+
+        //@}
+
+        /// @name Indexes of the specific columns
+        //@{
+        /// Option id
+        size_t option_id_index_;
+
+        /// Code
+        size_t code_index_;
+
+        /// Value
+        size_t value_index_;
+
+        /// Formatted value
+        size_t formatted_value_index_;
+
+        /// Space
+        size_t space_index_;
+
+        /// Persistent
+        size_t persistent_index_;
+        //@}
+
+        /// @brief Option id for last processed row.
+        uint64_t most_recent_option_id_;
+    };
+
+    typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
+
+public:
+
+    enum FetchedOptions {
+        DHCP4_ONLY,
+        DHCP6_ONLY,
+        DHCP4_AND_DHCP6
+    };
+
+    MySqlHostExchangeOpts(const FetchedOptions& fetched_options,
+                          const size_t additional_columns_num = 0)
+        : MySqlHostExchange(getRequiredColumnsNum(fetched_options)
+                            + additional_columns_num),
+          opt_proc4_(), opt_proc6_() {
+
+        if ((fetched_options == DHCP4_ONLY) ||
+            (fetched_options == DHCP4_AND_DHCP6)) {
+            opt_proc4_.reset(new OptionProcessor(Option::V4,
+                                                 findAvailColumn()));
+            opt_proc4_->setColumnNames(columns_);
+        }
+
+        if ((fetched_options == DHCP6_ONLY) ||
+            (fetched_options == DHCP4_AND_DHCP6)) {
+            opt_proc6_.reset(new OptionProcessor(Option::V6,
+                                                 findAvailColumn()));
+            opt_proc6_->setColumnNames(columns_);
+        }
+    }
+
+    virtual void processFetchedData(ConstHostCollection& hosts) {
+        HostPtr host;
+        HostPtr most_recent_host;
+
+        if (!hosts.empty()) {
+            // Const cast is not very elegant way to deal with it, but
+            // there is a good reason to use it here. This method is called
+            // to build a collection of const hosts to be returned to the
+            // caller. If we wanted to use non-const collection we'd need
+            // to copy the whole collection before returning it, which has
+            // performance implications. Alternatively, we could store the
+            // most recently added host in a class member but this would
+            // make the code less readable.
+            most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
+
+        }
+
+        if (!most_recent_host || (most_recent_host->getHostId() != getHostId())) {
+            host = retrieveHost();
+            hosts.push_back(host);
+            most_recent_host = host;
+        }
+
+        if (opt_proc4_) {
+            CfgOptionPtr cfg = most_recent_host->getCfgOption4();
+            opt_proc4_->retrieveOption(cfg);
+        }
+
+        if (opt_proc6_) {
+            CfgOptionPtr cfg = most_recent_host->getCfgOption6();
+            opt_proc6_->retrieveOption(cfg);
+        }
+    }
+
+    virtual std::vector<MYSQL_BIND> createBindForReceive() {
+        // The following call sets bind_ values between 0 and 8.
+        static_cast<void>(MySqlHostExchange::createBindForReceive());
+
+        if (opt_proc4_) {
+            opt_proc4_->setBindFields(bind_);
+        }
+
+        if (opt_proc6_) {
+            opt_proc6_->setBindFields(bind_);
+        }
+
+        // Add the error flags
+        setErrorIndicators(bind_, error_);
+
+        // Add the data to the vector.  Note the end element is one after the
+        // end of the array.
+        return (bind_);
+    };
+
+private:
+
+    static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
+        return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
+                OPTION_COLUMNS);
+    }
+
+    /// @brief Pointer to DHCPv4 options processor.
+    ///
+    /// If this object is NULL, the DHCPv4 options are not fetched.
+    OptionProcessorPtr opt_proc4_;
+
+    /// @brief Pointer to DHCPv6 options processor.
+    ///
+    /// If this object is NULL, the DHCPv6 options are not fetched.
+    OptionProcessorPtr opt_proc6_;
+};
+
+/// @Brief This class provides mechanisms for sending and retrieving
 /// host information and associated IPv6 reservations.
 ///
 /// This class extends the @ref MySqlHostExchange class with the
@@ -674,12 +1071,11 @@ private:
 /// (for each IPv6 reservation). This class is responsible for
 /// converting those multiple host instances into a single Host
 /// object with multiple IPv6 reservations.
-class MySqlHostIPv6Exchange : public MySqlHostExchange {
+class MySqlHostIPv6Exchange : public MySqlHostExchangeOpts {
 private:
 
-    /// @brief Number of columns returned in the queries used by
-    /// @ref MySqlHostIPv6Exchange.
-    static const size_t RESERVATION_COLUMNS = 13;
+    /// @brief Number of columns holding IPv6 reservation information.
+    static const size_t RESERVATION_COLUMNS = 5;
 
 public:
 
@@ -687,34 +1083,36 @@ public:
     ///
     /// Apart from initializing the base class data structures it also
     /// initializes values representing IPv6 reservation information.
-    MySqlHostIPv6Exchange()
-        : MySqlHostExchange(), reserv_type_(0), reserv_type_null_(MLM_FALSE),
-          ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0) {
+    MySqlHostIPv6Exchange(const FetchedOptions& fetched_options)
+        : MySqlHostExchangeOpts(fetched_options, RESERVATION_COLUMNS),
+          reservation_id_(0),
+          reserv_type_(0), reserv_type_null_(MLM_FALSE),
+          ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0),
+          reservation_id_index_(findAvailColumn()),
+          address_index_(reservation_id_index_ + 1),
+          prefix_len_index_(reservation_id_index_ + 2),
+          type_index_(reservation_id_index_ + 3),
+          iaid_index_(reservation_id_index_ + 4),
+          most_recent_reservation_id_(0) {
 
         memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
 
         // Append additional columns returned by the queries.
-        columns_.push_back("address");
-        columns_.push_back("prefix_len");
-        columns_.push_back("type");
-        columns_.push_back("dhcp6_iaid");
-
-        // Resize binding table initialized in the base class. Do not
-        // run memset on this table, because it is when createBindForReceive
-        // is called.
-        bind_.resize(RESERVATION_COLUMNS);
-
-        // Resize error table.
-        error_.resize(RESERVATION_COLUMNS);
-        std::fill(&error_[0], &error_[RESERVATION_COLUMNS], MLM_FALSE);
+        columns_[reservation_id_index_] = "reservation_id";
+        columns_[address_index_] = "address";
+        columns_[prefix_len_index_] = "prefix_len";
+        columns_[type_index_] = "type";
+        columns_[iaid_index_] = "dhcp6_iaid";
     }
 
-    /// @brief Checks if a currently processed row contains IPv6 reservation.
+    /// @brief Returns last fetched reservation id.
     ///
-    /// @return true if IPv6 reservation data is non-null for the processed
-    /// row, false otherwise.
-    bool hasReservation() const {
-        return (reserv_type_null_ == MLM_FALSE);
+    /// @return Reservation id or 0 if no reservation data is fetched.
+    uint64_t getReservationId() const {
+        if (reserv_type_null_ == MLM_FALSE) {
+            return (reservation_id_);
+        }
+        return (0);
     };
 
     /// @brief Create IPv6 reservation from the data contained in the
@@ -767,42 +1165,28 @@ public:
     /// @param [out] hosts Collection of hosts to which a new host created
     ///        from the processed data should be inserted.
     virtual void processFetchedData(ConstHostCollection& hosts) {
-        HostPtr host;
-        HostPtr most_recent_host;
 
-        // If there are any hosts already created, let's obtain an instance
-        // to the most recently added host. We will have to check if the
-        // currently processed row contains some data for this host or a
-        // different host. In the former case, we'll need to update the
-        // host information.
-        if (!hosts.empty()) {
-            // Const cast is not very elegant way to deal with it, but
-            // there is a good reason to use it here. This method is called
-            // to build a collection of const hosts to be returned to the
-            // caller. If we wanted to use non-const collection we'd need
-            // to copy the whole collection before returning it, which has
-            // performance implications. Alternatively, we could store the
-            // most recently added host in a class member but this would
-            // make the code less readable.
-            most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
+        // Call parent class to fetch host information and options.
+        MySqlHostExchangeOpts::processFetchedData(hosts);
+
+        if (getReservationId() == 0) {
+            return;
         }
 
-        // If there is no existing host or the new host id doesn't match
-        // we need to create a new host.
-        if (!most_recent_host || (most_recent_host->getHostId() != getHostId())) {
-            host = retrieveHost();
-            // If the row also contains IPv6 reservation we should add it
-            // to the host.
-            if (hasReservation()) {
+        if (hosts.empty()) {
+            isc_throw(Unexpected, "no host information while retrieving"
+                      " IPv6 reservation");
+        }
+        HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
+
+        // If we're dealing with a new reservation, let's add it to the
+        // host.
+        if (getReservationId() > most_recent_reservation_id_) {
+            most_recent_reservation_id_ = getReservationId();
+
+            if (most_recent_reservation_id_ > 0) {
                 host->addReservation(retrieveReservation());
             }
-            // In any case let's put the new host in the results.
-            hosts.push_back(host);
-
-        // If the returned row pertains to an existing host, let's just
-        // add a reservation.
-        } else if (hasReservation() && most_recent_host) {
-            most_recent_host->addReservation(retrieveReservation());
         }
     }
 
@@ -815,32 +1199,41 @@ public:
     ///
     /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
     virtual std::vector<MYSQL_BIND> createBindForReceive() {
+        // Reset most recent reservation id value because we're now making
+        // a new SELECT query.
+        most_recent_reservation_id_ = 0;
+
         // The following call sets bind_ values between 0 and 8.
-        static_cast<void>(MySqlHostExchange::createBindForReceive());
+        static_cast<void>(MySqlHostExchangeOpts::createBindForReceive());
+
+        // reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT
+        bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG;
+        bind_[reservation_id_index_].buffer = reinterpret_cast<char*>(&reservation_id_);
+        bind_[reservation_id_index_].is_unsigned = MLM_TRUE;
 
         // IPv6 address/prefix VARCHAR(39)
         ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1;
-        bind_[9].buffer_type = MYSQL_TYPE_STRING;
-        bind_[9].buffer = ipv6_address_buffer_;
-        bind_[9].buffer_length = ipv6_address_buffer_len_;
-        bind_[9].length = &ipv6_address_buffer_len_;
+        bind_[address_index_].buffer_type = MYSQL_TYPE_STRING;
+        bind_[address_index_].buffer = ipv6_address_buffer_;
+        bind_[address_index_].buffer_length = ipv6_address_buffer_len_;
+        bind_[address_index_].length = &ipv6_address_buffer_len_;
 
         // prefix_len : TINYINT
-        bind_[10].buffer_type = MYSQL_TYPE_TINY;
-        bind_[10].buffer = reinterpret_cast<char*>(&prefix_len_);
-        bind_[10].is_unsigned = MLM_TRUE;
+        bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
+        bind_[prefix_len_index_].buffer = reinterpret_cast<char*>(&prefix_len_);
+        bind_[prefix_len_index_].is_unsigned = MLM_TRUE;
 
         // (reservation) type : TINYINT
         reserv_type_null_ = MLM_FALSE;
-        bind_[11].buffer_type = MYSQL_TYPE_TINY;
-        bind_[11].buffer = reinterpret_cast<char*>(&reserv_type_);
-        bind_[11].is_unsigned = MLM_TRUE;
-        bind_[11].is_null = &reserv_type_null_;
+        bind_[type_index_].buffer_type = MYSQL_TYPE_TINY;
+        bind_[type_index_].buffer = reinterpret_cast<char*>(&reserv_type_);
+        bind_[type_index_].is_unsigned = MLM_TRUE;
+        bind_[type_index_].is_null = &reserv_type_null_;
 
         // dhcp6_iaid INT UNSIGNED
-        bind_[12].buffer_type = MYSQL_TYPE_LONG;
-        bind_[12].buffer = reinterpret_cast<char*>(&iaid_);
-        bind_[12].is_unsigned = MLM_TRUE;
+        bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG;
+        bind_[iaid_index_].buffer = reinterpret_cast<char*>(&iaid_);
+        bind_[iaid_index_].is_unsigned = MLM_TRUE;
 
         // Add the error flags
         setErrorIndicators(bind_, error_);
@@ -852,6 +1245,9 @@ public:
 
 private:
 
+    /// @brief IPv6 reservation id.
+    uint64_t reservation_id_;
+
     /// @brief IPv6 reservation type.
     uint8_t reserv_type_;
 
@@ -873,6 +1269,28 @@ private:
     /// @brief IAID.
     uint8_t iaid_;
 
+    /// @name Indexes of columns holding information about IPv6 reservations.
+    //@{
+    /// @brief Index of reservation_id column.
+    size_t reservation_id_index_;
+
+    /// @brief Index of address column.
+    size_t address_index_;
+
+    /// @brief Index of prefix_len column.
+    size_t prefix_len_index_;
+
+    /// @brief Index of type column.
+    size_t type_index_;
+
+    /// @brief Index of IAID column.
+    size_t iaid_index_;
+
+    //@}
+
+    /// @brief Reservation id for last processed row.
+    uint64_t most_recent_reservation_id_;
+
 };
 
 /// @brief This class is used for storing IPv6 reservations in a MySQL database.
@@ -1021,6 +1439,141 @@ private:
     my_bool error_[RESRV_COLUMNS];
 };
 
+class MySqlOptionExchange {
+private:
+
+    static const size_t OPTION_COLUMNS = 8;
+
+public:
+
+    MySqlOptionExchange()
+        : type_(0), value_len_(0), space_(), space_len_(0),
+          persistent_(false), client_class_(), client_class_len_(0),
+          subnet_id_(0), host_id_(0), option_() {
+
+        BOOST_STATIC_ASSERT(7 < OPTION_COLUMNS);
+    }
+
+
+    std::vector<MYSQL_BIND>
+    createBindForSend(const OptionDescriptor& opt_desc,
+                      const std::string& opt_space,
+                      const OptionalValue<SubnetID>& subnet_id,
+                      const HostID& host_id) {
+
+        // Hold pointer to the option to make sure it remains valid until
+        // we make a query.
+        option_ = opt_desc.option_;
+
+        memset(bind_, 0, sizeof(bind_));
+
+        try {
+            // option_id: INT UNSIGNED NOT NULL
+            // The option_id is auto_incremented, so we need to pass the NULL
+            // value.
+            bind_[0].buffer_type = MYSQL_TYPE_NULL;
+
+            // code: SMALLINT UNSIGNED NOT NULL
+            type_ = option_->getType();
+            bind_[1].buffer_type = MYSQL_TYPE_SHORT;
+            bind_[1].buffer = reinterpret_cast<char*>(&type_);
+            bind_[1].is_unsigned = MLM_TRUE;
+
+            // value: BLOB NULL
+            OutputBuffer buf(opt_desc.option_->len());
+            opt_desc.option_->pack(buf);
+            if (buf.getLength() > opt_desc.option_->getHeaderLen()) {
+                const char* buf_ptr = static_cast<const char*>(buf.getData());
+                value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
+                              buf_ptr + buf.getLength());
+                value_len_ = value_.size();
+                bind_[2].buffer_type = MYSQL_TYPE_BLOB;
+                bind_[2].buffer = &value_[0];
+                bind_[2].buffer_length = value_len_;
+                bind_[2].length = &value_len_;
+
+            } else {
+                value_.clear();
+                bind_[2].buffer_type = MYSQL_TYPE_NULL;
+            }
+
+            // space: VARCHAR(128) NULL
+            space_ = opt_space;
+            space_len_ = space_.size();
+            bind_[3].buffer_type = MYSQL_TYPE_STRING;
+            bind_[3].buffer = const_cast<char*>(space_.c_str());
+            bind_[3].buffer_length = space_len_;
+            bind_[3].length = &space_len_;
+
+            // persistent: TINYINT(1) NOT NULL DEFAULT 0
+            persistent_ = opt_desc.persistent_;
+            bind_[4].buffer_type = MYSQL_TYPE_TINY;
+            bind_[4].buffer = reinterpret_cast<char*>(&persistent_);
+            bind_[4].is_unsigned = MLM_TRUE;
+
+            // dhcp_client_class: VARCHAR(128) NULL
+            /// @todo Assign actual value to client class string.
+            client_class_len_ = client_class_.size();
+            bind_[5].buffer_type = MYSQL_TYPE_STRING;
+            bind_[5].buffer = const_cast<char*>(client_class_.c_str());
+            bind_[5].buffer_length = client_class_len_;
+            bind_[5].length = &client_class_len_;
+
+            // dhcp4_subnet_id: INT UNSIGNED NULL
+            if (subnet_id.isSpecified()) {
+                subnet_id_ = subnet_id;
+                bind_[6].buffer_type = MYSQL_TYPE_LONG;
+                bind_[6].buffer = reinterpret_cast<char*>(subnet_id_);
+                bind_[6].is_unsigned = MLM_TRUE;
+
+            } else {
+                bind_[6].buffer_type = MYSQL_TYPE_NULL;
+            }
+
+            // host_id: INT UNSIGNED NOT NULL
+            host_id_ = host_id;
+            bind_[7].buffer_type = MYSQL_TYPE_LONG;
+            bind_[7].buffer = reinterpret_cast<char*>(&host_id_);
+            bind_[7].is_unsigned = MLM_TRUE;
+
+        } catch (const std::exception& ex) {
+            isc_throw(DbOperationError,
+                      "Could not create bind array for inserting DHCP "
+                      "option: " << option_->toText() << ", reason: "
+                      << ex.what());
+        }
+
+        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[OPTION_COLUMNS]));
+    }
+
+private:
+
+    uint16_t type_;
+
+    std::vector<uint8_t> value_;
+
+    size_t value_len_;
+
+    std::string space_;
+
+    size_t space_len_;
+
+    bool persistent_;
+
+    std::string client_class_;
+
+    size_t client_class_len_;
+
+    uint32_t subnet_id_;
+
+    uint32_t host_id_;
+
+    OptionPtr option_;
+
+    MYSQL_BIND bind_[OPTION_COLUMNS];
+
+};
+
 } // end of anonymous namespace
 
 namespace isc {
@@ -1055,6 +1608,16 @@ public:
     /// @param id ID of a host owning this reservation
     void addResv(const IPv6Resrv& resv, const HostID& id);
 
+    void addOption(const MySqlHostDataSource::StatementIndex& stindex,
+                   const OptionDescriptor& opt_desc,
+                   const std::string& opt_space,
+                   const OptionalValue<SubnetID>& subnet_id,
+                   const HostID& id);
+
+    void addOptions(const MySqlHostDataSource::StatementIndex& stindex,
+                    const ConstCfgOptionPtr& options_cfg,
+                    uint64_t& host_id);
+
     /// @brief Check Error and Throw Exception
     ///
     /// Virtually all MySQL functions return a status which, if non-zero,
@@ -1117,16 +1680,25 @@ public:
 
     /// @brief Pointer to the object representing an exchange which
     /// can be used to retrieve DHCPv4 reservation.
-    boost::shared_ptr<MySqlHostExchange> host_exchange_;
+    boost::shared_ptr<MySqlHostExchangeOpts> host_exchange_;
 
     /// @brief Pointer to an object representing an exchange which can
     /// be used to retrieve DHCPv6 reservations.
     boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv6_exchange_;
 
+    /// @brief Pointer to an object representing an exchange which can
+    /// be used to retrieve DHCPv4 and DHCPv6 reservations using a
+    /// single query.
+    boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv46_exchange_;
+
     /// @brief Pointer to an object representing an exchange which can
     /// be used to insert new IPv6 reservation.
     boost::shared_ptr<MySqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
 
+    /// @brief Pointer to an object representing an exchange which can
+    /// be used to insert DHCPv4 option into 'dhcp4_options' table.
+    boost::shared_ptr<MySqlOptionExchange> host_ipv4_option_exchange_;
+
     /// @brief MySQL connection
     MySqlConnection conn_;
 
@@ -1134,9 +1706,12 @@ public:
 
 MySqlHostDataSourceImpl::
 MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
-    : host_exchange_(new MySqlHostExchange()),
-      host_ipv6_exchange_(new MySqlHostIPv6Exchange()),
+    : host_exchange_(new MySqlHostExchangeOpts(MySqlHostExchangeOpts::DHCP4_ONLY)),
+      host_ipv6_exchange_(new MySqlHostIPv6Exchange(MySqlHostExchangeOpts::DHCP6_ONLY)),
+      host_ipv46_exchange_(new MySqlHostIPv6Exchange(MySqlHostExchangeOpts::
+                                                     DHCP4_AND_DHCP6)),
       host_ipv6_reservation_exchange_(new MySqlIPv6ReservationExchange()),
+      host_ipv4_option_exchange_(new MySqlOptionExchange()),
       conn_(parameters) {
 
     // Open the database.
@@ -1182,6 +1757,7 @@ MySqlHostDataSourceImpl::addQuery(MySqlHostDataSource::StatementIndex stindex,
 
     // Execute the statement
     status = mysql_stmt_execute(conn_.statements_[stindex]);
+
     if (status != 0) {
         // Failure: check for the special case of duplicate entry.
         if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
@@ -1200,6 +1776,46 @@ MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
     addQuery(MySqlHostDataSource::INSERT_V6_RESRV, bind);
 }
 
+void
+MySqlHostDataSourceImpl::addOption(const MySqlHostDataSource::StatementIndex& stindex,
+                                   const OptionDescriptor& opt_desc,
+                                   const std::string& opt_space,
+                                   const OptionalValue<SubnetID>& subnet_id,
+                                   const HostID& id) {
+    std::vector<MYSQL_BIND> bind =
+        host_ipv4_option_exchange_->createBindForSend(opt_desc, opt_space,
+                                                      subnet_id, id);
+
+    addQuery(stindex, bind);
+}
+
+void
+MySqlHostDataSourceImpl::addOptions(const MySqlHostDataSource::StatementIndex& stindex,
+                                    const ConstCfgOptionPtr& options_cfg,
+                                    uint64_t& host_id) {
+    std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
+    std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
+    option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
+                         vendor_spaces.end());
+
+    // Retrieve host id only if there are any options to be added and the
+    // host id hasn't been retrieved yet.
+    if ((host_id == 0) && !option_spaces.empty()) {
+        host_id = mysql_insert_id(conn_.mysql_);
+    }
+    for (std::list<std::string>::const_iterator space = option_spaces.begin();
+         space != option_spaces.end(); ++space) {
+        OptionContainerPtr options = options_cfg->getAll(*space);
+        if (options && !options->empty()) {
+            for (OptionContainer::const_iterator opt = options->begin();
+                 opt != options->end(); ++opt) {
+                addOption(stindex, *opt, *space, OptionalValue<SubnetID>(),
+                          host_id);
+            }
+        }
+    }
+}
+
 void
 MySqlHostDataSourceImpl::
 checkError(const int status, const MySqlHostDataSource::StatementIndex index,
@@ -1336,17 +1952,26 @@ MySqlHostDataSource::add(const HostPtr& host) {
     // ... and call addHost() code.
     impl_->addQuery(INSERT_HOST, bind);
 
+    // Gets the last inserted hosts id
+    uint64_t host_id = 0;
+
     IPv6ResrvRange v6resv = host->getIPv6Reservations();
-    if (std::distance(v6resv.first, v6resv.second) == 0) {
-        // If there are no v6 reservations, we're done here.
-        return;
+    if (std::distance(v6resv.first, v6resv.second) > 0) {
+        host_id = mysql_insert_id(impl_->conn_.mysql_);
+        for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
+             ++resv) {
+            impl_->addResv(resv->second, host_id);
+        }
     }
 
-    // Gets the last inserted hosts id
-    uint64_t host_id = mysql_insert_id(impl_->conn_.mysql_);
-    for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
-         ++resv) {
-        impl_->addResv(resv->second, host_id);
+    ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
+    if (cfg_option4) {
+        impl_->addOptions(INSERT_V4_OPTION, cfg_option4, host_id);
+    }
+
+    ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
+    if (cfg_option6) {
+        impl_->addOptions(INSERT_V6_OPTION, cfg_option6, host_id);
     }
 }
 
@@ -1392,7 +2017,7 @@ MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
 
     ConstHostCollection result;
     impl_->getHostCollection(GET_HOST_DHCPID, inbind,
-                             impl_->host_ipv6_exchange_,
+                             impl_->host_ipv46_exchange_,
                              result, false);
     return (result);
 }
index 665dea2073dffd7b1f88b1093661704db625bbff..93bd49f860c88e13c29df14d0bb8192438501fdc 100644 (file)
@@ -257,6 +257,8 @@ public:
     enum StatementIndex {
         INSERT_HOST,            // Insert new host to collection
         INSERT_V6_RESRV,        // Insert v6 reservation
+        INSERT_V4_OPTION,       // Insert DHCPv4 option
+        INSERT_V6_OPTION,       // Insert DHCPv6 option
         GET_HOST_DHCPID,        // Gets hosts by host identifier
         GET_HOST_ADDR,          // Gets hosts by IPv4 address
         GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
index b14fd0f5192ea12a9cb8d288b34f5570b2c5ecd8..cc20213de2a67f217078697936c3cf0bcc7a26c0 100644 (file)
@@ -551,7 +551,7 @@ OptionDataParser::findOptionDefinition(const std::string& option_space,
     if (!def) {
         // Check if this is a vendor-option. If it is, get vendor-specific
         // definition.
-        uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space);
+        uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
         if (vendor_id) {
             def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
         }
index d0e42e8965935eb78fa98b96908160eb92b35afb..adaccf4da279964fe613ae3a1795c468addefcf3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 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
@@ -12,7 +12,9 @@
 #include <dhcpsrv/cfg_option.h>
 #include <boost/pointer_cast.hpp>
 #include <gtest/gtest.h>
+#include <iterator>
 #include <limits>
+#include <list>
 #include <sstream>
 
 using namespace isc;
@@ -490,5 +492,34 @@ TEST(CfgOptionTest, addVendorOptions) {
     EXPECT_TRUE(options->empty());
 }
 
+// This test verifies that option space names for the vendor options are
+// correct.
+TEST(CfgOptionTest, getVendorIdsSpaceNames) {
+    CfgOption cfg;
+
+    // Create 10 options, each goes under a different vendor id.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        // Generate space name for a unique vendor id.
+        std::ostringstream s;
+        s << "vendor-" << code;
+        ASSERT_NO_THROW(cfg.add(option, false, s.str()));
+    }
+
+    // We should now have 10 different vendor ids.
+    std::list<std::string> space_names = cfg.getVendorIdsSpaceNames();
+    ASSERT_EQ(10, space_names.size());
+
+    // Check that the option space names for those vendor ids are correct.
+    for (std::list<std::string>::iterator name = space_names.begin();
+         name != space_names.end(); ++name) {
+        uint16_t id = static_cast<uint16_t>(std::distance(space_names.begin(),
+                                                          name));
+        std::ostringstream s;
+        s << "vendor-" << (100 + id);
+        EXPECT_EQ(s.str(), *name);
+    }
+}
+
 
 } // end of anonymous namespace
index 01b5316a0f8943d35699c84afb1efe3f8d28efad..992a9ef95772963b94f9db81fb2fecdd58205537 100644 (file)
@@ -4,15 +4,30 @@
 // 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 <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_vendor.h>
 #include <dhcpsrv/tests/generic_host_data_source_unittest.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <dhcpsrv/database_connection.h>
 #include <asiolink/io_address.h>
+#include <util/buffer.h>
+#include <boost/foreach.hpp>
 #include <gtest/gtest.h>
+#include <cstring>
+#include <list>
+#include <string>
 #include <sstream>
+#include <typeinfo>
 
 using namespace std;
 using namespace isc::asiolink;
+using namespace isc::util;
 
 namespace isc {
 namespace dhcp {
@@ -20,10 +35,11 @@ namespace test {
 
 GenericHostDataSourceTest::GenericHostDataSourceTest()
     :hdsptr_() {
-
+    LibDHCP::clearRuntimeOptionDefs();
 }
 
 GenericHostDataSourceTest::~GenericHostDataSourceTest() {
+    LibDHCP::clearRuntimeOptionDefs();
 }
 
 std::vector<uint8_t>
@@ -229,12 +245,15 @@ void GenericHostDataSourceTest::compareHosts(const ConstHostPtr& host1,
     compareReservations6(host1->getIPv6Reservations(),
                          host2->getIPv6Reservations());
 
-    // And compare client classification details
+    // Compare client classification details
     compareClientClasses(host1->getClientClasses4(),
                          host2->getClientClasses4());
 
     compareClientClasses(host1->getClientClasses6(),
                          host2->getClientClasses6());
+
+    compareOptions(host1->getCfgOption4(), host2->getCfgOption4());
+    compareOptions(host1->getCfgOption6(), host2->getCfgOption6());
 }
 
 DuidPtr
@@ -313,6 +332,125 @@ GenericHostDataSourceTest::compareClientClasses(const ClientClasses& /*classes1*
     ///        This is part of the work for #4213.
 }
 
+void
+GenericHostDataSourceTest::compareOptions(const ConstCfgOptionPtr& cfg1,
+                                          const ConstCfgOptionPtr& cfg2) const {
+    ASSERT_TRUE(cfg1);
+    ASSERT_TRUE(cfg2);
+
+    std::list<std::string> option_spaces = cfg2->getOptionSpaceNames();
+    std::list<std::string> vendor_spaces = cfg2->getVendorIdsSpaceNames();
+    option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
+                         vendor_spaces.end());
+
+    BOOST_FOREACH(std::string space, option_spaces) {
+        OptionContainerPtr options1 = cfg1->getAll(space);
+        OptionContainerPtr options2 = cfg2->getAll(space);
+        ASSERT_TRUE(options1);
+        ASSERT_TRUE(options2);
+
+        ASSERT_EQ(options1->size(), options2->size());
+
+        BOOST_FOREACH(OptionDescriptor desc1, *options1) {
+            OptionDescriptor desc2 = cfg2->get(space, desc1.option_->getType());
+            ASSERT_EQ(desc1.persistent_, desc2.persistent_);
+            Option* option1 = desc1.option_.get();
+            Option* option2 = desc2.option_.get();
+
+            ASSERT_TRUE(typeid(*option1) == typeid(*option2))
+                << "Comapared DHCP options, having option code "
+                << desc1.option_->getType() << " and belonging to the "
+                << space << " option space, are represented "
+                "by different C++ classes: "
+                << typeid(*option1).name() << " vs "
+                << typeid(*option2).name();
+
+            OutputBuffer buf1(option1->len());
+            ASSERT_NO_THROW(option1->pack(buf1));
+            OutputBuffer buf2(option2->len());
+            ASSERT_NO_THROW(option2->pack(buf2));
+
+            ASSERT_EQ(buf1.getLength(), buf2.getLength());
+            ASSERT_EQ(0, memcmp(buf1.getData(), buf2.getData(), buf1.getLength()));
+        }
+    }
+}
+
+
+OptionDescriptor
+GenericHostDataSourceTest::createVendorOption(const Option::Universe& universe,
+                                              const bool persist,
+                                              const uint32_t vendor_id) const {
+    OptionVendorPtr option(new OptionVendor(universe, vendor_id));
+    OptionDescriptor desc(option, persist);
+    return (desc);
+}
+
+void
+GenericHostDataSourceTest::addTestOptions(const HostPtr& host) const {
+    // Add DHCPv4 options.
+    CfgOptionPtr opts = host->getCfgOption4();
+    opts->add(createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+                                         true, "my-boot-file"),
+              DHCP4_OPTION_SPACE);
+    opts->add(createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+                                        false, 64),
+              DHCP4_OPTION_SPACE);
+    opts->add(createOption<OptionUint32>(Option::V4, 1, false, 312131),
+              "vendor-encapsulated-options");
+    opts->add(createAddressOption<Option4AddrLst>(254, false, "192.0.2.3"),
+              DHCP4_OPTION_SPACE);
+    opts->add(createOption<Option>(Option::V4, 1, true, OptionBuffer()),
+              "isc");
+    opts->add(createAddressOption<Option4AddrLst>(2, false, "10.0.0.5",
+                                                  "10.0.0.3", "10.0.3.4"),
+              "isc");
+
+    OptionDefSpaceContainer defs;
+
+    // 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");
+
+    // Add DHCPv6 options.
+    opts = host->getCfgOption6();
+    opts->add(createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+                                         true, "my-boot-file"),
+              DHCP6_OPTION_SPACE);
+    opts->add(createOption<OptionUint32>(Option::V6, D6O_INFORMATION_REFRESH_TIME,
+                                         false, 3600),
+              DHCP6_OPTION_SPACE);
+    opts->add(createVendorOption(Option::V6, false, 2495), DHCP6_OPTION_SPACE);
+    opts->add(createAddressOption<Option6AddrLst>(1024, false, "2001:db8:1::1"),
+              DHCP6_OPTION_SPACE);
+    opts->add(createOption<Option>(Option::V6, 1, true, OptionBuffer()),
+              "isc2");
+    opts->add(createAddressOption<Option6AddrLst>(2, false, "3000::1", "3000::2",
+                                                  "3000::3"),
+              "isc2");
+
+    // Add definitions for DHCPv6 non-standard options.
+    defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-1024", 1024,
+                                                          "ipv6-address", true)),
+                 DHCP6_OPTION_SPACE);
+    defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-1", 1, "empty")),
+                 "isc2");
+    defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-2", 2,
+                                                          "ipv6-address", true)),
+                 "isc2");
+
+    LibDHCP::setRuntimeOptionDefs(defs);
+}
+
 void GenericHostDataSourceTest::testBasic4(const Host::IdentifierType& id) {
     // Make sure we have the pointer to the host data source.
     ASSERT_TRUE(hdsptr_);
@@ -898,6 +1036,73 @@ void GenericHostDataSourceTest::testMultipleReservationsDifferentOrder(){
 
 }
 
+void GenericHostDataSourceTest::testOptionsReservations4() {
+    HostPtr host = initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
+    // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+    ASSERT_NO_THROW(addTestOptions(host));
+    // Insert host and the options into respective tables.
+    ASSERT_NO_THROW(hdsptr_->add(host));
+    // Subnet id will be used in quries to the database.
+    SubnetID subnet_id = host->getIPv4SubnetID();
+
+    // getAll4(address)
+    ConstHostCollection hosts_by_addr = hdsptr_->getAll4(host->getIPv4Reservation());
+    ASSERT_EQ(1, hosts_by_addr.size());
+    ASSERT_NO_FATAL_FAILURE(compareHosts(host, *hosts_by_addr.begin()));
+
+    // get4(subnet_id, identifier_type, identifier, identifier_size)
+    ConstHostPtr host_by_id = hdsptr_->get4(subnet_id,
+                                            host->getIdentifierType(),
+                                            &host->getIdentifier()[0],
+                                            host->getIdentifier().size());
+    ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_id));
+
+    // get4(subnet_id, address)
+    ConstHostPtr host_by_addr = hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
+    ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_addr));
+
+    hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
+}
+
+void GenericHostDataSourceTest::testOptionsReservations6() {
+    HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+    // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+    ASSERT_NO_THROW(addTestOptions(host));
+    // Insert host, options and IPv6 reservations into respective tables.
+    ASSERT_NO_THROW(hdsptr_->add(host));
+    // Subnet id will be used in queries to the database.
+    SubnetID subnet_id = host->getIPv6SubnetID();
+
+    // get6(subnet_id, identifier_type, identifier, identifier_size)
+    ConstHostPtr host_by_id = hdsptr_->get6(subnet_id, host->getIdentifierType(),
+                                            &host->getIdentifier()[0],
+                                            host->getIdentifier().size());
+    ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_id));
+
+    // get6(address, prefix_len)
+    ConstHostPtr host_by_addr = hdsptr_->get6(IOAddress("2001:db8::1"), 128);
+    ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_addr));
+}
+
+void GenericHostDataSourceTest::testOptionsReservations46() {
+    HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+
+    // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+    ASSERT_NO_THROW(addTestOptions(host));
+    // Insert host, options and IPv6 reservations into respective tables.
+    ASSERT_NO_THROW(hdsptr_->add(host));
+    // Subnet id will be used in queries to the database.
+    SubnetID subnet_id = host->getIPv6SubnetID();
+
+    // getAll(identifier_type, identifier, identifier_size)
+    ConstHostCollection hosts_by_id = hdsptr_->getAll(host->getIdentifierType(),
+                                                      &host->getIdentifier()[0],
+                                                      host->getIdentifier().size());
+    ASSERT_EQ(1, hosts_by_id.size());
+    ASSERT_NO_FATAL_FAILURE(compareHosts(host, *hosts_by_id.begin()));
+}
+
+
 }; // namespace test
 }; // namespace dhcp
 }; // namespace isc
index d9f50ee3571c2fa171924d4f63c5500c7a7ddf6a..88251464d16dfdedb55fd988a669723e1df1d79e 100644 (file)
@@ -7,9 +7,12 @@
 #ifndef GENERIC_HOST_DATA_SOURCE_UNITTEST_H
 #define GENERIC_HOST_DATA_SOURCE_UNITTEST_H
 
+#include <asiolink/io_address.h>
 #include <dhcpsrv/base_host_data_source.h>
 #include <dhcpsrv/host.h>
 #include <dhcp/classify.h>
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
 #include <gtest/gtest.h>
 #include <vector>
 
@@ -122,7 +125,71 @@ public:
     /// @param classes1 first list of client classes
     /// @param classes2 second list of client classes
     void compareClientClasses(const ClientClasses& classes1,
-                              const ClientClasses& classes2);
+                              const ClientClasses& classes2);                           
+
+    /// @brief Compares options within two configurations.
+    ///
+    /// @param cfg1 First configuration.
+    /// @param cfg2 Second configuration.
+    void compareOptions(const ConstCfgOptionPtr& cfg1,
+                        const ConstCfgOptionPtr& cfg2) const;
+
+    /// @brief Creates an instance of the option for which it is possible to
+    /// specify universe, option type and value in the constructor.
+    ///
+    /// Examples of options that can be created using this function are:
+    /// - @ref OptionString
+    /// - different variants of @ref OptionInt.
+    ///
+    /// @param encapsulated_space 
+    /// @return 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 DataType& value) const {
+        boost::shared_ptr<OptionType> option(new OptionType(universe, option_type,
+                                                            value));
+        OptionDescriptor desc(option, persist);
+        return (desc);
+    }
+
+    template<typename OptionType, typename DataType>
+    OptionDescriptor createOption(const uint16_t option_type,
+                                  const bool persist,
+                                  const DataType& value) const {
+        boost::shared_ptr<OptionType> option(new OptionType(option_type, value));
+        OptionDescriptor desc(option, persist);
+        return (desc);
+    }
+
+    template<typename OptionType>
+    OptionDescriptor
+    createAddressOption(const uint16_t option_type,
+                        const bool persist,
+                        const std::string& address1 = "",
+                        const std::string& address2 = "",
+                        const std::string& address3 = "") const {
+        typename OptionType::AddressContainer addresses;
+        if (!address1.empty()) {
+            addresses.push_back(asiolink::IOAddress(address1));
+        }
+        if (!address2.empty()) {
+            addresses.push_back(asiolink::IOAddress(address2));
+        }
+        if (!address3.empty()) {
+            addresses.push_back(asiolink::IOAddress(address3));
+        }
+        boost::shared_ptr<OptionType> option(new OptionType(option_type, addresses));
+        OptionDescriptor desc(option, persist);
+        return (desc);
+    }
+
+    OptionDescriptor createVendorOption(const Option::Universe& universe,
+                                        const bool persist,
+                                        const uint32_t vendor_id) const;
+
+    void addTestOptions(const HostPtr& host) const;
 
     /// @brief Pointer to the host data source
     HostDataSourcePtr hdsptr_;
@@ -238,6 +305,24 @@ public:
     /// Uses gtest macros to report failures.
     void testAddDuplicate4();
 
+    /// @brief Test that DHCPv4 options can be inserted and retrieved from
+    /// the database.
+    ///
+    /// Uses gtest macros to report failures.
+    void testOptionsReservations4();
+
+    /// @brief Test that DHCPv6 options can be inserted and retrieved from
+    /// the database.
+    ///
+    /// Uses gtest macros to report failures.
+    void testOptionsReservations6();
+
+    /// @brief Test that DHCPv4 and DHCPv6 options can be inserted and retrieved
+    /// with a single query to the database.
+    ///
+    /// Uses gtest macros to report failures.
+    void testOptionsReservations46();
+
     /// @brief Returns DUID with identical content as specified HW address
     ///
     /// This method does not have any sense in real life and is only useful
@@ -257,6 +342,7 @@ public:
     /// @param duid DUID to be copied
     /// @return HW address with the same value as specified DUID
     HWAddrPtr DuidToHWAddr(const DuidPtr& duid);
+
 };
 
 }; // namespace test
index 7ec4e4e3b350c5ba8dee7802af686ca8769dbf32..dfb7d2cc5cc90f0425da350c984c4af75ca36a47 100644 (file)
@@ -406,4 +406,23 @@ TEST_F(MySqlHostDataSourceTest, addDuplicate4) {
     testAddDuplicate4();
 }
 
+// This test verifies that DHCPv4 options can be inserted and retrieved from
+// the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations4) {
+    testOptionsReservations4();
+}
+
+// This test verifies that DHCPv6 options can be inserted and retrieved from
+// the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations6) {
+    testOptionsReservations6();
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted and
+// retrieved with a single query to the database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations46) {
+    testOptionsReservations46();
+}
+
+
 }; // Of anonymous namespace