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 += db_exceptions.h
libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libkea_dhcpsrv_la_SOURCES += host.cc host.h
libkea_dhcpsrv_la_SOURCES += host_container.h
+libkea_dhcpsrv_la_SOURCES += host_data_source_factory.cc host_data_source_factory.h
libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
libkea_dhcpsrv_la_SOURCES += hosts_log.cc hosts_log.h
libkea_dhcpsrv_la_SOURCES += key_from_key.h
if HAVE_MYSQL
libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
libkea_dhcpsrv_la_SOURCES += mysql_connection.cc mysql_connection.h
+libkea_dhcpsrv_la_SOURCES += mysql_host_data_source.cc mysql_host_data_source.h
endif
libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
class BaseHostDataSource {
public:
+ /// @brief Specifies the type of an identifier.
+ ///
+ /// This is currently used only by MySQL host data source for now, but
+ /// it is envisagad that it will be used by other host data sources
+ /// in the future. Also, this list will grow over time. It is likely
+ /// that we'll implement other identifiers in the future, e.g. remote-id.
+ ///
+ /// Those value correspond direclty to dhcp_identifier_type in hosts
+ /// table in MySQL schema.
+ enum IdType {
+ ID_HWADDR = 0, ///< Hardware address
+ ID_DUID = 1 ///< DUID/client-id
+ };
+
/// @brief Default destructor implementation.
virtual ~BaseHostDataSource() { }
/// @param host Pointer to the new @c Host object being added.
virtual void add(const HostPtr& host) = 0;
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const = 0;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void commit() {};
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void rollback() {};
};
}
/// has already been added to the IPv4 or IPv6 subnet.
virtual void add(const HostPtr& host);
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return (std::string("configuration file"));
+ }
+
private:
/// @brief Returns @c Host objects for the specific identifier and type.
namespace isc {
namespace dhcp {
+const time_t DatabaseConnection::MAX_DB_TIME = 2147483647;
+
std::string
DatabaseConnection::getParameter(const std::string& name) const {
ParameterMap::const_iterator param = parameters_.find(name);
/// @ref BaseHostDataSource derived classes.
class DatabaseConnection : public boost::noncopyable {
public:
+
+ /// @brief Defines maximum value for time that can be reliably stored.
+ ///
+ /// @todo: Is this common for MySQL and Postgres? Maybe we should have
+ /// specific values for each backend?
+ ///
+ /// If I'm still alive I'll be too old to care. You fix it.
+ static const time_t MAX_DB_TIME;
+
/// @brief Database configuration parameter map
typedef std::map<std::string, std::string> ParameterMap;
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DB_EXCEPTIONS_H
+#define DB_EXCEPTIONS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Multiple lease records found where one expected
+class MultipleRecords : public Exception {
+public:
+ MultipleRecords(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Attempt to update lease that was not there
+class NoSuchLease : public Exception {
+public:
+ NoSuchLease(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Data is truncated
+class DataTruncated : public Exception {
+public:
+ DataTruncated(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Database duplicate entry error
+class DuplicateEntry : public Exception {
+public:
+ DuplicateEntry(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+};
+};
+
+#endif
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "config.h"
+
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/hosts_log.h>
+
+#ifdef HAVE_MYSQL
+#include <dhcpsrv/mysql_host_data_source.h>
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <map>
+#include <sstream>
+#include <utility>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+
+boost::scoped_ptr<BaseHostDataSource>&
+HostDataSourceFactory::getHostDataSourcePtr() {
+ static boost::scoped_ptr<BaseHostDataSource> hostDataSourcePtr;
+ return (hostDataSourcePtr);
+}
+
+void
+HostDataSourceFactory::create(const std::string& dbaccess) {
+ const std::string type = "type";
+
+ // Parse the access string and create a redacted string for logging.
+ DatabaseConnection::ParameterMap parameters =
+ DatabaseConnection::parse(dbaccess);
+ std::string redacted =
+ DatabaseConnection::redactedAccessString(parameters);
+
+ // Is "type" present?
+ if (parameters.find(type) == parameters.end()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
+ isc_throw(InvalidParameter, "Database configuration parameters do not "
+ "contain the 'type' keyword");
+ }
+
+
+ // Yes, check what it is.
+#ifdef HAVE_MYSQL
+ if (parameters[type] == string("mysql")) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_DB).arg(redacted);
+ getHostDataSourcePtr().reset(new MySqlHostDataSource(parameters));
+ return;
+ }
+#endif
+
+#ifdef HAVE_PGSQL
+ if (parameters[type] == string("postgresql")) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_DB).arg(redacted);
+ isc_throw(NotImplemented, "Sorry, Postgres backend for host reservations "
+ "is not implemented yet.");
+ // Set pgsql data source here, when it will be implemented.
+ return;
+ }
+#endif
+
+ // Get here on no match.
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+ isc_throw(InvalidType, "Database access parameter 'type' does "
+ "not specify a supported database backend");
+}
+
+void
+HostDataSourceFactory::destroy() {
+ // Destroy current host data source instance. This is a no-op if no host
+ // data source is available.
+ if (getHostDataSourcePtr()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, HOSTS_CFG_CLOSE_HOST_DATA_SOURCE)
+ .arg(getHostDataSourcePtr()->getType());
+ }
+ getHostDataSourcePtr().reset();
+}
+
+BaseHostDataSource&
+HostDataSourceFactory::instance() {
+ BaseHostDataSource* hdsptr = getHostDataSourcePtr().get();
+ if (hdsptr == NULL) {
+ isc_throw(NoHostDataSourceManager,
+ "no current host data source instance is available");
+ }
+ return (*hdsptr);
+}
+
+}; // namespace dhcp
+}; // namespace isc
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HOST_DATA_SOURCE_FACTORY_H
+#define HOST_DATA_SOURCE_FACTORY_H
+
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/database_connection.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Invalid type exception
+///
+/// Thrown when the factory doesn't recognise the type of the backend.
+class InvalidType : public Exception {
+public:
+ InvalidType(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief No host data source instance exception
+///
+/// Thrown if an attempt is made to get a reference to the current
+/// host data source instance and none is currently available.
+class NoHostDataSourceManager : public Exception {
+public:
+ NoHostDataSourceManager(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Host Data Source Factory
+///
+/// This class comprises nothing but static methods used to create a host data source object.
+/// It analyzes the database information passed to the creation function and instantiates
+/// an appropriate host data source object based on the type requested.
+///
+/// Strictly speaking these functions could be stand-alone functions. However,
+/// it is convenient to encapsulate them in a class for naming purposes.
+
+class HostDataSourceFactory {
+public:
+ /// @brief Create an instance of a host data source.
+ ///
+ /// Each database backend has its own host data source type. This static
+ /// method sets the "current" host data source to be an object of the
+ /// appropriate type. The actual host data source is returned by the
+ /// "instance" method.
+ ///
+ /// @note When called, the current host data source is <b>always</b> destroyed
+ /// and a new one created - even if the parameters are the same.
+ ///
+ /// dbaccess is a generic way of passing parameters. Parameters are passed
+ /// in the "name=value" format, separated by spaces. The data MUST include
+ /// a keyword/value pair of the form "type=dbtype" giving the database
+ /// type, e.q. "mysql" or "sqlite3".
+ ///
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
+ ///
+ /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
+ /// keyword.
+ /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
+ /// identify a supported backend.
+ static void create(const std::string& dbaccess);
+
+ /// @brief Destroy host data source
+ ///
+ /// Destroys the current host data source object. This should have the effect
+ /// of closing the database connection. The method is a no-op if no
+ /// host data source is available.
+ static void destroy();
+
+ /// @brief Return current host data source
+ ///
+ /// @returns An instance of the "current" host data source. An exception
+ /// will be thrown if none is available.
+ ///
+ /// @throw NoHostDataSourceManager No host data source is available: use
+ /// create() to create one before calling this method.
+ static BaseHostDataSource& instance();
+
+private:
+ /// @brief Hold pointer to host data source instance
+ ///
+ /// Holds a pointer to the singleton host data source. The singleton
+ /// is encapsulated in this method to avoid a "static initialization
+ /// fiasco" if defined in an external static variable.
+ static boost::scoped_ptr<BaseHostDataSource>& getHostDataSourcePtr();
+
+};
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/hosts_log.h>
+#include <dhcpsrv/host_data_source_factory.h>
namespace {
using namespace isc::asiolink;
+boost::shared_ptr<BaseHostDataSource> HostMgr::alternate_source;
+
boost::scoped_ptr<HostMgr>&
HostMgr::getHostMgrPtr() {
static boost::scoped_ptr<HostMgr> host_mgr_ptr;
}
void
-HostMgr::create(const std::string&) {
+HostMgr::create(const std::string& access) {
getHostMgrPtr().reset(new HostMgr());
- /// @todo Initialize alternate_source here, using the parameter.
- /// For example: alternate_source.reset(new MysqlHostDataSource(access)).
+ if (!access.empty()) {
+ HostDataSourceFactory::create(access);
+
+ /// @todo Initialize alternate_source here.
+ //alternate_source = HostDataSourceFactory::getHostDataSourcePtr();
+ }
}
HostMgr&
return (host);
}
-
void
HostMgr::add(const HostPtr&) {
isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");
/// @param host Pointer to the new @c Host object being added.
virtual void add(const HostPtr& host);
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return (std::string("host_mgr"));
+ }
+
private:
/// @brief Private default constructor.
/// @brief Pointer to an alternate host data source.
///
/// If this pointer is NULL, the source is not in use.
- boost::scoped_ptr<BaseHostDataSource> alternate_source;
+ static boost::shared_ptr<BaseHostDataSource> alternate_source;
/// @brief Returns a pointer to the currently used instance of the
/// @c HostMgr.
the server's configuration. The argument describes the host and its
reservations in detail.
+% HOSTS_CFG_CLOSE_HOST_DATA_SOURCE Closing host data source: %1
+This is a normal message being printed when the server closes host data
+source connection.
+
% HOSTS_CFG_GET_ALL_ADDRESS4 get all hosts with reservations for IPv4 address %1
This debug message is issued when starting to retrieve all hosts, holding the
reservation for the specific IPv4 address, from the configuration. The
namespace isc {
namespace dhcp {
-const time_t LeaseMgr::MAX_DB_TIME = 2147483647;
-
Lease6Ptr
LeaseMgr::getLease6(Lease::Type type, const DUID& duid,
uint32_t iaid, SubnetID subnet_id) const {
#include <dhcp/hwaddr.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/subnet.h>
-#include <exceptions/exceptions.h>
+#include <dhcpsrv/db_exceptions.h>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
namespace isc {
namespace dhcp {
-/// @brief Multiple lease records found where one expected
-class MultipleRecords : public Exception {
-public:
- MultipleRecords(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// @brief Attempt to update lease that was not there
-class NoSuchLease : public Exception {
-public:
- NoSuchLease(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// @brief Data is truncated
-class DataTruncated : public Exception {
-public:
- DataTruncated(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-
/// @brief Abstract Lease Manager
///
/// This is an abstract API for lease database backends. It provides unified
/// of those classes for details.
class LeaseMgr {
public:
- /// @brief Defines maximum value for time that can be reliably stored.
- // If I'm still alive I'll be too old to care. You fix it.
- static const time_t MAX_DB_TIME;
-
/// @brief Constructor
///
LeaseMgr()
}
statements_.clear();
text_statements_.clear();
+}
+
+// Time conversion methods.
+//
+// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
+// from the current timezone to UTC for storage, and from UTC to the current
+// timezone for retrieval.
+//
+// This causes no problems providing that:
+// a) cltt is given in local time
+// b) We let the system take care of timezone conversion when converting
+// from a time read from the database into a local time.
+void
+MySqlConnection::convertToDatabaseTime(const time_t input_time,
+ MYSQL_TIME& output_time) {
+
+ // Convert to broken-out time
+ struct tm time_tm;
+ (void) localtime_r(&input_time, &time_tm);
+
+ // Place in output expire structure.
+ output_time.year = time_tm.tm_year + 1900;
+ output_time.month = time_tm.tm_mon + 1; // Note different base
+ output_time.day = time_tm.tm_mday;
+ output_time.hour = time_tm.tm_hour;
+ output_time.minute = time_tm.tm_min;
+ output_time.second = time_tm.tm_sec;
+ output_time.second_part = 0; // No fractional seconds
+ output_time.neg = my_bool(0); // Not negative
+}
+
+void
+MySqlConnection::convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime,
+ MYSQL_TIME& expire) {
+
+ // Calculate expiry time. Store it in the 64-bit value so as we can detect
+ // overflows.
+ int64_t expire_time_64 = static_cast<int64_t>(cltt) +
+ static_cast<int64_t>(valid_lifetime);
+
+ // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
+ // beyond the max value of int32_t.
+ if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
+ isc_throw(BadValue, "Time value is too large: " << expire_time_64);
+ }
+
+ const time_t expire_time = static_cast<const time_t>(expire_time_64);
+ // Convert to broken-out time
+ struct tm expire_tm;
+ (void) localtime_r(&expire_time, &expire_tm);
+
+ // Place in output expire structure.
+ expire.year = expire_tm.tm_year + 1900;
+ expire.month = expire_tm.tm_mon + 1; // Note different base
+ expire.day = expire_tm.tm_mday;
+ expire.hour = expire_tm.tm_hour;
+ expire.minute = expire_tm.tm_min;
+ expire.second = expire_tm.tm_sec;
+ expire.second_part = 0; // No fractional seconds
+ expire.neg = my_bool(0); // Not negative
+}
+
+void
+MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime, time_t& cltt) {
+
+ // Copy across fields from MYSQL_TIME structure.
+ struct tm expire_tm;
+ memset(&expire_tm, 0, sizeof(expire_tm));
+
+ expire_tm.tm_year = expire.year - 1900;
+ expire_tm.tm_mon = expire.month - 1;
+ expire_tm.tm_mday = expire.day;
+ expire_tm.tm_hour = expire.hour;
+ expire_tm.tm_min = expire.minute;
+ expire_tm.tm_sec = expire.second;
+ expire_tm.tm_isdst = -1; // Let the system work out about DST
+
+ // Convert to local time
+ cltt = mktime(&expire_tm) - valid_lifetime;
}
+void MySqlConnection::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
+ if (mysql_commit(mysql_) != 0) {
+ isc_throw(DbOperationError, "commit failed: "
+ << mysql_error(mysql_));
+ }
+}
+
+void MySqlConnection::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
+ if (mysql_rollback(mysql_) != 0) {
+ isc_throw(DbOperationError, "rollback failed: "
+ << mysql_error(mysql_));
+ }
+}
+
+
} // namespace isc::dhcp
} // namespace isc
#include <dhcpsrv/database_connection.h>
#include <boost/scoped_ptr.hpp>
-#include <mysql.h>
+#include <mysql/mysql.h>
#include <vector>
namespace isc {
extern const my_bool MLM_FALSE;
extern const my_bool MLM_TRUE;
+// Define the current database schema values
+
+const uint32_t CURRENT_VERSION_VERSION = 3;
+const uint32_t CURRENT_VERSION_MINOR = 0;
+
/// @brief Fetch and Release MySQL Results
///
/// When a MySQL statement is expected, to fetch the results the function
/// @throw DbOpenError Error opening the database
void openDatabase();
+ ///@{
+ /// The following methods are used to convert between times and time
+ /// intervals stored in the Lease object, and the times stored in the
+ /// database. The reason for the difference is because in the DHCP server,
+ /// the cltt (Client Time Since Last Transmission) is the natural data; in
+ /// the lease file - which may be read by the user - it is the expiry time
+ /// of the lease.
+
+ /// @brief Convert time_t value to database time.
+ ///
+ /// @param input_time A time_t value representing time.
+ /// @param output_time Reference to MYSQL_TIME object where converted time
+ /// will be put.
+ static
+ void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
+
+ /// @brief Convert Lease Time to Database Times
+ ///
+ /// Within the DHCP servers, times are stored as client last transmit time
+ /// and valid lifetime. In the database, the information is stored as
+ /// valid lifetime and "expire" (time of expiry of the lease). They are
+ /// related by the equation:
+ ///
+ /// - expire = client last transmit time + valid lifetime
+ ///
+ /// This method converts from the times in the lease object into times
+ /// able to be added to the database.
+ ///
+ /// @param cltt Client last transmit time
+ /// @param valid_lifetime Valid lifetime
+ /// @param expire Reference to MYSQL_TIME object where the expiry time of
+ /// the lease will be put.
+ ///
+ /// @throw isc::BadValue if the sum of the calculated expiration time is
+ /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
+ static
+ void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
+ MYSQL_TIME& expire);
+
+ /// @brief Convert Database Time to Lease Times
+ ///
+ /// Within the database, time is stored as "expire" (time of expiry of the
+ /// lease) and valid lifetime. In the DHCP server, the information is
+ /// stored client last transmit time and valid lifetime. These are related
+ /// by the equation:
+ ///
+ /// - client last transmit time = expire - valid_lifetime
+ ///
+ /// This method converts from the times in the database into times
+ /// able to be inserted into the lease object.
+ ///
+ /// @param expire Reference to MYSQL_TIME object from where the expiry
+ /// time of the lease is taken.
+ /// @param valid_lifetime lifetime of the lease.
+ /// @param cltt Reference to location where client last transmit time
+ /// is put.
+ static
+ void convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime, time_t& cltt);
+ ///@}
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// @throw DbOperationError If the commit failed.
+ void commit();
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// @throw DbOperationError If the rollback failed.
+ void rollback();
+
/// @brief Prepared statements
///
/// This field is public, because it is used heavily from MySqlConnection
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/mysql_host_data_source.h>
+#include <dhcpsrv/mysql_connection.h>
+#include <dhcpsrv/db_exceptions.h>
+
+#include <boost/static_assert.hpp>
+#include <mysqld_error.h>
+
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace {
+/// @brief Maximum length of classes stored in a dhcp4/6_client_classes field
+///
+const size_t CLIENT_CLASSES_MAX_LEN = 255;
+
+/// @brief Maximum length of the hostname stored in DNS.
+///
+/// This length is restricted by the length of the domain-name carried
+/// in the Client FQDN %Option (see RFC4702 and RFC4704).
+const size_t HOSTNAME_MAX_LEN = 255;
+
+
+TaggedStatement tagged_statements[] = {
+ {MySqlHostDataSource::INSERT_HOST,
+ "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
+ {MySqlHostDataSource::GET_HOST_HWADDR_DUID,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ?"},
+ {MySqlHostDataSource::GET_HOST_ADDR,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE ipv4_address = ?"},
+ {MySqlHostDataSource::GET_HOST_SUBID4_DHCPID,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type = ? "
+ " AND dhcp_identifier = ?"},
+ {MySqlHostDataSource::GET_HOST_SUBID6_DHCPID,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type = ? "
+ " AND dhcp_identifier = ?"},
+ {MySqlHostDataSource::GET_HOST_SUBID_ADDR,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
+ {MySqlHostDataSource::GET_HOST_PREFIX,
+ "SELECT h.host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts h, ipv6_reservations r "
+ "WHERE h.host_id = r.host_id AND r.prefix_len = ? "
+ " AND r.address = ?"},
+ {MySqlHostDataSource::GET_VERSION,
+ "SELECT version, minor FROM schema_version"},
+ {MySqlHostDataSource::NUM_STATEMENTS, NULL}
+};
+
+};
+
+namespace isc {
+namespace dhcp {
+
+class MySqlHostReservationExchange {
+ /// @brief Set number of database columns for this host structure
+ static const size_t HOST_COLUMNS = 9;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// The initialization of the variables here is only to satisfy cppcheck -
+ /// all variables are initialized/set in the methods before they are used.
+ MySqlHostReservationExchange() : host_id_(0), dhcp_identifier_length_(0),
+ dhcp_identifier_type_(0), dhcp4_subnet_id_(0), dhcp6_subnet_id_(0),
+ ipv4_address_(0), hostname_length_(0), dhcp4_client_classes_length_(0),
+ dhcp6_client_classes_length_(0), dhcp4_subnet_id_null_(MLM_FALSE),
+ dhcp6_subnet_id_null_(MLM_FALSE), ipv4_address_null_(MLM_FALSE),
+ hostname_null_(MLM_FALSE), dhcp4_client_classes_null_(MLM_FALSE),
+ dhcp6_client_classes_null_(MLM_FALSE){
+
+ memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
+ memset(hostname_, 0, sizeof(hostname_));
+ memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
+ memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
+ std::fill(&error_[0], &error_[HOST_COLUMNS], MLM_FALSE);
+
+ // Set the column names (for error messages)
+ columns_[0] = "host_id";
+ columns_[1] = "dhcp_identifier";
+ columns_[2] = "dhcp_identifier_type";
+ columns_[3] = "dhcp4_subnet_id";
+ columns_[4] = "dhcp6_subnet_id";
+ columns_[5] = "ipv4_address";
+ columns_[6] = "hostname";
+ columns_[7] = "dhcp4_client_classes";
+ columns_[8] = "dhcp6_client_classes";
+ BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
+ }
+
+ /// @brief Set error indicators
+ ///
+ /// Sets the error indicator for each of the MYSQL_BIND elements. It points
+ /// the "error" field within an element of the input array to the
+ /// corresponding element of the passed error array.
+ ///
+ /// @param bind Array of BIND elements
+ /// @param error Array of error elements. If there is an error in getting
+ /// data associated with one of the "bind" elements, the
+ /// corresponding element in the error array is set to MLM_TRUE.
+ /// @param count Size of each of the arrays.
+ static void setErrorIndicators(MYSQL_BIND* bind, my_bool* error,
+ size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ error[i] = MLM_FALSE;
+ bind[i].error = reinterpret_cast<char*>(&error[i]);
+ }
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @param error Array of error elements. An element is set to MLM_TRUE
+ /// if the corresponding column in the database is the source of
+ /// the error.
+ /// @param names Array of column names, the same size as the error array.
+ /// @param count Size of each of the arrays.
+ static std::string getColumnsInError(my_bool* error, std::string* names,
+ size_t count) {
+ std::string result = "";
+
+ // Accumulate list of column names
+ for (size_t i = 0; i < count; ++i) {
+ if (error[i] == MLM_TRUE) {
+ if (!result.empty()) {
+ result += ", ";
+ }
+ result += names[i];
+ }
+ }
+
+ if (result.empty()) {
+ result = "(None)";
+ }
+
+ return (result);
+ }
+
+ /// @brief Create MYSQL_BIND objects for Host Pointer
+ ///
+ /// Fills in the MYSQL_BIND array for sending data in the Host object to
+ /// the database.
+ ///
+ /// @param host Host object to be added to the database.
+ /// None of the fields in the host reservation are modified -
+ /// the host data is only read.
+ ///
+ /// @return Vector of MySQL BIND objects representing the data to be added.
+ std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
+
+ // Store host object to ensure it remains valid.
+ host_ = host;
+
+ // Initialize prior to constructing the array of MYSQL_BIND structures.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ // This also takes care of seeeting bind_[X].is_null to MLM_FALSE.
+ memset(bind_, 0, sizeof(bind_));
+
+ // Set up the structures for the various components of the host structure.
+
+ try {
+ // host_id : INT UNSIGNED NOT NULL
+ // The host_id is auto_incremented by MySQL database,
+ // so we need to pass the NULL value
+ host_id_ = static_cast<uint32_t>(NULL);
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[0].is_unsigned = MLM_TRUE;
+
+ // dhcp_identifier : VARBINARY(128) NOT NULL
+ // Check which of the identifiers is used and set values accordingly
+ if (host_->getDuid()) {
+ dhcp_identifier_length_ = host_->getDuid()->getDuid().size();
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>
+ (const_cast<uint8_t*>(&(host_->getDuid()->getDuid()[0])));
+ bind_[1].buffer_length = dhcp_identifier_length_;
+ bind_[1].length = &dhcp_identifier_length_;
+ } else if (host_->getHWAddress()){
+ dhcp_identifier_length_ = host_->getHWAddress()->hwaddr_.size();
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>
+ (&(host_->getHWAddress()->hwaddr_[0]));
+ bind_[1].buffer_length = dhcp_identifier_length_;
+ bind_[1].length = &dhcp_identifier_length_;
+ }
+
+ // dhcp_identifier_type : TINYINT NOT NULL
+ // Check which of the identifier types is used and set values accordingly
+ if (host_->getHWAddress()) {
+ dhcp_identifier_type_ = BaseHostDataSource::ID_HWADDR; // 0
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+ bind_[2].is_unsigned = MLM_TRUE;
+ } else if (host_->getDuid()) {
+ dhcp_identifier_type_ = BaseHostDataSource::ID_DUID; // 1
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+ bind_[2].is_unsigned = MLM_TRUE;
+ }
+
+ // dhcp4_subnet_id : INT UNSIGNED NULL
+ // Can't take an address of intermediate object, so let's store it
+ // in dhcp4_subnet_id_
+ dhcp4_subnet_id_ = host_->getIPv4SubnetID();
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
+ bind_[3].is_unsigned = MLM_TRUE;
+
+ // dhcp6_subnet_id : INT UNSIGNED NULL
+ // Can't take an address of intermediate object, so let's store it
+ // in dhcp6_subnet_id_
+ dhcp6_subnet_id_ = host_->getIPv6SubnetID();
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
+ bind_[4].is_unsigned = MLM_TRUE;
+
+ // ipv4_address : INT UNSIGNED NULL
+ // The address in the Host structure is an IOAddress object. Convert
+ // this to an integer for storage.
+ ipv4_address_ = static_cast<uint32_t>(host_->getIPv4Reservation());
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hostname : VARCHAR(255) NULL
+ strncpy(hostname_, host_->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
+ hostname_length_ = host_->getHostname().length();
+ bind_[6].buffer_type = MYSQL_TYPE_STRING;
+ bind_[6].buffer = reinterpret_cast<char*>(hostname_);
+ bind_[6].buffer_length = hostname_length_;
+
+ // dhcp4_client_classes : VARCHAR(255) NULL
+ bind_[7].buffer_type = MYSQL_TYPE_STRING;
+ string classes4_txt = classesToString(host_->getClientClasses4());
+ strncpy(dhcp4_client_classes_, classes4_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
+ bind_[7].buffer = dhcp4_client_classes_;
+ bind_[7].buffer_length = classes4_txt.length();
+
+ // dhcp6_client_classes : VARCHAR(255) NULL
+ bind_[8].buffer_type = MYSQL_TYPE_STRING;
+ string classes6_txt = classesToString(host->getClientClasses6());
+ strncpy(dhcp6_client_classes_, classes6_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
+ bind_[8].buffer = dhcp6_client_classes_;
+ bind_[8].buffer_length = classes6_txt.length();
+ bind_[8].buffer_length = sizeof(host_->getClientClasses6());
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from Host: "
+ << host_->getHostname() << ", reason: " << ex.what());
+ }
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[HOST_COLUMNS]));
+ }
+
+ /// @brief Create BIND array to receive data
+ ///
+ /// Creates a MYSQL_BIND array to receive Lease4 data from the database.
+ /// After data is successfully received, getLeaseData() can be used to copy
+ /// it to a Lease6 object.
+ ///
+ std::vector<MYSQL_BIND> createBindForReceive() {
+ // Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ // This also takes care of seeeting bind_[X].is_null to MLM_FALSE.
+ memset(bind_, 0, sizeof(bind_));
+
+ // host_id : INT UNSIGNED NOT NULL
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[0].is_unsigned = MLM_TRUE;
+
+ // dhcp_identifier : VARBINARY(128) NOT NULL
+ dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
+ bind_[1].buffer_length = dhcp_identifier_length_;
+ bind_[1].length = &dhcp_identifier_length_;
+
+ // dhcp_identifier_type : TINYINT NOT NULL
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+ bind_[2].is_unsigned = MLM_TRUE;
+
+ // dhcp4_subnet_id : INT UNSIGNED NULL
+ dhcp4_subnet_id_null_ = MLM_FALSE;
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
+ bind_[3].is_unsigned = MLM_TRUE;
+ bind_[3].is_null = &dhcp4_subnet_id_null_;
+
+ // dhcp6_subnet_id : INT UNSIGNED NULL
+ dhcp6_subnet_id_null_ = MLM_FALSE;
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
+ bind_[4].is_unsigned = MLM_TRUE;
+ bind_[4].is_null = &dhcp6_subnet_id_null_;
+
+ // ipv4_address : INT UNSIGNED NULL
+ ipv4_address_null_ = MLM_FALSE;
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ bind_[5].is_null = &ipv4_address_null_;
+
+ // hostname : VARCHAR(255) NULL
+ hostname_null_ = MLM_FALSE;
+ hostname_length_ = sizeof(hostname_);
+ bind_[6].buffer_type = MYSQL_TYPE_STRING;
+ bind_[6].buffer = reinterpret_cast<char*>(hostname_);
+ bind_[6].buffer_length = hostname_length_;
+ bind_[6].length = &hostname_length_;
+ bind_[6].is_null = &hostname_null_;
+
+ // dhcp4_client_classes : VARCHAR(255) NULL
+ dhcp4_client_classes_null_ = MLM_FALSE;
+ dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
+ bind_[7].buffer_type = MYSQL_TYPE_STRING;
+ bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
+ bind_[7].buffer_length = dhcp4_client_classes_length_;
+ bind_[7].length = &dhcp4_client_classes_length_;
+ bind_[7].is_null = &dhcp4_client_classes_null_;
+
+ // dhcp6_client_classes : VARCHAR(255) NULL
+ dhcp6_client_classes_null_ = MLM_FALSE;
+ dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
+ bind_[8].buffer_type = MYSQL_TYPE_STRING;
+ bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
+ bind_[8].buffer_length = dhcp6_client_classes_length_;
+ bind_[8].length = &dhcp6_client_classes_length_;
+ bind_[8].is_null = &dhcp6_client_classes_null_;
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, HOST_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[HOST_COLUMNS]));
+
+ }
+
+ /// @brief Copy Received Data into Host Object
+ ///
+ /// Called after the MYSQL_BIND array created by createBindForReceive()
+ /// has been used, this copies data from the internal member variables
+ /// into a Host objec.
+ ///
+ /// @return Host Pointer to a Lease6 object holding the relevant data.
+ HostPtr getHostData(){
+
+ // Set the dhcp identifier type in a variable of the appropriate data type,
+ // which has been initialized with an arbitrary (but valid) value.
+ Host::IdentifierType type = Host::IDENT_HWADDR;
+
+ switch (dhcp_identifier_type_) {
+ case 0:
+ type = Host::IDENT_HWADDR;
+ break;
+
+ case 1:
+ type = Host::IDENT_DUID;
+ break;
+
+ default:
+ isc_throw(BadValue, "invalid dhcp identifier type returned: "
+ << static_cast<int>(dhcp_identifier_type_)
+ << ". Only 0 or 1 are allowed.");
+ }
+
+ // Set subnets ID's if they are given, if not, leave an empty object
+ SubnetID ipv4_subnet_id(0);
+ if (dhcp4_subnet_id_null_ == MLM_FALSE) {
+ ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
+ }
+
+ SubnetID ipv6_subnet_id(0);
+ if (dhcp6_subnet_id_null_ == MLM_FALSE) {
+ ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
+ }
+
+ // Set IPv4 address reservation if it was given, if not, set IPv4 zero address
+ asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+ if (ipv4_address_null_ == MLM_FALSE) {
+ ipv4_reservation = asiolink::IOAddress(ipv4_address_);
+ }
+
+ // Set hostname if it was given, if not lease empty string
+ std::string hostname;
+ if (hostname_null_ == MLM_FALSE) {
+ hostname = std::string (hostname_, hostname_ + hostname_length_);
+ }
+
+ // Set client classes if they were given, if not, set empty client classes
+ ClientClasses dhcp4_client_classes;
+ if (dhcp4_client_classes_null_ == MLM_FALSE) {
+ dhcp4_client_classes.insert(dhcp4_client_classes_);
+ }
+
+ ClientClasses dhcp6_client_classes;
+ if (dhcp6_client_classes_null_ == MLM_FALSE) {
+ dhcp6_client_classes.insert(dhcp6_client_classes_);
+ }
+
+ // Returning Host object with set fields
+ return (HostPtr(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
+ type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
+ hostname, dhcp4_client_classes_, dhcp6_client_classes_)));
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @return Comma-separated list of columns in error, or the string
+ /// "(None)".
+ std::string getErrorColumns() {
+ return (getColumnsInError(error_, columns_, HOST_COLUMNS));
+ }
+
+ /// @brief Converts ClientClasses to a signgle string with coma separated values
+ ///
+ /// @param classes classes structure that contains zero or more classes
+ /// @return a single string with coma separated values
+ std::string classesToString(const ClientClasses& classes) {
+ string txt;
+ bool first = true;
+ for (ClientClasses::const_iterator it = classes.begin();
+ it != classes.end(); ++it) {
+ if (!first) {
+ txt += ",";
+ }
+ txt += (*it);
+
+ first = false;
+ }
+
+ return (txt);
+ }
+
+private:
+ uint32_t host_id_; /// Host unique identifier
+ std::vector<uint8_t> dhcp_identifier_; /// HW address (0) / DUID (1)
+ uint8_t dhcp_identifier_buffer_[DUID::MAX_DUID_LEN];
+ /// Buffer for dhcp identifier
+ size_t dhcp_identifier_length_; /// Length of dhcp identifier
+ uint8_t dhcp_identifier_type_; /// Type of dhcp_identifier
+ uint32_t dhcp4_subnet_id_; /// Subnet DHCPv4 identifier
+ uint32_t dhcp6_subnet_id_; /// Subnet DHCPv6 identifier
+ uint32_t ipv4_address_; /// Reserved IPv4 address.
+ IPv6ResrvCollection ipv6_reservations_; /// IPv6 reservations collection
+ char hostname_[HOSTNAME_MAX_LEN]; /// Name reserved for the host
+ unsigned long hostname_length_; /// hostname length
+ char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];
+ /// DHCPv4 client classes
+ unsigned long dhcp4_client_classes_length_; /// dhcp4_client_classes length
+ char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];
+ /// DHCPv6 client classes
+ unsigned long dhcp6_client_classes_length_; /// dhcp6_client_classes length
+ HWAddrPtr hw_address_; /// Pointer to hardware address
+ DuidPtr duid_; /// Pointer to DUID
+
+ // NULL flags for subnets id, ipv4 address, hostname and client classes
+ my_bool dhcp4_subnet_id_null_;
+ my_bool dhcp6_subnet_id_null_;
+ my_bool ipv4_address_null_;
+ my_bool hostname_null_;
+ my_bool dhcp4_client_classes_null_;
+ my_bool dhcp6_client_classes_null_;
+
+ MYSQL_BIND bind_[HOST_COLUMNS];
+ std::string columns_[HOST_COLUMNS]; /// Column names
+ my_bool error_[HOST_COLUMNS]; /// Error array
+ HostPtr host_; // Pointer to Host object
+};
+
+// MySqlHostDataSource Constructor and Destructor
+
+MySqlHostDataSource::MySqlHostDataSource(
+ const MySqlConnection::ParameterMap& parameters) : conn_(parameters) {
+
+ // Open the database.
+ 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(conn_.mysql_, 1);
+ if (result != 0) {
+ isc_throw(DbOperationError, mysql_error(conn_.mysql_));
+ }
+
+ // Prepare all statements likely to be used.
+ conn_.prepareStatements(tagged_statements,
+ MySqlHostDataSource::NUM_STATEMENTS);
+
+ // Create the exchange objects for use in exchanging data between the
+ // program and the database.
+ hostExchange_.reset(new MySqlHostReservationExchange());
+}
+
+MySqlHostDataSource::~MySqlHostDataSource() {
+ // Free up the prepared statements, ignoring errors. (What would we do
+ // about them? We're destroying this object and are not really concerned
+ // with errors on a database connection that is about to go away.)
+ for (int i = 0; i < conn_.statements_.size(); ++i) {
+ if (conn_.statements_[i] != NULL) {
+ (void) mysql_stmt_close(conn_.statements_[i]);
+ conn_.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.
+}
+
+bool
+MySqlHostDataSource::checkIfExists(const HostPtr& host){
+ /// @todo: Implement this as a single query get(identifier_type, identifier)
+ return (get4(host->getIPv4SubnetID(), host->getHWAddress(), host->getDuid()) ||
+ get6(host->getIPv6SubnetID(), host->getDuid(), host->getHWAddress()));
+}
+
+void
+MySqlHostDataSource::add(const HostPtr& host) {
+ // Check if the host is not a duplicate
+ if (checkIfExists(host)){
+ isc_throw(DuplicateEntry, "Host with same parameters already exists.");
+ } else {
+ // Create the MYSQL_BIND array for the host
+ std::vector<MYSQL_BIND> bind = hostExchange_->createBindForSend(host);
+
+ // ... and call addHost() code.
+ addHost(INSERT_HOST, bind);
+
+ /// @todo: Insert IPv6 reservations, if present (ticket #4212)
+ }
+}
+
+void
+MySqlHostDataSource::addHost(StatementIndex stindex,
+ std::vector<MYSQL_BIND>& bind) {
+
+ // Bind the parameters to the statement
+ 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(conn_.statements_[stindex]);
+ if (status != 0) {
+ // Failure: check for the special case of duplicate entry.
+ if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+ checkError(status, stindex, "unable to execute");
+ }
+}
+
+void
+MySqlHostDataSource::getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
+ boost::shared_ptr<MySqlHostReservationExchange> exchange,
+ ConstHostCollection& result, bool single) const {
+
+ // Bind the selection parameters to the statement
+ int status = mysql_stmt_bind_param(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(conn_.statements_[stindex], &outbind[0]);
+ checkError(status, stindex, "unable to bind SELECT clause parameters");
+
+ // Execute the statement
+ 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(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(conn_.statements_[stindex]);
+ int count = 0;
+ while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) == 0) {
+ try {
+ result.push_back(exchange->getHostData());
+
+ } catch (const isc::BadValue& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ 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 "
+ << conn_.text_statements_[stindex]);
+ }
+ }
+
+ // How did the fetch end?
+ if (status == 1) {
+ // Error - unable to fetch results
+ checkError(status, stindex, "unable to fetch results");
+ } else if (status == MYSQL_DATA_TRUNCATED) {
+ // Data truncated - throw an exception indicating what was at fault
+ isc_throw(DataTruncated, conn_.text_statements_[stindex]
+ << " returned truncated data: columns affected are "
+ << exchange->getErrorColumns());
+ }
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint8_t dhcp_identifier_type = 0;
+ long unsigned int length = 0;
+ if (duid){
+ // DUID
+ // set proper dhcp_identifier_type
+ dhcp_identifier_type = BaseHostDataSource::ID_DUID; // 1
+ inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type);
+
+ const vector<uint8_t>& duid_vector = duid->getDuid();
+ length = duid_vector.size();
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>
+ (const_cast<uint8_t*>(&duid_vector[0]));
+ inbind[0].buffer_length = length;
+ inbind[0].length = &length;
+ } else if (hwaddr) {
+ // HW Address
+ dhcp_identifier_type = BaseHostDataSource::ID_HWADDR; // 0
+ inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type);
+
+ const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+ length = hwaddr_vector.size();
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>
+ (const_cast<uint8_t*>(&hwaddr_vector[0]));
+ inbind[0].buffer_length = length;
+ inbind[0].length = &length;
+ }
+
+ // dhcp identifier type
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ getHostCollection(GET_HOST_HWADDR_DUID, inbind, hostExchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t addr4 = static_cast<uint32_t>(address);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ getHostCollection(GET_HOST_ADDR, inbind, hostExchange_, result, false);
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+ const DuidPtr& duid) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
+ if (hwaddr && duid) {
+ isc_throw(BadValue, "MySQL host data source get4() called with both"
+ " hwaddr and duid, only one of them is allowed");
+ }
+ if (!hwaddr && !duid) {
+ isc_throw(BadValue, "MySQL host data source get4() called with "
+ "neither hwaddr or duid specified, one of them is required");
+ }
+
+ unsigned long length = 0;
+ uint8_t dhcp_identifier_type_ = 0;
+
+ // Choosing one of the identifiers
+ if (hwaddr) {
+ // set identifier type
+ dhcp_identifier_type_ = BaseHostDataSource::ID_HWADDR; // 0
+
+ // set identifier value
+ const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+ length = hwaddr_vector.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = reinterpret_cast<char*>
+ (const_cast<uint8_t*>(&hwaddr_vector[0]));
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+
+ } else if (duid) {
+ // set identifier type
+ dhcp_identifier_type_ = BaseHostDataSource::ID_DUID; // 1
+
+ // set identifier value
+ const vector<uint8_t>& duid_vector = duid->getDuid();
+ length = duid_vector.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = reinterpret_cast<char*>
+ (const_cast<uint8_t*>(&duid_vector[0]));
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+ }
+
+ // dhcp identifier type
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_SUBID4_DHCPID, inbind, hostExchange_,
+ collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty())
+ result = *collection.begin();
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ uint32_t subnet = subnet_id;
+ memset(inbind, 0, sizeof(inbind));
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ uint32_t addr4 = static_cast<uint32_t>(address);
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_SUBID_ADDR, inbind, hostExchange_,
+ collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty())
+ result = *collection.begin();
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+ const HWAddrPtr& hwaddr) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
+ if (hwaddr && duid) {
+ isc_throw(BadValue, "MySQL host data source get6() called with both"
+ " hwaddr and duid, only one of them is allowed");
+ }
+ if (!hwaddr && !duid) {
+ isc_throw(BadValue, "MySQL host data source get6() called with "
+ "neither hwaddr or duid specified, one of them is required");
+ }
+
+ unsigned long length = 0;
+ uint8_t dhcp_identifier_type_ = 0;
+
+ // Choosing one of the identifiers
+ if (hwaddr) {
+ // set identifier type
+ dhcp_identifier_type_ = BaseHostDataSource::ID_HWADDR; // 0
+
+ // set identifier value
+ const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+ length = hwaddr_vector.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = reinterpret_cast<char*>
+ (const_cast<uint8_t*>(&hwaddr_vector[0]));
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+ } else if (duid) {
+ // set identifier type
+ dhcp_identifier_type_ = BaseHostDataSource::ID_DUID; // 1
+
+ // set identifier value
+ const vector<uint8_t>& duid_vector = duid->getDuid();
+ length = duid_vector.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = reinterpret_cast<char*>
+ (const_cast<uint8_t*>(&duid_vector[0]));
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+ }
+
+ // dhcp identifier type
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_SUBID6_DHCPID, inbind, hostExchange_,
+ collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&prefix.toBytes()[0]);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ uint8_t tmp = prefix_len;
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&tmp);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_PREFIX, inbind, hostExchange_,
+ collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+// Miscellaneous database methods.
+
+std::string MySqlHostDataSource::getName() const {
+ std::string name = "";
+ try {
+ name = conn_.getParameter("name");
+ } catch (...) {
+ // Return an empty name
+ }
+ return (name);
+}
+
+std::string MySqlHostDataSource::getDescription() const {
+ return (std::string("Host data source that stores host information"
+ "in MySQL database"));
+}
+
+std::pair<uint32_t, uint32_t> MySqlHostDataSource::getVersion() const {
+ const StatementIndex stindex = GET_VERSION;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_VERSION);
+
+ uint32_t major; // Major version number
+ uint32_t minor; // Minor version number
+
+ // Execute the prepared statement
+ int status = mysql_stmt_execute(conn_.statements_[stindex]);
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to execute <"
+ << conn_.text_statements_[stindex]
+ << "> - reason: " << mysql_error(conn_.mysql_));
+ }
+
+ // Bind the output of the statement to the appropriate variables.
+ MYSQL_BIND bind[2];
+ memset(bind, 0, sizeof(bind));
+
+ bind[0].buffer_type = MYSQL_TYPE_LONG;
+ bind[0].is_unsigned = 1;
+ bind[0].buffer = &major;
+ bind[0].buffer_length = sizeof(major);
+
+ bind[1].buffer_type = MYSQL_TYPE_LONG;
+ bind[1].is_unsigned = 1;
+ bind[1].buffer = &minor;
+ bind[1].buffer_length = sizeof(minor);
+
+ status = mysql_stmt_bind_result(conn_.statements_[stindex], bind);
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to bind result set: "
+ << 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(conn_.statements_[stindex]);
+ status = mysql_stmt_fetch(conn_.statements_[stindex]);
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to obtain result set: "
+ << mysql_error(conn_.mysql_));
+ }
+
+ return (std::make_pair(major, minor));
+}
+
+void
+MySqlHostDataSource::commit() {
+ conn_.commit();
+}
+
+
+void
+MySqlHostDataSource::rollback() {
+ conn_.rollback();
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MYSQL_HOST_DATA_SOURCE_H
+#define MYSQL_HOST_DATA_SOURCE_H
+
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/mysql_connection.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/utility.hpp>
+#include <mysql/mysql.h>
+
+namespace isc {
+namespace dhcp {
+
+// Forward declaration of the Host exchange objects. These classes are defined
+// in the .cc file.
+class MySqlHostReservationExchange;
+
+/// @brief MySQL Host Data Source
+///
+/// This class provides the @ref isc::dhcp::BaseHostDataSource interface to the MySQL
+/// database. Use of this backend presupposes that a MySQL database is
+/// available and that the Kea schema has been created within it.
+class MySqlHostDataSource: public BaseHostDataSource {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Uses the following keywords in the parameters passed to it to
+ /// connect to the database:
+ /// - name - Name of the database to which to connect (mandatory)
+ /// - host - Host to which to connect (optional, defaults to "localhost")
+ /// - user - Username under which to connect (optional)
+ /// - password - Password for "user" on the database (optional)
+ ///
+ /// If the database is successfully opened, the version number in the
+ /// schema_version table will be checked against hard-coded value in
+ /// the implementation file.
+ ///
+ /// Finally, all the SQL commands are pre-compiled.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+ /// @throw isc::dhcp::DbOpenError Error opening the database
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ MySqlHostDataSource(const DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Destructor (closes database)
+ virtual ~MySqlHostDataSource();
+
+ /// @brief Return all hosts for the specified HW address or DUID.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// for the specified HW address or DUID. Note, that this method may
+ /// return multiple reservations because a particular client may have
+ /// reservations in multiple subnets and the same client may be identified
+ /// by HW address or DUID. The server is unable to verify that the specific
+ /// DUID and HW address belong to the same client, until the client sends
+ /// a DHCP message.
+ ///
+ /// Specifying both hardware address and DUID is allowed for this method
+ /// and results in returning all objects that are associated with hardware
+ /// address OR duid. For example: if one host is associated with the
+ /// specified hardware address and another host is associated with the
+ /// specified DUID, two hosts will be returned.
+ ///
+ /// @param hwaddr HW address of the client or NULL if no HW address
+ /// available.
+ /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// Implementations of this method should guard against the case when
+ /// mutliple instances of the @c Host are present, e.g. when two
+ /// @c Host objects are found, one for the DUID, another one for the
+ /// HW address. In such case, an implementation of this method
+ /// should throw an MultipleRecords exception.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param hwaddr HW address of the client or NULL if no HW address
+ /// available.
+ /// @param duid client id or NULL if not available.
+ ///
+ /// @return Const @c Host object using a specified HW address or DUID.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+ const DuidPtr& duid = DuidPtr()) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// One of the use cases for this method is to detect collisions between
+ /// dynamically allocated addresses and reserved addresses. When the new
+ /// address is assigned to a client, the allocation mechanism should check
+ /// if this address is not reserved for some other host and do not allocate
+ /// this address if reservation is present.
+ ///
+ /// Implementations of this method should guard against invalid addresses,
+ /// such as IPv6 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Const @c Host object using a specified IPv4 address.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// Implementations of this method should guard against the case when
+ /// mutliple instances of the @c Host are present, e.g. when two
+ /// @c Host objects are found, one for the DUID, another one for the
+ /// HW address. In such case, an implementation of this method
+ /// should throw an MultipleRecords exception.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param hwaddr HW address of the client or NULL if no HW address
+ /// available.
+ /// @param duid DUID or NULL if not available.
+ ///
+ /// @return Const @c Host object using a specified HW address or DUID.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const DuidPtr& duid,
+ const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ ///
+ /// @return Const @c Host object using a specified HW address or DUID.
+ virtual ConstHostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// The implementations of this method should guard against duplicate
+ /// reservations for the same host, where possible. For example, when the
+ /// reservation for the same HW address and subnet id is added twice, the
+ /// addHost method should throw an DuplicateEntry exception. Note, that
+ /// usually it is impossible to guard against adding duplicated host, where
+ /// one instance is identified by HW address, another one by DUID.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ virtual void add(const HostPtr& host);
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return (std::string("mysql"));
+ }
+
+ /// @brief Returns backend name.
+ ///
+ /// Each backend have specific name, e.g. "mysql" or "sqlite".
+ ///
+ /// @return Name of the backend.
+ virtual std::string getName() const;
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const;
+
+ /// @brief Returns backend version.
+ ///
+ /// @return Version number stored in the database, as a pair of unsigned
+ /// integers. "first" is the major version number, "second" the
+ /// minor number.
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database
+ /// has failed.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void commit();
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void rollback();
+
+ MySqlConnection* getDatabaseConnection() {
+ return &conn_;
+ }
+
+ /// @brief Statement Tags
+ ///
+ /// The contents of the enum are indexes into the list of SQL statements
+ enum StatementIndex {
+ INSERT_HOST, // Insert new host to collection
+ GET_HOST_HWADDR_DUID, // Gets hosts by DUID and/or HW address
+ GET_HOST_ADDR, // Gets hosts by IPv4 address
+ GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
+ GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
+ GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
+ GET_HOST_PREFIX, // Gets host by IPv6 prefix
+ GET_VERSION, // Obtain version number
+ NUM_STATEMENTS // Number of statements
+ };
+
+private:
+ /// @brief Add Host Code
+ ///
+ /// This method performs adding a host operation.
+ /// It binds the contents of the host object to
+ /// the prepared statement and adds it to the database.
+ ///
+ /// @param stindex Index of statemnent being executed
+ /// @param bind MYSQL_BIND array that has been created for the host
+ ///
+ /// @htrow isc::dhcp::DuplicateEntry Database throws duplicate entry error
+ void addHost(StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
+
+ /// @brief Get Host Collection Code
+ ///
+ /// This method obtains multiple hosts from the database.
+ ///
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param exchange Exchange object to use
+ /// @param result ConstHostCollection object returned. Note that any hosts
+ /// in the collection when this method is called are not erased: the
+ /// new data is appended to the end.
+ /// @param single If true, only a single data item is to be retrieved.
+ /// If more than one is present, a MultipleRecords exception will
+ /// be thrown.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ void getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
+ boost::shared_ptr<MySqlHostReservationExchange> exchange,
+ ConstHostCollection& result, bool single = false) const;
+
+ /// @brief Check Error and Throw Exception
+ ///
+ /// Virtually all MySQL functions return a status which, if non-zero,
+ /// indicates an error. This inline function conceals a lot of error
+ /// checking/exception-throwing code.
+ ///
+ /// @param status Status code: non-zero implies an error
+ /// @param index Index of statement that caused the error
+ /// @param what High-level description of the error
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database
+ /// has failed.
+ inline void checkError(int status, StatementIndex index,
+ const char* what) const {
+ if (status != 0) {
+ isc_throw(DbOperationError, what << " for <"
+ << conn_.text_statements_[index] << ">, reason: "
+ << mysql_error(conn_.mysql_) << " (error code "
+ << mysql_errno(conn_.mysql_) << ")");
+ }
+ }
+
+ /// @brief Checks if Host with same parameters already been added.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ bool checkIfExists(const HostPtr& host);
+
+ // Members
+
+ /// The exchange objects are used for transfer of data to/from the database.
+ /// They are pointed-to objects as the contents may change in "const" calls,
+ /// while the rest of this object does not. (At alternative would be to
+ /// declare them as "mutable".)
+
+ /// @brief MySQL Host Reservation Exchange object
+ boost::shared_ptr<MySqlHostReservationExchange> hostExchange_;
+
+ /// @brief MySQL connection
+ MySqlConnection conn_;
+
+};
+
+}
+}
+
+#endif // MYSQL_HOST_DATA_SOURCE_H
// expiry time (expire). The relationship is given by:
//
// expire = cltt_ + valid_lft_
- MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
- expire_);
+ MySqlConnection::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
+ expire_);
bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[4].buffer = reinterpret_cast<char*>(&expire_);
bind_[4].buffer_length = sizeof(expire_);
// Convert times received from the database to times for the lease
// structure
time_t cltt = 0;
- MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+ MySqlConnection::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
if (client_id_null_==MLM_TRUE) {
// There's no client-id, so we pass client-id_length_ set to 0
//
// expire = cltt_ + valid_lft_
//
- MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
- expire_);
+ MySqlConnection::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
+ expire_);
bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[3].buffer = reinterpret_cast<char*>(&expire_);
bind_[3].buffer_length = sizeof(expire_);
subnet_id_, fqdn_fwd_, fqdn_rev_,
hostname, hwaddr, prefixlen_));
time_t cltt = 0;
- MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+ MySqlConnection::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
result->cltt_ = cltt;
// Set state.
return (tmp.str());
}
-
-// Time conversion methods.
-//
-// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
-// from the current timezone to UTC for storage, and from UTC to the current
-// timezone for retrieval.
-//
-// This causes no problems providing that:
-// a) cltt is given in local time
-// b) We let the system take care of timezone conversion when converting
-// from a time read from the database into a local time.
-
-void
-MySqlLeaseMgr::convertToDatabaseTime(const time_t input_time,
- MYSQL_TIME& output_time) {
-
- // Convert to broken-out time
- struct tm time_tm;
- (void) localtime_r(&input_time, &time_tm);
-
- // Place in output expire structure.
- output_time.year = time_tm.tm_year + 1900;
- output_time.month = time_tm.tm_mon + 1; // Note different base
- output_time.day = time_tm.tm_mday;
- output_time.hour = time_tm.tm_hour;
- output_time.minute = time_tm.tm_min;
- output_time.second = time_tm.tm_sec;
- output_time.second_part = 0; // No fractional seconds
- output_time.neg = my_bool(0); // Not negative
-}
-
-void
-MySqlLeaseMgr::convertToDatabaseTime(const time_t cltt,
- const uint32_t valid_lifetime,
- MYSQL_TIME& expire) {
-
- // Calculate expiry time. Store it in the 64-bit value so as we can detect
- // overflows.
- const int64_t expire_time_64 = static_cast<int64_t>(cltt) +
- static_cast<int64_t>(valid_lifetime);
-
- // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
- // beyond the max value of int32_t.
- if (expire_time_64 > LeaseMgr::MAX_DB_TIME) {
- isc_throw(BadValue, "Time value is too large: " << expire_time_64);
- }
-
- convertToDatabaseTime(static_cast<time_t>(expire_time_64), expire);
-}
-
-void
-MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
- uint32_t valid_lifetime, time_t& cltt) {
-
- // Copy across fields from MYSQL_TIME structure.
- struct tm expire_tm;
- memset(&expire_tm, 0, sizeof(expire_tm));
-
- expire_tm.tm_year = expire.year - 1900;
- expire_tm.tm_mon = expire.month - 1;
- expire_tm.tm_mday = expire.day;
- expire_tm.tm_hour = expire.hour;
- expire_tm.tm_min = expire.minute;
- expire_tm.tm_sec = expire.second;
- expire_tm.tm_isdst = -1; // Let the system work out about DST
-
- // Convert to local time
- cltt = mktime(&expire_tm) - valid_lifetime;
-}
-
-
// Add leases to the database. The two public methods accept a lease object
// (either V4 of V6), bind the contents to the appropriate prepared
// statement, then call common code to execute the statement.
// Expiration timestamp.
MYSQL_TIME expire_time;
- convertToDatabaseTime(time(NULL), expire_time);
+ conn_.convertToDatabaseTime(time(NULL), expire_time);
inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
inbind[1].buffer = reinterpret_cast<char*>(&expire_time);
inbind[1].buffer_length = sizeof(expire_time);
// Expiration timestamp.
MYSQL_TIME expire_time;
- convertToDatabaseTime(time(NULL) - static_cast<time_t>(secs), expire_time);
+ conn_.convertToDatabaseTime(time(NULL) - static_cast<time_t>(secs), expire_time);
inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
inbind[1].buffer = reinterpret_cast<char*>(&expire_time);
inbind[1].buffer_length = sizeof(expire_time);
#include <boost/scoped_ptr.hpp>
#include <boost/utility.hpp>
-#include <mysql.h>
+#include <mysql/mysql.h>
#include <time.h>
namespace isc {
namespace dhcp {
-// Define the current database schema values
-
-const uint32_t CURRENT_VERSION_VERSION = 3;
-const uint32_t CURRENT_VERSION_MINOR = 0;
-
-
// Forward declaration of the Lease exchange objects. These classes are defined
// in the .cc file.
class MySqlLease4Exchange;
/// @throw DbOperationError If the rollback failed.
virtual void rollback();
- ///@{
- /// The following methods are used to convert between times and time
- /// intervals stored in the Lease object, and the times stored in the
- /// database. The reason for the difference is because in the DHCP server,
- /// the cltt (Client Time Since Last Transmission) is the natural data; in
- /// the lease file - which may be read by the user - it is the expiry time
- /// of the lease.
-
- /// @brief Convert time_t value to database time.
- ///
- /// @param input_time A time_t value representing time.
- /// @param output_time Reference to MYSQL_TIME object where converted time
- /// will be put.
- static
- void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
-
- /// @brief Convert Lease Time to Database Times
- ///
- /// Within the DHCP servers, times are stored as client last transmit time
- /// and valid lifetime. In the database, the information is stored as
- /// valid lifetime and "expire" (time of expiry of the lease). They are
- /// related by the equation:
- ///
- /// - expire = client last transmit time + valid lifetime
- ///
- /// This method converts from the times in the lease object into times
- /// able to be added to the database.
- ///
- /// @param cltt Client last transmit time
- /// @param valid_lifetime Valid lifetime
- /// @param expire Reference to MYSQL_TIME object where the expiry time of
- /// the lease will be put.
- ///
- /// @throw isc::BadValue if the sum of the calculated expiration time is
- /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
- static
- void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
- MYSQL_TIME& expire);
-
- /// @brief Convert Database Time to Lease Times
- ///
- /// Within the database, time is stored as "expire" (time of expiry of the
- /// lease) and valid lifetime. In the DHCP server, the information is
- /// stored client last transmit time and valid lifetime. These are related
- /// by the equation:
- ///
- /// - client last transmit time = expire - valid_lifetime
- ///
- /// This method converts from the times in the database into times
- /// able to be inserted into the lease object.
- ///
- /// @param expire Reference to MYSQL_TIME object from where the expiry
- /// time of the lease is taken.
- /// @param valid_lifetime lifetime of the lease.
- /// @param cltt Reference to location where client last transmit time
- /// is put.
- static
- void convertFromDatabaseTime(const MYSQL_TIME& expire,
- uint32_t valid_lifetime, time_t& cltt);
- ///@}
-
/// @brief Statement Tags
///
/// The contents of the enum are indexes into the list of SQL statements
///
/// @return std::string containing the stringified time
/// @throw isc::BadValue if the sum of the calculated expiration time is
- /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
+ /// greater than the value of @c DataSource::MAX_DB_TIME.
static std::string
convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime) {
// Calculate expiry time. Store it in the 64-bit value so as we can detect
static_cast<int64_t>(valid_lifetime);
// It has been observed that the PostgreSQL doesn't deal well with the
- // timestamp values beyond the LeaseMgr::MAX_DB_TIME seconds since the
+ // timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
// beginning of the epoch (around year 2038). The value is often
// stored in the database but it is invalid when read back (overflow?).
// Hence, the maximum timestamp value is restricted here.
- if (expire_time_64 > LeaseMgr::MAX_DB_TIME) {
+ if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
}
libdhcpsrv_unittests_SOURCES += logging_unittest.cc
libdhcpsrv_unittests_SOURCES += logging_info_unittest.cc
libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc generic_lease_mgr_unittest.h
+libdhcpsrv_unittests_SOURCES += generic_host_data_source_unittest.cc generic_host_data_source_unittest.h
libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
if HAVE_MYSQL
libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += mysql_host_data_source_unittest.cc
endif
libdhcpsrv_unittests_SOURCES += ncr_generator_unittest.cc
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/tests/generic_host_data_source_unittest.h>
+#include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/database_connection.h>
+#include <asiolink/io_address.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+GenericHostDataSourceTest::GenericHostDataSourceTest()
+ :hdsptr_(NULL) {
+
+}
+
+GenericHostDataSourceTest::~GenericHostDataSourceTest() {
+}
+
+std::string
+GenericHostDataSourceTest::generateHWAddr() {
+ /// @todo: Consider moving this somewhere to lib/testutils.
+
+ // Let's use something that is easily printable. That's convenient
+ // if you need to enter MySQL queries by hand.
+ static uint8_t hwaddr[] = {65, 66, 67, 68, 69, 70};
+
+ stringstream tmp;
+ for (int i = 0; i < sizeof(hwaddr); ++i) {
+ if (i) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::hex << std::setfill('0')
+ << static_cast<unsigned int>(hwaddr[i]);
+ }
+
+ // Increase the address for the next time we use it.
+ // This is primitive, but will work for 65k unique
+ // addresses.
+ hwaddr[sizeof(hwaddr) - 1]++;
+ if (hwaddr[sizeof(hwaddr) - 1] == 0) {
+ hwaddr[sizeof(hwaddr) - 2]++;
+ }
+ return (tmp.str());
+}
+
+std::string
+GenericHostDataSourceTest::generateDuid() {
+ /// @todo: Consider moving this somewhere to lib/testutils.
+
+ // Let's use something that is easily printable. That's convenient
+ // if you need to enter MySQL queries by hand.
+ static uint8_t duid[] = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 };
+
+ stringstream tmp;
+ for (int i = 0; i < sizeof(duid); ++i) {
+ tmp << std::setw(2) << std::hex << std::setfill('0')
+ << static_cast<unsigned int>(duid[i]);
+ }
+
+ // Increase the DUID for the next time we use it.
+ // This is primitive, but will work for 65k unique
+ // DUIDs.
+ duid[sizeof(duid) - 1]++;
+ if (duid[sizeof(duid) - 1] == 0) {
+ duid[sizeof(duid) - 2]++;
+ }
+ return (tmp.str());
+}
+
+HostPtr GenericHostDataSourceTest::initializeHost4(std::string address,
+ bool hwaddr) {
+ string ident;
+ string ident_type;
+
+ if (hwaddr) {
+ ident = generateHWAddr();
+ ident_type = "hw-address";
+ } else {
+ ident = generateDuid();
+ ident_type = "duid";
+ }
+
+ // Let's create ever increasing subnet-ids. Let's keep those different,
+ // so subnet4 != subnet6. Useful for catching cases if the code confuses
+ // subnet4 with subnet6.
+ static SubnetID subnet4 = 0;
+ static SubnetID subnet6 = 100;
+ subnet4++;
+ subnet6++;
+
+ IOAddress addr(address);
+ HostPtr host(new Host(ident, ident_type, subnet4, subnet6, addr));
+
+ return (host);
+}
+
+HostPtr GenericHostDataSourceTest::initializeHost6(std::string address,
+ BaseHostDataSource::IdType identifier,
+ bool prefix) {
+ string ident;
+ string ident_type;
+
+ switch (identifier) {
+ case BaseHostDataSource::ID_HWADDR:
+ ident = generateHWAddr();
+ ident_type = "hw-address";
+ break;
+ case BaseHostDataSource::ID_DUID:
+ ident = generateDuid();
+ ident_type = "duid";
+ break;
+ default:
+ ADD_FAILURE() << "Unknown IdType: " << identifier;
+ return HostPtr();
+ }
+
+ // Let's create ever increasing subnet-ids. Let's keep those different,
+ // so subnet4 != subnet6. Useful for catching cases if the code confuses
+ // subnet4 with subnet6.
+ static SubnetID subnet4 = 0;
+ static SubnetID subnet6 = 100;
+ subnet4++;
+ subnet6++;
+
+ HostPtr host(new Host(ident, ident_type, subnet4, subnet6, IOAddress("0.0.0.0")));
+
+ if (!prefix) {
+ // Create IPv6 reservation (for an address)
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, IOAddress(address), 128);
+ host->addReservation(resv);
+ } else {
+ // Create IPv6 reservation for a /64 prefix
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, IOAddress(address), 64);
+ host->addReservation(resv);
+ }
+ return (host);
+}
+
+void
+GenericHostDataSourceTest::compareHwaddrs(const ConstHostPtr& host1,
+ const ConstHostPtr& host2,
+ bool expect_match) {
+ ASSERT_TRUE(host1);
+ ASSERT_TRUE(host2);
+
+ // Compare if both have or have not HWaddress set.
+ if ((host1->getHWAddress() && !host2->getHWAddress()) ||
+ (!host1->getHWAddress() && host2->getHWAddress())) {
+
+ // One host has hardware address set while the other has not.
+ // Let's see if it's a problem.
+ if (expect_match) {
+ ADD_FAILURE() << "Host comparison failed: host1 hwaddress="
+ << host1->getHWAddress() << ", host2 hwaddress="
+ << host2->getHWAddress();
+ }
+ return;
+ }
+
+ // Now we know that either both or neither have hw address set.
+ // If host1 has it, we can proceed to value comparison.
+ if (host1->getHWAddress()) {
+
+ if (expect_match) {
+ // Compare the actual address if they match.
+ EXPECT_TRUE(*host1->getHWAddress() == *host2->getHWAddress());
+ } else {
+ EXPECT_FALSE(*host1->getHWAddress() == *host2->getHWAddress());
+ }
+ if (*host1->getHWAddress() != *host2->getHWAddress()) {
+ cout << host1->getHWAddress()->toText(true) << endl;
+ cout << host2->getHWAddress()->toText(true) << endl;
+ }
+ }
+}
+
+void
+GenericHostDataSourceTest::compareDuids(const ConstHostPtr& host1,
+ const ConstHostPtr& host2,
+ bool expect_match) {
+ ASSERT_TRUE(host1);
+ ASSERT_TRUE(host2);
+
+ // compare if both have or have not DUID set
+ if ((host1->getDuid() && !host2->getDuid()) ||
+ (!host1->getDuid() && host2->getDuid())) {
+
+ // One host has a DUID and the other doesn't.
+ // Let's see if it's a problem.
+ if (expect_match) {
+ ADD_FAILURE() << "DUID comparison failed: host1 duid="
+ << host1->getDuid() << ", host2 duid="
+ << host2->getDuid();
+ }
+ return;
+ }
+
+ // Now we know that either both or neither have DUID set.
+ // If host1 has it, we can proceed to value comparison.
+ if (host1->getDuid()) {
+
+ if (expect_match) {
+ EXPECT_TRUE(*host1->getDuid() == *host2->getDuid());
+ } else {
+ EXPECT_FALSE(*host1->getDuid() == *host2->getDuid());
+ }
+ if (*host1->getDuid() != *host2->getDuid()) {
+ cout << host1->getDuid()->toText() << endl;
+ cout << host2->getDuid()->toText() << endl;
+ }
+ }
+}
+
+void GenericHostDataSourceTest::compareHosts(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+
+ // Let's compare HW addresses and expect match.
+ compareHwaddrs(host1, host2, true);
+
+ // Now compare DUIDs
+ compareDuids(host1, host2, true);
+
+ // Now check that the identifiers returned as vectors are the same
+ EXPECT_EQ(host1->getIdentifierType(), host2->getIdentifierType());
+ EXPECT_EQ(host1->getIdentifier(), host2->getIdentifier());
+
+ // Check host parameters
+ EXPECT_EQ(host1->getIPv4SubnetID(), host2->getIPv4SubnetID());
+ EXPECT_EQ(host1->getIPv6SubnetID(), host2->getIPv6SubnetID());
+ EXPECT_EQ(host1->getIPv4Reservation(), host2->getIPv4Reservation());
+ EXPECT_EQ(host1->getHostname(), host2->getHostname());
+
+ // Compare IPv6 reservations
+ compareReservations6(host1->getIPv6Reservations(),
+ host2->getIPv6Reservations(),
+ true);
+
+ // And compare client classification details
+ compareClientClasses(host1->getClientClasses4(),
+ host2->getClientClasses4());
+
+ compareClientClasses(host1->getClientClasses6(),
+ host2->getClientClasses6());
+}
+
+DuidPtr
+GenericHostDataSourceTest::HWAddrToDuid(const HWAddrPtr& hwaddr) {
+ if (!hwaddr) {
+ return (DuidPtr());
+ }
+
+ return (DuidPtr(new DUID(hwaddr->hwaddr_)));
+}
+
+HWAddrPtr
+GenericHostDataSourceTest::DuidToHWAddr(const DuidPtr& duid) {
+ if (!duid) {
+ return (HWAddrPtr());
+ }
+
+ return (HWAddrPtr(new HWAddr(duid->getDuid(), HTYPE_ETHER)));
+}
+
+
+void
+GenericHostDataSourceTest::compareReservations6(IPv6ResrvRange resrv1,
+ IPv6ResrvRange resrv2,
+ bool expect_match) {
+
+ // Compare number of reservations for both hosts
+ if (std::distance(resrv1.first, resrv1.second) !=
+ std::distance(resrv2.first, resrv2.second)){
+ // Number of reservations is not equal.
+ // Let's see if it's a problem.
+ if (expect_match) {
+ ADD_FAILURE()<< "Reservation comparison failed, "
+ "hosts got different number of reservations.";
+ }
+ return;
+ }
+
+ if (std::distance(resrv1.first, resrv1.second) > 0) {
+ if (expect_match){
+ /// @todo Compare every reservation from both hosts
+ /// This is part of the work for #4212.
+ }
+ }
+}
+
+void
+GenericHostDataSourceTest::compareClientClasses(const ClientClasses& /*classes1*/,
+ const ClientClasses& /*classes2*/) {
+ /// @todo: Implement client classes comparison.
+ /// This is part of the work for #4213.
+}
+
+void GenericHostDataSourceTest::testBasic4(bool hwaddr) {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservation.
+ HostPtr host = initializeHost4("192.0.2.1", hwaddr);
+ ASSERT_TRUE(host); // Make sure the host is generate properly.
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // This should not return anything
+ ConstHostPtr from_hds = hdsptr_->get4(subnet, IOAddress("10.10.10.10"));
+ ASSERT_FALSE(from_hds);
+
+ // This time it should return a host
+ from_hds = hdsptr_->get4(subnet, IOAddress("192.0.2.1"));
+ ASSERT_TRUE(from_hds);
+
+ // Finally, let's check if what we got makes any sense.
+ compareHosts(host, from_hds);
+}
+
+
+void GenericHostDataSourceTest::testGetByIPv4(bool hwaddr) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ HostPtr host1 = initializeHost4("192.0.2.1", hwaddr);
+ HostPtr host2 = initializeHost4("192.0.2.2", hwaddr);
+ HostPtr host3 = initializeHost4("192.0.2.3", hwaddr);
+ HostPtr host4 = initializeHost4("192.0.2.4", hwaddr);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+ SubnetID subnet2 = host2->getIPv4SubnetID();
+ SubnetID subnet3 = host3->getIPv4SubnetID();
+ SubnetID subnet4 = host4->getIPv4SubnetID();
+
+ // And then try to retrieve them back.
+ ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+ ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, IOAddress("192.0.2.2"));
+ ConstHostPtr from_hds3 = hdsptr_->get4(subnet3, IOAddress("192.0.2.3"));
+ ConstHostPtr from_hds4 = hdsptr_->get4(subnet4, IOAddress("192.0.2.4"));
+
+ // Make sure we got something back.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ ASSERT_TRUE(from_hds3);
+ ASSERT_TRUE(from_hds4);
+
+ // Then let's check that what we got seems correct.
+ compareHosts(host1, from_hds1);
+ compareHosts(host2, from_hds2);
+ compareHosts(host3, from_hds3);
+ compareHosts(host4, from_hds4);
+
+ // Ok, finally let's check that getting by a different address
+ // will not work.
+ EXPECT_FALSE(hdsptr_->get4(subnet1, IOAddress("192.0.1.5")));
+}
+
+void GenericHostDataSourceTest::testGet4ByHWAddr() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host1 = initializeHost4("192.0.2.1", true);
+ HostPtr host2 = initializeHost4("192.0.2.2", true);
+
+ // Sanity check: make sure the hosts have different HW addresses.
+ ASSERT_TRUE(host1->getHWAddress());
+ ASSERT_TRUE(host2->getHWAddress());
+ compareHwaddrs(host1, host2, false);
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+ SubnetID subnet2 = host2->getIPv4SubnetID();
+
+ ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, host1->getHWAddress());
+ ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, host2->getHWAddress());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ compareHosts(host1, from_hds1);
+ compareHosts(host2, from_hds2);
+}
+
+void GenericHostDataSourceTest::testGet4ByClientId() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host1 = initializeHost4("192.0.2.1", false);
+ HostPtr host2 = initializeHost4("192.0.2.2", false);
+
+ // Sanity check: make sure the hosts have different client-ids.
+ ASSERT_TRUE(host1->getDuid());
+ ASSERT_TRUE(host2->getDuid());
+ compareDuids(host1, host2, false);
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+ SubnetID subnet2 = host2->getIPv4SubnetID();
+
+ ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, HWAddrPtr(), host1->getDuid());
+ ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, HWAddrPtr(), host2->getDuid());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ compareHosts(host1, from_hds1);
+ compareHosts(host2, from_hds2);
+}
+
+void GenericHostDataSourceTest::testHWAddrNotClientId() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with HW address
+ HostPtr host = initializeHost4("192.0.2.1", true);
+ ASSERT_TRUE(host->getHWAddress());
+ ASSERT_FALSE(host->getDuid());
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ DuidPtr duid = HWAddrToDuid(host->getHWAddress());
+
+ // Get the host by HW address (should succeed)
+ ConstHostPtr by_hwaddr = hdsptr_->get4(subnet, host->getHWAddress(), DuidPtr());
+
+ // Get the host by DUID (should fail)
+ ConstHostPtr by_duid = hdsptr_->get4(subnet, HWAddrPtr(), duid);
+
+ // Now let's check if we got what we expected.
+ EXPECT_TRUE(by_hwaddr);
+ EXPECT_FALSE(by_duid);
+}
+
+void GenericHostDataSourceTest::testClientIdNotHWAddr() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with client-id
+ HostPtr host = initializeHost4("192.0.2.1", false);
+ ASSERT_FALSE(host->getHWAddress());
+ ASSERT_TRUE(host->getDuid());
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ HWAddrPtr hwaddr = DuidToHWAddr(host->getDuid());
+
+ // Get the host by DUID (should succeed)
+ ConstHostPtr by_duid = hdsptr_->get4(subnet, HWAddrPtr(), host->getDuid());
+
+ // Get the host by HW address (should fail)
+ ConstHostPtr by_hwaddr = hdsptr_->get4(subnet, hwaddr, DuidPtr());
+
+ // Now let's check if we got what we expected.
+ EXPECT_TRUE(by_duid);
+ EXPECT_FALSE(by_hwaddr);
+}
+
+void
+GenericHostDataSourceTest::testHostname(std::string name, int num) {
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Initialize the address to 192.0.2.0 (this will be bumped
+ // up to 192.0.2.1 in the first iteration)
+ IOAddress addr("192.0.2.0");
+
+ vector<HostPtr> hosts;
+
+ // Prepare a vector of hosts with unique hostnames
+ for (int i = 0; i < num; ++i) {
+
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = initializeHost4(addr.toText(), false);
+
+ stringstream hostname;
+ hostname.str("");
+ if (num > 1) {
+ hostname << i;
+ }
+ hostname << name;
+ host->setHostname(hostname.str());
+
+ hosts.push_back(host);
+ }
+
+ // Now add them all to the host data source.
+ for (vector<HostPtr>::const_iterator it = hosts.begin();
+ it != hosts.end(); ++it) {
+ // Try to add both of the to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(*it));
+ }
+
+ // And finally retrieve them one by one and check
+ // if the hostname was preserved.
+ for (vector<HostPtr>::const_iterator it = hosts.begin();
+ it != hosts.end(); ++it) {
+
+ ConstHostPtr from_hds;
+ ASSERT_NO_THROW(from_hds = hdsptr_->get4(
+ (*it)->getIPv4SubnetID(),
+ (*it)->getIPv4Reservation()));
+ ASSERT_TRUE(from_hds);
+
+ EXPECT_EQ((*it)->getHostname(), from_hds->getHostname());
+ }
+}
+
+void
+GenericHostDataSourceTest::testMultipleSubnets(int subnets, bool hwaddr) {
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host = initializeHost4("192.0.2.1", hwaddr);
+
+ for (int i = 0; i < subnets; ++i) {
+ host->setIPv4SubnetID(i + 1000);
+ host->setIPv6SubnetID(i + 1000);
+
+ // Check that the same host can have reservations in multiple subnets.
+ EXPECT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Now check that the reservations can be retrieved by IPv4 address from
+ // each subnet separately.
+ for (int i = 0; i < subnets; ++i) {
+
+ // Try to retrieve the host by IPv4 address.
+ ConstHostPtr from_hds = hdsptr_->get4(i + 1000, host->getIPv4Reservation());
+
+ ASSERT_TRUE(from_hds);
+ EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
+
+ // Try to retrieve the host by either HW address of client-id
+ from_hds = hdsptr_->get4(i + 1000, host->getHWAddress(), host->getDuid());
+ ASSERT_TRUE(from_hds);
+ EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
+ }
+
+ // Now check that they can be retrieved all at once, by IPv4 address.
+ ConstHostCollection all_by_addr = hdsptr_->getAll4(IOAddress("192.0.2.1"));
+ ASSERT_EQ(subnets, all_by_addr.size());
+
+ // Verify that the values returned are proper.
+ int i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_addr.begin();
+ it != all_by_addr.end(); ++it) {
+ EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
+ }
+
+ // Finally, check that the hosts can be retrived by HW address or DUID
+ ConstHostCollection all_by_id = hdsptr_->getAll(host->getHWAddress(),
+ host->getDuid());
+ ASSERT_EQ(subnets, all_by_id.size());
+
+ // Check that the returned values are as expected.
+ i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_id.begin();
+ it != all_by_id.end(); ++it) {
+ EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
+ }
+}
+
+void GenericHostDataSourceTest::testGet6ByHWAddr() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host1 = initializeHost6("2001:db8::0", BaseHostDataSource::ID_HWADDR, true);
+ HostPtr host2 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_HWADDR, true);
+
+ // Sanity check: make sure the hosts have different HW addresses.
+ ASSERT_TRUE(host1->getHWAddress());
+ ASSERT_TRUE(host2->getHWAddress());
+
+ compareHwaddrs(host1, host2, false);
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ SubnetID subnet2 = host2->getIPv6SubnetID();
+
+ ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, DuidPtr(), host1->getHWAddress());
+ ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, DuidPtr(), host2->getHWAddress());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ compareHosts(host1, from_hds1);
+ compareHosts(host2, from_hds2);
+}
+
+void GenericHostDataSourceTest::testGet6ByClientId() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host1 = initializeHost6("2001:db8::0", BaseHostDataSource::ID_DUID, true);
+ HostPtr host2 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
+
+ // Sanity check: make sure the hosts have different HW addresses.
+ ASSERT_TRUE(host1->getDuid());
+ ASSERT_TRUE(host2->getDuid());
+
+ compareDuids(host1, host2, false);
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ SubnetID subnet2 = host2->getIPv6SubnetID();
+
+ ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, host1->getDuid(), HWAddrPtr());
+ ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, host2->getDuid(), HWAddrPtr());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ compareHosts(host1, from_hds1);
+ compareHosts(host2, from_hds2);
+}
+
+void
+GenericHostDataSourceTest::testSubnetId6(int subnets, BaseHostDataSource::IdType id) {
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host = initializeHost6("2001:db8::0", id, true);
+
+ for (int i = 0; i < subnets; ++i) {
+ host->setIPv4SubnetID(i + 1000);
+ host->setIPv6SubnetID(i + 1000);
+
+ // Check that the same host can have reservations in multiple subnets.
+ EXPECT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Check that the reservations can be retrieved from each subnet separately.
+ for (int i = 0; i < subnets; ++i) {
+
+ // Try to retrieve the host
+ ConstHostPtr from_hds = hdsptr_->get6(i + 1000, host->getDuid(),
+ host->getHWAddress());
+
+ ASSERT_TRUE(from_hds);
+ EXPECT_EQ(i + 1000, from_hds->getIPv6SubnetID());
+ }
+
+ // Check that the hosts can all be retrived by HW address or DUID
+ ConstHostCollection all_by_id = hdsptr_->getAll(host->getHWAddress(),
+ host->getDuid());
+ ASSERT_EQ(subnets, all_by_id.size());
+
+ // Check that the returned values are as expected.
+ int i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_id.begin();
+ it != all_by_id.end(); ++it) {
+ EXPECT_EQ(IOAddress("0.0.0.0"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv6SubnetID());
+ }
+}
+
+void GenericHostDataSourceTest::testGetByIPv6(BaseHostDataSource::IdType id,
+ bool prefix) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ HostPtr host1 = initializeHost6("2001:db8::1", id, prefix);
+ HostPtr host2 = initializeHost6("2001:db8::2", id, prefix);
+ HostPtr host3 = initializeHost6("2001:db8::3", id, prefix);
+ HostPtr host4 = initializeHost6("2001:db8::4", id, prefix);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // Are we talking about addresses or prefixes?
+ uint8_t len = prefix ? 64 : 128;
+
+ // And then try to retrieve them back.
+ ConstHostPtr from_hds1 = hdsptr_->get6(IOAddress("2001:db8::1"), len);
+ ConstHostPtr from_hds2 = hdsptr_->get6(IOAddress("2001:db8::2"), len);
+ ConstHostPtr from_hds3 = hdsptr_->get6(IOAddress("2001:db8::3"), len);
+ ConstHostPtr from_hds4 = hdsptr_->get6(IOAddress("2001:db8::4"), len);
+
+ // Make sure we got something back.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ ASSERT_TRUE(from_hds3);
+ ASSERT_TRUE(from_hds4);
+
+ // Then let's check that what we got seems correct.
+ compareHosts(host1, from_hds1);
+ compareHosts(host2, from_hds2);
+ compareHosts(host3, from_hds3);
+ compareHosts(host4, from_hds4);
+
+ // Ok, finally let's check that getting by a different address
+ // will not work.
+ EXPECT_FALSE(hdsptr_->get6(IOAddress("2001:db8::5"), len));
+}
+
+void GenericHostDataSourceTest::testAddDuplicate() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID,
+ true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+}
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef GENERIC_HOST_DATA_SOURCE_UNITTEST_H
+#define GENERIC_HOST_DATA_SOURCE_UNITTEST_H
+
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcp/classify.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test Fixture class with utility functions for HostDataSource backends
+///
+/// It contains utility functions for test purposes.
+/// All concrete HostDataSource test classes should be derived from it.
+class GenericHostDataSourceTest : public ::testing::Test {
+public:
+
+ /// @brief Universe (V4 or V6).
+ enum Universe {
+ V4,
+ V6
+ };
+
+ /// @brief Default constructor.
+ GenericHostDataSourceTest();
+
+ /// @brief Virtual destructor.
+ virtual ~GenericHostDataSourceTest();
+
+ /// @brief Creates a host reservation for specified IPv4 address.
+ ///
+ /// @param address IPv4 address to be set
+ /// @param hwaddr type of identifier (true = hwaddr, false = client-id)
+ ///
+ /// @return generated Host object
+ HostPtr initializeHost4(std::string address, bool hwaddr);
+
+ /// @brief Creates a host reservation for specified IPv6 address.
+ ///
+ /// @param address IPv6 address to be reserved
+ /// @param id type of identifier (ID_DUID or ID_HWADDR are supported)
+ /// @param prefix reservation type (true = prefix, false = address)
+ ///
+ /// @return generated Host object
+ HostPtr initializeHost6(std::string address, BaseHostDataSource::IdType id,
+ bool prefix);
+
+ /// @brief Generates a hardware address in text version.
+ ///
+ /// @return HW address in textual form acceptable by Host constructor
+ std::string generateHWAddr();
+
+ /// @brief Generates a hardware address in text version.
+ /// @return DUID in textual form acceptable by Host constructor
+ std::string generateDuid();
+
+ /// @brief Compares hardware addresses of the two hosts.
+ ///
+ /// This method compares two hwardware address and uses gtest
+ /// macros to signal unexpected (mismatch if expect_match is true;
+ /// match if expect_match is false) values.
+ ///
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @param expect_match true = HW addresses expected to be the same,
+ /// false = HW addresses expected to be different
+ void
+ compareHwaddrs(const ConstHostPtr& host1, const ConstHostPtr& host2,
+ bool expect_match);
+
+ /// @brief Compares DUIDs of the two hosts.
+ ///
+ /// This method compares two DUIDs (client-ids) and uses gtest
+ /// macros to signal unexpected (mismatch if expect_match is true;
+ /// match if expect_match is false) values.
+ ///
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @param expect_match true = DUIDs expected to be the same,
+ /// false = DUIDs expected to be different
+ void
+ compareDuids(const ConstHostPtr& host1, const ConstHostPtr& host2,
+ bool expect_match);
+
+ /// @brief Compares two hosts
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param host1 first host to compare
+ /// @param host2 second host to compare
+ void compareHosts(const ConstHostPtr& host1, const ConstHostPtr& host2);
+
+ /// @brief Compares two IPv6 reservation lists.
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param resv1 first IPv6 reservations list
+ /// @param resv2 second IPv6 reservations list
+ void compareReservations6(IPv6ResrvRange resv1, IPv6ResrvRange resv2,
+ bool expect_match);
+
+ /// @brief Compares two client classes
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param classes1 first list of client classes
+ /// @param classes2 second list of client classes
+ void compareClientClasses(const ClientClasses& classes1,
+ const ClientClasses& classes2);
+
+ /// @brief Pointer to the host data source
+ BaseHostDataSource* hdsptr_;
+
+ /// @brief Test that checks that simple host with IPv4 reservation
+ /// can be inserted and later retrieved.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param hwaddr true = use HW address as identifier, false = use client-id(DUID)
+ void testBasic4(bool hwaddr);
+
+ /// @brief Test inserts several hosts with unique IPv4 address and
+ /// checks that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param hwaddr true = use HW address as identifier, false = use client-id(DUID)
+ void testGetByIPv4(bool hwaddr);
+
+ /// @brief Test that hosts can be retrieved by hardware address.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet4ByHWAddr();
+
+ /// @brief Test that hosts can be retrieved by client-id
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet4ByClientId();
+
+ /// @brief Test that clients with stored HW address can't be retrieved
+ /// by DUID with the same value.
+ ///
+ /// Test procedure: add host reservation with hardware address X, try to retrieve
+ /// host by client-identifier X, verify that the reservation is not returned.
+ ///
+ /// Uses gtest macros to report failures.
+ void testHWAddrNotClientId();
+
+ /// @brief Test that clients with stored DUID can't be retrieved
+ /// by HW address of the same value.
+ ///
+ /// Test procedure: add host reservation with client identifier X, try to
+ /// retrieve host by hardware address X, verify that the reservation is not
+ /// returned.
+ ///
+ /// Uses gtest macros to report failures.
+ void testClientIdNotHWAddr();
+
+ /// @brief Test adds specified number of hosts with unique hostnames, then
+ /// retrives them and checks that the hostnames are set properly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param name hostname to be used (if n>1, numbers will be appended)
+ /// @param num number of hostnames to be added.
+ void testHostname(std::string name, int num);
+
+ /// @brief Test inserts multiple reservations for the same host for different
+ /// subnets and check that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param subnets number of subnets to test
+ /// @param hwaddr true = use HW address, false = use client-id
+ void testMultipleSubnets(int subnets, bool hwaddr);
+
+ /// @brief Test inserts several hosts with unique IPv6 addresses and
+ /// checks that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id type of the identifier to be used (HWAddr or DUID)
+ /// @param prefix true - reserve IPv6 prefix, false - reserve IPv6 address
+ void testGetByIPv6(BaseHostDataSource::IdType id, bool prefix);
+
+ /// @brief Test that hosts can be retrieved by hardware address.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet6ByHWAddr();
+
+ /// @brief Test that hosts can be retrieved by client-id
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet6ByClientId();
+
+ /// @brief Test if host reservations made for different IPv6 subnets
+ /// are handled correctly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param subnets number of subnets to test
+ /// @param id identifier type (ID_HWADDR or ID_DUID)
+ void testSubnetId6(int subnets, BaseHostDataSource::IdType id);
+
+ /// @brief Test if the duplicate host instances can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicate();
+
+ /// @brief Returns DUID with identical content as specified HW address
+ ///
+ /// This method does not have any sense in real life and is only useful
+ /// in testing corner cases in the database backends (e.g. whether the DB
+ /// is able to tell the difference between hwaddr and duid)
+ ///
+ /// @param hwaddr hardware address to be copied
+ /// @return duid with the same value as specified HW address
+ DuidPtr HWAddrToDuid(const HWAddrPtr& hwaddr);
+
+ /// @brief Returns HW address with identical content as specified DUID
+ ///
+ /// This method does not have any sense in real life and is only useful
+ /// in testing corner cases in the database backends (e.g. whether the DB
+ /// is able to tell the difference between hwaddr and duid)
+ ///
+ /// @param duid DUID to be copied
+ /// @return HW address with the same value as specified DUID
+ HWAddrPtr DuidToHWAddr(const DuidPtr& duid);
+};
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
+
+#endif
// Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
// time too large to store.
lease->valid_lft_ = 24*60*60;
- lease->cltt_ = LeaseMgr::MAX_DB_TIME;
+ lease->cltt_ = DatabaseConnection::MAX_DB_TIME;
// Insert should throw.
ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
// Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
// time too large to store.
lease->valid_lft_ = 24*60*60;
- lease->cltt_ = LeaseMgr::MAX_DB_TIME;
+ lease->cltt_ = DatabaseConnection::MAX_DB_TIME;
// Insert should throw.
ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/tests/test_utils.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/mysql_connection.h>
+#include <dhcpsrv/mysql_host_data_source.h>
+#include <dhcpsrv/tests/generic_host_data_source_unittest.h>
+#include <dhcpsrv/host_data_source_factory.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace std;
+
+namespace {
+
+// This holds statements to create and destroy the schema.
+#include "schema_mysql_copy.h"
+
+// Connection strings.
+// Database: keatest
+// Host: localhost
+// Username: keatest
+// Password: keatest
+const char* VALID_TYPE = "type=mysql";
+const char* INVALID_TYPE = "type=unknown";
+const char* VALID_NAME = "name=keatest";
+const char* INVALID_NAME = "name=invalidname";
+const char* VALID_HOST = "host=localhost";
+const char* INVALID_HOST = "host=invalidhost";
+const char* VALID_USER = "user=keatest";
+const char* INVALID_USER = "user=invaliduser";
+const char* VALID_PASSWORD = "password=keatest";
+const char* INVALID_PASSWORD = "password=invalid";
+
+// Given a combination of strings above, produce a connection string.
+string connectionString(const char* type, const char* name, const char* host,
+ const char* user, const char* password) {
+ const string space = " ";
+ string result = "";
+
+ if (type != NULL) {
+ result += string(type);
+ }
+ if (name != NULL) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(name);
+ }
+
+ if (host != NULL) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(host);
+ }
+
+ if (user != NULL) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(user);
+ }
+
+ if (password != NULL) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(password);
+ }
+
+ return (result);
+}
+
+// Return valid connection string
+string
+validConnectionString() {
+ return (connectionString(VALID_TYPE, VALID_NAME, VALID_HOST,
+ VALID_USER, VALID_PASSWORD));
+}
+
+// @brief Clear everything from the database
+//
+// There is no error checking in this code: if something fails, one of the
+// tests will (should) fall over.
+void destroySchema() {
+ MySqlHolder mysql;
+
+ // Open database
+ (void) mysql_real_connect(mysql, "localhost", "keatest",
+ "keatest", "keatest", 0, NULL, 0);
+
+ // Get rid of everything in it.
+ for (int i = 0; destroy_statement[i] != NULL; ++i) {
+ (void) mysql_query(mysql, destroy_statement[i]);
+ }
+}
+
+// @brief Create the Schema
+//
+// Creates all the tables in what is assumed to be an empty database.
+//
+// There is no error checking in this code: if it fails, one of the tests
+// will fall over.
+void createSchema() {
+ MySqlHolder mysql;
+
+ // Open database
+ (void) mysql_real_connect(mysql, "localhost", "keatest",
+ "keatest", "keatest", 0, NULL, 0);
+
+ // Execute creation statements.
+ for (int i = 0; create_statement[i] != NULL; ++i) {
+ ASSERT_EQ(0, mysql_query(mysql, create_statement[i]))
+ << "Failed on statement " << i << ": " << create_statement[i];
+ }
+}
+
+
+
+class MySqlHostDataSourceTest : public GenericHostDataSourceTest {
+public:
+ /// @brief Constructor
+ ///
+ /// Deletes everything from the database and opens it.
+ MySqlHostDataSourceTest() {
+
+ // Ensure schema is the correct one.
+ destroySchema();
+ createSchema();
+
+ // Connect to the database
+ try {
+ HostDataSourceFactory::create(validConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the MySQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ hdsptr_ = &(HostDataSourceFactory::instance());
+ }
+
+ /// @brief Destructor
+ ///
+ /// Rolls back all pending transactions. The deletion of myhdsptr_ will close
+ /// the database. Then reopen it and delete everything created by the test.
+ virtual ~MySqlHostDataSourceTest() {
+ hdsptr_->rollback();
+ HostDataSourceFactory::destroy();
+ destroySchema();
+ }
+
+ /// @brief Reopen the database
+ ///
+ /// Closes the database and re-open it. Anything committed should be
+ /// visible.
+ ///
+ /// Parameter is ignored for MySQL backend as the v4 and v6 leases share
+ /// the same database.
+ void reopen(Universe) {
+ HostDataSourceFactory::destroy();
+ HostDataSourceFactory::create(validConnectionString());
+ hdsptr_ = &(HostDataSourceFactory::instance());
+ }
+
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the MySqlHostDataSource can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// MySqlLeaseMgr test fixure set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+
+TEST(MySqlHostDataSource, OpenDatabase) {
+
+ // Schema needs to be created for the test to work.
+ destroySchema();
+ createSchema();
+
+ // Check that lease manager open the database opens correctly and tidy up.
+ // If it fails, print the error message.
+ try {
+ HostDataSourceFactory::create(validConnectionString());
+ EXPECT_NO_THROW((void) HostDataSourceFactory::instance());
+ HostDataSourceFactory::destroy();
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Check that attempting to get an instance of the lease manager when
+ // none is set throws an exception.
+ EXPECT_THROW(HostDataSourceFactory::instance(), NoHostDataSourceManager);
+
+ // Check that wrong specification of backend throws an exception.
+ // (This is really a check on LeaseMgrFactory, but is convenient to
+ // perform here.)
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ InvalidParameter);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ InvalidType);
+
+ // Check that invalid login data causes an exception.
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+ DbOpenError);
+
+ // Check for missing parameters
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ NoDatabaseName);
+
+ // Tidy up after the test
+ destroySchema();
+}
+
+/// @brief Check conversion functions
+///
+/// The server works using cltt and valid_filetime. In the database, the
+/// information is stored as expire_time and valid-lifetime, which are
+/// related by
+///
+/// expire_time = cltt + valid_lifetime
+///
+/// This test checks that the conversion is correct. It does not check that the
+/// data is entered into the database correctly, only that the MYSQL_TIME
+/// structure used for the entry is correctly set up.
+TEST(MySqlConnection, checkTimeConversion) {
+ const time_t cltt = time(NULL);
+ const uint32_t valid_lft = 86400; // 1 day
+ struct tm tm_expire;
+ MYSQL_TIME mysql_expire;
+
+ // Work out what the broken-down time will be for one day
+ // after the current time.
+ time_t expire_time = cltt + valid_lft;
+ (void) localtime_r(&expire_time, &tm_expire);
+
+ // Convert to the database time
+ MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
+
+ // Are the times the same?
+ EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
+ EXPECT_EQ(tm_expire.tm_mon + 1, mysql_expire.month);
+ EXPECT_EQ(tm_expire.tm_mday, mysql_expire.day);
+ EXPECT_EQ(tm_expire.tm_hour, mysql_expire.hour);
+ EXPECT_EQ(tm_expire.tm_min, mysql_expire.minute);
+ EXPECT_EQ(tm_expire.tm_sec, mysql_expire.second);
+ EXPECT_EQ(0, mysql_expire.second_part);
+ EXPECT_EQ(0, mysql_expire.neg);
+
+ // Convert back
+ time_t converted_cltt = 0;
+ MySqlConnection::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
+ EXPECT_EQ(cltt, converted_cltt);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses hw address as identifier.
+TEST_F(MySqlHostDataSourceTest, basic4HWAddr) {
+ testBasic4(true);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses client-id (DUID) as identifier.
+TEST_F(MySqlHostDataSourceTest, basic4ClientId) {
+ testBasic4(false);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(MySqlHostDataSourceTest, getByIPv4HWaddr) {
+ testGetByIPv4(true);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(MySqlHostDataSourceTest, getByIPv4ClientId) {
+ testGetByIPv4(false);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(MySqlHostDataSourceTest, get4ByHWaddr) {
+ testGet4ByHWAddr();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// client identifier.
+TEST_F(MySqlHostDataSourceTest, get4ByClientId) {
+ testGet4ByClientId();
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId1) {
+ testHWAddrNotClientId();
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId2) {
+ testClientIdNotHWAddr();
+}
+
+// Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(MySqlHostDataSourceTest, hostnameFQDN) {
+ testHostname("foo.example.org", 1);
+}
+
+// Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+// retrieved.
+TEST_F(MySqlHostDataSourceTest, hostnameFQDN100) {
+ testHostname("foo.example.org", 100);
+}
+
+// Test verifies if a host without any hostname specified can be stored and later
+// retrieved.
+TEST_F(MySqlHostDataSourceTest, noHostname) {
+ testHostname("", 1);
+}
+
+// Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId1) {
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with hardware address X, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+// Test verifies if the hardware or client-id query can match client-id.
+TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with client identifier Y, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+// Test verifies that host with IPv6 address and DUID can be added and
+// later retrieved by IPv6 address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6AddrWithDuid) {
+ /// @todo: Uncomment when IPv6 support (4212) is implemented.
+ testGetByIPv6(BaseHostDataSource::ID_DUID, false);
+}
+
+// Test verifies that host with IPv6 address and HWAddr can be added and
+// later retrieved by IPv6 address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6AddrWithHWAddr) {
+ /// @todo: Uncomment when IPv6 support (4212) is implemented.
+ testGetByIPv6(BaseHostDataSource::ID_HWADDR, false);
+}
+
+// Test verifies that host with IPv6 prefix and DUID can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6PrefixWithDuid) {
+ /// @todo: Uncomment when IPv6 support (4212) is implemented.
+ testGetByIPv6(BaseHostDataSource::ID_DUID, true);
+}
+
+// Test verifies that host with IPv6 prefix and HWAddr can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6PrefixWithHWaddr) {
+ /// @todo: Uncomment when IPv6 support (4212) is implemented.
+ testGetByIPv6(BaseHostDataSource::ID_HWADDR, true);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6ByHWaddr) {
+ /// @todo: Uncomment when IPv6 support (4212) is implemented.
+ testGet6ByHWAddr();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// client identifier.
+TEST_F(MySqlHostDataSourceTest, DISABLED_get6ByClientId) {
+ /// @todo: Uncomment when IPv6 support (4212) is implemented.
+ testGet6ByClientId();
+}
+
+// Test verifies if a host reservation can be stored with both IPv6 address and
+// prefix.
+TEST_F(MySqlHostDataSourceTest, DISABLED_addr6AndPrefix) {
+ /// @todo: Implement this test as part of #4212.
+
+ /// @todo: Add host reservation with an IPv6 address and IPv6 prefix,
+ /// retrieve it and verify that both v6 address and prefix are retrieved
+ /// correctly.
+}
+
+// Test verifies if multiple client classes for IPv4 can be stored.
+TEST_F(MySqlHostDataSourceTest, DISABLED_multipleClientClasses4) {
+ /// @todo: Implement this test as part of #4213.
+
+ /// Add host reservation with a multiple v4 client-classes, retrieve it and
+ /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for IPv6 can be stored.
+TEST_F(MySqlHostDataSourceTest, DISABLED_multipleClientClasses6) {
+ /// @todo: Implement this test as part of #4213.
+
+ /// Add host reservation with a multiple v6 client-classes, retrieve it and
+ /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for both IPv4 and IPv6 can be stored.
+TEST_F(MySqlHostDataSourceTest, DISABLED_multipleClientClassesBoth) {
+ /// @todo: Implement this test as part of #4213..
+
+ /// Add host reservation with a multiple v4 and v6 client-classes, retrieve
+ /// it and make sure that all client classes are retrieved properly. Also,
+ /// check that the classes are not confused.
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same hardware address). The test logic is as follows:
+// Insert 10 host reservations for a given physical host (the same
+// hardware address), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them all correctly.
+TEST_F(MySqlHostDataSourceTest, multipleSubnetsHWAddr) {
+ testMultipleSubnets(10, true);
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same client identifier). The test logic is as follows:
+//
+// Insert 10 host reservations for a given physical host (the same
+// client-identifier), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them correctly.
+TEST_F(MySqlHostDataSourceTest, multipleSubnetsClientId) {
+ testMultipleSubnets(10, false);
+}
+
+// Test if host reservations made for different IPv6 subnets are handled correctly.
+// The test logic is as follows:
+//
+// Insert 10 host reservations for different subnets. Make sure that
+// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(MySqlHostDataSourceTest, subnetId6) {
+ testSubnetId6(10, BaseHostDataSource::ID_HWADDR);
+}
+
+// Test if the duplicate host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+TEST_F(MySqlHostDataSourceTest, addDuplicate) {
+ testAddDuplicate();
+}
+
+}; // Of anonymous namespace
#include <asiolink/io_address.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/mysql_connection.h>
#include <dhcpsrv/mysql_lease_mgr.h>
#include <dhcpsrv/tests/test_utils.h>
#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
(void) localtime_r(&expire_time, &tm_expire);
// Convert to the database time
- MySqlLeaseMgr::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
+ MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
// Are the times the same?
EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
// Convert back
time_t converted_cltt = 0;
- MySqlLeaseMgr::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
+ MySqlConnection::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
EXPECT_EQ(cltt, converted_cltt);
}
"DROP TABLE hosts",
"DROP TABLE dhcp4_options",
"DROP TABLE dhcp6_options",
+
+ "DROP TRIGGER host_BDEL",
NULL
};