Browse Source

[master] Merge branch 'trac3682_rebase' (MySQL Host Data Source)

Tomek Mrugalski 9 years ago
parent
commit
275b40fb94

+ 3 - 0
src/lib/dhcpsrv/Makefile.am

@@ -103,9 +103,11 @@ libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
 libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h
 libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h
 libkea_dhcpsrv_la_SOURCES += database_connection.cc database_connection.h
 libkea_dhcpsrv_la_SOURCES += database_connection.cc database_connection.h
+libkea_dhcpsrv_la_SOURCES += db_exceptions.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += host_container.h
 libkea_dhcpsrv_la_SOURCES += host_container.h
+libkea_dhcpsrv_la_SOURCES += host_data_source_factory.cc host_data_source_factory.h
 libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
 libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
 libkea_dhcpsrv_la_SOURCES += hosts_log.cc hosts_log.h
 libkea_dhcpsrv_la_SOURCES += hosts_log.cc hosts_log.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
@@ -122,6 +124,7 @@ libkea_dhcpsrv_la_SOURCES += memfile_lease_storage.h
 if HAVE_MYSQL
 if HAVE_MYSQL
 libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += mysql_connection.cc mysql_connection.h
 libkea_dhcpsrv_la_SOURCES += mysql_connection.cc mysql_connection.h
+libkea_dhcpsrv_la_SOURCES += mysql_host_data_source.cc mysql_host_data_source.h
 endif
 endif
 
 
 libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
 libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h

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

@@ -64,6 +64,20 @@ public:
 class BaseHostDataSource {
 class BaseHostDataSource {
 public:
 public:
 
 
+    /// @brief Specifies the type of an identifier.
+    ///
+    /// This is currently used only by MySQL host data source for now, but
+    /// it is envisagad that it will be used by other host data sources
+    /// in the future. Also, this list will grow over time. It is likely
+    /// that we'll implement other identifiers in the future, e.g. remote-id.
+    ///
+    /// Those value correspond direclty to dhcp_identifier_type in hosts
+    /// table in MySQL schema.
+    enum IdType {
+        ID_HWADDR = 0, ///< Hardware address
+        ID_DUID = 1    ///< DUID/client-id
+    };
+
     /// @brief Default destructor implementation.
     /// @brief Default destructor implementation.
     virtual ~BaseHostDataSource() { }
     virtual ~BaseHostDataSource() { }
 
 
@@ -180,6 +194,24 @@ public:
     /// @param host Pointer to the new @c Host object being added.
     /// @param host Pointer to the new @c Host object being added.
     virtual void add(const HostPtr& host) = 0;
     virtual void add(const HostPtr& host) = 0;
 
 
+    /// @brief Return backend type
+    ///
+    /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+    ///
+    /// @return Type of the backend.
+    virtual std::string getType() const = 0;
+
+    /// @brief Commit Transactions
+    ///
+    /// Commits all pending database operations.  On databases that don't
+    /// support transactions, this is a no-op.
+    virtual void commit() {};
+
+    /// @brief Rollback Transactions
+    ///
+    /// Rolls back all pending database operations.  On databases that don't
+    /// support transactions, this is a no-op.
+    virtual void rollback() {};
 };
 };
 
 
 }
 }

+ 9 - 0
src/lib/dhcpsrv/cfg_hosts.h

@@ -237,6 +237,15 @@ public:
     /// has already been added to the IPv4 or IPv6 subnet.
     /// has already been added to the IPv4 or IPv6 subnet.
     virtual void add(const HostPtr& host);
     virtual void add(const HostPtr& host);
 
 
+    /// @brief Return backend type
+    ///
+    /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+    ///
+    /// @return Type of the backend.
+    virtual std::string getType() const {
+        return (std::string("configuration file"));
+    }
+
 private:
 private:
 
 
     /// @brief Returns @c Host objects for the specific identifier and type.
     /// @brief Returns @c Host objects for the specific identifier and type.

+ 2 - 0
src/lib/dhcpsrv/database_connection.cc

