]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[3682] Patch sent by Adam Kalmus
authorTomek Mrugalski <tomasz@isc.org>
Fri, 18 Sep 2015 17:55:19 +0000 (19:55 +0200)
committerTomek Mrugalski <tomasz@isc.org>
Fri, 18 Sep 2015 18:00:49 +0000 (20:00 +0200)
 The patch did not apply cleanly. The closest commit (with smallest
 number of conflicts was 66fb75897931e04b145c3b4e76239746df82a59c).
 I managed to resolve 2 conflicts.

22 files changed:
src/lib/dhcpsrv/Makefile.am [changed mode: 0644->0755]
src/lib/dhcpsrv/database_connection.cc
src/lib/dhcpsrv/database_connection.h
src/lib/dhcpsrv/host_data_source_factory.cc [new file with mode: 0644]
src/lib/dhcpsrv/host_data_source_factory.h [new file with mode: 0644]
src/lib/dhcpsrv/host_mgr.cc
src/lib/dhcpsrv/host_mgr.h
src/lib/dhcpsrv/lease_mgr.cc
src/lib/dhcpsrv/lease_mgr.h
src/lib/dhcpsrv/mysql_connection.cc
src/lib/dhcpsrv/mysql_connection.h
src/lib/dhcpsrv/mysql_host_data_source.cc [new file with mode: 0644]
src/lib/dhcpsrv/mysql_host_data_source.h [new file with mode: 0644]
src/lib/dhcpsrv/mysql_lease_mgr.cc
src/lib/dhcpsrv/mysql_lease_mgr.h
src/lib/dhcpsrv/pgsql_lease_mgr.cc
src/lib/dhcpsrv/tests/Makefile.am [changed mode: 0644->0755]
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h [new file with mode: 0644]
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc

old mode 100644 (file)
new mode 100755 (executable)
index 69a80c9..3aeefb2
@@ -96,6 +96,7 @@ libkea_dhcpsrv_la_SOURCES += database_connection.cc database_connection.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.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 += hosts_log.cc hosts_log.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
@@ -112,6 +113,7 @@ libkea_dhcpsrv_la_SOURCES += memfile_lease_storage.h
 if HAVE_MYSQL
 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_host_data_source.cc mysql_host_data_source.h
 endif
 if HAVE_PGSQL
 libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
