Browse Source

Merge branch 'master' of ssh://git.kea.isc.org/git/kea

Conflicts:
	ChangeLog
Shawn Routhier 9 years ago
parent
commit
28c4b2575b

+ 6 - 1
ChangeLog

@@ -1,8 +1,13 @@
-1120.	[bug]		sar
+1121.	[bug]		sar
 	Update the classification document to match the output from
 	the debug statements.
 	(Trac NA, git TBD)
 
+1120.	[func]		marcin
+	Extended MySQL host data source to retrieve DHCPv4 and DHCPv6
+	options associated with hosts from a MySQL database.
+	(Trac #4281, git b8a306a27d1cae03f6bc5223c30806f5cd1b64f4)
+
 1119.	[func]		sar
 	Add debug logging to the classification tokens.  This uses
 	the loggers "kea-dhcp4.eval" and "kea-dhcp6.eval" to capture

+ 6 - 0
src/bin/admin/tests/mysql_tests.sh.in

@@ -165,6 +165,12 @@ EOF
 EOF
     ERRCODE=$?
     assert_eq 0 $ERRCODE "host_identifier_type table is missing or broken. (returned status code %d, expected %d)"
+    # Seventh table: dhcp_option_scope
+    mysql -u$db_user -p$db_password $db_name >/dev/null 2>&1 <<EOF
+    SELECT scope_id, scope_name FROM dhcp_option_scope;
+EOF
+    ERRCODE=$?
+    assert_eq 0 $ERRCODE "dhcp_option_scope table is missing or broken. (returned status code %d, expected %d)"
 
     # Let's wipe the whole database
     mysql_wipe

+ 91 - 4
src/bin/dhcp4/tests/dora_unittest.cc

@@ -59,8 +59,17 @@ namespace {
 ///     uses HW address for lease lookup, rather than client id
 ///
 /// - Configuration 4:
-///   - The same as configuration 2, but using different values in
-///     'host-reservation-identifiers'
+///   - Used for testing host reservations where circuit-id takes precedence
+///     over hw-address, and the hw-address takes precedence over duid.
+///   - 1 subnet: 10.0.0.0/24
+///   - 3 reservations for this subnet:
+///     - IP address 10.0.0.7 for HW address aa:bb:cc:dd:ee:ff
+///     - IP address 10.0.0.8 for DUID 01:02:03:04:05
+///     - IP address 10.0.0.9 for circuit-id 'charter950'
+///
+/// - Configuration 5:
+///   - The same as configuration 4, but using the following order of
+///     host-reservation-identifiers: duid, circuit-id, hw-address.
 ///
 const char* DORA_CONFIGS[] = {
 // Configuration 0
@@ -164,7 +173,33 @@ const char* DORA_CONFIGS[] = {
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
-        "\"host-reservation-identifiers\": [ \"circuit-id\", \"hw-address\" ],"
+        "\"host-reservation-identifiers\": [ \"circuit-id\", \"hw-address\", \"duid\" ],"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+        "    \"reservations\": [ "
+        "       {"
+        "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+        "         \"ip-address\": \"10.0.0.7\""
+        "       },"
+        "       {"
+        "         \"duid\": \"01:02:03:04:05\","
+        "         \"ip-address\": \"10.0.0.8\""
+        "       },"
+        "       {"
+        "         \"circuit-id\": \"'charter950'\","
+        "         \"ip-address\": \"10.0.0.9\""
+        "       }"
+        "    ]"
+        "} ]"
+    "}",
+
+    // Configuration 5
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"host-reservation-identifiers\": [ \"duid\", \"circuit-id\", \"hw-address\" ],"
         "\"valid-lifetime\": 600,"
         "\"subnet4\": [ { "
         "    \"subnet\": \"10.0.0.0/24\", "
@@ -728,6 +763,38 @@ TEST_F(DORATest, reservation) {
 }
 
 // This test checks that it is possible to make a reservation by
+// DUID carried in the Client Identifier option.
+TEST_F(DORATest, reservationByDUID) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Use relay agent so as the circuit-id can be inserted.
+    client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+    // Modify HW address so as the server doesn't assign reserved
+    // address by HW address.
+    client.modifyHWAddr();
+    // Specify DUID for which address 10.0.0.8 is reserved.
+    // The value specified as client id includes:
+    // - FF is a client identifier type for DUID,
+    // - 45454545 - represents 4 bytes for IAID
+    // - 01:02:03:04:05 - is an actual DUID for which there is a
+    //   reservation.
+    client.includeClientId("FF:45:45:45:45:01:02:03:04:05");
+
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[2], *client.getServer());
+    // Client A performs 4-way exchange and should obtain a reserved
+    // address.
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("0.0.0.0"))));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Make sure that the client has got the lease for the reserved address.
+    ASSERT_EQ("10.0.0.8", client.config_.lease_.addr_.toText());
+}
+
+// This test checks that it is possible to make a reservation by
 // circuit-id inserted by the relay agent.
 TEST_F(DORATest, reservationByCircuitId) {
     Dhcp4Client client(Dhcp4Client::SELECTING);
@@ -758,6 +825,13 @@ TEST_F(DORATest, hostIdentifiersOrder) {
     client.setHWAddress("aa:bb:cc:dd:ee:ff");
     // Use relay agent so as the circuit-id can be inserted.
     client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+    // Specify DUID for which address 10.0.0.8 is reserved.
+    // The value specified as client id includes:
+    // - FF is a client identifier type for DUID,
+    // - 45454545 - represents 4 bytes for IAID
+    // - 01:02:03:04:05 - is an actual DUID for which there is a
+    //   reservation.
+    client.includeClientId("FF:45:45:45:45:01:02:03:04:05");
     // Specify circuit-id.
     client.setCircuitId("charter950");
 
@@ -778,7 +852,7 @@ TEST_F(DORATest, hostIdentifiersOrder) {
 
     // Reconfigure the server to change the preference order of the
     // host identifiers. The 'circuit-id' should now take precedence over
-    // the hw-address.
+    // the hw-address and duid.
     configure(DORA_CONFIGS[4], *client.getServer());
     ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
                                   IOAddress>(new IOAddress("0.0.0.0"))));
@@ -790,6 +864,19 @@ TEST_F(DORATest, hostIdentifiersOrder) {
     // Make sure that the client has got the lease for the reserved address.
     ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText());
 
+    // Reconfigure the server to change the preference order of the
+    // host identifiers. The 'duid' should now take precedence over
+    // the hw-address and circuit-id
+    configure(DORA_CONFIGS[5], *client.getServer());
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("0.0.0.0"))));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Make sure that the client has got the lease for the reserved address.
+    ASSERT_EQ("10.0.0.8", client.config_.lease_.addr_.toText());
 }
 
 // This test checks that setting the match-client-id value to false causes