@@ -25,6 +25,8 @@ using namespace std;
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
+const time_t DatabaseConnection::MAX_DB_TIME = 2147483647;
+
 std::string
 std::string
 DatabaseConnection::getParameter(const std::string& name) const {
 DatabaseConnection::getParameter(const std::string& name) const {
     ParameterMap::const_iterator param = parameters_.find(name);
     ParameterMap::const_iterator param = parameters_.find(name);

+ 9 - 0
src/lib/dhcpsrv/database_connection.h

@@ -55,6 +55,15 @@ public:
 /// @ref BaseHostDataSource derived classes.
 /// @ref BaseHostDataSource derived classes.
 class DatabaseConnection : public boost::noncopyable {
 class DatabaseConnection : public boost::noncopyable {
 public:
 public:
+
+    /// @brief Defines maximum value for time that can be reliably stored.
+    ///
+    /// @todo: Is this common for MySQL and Postgres? Maybe we should have
+    /// specific values for each backend?
+    ///
+    /// If I'm still alive I'll be too old to care. You fix it.
+    static const time_t MAX_DB_TIME;
+
     /// @brief Database configuration parameter map
     /// @brief Database configuration parameter map
     typedef std::map<std::string, std::string> ParameterMap;
     typedef std::map<std::string, std::string> ParameterMap;
 
 

+ 54 - 0
src/lib/dhcpsrv/db_exceptions.h

@@ -0,0 +1,54 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DB_EXCEPTIONS_H
+#define DB_EXCEPTIONS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Multiple lease records found where one expected
+class MultipleRecords : public Exception {
+public:
+    MultipleRecords(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Attempt to update lease that was not there
+class NoSuchLease : public Exception {
+public:
+    NoSuchLease(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Data is truncated
+class DataTruncated : public Exception {
+public:
+    DataTruncated(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Database duplicate entry error
+class DuplicateEntry : public Exception {
+public:
+    DuplicateEntry(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+};
+};
+
+#endif

+ 113 - 0
src/lib/dhcpsrv/host_data_source_factory.cc

@@ -0,0 +1,113 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "config.h"
+
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/hosts_log.h>
+
+#ifdef HAVE_MYSQL
+#include <dhcpsrv/mysql_host_data_source.h>
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <map>
+#include <sstream>
+#include <utility>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+
+boost::scoped_ptr<BaseHostDataSource>&
+HostDataSourceFactory::getHostDataSourcePtr() {
+    static boost::scoped_ptr<BaseHostDataSource> hostDataSourcePtr;
+    return (hostDataSourcePtr);
+}
+
+void
+HostDataSourceFactory::create(const std::string& dbaccess) {
+    const std::string type = "type";
+
+    // Parse the access string and create a redacted string for logging.
+    DatabaseConnection::ParameterMap parameters =
+            DatabaseConnection::parse(dbaccess);
+    std::string redacted =
+            DatabaseConnection::redactedAccessString(parameters);
+
+    // Is "type" present?
+    if (parameters.find(type) == parameters.end()) {
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
+        isc_throw(InvalidParameter, "Database configuration parameters do not "
+                  "contain the 'type' keyword");
+    }
+
+
+    // Yes, check what it is.
+#ifdef HAVE_MYSQL
+    if (parameters[type] == string("mysql")) {
+        LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_DB).arg(redacted);
+        getHostDataSourcePtr().reset(new MySqlHostDataSource(parameters));
+        return;
+    }
+#endif
+
+#ifdef HAVE_PGSQL
+    if (parameters[type] == string("postgresql")) {
+        LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_DB).arg(redacted);
+        isc_throw(NotImplemented, "Sorry, Postgres backend for host reservations "
+                  "is not implemented yet.");
+        // Set pgsql data source here, when it will be implemented.
+        return;
+    }
+#endif
+
+    // Get here on no match.
+    LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+    isc_throw(InvalidType, "Database access parameter 'type' does "
+              "not specify a supported database backend");
+}
+
+void
+HostDataSourceFactory::destroy() {
+    // Destroy current host data source instance.  This is a no-op if no host
+    // data source is available.
+    if (getHostDataSourcePtr()) {
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, HOSTS_CFG_CLOSE_HOST_DATA_SOURCE)
+            .arg(getHostDataSourcePtr()->getType());
+    }
+    getHostDataSourcePtr().reset();
+}
+
+BaseHostDataSource&
+HostDataSourceFactory::instance() {
+    BaseHostDataSource* hdsptr = getHostDataSourcePtr().get();
+    if (hdsptr == NULL) {
+        isc_throw(NoHostDataSourceManager,
+                "no current host data source instance is available");
+    }
+    return (*hdsptr);
+}
+
+}; // namespace dhcp
+}; // namespace isc

+ 114 - 0
src/lib/dhcpsrv/host_data_source_factory.h

@@ -0,0 +1,114 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HOST_DATA_SOURCE_FACTORY_H
+#define HOST_DATA_SOURCE_FACTORY_H
+
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/database_connection.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Invalid type exception
+///
+/// Thrown when the factory doesn't recognise the type of the backend.
+class InvalidType : public Exception {
+public:
+    InvalidType(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief No host data source instance exception
+///
+/// Thrown if an attempt is made to get a reference to the current
+/// host data source instance and none is currently available.
+class NoHostDataSourceManager : public Exception {
+public:
+    NoHostDataSourceManager(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Host Data Source Factory
+///
+/// This class comprises nothing but static methods used to create a host data source object.
+/// It analyzes the database information passed to the creation function and instantiates
+/// an appropriate host data source object based on the type requested.
+///
+/// Strictly speaking these functions could be stand-alone functions.  However,
+/// it is convenient to encapsulate them in a class for naming purposes.
+
+class HostDataSourceFactory {
+public:
+    /// @brief Create an instance of a host data source.
+    ///
+    /// Each database backend has its own host data source type. This static
+    /// method sets the "current" host data source to be an object of the
+    /// appropriate type.  The actual host data source is returned by the
+    /// "instance" method.
+    ///
+    /// @note When called, the current host data source is <b>always</b> destroyed
+    ///       and a new one created - even if the parameters are the same.
+    ///
+    /// dbaccess is a generic way of passing parameters. Parameters are passed
+    /// in the "name=value" format, separated by spaces.  The data MUST include
+    /// a keyword/value pair of the form "type=dbtype" giving the database
+    /// type, e.q. "mysql" or "sqlite3".
+    ///
+    /// @param dbaccess Database access parameters.  These are in the form of
+    ///        "keyword=value" pairs, separated by spaces. They are backend-
+    ///        -end specific, although must include the "type" keyword which
+    ///        gives the backend in use.
+    ///
+    /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
+    ///        keyword.
+    /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
+    ///        identify a supported backend.
+    static void create(const std::string& dbaccess);
+
+    /// @brief Destroy host data source
+    ///
+    /// Destroys the current host data source object. This should have the effect
+    /// of closing the database connection.  The method is a no-op if no
+    /// host data source is available.
+    static void destroy();
+
+    /// @brief Return current host data source
+    ///
+    /// @returns An instance of the "current" host data source.  An exception
+    /// will be thrown if none is available.
+    ///
+    /// @throw NoHostDataSourceManager No host data source is available: use
+    ///        create() to create one before calling this method.
+    static BaseHostDataSource& instance();
+
+private:
+    /// @brief Hold pointer to host data source instance
+    ///
+    /// Holds a pointer to the singleton host data source.  The singleton
+    /// is encapsulated in this method to avoid a "static initialization
+    /// fiasco" if defined in an external static variable.
+    static boost::scoped_ptr<BaseHostDataSource>& getHostDataSourcePtr();
+
+};
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif

+ 10 - 4
src/lib/dhcpsrv/host_mgr.cc

@@ -17,6 +17,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/hosts_log.h>
 #include <dhcpsrv/hosts_log.h>
+#include <dhcpsrv/host_data_source_factory.h>
 
 
 namespace {
 namespace {
 
 
@@ -37,6 +38,8 @@ namespace dhcp {
 
 
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 
 
+boost::shared_ptr<BaseHostDataSource> HostMgr::alternate_source;
+
 boost::scoped_ptr<HostMgr>&
 boost::scoped_ptr<HostMgr>&
 HostMgr::getHostMgrPtr() {
 HostMgr::getHostMgrPtr() {
     static boost::scoped_ptr<HostMgr> host_mgr_ptr;
     static boost::scoped_ptr<HostMgr> host_mgr_ptr;
@@ -44,11 +47,15 @@ HostMgr::getHostMgrPtr() {
 }
 }
 
 
 void
 void
-HostMgr::create(const std::string&) {
+HostMgr::create(const std::string& access) {
     getHostMgrPtr().reset(new HostMgr());
     getHostMgrPtr().reset(new HostMgr());
 
 
-    /// @todo Initialize alternate_source here, using the parameter.
-    /// For example: alternate_source.reset(new MysqlHostDataSource(access)).
+    if (!access.empty()) {
+        HostDataSourceFactory::create(access);
+
+        /// @todo Initialize alternate_source here.
+        //alternate_source = HostDataSourceFactory::getHostDataSourcePtr();
+    }
 }
 }
 
 
 HostMgr&
 HostMgr&
@@ -152,7 +159,6 @@ HostMgr::get6(const SubnetID& subnet_id,
     return (host);
     return (host);
 }
 }
 
 
-
 void
 void
 HostMgr::add(const HostPtr&) {
 HostMgr::add(const HostPtr&) {
     isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");
     isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");

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

@@ -198,6 +198,15 @@ public:
     /// @param host Pointer to the new @c Host object being added.
     /// @param host Pointer to the new @c Host object being added.
     virtual void add(const HostPtr& host);
     virtual void add(const HostPtr& host);
 
 
+    /// @brief Return backend type
+    ///
+    /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+    ///
+    /// @return Type of the backend.
+    virtual std::string getType() const {
+        return (std::string("host_mgr"));
+    }
+
 private:
 private:
 
 
     /// @brief Private default constructor.
     /// @brief Private default constructor.
@@ -206,7 +215,7 @@ private:
     /// @brief Pointer to an alternate host data source.
     /// @brief Pointer to an alternate host data source.
     ///
     ///
     /// If this pointer is NULL, the source is not in use.
     /// If this pointer is NULL, the source is not in use.
-    boost::scoped_ptr<BaseHostDataSource> alternate_source;
+    static boost::shared_ptr<BaseHostDataSource> alternate_source;
 
 
     /// @brief Returns a pointer to the currently used instance of the
     /// @brief Returns a pointer to the currently used instance of the
     /// @c HostMgr.
     /// @c HostMgr.

+ 4 - 0
src/lib/dhcpsrv/hosts_messages.mes

@@ -19,6 +19,10 @@ This debug message is issued when new host (with reservations) is added to
 the server's configuration. The argument describes the host and its
 the server's configuration. The argument describes the host and its
 reservations in detail.
 reservations in detail.
 
 
+% HOSTS_CFG_CLOSE_HOST_DATA_SOURCE Closing host data source: %1
+This is a normal message being printed when the server closes host data
+source connection.
+
 % HOSTS_CFG_GET_ALL_ADDRESS4 get all hosts with reservations for IPv4 address %1
 % HOSTS_CFG_GET_ALL_ADDRESS4 get all hosts with reservations for IPv4 address %1
 This debug message is issued when starting to retrieve all hosts, holding the
 This debug message is issued when starting to retrieve all hosts, holding the
 reservation for the specific IPv4 address, from the configuration. The
 reservation for the specific IPv4 address, from the configuration. The

+ 0 - 2
src/lib/dhcpsrv/lease_mgr.cc

@@ -35,8 +35,6 @@ using namespace std;
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
-const time_t LeaseMgr::MAX_DB_TIME = 2147483647;
-
 Lease6Ptr
 Lease6Ptr
 LeaseMgr::getLease6(Lease::Type type, const DUID& duid,
 LeaseMgr::getLease6(Lease::Type type, const DUID& duid,
                     uint32_t iaid, SubnetID subnet_id) const {
                     uint32_t iaid, SubnetID subnet_id) const {

+ 1 - 27
src/lib/dhcpsrv/lease_mgr.h

@@ -22,7 +22,7 @@
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
-#include <exceptions/exceptions.h>
+#include <dhcpsrv/db_exceptions.h>
 
 
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -68,28 +68,6 @@
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
-/// @brief Multiple lease records found where one expected
-class MultipleRecords : public Exception {
-public:
-    MultipleRecords(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-/// @brief Attempt to update lease that was not there
-class NoSuchLease : public Exception {
-public:
-    NoSuchLease(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-/// @brief Data is truncated
-class DataTruncated : public Exception {
-public:
-    DataTruncated(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-
 /// @brief Abstract Lease Manager
 /// @brief Abstract Lease Manager
 ///
 ///
 /// This is an abstract API for lease database backends. It provides unified
 /// This is an abstract API for lease database backends. It provides unified
@@ -102,10 +80,6 @@ public:
 /// of those classes for details.
 /// of those classes for details.
 class LeaseMgr {
 class LeaseMgr {
 public:
 public:
-    /// @brief Defines maximum value for time that can be reliably stored.
-    // If I'm still alive I'll be too old to care. You fix it.
-    static const time_t MAX_DB_TIME;
-
     /// @brief Constructor
     /// @brief Constructor
     ///
     ///
     LeaseMgr()
     LeaseMgr()

+ 98 - 0
src/lib/dhcpsrv/mysql_connection.cc

@@ -192,8 +192,106 @@ MySqlConnection::~MySqlConnection() {
     }
     }
     statements_.clear();
     statements_.clear();
     text_statements_.clear();
     text_statements_.clear();
+}
+
+// Time conversion methods.
+//
+// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
+// from the current timezone to UTC for storage, and from UTC to the current
+// timezone for retrieval.
+//
+// This causes no problems providing that:
+// a) cltt is given in local time
+// b) We let the system take care of timezone conversion when converting
+//    from a time read from the database into a local time.
+void
+MySqlConnection::convertToDatabaseTime(const time_t input_time,
+                                       MYSQL_TIME& output_time) {
+
+    // Convert to broken-out time
+    struct tm time_tm;
+    (void) localtime_r(&input_time, &time_tm);
+
+    // Place in output expire structure.
+    output_time.year = time_tm.tm_year + 1900;
+    output_time.month = time_tm.tm_mon + 1;     // Note different base
+    output_time.day = time_tm.tm_mday;
+    output_time.hour = time_tm.tm_hour;
+    output_time.minute = time_tm.tm_min;
+    output_time.second = time_tm.tm_sec;
+    output_time.second_part = 0;                // No fractional seconds
+    output_time.neg = my_bool(0);               // Not negative
+}
+
+void
+MySqlConnection::convertToDatabaseTime(const time_t cltt,
+                                     const uint32_t valid_lifetime,
+                                     MYSQL_TIME& expire) {
+
+    // Calculate expiry time. Store it in the 64-bit value so as we can detect
+    // overflows.
+    int64_t expire_time_64 = static_cast<int64_t>(cltt) +
+        static_cast<int64_t>(valid_lifetime);
+
+    // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
+    // beyond the max value of int32_t.
+    if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
+        isc_throw(BadValue, "Time value is too large: " << expire_time_64);
+    }
+
+    const time_t expire_time = static_cast<const time_t>(expire_time_64);
 
 
+    // Convert to broken-out time
+    struct tm expire_tm;
+    (void) localtime_r(&expire_time, &expire_tm);
+
+    // Place in output expire structure.
+    expire.year = expire_tm.tm_year + 1900;
+    expire.month = expire_tm.tm_mon + 1;     // Note different base
+    expire.day = expire_tm.tm_mday;
+    expire.hour = expire_tm.tm_hour;
+    expire.minute = expire_tm.tm_min;
+    expire.second = expire_tm.tm_sec;
+    expire.second_part = 0;                  // No fractional seconds
+    expire.neg = my_bool(0);                 // Not negative
+}
+
+void
+MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire,
+                                       uint32_t valid_lifetime, time_t& cltt) {
+
+    // Copy across fields from MYSQL_TIME structure.
+    struct tm expire_tm;
+    memset(&expire_tm, 0, sizeof(expire_tm));
+
+    expire_tm.tm_year = expire.year - 1900;
+    expire_tm.tm_mon = expire.month - 1;
+    expire_tm.tm_mday = expire.day;
+    expire_tm.tm_hour = expire.hour;
+    expire_tm.tm_min = expire.minute;
+    expire_tm.tm_sec = expire.second;
+    expire_tm.tm_isdst = -1;    // Let the system work out about DST
+
+    // Convert to local time
+    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::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_));
+        }
+}
+
+
 } // namespace isc::dhcp
 } // namespace isc::dhcp
 } // namespace isc
 } // namespace isc

+ 83 - 1
src/lib/dhcpsrv/mysql_connection.h

@@ -17,7 +17,7 @@
 
 
 #include <dhcpsrv/database_connection.h>
 #include <dhcpsrv/database_connection.h>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
-#include <mysql.h>
+#include <mysql/mysql.h>
 #include <vector>
 #include <vector>
 
 
 namespace isc {
 namespace isc {
@@ -32,6 +32,11 @@ namespace dhcp {
 extern const my_bool MLM_FALSE;
 extern const my_bool MLM_FALSE;
 extern const my_bool MLM_TRUE;
 extern const my_bool MLM_TRUE;
 
 
+// Define the current database schema values
+
+const uint32_t CURRENT_VERSION_VERSION = 3;
+const uint32_t CURRENT_VERSION_MINOR = 0;
+
 /// @brief Fetch and Release MySQL Results
 /// @brief Fetch and Release MySQL Results
 ///
 ///
 /// When a MySQL statement is expected, to fetch the results the function
 /// When a MySQL statement is expected, to fetch the results the function
@@ -189,6 +194,83 @@ public:
     /// @throw DbOpenError Error opening the database
     /// @throw DbOpenError Error opening the database
     void openDatabase();
     void openDatabase();
 
 
+    ///@{
+    /// The following methods are used to convert between times and time
+    /// intervals stored in the Lease object, and the times stored in the
+    /// database.  The reason for the difference is because in the DHCP server,
+    /// the cltt (Client Time Since Last Transmission) is the natural data; in
+    /// the lease file - which may be read by the user - it is the expiry time
+    /// of the lease.
+
+    /// @brief Convert time_t value to database time.
+    ///
+    /// @param input_time A time_t value representing time.
+    /// @param output_time Reference to MYSQL_TIME object where converted time
+    ///        will be put.
+    static
+    void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
+
+    /// @brief Convert Lease Time to Database Times
+    ///
+    /// Within the DHCP servers, times are stored as client last transmit time
+    /// and valid lifetime.  In the database, the information is stored as
+    /// valid lifetime and "expire" (time of expiry of the lease).  They are
+    /// related by the equation:
+    ///
+    /// - expire = client last transmit time + valid lifetime
+    ///
+    /// This method converts from the times in the lease object into times
+    /// able to be added to the database.
+    ///
+    /// @param cltt Client last transmit time
+    /// @param valid_lifetime Valid lifetime
+    /// @param expire Reference to MYSQL_TIME object where the expiry time of
+    ///        the lease will be put.
+    ///
+    /// @throw isc::BadValue if the sum of the calculated expiration time is
+    /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
+    static
+    void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
+            MYSQL_TIME& expire);
+
+    /// @brief Convert Database Time to Lease Times
+    ///
+    /// Within the database, time is stored as "expire" (time of expiry of the
+    /// lease) and valid lifetime.  In the DHCP server, the information is
+    /// stored client last transmit time and valid lifetime.  These are related
+    /// by the equation:
+    ///
+    /// - client last transmit time = expire - valid_lifetime
+    ///
+    /// This method converts from the times in the database into times
+    /// able to be inserted into the lease object.
+    ///
+    /// @param expire Reference to MYSQL_TIME object from where the expiry
+    ///        time of the lease is taken.
+    /// @param valid_lifetime lifetime of the lease.
+    /// @param cltt Reference to location where client last transmit time
+    ///        is put.
+    static
+    void convertFromDatabaseTime(const MYSQL_TIME& expire,
+            uint32_t valid_lifetime, time_t& cltt);
+    ///@}
+
+    /// @brief Commit Transactions
+    ///
+    /// Commits all pending database operations. On databases that don't
+    /// support transactions, this is a no-op.
+    ///
+    /// @throw DbOperationError If the commit failed.
+    void commit();
+
+    /// @brief Rollback Transactions
+    ///
+    /// Rolls back all pending database operations. On databases that don't
+    /// support transactions, this is a no-op.
+    ///
+    /// @throw DbOperationError If the rollback failed.
+    void rollback();
+
     /// @brief Prepared statements
     /// @brief Prepared statements
     ///
     ///
     /// This field is public, because it is used heavily from MySqlConnection
     /// This field is public, because it is used heavily from MySqlConnection

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


+ 324 - 0
src/lib/dhcpsrv/mysql_host_data_source.h

@@ -0,0 +1,324 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MYSQL_HOST_DATA_SOURCE_H
+#define MYSQL_HOST_DATA_SOURCE_H
+
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/mysql_connection.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/utility.hpp>
+#include <mysql/mysql.h>
+
+namespace isc {
+namespace dhcp {
+
+// Forward declaration of the Host exchange objects.  These classes are defined
+// in the .cc file.
+class MySqlHostReservationExchange;
+
+/// @brief MySQL Host Data Source
+///
+/// This class provides the @ref isc::dhcp::BaseHostDataSource interface to the MySQL
+/// database.  Use of this backend presupposes that a MySQL database is
+/// available and that the Kea schema has been created within it.
+class MySqlHostDataSource: public BaseHostDataSource {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Uses the following keywords in the parameters passed to it to
+    /// connect to the database:
+    /// - name - Name of the database to which to connect (mandatory)
+    /// - host - Host to which to connect (optional, defaults to "localhost")
+    /// - user - Username under which to connect (optional)
+    /// - password - Password for "user" on the database (optional)
+    ///
+    /// If the database is successfully opened, the version number in the
+    /// schema_version table will be checked against hard-coded value in
+    /// the implementation file.
+    ///
+    /// Finally, all the SQL commands are pre-compiled.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the database.
+    ///
+    /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+    /// @throw isc::dhcp::DbOpenError Error opening the database
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    MySqlHostDataSource(const DatabaseConnection::ParameterMap& parameters);
+
+    /// @brief Destructor (closes database)
+    virtual ~MySqlHostDataSource();
+
+    /// @brief Return all hosts for the specified HW address or DUID.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for the specified HW address or DUID. Note, that this method may
+    /// return multiple reservations because a particular client may have
+    /// reservations in multiple subnets and the same client may be identified
+    /// by HW address or DUID. The server is unable to verify that the specific
+    /// DUID and HW address belong to the same client, until the client sends
+    /// a DHCP message.
+    ///
+    /// Specifying both hardware address and DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one host is associated with the
+    /// specified hardware address and another host is associated with the
+    /// specified DUID, two hosts will be returned.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an MultipleRecords exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+            const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet and having
+    /// a reservation for a specified IPv4 address.
+    ///
+    /// One of the use cases for this method is to detect collisions between
+    /// dynamically allocated addresses and reserved addresses. When the new
+    /// address is assigned to a client, the allocation mechanism should check
+    /// if this address is not reserved for some other host and do not allocate
+    /// this address if reservation is present.
+    ///
+    /// Implementations of this method should guard against invalid addresses,
+    /// such as IPv6 address.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address reserved IPv4 address.
+    ///
+    /// @return Const @c Host object using a specified IPv4 address.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an MultipleRecords exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+            const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+    /// @brief Adds a new host to the collection.
+    ///
+    /// The implementations of this method should guard against duplicate
+    /// reservations for the same host, where possible. For example, when the
+    /// reservation for the same HW address and subnet id is added twice, the
+    /// addHost method should throw an DuplicateEntry exception. Note, that
+    /// usually it is impossible to guard against adding duplicated host, where
+    /// one instance is identified by HW address, another one by DUID.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    virtual void add(const HostPtr& host);
+
+    /// @brief Return backend type
+    ///
+    /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+    ///
+    /// @return Type of the backend.
+    virtual std::string getType() const {
+        return (std::string("mysql"));
+    }
+
+    /// @brief Returns backend name.
+    ///
+    /// Each backend have specific name, e.g. "mysql" or "sqlite".
+    ///
+    /// @return Name of the backend.
+    virtual std::string getName() const;
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    ///
+    /// @return Description of the backend.
+    virtual std::string getDescription() const;
+
+    /// @brief Returns backend version.
+    ///
+    /// @return Version number stored in the database, as a pair of unsigned
+    ///         integers. "first" is the major version number, "second" the
+    ///         minor number.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database
+    ///        has failed.
+    virtual std::pair<uint32_t, uint32_t> getVersion() const;
+
+    /// @brief Commit Transactions
+    ///
+    /// Commits all pending database operations.  On databases that don't
+    /// support transactions, this is a no-op.
+    virtual void commit();
+
+    /// @brief Rollback Transactions
+    ///
+    /// Rolls back all pending database operations.  On databases that don't
+    /// support transactions, this is a no-op.
+    virtual void rollback();
+
+    MySqlConnection* getDatabaseConnection() {
+        return &conn_;
+    }
+
+    /// @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
+        GET_HOST_HWADDR_DUID,   // Gets hosts by DUID and/or HW address
+        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 Add Host Code
+    ///
+    /// This method performs adding a host operation.
+    ///	It binds the contents of the host object to
+    /// the prepared statement and adds it to the database.
+    ///
+    /// @param stindex Index of statemnent being executed
+    /// @param bind MYSQL_BIND array that has been created for the host
+    ///
+    /// @htrow isc::dhcp::DuplicateEntry Database throws duplicate entry error
+    void addHost(StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
+
+    /// @brief Get Host Collection Code
+    ///
+    /// This method obtains multiple hosts from the database.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param exchange Exchange object to use
+    /// @param result ConstHostCollection object returned.  Note that any hosts
+    ///        in the collection when this method is called are not erased: the
+    ///        new data is appended to the end.
+    /// @param single If true, only a single data item is to be retrieved.
+    ///        If more than one is present, a MultipleRecords exception will
+    ///        be thrown.
+    ///
+    /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+    ///        from the database where only one was expected.
+    void getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
+            boost::shared_ptr<MySqlHostReservationExchange> exchange,
+            ConstHostCollection& result, bool single = false) const;
+
+    /// @brief Check Error and Throw Exception
+    ///
+    /// Virtually all MySQL functions return a status which, if non-zero,
+    /// indicates an error.  This inline function conceals a lot of error
+    /// checking/exception-throwing code.
+    ///
+    /// @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
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database
+    ///        has failed.
+    inline void checkError(int status, StatementIndex index,
+            const char* what) const {
+        if (status != 0) {
+            isc_throw(DbOperationError, what << " for <"
+                    << conn_.text_statements_[index] << ">, reason: "
+                    << mysql_error(conn_.mysql_) << " (error code "
+                    << mysql_errno(conn_.mysql_) << ")");
+        }
+    }
+
+    /// @brief Checks if Host with same parameters already been added.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    bool checkIfExists(const HostPtr& host);
+
+    // Members
+
+    /// The exchange objects are used for transfer of data to/from the database.
+    /// They are pointed-to objects as the contents may change in "const" calls,
+    /// while the rest of this object does not.  (At alternative would be to
+    /// declare them as "mutable".)
+
+    /// @brief MySQL Host Reservation Exchange object
+    boost::shared_ptr<MySqlHostReservationExchange> hostExchange_;
+
+    /// @brief MySQL connection
+    MySqlConnection conn_;
+
+};
+
+}
+}
+
+#endif // MYSQL_HOST_DATA_SOURCE_H

+ 8 - 79
src/lib/dhcpsrv/mysql_lease_mgr.cc

@@ -415,8 +415,8 @@ public:
             // expiry time (expire).  The relationship is given by:
             // expiry time (expire).  The relationship is given by:
             //
             //
             // expire = cltt_ + valid_lft_
             // expire = cltt_ + valid_lft_
-            MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
-                                                 expire_);
+            MySqlConnection::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
+                                                   expire_);
             bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
             bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
             bind_[4].buffer = reinterpret_cast<char*>(&expire_);
             bind_[4].buffer = reinterpret_cast<char*>(&expire_);
             bind_[4].buffer_length = sizeof(expire_);
             bind_[4].buffer_length = sizeof(expire_);
@@ -590,7 +590,7 @@ public:
         // Convert times received from the database to times for the lease
         // Convert times received from the database to times for the lease
         // structure
         // structure
         time_t cltt = 0;
         time_t cltt = 0;
-        MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+        MySqlConnection::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
 
 
         if (client_id_null_==MLM_TRUE) {
         if (client_id_null_==MLM_TRUE) {
             // There's no client-id, so we pass client-id_length_ set to 0
             // There's no client-id, so we pass client-id_length_ set to 0
@@ -795,8 +795,8 @@ public:
             //
             //
             // expire = cltt_ + valid_lft_
             // expire = cltt_ + valid_lft_
             //
             //
-            MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
-                                                 expire_);
+            MySqlConnection::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
+                                                   expire_);
             bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
             bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
             bind_[3].buffer = reinterpret_cast<char*>(&expire_);
             bind_[3].buffer = reinterpret_cast<char*>(&expire_);
             bind_[3].buffer_length = sizeof(expire_);
             bind_[3].buffer_length = sizeof(expire_);
@@ -1161,7 +1161,7 @@ public:
                                     subnet_id_, fqdn_fwd_, fqdn_rev_,
                                     subnet_id_, fqdn_fwd_, fqdn_rev_,
                                     hostname, hwaddr, prefixlen_));
                                     hostname, hwaddr, prefixlen_));
         time_t cltt = 0;
         time_t cltt = 0;
-        MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+        MySqlConnection::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
         result->cltt_ = cltt;
         result->cltt_ = cltt;
 
 
         // Set state.
         // Set state.
@@ -1268,77 +1268,6 @@ MySqlLeaseMgr::getDBVersion() {
     return (tmp.str());
     return (tmp.str());
 }
 }
 
 
-
-// Time conversion methods.
-//
-// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
-// from the current timezone to UTC for storage, and from UTC to the current
-// timezone for retrieval.
-//
-// This causes no problems providing that:
-// a) cltt is given in local time
-// b) We let the system take care of timezone conversion when converting
-//    from a time read from the database into a local time.
-
-void
-MySqlLeaseMgr::convertToDatabaseTime(const time_t input_time,
-                                     MYSQL_TIME& output_time) {
-
-    // Convert to broken-out time
-    struct tm time_tm;
-    (void) localtime_r(&input_time, &time_tm);
-
-    // Place in output expire structure.
-    output_time.year = time_tm.tm_year + 1900;
-    output_time.month = time_tm.tm_mon + 1;     // Note different base
-    output_time.day = time_tm.tm_mday;
-    output_time.hour = time_tm.tm_hour;
-    output_time.minute = time_tm.tm_min;
-    output_time.second = time_tm.tm_sec;
-    output_time.second_part = 0;                // No fractional seconds
-    output_time.neg = my_bool(0);               // Not negative
-}
-
-void
-MySqlLeaseMgr::convertToDatabaseTime(const time_t cltt,
-                                     const uint32_t valid_lifetime,
-                                     MYSQL_TIME& expire) {
-
-    // Calculate expiry time. Store it in the 64-bit value so as we can detect
-    // overflows.
-    const int64_t expire_time_64 = static_cast<int64_t>(cltt) +
-        static_cast<int64_t>(valid_lifetime);
-
-    // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
-    // beyond the max value of int32_t.
-    if (expire_time_64 > LeaseMgr::MAX_DB_TIME) {
-        isc_throw(BadValue, "Time value is too large: " << expire_time_64);
-    }
-
-    convertToDatabaseTime(static_cast<time_t>(expire_time_64), expire);
-}
-
-void
-MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
-                                       uint32_t valid_lifetime, time_t& cltt) {
-
-    // Copy across fields from MYSQL_TIME structure.
-    struct tm expire_tm;
-    memset(&expire_tm, 0, sizeof(expire_tm));
-
-    expire_tm.tm_year = expire.year - 1900;
-    expire_tm.tm_mon = expire.month - 1;
-    expire_tm.tm_mday = expire.day;
-    expire_tm.tm_hour = expire.hour;
-    expire_tm.tm_min = expire.minute;
-    expire_tm.tm_sec = expire.second;
-    expire_tm.tm_isdst = -1;    // Let the system work out about DST
-
-    // Convert to local time
-    cltt = mktime(&expire_tm) - valid_lifetime;
-}
-
-
 // Add leases to the database.  The two public methods accept a lease object
 // Add leases to the database.  The two public methods accept a lease object
 // (either V4 of V6), bind the contents to the appropriate prepared
 // (either V4 of V6), bind the contents to the appropriate prepared
 // statement, then call common code to execute the statement.
 // statement, then call common code to execute the statement.
@@ -1828,7 +1757,7 @@ MySqlLeaseMgr::getExpiredLeasesCommon(LeaseCollection& expired_leases,
 
 
     // Expiration timestamp.
     // Expiration timestamp.
     MYSQL_TIME expire_time;
     MYSQL_TIME expire_time;
-    convertToDatabaseTime(time(NULL), expire_time);
+    conn_.convertToDatabaseTime(time(NULL), expire_time);
     inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
     inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
     inbind[1].buffer = reinterpret_cast<char*>(&expire_time);
     inbind[1].buffer = reinterpret_cast<char*>(&expire_time);
     inbind[1].buffer_length = sizeof(expire_time);
     inbind[1].buffer_length = sizeof(expire_time);
@@ -2019,7 +1948,7 @@ MySqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
 
 
     // Expiration timestamp.
     // Expiration timestamp.
     MYSQL_TIME expire_time;
     MYSQL_TIME expire_time;
-    convertToDatabaseTime(time(NULL) - static_cast<time_t>(secs), expire_time);
+    conn_.convertToDatabaseTime(time(NULL) - static_cast<time_t>(secs), expire_time);
     inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
     inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
     inbind[1].buffer = reinterpret_cast<char*>(&expire_time);
     inbind[1].buffer = reinterpret_cast<char*>(&expire_time);
     inbind[1].buffer_length = sizeof(expire_time);
     inbind[1].buffer_length = sizeof(expire_time);

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

@@ -21,19 +21,13 @@
 
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/utility.hpp>
 #include <boost/utility.hpp>
-#include <mysql.h>
+#include <mysql/mysql.h>
 
 
 #include <time.h>
 #include <time.h>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
-// Define the current database schema values
-
-const uint32_t CURRENT_VERSION_VERSION = 3;
-const uint32_t CURRENT_VERSION_MINOR = 0;
-
-
 // Forward declaration of the Lease exchange objects.  These classes are defined
 // Forward declaration of the Lease exchange objects.  These classes are defined
 // in the .cc file.
 // in the .cc file.
 class MySqlLease4Exchange;
 class MySqlLease4Exchange;
@@ -401,67 +395,6 @@ public:
     /// @throw DbOperationError If the rollback failed.
     /// @throw DbOperationError If the rollback failed.
     virtual void rollback();
     virtual void rollback();
 
 
-    ///@{
-    /// The following methods are used to convert between times and time
-    /// intervals stored in the Lease object, and the times stored in the
-    /// database.  The reason for the difference is because in the DHCP server,
-    /// the cltt (Client Time Since Last Transmission) is the natural data; in
-    /// the lease file - which may be read by the user - it is the expiry time
-    /// of the lease.
-
-    /// @brief Convert time_t value to database time.
-    ///
-    /// @param input_time A time_t value representing time.
-    /// @param output_time Reference to MYSQL_TIME object where converted time
-    ///        will be put.
-    static
-    void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
-
-    /// @brief Convert Lease Time to Database Times
-    ///
-    /// Within the DHCP servers, times are stored as client last transmit time
-    /// and valid lifetime.  In the database, the information is stored as
-    /// valid lifetime and "expire" (time of expiry of the lease).  They are
-    /// related by the equation:
-    ///
-    /// - expire = client last transmit time + valid lifetime
-    ///
-    /// This method converts from the times in the lease object into times
-    /// able to be added to the database.
-    ///
-    /// @param cltt Client last transmit time
-    /// @param valid_lifetime Valid lifetime
-    /// @param expire Reference to MYSQL_TIME object where the expiry time of
-    ///        the lease will be put.
-    ///
-    /// @throw isc::BadValue if the sum of the calculated expiration time is
-    /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
-    static
-    void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
-                               MYSQL_TIME& expire);
-
-    /// @brief Convert Database Time to Lease Times
-    ///
-    /// Within the database, time is stored as "expire" (time of expiry of the
-    /// lease) and valid lifetime.  In the DHCP server, the information is
-    /// stored client last transmit time and valid lifetime.  These are related
-    /// by the equation:
-    ///
-    /// - client last transmit time = expire - valid_lifetime
-    ///
-    /// This method converts from the times in the database into times
-    /// able to be inserted into the lease object.
-    ///
-    /// @param expire Reference to MYSQL_TIME object from where the expiry
-    ///        time of the lease is taken.
-    /// @param valid_lifetime lifetime of the lease.
-    /// @param cltt Reference to location where client last transmit time
-    ///        is put.
-    static
-    void convertFromDatabaseTime(const MYSQL_TIME& expire,
-                                 uint32_t valid_lifetime, time_t& cltt);
-    ///@}
-
     /// @brief Statement Tags
     /// @brief Statement Tags
     ///
     ///
     /// The contents of the enum are indexes into the list of SQL statements
     /// The contents of the enum are indexes into the list of SQL statements

+ 3 - 3
src/lib/dhcpsrv/pgsql_lease_mgr.cc

@@ -364,7 +364,7 @@ public:
     ///
     ///
     /// @return std::string containing the stringified time
     /// @return std::string containing the stringified time
     /// @throw isc::BadValue if the sum of the calculated expiration time is
     /// @throw isc::BadValue if the sum of the calculated expiration time is
-    /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
+    /// greater than the value of @c DataSource::MAX_DB_TIME.
     static std::string
     static std::string
     convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime) {
     convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime) {
         // Calculate expiry time. Store it in the 64-bit value so as we can detect
         // Calculate expiry time. Store it in the 64-bit value so as we can detect
@@ -373,11 +373,11 @@ public:
             static_cast<int64_t>(valid_lifetime);
             static_cast<int64_t>(valid_lifetime);
 
 
         // It has been observed that the PostgreSQL doesn't deal well with the
         // It has been observed that the PostgreSQL doesn't deal well with the
-        // timestamp values beyond the LeaseMgr::MAX_DB_TIME seconds since the
+        // timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
         // beginning of the epoch (around year 2038). The value is often
         // beginning of the epoch (around year 2038). The value is often
         // stored in the database but it is invalid when read back (overflow?).
         // stored in the database but it is invalid when read back (overflow?).
         // Hence, the maximum timestamp value is restricted here.
         // Hence, the maximum timestamp value is restricted here.
-        if (expire_time_64 > LeaseMgr::MAX_DB_TIME) {
+        if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
             isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
             isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
         }
         }
 
 

+ 2 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -100,10 +100,12 @@ libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += logging_unittest.cc
 libdhcpsrv_unittests_SOURCES += logging_unittest.cc
 libdhcpsrv_unittests_SOURCES += logging_info_unittest.cc
 libdhcpsrv_unittests_SOURCES += logging_info_unittest.cc
 libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc generic_lease_mgr_unittest.h
 libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc generic_lease_mgr_unittest.h
+libdhcpsrv_unittests_SOURCES += generic_host_data_source_unittest.cc generic_host_data_source_unittest.h
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 if HAVE_MYSQL
 if HAVE_MYSQL
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += mysql_host_data_source_unittest.cc
 endif
 endif
 
 
 libdhcpsrv_unittests_SOURCES += ncr_generator_unittest.cc
 libdhcpsrv_unittests_SOURCES += ncr_generator_unittest.cc

+ 770 - 0
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc

@@ -0,0 +1,770 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#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 <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+GenericHostDataSourceTest::GenericHostDataSourceTest()
+    :hdsptr_(NULL) {
+
+}
+
+GenericHostDataSourceTest::~GenericHostDataSourceTest() {
+}
+
+std::string
+GenericHostDataSourceTest::generateHWAddr() {
+    /// @todo: Consider moving this somewhere to lib/testutils.
+
+    // Let's use something that is easily printable. That's convenient
+    // if you need to enter MySQL queries by hand.
+    static uint8_t hwaddr[] = {65, 66, 67, 68, 69, 70};
+
+    stringstream tmp;
+    for (int i = 0; i < sizeof(hwaddr); ++i) {
+        if (i) {
+            tmp << ":";
+        }
+        tmp << std::setw(2) << std::hex << std::setfill('0')
+            << static_cast<unsigned int>(hwaddr[i]);
+    }
+
+    // Increase the address for the next time we use it.
+    // This is primitive, but will work for 65k unique
+    // addresses.
+    hwaddr[sizeof(hwaddr) - 1]++;
+    if (hwaddr[sizeof(hwaddr) - 1] == 0) {
+        hwaddr[sizeof(hwaddr) - 2]++;
+    }
+    return (tmp.str());
+}
+
+std::string
+GenericHostDataSourceTest::generateDuid() {
+    /// @todo: Consider moving this somewhere to lib/testutils.
+
+    // Let's use something that is easily printable. That's convenient
+    // if you need to enter MySQL queries by hand.
+    static uint8_t duid[] = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 };
+
+    stringstream tmp;
+    for (int i = 0; i < sizeof(duid); ++i) {
+        tmp << std::setw(2) << std::hex << std::setfill('0')
+            << static_cast<unsigned int>(duid[i]);
+    }
+
+    // Increase the DUID for the next time we use it.
+    // This is primitive, but will work for 65k unique
+    // DUIDs.
+    duid[sizeof(duid) - 1]++;
+    if (duid[sizeof(duid) - 1] == 0) {
+        duid[sizeof(duid) - 2]++;
+    }
+    return (tmp.str());
+}
+
+HostPtr GenericHostDataSourceTest::initializeHost4(std::string address,
+                                                   bool hwaddr) {
+    string ident;
+    string ident_type;
+
+    if (hwaddr) {
+        ident = generateHWAddr();
+        ident_type = "hw-address";
+    } else {
+        ident = generateDuid();
+        ident_type = "duid";
+    }
+
+    // Let's create ever increasing subnet-ids. Let's keep those different,
+    // so subnet4 != subnet6. Useful for catching cases if the code confuses
+    // subnet4 with subnet6.
+    static SubnetID subnet4 = 0;
+    static SubnetID subnet6 = 100;
+    subnet4++;
+    subnet6++;
+
+    IOAddress addr(address);
+    HostPtr host(new Host(ident, ident_type, subnet4, subnet6, addr));
+
+    return (host);
+}
+
+HostPtr GenericHostDataSourceTest::initializeHost6(std::string address,
+                                                   BaseHostDataSource::IdType identifier,
+                                                   bool prefix) {
+    string ident;
+    string ident_type;
+
+    switch (identifier) {
+    case BaseHostDataSource::ID_HWADDR:
+        ident = generateHWAddr();
+        ident_type = "hw-address";
+        break;
+    case BaseHostDataSource::ID_DUID:
+        ident = generateDuid();
+        ident_type = "duid";
+        break;
+    default:
+        ADD_FAILURE() << "Unknown IdType: " << identifier;
+        return HostPtr();
+    }
+
+    // Let's create ever increasing subnet-ids. Let's keep those different,
+    // so subnet4 != subnet6. Useful for catching cases if the code confuses
+    // subnet4 with subnet6.
+    static SubnetID subnet4 = 0;
+    static SubnetID subnet6 = 100;
+    subnet4++;
+    subnet6++;
+
+    HostPtr host(new Host(ident, ident_type, subnet4, subnet6, IOAddress("0.0.0.0")));
+
+    if (!prefix) {
+        // Create IPv6 reservation (for an address)
+        IPv6Resrv resv(IPv6Resrv::TYPE_NA, IOAddress(address), 128);
+        host->addReservation(resv);
+    } else {
+        // Create IPv6 reservation for a /64 prefix
+        IPv6Resrv resv(IPv6Resrv::TYPE_PD, IOAddress(address), 64);
+        host->addReservation(resv);
+    }
+    return (host);
+}
+
+void
+GenericHostDataSourceTest::compareHwaddrs(const ConstHostPtr& host1,
+                                          const ConstHostPtr& host2,
+                                          bool expect_match) {
+    ASSERT_TRUE(host1);
+    ASSERT_TRUE(host2);
+
+    // Compare if both have or have not HWaddress set.
+    if ((host1->getHWAddress() && !host2->getHWAddress()) ||
+        (!host1->getHWAddress() && host2->getHWAddress())) {
+
+        // One host has hardware address set while the other has not.
+        // Let's see if it's a problem.
+        if (expect_match) {
+            ADD_FAILURE() << "Host comparison failed: host1 hwaddress="
+                          << host1->getHWAddress() << ", host2 hwaddress="
+                          << host2->getHWAddress();
+        }
+        return;
+    }
+
+    // Now we know that either both or neither have hw address set.
+    // If host1 has it, we can proceed to value comparison.
+    if (host1->getHWAddress()) {
+
+        if (expect_match) {
+            // Compare the actual address if they match.
+            EXPECT_TRUE(*host1->getHWAddress() == *host2->getHWAddress());
+        } else {
+            EXPECT_FALSE(*host1->getHWAddress() == *host2->getHWAddress());
+        }
+        if (*host1->getHWAddress() != *host2->getHWAddress()) {
+            cout << host1->getHWAddress()->toText(true) << endl;
+            cout << host2->getHWAddress()->toText(true) << endl;
+        }
+    }
+}
+
+void
+GenericHostDataSourceTest::compareDuids(const ConstHostPtr& host1,
+                                        const ConstHostPtr& host2,
+                                        bool expect_match) {
+    ASSERT_TRUE(host1);
+    ASSERT_TRUE(host2);
+
+    // compare if both have or have not DUID set
+    if ((host1->getDuid() && !host2->getDuid()) ||
+        (!host1->getDuid() && host2->getDuid())) {
+
+        // One host has a DUID and the other doesn't.
+        // Let's see if it's a problem.
+        if (expect_match) {
+            ADD_FAILURE() << "DUID comparison failed: host1 duid="
+                          << host1->getDuid() << ", host2 duid="
+                          << host2->getDuid();
+        }
+        return;
+    }
+
+    // Now we know that either both or neither have DUID set.
+    // If host1 has it, we can proceed to value comparison.
+    if (host1->getDuid()) {
+
+        if (expect_match) {
+            EXPECT_TRUE(*host1->getDuid() == *host2->getDuid());
+        } else {
+            EXPECT_FALSE(*host1->getDuid() == *host2->getDuid());
+        }
+        if (*host1->getDuid() != *host2->getDuid()) {
+            cout << host1->getDuid()->toText() << endl;
+            cout << host2->getDuid()->toText() << endl;
+        }
+    }
+}
+
+void GenericHostDataSourceTest::compareHosts(const ConstHostPtr& host1,
+                                             const ConstHostPtr& host2) {
+
+    // Let's compare HW addresses and expect match.
+    compareHwaddrs(host1, host2, true);
+
+    // Now compare DUIDs
+    compareDuids(host1, host2, true);
+
+    // Now check that the identifiers returned as vectors are the same
+    EXPECT_EQ(host1->getIdentifierType(), host2->getIdentifierType());
+    EXPECT_EQ(host1->getIdentifier(), host2->getIdentifier());
+
+    // Check host parameters
+    EXPECT_EQ(host1->getIPv4SubnetID(), host2->getIPv4SubnetID());
+    EXPECT_EQ(host1->getIPv6SubnetID(), host2->getIPv6SubnetID());
+    EXPECT_EQ(host1->getIPv4Reservation(), host2->getIPv4Reservation());
+    EXPECT_EQ(host1->getHostname(), host2->getHostname());
+
+    // Compare IPv6 reservations
+    compareReservations6(host1->getIPv6Reservations(),
+                         host2->getIPv6Reservations(),
+                         true);
+
+    // And compare client classification details
+    compareClientClasses(host1->getClientClasses4(),
+                         host2->getClientClasses4());
+
+    compareClientClasses(host1->getClientClasses6(),
+                         host2->getClientClasses6());
+}
+
+DuidPtr
+GenericHostDataSourceTest::HWAddrToDuid(const HWAddrPtr& hwaddr) {
+    if (!hwaddr) {
+        return (DuidPtr());
+    }
+
+    return (DuidPtr(new DUID(hwaddr->hwaddr_)));
+}
+
+HWAddrPtr
+GenericHostDataSourceTest::DuidToHWAddr(const DuidPtr& duid) {
+    if (!duid) {
+        return (HWAddrPtr());
+    }
+
+    return (HWAddrPtr(new HWAddr(duid->getDuid(), HTYPE_ETHER)));
+}
+
+
+void
+GenericHostDataSourceTest::compareReservations6(IPv6ResrvRange resrv1,
+                                                IPv6ResrvRange resrv2,
+                                                bool expect_match) {
+
+    // Compare number of reservations for both hosts
+    if (std::distance(resrv1.first, resrv1.second) !=
+            std::distance(resrv2.first, resrv2.second)){
+        // Number of reservations is not equal.
+        // Let's see if it's a problem.
+        if (expect_match) {
+            ADD_FAILURE()<< "Reservation comparison failed, "
+                    "hosts got different number of reservations.";
+        }
+        return;
+    }
+
+    if (std::distance(resrv1.first, resrv1.second) > 0) {
+        if (expect_match){
+            /// @todo Compare every reservation from both hosts
+            ///       This is part of the work for #4212.
+        }
+    }
+}
+
+void
+GenericHostDataSourceTest::compareClientClasses(const ClientClasses& /*classes1*/,
+                                                const ClientClasses& /*classes2*/) {
+    /// @todo: Implement client classes comparison.
+    ///        This is part of the work for #4213.
+}
+
+void GenericHostDataSourceTest::testBasic4(bool hwaddr) {
+    // Make sure we have the pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Create a host reservation.
+    HostPtr host = initializeHost4("192.0.2.1", hwaddr);
+    ASSERT_TRUE(host); // Make sure the host is generate properly.
+    SubnetID subnet = host->getIPv4SubnetID();
+
+    // Try to add it to the host data source.
+    ASSERT_NO_THROW(hdsptr_->add(host));
+
+    // This should not return anything
+    ConstHostPtr from_hds = hdsptr_->get4(subnet, IOAddress("10.10.10.10"));
+    ASSERT_FALSE(from_hds);
+
+    // This time it should return a host
+    from_hds = hdsptr_->get4(subnet, IOAddress("192.0.2.1"));
+    ASSERT_TRUE(from_hds);
+
+    // Finally, let's check if what we got makes any sense.
+    compareHosts(host, from_hds);
+}
+
+
+void GenericHostDataSourceTest::testGetByIPv4(bool hwaddr) {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Let's create a couple of hosts...
+    HostPtr host1 = initializeHost4("192.0.2.1", hwaddr);
+    HostPtr host2 = initializeHost4("192.0.2.2", hwaddr);
+    HostPtr host3 = initializeHost4("192.0.2.3", hwaddr);
+    HostPtr host4 = initializeHost4("192.0.2.4", hwaddr);
+
+    // ... and add them to the data source.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+    ASSERT_NO_THROW(hdsptr_->add(host2));
+    ASSERT_NO_THROW(hdsptr_->add(host3));
+    ASSERT_NO_THROW(hdsptr_->add(host4));
+
+    SubnetID subnet1 = host1->getIPv4SubnetID();
+    SubnetID subnet2 = host2->getIPv4SubnetID();
+    SubnetID subnet3 = host3->getIPv4SubnetID();
+    SubnetID subnet4 = host4->getIPv4SubnetID();
+
+    // And then try to retrieve them back.
+    ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+    ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, IOAddress("192.0.2.2"));
+    ConstHostPtr from_hds3 = hdsptr_->get4(subnet3, IOAddress("192.0.2.3"));
+    ConstHostPtr from_hds4 = hdsptr_->get4(subnet4, IOAddress("192.0.2.4"));
+
+    // Make sure we got something back.
+    ASSERT_TRUE(from_hds1);
+    ASSERT_TRUE(from_hds2);
+    ASSERT_TRUE(from_hds3);
+    ASSERT_TRUE(from_hds4);
+
+    // Then let's check that what we got seems correct.
+    compareHosts(host1, from_hds1);
+    compareHosts(host2, from_hds2);
+    compareHosts(host3, from_hds3);
+    compareHosts(host4, from_hds4);
+
+    // Ok, finally let's check that getting by a different address
+    // will not work.
+    EXPECT_FALSE(hdsptr_->get4(subnet1, IOAddress("192.0.1.5")));
+}
+
+void GenericHostDataSourceTest::testGet4ByHWAddr() {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    HostPtr host1 = initializeHost4("192.0.2.1", true);
+    HostPtr host2 = initializeHost4("192.0.2.2", true);
+
+    // Sanity check: make sure the hosts have different HW addresses.
+    ASSERT_TRUE(host1->getHWAddress());
+    ASSERT_TRUE(host2->getHWAddress());
+    compareHwaddrs(host1, host2, false);
+
+    // Try to add both of them to the host data source.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+    ASSERT_NO_THROW(hdsptr_->add(host2));
+
+    SubnetID subnet1 = host1->getIPv4SubnetID();
+    SubnetID subnet2 = host2->getIPv4SubnetID();
+
+    ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, host1->getHWAddress());
+    ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, host2->getHWAddress());
+
+    // Now let's check if we got what we expected.
+    ASSERT_TRUE(from_hds1);
+    ASSERT_TRUE(from_hds2);
+    compareHosts(host1, from_hds1);
+    compareHosts(host2, from_hds2);
+}
+
+void GenericHostDataSourceTest::testGet4ByClientId() {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    HostPtr host1 = initializeHost4("192.0.2.1", false);
+    HostPtr host2 = initializeHost4("192.0.2.2", false);
+
+    // Sanity check: make sure the hosts have different client-ids.
+    ASSERT_TRUE(host1->getDuid());
+    ASSERT_TRUE(host2->getDuid());
+    compareDuids(host1, host2, false);
+
+    // Try to add both of them to the host data source.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+    ASSERT_NO_THROW(hdsptr_->add(host2));
+
+    SubnetID subnet1 = host1->getIPv4SubnetID();
+    SubnetID subnet2 = host2->getIPv4SubnetID();
+
+    ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, HWAddrPtr(), host1->getDuid());
+    ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, HWAddrPtr(), host2->getDuid());
+
+    // Now let's check if we got what we expected.
+    ASSERT_TRUE(from_hds1);
+    ASSERT_TRUE(from_hds2);
+    compareHosts(host1, from_hds1);
+    compareHosts(host2, from_hds2);
+}
+
+void GenericHostDataSourceTest::testHWAddrNotClientId() {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Create a host with HW address
+    HostPtr host = initializeHost4("192.0.2.1", true);
+    ASSERT_TRUE(host->getHWAddress());
+    ASSERT_FALSE(host->getDuid());
+
+    // Try to add it to the host data source.
+    ASSERT_NO_THROW(hdsptr_->add(host));
+
+    SubnetID subnet = host->getIPv4SubnetID();
+
+    DuidPtr duid = HWAddrToDuid(host->getHWAddress());
+
+    // Get the host by HW address (should succeed)
+    ConstHostPtr by_hwaddr = hdsptr_->get4(subnet, host->getHWAddress(), DuidPtr());
+
+    // Get the host by DUID (should fail)
+    ConstHostPtr by_duid   = hdsptr_->get4(subnet, HWAddrPtr(), duid);
+
+    // Now let's check if we got what we expected.
+    EXPECT_TRUE(by_hwaddr);
+    EXPECT_FALSE(by_duid);
+}
+
+void GenericHostDataSourceTest::testClientIdNotHWAddr() {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Create a host with client-id
+    HostPtr host = initializeHost4("192.0.2.1", false);
+    ASSERT_FALSE(host->getHWAddress());
+    ASSERT_TRUE(host->getDuid());
+
+    // Try to add it to the host data source.
+    ASSERT_NO_THROW(hdsptr_->add(host));
+
+    SubnetID subnet = host->getIPv4SubnetID();
+
+    HWAddrPtr hwaddr = DuidToHWAddr(host->getDuid());
+
+    // Get the host by DUID (should succeed)
+    ConstHostPtr by_duid   = hdsptr_->get4(subnet, HWAddrPtr(), host->getDuid());
+
+    // Get the host by HW address (should fail)
+    ConstHostPtr by_hwaddr = hdsptr_->get4(subnet, hwaddr, DuidPtr());
+
+    // Now let's check if we got what we expected.
+    EXPECT_TRUE(by_duid);
+    EXPECT_FALSE(by_hwaddr);
+}
+
+void
+GenericHostDataSourceTest::testHostname(std::string name, int num) {
+
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Initialize the address to 192.0.2.0 (this will be bumped
+    // up to 192.0.2.1 in the first iteration)
+    IOAddress addr("192.0.2.0");
+
+    vector<HostPtr> hosts;
+
+    // Prepare a vector of hosts with unique hostnames
+    for (int i = 0; i < num; ++i) {
+
+        addr = IOAddress::increase(addr);
+
+        HostPtr host = initializeHost4(addr.toText(), false);
+
+        stringstream hostname;
+        hostname.str("");
+        if (num > 1) {
+            hostname << i;
+        }
+        hostname << name;
+        host->setHostname(hostname.str());
+
+        hosts.push_back(host);
+    }
+
+    // Now add them all to the host data source.
+    for (vector<HostPtr>::const_iterator it = hosts.begin();
+         it != hosts.end(); ++it) {
+        // Try to add both of the to the host data source.
+        ASSERT_NO_THROW(hdsptr_->add(*it));
+    }
+
+    // And finally retrieve them one by one and check
+    // if the hostname was preserved.
+    for (vector<HostPtr>::const_iterator it = hosts.begin();
+         it != hosts.end(); ++it) {
+
+        ConstHostPtr from_hds;
+        ASSERT_NO_THROW(from_hds = hdsptr_->get4(
+                            (*it)->getIPv4SubnetID(),
+                            (*it)->getIPv4Reservation()));
+        ASSERT_TRUE(from_hds);
+
+        EXPECT_EQ((*it)->getHostname(), from_hds->getHostname());
+    }
+}
+
+void
+GenericHostDataSourceTest::testMultipleSubnets(int subnets, bool hwaddr) {
+
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    HostPtr host = initializeHost4("192.0.2.1", hwaddr);
+
+    for (int i = 0; i < subnets; ++i) {
+        host->setIPv4SubnetID(i + 1000);
+        host->setIPv6SubnetID(i + 1000);
+
+        // Check that the same host can have reservations in multiple subnets.
+        EXPECT_NO_THROW(hdsptr_->add(host));
+    }
+
+    // Now check that the reservations can be retrieved by IPv4 address from
+    // each subnet separately.
+    for (int i = 0; i < subnets; ++i) {
+
+        // Try to retrieve the host by IPv4 address.
+        ConstHostPtr from_hds = hdsptr_->get4(i + 1000, host->getIPv4Reservation());
+
+        ASSERT_TRUE(from_hds);
+        EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
+
+        // Try to retrieve the host by either HW address of client-id
+        from_hds = hdsptr_->get4(i + 1000, host->getHWAddress(), host->getDuid());
+        ASSERT_TRUE(from_hds);
+        EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
+    }
+
+    // Now check that they can be retrieved all at once, by IPv4 address.
+    ConstHostCollection all_by_addr = hdsptr_->getAll4(IOAddress("192.0.2.1"));
+    ASSERT_EQ(subnets, all_by_addr.size());
+
+    // Verify that the values returned are proper.
+    int i = 0;
+    for (ConstHostCollection::const_iterator it = all_by_addr.begin();
+         it != all_by_addr.end(); ++it) {
+        EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
+        EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
+    }
+
+    // Finally, check that the hosts can be retrived by HW address or DUID
+    ConstHostCollection all_by_id = hdsptr_->getAll(host->getHWAddress(),
+                                                    host->getDuid());
+    ASSERT_EQ(subnets, all_by_id.size());
+
+    // Check that the returned values are as expected.
+    i = 0;
+    for (ConstHostCollection::const_iterator it = all_by_id.begin();
+         it != all_by_id.end(); ++it) {
+        EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
+        EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
+    }
+}
+
+void GenericHostDataSourceTest::testGet6ByHWAddr() {
+    // Make sure we have the pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Create a host reservations.
+    HostPtr host1 = initializeHost6("2001:db8::0", BaseHostDataSource::ID_HWADDR, true);
+    HostPtr host2 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_HWADDR, true);
+
+    // Sanity check: make sure the hosts have different HW addresses.
+    ASSERT_TRUE(host1->getHWAddress());
+    ASSERT_TRUE(host2->getHWAddress());
+
+    compareHwaddrs(host1, host2, false);
+
+    // Try to add both of them to the host data source.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+    ASSERT_NO_THROW(hdsptr_->add(host2));
+
+    SubnetID subnet1 = host1->getIPv6SubnetID();
+    SubnetID subnet2 = host2->getIPv6SubnetID();
+
+    ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, DuidPtr(), host1->getHWAddress());
+    ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, DuidPtr(), host2->getHWAddress());
+
+    // Now let's check if we got what we expected.
+    ASSERT_TRUE(from_hds1);
+    ASSERT_TRUE(from_hds2);
+    compareHosts(host1, from_hds1);
+    compareHosts(host2, from_hds2);
+}
+
+void GenericHostDataSourceTest::testGet6ByClientId() {
+    // Make sure we have the pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Create a host reservations.
+    HostPtr host1 = initializeHost6("2001:db8::0", BaseHostDataSource::ID_DUID, true);
+    HostPtr host2 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
+
+    // Sanity check: make sure the hosts have different HW addresses.
+    ASSERT_TRUE(host1->getDuid());
+    ASSERT_TRUE(host2->getDuid());
+
+    compareDuids(host1, host2, false);
+
+    // Try to add both of them to the host data source.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+    ASSERT_NO_THROW(hdsptr_->add(host2));
+
+    SubnetID subnet1 = host1->getIPv6SubnetID();
+    SubnetID subnet2 = host2->getIPv6SubnetID();
+
+    ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, host1->getDuid(), HWAddrPtr());
+    ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, host2->getDuid(), HWAddrPtr());
+
+    // Now let's check if we got what we expected.
+    ASSERT_TRUE(from_hds1);
+    ASSERT_TRUE(from_hds2);
+    compareHosts(host1, from_hds1);
+    compareHosts(host2, from_hds2);
+}
+
+void
+GenericHostDataSourceTest::testSubnetId6(int subnets, BaseHostDataSource::IdType id) {
+
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    HostPtr host = initializeHost6("2001:db8::0", id, true);
+
+    for (int i = 0; i < subnets; ++i) {
+        host->setIPv4SubnetID(i + 1000);
+        host->setIPv6SubnetID(i + 1000);
+
+        // Check that the same host can have reservations in multiple subnets.
+        EXPECT_NO_THROW(hdsptr_->add(host));
+    }
+
+    // Check that the reservations can be retrieved from each subnet separately.
+    for (int i = 0; i < subnets; ++i) {
+
+        // Try to retrieve the host
+        ConstHostPtr from_hds = hdsptr_->get6(i + 1000, host->getDuid(),
+                                              host->getHWAddress());
+
+        ASSERT_TRUE(from_hds);
+        EXPECT_EQ(i + 1000, from_hds->getIPv6SubnetID());
+    }
+
+    // Check that the hosts can all be retrived by HW address or DUID
+    ConstHostCollection all_by_id = hdsptr_->getAll(host->getHWAddress(),
+                                                    host->getDuid());
+    ASSERT_EQ(subnets, all_by_id.size());
+
+    // Check that the returned values are as expected.
+    int i = 0;
+    for (ConstHostCollection::const_iterator it = all_by_id.begin();
+         it != all_by_id.end(); ++it) {
+        EXPECT_EQ(IOAddress("0.0.0.0"), (*it)->getIPv4Reservation());
+        EXPECT_EQ(1000 + i++, (*it)->getIPv6SubnetID());
+    }
+}
+
+void GenericHostDataSourceTest::testGetByIPv6(BaseHostDataSource::IdType id,
+                                              bool prefix) {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Let's create a couple of hosts...
+    HostPtr host1 = initializeHost6("2001:db8::1", id, prefix);
+    HostPtr host2 = initializeHost6("2001:db8::2", id, prefix);
+    HostPtr host3 = initializeHost6("2001:db8::3", id, prefix);
+    HostPtr host4 = initializeHost6("2001:db8::4", id, prefix);
+
+    // ... and add them to the data source.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+    ASSERT_NO_THROW(hdsptr_->add(host2));
+    ASSERT_NO_THROW(hdsptr_->add(host3));
+    ASSERT_NO_THROW(hdsptr_->add(host4));
+
+    // Are we talking about addresses or prefixes?
+    uint8_t len = prefix ? 64 : 128;
+
+    // And then try to retrieve them back.
+    ConstHostPtr from_hds1 = hdsptr_->get6(IOAddress("2001:db8::1"), len);
+    ConstHostPtr from_hds2 = hdsptr_->get6(IOAddress("2001:db8::2"), len);
+    ConstHostPtr from_hds3 = hdsptr_->get6(IOAddress("2001:db8::3"), len);
+    ConstHostPtr from_hds4 = hdsptr_->get6(IOAddress("2001:db8::4"), len);
+
+    // Make sure we got something back.
+    ASSERT_TRUE(from_hds1);
+    ASSERT_TRUE(from_hds2);
+    ASSERT_TRUE(from_hds3);
+    ASSERT_TRUE(from_hds4);
+
+    // Then let's check that what we got seems correct.
+    compareHosts(host1, from_hds1);
+    compareHosts(host2, from_hds2);
+    compareHosts(host3, from_hds3);
+    compareHosts(host4, from_hds4);
+
+    // Ok, finally let's check that getting by a different address
+    // will not work.
+    EXPECT_FALSE(hdsptr_->get6(IOAddress("2001:db8::5"), len));
+}
+
+void GenericHostDataSourceTest::testAddDuplicate() {
+    // Make sure we have the pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Create a host reservations.
+    HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID,
+                                   true);
+
+    // Add this reservation once.
+    ASSERT_NO_THROW(hdsptr_->add(host));
+
+    // Then try to add it again, it should throw an exception.
+    ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+}
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc

