Browse Source

[master] Merge branch 'trac3681_rebase' (Common MySQL Connection class)

Conflicts:
	src/lib/dhcpsrv/lease_mgr.h
	src/lib/dhcpsrv/memfile_lease_mgr.cc
	src/lib/dhcpsrv/mysql_lease_mgr.cc
	src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
	src/lib/dhcpsrv/tests/schema_mysql_copy.h
Tomek Mrugalski 9 years ago
parent
commit
884d8bb4a5
32 changed files with 1174 additions and 675 deletions
  1. 1 0
      AUTHORS
  2. 0 0
      configure.ac
  3. 0 0
      src/bin/admin/scripts/mysql/Makefile.am
  4. 0 0
      src/bin/admin/scripts/mysql/dhcpdb_create.mysql
  5. 5 1
      src/bin/admin/tests/mysql_tests.sh.in
  6. 2 0
      src/lib/dhcpsrv/Makefile.am
  7. 96 0
      src/lib/dhcpsrv/database_connection.cc
  8. 110 0
      src/lib/dhcpsrv/database_connection.h
  9. 0 0
      src/lib/dhcpsrv/lease.cc
  10. 1 8
      src/lib/dhcpsrv/lease_mgr.cc
  11. 1 39
      src/lib/dhcpsrv/lease_mgr.h
  12. 2 60
      src/lib/dhcpsrv/lease_mgr_factory.cc
  13. 2 20
      src/lib/dhcpsrv/lease_mgr_factory.h
  14. 7 6
      src/lib/dhcpsrv/memfile_lease_mgr.cc
  15. 9 3
      src/lib/dhcpsrv/memfile_lease_mgr.h
  16. 199 0
      src/lib/dhcpsrv/mysql_connection.cc
  17. 216 0
      src/lib/dhcpsrv/mysql_connection.h
  18. 43 261
      src/lib/dhcpsrv/mysql_lease_mgr.cc
  19. 10 91
      src/lib/dhcpsrv/mysql_lease_mgr.h
  20. 8 8
      src/lib/dhcpsrv/pgsql_lease_mgr.cc
  21. 8 2
      src/lib/dhcpsrv/pgsql_lease_mgr.h
  22. 1 0
      src/lib/dhcpsrv/tests/Makefile.am
  23. 169 0
      src/lib/dhcpsrv/tests/data_source_unittest.cc
  24. 169 0
      src/lib/dhcpsrv/tests/database_connection_unittest.cc
  25. 1 1
      src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
  26. 1 0
      src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
  27. 4 138
      src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
  28. 3 22
      src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
  29. 14 14
      src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
  30. 0 0
      src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
  31. 4 1
      src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
  32. 88 0
      src/lib/dhcpsrv/tests/schema_mysql_copy.h

+ 1 - 0
AUTHORS

@@ -80,6 +80,7 @@ We have received the following contributions:
    2014-12: Extract MAC address from DUID-LL and DUID-LLT types
    2015-01: Extract MAC address from remote-id
    2015-05: MySQL schema extended to cover host reservation