+ 20 - 0
src/lib/cc/tests/data_unittests.cc

@@ -561,6 +561,26 @@ TEST(Element, escape) {
     EXPECT_NO_THROW(Element::fromJSON("\"  \n  \r \t \f  \n \n    \t\""));
 }
 
+// This test verifies that a backslash can be used in element content
+// when the element is created using constructor.
+TEST(Element, backslash1) {
+    string input = "SMSBoot\\x64";// One slash passed to elem constructor...
+    string exp = "SMSBoot\\x64";  // ... should result in one slash in the actual option.
+
+    StringElement elem(input);
+    EXPECT_EQ(exp, elem.stringValue());
+}
+
+// This test verifies that a backslash can be used in element content
+// when the element is created using fromJSON.
+TEST(Element, backslash2) {
+    string input = "\"SMSBoot\\\\x64\""; // Two slashes put in the config file...
+    string exp = "SMSBoot\\x64"; // ... should result in one slash in the actual option.
+
+    ElementPtr elem = Element::fromJSON(input);
+    EXPECT_EQ(exp, elem->stringValue());
+}
+
 TEST(Element, ListElement) {
     // this function checks the specific functions for ListElements
     ElementPtr el = Element::fromJSON("[ 1, \"bar\", 3 ]");

+ 29 - 1
src/lib/dhcp/libdhcp++.cc

@@ -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
@@ -21,9 +21,11 @@
 #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,32 @@ LibDHCP::initVendorOptsIsc6() {
     initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_DEFS, ISC_V6_DEFS_SIZE);
 }
 