+ 248 - 0
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h

@@ -0,0 +1,248 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef GENERIC_HOST_DATA_SOURCE_UNITTEST_H
+#define GENERIC_HOST_DATA_SOURCE_UNITTEST_H
+
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcp/classify.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test Fixture class with utility functions for HostDataSource backends
+///
+/// It contains utility functions for test purposes.
+/// All concrete HostDataSource test classes should be derived from it.
+class GenericHostDataSourceTest : public ::testing::Test {
+public:
+
+    /// @brief Universe (V4 or V6).
+    enum Universe {
+        V4,
+        V6
+    };
+
+    /// @brief Default constructor.
+    GenericHostDataSourceTest();
+
+    /// @brief Virtual destructor.
+    virtual ~GenericHostDataSourceTest();
+
+    /// @brief Creates a host reservation for specified IPv4 address.
+    ///
+    /// @param address IPv4 address to be set
+    /// @param hwaddr type of identifier (true = hwaddr, false = client-id)
+    ///
+    /// @return generated Host object
+    HostPtr initializeHost4(std::string address, bool hwaddr);
+
+    /// @brief Creates a host reservation for specified IPv6 address.
+    ///
+    /// @param address IPv6 address to be reserved
+    /// @param id type of identifier (ID_DUID or ID_HWADDR are supported)
+    /// @param prefix reservation type (true = prefix, false = address)
+    ///
+    /// @return generated Host object
+    HostPtr initializeHost6(std::string address, BaseHostDataSource::IdType id,
+                            bool prefix);
+
+    /// @brief Generates a hardware address in text version.
+    ///
+    /// @return HW address in textual form acceptable by Host constructor
+    std::string generateHWAddr();
+
+    /// @brief Generates a hardware address in text version.
+    /// @return DUID in textual form acceptable by Host constructor
+    std::string generateDuid();
+
+    /// @brief Compares hardware addresses of the two hosts.
+    ///
+    /// This method compares two hwardware address and uses gtest
+    /// macros to signal unexpected (mismatch if expect_match is true;
+    /// match if expect_match is false) values.
+    ///
+    /// @param host1 first host to be compared
+    /// @param host2 second host to be compared
+    /// @param expect_match true = HW addresses expected to be the same,
+    ///                     false = HW addresses expected to be different
+    void
+    compareHwaddrs(const ConstHostPtr& host1, const ConstHostPtr& host2,
+                   bool expect_match);
+
+    /// @brief Compares DUIDs of the two hosts.
+    ///
+    /// This method compares two DUIDs (client-ids) and uses gtest
+    /// macros to signal unexpected (mismatch if expect_match is true;
+    /// match if expect_match is false) values.
+    ///
+    /// @param host1 first host to be compared
+    /// @param host2 second host to be compared
+    /// @param expect_match true = DUIDs expected to be the same,
+    ///                     false = DUIDs expected to be different
+    void
+    compareDuids(const ConstHostPtr& host1, const ConstHostPtr& host2,
+                 bool expect_match);
+
+    /// @brief Compares two hosts
+    ///
+    /// This method uses gtest macros to signal errors.
+    ///
+    /// @param host1 first host to compare
+    /// @param host2 second host to compare
+    void compareHosts(const ConstHostPtr& host1, const ConstHostPtr& host2);
+
+    /// @brief Compares two IPv6 reservation lists.
+    ///
+    /// This method uses gtest macros to signal errors.
+    ///
+    /// @param resv1 first IPv6 reservations list
+    /// @param resv2 second IPv6 reservations list
+    void compareReservations6(IPv6ResrvRange resv1, IPv6ResrvRange resv2,
+                              bool expect_match);
+
+    /// @brief Compares two client classes
+    ///
+    /// This method uses gtest macros to signal errors.
+    ///
+    /// @param classes1 first list of client classes
+    /// @param classes2 second list of client classes
+    void compareClientClasses(const ClientClasses& classes1,
+                              const ClientClasses& classes2);
+
+    /// @brief Pointer to the host data source
+    BaseHostDataSource* hdsptr_;
+
+    /// @brief Test that checks that simple host with IPv4 reservation
+    ///        can be inserted and later retrieved.
+    ///
+    /// Uses gtest macros to report failures.
+    /// @param hwaddr true = use HW address as identifier, false = use client-id(DUID)
+    void testBasic4(bool hwaddr);
+
+    /// @brief Test inserts several hosts with unique IPv4 address and
+    ///        checks that they can be retrieved properly.
+    ///
+    /// Uses gtest macros to report failures.
+    /// @param hwaddr true = use HW address as identifier, false = use client-id(DUID)
+    void testGetByIPv4(bool hwaddr);
+
+    /// @brief Test that hosts can be retrieved by hardware address.
+    ///
+    /// Uses gtest macros to report failures.
+    void testGet4ByHWAddr();
+
+    /// @brief Test that hosts can be retrieved by client-id
+    ///
+    /// Uses gtest macros to report failures.
+    void testGet4ByClientId();
+
+    /// @brief Test that clients with stored HW address can't be retrieved
+    ///        by DUID with the same value.
+    ///
+    /// Test procedure: add host reservation with hardware address X, try to retrieve
+    /// host by client-identifier X, verify that the reservation is not returned.
+    ///
+    /// Uses gtest macros to report failures.
+    void testHWAddrNotClientId();
+
+    /// @brief Test that clients with stored DUID can't be retrieved
+    ///        by HW address of the same value.
+    ///
+    /// Test procedure: add host reservation with client identifier X, try to
+    /// retrieve host by hardware address X, verify that the reservation is not
+    /// returned.
+    ///
+    /// Uses gtest macros to report failures.
+    void testClientIdNotHWAddr();
+
+    /// @brief Test adds specified number of hosts with unique hostnames, then
+    /// retrives them and checks that the hostnames are set properly.
+    ///
+    /// Uses gtest macros to report failures.
+    ///
+    /// @param name hostname to be used (if n>1, numbers will be appended)
+    /// @param num number of hostnames to be added.
+    void testHostname(std::string name, int num);
+
+    /// @brief Test inserts multiple reservations for the same host for different
+    /// subnets and check that they can be retrieved properly.
+    ///
+    /// Uses gtest macros to report failures.
+    ///
+    /// @param subnets number of subnets to test
+    /// @param hwaddr true = use HW address, false = use client-id
+    void testMultipleSubnets(int subnets, bool hwaddr);
+
+    /// @brief Test inserts several hosts with unique IPv6 addresses and
+    ///        checks that they can be retrieved properly.
+    ///
+    /// Uses gtest macros to report failures.
+    /// @param id type of the identifier to be used (HWAddr or DUID)
+    /// @param prefix true - reserve IPv6 prefix, false - reserve IPv6 address
+    void testGetByIPv6(BaseHostDataSource::IdType id, bool prefix);
+
+    /// @brief Test that hosts can be retrieved by hardware address.
+    ///
+    /// Uses gtest macros to report failures.
+    void testGet6ByHWAddr();
+
+    /// @brief Test that hosts can be retrieved by client-id
+    ///
+    /// Uses gtest macros to report failures.
+    void testGet6ByClientId();
+
+    /// @brief Test if host reservations made for different IPv6 subnets
+    ///        are handled correctly.
+    ///
+    /// Uses gtest macros to report failures.
+    ///
+    /// @param subnets number of subnets to test
+    /// @param id identifier type (ID_HWADDR or ID_DUID)
+    void testSubnetId6(int subnets, BaseHostDataSource::IdType id);
+
+    /// @brief Test if the duplicate host instances can't be inserted.
+    ///
+    /// Uses gtest macros to report failures.
+    void testAddDuplicate();
+
+    /// @brief Returns DUID with identical content as specified HW address
+    ///
+    /// This method does not have any sense in real life and is only useful
+    /// in testing corner cases in the database backends (e.g. whether the DB
+    /// is able to tell the difference between hwaddr and duid)
+    ///
+    /// @param hwaddr hardware address to be copied
+    /// @return duid with the same value as specified HW address
+    DuidPtr HWAddrToDuid(const HWAddrPtr& hwaddr);
+
+    /// @brief Returns HW address with identical content as specified DUID
+    ///
+    /// This method does not have any sense in real life and is only useful
+    /// in testing corner cases in the database backends (e.g. whether the DB
+    /// is able to tell the difference between hwaddr and duid)
+    ///
+    /// @param duid DUID to be copied
+    /// @return HW address with the same value as specified DUID
+    HWAddrPtr DuidToHWAddr(const DuidPtr& duid);
+};
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
+
+#endif