index 440a4d21cd11c19853e7182e8553db55fd003322..a64cf17ca27472345332d16a7085d218ff5b2afc 100755 (executable)
@@ -25,6 +25,8 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
+const time_t DataSource::MAX_DB_TIME = 2147483647;
+
 std::string
 DatabaseConnection::getParameter(const std::string& name) const {
     ParameterMap::const_iterator param = parameters_.find(name);
index 7e75debaeb045392b037ee9d85c0667e83ae8ed2..81a8c6f2b6ce71a23cfc43198bdfc282a0ef560d 100755 (executable)
@@ -55,6 +55,11 @@ public:
 /// @ref BaseHostDataSource derived classes.
 class DatabaseConnection : public boost::noncopyable {
 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 Database configuration parameter map
     typedef std::map<std::string, std::string> ParameterMap;
 
diff --git a/src/lib/dhcpsrv/host_data_source_factory.cc b/src/lib/dhcpsrv/host_data_source_factory.cc
new file mode 100644 (file)
index 0000000..9ccb929
--- /dev/null
@@ -0,0 +1,102 @@
+// 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/mysql_host_data_source.h>
+
+#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.
+    DataSource::ParameterMap parameters = DataSource::parse(dbaccess);
+    std::string redacted = DataSource::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);
+        // set pgsql data source here, when it will be featured
+        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, DHCPSRV_CLOSE_DB);
+    }
+    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
diff --git a/src/lib/dhcpsrv/host_data_source_factory.h b/src/lib/dhcpsrv/host_data_source_factory.h
new file mode 100644 (file)
index 0000000..d485025
--- /dev/null
@@ -0,0 +1,73 @@
+// 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/data_source.h>
+#include <exceptions/exceptions.h>
+
+#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) {}
+};
+
+class HostDataSourceFactory {
+public:
+    /// @brief todo
+       ///
+    static void create(const std::string& dbaccess);
+
+    /// @brief Destroy host data source instance
+       ///
+       /// todo
+       static void destroy();
+
+       /// @brief Return current host data source instance
+       /// todo
+       static BaseHostDataSource& instance();
+
+private:
+    /// @brief Hold pointer to host data source instance
+    ///
+    /// todo
+    static boost::scoped_ptr<BaseHostDataSource>& getHostDataSourcePtr();
+
+};
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
index a0a3e455cfe91ed9753e395fe6bac16f3685aea1..5b7d8577fc1bbcf4e1fc2f60d96292cd8e04975f 100644 (file)
@@ -17,6 +17,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/hosts_log.h>
+#include <dhcpsrv/mysql_host_data_source.h>
 
 namespace {
 
@@ -47,6 +48,7 @@ void
 HostMgr::create(const std::string&) {
     getHostMgrPtr().reset(new HostMgr());
 
+    //alternate_source.reset(new MySqlHostDataSource());
     /// @todo Initialize alternate_source here, using the parameter.
     /// For example: alternate_source.reset(new MysqlHostDataSource(access)).
 }
@@ -152,7 +154,6 @@ HostMgr::get6(const SubnetID& subnet_id,
     return (host);
 }
 
-
 void
 HostMgr::add(const HostPtr&) {
     isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");
index 55a4f4c05e0ef24f64fed15ad97ed8bc67e3da54..82c6cd6308bb42a5ee8dfbcbacbceaccf453bf17 100644 (file)
@@ -18,6 +18,7 @@
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/mysql_host_data_source.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/subnet_id.h>
 #include <boost/noncopyable.hpp>
index 4153771131a4bc3dc12fa6001f96bb0baea97e24..2067e7a74e0098cd433c88030fb3f982bc60f7be 100755 (executable)
@@ -35,8 +35,6 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
-const time_t LeaseMgr::MAX_DB_TIME = 2147483647;
-
 Lease6Ptr
 LeaseMgr::getLease6(Lease::Type type, const DUID& duid,
                     uint32_t iaid, SubnetID subnet_id) const {
index d7536a9d12ece055da2946c82aebb8d460a1663b..54497f08d0e311ef125bb21b90a2c7089e64e502 100755 (executable)
@@ -112,10 +112,6 @@ public:
 /// of those classes for details.
 class LeaseMgr {
 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
     ///
     LeaseMgr() : io_service_(new asiolink::IOService())
index b9d3e05e68e65c7a37023d3df566fe792b0d6eb1..3921d9497b4ee623ad624a14c47913b880291363 100755 (executable)
@@ -193,6 +193,68 @@ MySqlConnection::~MySqlConnection() {
     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 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 > DataSource::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;
 }
 
 } // namespace isc::dhcp
index 0824d4955aba84839a2ba400b1ebcb387781c9e3..e940c754fa0ebfb5dd52bea03fb1da884f9813eb 100755 (executable)
@@ -189,6 +189,59 @@ public:
     /// @throw DbOpenError Error opening the database
     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 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 Prepared statements
     ///
     /// This field is public, because it is used heavily from MySqlConnection
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc
new file mode 100644 (file)
index 0000000..b2f0732
--- /dev/null
@@ -0,0 +1,1002 @@
+// 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 <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/mysql_host_data_source.h>
+#include <dhcpsrv/mysql_connection.h>
+
+#include <boost/static_assert.hpp>
+#include <mysqld_error.h>
+
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace {
+/// @brief Maximum length of classes stored in a dhcp4/6_client_classes field
+///
+const size_t CLIENT_CLASSES_MAX_LEN = 255;
+
+/// @brief Maximum length of the hostname stored in DNS.
+///
+/// This length is restricted by the length of the domain-name carried
+/// in the Client FQDN %Option (see RFC4702 and RFC4704).
+const size_t HOSTNAME_MAX_LEN = 255;
+
+/// @brief Maximum length of dhcp identifier field
+///
+const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
+
+TaggedStatement tagged_statements[] = {
+       {MySqlHostDataSource::INSERT_HOST,
+                           "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+                       "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, "
+                                       "hostname, dhcp4_client_classes, dhcp6_client_classes) "
+                                       "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
+       {MySqlHostDataSource::GET_HOST_HWADDR_DUID,
+                                       "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+                               "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+                               "FROM hosts "
+                               "WHERE dhcp_identifier = ?"},
+       {MySqlHostDataSource::GET_HOST_ADDR,
+                               "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+                               "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+                               "FROM hosts "
+                               "WHERE ipv4_address = ?"},
+       {MySqlHostDataSource::GET_HOST_SUBID4_DHCPID,
+                                       "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+                                       "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+                                       "FROM hosts "
+                                       "WHERE dhcp4_subnet_id = ? AND dhcp_identifier = ?"},
+       {MySqlHostDataSource::GET_HOST_SUBID6_DHCPID,
+                                       "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+                                       "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+                                       "FROM hosts "
+                                       "WHERE dhcp6_subnet_id = ? AND dhcp_identifier = ?"},
+       {MySqlHostDataSource::GET_HOST_SUBID_ADDR,
+                                       "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+                                       "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+                                       "FROM hosts "
+                                       "WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
+       {MySqlHostDataSource::GET_HOST_PREFIX,
+                                       "SELECT h.host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+                                       "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+                                       "FROM hosts h "
+                               "LEFT JOIN ipv6_reservations r ON h.host_id = r.host_id "
+                               "WHERE r.prefix_len = ? AND r.address = ?"},
+    {MySqlHostDataSource::GET_VERSION,
+                    "SELECT version, minor FROM schema_version"},
+    {MySqlHostDataSource::NUM_STATEMENTS, NULL}
+};
+
+};
+
+namespace isc {
+namespace dhcp {
+
+class MySqlHostReservationExchange {
+    /// @brief Set number of database columns for this host structure
+    static const size_t HOST_COLUMNS = 9;
+
+public:
+
+    /// @brief Constructor
+    ///
+    /// The initialization of the variables here is only to satisfy cppcheck -
+    /// all variables are initialized/set in the methods before they are used.
+    MySqlHostReservationExchange() : host_id_(0), dhcp_identifier_length_(0),
+                                                                       dhcp_identifier_type_(0), dhcp4_subnet_id_(0),
+                                                                       dhcp6_subnet_id_(0), ipv4_address_(0),
+                                                                       hostname_length_(0), dhcp4_client_classes_length_(0),
+                                                                       dhcp6_client_classes_length_(0), dhcp4_subnet_id_null_(MLM_FALSE),
+                                                                       dhcp6_subnet_id_null_(MLM_FALSE), ipv4_address_null_(MLM_FALSE),
+                                                                       hostname_null_(MLM_FALSE), dhcp4_client_classes_null_(MLM_FALSE),
+                                                                       dhcp6_client_classes_null_(MLM_FALSE){
+
+        memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
+        memset(hostname_, 0, sizeof(hostname_));
+        memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
+        memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
+        std::fill(&error_[0], &error_[HOST_COLUMNS], MLM_FALSE);
+
+        // Set the column names (for error messages)
+        columns_[0] = "host_id";
+        columns_[1] = "dhcp_identifier";
+        columns_[2] = "dhcp_identifier_type";
+        columns_[3] = "dhcp4_subnet_id";
+        columns_[4] = "dhcp6_subnet_id";
+        columns_[5] = "ipv4_address";
+        columns_[6] = "hostname";
+        columns_[7] = "dhcp4_client_classes";
+        columns_[8] = "dhcp6_client_classes";
+        BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
+    }
+
+    /// @brief Set error indicators
+    ///
+    /// Sets the error indicator for each of the MYSQL_BIND elements.  It points
+    /// the "error" field within an element of the input array to the
+    /// corresponding element of the passed error array.
+    ///
+    /// @param bind Array of BIND elements
+    /// @param error Array of error elements.  If there is an error in getting
+    ///        data associated with one of the "bind" elements, the
+    ///        corresponding element in the error array is set to MLM_TRUE.
+    /// @param count Size of each of the arrays.
+    static void setErrorIndicators(MYSQL_BIND* bind, my_bool* error,
+                                   size_t count) {
+        for (size_t i = 0; i < count; ++i) {
+            error[i] = MLM_FALSE;
+            bind[i].error = reinterpret_cast<char*>(&error[i]);
+        }
+    }
+
+    /// @brief Return columns in error
+    ///
+    /// If an error is returned from a fetch (in particular, a truncated
+    /// status), this method can be called to get the names of the fields in
+    /// error.  It returns a string comprising the names of the fields
+    /// separated by commas.  In the case of there being no error indicators
+    /// set, it returns the string "(None)".
+    ///
+    /// @param error Array of error elements.  An element is set to MLM_TRUE
+    ///        if the corresponding column in the database is the source of
+    ///        the error.
+    /// @param names Array of column names, the same size as the error array.
+    /// @param count Size of each of the arrays.
+    static std::string getColumnsInError(my_bool* error, std::string* names,
+                                         size_t count) {
+        std::string result = "";
+
+        // Accumulate list of column names
+        for (size_t i = 0; i < count; ++i) {
+            if (error[i] == MLM_TRUE) {
+                if (!result.empty()) {
+                    result += ", ";
+                }
+                result += names[i];
+            }
+        }
+
+        if (result.empty()) {
+            result = "(None)";
+        }
+
+        return (result);
+    }
+
+    /// @brief Create MYSQL_BIND objects for Host Pointer
+    ///
+    /// Fills in the MYSQL_BIND array for sending data in the Host object to
+    /// the database.
+    ///
+    /// @param host Host object to be added to the database.  None of the
+    ///        fields in the host reservation are modified - the host data is only read.
+    ///
+    /// @return Vector of MySQL BIND objects representing the data to be added.
+    std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
+
+       // Store host object to ensure it remains valid.
+       host_ = host;
+
+        // Initialize prior to constructing the array of MYSQL_BIND structures.
+        // It sets all fields, including is_null, to zero, so we need to set
+        // is_null only if it should be true. This gives up minor performance
+        // benefit while being safe approach. For improved readability, the
+        // code that explicitly sets is_null is there, but is commented out.
+        memset(bind_, 0, sizeof(bind_));
+
+        // Set up the structures for the various components of the host structure.
+
+        try {
+            // host_id : INT UNSIGNED NOT NULL
+               // The host_id is auto_incremented by MySQL database,
+               // so we need to pass the NULL value
+            host_id_ = static_cast<uint32_t>(NULL);
+            bind_[0].buffer_type = MYSQL_TYPE_LONG;
+            bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+            bind_[0].is_unsigned = MLM_TRUE;
+            // bind_[0].is_null = &MLM_TRUE;   //compile problems here!
+
+            // dhcp_identifier : VARBINARY(128) NOT NULL
+            // Check which of the identifiers is used and set values accordingly
+            if (host_->getDuid()) {
+               dhcp_identifier_length_ = host_->getDuid()->getDuid().size();
+                               bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+                               bind_[1].buffer = reinterpret_cast<char*>(host_->getDuid()->getDuid()[0]);
+                               bind_[1].buffer_length = dhcp_identifier_length_;
+                               bind_[1].length = &dhcp_identifier_length_;
+                               // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+                                                 // reasons, see memset() above
+            } else if (host_->getHWAddress()){
+                               dhcp_identifier_length_ = host_->getHWAddress()->hwaddr_.size();
+                               bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+                               bind_[1].buffer = reinterpret_cast<char*>(&(host_->getHWAddress()->hwaddr_[0]));
+                               bind_[1].buffer_length = dhcp_identifier_length_;
+                               bind_[1].length = &dhcp_identifier_length_;
+                               // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+                                                 // reasons, see memset() above
+            }
+
+            // dhcp_identifier_type : TINYINT NOT NULL
+            // Check which of the identifier types is used and set values accordingly
+            if (host_->getHWAddress()) {
+                               bind_[2].buffer_type = MYSQL_TYPE_TINY;
+                               bind_[2].buffer = reinterpret_cast<char*>(0);   // 0 = IDENT_HWADDR
+                               bind_[2].is_unsigned = MLM_TRUE;
+                               // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+                                                 // reasons, see memset() above
+            } else if (host_->getDuid()) {
+                               bind_[2].buffer_type = MYSQL_TYPE_TINY;
+                               bind_[2].buffer = reinterpret_cast<char*>(1);   // 1 = IDENT_DUID
+                               bind_[2].is_unsigned = MLM_TRUE;
+                               // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+                                                 // reasons, see memset() above
+            }
+
+            // dhcp4_subnet_id : INT UNSIGNED NULL
+            bind_[3].buffer_type = MYSQL_TYPE_LONG;
+            bind_[3].buffer = reinterpret_cast<char*>(host_->getIPv4SubnetID());
+            bind_[3].is_unsigned = MLM_TRUE;
+            // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+                                              // reasons, see memset() above
+
+            // dhcp6_subnet_id : INT UNSIGNED NULL
+            bind_[4].buffer_type = MYSQL_TYPE_LONG;
+            bind_[4].buffer = reinterpret_cast<char*>(host_->getIPv6SubnetID());
+            bind_[4].is_unsigned = MLM_TRUE;
+            // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+                                              // reasons, see memset() above
+
+            // ipv4_address : INT UNSIGNED NULL
+            // The address in the Host structure is an IOAddress object.  Convert
+            // this to an integer for storage.
+            ipv4_address_ = static_cast<uint32_t>(host_->getIPv4Reservation());
+            bind_[5].buffer_type = MYSQL_TYPE_LONG;
+            bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
+            bind_[5].is_unsigned = MLM_TRUE;
+            // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+                                              // reasons, see memset() above
+
+            // hostname : VARCHAR(255) NULL
+            bind_[6].buffer_type = MYSQL_TYPE_STRING;
+            bind_[6].buffer = reinterpret_cast<char*>(host_->getHostname()[0]);
+            bind_[6].buffer_length = host_->getHostname().length();
+            // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+                                              // reasons, see memset() above
+
+            // dhcp4_client_classes : VARCHAR(255) NULL
+            // TODO this should probably be converted in some way... Don't know how yet,
+            // also size method might be wrong
+            bind_[7].buffer_type = MYSQL_TYPE_STRING;
+            //bind_[7].buffer = reinterpret_cast<char*>(host_->getClientClasses4());
+            bind_[7].buffer_length = sizeof(host_->getClientClasses4());
+            // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+                                              // reasons, see memset() above
+
+            // dhcp6_client_classes : VARCHAR(255) NULL
+            bind_[8].buffer_type = MYSQL_TYPE_STRING;
+            //bind_[8].buffer = reinterpret_cast<char*>(host_->getClientClasses6());
+            bind_[8].buffer_length = sizeof(host_->getClientClasses6());
+            // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+                                              // reasons, see memset() above
+
+
+        } catch (const std::exception& ex) {
+            isc_throw(DbOperationError,
+                      "Could not create bind array from Host: "
+                      << host_->getHostname() << ", reason: " << ex.what());
+        }
+
+        // Add the data to the vector.  Note the end element is one after the
+        // end of the array.
+        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[HOST_COLUMNS]));
+    }
+
+    /// @brief Create BIND array to receive data
+    ///
+    /// Creates a MYSQL_BIND array to receive Lease4 data from the database.
+    /// After data is successfully received, getLeaseData() can be used to copy
+    /// it to a Lease6 object.
+    ///
+    std::vector<MYSQL_BIND> createBindForReceive() {
+        // Initialize MYSQL_BIND array.
+        // It sets all fields, including is_null, to zero, so we need to set
+        // is_null only if it should be true. This gives up minor performance
+        // benefit while being safe approach. For improved readability, the
+        // code that explicitly sets is_null is there, but is commented out.
+        memset(bind_, 0, sizeof(bind_));
+
+        // host_id : INT UNSIGNED NOT NULL
+        bind_[0].buffer_type = MYSQL_TYPE_LONG;
+        bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+        bind_[0].is_unsigned = MLM_TRUE;
+        // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+                                          // reasons, see memset() above
+
+        // dhcp_identifier : VARBINARY(128) NOT NULL
+        dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
+        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+        bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
+        bind_[1].buffer_length = dhcp_identifier_length_;
+        bind_[1].length = &dhcp_identifier_length_;
+        // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+                                          // reasons, see memset() above
+
+        // dhcp_identifier_type : TINYINT NOT NULL
+        bind_[2].buffer_type = MYSQL_TYPE_TINY;
+        bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+        bind_[2].is_unsigned = MLM_TRUE;
+        // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+                                          // reasons, see memset() above
+
+        // dhcp4_subnet_id : INT UNSIGNED NULL
+        dhcp4_subnet_id_null_ = MLM_FALSE;
+        bind_[3].buffer_type = MYSQL_TYPE_LONG;
+        bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
+        bind_[3].is_unsigned = MLM_TRUE;
+        bind_[3].is_null = &dhcp4_subnet_id_null_;
+
+        // dhcp6_subnet_id : INT UNSIGNED NULL
+        dhcp6_subnet_id_null_ = MLM_FALSE;
+        bind_[4].buffer_type = MYSQL_TYPE_LONG;
+        bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
+        bind_[4].is_unsigned = MLM_TRUE;
+        bind_[4].is_null = &dhcp6_subnet_id_null_;
+
+        // ipv4_address : INT UNSIGNED NULL
+        ipv4_address_null_ = MLM_FALSE;
+        bind_[5].buffer_type = MYSQL_TYPE_LONG;
+        bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
+        bind_[5].is_unsigned = MLM_TRUE;
+        bind_[5].is_null = &ipv4_address_null_;
+
+        // hostname : VARCHAR(255) NULL
+        hostname_null_ = MLM_FALSE;
+        hostname_length_ = sizeof(hostname_);
+        bind_[6].buffer_type = MYSQL_TYPE_STRING;
+        bind_[6].buffer = reinterpret_cast<char*>(hostname_);
+        bind_[6].buffer_length = hostname_length_;
+        bind_[6].length = &hostname_length_;
+        bind_[6].is_null = &hostname_null_;
+
+        // dhcp4_client_classes : VARCHAR(255) NULL
+        dhcp4_client_classes_null_ = MLM_FALSE;
+        dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
+        bind_[7].buffer_type = MYSQL_TYPE_STRING;
+        bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
+        bind_[7].buffer_length = dhcp4_client_classes_length_;
+        bind_[7].length = &dhcp4_client_classes_length_;
+        bind_[7].is_null = &dhcp4_client_classes_null_;
+
+        // dhcp6_client_classes : VARCHAR(255) NULL
+        dhcp6_client_classes_null_ = MLM_FALSE;
+        dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
+        bind_[8].buffer_type = MYSQL_TYPE_STRING;
+        bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
+        bind_[8].buffer_length = dhcp6_client_classes_length_;
+        bind_[8].length = &dhcp6_client_classes_length_;
+        bind_[8].is_null = &dhcp6_client_classes_null_;
+
+        // Add the error flags
+        setErrorIndicators(bind_, error_, HOST_COLUMNS);
+
+        // .. and check that we have the numbers correct at compile time.
+        BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
+
+        // Add the data to the vector.  Note the end element is one after the
+        // end of the array.
+        return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[HOST_COLUMNS]));
+
+    }
+
+    /// @brief Copy Received Data into Host Object
+    ///
+    /// Called after the MYSQL_BIND array created by createBindForReceive()
+    /// has been used, this copies data from the internal member variables
+    /// into a Host objec.
+    ///
+    /// @return Host Pointer to a Lease6 object holding the relevant data.
+    HostPtr getHostData(){
+
+        // Set the dhcp identifier type in a variable of the appropriate data type, which
+        // has been initialized with an arbitrary (but valid) value.
+        Host::IdentifierType type = Host::IDENT_HWADDR;
+
+        switch (dhcp_identifier_type_) {
+            case 0:
+                type = Host::IDENT_HWADDR;
+                break;
+
+            case 1:
+                type = Host::IDENT_DUID;
+                break;
+
+            default:
+                isc_throw(BadValue, "invalid dhcp identifier type returned (" <<
+                          static_cast<int>(dhcp_identifier_type_) << ") for host with "
+                          << "identifier " << reinterpret_cast<char*>(dhcp_identifier_[0]) << ". Only 0 or 1 are allowed.");
+        }
+
+        // Set subnets ID's if they are given, if not, leave an empty object
+        SubnetID ipv4_subnet_id = static_cast<SubnetID>(NULL);                                 // This is probably wrong way to do it...
+        if (dhcp4_subnet_id_null_ == MLM_FALSE)
+               ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
+
+        SubnetID ipv6_subnet_id = static_cast<SubnetID>(NULL);
+        if (dhcp6_subnet_id_null_ == MLM_FALSE)
+            ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
+
+        // Set IPv4 address reservation if it was given, if not, set IPv4 zero address
+        asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+        if (ipv4_address_null_ == MLM_FALSE)
+               //ipv4_reservation = static_cast<asiolink::IOAddress>(ipv4_address_);
+               ipv4_reservation = asiolink::IOAddress(ipv4_address_);
+
+        // Set hostname if it was given, if not lease empty string
+        std::string hostname;
+        if (hostname_null_ == MLM_FALSE)
+            hostname = std::string (hostname_, hostname_ + hostname_length_);
+
+        // Not sure if this is necessary yet, since Host constructor takes strings, not ClientClasses objects
+        // Set client classes if they were given, if not, set empty client classes
+        ClientClasses dhcp4_client_classes;
+        if (dhcp4_client_classes_null_ == MLM_FALSE)
+               dhcp4_client_classes.insert(dhcp4_client_classes_);
+
+        ClientClasses dhcp6_client_classes;
+        if (dhcp6_client_classes_null_ == MLM_FALSE)
+            dhcp6_client_classes.insert(dhcp6_client_classes_);
+
+        // Returning Host object with set fields
+        return (HostPtr(new Host(dhcp_identifier_buffer_,
+                                                               dhcp_identifier_length_,
+                                                               type,
+                                                               ipv4_subnet_id,
+                                                               ipv6_subnet_id,
+                                                               ipv4_reservation,
+                                                               hostname,
+                                                               dhcp4_client_classes_,
+                                                               dhcp6_client_classes_)));
+    }
+
+    /// @brief Return columns in error
+    ///
+    /// If an error is returned from a fetch (in particular, a truncated
+    /// status), this method can be called to get the names of the fields in
+    /// error.  It returns a string comprising the names of the fields
+    /// separated by commas.  In the case of there being no error indicators
+    /// set, it returns the string "(None)".
+    ///
+    /// @return Comma-separated list of columns in error, or the string
+    ///         "(None)".
+    std::string getErrorColumns() {
+        return (getColumnsInError(error_, columns_, HOST_COLUMNS));
+    }
+
+private:
+    //std::string              addr6_;                         //< String form of address
+    //char                     addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];  // array form of V6 address
+    uint32_t                           host_id_;                                       // Unique identifier of the host
+    std::vector<uint8_t>       dhcp_identifier_;                       // DHCP identifier, can be HW address (0) or DUID (1)
+    uint8_t                            dhcp_identifier_buffer_[DHCP_IDENTIFIER_MAX_LEN];       // Buffer form of dhcp identifier
+    size_t                                     dhcp_identifier_length_;        // Length of the dhcp identifier
+    uint8_t                            dhcp_identifier_type_;          // Type of the dhcp_identifier (HW address or DUID)
+    uint32_t                           dhcp4_subnet_id_;                       // Subnet identifier for the DHCPv4 client.
+    uint32_t                           dhcp6_subnet_id_;                       // Subnet identifier for the DHCPv6 client.
+    uint32_t                           ipv4_address_;                          // Reserved IPv4 address.
+    IPv6ResrvCollection        ipv6_reservations_;                     // Collection of IPv6 reservations for the host.
+    char                                       hostname_[HOSTNAME_MAX_LEN];// Name reserved for the host.
+    unsigned long              hostname_length_;                       //  hostname length
+    char                                       dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];  // Collection of classes associated with a DHCPv4 client.
+    unsigned long              dhcp4_client_classes_length_;// dhcp4_client_classes length
+    char                                       dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];  // Collection of classes associated with a DHCPv6 client.
+    unsigned long              dhcp6_client_classes_length_;// dhcp6_client_classes length
+    HWAddrPtr                          hw_address_;                            // Pointer to the hardware address associated with the reservations for the host.
+    DuidPtr                            duid_;                                          // Pointer to the DUID associated with the reservations for the host.
+
+    // NULL flags for subnets id, ipv4 address, hostname and client classes
+    my_bool            dhcp4_subnet_id_null_;
+    my_bool            dhcp6_subnet_id_null_;
+    my_bool                            ipv4_address_null_;
+    my_bool            hostname_null_;
+    my_bool            dhcp4_client_classes_null_;
+    my_bool            dhcp6_client_classes_null_;
+
+    MYSQL_BIND         bind_[HOST_COLUMNS];
+    std::string        columns_[HOST_COLUMNS];                 // Column names
+    my_bool            error_[HOST_COLUMNS];                   // Error array
+    HostPtr                            host_;                                                  // Pointer to Host object
+};
+
+// MySqlHostDataSource Constructor and Destructor
+
+MySqlHostDataSource::MySqlHostDataSource(
+               const MySqlConnection::ParameterMap& parameters) :
+                               MySqlConnection(parameters) {
+
+       // Open the database.
+       openDatabase();
+
+       // Enable autocommit.  To avoid a flush to disk on every commit, the global
+       // parameter innodb_flush_log_at_trx_commit should be set to 2.  This will
+       // cause the changes to be written to the log, but flushed to disk in the
+       // background every second.  Setting the parameter to that value will speed
+       // up the system, but at the risk of losing data if the system crashes.
+       my_bool result = mysql_autocommit(mysql_, 1);
+       if (result != 0) {
+               isc_throw(DbOperationError, mysql_error(mysql_));
+       }
+
+       // Prepare all statements likely to be used.
+       prepareStatements(tagged_statements, MySqlHostDataSource::NUM_STATEMENTS);
+
+       // Create the exchange objects for use in exchanging data between the
+       // program and the database.
+       hostExchange_.reset(new MySqlHostReservationExchange());
+}
+
+MySqlHostDataSource::~MySqlHostDataSource() {
+       // Free up the prepared statements, ignoring errors. (What would we do
+       // about them? We're destroying this object and are not really concerned
+       // with errors on a database connection that is about to go away.)
+       for (int i = 0; i < statements_.size(); ++i) {
+               if (statements_[i] != NULL) {
+                       (void) mysql_stmt_close(statements_[i]);
+                       statements_[i] = NULL;
+               }
+       }
+
+       // There is no need to close the database in this destructor: it is
+       // closed in the destructor of the mysql_ member variable.
+}
+
+// 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
+MySqlHostDataSource::add(const HostPtr& host) {
+       // Create the MYSQL_BIND array for the host
+       std::vector<MYSQL_BIND> bind = hostExchange_->createBindForSend(host);
+
+       // ... and drop to add code.
+       if (!addHost(INSERT_HOST, bind)) {
+               // Throw an failure exception here
+       }
+}
+
+bool
+MySqlHostDataSource::addHost(StatementIndex stindex,
+               std::vector<MYSQL_BIND>& bind) {
+
+       // Bind the parameters to the statement
+       int status = mysql_stmt_bind_param(statements_[stindex], &bind[0]);
+       checkError(status, stindex, "unable to bind parameters");
+
+       // Execute the statement
+       status = mysql_stmt_execute(statements_[stindex]);
+       if (status != 0) {
+
+               // Failure: check for the special case of duplicate entry.  If this is
+               // the case, we return false to indicate that the row was not added.
+               // Otherwise we throw an exception.
+               if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
+                       return (false);
+               }
+               checkError(status, stindex, "unable to execute");
+       }
+
+       // Insert succeeded
+       return (true);
+}
+
+void
+MySqlHostDataSource::getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
+                                       boost::shared_ptr<MySqlHostReservationExchange> exchange,
+                                       ConstHostCollection& result, bool single) const {
+
+    // Bind the selection parameters to the statement
+    int status = mysql_stmt_bind_param(statements_[stindex], bind);
+    checkError(status, stindex, "unable to bind WHERE clause parameter");
+
+    // Set up the MYSQL_BIND array for the data being returned and bind it to
+    // the statement.
+    std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
+    status = mysql_stmt_bind_result(statements_[stindex], &outbind[0]);
+    checkError(status, stindex, "unable to bind SELECT clause parameters");
+
+    // Execute the statement
+    status = mysql_stmt_execute(statements_[stindex]);
+    checkError(status, stindex, "unable to execute");
+
+    // Ensure that all the lease information is retrieved in one go to avoid
+    // overhead of going back and forth between client and server.
+    status = mysql_stmt_store_result(statements_[stindex]);
+    checkError(status, stindex, "unable to set up for storing all results");
+
+    // Set up the fetch "release" object to release resources associated
+    // with the call to mysql_stmt_fetch when this method exits, then
+    // retrieve the data.
+    MySqlFreeResult fetch_release(statements_[stindex]);
+    int count = 0;
+    while ((status = mysql_stmt_fetch(statements_[stindex])) == 0) {
+        try {
+            result.push_back(exchange->getHostData());
+
+        } catch (const isc::BadValue& ex) {
+            // Rethrow the exception with a bit more data.
+            isc_throw(BadValue, ex.what() << ". Statement is <" <<
+                      text_statements_[stindex] << ">");
+        }
+
+        if (single && (++count > 1)) {
+            isc_throw(MultipleRecords, "multiple records were found in the "
+                      "database where only one was expected for query "
+                      << text_statements_[stindex]);
+        }
+    }
+
+    // How did the fetch end?
+    if (status == 1) {
+        // Error - unable to fetch results
+        checkError(status, stindex, "unable to fetch results");
+    } else if (status == MYSQL_DATA_TRUNCATED) {
+        // Data truncated - throw an exception indicating what was at fault
+        isc_throw(DataTruncated, text_statements_[stindex]
+                  << " returned truncated data: columns affected are "
+                  << exchange->getErrorColumns());
+    }
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+
+       // Set up the WHERE clause value
+       MYSQL_BIND inbind[2];
+       memset(inbind, 0, sizeof(inbind));
+
+       // DUID
+       const vector<uint8_t>& duid_vector = duid->getDuid();
+       unsigned long duid_length = duid_vector.size();
+       inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+       inbind[0].buffer =
+                       reinterpret_cast<char*>(const_cast<uint8_t*>(&duid_vector[0]));
+       inbind[0].buffer_length = duid_length;
+       inbind[0].length = &duid_length;
+
+       // HW Address
+       const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+       unsigned long hwaddr_length = hwaddr_vector.size();
+       inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+       inbind[1].buffer =
+                       reinterpret_cast<char*>(const_cast<uint8_t*>(&hwaddr_vector[0]));
+       inbind[1].buffer_length = hwaddr_length;
+       inbind[1].length = &hwaddr_length;
+
+       ConstHostCollection result;
+       getHostCollection(GET_HOST_HWADDR_DUID, inbind, hostExchange_, result, false);
+
+       return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll4(
+               const asiolink::IOAddress& address) const {
+
+       // Set up the WHERE clause value
+       MYSQL_BIND inbind[1];
+       memset(inbind, 0, sizeof(inbind));
+
+       uint32_t addr4 = static_cast<uint32_t>(address);
+       inbind[0].buffer_type = MYSQL_TYPE_LONG;
+       inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+       inbind[0].is_unsigned = MLM_TRUE;
+
+       ConstHostCollection result;
+       getHostCollection(GET_HOST_ADDR, inbind, hostExchange_, result, false);
+
+       return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id,
+               const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+
+       // Set up the WHERE clause value
+       MYSQL_BIND inbind[2];
+       memset(inbind, 0, sizeof(inbind));
+
+       inbind[0].buffer_type = MYSQL_TYPE_LONG;
+       inbind[0].buffer = reinterpret_cast<char*>(subnet_id);
+       inbind[0].is_unsigned = MLM_TRUE;
+
+       // Choosing one of the identifiers
+       if (hwaddr) {
+               // HW Address
+               const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+               unsigned long hwaddr_length = hwaddr_vector.size();
+               inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+               inbind[1].buffer =
+                               reinterpret_cast<char*>(const_cast<uint8_t*>(&hwaddr_vector[0]));
+               inbind[1].buffer_length = hwaddr_length;
+               inbind[1].length = &hwaddr_length;
+       } else if (duid) {
+               // DUID
+               const vector<uint8_t>& duid_vector = duid->getDuid();
+               unsigned long duid_length = duid_vector.size();
+               inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+               inbind[1].buffer =
+                               reinterpret_cast<char*>(const_cast<uint8_t*>(&duid_vector[0]));
+               inbind[1].buffer_length = duid_length;
+               inbind[1].length = &duid_length;
+       }
+       // if none of the identifiers was given, this field should remain null
+
+       ConstHostCollection collection;
+       getHostCollection(GET_HOST_SUBID4_DHCPID, inbind, hostExchange_, collection,
+                       true);
+
+       // Return single record if present, else clear the host.
+       ConstHostPtr result;
+       if (collection.empty()) {
+               result.reset();
+       } else {
+               result = *collection.begin();
+       }
+
+       return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id,
+               const asiolink::IOAddress& address) const {
+
+       // Set up the WHERE clause value
+       MYSQL_BIND inbind[2];
+       memset(inbind, 0, sizeof(inbind));
+       inbind[0].buffer_type = MYSQL_TYPE_LONG;
+       inbind[0].buffer = reinterpret_cast<char*>(subnet_id);
+       inbind[0].is_unsigned = MLM_TRUE;
+
+       uint32_t addr4 = static_cast<uint32_t>(address);
+       inbind[1].buffer_type = MYSQL_TYPE_LONG;
+       inbind[1].buffer = reinterpret_cast<char*>(&addr4);
+       inbind[1].is_unsigned = MLM_TRUE;
+
+       ConstHostCollection collection;
+       getHostCollection(GET_HOST_SUBID_ADDR, inbind, hostExchange_, collection,
+                       true);
+
+       // Return single record if present, else clear the host.
+       ConstHostPtr result;
+       if (collection.empty()) {
+               result.reset();
+       } else {
+               result = *collection.begin();
+       }
+
+       return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const SubnetID& subnet_id,
+               const DuidPtr& duid, const HWAddrPtr& hwaddr) const {
+
+       // Set up the WHERE clause value
+       MYSQL_BIND inbind[2];
+       memset(inbind, 0, sizeof(inbind));
+
+       inbind[0].buffer_type = MYSQL_TYPE_LONG;
+       inbind[0].buffer = reinterpret_cast<char*>(subnet_id);
+       inbind[0].is_unsigned = MLM_TRUE;
+
+       // Choosing one of the identifiers
+       if (hwaddr) {
+               // HW Address
+               const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+               unsigned long hwaddr_length = hwaddr_vector.size();
+               inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+               inbind[1].buffer =
+                               reinterpret_cast<char*>(const_cast<uint8_t*>(&hwaddr_vector[0]));
+               inbind[1].buffer_length = hwaddr_length;
+               inbind[1].length = &hwaddr_length;
+       } else if (duid) {
+               // DUID
+               const vector<uint8_t>& duid_vector = duid->getDuid();
+               unsigned long duid_length = duid_vector.size();
+               inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+               inbind[1].buffer =
+                               reinterpret_cast<char*>(const_cast<uint8_t*>(&duid_vector[0]));
+               inbind[1].buffer_length = duid_length;
+               inbind[1].length = &duid_length;
+       }
+       // if none of the identifiers was given, this field should remain null
+
+       ConstHostCollection collection;
+       getHostCollection(GET_HOST_SUBID6_DHCPID, inbind, hostExchange_, collection,
+                       true);
+
+       // Return single record if present, else clear the host.
+       ConstHostPtr result;
+       if (collection.empty()) {
+               result.reset();
+       } else {
+               result = *collection.begin();
+       }
+
+       return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
+               const uint8_t prefix_len) const {
+
+       // Set up the WHERE clause value
+       MYSQL_BIND inbind[2];
+       memset(inbind, 0, sizeof(inbind));
+
+       uint32_t pref = static_cast<uint32_t>(prefix);
+       inbind[0].buffer_type = MYSQL_TYPE_LONG;
+       inbind[0].buffer = reinterpret_cast<char*>(&pref);
+       inbind[0].is_unsigned = MLM_TRUE;
+
+       inbind[1].buffer_type = MYSQL_TYPE_LONG;
+       inbind[1].buffer = reinterpret_cast<char*>(prefix_len);
+       inbind[1].is_unsigned = MLM_TRUE;
+
+       ConstHostCollection collection;
+       getHostCollection(GET_HOST_PREFIX, inbind, hostExchange_, collection, true);
+
+       // Return single record if present, else clear the host.
+       ConstHostPtr result;
+       if (collection.empty()) {
+               result.reset();
+       } else {
+               result = *collection.begin();
+       }
+
+       return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const SubnetID& subnet_id,
+               const asiolink::IOAddress& address) const {
+
+       // Set up the WHERE clause value
+       MYSQL_BIND inbind[2];
+       memset(inbind, 0, sizeof(inbind));
+
+       inbind[0].buffer_type = MYSQL_TYPE_LONG;
+       inbind[0].buffer = reinterpret_cast<char*>(subnet_id);
+       inbind[0].is_unsigned = MLM_TRUE;
+
+       uint32_t addr6 = static_cast<uint32_t>(address);
+       inbind[1].buffer_type = MYSQL_TYPE_LONG;
+       inbind[1].buffer = reinterpret_cast<char*>(&addr6);
+       inbind[1].is_unsigned = MLM_TRUE;
+
+       ConstHostCollection collection;
+       getHostCollection(GET_HOST_SUBID_ADDR, inbind, hostExchange_, collection,
+                       true);
+
+       // Return single record if present, else clear the host.
+       ConstHostPtr result;
+       if (collection.empty()) {
+               result.reset();
+       } else {
+               result = *collection.begin();
+       }
+
+       return (result);
+}
+
+// Miscellaneous database methods.
+
+std::string MySqlHostDataSource::getName() const {
+       std::string name = "";
+       try {
+               name = getParameter("name");
+       } catch (...) {
+               // Return an empty name
+       }
+       return (name);
+}
+
+std::string MySqlHostDataSource::getDescription() const {
+       return (std::string("MySQL Database"));
+}
+
+std::pair<uint32_t, uint32_t> MySqlHostDataSource::getVersion() const {
+       const StatementIndex stindex = GET_VERSION;
+
+       LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+                       DHCPSRV_MYSQL_GET_VERSION);
+
+       uint32_t major;      // Major version number
+       uint32_t minor;      // Minor version number
+
+       // Execute the prepared statement
+       int status = mysql_stmt_execute(statements_[stindex]);
+       if (status != 0) {
+               isc_throw(DbOperationError,
+                               "unable to execute <" << text_statements_[stindex] << "> - reason: " << mysql_error(mysql_));
+       }
+
+       // Bind the output of the statement to the appropriate variables.
+       MYSQL_BIND bind[2];
+       memset(bind, 0, sizeof(bind));
+
+       bind[0].buffer_type = MYSQL_TYPE_LONG;
+       bind[0].is_unsigned = 1;
+       bind[0].buffer = &major;
+       bind[0].buffer_length = sizeof(major);
+
+       bind[1].buffer_type = MYSQL_TYPE_LONG;
+       bind[1].is_unsigned = 1;
+       bind[1].buffer = &minor;
+       bind[1].buffer_length = sizeof(minor);
+
+       status = mysql_stmt_bind_result(statements_[stindex], bind);
+       if (status != 0) {
+               isc_throw(DbOperationError,
+                               "unable to bind result set: " << mysql_error(mysql_));
+       }
+
+       // Fetch the data and set up the "release" object to release associated
+       // resources when this method exits then retrieve the data.
+       MySqlFreeResult fetch_release(statements_[stindex]);
+       status = mysql_stmt_fetch(statements_[stindex]);
+       if (status != 0) {
+               isc_throw(DbOperationError,
+                               "unable to obtain result set: " << mysql_error(mysql_));
+       }
+
+       return (std::make_pair(major, minor));
+}
+
+void MySqlHostDataSource::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 MySqlHostDataSource::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_));
+       }
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h
new file mode 100644 (file)
index 0000000..f78d9bb
--- /dev/null
@@ -0,0 +1,325 @@
+// 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.h>
+
+
+namespace isc {
+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 Host exchange objects.  These classes are defined
+// in the .cc file.
+class MySqlHostReservationExchange;
+
+
+class MySqlHostDataSource : public BaseHostDataSource, public MySqlConnection {
+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 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 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 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 Returns a host from specific subnet and reserved address.
+       ///
+       /// @param subnet_id subnet identfier.
+       /// @param address specified address.
+       ///
+       /// @return Const @c host object that has a reservation for specified address.
+       virtual ConstHostPtr
+       get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) 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
+    /// implementation should throw an 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 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.
+       ///
+       /// @throw DbOperationError If the commit failed.
+       virtual 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.
+       virtual void rollback();
+
+    /// @brief Statement Tags
+    ///
+    /// The contents of the enum are indexes into the list of SQL statements
+       enum StatementIndex {
+               INSERT_HOST,                            // Insert new host to collection
+               GET_HOST_HWADDR_DUID,           // Get hosts identified by DUID and/or HW address
+               GET_HOST_ADDR,                          // Get hosts with specified IPv4 address
+               GET_HOST_SUBID4_DHCPID,         // Get host with specified IPv4 SubnetID and HW address and/or DUID
+               GET_HOST_SUBID6_DHCPID,         // Get host with specified IPv6 SubnetID and HW address and/or DUID
+               GET_HOST_SUBID_ADDR,            // Get host with specified IPv4 SubnetID and IPv4 address
+               GET_HOST_PREFIX,                        // Get host with specified 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
+       ///
+       /// @return true if the host was added, false if it was not.
+       bool 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 <" << text_statements_[index] << ">, reason: "
+                                       << mysql_error(mysql_) << " (error code " << mysql_errno(mysql_) << ")");
+               }
+       }
+
+
+    // 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".)
+    boost::shared_ptr<MySqlHostReservationExchange> hostExchange_; ///< Exchange object
+
+};
+
+}
+}
+
+#endif // MYSQL_HOST_DATA_SOURCE_H
index e1e8bb360f832b9bcdf130feed865ce61d7be2f0..543f7d3478410663f270759daada4212428032d6 100755 (executable)
@@ -1190,72 +1190,6 @@ MySqlLeaseMgr::getDBVersion() {
     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 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 > LeaseMgr::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
-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
 // (either V4 of V6), bind the contents to the appropriate prepared
 // statement, then call common code to execute the statement.
index 1e25ed4826c5534079458ab7df3f3b6165034fa2..56e2a721778503f861ecc26001aec3f1dad5f552 100755 (executable)
@@ -356,59 +356,6 @@ public:
     /// @throw DbOperationError If the rollback failed.
     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 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
     ///
     /// The contents of the enum are indexes into the list of SQL statements
index 511fcfc7547645f82b8c5fe309ec962124dafe83..0ed10ae556025086f30aaf13f9829d35667b68b4 100644 (file)
@@ -307,7 +307,7 @@ public:
     ///
     /// @return std::string containing the stringified time
     /// @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
     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
@@ -316,11 +316,11 @@ public:
             static_cast<int64_t>(valid_lifetime);
 
         // 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
         // stored in the database but it is invalid when read back (overflow?).
         // Hence, the maximum timestamp value is restricted here.
-        if (expire_time_64 > LeaseMgr::MAX_DB_TIME) {
+        if (expire_time_64 > DataSource::MAX_DB_TIME) {
             isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
         }
 
old mode 100644 (file)
new mode 100755 (executable)
index 59120f4..891f462
@@ -88,10 +88,12 @@ libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += logging_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_host_data_source_unittest.cc generic_host_data_source_unittest.h
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 if HAVE_MYSQL
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += mysql_host_data_source_unittest.cc
 endif
 if HAVE_PGSQL
 libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
new file mode 100644 (file)
index 0000000..a03e83e
--- /dev/null
@@ -0,0 +1,41 @@
+// 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/data_source.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(){}
+
+
+
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
new file mode 100644 (file)
index 0000000..8789f6b
--- /dev/null
@@ -0,0 +1,51 @@
+// 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 <gtest/gtest.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+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 Pointer to the host data source
+    BaseHostDataSource* hdsptr_;
+
+};
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
+
+#endif
index 956c359c1bb3d0e46a1c70116ac45c97513b03ca..060e18b14569e3ba285818fd39d4488dbf3fd3f8 100644 (file)
@@ -693,7 +693,7 @@ GenericLeaseMgrTest::testMaxDate4() {
     // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
     // time too large to store.
     lease->valid_lft_ = 24*60*60;
-    lease->cltt_ = LeaseMgr::MAX_DB_TIME;
+    lease->cltt_ = DataSource::MAX_DB_TIME;
 
     // Insert should throw.
     ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
@@ -862,7 +862,7 @@ GenericLeaseMgrTest::testMaxDate6() {
     // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
     // time too large to store.
     lease->valid_lft_ = 24*60*60;
-    lease->cltt_ = LeaseMgr::MAX_DB_TIME;
+    lease->cltt_ = DataSource::MAX_DB_TIME;
 
     // Insert should throw.
     ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
new file mode 100644 (file)
index 0000000..79680ac
--- /dev/null
@@ -0,0 +1,301 @@
+// 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 hdsptr_ will close
+    /// the database.  Then reopen it and delete everything created by the test.
+    virtual ~MySqlHostDataSourceTest() {
+        //hdsptr_->rollback();                         // need to decide where to implement rollback method (HostMgr or BaseHostDataSource maybe?)
+        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 MySqlLeaseMgr 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(MySqlOpenTest, 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_F(MySqlHostDataSourceTest, 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);
+}
+
+}; // Of anonymous namespace
index 0e1cf7282e03c5eb90f2461cbb759ed6727afd7e..781efe31024f041e2a6c7240bdcdb299fdd02b32 100755 (executable)
@@ -16,6 +16,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/mysql_connection.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
@@ -289,7 +290,7 @@ TEST_F(MySqlLeaseMgrTest, checkTimeConversion) {
     (void) localtime_r(&expire_time, &tm_expire);
 
     // Convert to the database time
-    MySqlLeaseMgr::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
+    MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
 
     // Are the times the same?
     EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
@@ -303,7 +304,7 @@ TEST_F(MySqlLeaseMgrTest, checkTimeConversion) {
 
     // Convert back
     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);
 }