+uint32_t
+LibDHCP::optionSpaceToVendorId(const std::string& option_space) {
+    // 8 is a minimal length of "vendor-X" format
+    if ((option_space.size() < 8) || (option_space.substr(0,7) != "vendor-")) {
+        return (0);
+    }
+
+    int64_t check;
+    try {
+        // text after "vendor-", supposedly numbers only
+        std::string x = option_space.substr(7);
+
+        check = boost::lexical_cast<int64_t>(x);
+
+    } catch (const boost::bad_lexical_cast &) {
+        return (0);
+    }
+
+    if ((check < 0) || (check > std::numeric_limits<uint32_t>::max())) {
+        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) {

+ 17 - 1
src/lib/dhcp/libdhcp++.h

@@ -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.

+ 1 - 0
src/lib/dhcpsrv/base_host_data_source.h

@@ -105,6 +105,7 @@ public:
     /// for a specified identifier. This method may return multiple hosts
     /// because a particular client may have reservations in multiple subnets.
     ///
+    /// @param identifier_type Identifier type.
     /// @param identifier_begin Pointer to a begining of a buffer containing
     /// an identifier.
     /// @param identifier_len Identifier length.

+ 1 - 1
src/lib/dhcpsrv/cfg_host_operations.h

@@ -63,7 +63,7 @@ public:
     /// @brief Adds new identifier type to a collection of identifiers
     /// to be used by the server to search for host reservations.
     ///
-    /// @param identifier_type Name of the identifier to be added. It
+    /// @param identifier_name Name of the identifier to be added. It
     /// must be one of the names supported by the @ref Host::getIdentifierType
     /// function.
     void addIdentifierType(const std::string& identifier_name);

+ 27 - 47
src/lib/dhcpsrv/cfg_option.cc

@@ -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 {
@@ -17,6 +16,7 @@ namespace dhcp {
 bool
 OptionDescriptor::equals(const OptionDescriptor& other) const {
     return (persistent_ == other.persistent_ &&
+            formatted_value_ == other.formatted_value_ &&
             option_->equals(other.option_));
 }
 
@@ -37,7 +37,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 +50,27 @@ 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;
+        // Vendor space name is constructed as "vendor-XYZ" where XYZ is an
+        // uint32_t value, without leading zeros.
+        s << "vendor-" << *id;
+        names.push_back(s.str());
     }
+    return (names);
 }
 
 void
@@ -152,44 +171,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

+ 61 - 19
src/lib/dhcpsrv/cfg_option.h

@@ -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
@@ -18,7 +18,6 @@
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 #include <string>
-#include <set>
 #include <list>
 
 namespace isc {
@@ -31,24 +30,47 @@ namespace dhcp {
 /// to DHCP client only on request (persistent = false) or always
 /// (persistent = true).
 struct OptionDescriptor {
-    /// Option instance.
+    /// @brief Option instance.
     OptionPtr option_;
-    /// Persistent flag, if true option is always sent to the client,
-    /// if false option is sent to the client on request.
+
+    /// @brief Persistence flag.
+    ///
+    /// If true, option is always sent to the client. If false, option is
+    /// sent to the client when requested using ORO or PRL option.
     bool persistent_;
 
+    /// @brief Option value in textual (CSV) format.
+    ///
+    /// This field is used to convey option value in human readable format,
+    /// the same as used to specify option value in the server configuration.
+    /// This value is optional and can be held in the host reservations
+    /// database instead of the binary format.
+    ///
+    /// Note that this value is carried in the option descriptor, rather than
+    /// @c Option instance because it is a server specific value (same as
+    /// persistence flag).
+    ///
+    /// An example of the formatted value is: "2001:db8:1::1, 23, some text"
+    /// for the option which carries IPv6 address, a number and a text.
+    std::string formatted_value_;
+
     /// @brief Constructor.
     ///
     /// @param opt option
     /// @param persist if true option is always sent.
-    OptionDescriptor(const OptionPtr& opt, bool persist)
-        : option_(opt), persistent_(persist) {};
+    /// @param formatted_value option value in the textual format. Default
+    /// value is empty indicating that the value is not set.
+    OptionDescriptor(const OptionPtr& opt, bool persist,
+                     const std::string& formatted_value = "")
+        : option_(opt), persistent_(persist),
+          formatted_value_(formatted_value) {};
 
     /// @brief Constructor
     ///
     /// @param persist if true option is always sent.
     OptionDescriptor(bool persist)
-        : option_(OptionPtr()), persistent_(persist) {};
+        : option_(OptionPtr()), persistent_(persist),
+          formatted_value_() {};
 
     /// @brief Checks if the one descriptor is equal to another.
     ///
@@ -254,6 +276,16 @@ public:
     void add(const OptionPtr& option, const bool persistent,
              const std::string& option_space);
 
+    /// @brief A variant of the @ref CfgOption::add method which takes option
+    /// descriptor as an argument.
+    ///
+    /// @param desc Option descriptor holding option instance and other
+    /// parameters pertaining to the 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 +369,30 @@ public:
         return (*od_itr);
     }
 
-    /// @brief Converts option space name to vendor id.
+    /// @brief Returns a list of configured option space names.
     ///
-    /// 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.
+    /// The returned option space names exclude vendor option spaces,
+    /// such as "vendor-1234". These are returned by the
+    /// @ref getVendorIdsSpaceNames.
+    ///
+    /// @return List comprising 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.
     ///
-    /// @todo remove this function once when the conversion is dealt by the
-    /// appropriate functions returning options by option space names.
+    /// For each vendor-id the option space name returned is constructed
+    /// as "vendor-XYZ" where XYZ is a @c uint32_t value without leading
+    /// zeros.
     ///
-    /// @param option_space Option space name.
-    /// @return vendor id.
-    static uint32_t optionSpaceToVendorId(const std::string& option_space);
+    /// @return List comprising option space names for vendor options.
+    std::list<std::string> getVendorIdsSpaceNames() const;
 
 private:
 

+ 9 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -539,6 +539,15 @@ information from the MySQL hosts database.
 The code has issued a rollback call.  All outstanding transaction will
 be rolled back and not committed to the database.
 
+% DHCPSRV_MYSQL_START_TRANSACTION starting new MySQL transaction
+A debug message issued whena new MySQL transaction is being started.
+This message is typically not issued when inserting data into a
+single table because the server doesn't explicitly start
+transactions in this case. This message is issued when data is
+inserted into multiple tables with multiple INSERT statements
+and there may be a need to rollback the whole transaction if
+any of these INSERT statements fail.
+
 % DHCPSRV_MYSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
 A debug message issued when the server is attempting to update IPv4
 lease from the MySQL database for the specified address.

+ 1 - 1
src/lib/dhcpsrv/host.cc

@@ -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);

+ 2 - 2
src/lib/dhcpsrv/host.h

@@ -317,7 +317,7 @@ public:
 
     /// @brief Returns host identifier in a textual form.
     ///
-    /// @return Identifier in the form of <type>=<value>.
+    /// @return Identifier in the form of type=value.
     std::string getIdentifierAsText() const;
 
     /// @brief Returns name of the identifier of a specified type.
@@ -328,7 +328,7 @@ public:
     /// @param type Identifier type.
     /// @param value Pointer to a buffer holding identifier.
     /// @param length Length of the identifier.
-    /// @return Identifier in the form of <type>=<value>.
+    /// @return Identifier in the form of type=value.
     static std::string getIdentifierAsText(const IdentifierType& type,
                                            const uint8_t* value,
                                            const size_t length);

+ 1 - 0
src/lib/dhcpsrv/host_mgr.h

@@ -116,6 +116,7 @@ public:
     /// reservations from the primary data source are placed before the
     /// reservations from the alternate source.
     ///
+    /// @param identifier_type Identifier type.
     /// @param identifier_begin Pointer to a begining of a buffer containing
     /// an identifier.
     /// @param identifier_len Identifier length.

+ 47 - 12
src/lib/dhcpsrv/mysql_connection.cc

@@ -31,6 +31,26 @@ const int MLM_MYSQL_FETCH_FAILURE = 1;
 
 const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5;	// seconds
 
+MySqlTransaction::MySqlTransaction(MySqlConnection& conn)
+    : conn_(conn), committed_(false) {
+    conn_.startTransaction();
+}
+
+MySqlTransaction::~MySqlTransaction() {
+    // Rollback if the MySqlTransaction::commit wasn't explicitly
+    // called.
+    if (!committed_) {
+        conn_.rollback();
+    }
+}
+
+void
+MySqlTransaction::commit() {
+    conn_.commit();
+    committed_ = true;
+}
+
+
 // Open the database using the parameters passed to the constructor.
 
 void
@@ -306,20 +326,35 @@ MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire,
     cltt = mktime(&expire_tm) - valid_lifetime;
 }
 
-void MySqlConnection::commit() {
-        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
-        if (mysql_commit(mysql_) != 0) {
-                isc_throw(DbOperationError, "commit failed: "
-                        << mysql_error(mysql_));
-        }
+void
+MySqlConnection::startTransaction() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_START_TRANSACTION);
+    // We create prepared statements for all other queries, but MySQL
+    // don't support prepared statements for START TRANSACTION.
+    int status = mysql_query(mysql_, "START TRANSACTION");
+    if (status != 0) {
+        isc_throw(DbOperationError, "unable to start transaction, "
+                  "reason: " << mysql_error(mysql_));
+    }
 }
 
-void MySqlConnection::rollback() {
-        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
-        if (mysql_rollback(mysql_) != 0) {
-                isc_throw(DbOperationError, "rollback failed: "
-                        << mysql_error(mysql_));
-        }
+void
+MySqlConnection::commit() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
+    if (mysql_commit(mysql_) != 0) {
+        isc_throw(DbOperationError, "commit failed: "
+                  << mysql_error(mysql_));
+    }
+}
+
+void
+MySqlConnection::rollback() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
+    if (mysql_rollback(mysql_) != 0) {
+        isc_throw(DbOperationError, "rollback failed: "
+                  << mysql_error(mysql_));
+    }
 }
 
 