+ 2 - 2
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc

@@ -688,7 +688,7 @@ GenericLeaseMgrTest::testMaxDate4() {
     // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
     // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
     // time too large to store.
     // time too large to store.
     lease->valid_lft_ = 24*60*60;
     lease->valid_lft_ = 24*60*60;
-    lease->cltt_ = LeaseMgr::MAX_DB_TIME;
+    lease->cltt_ = DatabaseConnection::MAX_DB_TIME;
 
 
     // Insert should throw.
     // Insert should throw.
     ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
     ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
@@ -857,7 +857,7 @@ GenericLeaseMgrTest::testMaxDate6() {
     // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
     // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
     // time too large to store.
     // time too large to store.
     lease->valid_lft_ = 24*60*60;
     lease->valid_lft_ = 24*60*60;
-    lease->cltt_ = LeaseMgr::MAX_DB_TIME;
+    lease->cltt_ = DatabaseConnection::MAX_DB_TIME;
 
 
     // Insert should throw.
     // Insert should throw.
     ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
     ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);

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

@@ -0,0 +1,496 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/tests/test_utils.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/mysql_connection.h>
+#include <dhcpsrv/mysql_host_data_source.h>
+#include <dhcpsrv/tests/generic_host_data_source_unittest.h>
+#include <dhcpsrv/host_data_source_factory.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace std;
+
+namespace {
+
+// This holds statements to create and destroy the schema.
+#include "schema_mysql_copy.h"
+
+// Connection strings.
+// Database: keatest
+// Host: localhost
+// Username: keatest
+// Password: keatest
+const char* VALID_TYPE = "type=mysql";
+const char* INVALID_TYPE = "type=unknown";
+const char* VALID_NAME = "name=keatest";
+const char* INVALID_NAME = "name=invalidname";
+const char* VALID_HOST = "host=localhost";
+const char* INVALID_HOST = "host=invalidhost";
+const char* VALID_USER = "user=keatest";
+const char* INVALID_USER = "user=invaliduser";
+const char* VALID_PASSWORD = "password=keatest";
+const char* INVALID_PASSWORD = "password=invalid";
+
+// Given a combination of strings above, produce a connection string.
+string connectionString(const char* type, const char* name, const char* host,
+                        const char* user, const char* password) {
+    const string space = " ";
+    string result = "";
+
+    if (type != NULL) {
+        result += string(type);
+    }
+    if (name != NULL) {
+        if (! result.empty()) {
+            result += space;
+        }
+        result += string(name);
+    }
+
+    if (host != NULL) {
+        if (! result.empty()) {
+            result += space;
+        }
+        result += string(host);
+    }
+
+    if (user != NULL) {
+        if (! result.empty()) {
+            result += space;
+        }
+        result += string(user);
+    }
+
+    if (password != NULL) {
+        if (! result.empty()) {
+            result += space;
+        }
+        result += string(password);
+    }
+
+    return (result);
+}
+
+// Return valid connection string
+string
+validConnectionString() {
+    return (connectionString(VALID_TYPE, VALID_NAME, VALID_HOST,
+                             VALID_USER, VALID_PASSWORD));
+}
+
+// @brief Clear everything from the database
+//
+// There is no error checking in this code: if something fails, one of the
+// tests will (should) fall over.
+void destroySchema() {
+    MySqlHolder mysql;
+
+    // Open database
+    (void) mysql_real_connect(mysql, "localhost", "keatest",
+                              "keatest", "keatest", 0, NULL, 0);
+
+    // Get rid of everything in it.
+    for (int i = 0; destroy_statement[i] != NULL; ++i) {
+        (void) mysql_query(mysql, destroy_statement[i]);
+    }
+}
+
+// @brief Create the Schema
+//
+// Creates all the tables in what is assumed to be an empty database.
+//
+// There is no error checking in this code: if it fails, one of the tests
+// will fall over.
+void createSchema() {
+    MySqlHolder mysql;
+
+    // Open database
+    (void) mysql_real_connect(mysql, "localhost", "keatest",
+                              "keatest", "keatest", 0, NULL, 0);
+
+    // Execute creation statements.
+    for (int i = 0; create_statement[i] != NULL; ++i) {
+        ASSERT_EQ(0, mysql_query(mysql, create_statement[i]))
+            << "Failed on statement " << i << ": " << create_statement[i];
+    }
+}
+
+
+
+class MySqlHostDataSourceTest : public GenericHostDataSourceTest {
+public:
+    /// @brief Constructor
+    ///
+    /// Deletes everything from the database and opens it.
+    MySqlHostDataSourceTest() {
+
+        // Ensure schema is the correct one.
+        destroySchema();
+        createSchema();
+
+        // Connect to the database
+        try {
+            HostDataSourceFactory::create(validConnectionString());
+        } catch (...) {
+            std::cerr << "*** ERROR: unable to open database. The test\n"
+                         "*** environment is broken and must be fixed before\n"
+                         "*** the MySQL tests will run correctly.\n"
+                         "*** The reason for the problem is described in the\n"
+                         "*** accompanying exception output.\n";
+            throw;
+        }
+
+        hdsptr_ = &(HostDataSourceFactory::instance());
+    }
+
+    /// @brief Destructor
+    ///
+    /// Rolls back all pending transactions.  The deletion of myhdsptr_ will close
+    /// the database.  Then reopen it and delete everything created by the test.
+    virtual ~MySqlHostDataSourceTest() {
+        hdsptr_->rollback();
+        HostDataSourceFactory::destroy();
+        destroySchema();
+    }
+
+    /// @brief Reopen the database
+    ///
+    /// Closes the database and re-open it.  Anything committed should be
+    /// visible.
+    ///
+    /// Parameter is ignored for MySQL backend as the v4 and v6 leases share
+    /// the same database.
+    void reopen(Universe) {
+        HostDataSourceFactory::destroy();
+        HostDataSourceFactory::create(validConnectionString());
+        hdsptr_ = &(HostDataSourceFactory::instance());
+    }
+
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the MySqlHostDataSource can be instantiated.  This happens
+/// only if the database can be opened.  Note that this is not part of the
+/// MySqlLeaseMgr test fixure set.  This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+
+TEST(MySqlHostDataSource, OpenDatabase) {
+
+    // Schema needs to be created for the test to work.
+    destroySchema();
+    createSchema();
+
+    // Check that lease manager open the database opens correctly and tidy up.
+    //  If it fails, print the error message.
+    try {
+        HostDataSourceFactory::create(validConnectionString());
+        EXPECT_NO_THROW((void) HostDataSourceFactory::instance());
+        HostDataSourceFactory::destroy();
+    } catch (const isc::Exception& ex) {
+        FAIL() << "*** ERROR: unable to open database, reason:\n"
+               << "    " << ex.what() << "\n"
+               << "*** The test environment is broken and must be fixed\n"
+               << "*** before the MySQL tests will run correctly.\n";
+    }
+
+    // Check that attempting to get an instance of the lease manager when
+    // none is set throws an exception.
+    EXPECT_THROW(HostDataSourceFactory::instance(), NoHostDataSourceManager);
+
+    // Check that wrong specification of backend throws an exception.
+    // (This is really a check on LeaseMgrFactory, but is convenient to
+    // perform here.)
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+        InvalidParameter);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+        InvalidType);
+
+    // Check that invalid login data causes an exception.
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+        DbOpenError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+        DbOpenError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+        DbOpenError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+        DbOpenError);
+
+    // Check for missing parameters
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+        NoDatabaseName);
+
+    // Tidy up after the test
+    destroySchema();
+}
+
+/// @brief Check conversion functions
+///
+/// The server works using cltt and valid_filetime.  In the database, the
+/// information is stored as expire_time and valid-lifetime, which are
+/// related by
+///
+/// expire_time = cltt + valid_lifetime
+///
+/// This test checks that the conversion is correct.  It does not check that the
+/// data is entered into the database correctly, only that the MYSQL_TIME
+/// structure used for the entry is correctly set up.
+TEST(MySqlConnection, checkTimeConversion) {
+    const time_t cltt = time(NULL);
+    const uint32_t valid_lft = 86400;       // 1 day
+    struct tm tm_expire;
+    MYSQL_TIME mysql_expire;
+
+    // Work out what the broken-down time will be for one day
+    // after the current time.
+    time_t expire_time = cltt + valid_lft;
+    (void) localtime_r(&expire_time, &tm_expire);
+
+    // Convert to the database time
+    MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
+
+    // Are the times the same?
+    EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
+    EXPECT_EQ(tm_expire.tm_mon + 1,  mysql_expire.month);
+    EXPECT_EQ(tm_expire.tm_mday, mysql_expire.day);
+    EXPECT_EQ(tm_expire.tm_hour, mysql_expire.hour);
+    EXPECT_EQ(tm_expire.tm_min, mysql_expire.minute);
+    EXPECT_EQ(tm_expire.tm_sec, mysql_expire.second);
+    EXPECT_EQ(0, mysql_expire.second_part);
+    EXPECT_EQ(0, mysql_expire.neg);
+
+    // Convert back
+    time_t converted_cltt = 0;
+    MySqlConnection::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
+    EXPECT_EQ(cltt, converted_cltt);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses hw address as identifier.
+TEST_F(MySqlHostDataSourceTest, basic4HWAddr) {
+    testBasic4(true);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses client-id (DUID) as identifier.
+TEST_F(MySqlHostDataSourceTest, basic4ClientId) {
+    testBasic4(false);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(MySqlHostDataSourceTest, getByIPv4HWaddr) {
+    testGetByIPv4(true);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(MySqlHostDataSourceTest, getByIPv4ClientId) {
+    testGetByIPv4(false);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(MySqlHostDataSourceTest, get4ByHWaddr) {
+    testGet4ByHWAddr();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// client identifier.
+TEST_F(MySqlHostDataSourceTest, get4ByClientId) {
+    testGet4ByClientId();
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId1) {
+    testHWAddrNotClientId();
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId2) {
+    testClientIdNotHWAddr();
+}
+
+// Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(MySqlHostDataSourceTest, hostnameFQDN) {
+    testHostname("foo.example.org", 1);
+}
+
+// Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+// retrieved.
+TEST_F(MySqlHostDataSourceTest, hostnameFQDN100) {
+    testHostname("foo.example.org", 100);
+}
+
+// Test verifies if a host without any hostname specified can be stored and later
+// retrieved.
+TEST_F(MySqlHostDataSourceTest, noHostname) {
+    testHostname("", 1);
+}
+
+// Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId1) {
+    /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+    /// be discussed.
+    ///
+    /// @todo: Add host reservation with hardware address X, try to retrieve
+    /// host for hardware address X or client identifier Y, verify that the
+    /// reservation is returned.
+}
+
+// Test verifies if the hardware or client-id query can match client-id.
+TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
+    /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+    /// be discussed.
+    ///
+    /// @todo: Add host reservation with client identifier Y, try to retrieve
+    /// host for hardware address X or client identifier Y, verify that the
+    /// reservation is returned.
+}
+
+// Test verifies that host with IPv6 address and DUID can be added and
+// later retrieved by IPv6 address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6AddrWithDuid) {
+    /// @todo: Uncomment when IPv6 support (4212) is implemented.
+    testGetByIPv6(BaseHostDataSource::ID_DUID, false);
+}
+
+// Test verifies that host with IPv6 address and HWAddr can be added and
+// later retrieved by IPv6 address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6AddrWithHWAddr) {
+    /// @todo: Uncomment when IPv6 support (4212) is implemented.
+    testGetByIPv6(BaseHostDataSource::ID_HWADDR, false);
+}
+
+// Test verifies that host with IPv6 prefix and DUID can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6PrefixWithDuid) {
+    /// @todo: Uncomment when IPv6 support (4212) is implemented.
+    testGetByIPv6(BaseHostDataSource::ID_DUID, true);
+}
+
+// Test verifies that host with IPv6 prefix and HWAddr can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6PrefixWithHWaddr) {
+    /// @todo: Uncomment when IPv6 support (4212) is implemented.
+    testGetByIPv6(BaseHostDataSource::ID_HWADDR, true);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6ByHWaddr) {
+    /// @todo: Uncomment when IPv6 support (4212) is implemented.
+    testGet6ByHWAddr();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// client identifier.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6ByClientId) {
+    /// @todo: Uncomment when IPv6 support (4212) is implemented.
+    testGet6ByClientId();
+}
+
+// Test verifies if a host reservation can be stored with both IPv6 address and
+// prefix.
+TEST_F(MySqlHostDataSourceTest, DISABLED_addr6AndPrefix) {
+    /// @todo: Implement this test as part of #4212.
+
+    /// @todo: Add host reservation with an IPv6 address and IPv6 prefix,
+    /// retrieve it and verify that both v6 address and prefix are retrieved
+    /// correctly.
+}
+
+// Test verifies if multiple client classes for IPv4 can be stored.
+TEST_F(MySqlHostDataSourceTest, DISABLED_multipleClientClasses4) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v4 client-classes, retrieve it and
+    /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for IPv6 can be stored.
+TEST_F(MySqlHostDataSourceTest, DISABLED_multipleClientClasses6) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v6 client-classes, retrieve it and
+    /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for both IPv4 and IPv6 can be stored.
+TEST_F(MySqlHostDataSourceTest, DISABLED_multipleClientClassesBoth) {
+    /// @todo: Implement this test as part of #4213..
+
+    /// Add host reservation with a multiple v4 and v6 client-classes, retrieve
+    /// it and make sure that all client classes are retrieved properly. Also,
+    /// check that the classes are not confused.
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same hardware address). The test logic is as follows:
+// Insert 10 host reservations for a given physical host (the same
+// hardware address), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them all correctly.
+TEST_F(MySqlHostDataSourceTest, multipleSubnetsHWAddr) {
+    testMultipleSubnets(10, true);
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same client identifier). The test logic is as follows:
+//
+// Insert 10 host reservations for a given physical host (the same
+// client-identifier), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them correctly.
+TEST_F(MySqlHostDataSourceTest, multipleSubnetsClientId) {
+    testMultipleSubnets(10, false);
+}
+
+// Test if host reservations made for different IPv6 subnets are handled correctly.
+// The test logic is as follows:
+//
+// Insert 10 host reservations for different subnets. Make sure that
+// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(MySqlHostDataSourceTest, subnetId6) {
+    testSubnetId6(10, BaseHostDataSource::ID_HWADDR);
+}
+
+// Test if the duplicate host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+TEST_F(MySqlHostDataSourceTest, addDuplicate) {
+    testAddDuplicate();
+}
+
+}; // Of anonymous namespace