+   2015-04: Common MySQL Connector Pool 
 
 Kea uses log4cplus (http://sourceforge.net/projects/log4cplus/) for logging,
 Boost (http://www.boost.org/) library for almost everything, and can use Botan

+ 0 - 0
configure.ac


+ 0 - 0
src/bin/admin/scripts/mysql/Makefile.am


+ 0 - 0
src/bin/admin/scripts/mysql/dhcpdb_create.mysql


+ 5 - 1
src/bin/admin/tests/mysql_tests.sh.in

@@ -38,6 +38,11 @@ keaadmin=@abs_top_builddir@/src/bin/admin/kea-admin
 mysql_wipe() {
     printf "Wiping whole database %s\n" $db_name
 
+    # ipv6_reservations table must be deleted first, as it has contraints that
+    # are dependent on hosts. Therefore hosts table cannot be deleted before
+    # ipv6_reservations.
+    mysql_execute "DROP TABLE IF EXISTS ipv6_reservations;"
+
     # First we build the list of drop table commands
     # We don't bother with "cascade" because as of MySQL
     # 5.1 it is only there to ease porting, it doesn't
@@ -257,7 +262,6 @@ EOF
     ERRCODE=$?
     assert_eq 0 $ERRCODE "dhcp6_options table is missing or broken. (returned status code %d, expected %d)"
 
-
     # Verify that it reports version 3.0.
     version=$(${keaadmin} lease-version mysql -u $db_user -p $db_password -n $db_name)
 

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

@@ -93,6 +93,7 @@ libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
 libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
 libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h
+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
@@ -111,6 +112,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
 endif
 if HAVE_PGSQL
 libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h

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

@@ -0,0 +1,96 @@
+// 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/database_connection.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <vector>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+std::string
+DatabaseConnection::getParameter(const std::string& name) const {
+    ParameterMap::const_iterator param = parameters_.find(name);
+    if (param == parameters_.end()) {
+        isc_throw(BadValue, "Parameter " << name << " not found");
+    }
+    return (param->second);
+}
+
+DatabaseConnection::ParameterMap
+DatabaseConnection::parse(const std::string& dbaccess) {
+    DatabaseConnection::ParameterMap mapped_tokens;
+
+    if (!dbaccess.empty()) {
+        vector<string> tokens;
+
+        // We need to pass a string to is_any_of, not just char*. Otherwise
+        // there are cryptic warnings on Debian6 running g++ 4.4 in
+        // /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
+        // array bounds"
+        boost::split(tokens, dbaccess, boost::is_any_of(string("\t ")));
+        BOOST_FOREACH(std::string token, tokens) {
+            size_t pos = token.find("=");
+            if (pos != string::npos) {
+                string name = token.substr(0, pos);
+                string value = token.substr(pos + 1);
+                mapped_tokens.insert(make_pair(name, value));
+            } else {
+                LOG_ERROR(dhcpsrv_logger, DHCPSRV_INVALID_ACCESS).arg(dbaccess);
+                isc_throw(InvalidParameter, "Cannot parse " << token
+                          << ", expected format is name=value");
+            }
+        }
+    }
+
+    return (mapped_tokens);
+}
+
+std::string
+DatabaseConnection::redactedAccessString(const ParameterMap& parameters) {
+    // Reconstruct the access string: start of with an empty string, then
+    // work through all the parameters in the original string and add them.
+    std::string access;
+    for (DatabaseConnection::ParameterMap::const_iterator i = parameters.begin();
+         i != parameters.end(); ++i) {
+
+        // Separate second and subsequent tokens are preceded by a space.
+        if (!access.empty()) {
+            access += " ";
+        }
+
+        // Append name of parameter...
+        access += i->first;
+        access += "=";
+
+        // ... and the value, except in the case of the password, where a
+        // redacted value is appended.
+        if (i->first == std::string("password")) {
+            access += "*****";
+        } else {
+            access += i->second;
+        }
+    }
+
+    return (access);
+}
+
+};
+};

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

@@ -0,0 +1,110 @@
+// 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 DATABASE_CONNECTION_H
+#define DATABASE_CONNECTION_H
+
+#include <boost/noncopyable.hpp>
+#include <exceptions/exceptions.h>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown if name of database is not specified
+class NoDatabaseName : public Exception {
+public:
+    NoDatabaseName(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown on failure to open database
+class DbOpenError : public Exception {
+public:
+    DbOpenError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown on failure to execute a database function
+class DbOperationError : public Exception {
+public:
+    DbOperationError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+
+/// @brief Common database connection class.
+///
+/// This class provides functions that are common for establishing
+/// connection with different types of databases; enables operations
+/// on access parameters strings. In particular, it provides a way
+/// to parse parameters in key=value format. This class is expected
+/// to be a base class for all @ref LeaseMgr and possibly
+/// @ref BaseHostDataSource derived classes.
+class DatabaseConnection : public boost::noncopyable {
+public:
+    /// @brief Database configuration parameter map
+    typedef std::map<std::string, std::string> ParameterMap;
+
+    /// @brief Constructor
+    ///
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the database.
+    DatabaseConnection(const ParameterMap& parameters)
+        :parameters_(parameters) {
+    }
+
+    /// @brief Returns value of a connection parameter.
+    ///
+    /// @param name Name of the parameter which value should be returned.
+    /// @return Value of one of the connection parameters.
+    /// @throw BadValue if parameter is not found
+    std::string getParameter(const std::string& name) const;
+
+    /// @brief Parse database access string
+    ///
+    /// Parses the string of "keyword=value" pairs and separates them
+    /// out into the map.
+    ///
+    /// @param dbaccess Database access string.
+    ///
+    /// @return @ref ParameterMap of keyword/value pairs.
+    static ParameterMap parse(const std::string& dbaccess);
+
+    /// @brief Redact database access string
+    ///
+    /// Takes the database parameters and returns a database access string
+    /// passwords replaced by asterisks. This string is used in log messages.
+    ///
+    /// @param parameters Database access parameters (output of "parse").
+    ///
+    /// @return Redacted database access string.
+    static std::string redactedAccessString(const ParameterMap& parameters);
+
+private:
+
+    /// @brief List of parameters passed in dbconfig
+    ///
+    /// That will be mostly used for storing database name, username,
+    /// password and other parameters required for DB access. It is not
+    /// intended to keep any DHCP-related parameters.
+    ParameterMap parameters_;
+
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DATABASE_CONNECTION_H

+ 0 - 0
src/lib/dhcpsrv/lease.cc


+ 1 - 8
src/lib/dhcpsrv/lease_mgr.cc

@@ -29,6 +29,7 @@
 
 #include <time.h>
 
+
 using namespace std;
 
 namespace isc {
@@ -36,14 +37,6 @@ namespace dhcp {
 
 const time_t LeaseMgr::MAX_DB_TIME = 2147483647;
 
-std::string LeaseMgr::getParameter(const std::string& name) const {
-    ParameterMap::const_iterator param = parameters_.find(name);
-    if (param == parameters_.end()) {
-        isc_throw(BadValue, "Parameter not found");
-    }
-    return (param->second);
-}
-
 Lease6Ptr
 LeaseMgr::getLease6(Lease::Type type, const DUID& duid,
                     uint32_t iaid, SubnetID subnet_id) const {

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

@@ -68,27 +68,6 @@
 namespace isc {
 namespace dhcp {
 
-/// @brief Exception thrown if name of database is not specified
-class NoDatabaseName : public Exception {
-public:
-    NoDatabaseName(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-/// @brief Exception thrown on failure to open database
-class DbOpenError : public Exception {
-public:
-    DbOpenError(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-/// @brief Exception thrown on failure to execute a database function
-class DbOperationError : public Exception {
-public:
-    DbOperationError(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
 /// @brief Multiple lease records found where one expected
 class MultipleRecords : public Exception {
 public:
@@ -127,15 +106,9 @@ public:
     // If I'm still alive I'll be too old to care. You fix it.
     static const time_t MAX_DB_TIME;
 
-    /// Database configuration parameter map
-    typedef std::map<std::string, std::string> ParameterMap;
-
     /// @brief Constructor
     ///
-    /// @param parameters A data structure relating keywords and values
-    ///        concerned with the database.
-    LeaseMgr(const ParameterMap& parameters)
-        : parameters_(parameters)
+    LeaseMgr()
     {}
 
     /// @brief Destructor
@@ -425,17 +398,6 @@ public:
     /// @todo: Add host management here
     /// As host reservation is outside of scope for 2012, support for hosts
     /// is currently postponed.
-
-    /// @brief returns value of the parameter
-    virtual std::string getParameter(const std::string& name) const;
-
-private:
-    /// @brief list of parameters passed in dbconfig
-    ///
-    /// That will be mostly used for storing database name, username,
-    /// password and other parameters required for DB access. It is not
-    /// intended to keep any DHCP-related parameters.
-    ParameterMap parameters_;
 };
 
 }; // end of isc::dhcp namespace

+ 2 - 60
src/lib/dhcpsrv/lease_mgr_factory.cc

@@ -45,71 +45,13 @@ LeaseMgrFactory::getLeaseMgrPtr() {
     return (leaseMgrPtr);
 }
 
-LeaseMgr::ParameterMap
-LeaseMgrFactory::parse(const std::string& dbaccess) {
-    LeaseMgr::ParameterMap mapped_tokens;
-
-    if (!dbaccess.empty()) {
-        vector<string> tokens;
-
-        // We need to pass a string to is_any_of, not just char*. Otherwise
-        // there are cryptic warnings on Debian6 running g++ 4.4 in
-        // /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
-        // array bounds"
-        boost::split(tokens, dbaccess, boost::is_any_of(string("\t ")));
-        BOOST_FOREACH(std::string token, tokens) {
-            size_t pos = token.find("=");
-            if (pos != string::npos) {
-                string name = token.substr(0, pos);
-                string value = token.substr(pos + 1);
-                mapped_tokens.insert(make_pair(name, value));
-            } else {
-                LOG_ERROR(dhcpsrv_logger, DHCPSRV_INVALID_ACCESS).arg(dbaccess);
-                isc_throw(InvalidParameter, "Cannot parse " << token
-                          << ", expected format is name=value");
-            }
-        }
-    }
-
-    return (mapped_tokens);
-}
-
-std::string
-LeaseMgrFactory::redactedAccessString(const LeaseMgr::ParameterMap& parameters) {
-    // Reconstruct the access string: start of with an empty string, then
-    // work through all the parameters in the original string and add them.
-    std::string access;
-    for (LeaseMgr::ParameterMap::const_iterator i = parameters.begin();
-         i != parameters.end(); ++i) {
-
-        // Separate second and subsequent tokens are preceded by a space.
-        if (!access.empty()) {
-            access += " ";
-        }
-
-        // Append name of parameter...
-        access += i->first;
-        access += "=";
-
-        // ... and the value, except in the case of the password, where a
-        // redacted value is appended.
-        if (i->first == std::string("password")) {
-            access += "*****";
-        } else {
-            access += i->second;
-        }
-    }
-
-    return (access);
-}
-
 void
 LeaseMgrFactory::create(const std::string& dbaccess) {
     const std::string type = "type";
 
     // Parse the access string and create a redacted string for logging.
-    LeaseMgr::ParameterMap parameters = parse(dbaccess);
-    std::string redacted = redactedAccessString(parameters);
+    DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(dbaccess);
+    std::string redacted = DatabaseConnection::redactedAccessString(parameters);
 
     // Is "type" present?
     if (parameters.find(type) == parameters.end()) {

+ 2 - 20
src/lib/dhcpsrv/lease_mgr_factory.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012, 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013,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
@@ -16,6 +16,7 @@
 #define LEASE_MGR_FACTORY_H
 
 #include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/database_connection.h>
 #include <exceptions/exceptions.h>
 
 #include <boost/scoped_ptr.hpp>
@@ -100,26 +101,7 @@ public:
     ///        create() to create one before calling this method.
     static LeaseMgr& instance();
 
-    /// @brief Parse database access string
-    ///
-    /// Parses the string of "keyword=value" pairs and separates them
-    /// out into the map.
-    ///
-    /// @param dbaccess Database access string.
-    ///
-    /// @return std::map<std::string, std::string> Map of keyword/value pairs.
-    static LeaseMgr::ParameterMap parse(const std::string& dbaccess);
 
-    /// @brief Redact database access string
-    ///
-    /// Takes the database parameters and returns a database access string
-    /// passwords replaced by asterisks. This string is used in log messages.
-    ///
-    /// @param parameters Database access parameters (output of "parse").
-    ///
-    /// @return Redacted database access string.
-    static std::string redactedAccessString(
-            const LeaseMgr::ParameterMap& parameters);
 
 private:
     /// @brief Hold pointer to lease manager

+ 7 - 6
src/lib/dhcpsrv/memfile_lease_mgr.cc

@@ -18,6 +18,7 @@
 #include <dhcpsrv/lease_file_loader.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcpsrv/timer_mgr.h>
+#include <dhcpsrv/database_connection.h>
 #include <exceptions/exceptions.h>
 #include <util/pid_file.h>
 #include <util/process_spawn.h>
@@ -249,11 +250,11 @@ LFCSetup::getExitStatus() const {
 const int Memfile_LeaseMgr::MAJOR_VERSION;
 const int Memfile_LeaseMgr::MINOR_VERSION;
 
-Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
-    : LeaseMgr(parameters), lfc_setup_()
+Memfile_LeaseMgr::Memfile_LeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+    : LeaseMgr(), lfc_setup_(), conn_(parameters)
     {
     // Check the universe and use v4 file or v6 file.
-    std::string universe = getParameter("universe");
+    std::string universe = conn_.getParameter("universe");
     if (universe == "4") {
         std::string file4 = initLeaseFilePath(V4);
         if (!file4.empty()) {
@@ -840,7 +841,7 @@ std::string
 Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
     std::string persist_val;
     try {
-        persist_val = getParameter("persist");
+        persist_val = conn_.getParameter("persist");
     } catch (const Exception&) {
         // If parameter persist hasn't been specified, we use a default value
         // 'yes'.
@@ -858,7 +859,7 @@ Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
 
     std::string lease_file;
     try {
-        lease_file = getParameter("name");
+        lease_file = conn_.getParameter("name");
     } catch (const Exception&) {
         lease_file = getDefaultLeaseFilePath(u);
     }
@@ -944,7 +945,7 @@ void
 Memfile_LeaseMgr::lfcSetup() {
     std::string lfc_interval_str = "0";
     try {
-        lfc_interval_str = getParameter("lfc-interval");
+        lfc_interval_str = conn_.getParameter("lfc-interval");
     } catch (const std::exception&) {
         // Ignore and default to 0.
     }

+ 9 - 3
src/lib/dhcpsrv/memfile_lease_mgr.h

@@ -20,6 +20,7 @@
 #include <dhcpsrv/csv_lease_file4.h>
 #include <dhcpsrv/csv_lease_file6.h>
 #include <dhcpsrv/memfile_lease_storage.h>
+#include <dhcpsrv/database_connection.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <util/process_spawn.h>
 
@@ -99,7 +100,6 @@ public:
 
     /// @}
 
-
     /// @brief Specifies universe (V4, V6)
     ///
     /// This enumeration is used by various functions in Memfile %Lease Manager,
@@ -124,7 +124,7 @@ public:
     ///
     /// @param parameters A data structure relating keywords and values
     ///        concerned with the database.
-    Memfile_LeaseMgr(const ParameterMap& parameters);
+    Memfile_LeaseMgr(const DatabaseConnection::ParameterMap& parameters);
 
     /// @brief Destructor (closes file)
     virtual ~Memfile_LeaseMgr();
@@ -655,8 +655,14 @@ private:
 
     /// @brief A pointer to the Lease File Cleanup configuration.
     boost::scoped_ptr<LFCSetup> lfc_setup_;
-    //@}
 
+    /// @brief Parameters storage
+    ///
+    /// DatabaseConnection object is used only for storing, accessing and
+    /// printing parameter map.
+    DatabaseConnection conn_;
+
+    //@}
 };
 
 }; // end of isc::dhcp namespace

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

@@ -0,0 +1,199 @@
+// Copyright (C) 2012-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/dhcpsrv_log.h>
+#include <dhcpsrv/mysql_connection.h>
+#include <exceptions/exceptions.h>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <stdint.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Maximum size of database fields
+///
+/// The following constants define buffer sizes for variable length database
+/// fields.  The values should be greater than or equal to the length set in
+/// the schema definition.
+///
+/// The exception is the length of any VARCHAR fields: buffers for these should
+/// be set greater than or equal to the length of the field plus 1: this allows
+/// for the insertion of a trailing null whatever data is returned.
+
+const my_bool MLM_FALSE = 0;                ///< False value
+const my_bool MLM_TRUE = 1;                 ///< True value
+
+///@}
+
+
+// Open the database using the parameters passed to the constructor.
+
+void
+MySqlConnection::openDatabase() {
+
+    // Set up the values of the parameters
+    const char* host = "localhost";
+    string shost;
+    try {
+        shost = getParameter("host");
+        host = shost.c_str();
+    } catch (...) {
+        // No host.  Fine, we'll use "localhost"
+    }
+
+    const char* user = NULL;
+    string suser;
+    try {
+        suser = getParameter("user");
+        user = suser.c_str();
+    } catch (...) {
+        // No user.  Fine, we'll use NULL
+    }
+
+    const char* password = NULL;
+    string spassword;
+    try {
+        spassword = getParameter("password");
+        password = spassword.c_str();
+    } catch (...) {
+        // No password.  Fine, we'll use NULL
+    }
+
+    const char* name = NULL;
+    string sname;
+    try {
+        sname = getParameter("name");
+        name = sname.c_str();
+    } catch (...) {
+        // No database name.  Throw a "NoName" exception
+        isc_throw(NoDatabaseName, "must specified a name for the database");
+    }
+
+    // Set options for the connection:
+    //
+    // Automatic reconnection: after a period of inactivity, the client will
+    // disconnect from the database.  This option causes it to automatically
+    // reconnect when another operation is about to be done.
+    my_bool auto_reconnect = MLM_TRUE;
+    int result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
+    if (result != 0) {
+        isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<
+                  mysql_error(mysql_));
+    }
+
+    // Set SQL mode options for the connection:  SQL mode governs how what
+    // constitutes insertable data for a given column, and how to handle
+    // invalid data.  We want to ensure we get the strictest behavior and
+    // to reject invalid data with an error.
+    const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
+    result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
+    if (result != 0) {
+        isc_throw(DbOpenError, "unable to set SQL mode options: " <<
+                  mysql_error(mysql_));
+    }
+
+    // Open the database.
+    //
+    // The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
+    // the affected rows are the number of rows found that match the
+    // WHERE clause of the SQL statement, not the rows changed.  The reason
+    // here is that MySQL apparently does not update a row if data has not
+    // changed and so the "affected rows" (retrievable from MySQL) is zero.
+    // This makes it hard to distinguish whether the UPDATE changed no rows
+    // because no row matching the WHERE clause was found, or because a
+    // row was found but no data was altered.
+    MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
+                                       0, NULL, CLIENT_FOUND_ROWS);
+    if (status != mysql_) {
+        isc_throw(DbOpenError, mysql_error(mysql_));
+    }
+}
+
+
+// Prepared statement setup.  The textual form of an SQL statement is stored
+// in a vector of strings (text_statements_) and is used in the output of
+// error messages.  The SQL statement is also compiled into a "prepared
+// statement" (stored in statements_), which avoids the overhead of compilation
+// during use.  As prepared statements have resources allocated to them, the
+// class destructor explicitly destroys them.
+
+void
+MySqlConnection::prepareStatement(uint32_t index, const char* text) {
+    // Validate that there is space for the statement in the statements array
+    // and that nothing has been placed there before.
+    if ((index >= statements_.size()) || (statements_[index] != NULL)) {
+        isc_throw(InvalidParameter, "invalid prepared statement index (" <<
+                  static_cast<int>(index) << ") or indexed prepared " <<
+                  "statement is not null");
+    }
+
+    // All OK, so prepare the statement
+    text_statements_[index] = std::string(text);
+    statements_[index] = mysql_stmt_init(mysql_);
+    if (statements_[index] == NULL) {
+        isc_throw(DbOperationError, "unable to allocate MySQL prepared "
+                  "statement structure, reason: " << mysql_error(mysql_));
+    }
+
+    int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
+    if (status != 0) {
+        isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<
+                  text << ">, reason: " << mysql_error(mysql_));
+    }
+}
+
+void
+MySqlConnection::prepareStatements(const TaggedStatement tagged_statements[],
+                                   size_t num_statements) {
+    // Allocate space for all statements
+    statements_.clear();
+    statements_.resize(num_statements, NULL);
+
+    text_statements_.clear();
+    text_statements_.resize(num_statements, std::string(""));
+
+    // Created the MySQL prepared statements for each DML statement.
+    for (int i = 0; tagged_statements[i].text != NULL; ++i) {
+        prepareStatement(tagged_statements[i].index,
+                         tagged_statements[i].text);
+    }
+}
+
+/// @brief Destructor
+MySqlConnection::~MySqlConnection() {
+    // 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;
+        }
+    }
+    statements_.clear();
+    text_statements_.clear();
+
+}
+
+} // namespace isc::dhcp
+} // namespace isc

+ 216 - 0
src/lib/dhcpsrv/mysql_connection.h

@@ -0,0 +1,216 @@
+// Copyright (C) 2012-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_CONNECTION_H
+#define MYSQL_CONNECTION_H
+
+#include <dhcpsrv/database_connection.h>
+#include <boost/scoped_ptr.hpp>
+#include <mysql.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief MySQL True/False constants
+///
+/// Declare typed values so as to avoid problems of data conversion.  These
+/// are local to the file but are given the prefix MLM (MySql Lease Manager) to
+/// avoid any likely conflicts with variables in header files named TRUE or
+/// FALSE.
+extern const my_bool MLM_FALSE;
+extern const my_bool MLM_TRUE;
+
+/// @brief Fetch and Release MySQL Results
+///
+/// When a MySQL statement is expected, to fetch the results the function
+/// mysql_stmt_fetch() must be called.  As well as getting data, this
+/// allocates internal state.  Subsequent calls to mysql_stmt_fetch can be
+/// made, but when all the data is retrieved, mysql_stmt_free_result must be
+/// called to free up the resources allocated.
+///
+/// Created prior to the first fetch, this class's destructor calls
+/// mysql_stmt_free_result, so eliminating the need for an explicit release
+/// in the method calling mysql_stmt_free_result.  In this way, it guarantees
+/// that the resources are released even if the MySqlLeaseMgr method concerned
+/// exits via an exception.
+
+class MySqlFreeResult {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Store the pointer to the statement for which data is being fetched.
+    ///
+    /// Note that according to the MySQL documentation, mysql_stmt_free_result
+    /// only releases resources if a cursor has been allocated for the
+    /// statement.  This implies that it is a no-op if none have been.  Either
+    /// way, any error from mysql_stmt_free_result is ignored. (Generating
+    /// an exception is not much help, as it will only confuse things if the
+    /// method calling mysql_stmt_fetch is exiting via an exception.)
+    MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
+    {}
+
+    /// @brief Destructor
+    ///
+    /// Frees up fetch context if a fetch has been successfully executed.
+    ~MySqlFreeResult() {
+        (void) mysql_stmt_free_result(statement_);
+    }
+
+private:
+    MYSQL_STMT*     statement_;     ///< Statement for which results are freed
+};
+
+/// @brief MySQL Selection Statements
+///
+/// Each statement is associated with an index, which is used to reference the
+/// associated prepared statement.
+
+struct TaggedStatement {
+    uint32_t index;
+    const char* text;
+};
+
+/// @brief MySQL Handle Holder
+///
+/// Small RAII object for safer initialization, will close the database
+/// connection upon destruction.  This means that if an exception is thrown
+/// during database initialization, resources allocated to the database are
+/// guaranteed to be freed.
+///
+/// It makes no sense to copy an object of this class.  After the copy, both
+/// objects would contain pointers to the same MySql context object.  The
+/// destruction of one would invalid the context in the remaining object.
+/// For this reason, the class is declared noncopyable.
+class MySqlHolder : public boost::noncopyable {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Initialize MySql and store the associated context object.
+    ///
+    /// @throw DbOpenError Unable to initialize MySql handle.
+    MySqlHolder() : mysql_(mysql_init(NULL)) {
+        if (mysql_ == NULL) {
+            isc_throw(DbOpenError, "unable to initialize MySQL");
+        }
+    }
+
+    /// @brief Destructor
+    ///
+    /// Frees up resources allocated by the initialization of MySql.
+    ~MySqlHolder() {
+        if (mysql_ != NULL) {
+            mysql_close(mysql_);
+        }
+        // The library itself shouldn't be needed anymore
+        mysql_library_end();
+    }
+
+    /// @brief Conversion Operator
+    ///
+    /// Allows the MySqlHolder object to be passed as the context argument to
+    /// mysql_xxx functions.
+    operator MYSQL*() const {
+        return (mysql_);
+    }
+
+private:
+    MYSQL* mysql_;      ///< Initialization context
+};
+
+/// @brief Common MySQL Connector Pool
+///
+/// This class provides common operations for MySQL database connection
+/// used by both MySqlLeaseMgr and MySqlHostDataSource. It manages connecting
+/// to the database and preparing compiled statements. Its fields are
+/// public, because they are used (both set and retrieved) in classes
+/// that use instances of MySqlConnection.
+class MySqlConnection : public DatabaseConnection {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Initialize MySqlConnection object with parameters needed for connection.
+    MySqlConnection(const ParameterMap& parameters)
+        : DatabaseConnection(parameters) {
+    }
+
+    /// @brief Destructor
+    virtual ~MySqlConnection();
+
+    /// @brief Prepare Single Statement
+    ///
+    /// Creates a prepared statement from the text given and adds it to the
+    /// statements_ vector at the given index.
+    ///
+    /// @param index Index into the statements_ vector into which the text
+    ///        should be placed.  The vector must be big enough for the index
+    ///        to be valid, else an exception will be thrown.
+    /// @param text Text of the SQL statement to be prepared.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::InvalidParameter 'index' is not valid for the vector.
+    void prepareStatement(uint32_t index, const char* text);
+
+    /// @brief Prepare statements
+    ///
+    /// Creates the prepared statements for all of the SQL statements used
+    /// by the MySQL backend.
+    /// @param tagged_statements an array of statements to be compiled
+    /// @param num_statements number of statements in tagged_statements
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::InvalidParameter 'index' is not valid for the vector.  This
+    ///        represents an internal error within the code.
+    void prepareStatements(const TaggedStatement tagged_statements[],
+                           size_t num_statements);
+
+    /// @brief Open Database
+    ///
+    /// Opens the database using the information supplied in the parameters
+    /// passed to the constructor.
+    ///
+    /// @throw NoDatabaseName Mandatory database name not given
+    /// @throw DbOpenError Error opening the database
+    void openDatabase();
+
+    /// @brief Prepared statements
+    ///
+    /// This field is public, because it is used heavily from MySqlConnection
+    /// and will be from MySqlHostDataSource.
+    std::vector<MYSQL_STMT*> statements_;
+
+    /// @brief Raw text of statements
+    ///
+    /// This field is public, because it is used heavily from MySqlConnection
+    /// and will be from MySqlHostDataSource.
+    std::vector<std::string> text_statements_;
+
+    /// @brief MySQL connection handle
+    ///
+    /// This field is public, because it is used heavily from MySqlConnection
+    /// and will be from MySqlHostDataSource.
+    MySqlHolder mysql_;
+};
+
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // MYSQL_CONNECTION_H

+ 43 - 261
src/lib/dhcpsrv/mysql_lease_mgr.cc

@@ -19,6 +19,7 @@
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
+#include <dhcpsrv/mysql_connection.h>
 
 #include <boost/static_assert.hpp>
 #include <mysqld_error.h>
@@ -77,51 +78,17 @@ using namespace std;
 ///   lease object.
 
 namespace {
-///@{
-
-/// @brief Maximum size of database fields
-///
-/// The following constants define buffer sizes for variable length database
-/// fields.  The values should be greater than or equal to the length set in
-/// the schema definition.
-///
-/// The exception is the length of any VARCHAR fields: buffers for these should
-/// be set greater than or equal to the length of the field plus 1: this allows
-/// for the insertion of a trailing null whatever data is returned.
-
-/// @brief Maximum size of an IPv6 address represented as a text string.
-///
-/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
-/// colon separators.
-const size_t ADDRESS6_TEXT_MAX_LEN = 39;
-
-/// @brief MySQL True/False constants
-///
-/// Declare typed values so as to avoid problems of data conversion.  These
-/// are local to the file but are given the prefix MLM (MySql Lease Manager) to
-/// avoid any likely conflicts with variables in header files named TRUE or
-/// FALSE.
-
-const my_bool MLM_FALSE = 0;                ///< False value
-const my_bool MLM_TRUE = 1;                 ///< True value
-
 /// @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 MySQL Selection Statements
+/// @brief Maximum size of an IPv6 address represented as a text string.
 ///
-/// Each statement is associated with an index, which is used to reference the
-/// associated prepared statement.
-
-struct TaggedStatement {
-    MySqlLeaseMgr::StatementIndex index;
-    const char*                   text;
-};
+/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
+/// colon separators.
+const size_t ADDRESS6_TEXT_MAX_LEN = 39;
 
 TaggedStatement tagged_statements[] = {
     {MySqlLeaseMgr::DELETE_LEASE4,
@@ -250,9 +217,7 @@ TaggedStatement tagged_statements[] = {
     {MySqlLeaseMgr::NUM_STATEMENTS, NULL}
 };
 
-};  // Anonymous namespace
-
-
+};
 
 namespace isc {
 namespace dhcp {
@@ -1258,67 +1223,28 @@ private:
 };
 
 
-/// @brief Fetch and Release MySQL Results
-///
-/// When a MySQL statement is expected, to fetch the results the function
-/// mysql_stmt_fetch() must be called.  As well as getting data, this
-/// allocates internal state.  Subsequent calls to mysql_stmt_fetch can be
-/// made, but when all the data is retrieved, mysql_stmt_free_result must be
-/// called to free up the resources allocated.
-///
-/// Created prior to the first fetch, this class's destructor calls
-/// mysql_stmt_free_result, so eliminating the need for an explicit release
-/// in the method calling mysql_stmt_free_result.  In this way, it guarantees
-/// that the resources are released even if the MySqlLeaseMgr method concerned
-/// exits via an exception.
 
-class MySqlFreeResult {
-public:
-
-    /// @brief Constructor
-    ///
-    /// Store the pointer to the statement for which data is being fetched.
-    ///
-    /// Note that according to the MySQL documentation, mysql_stmt_free_result
-    /// only releases resources if a cursor has been allocated for the
-    /// statement.  This implies that it is a no-op if none have been.  Either
-    /// way, any error from mysql_stmt_free_result is ignored. (Generating
-    /// an exception is not much help, as it will only confuse things if the
-    /// method calling mysql_stmt_fetch is exiting via an exception.)
-    MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
-    {}
-
-    /// @brief Destructor
-    ///
-    /// Frees up fetch context if a fetch has been successfully executed.
-    ~MySqlFreeResult() {
-        (void) mysql_stmt_free_result(statement_);
-    }
-
-private:
-    MYSQL_STMT*     statement_;     ///< Statement for which results are freed
-};
 
 // MySqlLeaseMgr Constructor and Destructor
 
-MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
-    : LeaseMgr(parameters) {
+MySqlLeaseMgr::MySqlLeaseMgr(const MySqlConnection::ParameterMap& parameters)
+    : conn_(parameters) {
 
     // Open the database.
-    openDatabase();
+    conn_.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);
+    my_bool result = mysql_autocommit(conn_.mysql_, 1);
     if (result != 0) {
-        isc_throw(DbOperationError, mysql_error(mysql_));
+        isc_throw(DbOperationError, mysql_error(conn_.mysql_));
     }
 
     // Prepare all statements likely to be used.
-    prepareStatements();
+    conn_.prepareStatements(tagged_statements, MySqlLeaseMgr::NUM_STATEMENTS);
 
     // Create the exchange objects for use in exchanging data between the
     // program and the database.
@@ -1328,16 +1254,6 @@ MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
 
 
 MySqlLeaseMgr::~MySqlLeaseMgr() {
-    // 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.
 }
@@ -1422,139 +1338,6 @@ MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
 }
 
 
-
-// Open the database using the parameters passed to the constructor.
-
-void
-MySqlLeaseMgr::openDatabase() {
-
-    // Set up the values of the parameters
-    const char* host = "localhost";
-    string shost;
-    try {
-        shost = getParameter("host");
-        host = shost.c_str();
-    } catch (...) {
-        // No host.  Fine, we'll use "localhost"
-    }
-
-    const char* user = NULL;
-    string suser;
-    try {
-        suser = getParameter("user");
-        user = suser.c_str();
-    } catch (...) {
-        // No user.  Fine, we'll use NULL
-    }
-
-    const char* password = NULL;
-    string spassword;
-    try {
-        spassword = getParameter("password");
-        password = spassword.c_str();
-    } catch (...) {
-        // No password.  Fine, we'll use NULL
-    }
-
-    const char* name = NULL;
-    string sname;
-    try {
-        sname = getParameter("name");
-        name = sname.c_str();
-    } catch (...) {
-        // No database name.  Throw a "NoName" exception
-        isc_throw(NoDatabaseName, "must specified a name for the database");
-    }
-
-    // Set options for the connection:
-    //
-    // Automatic reconnection: after a period of inactivity, the client will
-    // disconnect from the database.  This option causes it to automatically
-    // reconnect when another operation is about to be done.
-    my_bool auto_reconnect = MLM_TRUE;
-    int result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
-    if (result != 0) {
-        isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<
-                  mysql_error(mysql_));
-    }
-
-    // Set SQL mode options for the connection:  SQL mode governs how what
-    // constitutes insertable data for a given column, and how to handle
-    // invalid data.  We want to ensure we get the strictest behavior and
-    // to reject invalid data with an error.
-    const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
-    result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
-    if (result != 0) {
-        isc_throw(DbOpenError, "unable to set SQL mode options: " <<
-                  mysql_error(mysql_));
-    }
-
-    // Open the database.
-    //
-    // The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
-    // the affected rows are the number of rows found that match the
-    // WHERE clause of the SQL statement, not the rows changed.  The reason
-    // here is that MySQL apparently does not update a row if data has not
-    // changed and so the "affected rows" (retrievable from MySQL) is zero.
-    // This makes it hard to distinguish whether the UPDATE changed no rows
-    // because no row matching the WHERE clause was found, or because a
-    // row was found but no data was altered.
-    MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
-                                       0, NULL, CLIENT_FOUND_ROWS);
-    if (status != mysql_) {
-        isc_throw(DbOpenError, mysql_error(mysql_));
-    }
-}
-
-// Prepared statement setup.  The textual form of an SQL statement is stored
-// in a vector of strings (text_statements_) and is used in the output of
-// error messages.  The SQL statement is also compiled into a "prepared
-// statement" (stored in statements_), which avoids the overhead of compilation
-// during use.  As prepared statements have resources allocated to them, the
-// class destructor explicitly destroys them.
-
-void
-MySqlLeaseMgr::prepareStatement(StatementIndex index, const char* text) {
-    // Validate that there is space for the statement in the statements array
-    // and that nothing has been placed there before.
-    if ((index >= statements_.size()) || (statements_[index] != NULL)) {
-        isc_throw(InvalidParameter, "invalid prepared statement index (" <<
-                  static_cast<int>(index) << ") or indexed prepared " <<
-                  "statement is not null");
-    }
-
-    // All OK, so prepare the statement
-    text_statements_[index] = std::string(text);
-    statements_[index] = mysql_stmt_init(mysql_);
-    if (statements_[index] == NULL) {
-        isc_throw(DbOperationError, "unable to allocate MySQL prepared "
-                  "statement structure, reason: " << mysql_error(mysql_));
-    }
-
-    int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
-    if (status != 0) {
-        isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<
-                  text << ">, reason: " << mysql_error(mysql_));
-    }
-}
-
-
-void
-MySqlLeaseMgr::prepareStatements() {
-    // Allocate space for all statements
-    statements_.clear();
-    statements_.resize(NUM_STATEMENTS, NULL);
-
-    text_statements_.clear();
-    text_statements_.resize(NUM_STATEMENTS, std::string(""));
-
-    // Created the MySQL prepared statements for each DML statement.
-    for (int i = 0; tagged_statements[i].text != NULL; ++i) {
-        prepareStatement(tagged_statements[i].index,
-                         tagged_statements[i].text);
-    }
-}
-
 // 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.
@@ -1564,17 +1347,17 @@ MySqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
                               std::vector<MYSQL_BIND>& bind) {
 
     // Bind the parameters to the statement
-    int status = mysql_stmt_bind_param(statements_[stindex], &bind[0]);
+    int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
     checkError(status, stindex, "unable to bind parameters");
 
     // Execute the statement
-    status = mysql_stmt_execute(statements_[stindex]);
+    status = mysql_stmt_execute(conn_.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) {
+        if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
             return (false);
         }
         checkError(status, stindex, "unable to execute");
@@ -1642,43 +1425,43 @@ void MySqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
                                        bool single) const {
 
     // Bind the selection parameters to the statement
-    int status = mysql_stmt_bind_param(statements_[stindex], bind);
+    int status = mysql_stmt_bind_param(conn_.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]);
+    status = mysql_stmt_bind_result(conn_.statements_[stindex], &outbind[0]);
     checkError(status, stindex, "unable to bind SELECT clause parameters");
 
     // Execute the statement
-    status = mysql_stmt_execute(statements_[stindex]);
+    status = mysql_stmt_execute(conn_.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]);
+    status = mysql_stmt_store_result(conn_.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]);
+    MySqlFreeResult fetch_release(conn_.statements_[stindex]);
     int count = 0;
-    while ((status = mysql_stmt_fetch(statements_[stindex])) == 0) {
+    while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) == 0) {
         try {
             result.push_back(exchange->getLeaseData());
 
         } catch (const isc::BadValue& ex) {
             // Rethrow the exception with a bit more data.
             isc_throw(BadValue, ex.what() << ". Statement is <" <<
-                      text_statements_[stindex] << ">");
+                      conn_.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]);
+                      << conn_.text_statements_[stindex]);
         }
     }
 
@@ -1688,7 +1471,7 @@ void MySqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
         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]
+        isc_throw(DataTruncated, conn_.text_statements_[stindex]
                   << " returned truncated data: columns affected are "
                   << exchange->getErrorColumns());
     }
@@ -2073,16 +1856,16 @@ MySqlLeaseMgr::updateLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind,
                                  const LeasePtr& lease) {
 
     // Bind the parameters to the statement
-    int status = mysql_stmt_bind_param(statements_[stindex], bind);
+    int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
     checkError(status, stindex, "unable to bind parameters");
 
     // Execute
-    status = mysql_stmt_execute(statements_[stindex]);
+    status = mysql_stmt_execute(conn_.statements_[stindex]);
     checkError(status, stindex, "unable to execute");
 
     // See how many rows were affected.  The statement should only update a
     // single row.
-    int affected_rows = mysql_stmt_affected_rows(statements_[stindex]);
+    int affected_rows = mysql_stmt_affected_rows(conn_.statements_[stindex]);
     if (affected_rows == 0) {
         isc_throw(NoSuchLease, "unable to update lease for address " <<
                   lease->addr_ << " as it does not exist");
@@ -2159,19 +1942,18 @@ uint64_t
 MySqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind) {
 
     // Bind the input parameters to the statement
-    int status = mysql_stmt_bind_param(statements_[stindex], bind);
+    int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
     checkError(status, stindex, "unable to bind WHERE clause parameter");
 
     // Execute
-    status = mysql_stmt_execute(statements_[stindex]);
+    status = mysql_stmt_execute(conn_.statements_[stindex]);
     checkError(status, stindex, "unable to execute");
 
     // See how many rows were affected.  Note that the statement may delete
     // multiple rows.
-    return (static_cast<uint64_t>(mysql_stmt_affected_rows(statements_[stindex])));
+    return (static_cast<uint64_t>(mysql_stmt_affected_rows(conn_.statements_[stindex])));
 }
 
-
 bool
 MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
@@ -2258,7 +2040,7 @@ std::string
 MySqlLeaseMgr::getName() const {
     std::string name = "";
     try {
-        name = getParameter("name");
+        name = conn_.getParameter("name");
     } catch (...) {
         // Return an empty name
     }
@@ -2283,11 +2065,11 @@ MySqlLeaseMgr::getVersion() const {
     uint32_t    minor;      // Minor version number
 
     // Execute the prepared statement
-    int status = mysql_stmt_execute(statements_[stindex]);
+    int status = mysql_stmt_execute(conn_.statements_[stindex]);
     if (status != 0) {
         isc_throw(DbOperationError, "unable to execute <"
-                  << text_statements_[stindex] << "> - reason: " <<
-                  mysql_error(mysql_));
+                  << conn_.text_statements_[stindex] << "> - reason: " <<
+                  mysql_error(conn_.mysql_));
     }
 
     // Bind the output of the statement to the appropriate variables.
@@ -2304,19 +2086,19 @@ MySqlLeaseMgr::getVersion() const {
     bind[1].buffer = &minor;
     bind[1].buffer_length = sizeof(minor);
 
-    status = mysql_stmt_bind_result(statements_[stindex], bind);
+    status = mysql_stmt_bind_result(conn_.statements_[stindex], bind);
     if (status != 0) {
         isc_throw(DbOperationError, "unable to bind result set: " <<
-                  mysql_error(mysql_));
+                  mysql_error(conn_.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]);
+    MySqlFreeResult fetch_release(conn_.statements_[stindex]);
+    status = mysql_stmt_fetch(conn_.statements_[stindex]);
     if (status != 0) {
         isc_throw(DbOperationError, "unable to obtain result set: " <<
-                  mysql_error(mysql_));
+                  mysql_error(conn_.mysql_));
     }
 
     return (std::make_pair(major, minor));
@@ -2326,8 +2108,8 @@ MySqlLeaseMgr::getVersion() const {
 void
 MySqlLeaseMgr::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_));
+    if (mysql_commit(conn_.mysql_) != 0) {
+        isc_throw(DbOperationError, "commit failed: " << mysql_error(conn_.mysql_));
     }
 }
 
@@ -2335,8 +2117,8 @@ MySqlLeaseMgr::commit() {
 void
 MySqlLeaseMgr::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_));
+    if (mysql_rollback(conn_.mysql_) != 0) {
+        isc_throw(DbOperationError, "rollback failed: " << mysql_error(conn_.mysql_));
     }
 }
 

+ 10 - 91
src/lib/dhcpsrv/mysql_lease_mgr.h

@@ -17,6 +17,7 @@
 
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/mysql_connection.h>
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/utility.hpp>
@@ -27,57 +28,9 @@
 namespace isc {
 namespace dhcp {
 
-/// @brief MySQL Handle Holder
-///
-/// Small RAII object for safer initialization, will close the database
-/// connection upon destruction.  This means that if an exception is thrown
-/// during database initialization, resources allocated to the database are
-/// guaranteed to be freed.
-///
-/// It makes no sense to copy an object of this class.  After the copy, both
-/// objects would contain pointers to the same MySql context object.  The
-/// destruction of one would invalidate the context in the remaining object.
-/// For this reason, the class is declared noncopyable.
-class MySqlHolder : public boost::noncopyable {
-public:
-
-    /// @brief Constructor
-    ///
-    /// Initialize MySql and store the associated context object.
-    ///
-    /// @throw DbOpenError Unable to initialize MySql handle.
-    MySqlHolder() : mysql_(mysql_init(NULL)) {
-        if (mysql_ == NULL) {
-            isc_throw(DbOpenError, "unable to initialize MySQL");
-        }
-    }
-
-    /// @brief Destructor
-    ///
-    /// Frees up resources allocated by the initialization of MySql.
-    ~MySqlHolder() {
-        if (mysql_ != NULL) {
-            mysql_close(mysql_);
-        }
-        // The library itself shouldn't be needed anymore
-        mysql_library_end();
-    }
-
-    /// @brief Conversion Operator
-    ///
-    /// Allows the MySqlHolder object to be passed as the context argument to
-    /// mysql_xxx functions.
-    operator MYSQL*() const {
-        return (mysql_);
-    }
-
-private:
-    MYSQL* mysql_;      ///< Initialization context
-};
-
 // Define the current database schema values
 
-const uint32_t CURRENT_VERSION_VERSION = 2;
+const uint32_t CURRENT_VERSION_VERSION = 3;
 const uint32_t CURRENT_VERSION_MINOR = 0;
 
 
@@ -95,6 +48,7 @@ class MySqlLease6Exchange;
 
 class MySqlLeaseMgr : public LeaseMgr {
 public:
+
     /// @brief Constructor
     ///
     /// Uses the following keywords in the parameters passed to it to
@@ -117,7 +71,7 @@ public:
     /// @throw isc::dhcp::DbOpenError Error opening the database
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    MySqlLeaseMgr(const ParameterMap& parameters);
+    MySqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters);
 
     /// @brief Destructor (closes database)
     virtual ~MySqlLeaseMgr();
@@ -535,41 +489,6 @@ public:
     };
 
 private:
-    /// @brief Prepare Single Statement
-    ///
-    /// Creates a prepared statement from the text given and adds it to the
-    /// statements_ vector at the given index.
-    ///
-    /// @param index Index into the statements_ vector into which the text
-    ///        should be placed.  The vector must be big enough for the index
-    ///        to be valid, else an exception will be thrown.
-    /// @param text Text of the SQL statement to be prepared.
-    ///
-    /// @throw isc::dhcp::DbOperationError An operation on the open database has
-    ///        failed.
-    /// @throw isc::InvalidParameter 'index' is not valid for the vector.
-    void prepareStatement(StatementIndex index, const char* text);
-
-    /// @brief Prepare statements
-    ///
-    /// Creates the prepared statements for all of the SQL statements used
-    /// by the MySQL backend.
-    ///
-    /// @throw isc::dhcp::DbOperationError An operation on the open database has
-    ///        failed.
-    /// @throw isc::InvalidParameter 'index' is not valid for the vector.  This
-    ///        represents an internal error within the code.
-    void prepareStatements();
-
-    /// @brief Open Database
-    ///
-    /// Opens the database using the information supplied in the parameters
-    /// passed to the constructor.
-    ///
-    /// @throw NoDatabaseName Mandatory database name not given
-    /// @throw DbOpenError Error opening the database
-    void openDatabase();
-
     /// @brief Add Lease Common Code
     ///
     /// This method performs the common actions for both flavours (V4 and V6)
@@ -763,9 +682,9 @@ private:
                            const char* what) const {
         if (status != 0) {
             isc_throw(DbOperationError, what << " for <" <<
-                      text_statements_[index] << ">, reason: " <<
-                      mysql_error(mysql_) << " (error code " <<
-                      mysql_errno(mysql_) << ")");
+                      conn_.text_statements_[index] << ">, reason: " <<
+                      mysql_error(conn_.mysql_) << " (error code " <<
+                      mysql_errno(conn_.mysql_) << ")");
         }
     }
 
@@ -777,9 +696,9 @@ private:
     /// declare them as "mutable".)
     boost::scoped_ptr<MySqlLease4Exchange> exchange4_; ///< Exchange object
     boost::scoped_ptr<MySqlLease6Exchange> exchange6_; ///< Exchange object
-    MySqlHolder mysql_;
-    std::vector<MYSQL_STMT*> statements_;       ///< Prepared statements
-    std::vector<std::string> text_statements_;  ///< Raw text of statements
+
+    /// @brief MySQL connection
+    MySqlConnection conn_;
 };
 
 }; // end of isc::dhcp namespace

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