+ 127 - 2
src/lib/dhcpsrv/mysql_connection.h

@@ -8,8 +8,12 @@
 #define MYSQL_CONNECTION_H
 
 #include <dhcpsrv/database_connection.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <exceptions/exceptions.h>
 #include <boost/scoped_ptr.hpp>
 #include <mysql.h>
+#include <mysqld_error.h>
+#include <errmsg.h>
 #include <vector>
 #include <stdint.h>
 
@@ -140,6 +144,63 @@ private:
     MYSQL* mysql_;      ///< Initialization context
 };
 
+/// @brief Forward declaration to @ref MySqlConnection.
+class MySqlConnection;
+
+/// @brief RAII object representing MySQL transaction.
+///
+/// An instance of this class should be created in a scope where multiple
+/// INSERT statements should be executed within a single transaction. The
+/// transaction is started when the constructor of this class is invoked.
+/// The transaction is ended when the @ref MySqlTransaction::commit is
+/// explicitly called or when the instance of this class is destroyed.
+/// The @ref MySqlTransaction::commit commits changes to the database
+/// and the changes remain in the database when the instance of the
+/// class is destroyed. If the class instance is destroyed before the
+/// @ref MySqlTransaction::commit is called, the transaction is rolled
+/// back. The rollback on destruction guarantees that partial data is
+/// not stored in the database when there is an error during any
+/// of the operations belonging to a transaction.
+///
+/// The default MySQL backend configuration enables 'autocommit'.
+/// Starting a transaction overrides 'autocommit' setting for this
+/// particular transaction only. It does not affect the global 'autocommit'
+/// setting for the database connection, i.e. all modifications to the
+/// database which don't use transactions will still be auto committed.
+class MySqlTransaction : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Starts transaction by making a "START TRANSACTION" query.
+    ///
+    /// @param conn MySQL connection to use for the transaction. This
+    /// connection will be later used to commit or rollback changes.
+    ///
+    /// @throw DbOperationError if "START TRANSACTION" query fails.
+    MySqlTransaction(MySqlConnection& conn);
+
+    /// @brief Destructor.
+    ///
+    /// Rolls back the transaction if changes haven't been committed.
+    ~MySqlTransaction();
+
+    /// @brief Commits transaction.
+    void commit();
+
+private:
+
+    /// @brief Holds reference to the MySQL database connection.
+    MySqlConnection& conn_;
+
+    /// @brief Boolean flag indicating if the transaction has been committed.
+    ///
+    /// This flag is used in the class destructor to assess if the
+    /// transaction should be rolled back.
+    bool committed_;
+};
+
+
 /// @brief Common MySQL Connector Pool
 ///
 /// This class provides common operations for MySQL database connection
@@ -259,6 +320,9 @@ public:
             uint32_t valid_lifetime, time_t& cltt);
     ///@}
 
+    /// @brief Starts Transaction
+    void startTransaction();
+
     /// @brief Commit Transactions
     ///
     /// Commits all pending database operations. On databases that don't
@@ -275,6 +339,69 @@ public:
     /// @throw DbOperationError If the rollback failed.
     void rollback();
 
+    /// @brief Check Error and Throw Exception
+    ///
+    /// Virtually all MySQL functions return a status which, if non-zero,
+    /// indicates an error.  This function centralizes the error checking
+    /// code.
+    ///
+    /// It is used to determine whether or not the function succeeded, and
+    /// in the event of failures, decide whether or not those failures are
+    /// recoverable.
+    ///
+    /// If the error is recoverable, the method will throw a DbOperationError.
+    /// In the error is deemed unrecoverable, such as a loss of connectivity
+    /// with the server, this method will log the error and call exit(-1);
+    ///
+    /// @todo Calling exit() is viewed as a short term solution for Kea 1.0.
+    /// Two tickets are likely to alter this behavior, first is #3639, which
+    /// calls for the ability to attempt to reconnect to the database. The
+    /// second ticket, #4087 which calls for the implementation of a generic,
+    /// FatalException class which will propagate outward.
+    ///
+    /// @param status Status code: non-zero implies an error
+    /// @param index Index of statement that caused the error
+    /// @param what High-level description of the error
+    ///
+    /// @tparam Enumeration representing index of a statement to which an
+    /// error pertains.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    template<typename StatementIndex>
+    void checkError(const int status, const StatementIndex& index,
+                    const char* what) const {
+        if (status != 0) {
+            switch(mysql_errno(mysql_)) {
+                // These are the ones we consider fatal. Remember this method is
+                // used to check errors of API calls made subsequent to successfully
+                // connecting.  Errors occuring while attempting to connect are
+                // checked in the connection code. An alternative would be to call
+                // mysql_ping() - assuming autoreconnect is off. If that fails
+                // then we know connection is toast.
+            case CR_SERVER_GONE_ERROR:
+            case CR_SERVER_LOST:
+            case CR_OUT_OF_MEMORY:
+            case CR_CONNECTION_ERROR:
+                // We're exiting on fatal
+                LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_FATAL_ERROR)
+                         .arg(what)
+                         .arg(text_statements_[static_cast<int>(index)])
+                         .arg(mysql_error(mysql_))
+                         .arg(mysql_errno(mysql_));
+                exit (-1);
+
+            default:
+                // Connection is ok, so it must be an SQL error
+                isc_throw(DbOperationError, what << " for <"
+                          << text_statements_[static_cast<int>(index)]
+                          << ">, reason: "
+                          << mysql_error(mysql_) << " (error code "
+                          << mysql_errno(mysql_) << ")");
+            }
+        }
+    }
+
     /// @brief Prepared statements
     ///
     /// This field is public, because it is used heavily from MySqlConnection