+ 3 - 2
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc

@@ -16,6 +16,7 @@
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/mysql_connection.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
@@ -289,7 +290,7 @@ TEST_F(MySqlLeaseMgrTest, checkTimeConversion) {
     (void) localtime_r(&expire_time, &tm_expire);
     (void) localtime_r(&expire_time, &tm_expire);
 
 
     // Convert to the database time
     // Convert to the database time
-    MySqlLeaseMgr::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
+    MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
 
 
     // Are the times the same?
     // Are the times the same?
     EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
     EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
@@ -303,7 +304,7 @@ TEST_F(MySqlLeaseMgrTest, checkTimeConversion) {
 
 
     // Convert back
     // Convert back
     time_t converted_cltt = 0;
     time_t converted_cltt = 0;
-    MySqlLeaseMgr::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
+    MySqlConnection::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
     EXPECT_EQ(cltt, converted_cltt);
     EXPECT_EQ(cltt, converted_cltt);
 }
 }
 
 

+ 2 - 0
src/lib/dhcpsrv/tests/schema_mysql_copy.h

@@ -45,6 +45,8 @@ const char* destroy_statement[] = {
     "DROP TABLE hosts",
     "DROP TABLE hosts",
     "DROP TABLE dhcp4_options",
     "DROP TABLE dhcp4_options",
     "DROP TABLE dhcp6_options",
     "DROP TABLE dhcp6_options",
+
+    "DROP TRIGGER host_BDEL",
     NULL
     NULL
 };
 };