Browse Source

[master] Merge branch 'trac4281'

Marcin Siodelski 9 years ago
parent
commit
b8a306a27d

+ 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

+ 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

+ 1 - 1
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -571,7 +571,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

+ 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;