@@ -294,8 +421,6 @@ public:
     MySqlHolder mysql_;
 };
 
-
-
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 

File diff suppressed because it is too large
+ 1148 - 286
src/lib/dhcpsrv/mysql_host_data_source.cc


+ 1 - 16
src/lib/dhcpsrv/mysql_host_data_source.h

@@ -84,6 +84,7 @@ public:
     /// for a specified identifier. This method may return multiple hosts
     /// because a particular client may have reservations in multiple subnets.
     ///
+    /// @param identifier_type Identifier type.
     /// @param identifier_begin Pointer to a begining of a buffer containing
     /// an identifier.
     /// @param identifier_len Identifier length.
@@ -251,22 +252,6 @@ public:
     /// Rolls back all pending database operations.
     virtual void rollback();
 
-    /// @brief Statement Tags
-    ///
-    /// The contents of the enum are indexes into the list of SQL statements
-    enum StatementIndex {
-        INSERT_HOST,            // Insert new host to collection
-        INSERT_V6_RESRV,        // Insert v6 reservation
-        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
-        GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
-        GET_HOST_SUBID_ADDR,    // Gets host by IPv4 SubnetID and IPv4 address
-        GET_HOST_PREFIX,        // Gets host by IPv6 prefix
-        GET_VERSION,            // Obtain version number
-        NUM_STATEMENTS          // Number of statements
-    };
-
 private:
 
     /// @brief Pointer to the implementation of the @ref MySqlHostDataSource.

+ 1 - 29
src/lib/dhcpsrv/mysql_lease_mgr.cc

@@ -15,7 +15,6 @@
 
 #include <boost/static_assert.hpp>
 #include <mysqld_error.h>
-#include <errmsg.h>
 
 #include <iostream>
 #include <iomanip>
@@ -2047,34 +2046,7 @@ MySqlLeaseMgr::rollback() {
 void
 MySqlLeaseMgr::checkError(int status, StatementIndex index,
                            const char* what) const {
-    if (status != 0) {
-        switch(mysql_errno(conn_.mysql_)) {
-            // These are the ones we consider fatal. Remember this method is
-            // used to check errors of API calls made subsequent to successfully
-            // connecting.  Errors occuring while attempting to connect are
-            // checked in the connection code. An alternative would be to call
-            // mysql_ping() - assuming autoreconnect is off. If that fails
-            // then we know connection is toast.
-            case CR_SERVER_GONE_ERROR:
-            case CR_SERVER_LOST:
-            case CR_OUT_OF_MEMORY:
-            case CR_CONNECTION_ERROR:
-                // We're exiting on fatal
-                LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_FATAL_ERROR)
-                         .arg(what)
-                         .arg(conn_.text_statements_[index])
-                         .arg(mysql_error(conn_.mysql_))
-                         .arg(mysql_errno(conn_.mysql_));
-                exit (-1);
-
-            default:
-                // Connection is ok, so it must be an SQL error
-                isc_throw(DbOperationError, what << " for <"
-                          << conn_.text_statements_[index] << ">, reason: "
-                          << mysql_error(conn_.mysql_) << " (error code "
-                          << mysql_errno(conn_.mysql_) << ")");
-        }
-    }
+    conn_.checkError(status, index, what);
 }
 
 }; // end of isc::dhcp namespace

+ 1 - 17
src/lib/dhcpsrv/mysql_lease_mgr.h

@@ -593,23 +593,7 @@ private:
 
     /// @brief Check Error and Throw Exception
     ///
-    /// Virtually all MySQL functions return a status which, if non-zero,
-    /// indicates an error.  This function centralizes the error checking
-    /// code.
-    ///
-    /// It is used to determine whether or not the function succeeded, and
-    /// in the event of failures, decide whether or not those failures are
-    /// recoverable.
-    ///
-    /// If the error is recoverable, the method will throw a DbOperationError.
-    /// In the error is deemed unrecoverable, such as a loss of connectivity
-    /// with the server, this method will log the error and call exit(-1);
-    ///
-    /// @todo Calling exit() is viewed as a short term solution for Kea 1.0.
-    /// Two tickets are likely to alter this behavior, first is #3639, which
-    /// calls for the ability to attempt to reconnect to the database. The
-    /// second ticket, #4087 which calls for the implementation of a generic,
-    /// FatalException class which will propagate outward.
+    /// This method invokes @ref MySqlConnection::checkError.
     ///
     /// @param status Status code: non-zero implies an error
     /// @param index Index of statement that caused the error

+ 9 - 2
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -164,7 +164,14 @@ template <> void ValueParser<std::string>::build(ConstElementPtr value) {
     // Invoke common code for all specializations of build().
     buildCommon(value);
 
-    value_ = value->str();
+    // For strings we need to use stringValue() rather than str().
+    // str() returns fully escaped special characters, so
+    // single backslash would be misrepresented as "\\".
+    if (value->getType() == Element::string) {
+        value_ = value->stringValue();
+    } else {
+        value_ = value->str();
+    }
     boost::erase_all(value_, "\"");
 }
 
@@ -571,7 +578,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);
         }

+ 32 - 1
src/lib/dhcpsrv/tests/cfg_option_unittest.cc

@@ -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

+ 44 - 0
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -655,6 +655,50 @@ TEST_F(ParseConfigTest, minimalOptionDataTest) {
     EXPECT_EQ(val, opt_ptr->toText());
 }
 