@@ -942,9 +942,9 @@ private:
     //@}
 };
 
-PgSqlLeaseMgr::PgSqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
-    : LeaseMgr(parameters), exchange4_(new PgSqlLease4Exchange()),
-    exchange6_(new PgSqlLease6Exchange()), conn_(NULL) {
+PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+    : LeaseMgr(), exchange4_(new PgSqlLease4Exchange()),
+    exchange6_(new PgSqlLease6Exchange()), dbconn_(parameters), conn_(NULL) {
     openDatabase();
     prepareStatements();
 }
@@ -1000,7 +1000,7 @@ PgSqlLeaseMgr::openDatabase() {
     string dbconnparameters;
     string shost = "localhost";
     try {
-        shost = getParameter("host");
+        shost = dbconn_.getParameter("host");
     } catch(...) {
         // No host. Fine, we'll use "localhost"
     }
@@ -1009,7 +1009,7 @@ PgSqlLeaseMgr::openDatabase() {
 
     string suser;
     try {
-        suser = getParameter("user");
+        suser = dbconn_.getParameter("user");
         dbconnparameters += " user = '" + suser + "'";
     } catch(...) {
         // No user. Fine, we'll use NULL
@@ -1017,7 +1017,7 @@ PgSqlLeaseMgr::openDatabase() {
 
     string spassword;
     try {
-        spassword = getParameter("password");
+        spassword = dbconn_.getParameter("password");
         dbconnparameters += " password = '" + spassword + "'";
     } catch(...) {
         // No password. Fine, we'll use NULL
@@ -1025,7 +1025,7 @@ PgSqlLeaseMgr::openDatabase() {
 
     string sname;
     try {
-        sname= getParameter("name");
+        sname= dbconn_.getParameter("name");
         dbconnparameters += " dbname = '" + sname + "'";
     } catch(...) {
         // No database name.  Throw a "NoDatabaseName" exception
@@ -1523,7 +1523,7 @@ string
 PgSqlLeaseMgr::getName() const {
     string name = "";
     try {
-        name = getParameter("name");
+        name = dbconn_.getParameter("name");
     } catch (...) {
         // Return an empty name
     }

+ 8 - 2
src/lib/dhcpsrv/pgsql_lease_mgr.h

@@ -17,7 +17,7 @@
 
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/lease_mgr.h>
-
+#include <dhcpsrv/database_connection.h>
 #include <boost/scoped_ptr.hpp>
 #include <boost/utility.hpp>
 #include <libpq-fe.h>
@@ -146,7 +146,7 @@ public:
     /// @throw isc::dhcp::DbOpenError Error opening the database
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    PgSqlLeaseMgr(const ParameterMap& parameters);
+    PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters);
 
     /// @brief Destructor (closes database)
     virtual ~PgSqlLeaseMgr();
@@ -664,6 +664,12 @@ private:
     boost::scoped_ptr<PgSqlLease4Exchange> exchange4_; ///< Exchange object
     boost::scoped_ptr<PgSqlLease6Exchange> exchange6_; ///< Exchange object
 
+    /// Database connection object
+    ///
+    /// @todo: Implement PgSQLConnection object and collapse
+    /// dbconn_ and conn_ into a single object.
+    DatabaseConnection dbconn_;
+
     /// PostgreSQL connection handle
     PGconn* conn_;
 };

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

@@ -80,6 +80,7 @@ libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
+libdhcpsrv_unittests_SOURCES += database_connection_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_mgr_unittest.cc

+ 169 - 0
src/lib/dhcpsrv/tests/data_source_unittest.cc

@@ -0,0 +1,169 @@
+// 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 <exceptions/exceptions.h>
+#include <dhcpsrv/database_connection.h>
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+
+
+/// @brief getParameter test
+///
+/// This test checks if the LeaseMgr can be instantiated and that it
+/// parses parameters string properly.
+TEST(DatabaseConnectionTest, getParameter) {
+
+    DatabaseConnection::ParameterMap pmap;
+    pmap[std::string("param1")] = std::string("value1");
+    pmap[std::string("param2")] = std::string("value2");
+    DatabaseConnection datasrc(pmap);
+
+    EXPECT_EQ("value1", datasrc.getParameter("param1"));
+    EXPECT_EQ("value2", datasrc.getParameter("param2"));
+    EXPECT_THROW(datasrc.getParameter("param3"), isc::BadValue);
+}
+
+// This test checks that a database access string can be parsed correctly.
+TEST(DatabaseConnectionTest, parse) {
+
+    DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(
+        "user=me password=forbidden name=kea somethingelse= type=mysql");
+
+    EXPECT_EQ(5, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("forbidden", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+    EXPECT_EQ("", parameters["somethingelse"]);
+}
+
+// This test checks that an invalid database access string behaves as expected.
+TEST(DatabaseConnectionTest, parseInvalid) {
+
+    // No tokens in the string, so we expect no parameters
+    std::string invalid = "";
+    DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(invalid);
+    EXPECT_EQ(0, parameters.size());
+
+    // With spaces, there are some tokens so we expect invalid parameter
+    // as there are no equals signs.
+    invalid = "   \t  ";
+    EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter);
+
+    invalid = "   noequalshere  ";
+    EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter);
+
+    // A single "=" is valid string, but is placed here as the result is
+    // expected to be nothing.
+    invalid = "=";
+    parameters = DatabaseConnection::parse(invalid);
+    EXPECT_EQ(1, parameters.size());
+    EXPECT_EQ("", parameters[""]);
+}
+
+/// @brief redactConfigString test
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks.
+TEST(DatabaseConnectionTest, redactAccessString) {
+
+    DatabaseConnection::ParameterMap parameters =
+        DatabaseConnection::parse("user=me password=forbidden name=kea type=mysql");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("forbidden", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+    parameters = DatabaseConnection::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - empty password
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks, even if the password is null.
+TEST(DatabaseConnectionTest, redactAccessStringEmptyPassword) {
+
+    DatabaseConnection::ParameterMap parameters =
+        DatabaseConnection::parse("user=me name=kea type=mysql password=");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+    parameters = DatabaseConnection::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // ... and again to check that the position of the empty password in the
+    // string does not matter.
+    parameters = DatabaseConnection::parse("user=me password= name=kea type=mysql");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    redacted = DatabaseConnection::redactedAccessString(parameters);
+    parameters = DatabaseConnection::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - no password
+///
+/// Checks that the redacted configuration string excludes the password if there
+/// was no password to begin with.
+TEST(DatabaseConnectionTest, redactAccessStringNoPassword) {
+
+    DatabaseConnection::ParameterMap parameters =
+        DatabaseConnection::parse("user=me name=kea type=mysql");
+    EXPECT_EQ(3, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+    parameters = DatabaseConnection::parse(redacted);
+
+    EXPECT_EQ(3, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+}

+ 169 - 0
src/lib/dhcpsrv/tests/database_connection_unittest.cc

@@ -0,0 +1,169 @@
+// 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 <exceptions/exceptions.h>
+#include <dhcpsrv/database_connection.h>
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+
+
+/// @brief getParameter test
+///
+/// This test checks if the LeaseMgr can be instantiated and that it
+/// parses parameters string properly.
+TEST(DatabaseConnectionTest, getParameter) {
+
+    DatabaseConnection::ParameterMap pmap;
+    pmap[std::string("param1")] = std::string("value1");
+    pmap[std::string("param2")] = std::string("value2");
+    DatabaseConnection datasrc(pmap);
+
+    EXPECT_EQ("value1", datasrc.getParameter("param1"));
+    EXPECT_EQ("value2", datasrc.getParameter("param2"));
+    EXPECT_THROW(datasrc.getParameter("param3"), isc::BadValue);
+}
+
+// This test checks that a database access string can be parsed correctly.
+TEST(DatabaseConnectionTest, parse) {
+
+    DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(
+        "user=me password=forbidden name=kea somethingelse= type=mysql");
+
+    EXPECT_EQ(5, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("forbidden", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+    EXPECT_EQ("", parameters["somethingelse"]);
+}
+
+// This test checks that an invalid database access string behaves as expected.
+TEST(DatabaseConnectionTest, parseInvalid) {
+
+    // No tokens in the string, so we expect no parameters
+    std::string invalid = "";
+    DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(invalid);
+    EXPECT_EQ(0, parameters.size());
+
+    // With spaces, there are some tokens so we expect invalid parameter
+    // as there are no equals signs.
+    invalid = "   \t  ";
+    EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter);
+
+    invalid = "   noequalshere  ";
+    EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter);
+
+    // A single "=" is valid string, but is placed here as the result is
+    // expected to be nothing.
+    invalid = "=";
+    parameters = DatabaseConnection::parse(invalid);
+    EXPECT_EQ(1, parameters.size());
+    EXPECT_EQ("", parameters[""]);
+}
+
+/// @brief redactConfigString test
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks.
+TEST(DatabaseConnectionTest, redactAccessString) {
+
+    DatabaseConnection::ParameterMap parameters =
+        DatabaseConnection::parse("user=me password=forbidden name=kea type=mysql");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("forbidden", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+    parameters = DatabaseConnection::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - empty password
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks, even if the password is null.
+TEST(DatabaseConnectionTest, redactAccessStringEmptyPassword) {
+
+    DatabaseConnection::ParameterMap parameters =
+        DatabaseConnection::parse("user=me name=kea type=mysql password=");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+    parameters = DatabaseConnection::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // ... and again to check that the position of the empty password in the
+    // string does not matter.
+    parameters = DatabaseConnection::parse("user=me password= name=kea type=mysql");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    redacted = DatabaseConnection::redactedAccessString(parameters);
+    parameters = DatabaseConnection::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - no password
+///
+/// Checks that the redacted configuration string excludes the password if there
+/// was no password to begin with.
+TEST(DatabaseConnectionTest, redactAccessStringNoPassword) {
+
+    DatabaseConnection::ParameterMap parameters =
+        DatabaseConnection::parse("user=me name=kea type=mysql");
+    EXPECT_EQ(3, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+    parameters = DatabaseConnection::parse(redacted);
+
+    EXPECT_EQ(3, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+}

+ 1 - 1
src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc

@@ -161,7 +161,7 @@ public:
 
         // Check that the keywords and keyword values are the same: loop
         // through the keywords in the database access string.
-        for (LeaseMgr::ParameterMap::const_iterator actual = parameters.begin();
+        for (DatabaseConnection::ParameterMap::const_iterator actual = parameters.begin();
              actual != parameters.end(); ++actual) {
 
             // Does the keyword exist in the set of expected keywords?

+ 1 - 0
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc

@@ -15,6 +15,7 @@
 #include <config.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 #include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/database_connection.h>
 #include <asiolink/io_address.h>
 #include <gtest/gtest.h>
 #include <sstream>

+ 4 - 138
src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc

@@ -30,144 +30,10 @@ using namespace isc::dhcp;
 // Tests of the LeaseMgr create/instance/destroy are implicitly carried out
 // in the tests for the different concrete lease managers (e.g. MySqlLeaseMgr).
 
-namespace {
-// empty class for now, but may be extended once Addr6 becomes bigger
-class LeaseMgrFactoryTest : public ::testing::Test {
-public:
-    LeaseMgrFactoryTest() {
-    }
-};
-
-// This test checks that a database access string can be parsed correctly.
-TEST_F(LeaseMgrFactoryTest, parse) {
-
-    LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(
-        "user=me password=forbidden name=kea somethingelse= type=mysql");
-
-    EXPECT_EQ(5, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("forbidden", parameters["password"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-    EXPECT_EQ("", parameters["somethingelse"]);
-}
-
-// This test checks that an invalid database access string behaves as expected.
-TEST_F(LeaseMgrFactoryTest, parseInvalid) {
-
-    // No tokens in the string, so we expect no parameters
-    std::string invalid = "";
-    LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(invalid);
-    EXPECT_EQ(0, parameters.size());
-
-    // With spaces, there are some tokens so we expect invalid parameter
-    // as there are no equals signs.
-    invalid = "   \t  ";
-    EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
-
-    invalid = "   noequalshere  ";
-    EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
-
-    // A single "=" is valid string, but is placed here as the result is
-    // expected to be nothing.
-    invalid = "=";
-    parameters = LeaseMgrFactory::parse(invalid);
-    EXPECT_EQ(1, parameters.size());
-    EXPECT_EQ("", parameters[""]);
-}
-
-/// @brief redactConfigString test
-///
-/// Checks that the redacted configuration string includes the password only
-/// as a set of asterisks.
-TEST_F(LeaseMgrFactoryTest, redactAccessString) {
-
-    LeaseMgr::ParameterMap parameters =
-        LeaseMgrFactory::parse("user=me password=forbidden name=kea type=mysql");
-    EXPECT_EQ(4, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("forbidden", parameters["password"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-
-    // Redact the result.  To check, break the redacted string down into its
-    // components.
-    std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
-    parameters = LeaseMgrFactory::parse(redacted);
+// Currently there are no unit-tests as the sole testable method (parse)
+// was moved to its own class (DataSource). All existing unit-tests were
+// moved there.
 
-    EXPECT_EQ(4, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("*****", parameters["password"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-}
-
-/// @brief redactConfigString test - empty password
-///
-/// Checks that the redacted configuration string includes the password only
-/// as a set of asterisks, even if the password is null.
-TEST_F(LeaseMgrFactoryTest, redactAccessStringEmptyPassword) {
-
-    LeaseMgr::ParameterMap parameters =
-        LeaseMgrFactory::parse("user=me name=kea type=mysql password=");
-    EXPECT_EQ(4, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("", parameters["password"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-
-    // Redact the result.  To check, break the redacted string down into its
-    // components.
-    std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
-    parameters = LeaseMgrFactory::parse(redacted);
-
-    EXPECT_EQ(4, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("*****", parameters["password"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-
-    // ... and again to check that the position of the empty password in the
-    // string does not matter.
-    parameters = LeaseMgrFactory::parse("user=me password= name=kea type=mysql");
-    EXPECT_EQ(4, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("", parameters["password"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-
-    redacted = LeaseMgrFactory::redactedAccessString(parameters);
-    parameters = LeaseMgrFactory::parse(redacted);
-
-    EXPECT_EQ(4, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("*****", parameters["password"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-}
-
-/// @brief redactConfigString test - no password
-///
-/// Checks that the redacted configuration string excludes the password if there
-/// was no password to begin with.
-TEST_F(LeaseMgrFactoryTest, redactAccessStringNoPassword) {
-
-    LeaseMgr::ParameterMap parameters =
-        LeaseMgrFactory::parse("user=me name=kea type=mysql");
-    EXPECT_EQ(3, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-
-    // Redact the result.  To check, break the redacted string down into its
-    // components.
-    std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
-    parameters = LeaseMgrFactory::parse(redacted);
-
-    EXPECT_EQ(3, parameters.size());
-    EXPECT_EQ("me", parameters["user"]);
-    EXPECT_EQ("kea", parameters["name"]);
-    EXPECT_EQ("mysql", parameters["type"]);
-}
+namespace {
 
 }; // end of anonymous namespace

+ 3 - 22
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc

@@ -43,11 +43,8 @@ public:
     /// dbconfig is a generic way of passing parameters. Parameters
     /// are passed in the "name=value" format, separated by spaces.
     /// Values may be enclosed in double quotes, if needed.
-    ///
-    /// @param parameters A data structure relating keywords and values
-    ///        concerned with the database.
-    ConcreteLeaseMgr(const LeaseMgr::ParameterMap& parameters)
-        : LeaseMgr(parameters)
+    ConcreteLeaseMgr(const DatabaseConnection::ParameterMap&)
+        : LeaseMgr()
     {}
 
     /// @brief Destructor
@@ -297,27 +294,11 @@ public:
 
 namespace {
 
-/// @brief getParameter test
-///
-/// This test checks if the LeaseMgr can be instantiated and that it
-/// parses parameters string properly.
-TEST_F(LeaseMgrTest, getParameter) {
-
-    LeaseMgr::ParameterMap pmap;
-    pmap[std::string("param1")] = std::string("value1");
-    pmap[std::string("param2")] = std::string("value2");
-    ConcreteLeaseMgr leasemgr(pmap);
-
-    EXPECT_EQ("value1", leasemgr.getParameter("param1"));
-    EXPECT_EQ("value2", leasemgr.getParameter("param2"));
-    EXPECT_THROW(leasemgr.getParameter("param3"), BadValue);
-}
-
 // This test checks if getLease6() method is working properly for 0 (NULL),
 // 1 (return the lease) and more than 1 leases (throw).
 TEST_F(LeaseMgrTest, getLease6) {
 
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     boost::scoped_ptr<ConcreteLeaseMgr> mgr(new ConcreteLeaseMgr(pmap));
 
     vector<Lease6Ptr> leases = createLeases6();

+ 14 - 14
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -59,7 +59,7 @@ public:
     /// @brief Constructor.
     ///
     /// Sets the counter for callbacks to 0.
-    LFCMemfileLeaseMgr(const ParameterMap& parameters)
+    LFCMemfileLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
         : Memfile_LeaseMgr(parameters), lfc_cnt_(0) {
     }
 
@@ -93,7 +93,7 @@ public:
     /// @brief Constructor.
     ///
     /// Creates instance of the lease manager.
-    NakedMemfileLeaseMgr(const ParameterMap& parameters)
+    NakedMemfileLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
         : Memfile_LeaseMgr(parameters) {
     }
 
@@ -254,7 +254,7 @@ public:
 // This test checks if the LeaseMgr can be instantiated and that it
 // parses parameters string properly.
 TEST_F(MemfileLeaseMgrTest, constructor) {
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["universe"] = "4";
     pmap["persist"] = "false";
     boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr;
@@ -292,7 +292,7 @@ TEST_F(MemfileLeaseMgrTest, getLeaseFilePath) {
     LeaseFileIO io4(getLeaseFilePath("leasefile4_1.csv"));
     LeaseFileIO io6(getLeaseFilePath("leasefile6_1.csv"));
 
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["universe"] = "4";
     pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
     boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
@@ -314,7 +314,7 @@ TEST_F(MemfileLeaseMgrTest, persistLeases) {
     LeaseFileIO io4(getLeaseFilePath("leasefile4_1.csv"));
     LeaseFileIO io6(getLeaseFilePath("leasefile6_1.csv"));
 
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["universe"] = "4";
     // Specify the names of the lease files. Leases will be written.
     pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
@@ -340,7 +340,7 @@ TEST_F(MemfileLeaseMgrTest, persistLeases) {
 // Check if it is possible to schedule the timer to perform the Lease
 // File Cleanup periodically.
 TEST_F(MemfileLeaseMgrTest, lfcTimer) {
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "4";
     // Specify the names of the lease files. Leases will be written.
@@ -367,7 +367,7 @@ TEST_F(MemfileLeaseMgrTest, lfcTimer) {
 // This test checks if the LFC timer is disabled (doesn't trigger)
 // cleanups when the lfc-interval is set to 0.
 TEST_F(MemfileLeaseMgrTest, lfcTimerDisabled) {
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "4";
     pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
@@ -418,7 +418,7 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup4) {
     previous_file.writeFile(previous_file_contents);
 
     // Create the backend.
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "4";
     pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
@@ -498,7 +498,7 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup6) {
     previous_file.writeFile(previous_file_contents);
 
     // Create the backend.
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "6";
     pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
@@ -573,7 +573,7 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanupStartFail) {
     setenv("KEA_LFC_EXECUTABLE", "foobar", 1);
 
     // Create the backend.
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "4";
     pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
@@ -622,7 +622,7 @@ TEST_F(MemfileLeaseMgrTest, leaseFileFinish) {
     finish_file.writeFile(finish_file_contents);
 
     // Create the backend.
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "6";
     pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
@@ -687,7 +687,7 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCopy) {
     input_file.writeFile(input_file_contents);
 
     // Create the backend.
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "6";
     pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
@@ -1110,7 +1110,7 @@ TEST_F(MemfileLeaseMgrTest, load4CompletedFile) {
 // lease files if the LFC is in progress.
 TEST_F(MemfileLeaseMgrTest, load4LFCInProgress) {
     // Create the backend configuration.
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "4";
     pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
@@ -1360,7 +1360,7 @@ TEST_F(MemfileLeaseMgrTest, load6CompletedFile) {
 // lease files if the LFC is in progress.
 TEST_F(MemfileLeaseMgrTest, load6LFCInProgress) {
     // Create the backend configuration.
-    LeaseMgr::ParameterMap pmap;
+    DatabaseConnection::ParameterMap pmap;
     pmap["type"] = "memfile";
     pmap["universe"] = "6";
     pmap["name"] = getLeaseFilePath("leasefile6_0.csv");

+ 0 - 0
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc


+ 4 - 1
src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-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
@@ -252,6 +252,9 @@ TEST(PgSqlOpenTest, OpenDatabase) {
         VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
         DbOpenError);
 
+    // This test might fail if 'auth-method' in PostgresSQL host-based authentication
+    // file (/var/lib/pgsql/9.4/data/pg_hba.conf) is set to 'trust',
+    // which allows logging without password. 'Auth-method' should be changed to 'password'.
     EXPECT_THROW(LeaseMgrFactory::create(connectionString(
         VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
         DbOpenError);

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

@@ -37,6 +37,14 @@ const char* destroy_statement[] = {
     "DROP TABLE lease6_types",
     "DROP TABLE lease_hwaddr_source",
     "DROP TABLE schema_version",
+
+    // We need to drop ipv6_reservations before hosts, as it has constrains
+    // that depend on hosts. Therefore hosts table cannot be deleted while
+    // ipv6_reservations exists.
+    "DROP TABLE ipv6_reservations",
+    "DROP TABLE hosts",
+    "DROP TABLE dhcp4_options",
+    "DROP TABLE dhcp6_options",
     NULL
 };
 
@@ -125,6 +133,86 @@ const char* create_statement[] = {
     "UPDATE schema_version SET version=\"2\", minor=\"0\";",
     // Schema upgrade to 2.0 ends here.
 
+    // Schema upgrade to 3.0 starts here.
+
+    "CREATE TABLE IF NOT EXISTS hosts ("
+        "host_id INT UNSIGNED NOT NULL AUTO_INCREMENT,"
+        "dhcp_identifier VARBINARY(128) NOT NULL,"
+        "dhcp_identifier_type TINYINT NOT NULL,"
+        "dhcp4_subnet_id INT UNSIGNED NULL,"
+        "dhcp6_subnet_id INT UNSIGNED NULL,"
+        "ipv4_address INT UNSIGNED NULL,"
+        "hostname VARCHAR(255) NULL,"
+        "dhcp4_client_classes VARCHAR(255) NULL,"
+        "dhcp6_client_classes VARCHAR(255) NULL,"
+        "PRIMARY KEY (host_id),"
+        "INDEX key_dhcp4_identifier_subnet_id (dhcp_identifier ASC , dhcp_identifier_type ASC),"
+        "INDEX key_dhcp6_identifier_subnet_id (dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp6_subnet_id ASC)"
+    ")  ENGINE=INNODB",
+
+    "CREATE TABLE IF NOT EXISTS ipv6_reservations ("
+        "reservation_id INT NOT NULL AUTO_INCREMENT,"
+        "address VARCHAR(39) NOT NULL,"
+        "prefix_len TINYINT(3) UNSIGNED NOT NULL DEFAULT 128,"
+        "type TINYINT(4) UNSIGNED NOT NULL DEFAULT 0,"
+        "dhcp6_iaid INT UNSIGNED NULL,"
+        "host_id INT UNSIGNED NOT NULL,"
+        "PRIMARY KEY (reservation_id),"
+        "INDEX fk_ipv6_reservations_host_idx (host_id ASC),"
+        "CONSTRAINT fk_ipv6_reservations_Host FOREIGN KEY (host_id)"
+            "REFERENCES hosts (host_id)"
+            "ON DELETE NO ACTION ON UPDATE NO ACTION"
+    ")  ENGINE=INNODB",
+
+    "CREATE TABLE IF NOT EXISTS dhcp4_options ("
+        "option_id INT UNSIGNED NOT NULL AUTO_INCREMENT,"
+        "code TINYINT UNSIGNED NOT NULL,"
+        "value BLOB NULL,"
+        "formatted_value TEXT NULL,"
+        "space VARCHAR(128) NULL,"
+        "persistent TINYINT(1) NOT NULL DEFAULT 0,"
+        "dhcp_client_class VARCHAR(128) NULL,"
+        "dhcp4_subnet_id INT NULL,"
+        "host_id INT UNSIGNED NULL,"
+        "PRIMARY KEY (option_id),"
+        "UNIQUE INDEX option_id_UNIQUE (option_id ASC),"
+        "INDEX fk_options_host1_idx (host_id ASC),"
+        "CONSTRAINT fk_options_host1 FOREIGN KEY (host_id)"
+            "REFERENCES hosts (host_id)"
+            "ON DELETE NO ACTION ON UPDATE NO ACTION"
+    ")  ENGINE=INNODB",
+
+    "CREATE TABLE IF NOT EXISTS dhcp6_options ("
+        "option_id INT UNSIGNED NOT NULL AUTO_INCREMENT,"
+        "code INT UNSIGNED NOT NULL,"
+        "value BLOB NULL,"
+        "formatted_value TEXT NULL,"
+        "space VARCHAR(128) NULL,"
+        "persistent TINYINT(1) NOT NULL DEFAULT 0,"
+        "dhcp_client_class VARCHAR(128) NULL,"
+        "dhcp6_subnet_id INT NULL,"
+        "host_id INT UNSIGNED NULL,"
+        "PRIMARY KEY (option_id),"
+        "UNIQUE INDEX option_id_UNIQUE (option_id ASC),"
+        "INDEX fk_options_host1_idx (host_id ASC),"
+        "CONSTRAINT fk_options_host10 FOREIGN KEY (host_id)"
+            "REFERENCES hosts (host_id)"
+            "ON DELETE NO ACTION ON UPDATE NO ACTION"
+    ")  ENGINE=INNODB",
+
+
+    //"DELIMITER $$ ",
+    "CREATE TRIGGER host_BDEL BEFORE DELETE ON hosts FOR EACH ROW "
+    "BEGIN "
+    "DELETE FROM ipv6_reservations WHERE ipv6_reservations.host_id = OLD.host_id; "
+    "END ",
+    //"$$ ",
+    //"DELIMITER ;",
+
+    "UPDATE schema_version SET version = '3', minor = '0';",
+
+    // This line concludes database upgrade to version 3.0.
+
     // Schema upgrade to 4.0 starts here.
     "ALTER TABLE lease4 "
         "ADD COLUMN state INT UNSIGNED DEFAULT 0",