+/// @brief Check parsing of options with escape characters.
+///
+/// Note that this tests basic operation of the OptionDataListParser and
+/// OptionDataParser.  It uses a simple configuration consisting of one
+/// one definition and matching option data.  It verifies that the option
+/// is parsed and committed to storage correctly and that its content
+/// has the actual character (e.g. an actual backslash, not double backslash).
+TEST_F(ParseConfigTest, escapedOptionDataTest) {
+
+    parser_context_->universe_ = Option::V4;
+
+    // We need to use double escapes here. The first backslash will
+    // be consumed by C++ preprocessor, so the actual string will
+    // have two backslash characters: \\SMSBoot\\x64\\wdsnbp.com.
+    //
+    std::string config =
+        "{\"option-data\": [ {"
+        "    \"name\": \"boot-file-name\","
+        "    \"data\": \"\\\\SMSBoot\\\\x64\\\\wdsnbp.com\""
+        " } ]"
+        "}";
+    std::cout << config << std::endl;
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config);
+    ASSERT_EQ(0, rcode);
+
+    // Verify that the option can be retrieved.
+    OptionPtr opt = getOptionPtr("dhcp4", DHO_BOOT_FILE_NAME);
+    ASSERT_TRUE(opt);
+
+    util::OutputBuffer buf(100);
+
+    uint8_t exp[] = { DHO_BOOT_FILE_NAME, 23, '\\', 'S', 'M', 'S', 'B', 'o', 'o',
+                      't', '\\', 'x', '6', '4', '\\', 'w', 'd', 's', 'n', 'b',
+                      'p', '.', 'c', 'o', 'm' };
+    ASSERT_EQ(25, sizeof(exp));
+
+    opt->pack(buf);
+    EXPECT_EQ(Option::OPTION4_HDR_LEN + 23, buf.getLength());
+
+    EXPECT_TRUE(0 == memcmp(buf.getData(), exp, 25));
+}
+
 // This test checks behavior of the configuration parser for option data
 // for different values of csv-format parameter and when there is an option
 // definition present.

+ 259 - 4
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc

@@ -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>
@@ -82,8 +98,8 @@ GenericHostDataSourceTest::initializeHost4(const std::string& address,
     // subnet4 with subnet6.
     static SubnetID subnet4 = 0;
     static SubnetID subnet6 = 100;
-    subnet4++;
-    subnet6++;
+    ++subnet4;
+    ++subnet6;
 
     IOAddress addr(address);
     HostPtr host(new Host(&ident[0], ident.size(), id, subnet4, subnet6, addr));
@@ -229,12 +245,16 @@ 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());
+
+    // Compare DHCPv4 and DHCPv6 options.
+    compareOptions(host1->getCfgOption4(), host2->getCfgOption4());
+    compareOptions(host1->getCfgOption6(), host2->getCfgOption6());
 }
 
 DuidPtr
@@ -313,6 +333,178 @@ 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);
+
+    // Combine option space names with vendor space names in a single list.
+    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());
+
+    // Make sure that the number of option spaces is equal in both
+    // configurations.
+    EXPECT_EQ(option_spaces.size(), cfg1->getOptionSpaceNames().size());
+    EXPECT_EQ(vendor_spaces.size(), cfg1->getVendorIdsSpaceNames().size());
+
+    // Iterate over all option spaces existing in cfg2.
+    BOOST_FOREACH(std::string space, option_spaces) {
+        // Retrieve options belonging to the current option space.
+        OptionContainerPtr options1 = cfg1->getAll(space);
+        OptionContainerPtr options2 = cfg2->getAll(space);
+        ASSERT_TRUE(options1) << "failed for option space " << space;
+        ASSERT_TRUE(options2) << "failed for option space " << space;
+
+        // If number of options doesn't match, the test fails.
+        ASSERT_EQ(options1->size(), options2->size())
+            << "failed for option space " << space;
+
+        // Iterate over all options within this option space.
+        BOOST_FOREACH(OptionDescriptor desc1, *options1) {
+            OptionDescriptor desc2 = cfg2->get(space, desc1.option_->getType());
+            // Compare persistent flag.
+            EXPECT_EQ(desc1.persistent_, desc2.persistent_)
+                << "failed for option " << space << "." << desc1.option_->getType();
+            // Compare formatted value.
+            EXPECT_EQ(desc1.formatted_value_, desc2.formatted_value_)
+                << "failed for option " << space << "." << desc1.option_->getType();
+
+            // Retrieve options.
+            Option* option1 = desc1.option_.get();
+            Option* option2 = desc2.option_.get();
+
+            // Options must be represented by the same C++ class derived from
+            // the Option class.
+            EXPECT_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();
+
+            // Because we use different C++ classes to represent different
+            // options, the simplest way to make sure that the options are
+            // equal is to simply compare them in wire format.
+            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())
+                 << "failed for option " << space << "." << desc1.option_->getType();
+            EXPECT_EQ(0, memcmp(buf1.getData(), buf2.getData(), buf1.getLength()))
+                << "failed for option " << space << "." << desc1.option_->getType();
+        }
+    }
+}
+
+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,
+                                          const AddedOptions& added_options) const {
+
+    OptionDefSpaceContainer defs;
+
+    if ((added_options == DHCP4_ONLY) || (added_options == DHCP4_AND_DHCP6)) {
+        // Add DHCPv4 options.
+        CfgOptionPtr opts = host->getCfgOption4();
+        opts->add(createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+                                             true, formatted, "my-boot-file"),
+                  DHCP4_OPTION_SPACE);
+        opts->add(createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+                                            false, formatted, 64),
+                  DHCP4_OPTION_SPACE);
+        opts->add(createOption<OptionUint32>(Option::V4, 1, false, formatted, 312131),
+                  "vendor-encapsulated-options");
+        opts->add(createAddressOption<Option4AddrLst>(254, false, formatted, "192.0.2.3"),
+                  DHCP4_OPTION_SPACE);
+        opts->add(createEmptyOption(Option::V4, 1, true), "isc");
+        opts->add(createAddressOption<Option4AddrLst>(2, false, formatted, "10.0.0.5",
+                                                      "10.0.0.3", "10.0.3.4"),
+                  "isc");
+
+        // 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");
+    }
+
+    if ((added_options == DHCP6_ONLY) || (added_options == DHCP4_AND_DHCP6)) {
+        // Add DHCPv6 options.
+        CfgOptionPtr opts = host->getCfgOption6();
+        opts->add(createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+                                             true, formatted, "my-boot-file"),
+                  DHCP6_OPTION_SPACE);
+        opts->add(createOption<OptionUint32>(Option::V6, D6O_INFORMATION_REFRESH_TIME,
+                                             false, formatted, 3600),
+                  DHCP6_OPTION_SPACE);
+        opts->add(createVendorOption(Option::V6, false, formatted, 2495),
+                  DHCP6_OPTION_SPACE);
+        opts->add(createAddressOption<Option6AddrLst>(1024, false, formatted,
+                                                      "2001:db8:1::1"),
+                  DHCP6_OPTION_SPACE);
+        opts->add(createEmptyOption(Option::V6, 1, true), "isc2");
+        opts->add(createAddressOption<Option6AddrLst>(2, false, formatted, "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");
+    }
+
+    // Register created "runtime" option definitions. They will be used by a
+    // host data source to convert option data into the appropriate option
+    // classes when the options are retrieved.
+    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 +1090,69 @@ void GenericHostDataSourceTest::testMultipleReservationsDifferentOrder(){
 
 }
 
+void GenericHostDataSourceTest::testOptionsReservations4(const bool formatted) {
+    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, formatted, DHCP4_ONLY));
+    // 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));
+}
+
+void GenericHostDataSourceTest::testOptionsReservations6(const bool formatted) {
+    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, formatted, DHCP6_ONLY));
+    // 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(const bool formatted) {
+    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, formatted, DHCP4_AND_DHCP6));
+    // Insert host, options and IPv6 reservations into respective tables.
+    ASSERT_NO_THROW(hdsptr_->add(host));
+
+    // 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

+ 239 - 1
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h

@@ -7,10 +7,15 @@
 #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/algorithm/string/join.hpp>
+#include <boost/shared_ptr.hpp>
 #include <gtest/gtest.h>
+#include <sstream>
 #include <vector>
 
 namespace isc {
@@ -30,6 +35,16 @@ public:
         V6
     };
 
+    /// @brief Options to be inserted into a host.
+    ///
+    /// Parameter of this type is passed to the @ref addTestOptions to
+    /// control which option types should be inserted into a host.
+    enum AddedOptions {
+        DHCP4_ONLY,
+        DHCP6_ONLY,
+        DHCP4_AND_DHCP6
+    };
+
     /// @brief Default constructor.
     GenericHostDataSourceTest();
 
@@ -122,7 +137,202 @@ 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.
+    ///
+    /// This method uses gtest macros to signal errors.
+    ///
+    /// @param cfg1 First configuration.
+    /// @param cfg2 Second configuration.
+    void compareOptions(const ConstCfgOptionPtr& cfg1,
+                        const ConstCfgOptionPtr& cfg2) const;
+
+    /// @brief Creates an opton 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;
+
+    /// @brief Adds multiple options into the host.
+    ///
+    /// This method creates the following options into the host object:
+    /// - DHCPv4 boot file name option,
+    /// - DHCPv4 default ip ttl option,
+    /// - DHCPv4 option 1 within vendor-encapsulated-options space,
+    /// - DHCPv4 option 254 with a single IPv4 address,
+    /// - DHCPv4 option 1 within isc option space,
+    /// - DHCPv6 boot file url option,
+    /// - DHCPv6 information refresh time option,
+    /// - DHCPv6 vendor option with vendor id 2495,
+    /// - DHCPv6 option 1024, with a sigle IPv6 address,
+    /// - DHCPv6 empty option 1, within isc2 option space,
+    /// - DHCPv6 option 2, within isc2 option space with 3 IPv6 addresses,
+    ///
+    /// This method also creates option definitions for the non-standard
+    /// options and registers them in the LibDHCP as runtime option
+    /// definitions.
+    ///
+    /// @param host Host object into which options should be added.
+    /// @param formatted A boolean value selecting if the formatted option
+    /// value should be used (if true), or binary value (if false).
+    /// @param added_options Controls which options should be inserted into
+    /// a host: DHCPv4, DHCPv6 options or both.
+    void addTestOptions(const HostPtr& host, const bool formatted,
+                        const AddedOptions& added_options) const;
 
     /// @brief Pointer to the host data source
     HostDataSourcePtr hdsptr_;
@@ -238,6 +448,33 @@ 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.
+    ///
+    /// @param formatted Boolean value indicating if the option values
+    /// should be stored in the textual format in the database.
+    void testOptionsReservations4(const bool formatted);
+
+    /// @brief Test that DHCPv6 options can be inserted and retrieved from
+    /// the database.
+    ///
+    /// Uses gtest macros to report failures.
+    ///
+    /// @param formatted Boolean value indicating if the option values
+    /// should be stored in the textual format in the database.
+    void testOptionsReservations6(const bool formatted);
+
+    /// @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.
+    ///
+    /// @param formatted Boolean value indicating if the option values
+    /// should be stored in the textual format in the database.
+    void testOptionsReservations46(const bool formatted);
+
     /// @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 +494,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

+ 83 - 0
src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc

@@ -427,4 +427,87 @@ TEST_F(MySqlHostDataSourceTest, addDuplicate4) {
     testAddDuplicate4();
 }
 
+// This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations4) {
+    testOptionsReservations4(false);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations6) {
+    testOptionsReservations6(false);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations46) {
+    testOptionsReservations46(false);
+}
+
+// This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations4) {
+    testOptionsReservations4(true);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations6) {
+    testOptionsReservations6(true);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+// textual format and retrieved with a single query to the database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations46) {
+    testOptionsReservations46(true);
+}
+
+// This test checks transactional insertion of the host information
+// into the database. The failure to insert host information at
+// any stage should cause the whole transaction to be rolled back.
+TEST_F(MySqlHostDataSourceTest, testAddRollback) {
+    // Make sure we have the pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // To test the transaction rollback mechanism we need to cause the
+    // insertion of host information to fail at some stage. The 'hosts'
+    // table should be updated correctly but the failure should occur
+    // when inserting reservations or options. The simplest way to
+    // achieve that is to simply drop one of the tables. To do so, we
+    // connect to the database and issue a DROP query.
+    MySqlConnection::ParameterMap params;
+    params["name"] = "keatest";
+    params["user"] = "keatest";
+    params["password"] = "keatest";
+    MySqlConnection conn(params);
+    ASSERT_NO_THROW(conn.openDatabase());
+    int status = mysql_query(conn.mysql_,
+                             "DROP TABLE IF EXISTS ipv6_reservations");
+    ASSERT_EQ(0, status) << mysql_error(conn.mysql_);
+
+    // Create a host with a reservation.
+    HostPtr host = initializeHost6("2001:db8:1::1", Host::IDENT_HWADDR, false);
+    // Let's assign some DHCPv4 subnet to the host, because we will use the
+    // DHCPv4 subnet to try to retrieve the host after failed insertion.
+    host->setIPv4SubnetID(SubnetID(4));
+
+    // There is no ipv6_reservations table, so the insertion should fail.
+    ASSERT_THROW(hdsptr_->add(host), DbOperationError);
+
+    // Even though we have created a DHCPv6 host, we can't use get6()
+    // method to retrieve the host from the database, because the
+    // query would expect that the ipv6_reservations table is present.
+    // Therefore, the query would fail. Instead, we use the get4 method
+    // which uses the same client identifier, but doesn't attempt to
+    // retrieve the data from ipv6_reservations table. The query should
+    // pass but return no host because the (insert) transaction is expected
+    // to be rolled back.
+    ConstHostPtr from_hds = hdsptr_->get4(host->getIPv4SubnetID(),
+                                          host->getIdentifierType(),
+                                          &host->getIdentifier()[0],
+                                          host->getIdentifier().size());
+    EXPECT_FALSE(from_hds);
+}
+
 }; // Of anonymous namespace

+ 1 - 0
src/lib/dhcpsrv/writable_host_data_source.h

@@ -46,6 +46,7 @@ public:
     /// for a specified identifier. This method may return multiple hosts
     /// because a particular client may have reservations in multiple subnets.
     ///
+    /// @param identifier_type Identifier type.
     /// @param identifier_begin Pointer to a begining of a buffer containing
     /// an identifier.
     /// @param identifier_len Identifier length.

+ 34 - 0
src/share/database/scripts/mysql/dhcpdb_create.mysql

@@ -436,6 +436,40 @@ ALTER TABLE hosts
     ADD CONSTRAINT fk_host_identifier_type FOREIGN KEY (dhcp_identifier_type)
     REFERENCES host_identifier_type (type);
 
+# Store DHCPv6 option code as 16-bit unsigned integer.
+ALTER TABLE dhcp6_options MODIFY code SMALLINT UNSIGNED NOT NULL;
+
+# Subnet identifier is unsigned.
+ALTER TABLE dhcp4_options MODIFY dhcp4_subnet_id INT UNSIGNED NULL;
+ALTER TABLE dhcp6_options MODIFY dhcp6_subnet_id INT UNSIGNED NULL;
+
+# Scopes associate DHCP options stored in dhcp4_options and
+# dhcp6_options tables with hosts, subnets, classes or indicate
+# that they are global options.
+CREATE TABLE IF NOT EXISTS dhcp_option_scope (
+    scope_id TINYINT UNSIGNED PRIMARY KEY NOT NULL,
+    scope_name VARCHAR(32)
+) ENGINE = INNODB;
+
+START TRANSACTION;
+INSERT INTO dhcp_option_scope VALUES (0, "global");
+INSERT INTO dhcp_option_scope VALUES (1, "subnet");
+INSERT INTO dhcp_option_scope VALUES (2, "client-class");
+INSERT INTO dhcp_option_scope VALUES (3, "host");
+COMMIT;
+
+# Add scopes into table holding DHCPv4 options
+ALTER TABLE dhcp4_options ADD COLUMN scope_id TINYINT UNSIGNED NOT NULL;
+ALTER TABLE dhcp4_options
+    ADD CONSTRAINT fk_dhcp4_option_scope FOREIGN KEY (scope_id)
+    REFERENCES dhcp_option_scope (scope_id);
+
+# Add scopes into table holding DHCPv6 options
+ALTER TABLE dhcp6_options ADD COLUMN scope_id TINYINT UNSIGNED NOT NULL;
+ALTER TABLE dhcp6_options
+    ADD CONSTRAINT fk_dhcp6_option_scope FOREIGN KEY (scope_id)
+    REFERENCES dhcp_option_scope (scope_id);
+
 # Update the schema version number
 UPDATE schema_version
 SET version = '4', minor = '2';

+ 1 - 0
src/share/database/scripts/mysql/dhcpdb_drop.mysql

@@ -17,6 +17,7 @@ DROP TABLE IF EXISTS dhcp4_options;
 DROP TABLE IF EXISTS dhcp6_options;
 DROP TABLE IF EXISTS host_identifier_type;
 DROP TABLE IF EXISTS lease_state;
+DROP TABLE IF EXISTS dhcp_option_scope;
 DROP TRIGGER IF EXISTS host_BDEL;
 DROP PROCEDURE IF EXISTS lease4DumpHeader;
 DROP PROCEDURE IF EXISTS lease4DumpData;