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
if HAVE_PGSQL
libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
namespace isc {
namespace dhcp {
+const time_t DataSource::MAX_DB_TIME = 2147483647;
+
std::string
DatabaseConnection::getParameter(const std::string& name) const {
ParameterMap::const_iterator param = parameters_.find(name);
/// @ref BaseHostDataSource derived classes.
class DatabaseConnection : public boost::noncopyable {
public:
+
+ /// @brief Defines maximum value for time that can be reliably stored.
+ // If I'm still alive I'll be too old to care. You fix it.
+ static const time_t MAX_DB_TIME;
+
/// @brief Database configuration parameter map
typedef std::map<std::string, std::string> ParameterMap;
--- /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/mysql_host_data_source.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <map>
+#include <sstream>
+#include <utility>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+
+boost::scoped_ptr<BaseHostDataSource>&
+HostDataSourceFactory::getHostDataSourcePtr() {
+ static boost::scoped_ptr<BaseHostDataSource> hostDataSourcePtr;
+ return (hostDataSourcePtr);
+}
+
+void
+HostDataSourceFactory::create(const std::string& dbaccess) {
+ const std::string type = "type";
+
+ // Parse the access string and create a redacted string for logging.
+ DataSource::ParameterMap parameters = DataSource::parse(dbaccess);
+ std::string redacted = DataSource::redactedAccessString(parameters);
+
+ // Is "type" present?
+ if (parameters.find(type) == parameters.end()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
+ isc_throw(InvalidParameter, "Database configuration parameters do not "
+ "contain the 'type' keyword");
+ }
+
+
+ // Yes, check what it is.
+#ifdef HAVE_MYSQL
+ if (parameters[type] == string("mysql")) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_DB).arg(redacted);
+ getHostDataSourcePtr().reset(new MySqlHostDataSource(parameters));
+ return;
+ }
+#endif
+#ifdef HAVE_PGSQL
+ if (parameters[type] == string("postgresql")) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_DB).arg(redacted);
+ // set pgsql data source here, when it will be featured
+ return;
+ }
+#endif
+
+ // Get here on no match
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+ isc_throw(InvalidType, "Database access parameter 'type' does "
+ "not specify a supported database backend");
+}
+
+void
+HostDataSourceFactory::destroy() {
+ // Destroy current host data source instance. This is a no-op if no host
+ // data source is available.
+ if (getHostDataSourcePtr()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CLOSE_DB);
+ }
+ getHostDataSourcePtr().reset();
+}
+
+BaseHostDataSource&
+HostDataSourceFactory::instance() {
+ BaseHostDataSource* hdsptr = getHostDataSourcePtr().get();
+ if (hdsptr == NULL) {
+ isc_throw(NoHostDataSourceManager, "no current host data source instance is available");
+ }
+ return (*hdsptr);
+}
+
+}; // namespace dhcp
+}; // namespace isc
--- /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/data_source.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Invalid type exception
+///
+/// Thrown when the factory doesn't recognise the type of the backend.
+class InvalidType : public Exception {
+public:
+ InvalidType(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief No host data source instance exception
+///
+/// Thrown if an attempt is made to get a reference to the current
+/// host data source instance and none is currently available.
+class NoHostDataSourceManager : public Exception {
+public:
+ NoHostDataSourceManager(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+class HostDataSourceFactory {
+public:
+ /// @brief todo
+ ///
+ static void create(const std::string& dbaccess);
+
+ /// @brief Destroy host data source instance
+ ///
+ /// todo
+ static void destroy();
+
+ /// @brief Return current host data source instance
+ /// todo
+ static BaseHostDataSource& instance();
+
+private:
+ /// @brief Hold pointer to host data source instance
+ ///
+ /// todo
+ static boost::scoped_ptr<BaseHostDataSource>& getHostDataSourcePtr();
+
+};
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/hosts_log.h>
+#include <dhcpsrv/mysql_host_data_source.h>
namespace {
HostMgr::create(const std::string&) {
getHostMgrPtr().reset(new HostMgr());
+ //alternate_source.reset(new MySqlHostDataSource());
/// @todo Initialize alternate_source here, using the parameter.
/// For example: alternate_source.reset(new MysqlHostDataSource(access)).
}
return (host);
}
-
void
HostMgr::add(const HostPtr&) {
isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/mysql_host_data_source.h>
#include <dhcpsrv/host.h>
#include <dhcpsrv/subnet_id.h>
#include <boost/noncopyable.hpp>
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 {
/// of those classes for details.
class LeaseMgr {
public:
- /// @brief Defines maximum value for time that can be reliably stored.
- // If I'm still alive I'll be too old to care. You fix it.
- static const time_t MAX_DB_TIME;
-
/// @brief Constructor
///
LeaseMgr() : io_service_(new asiolink::IOService())
statements_.clear();
text_statements_.clear();
+// Time conversion methods.
+//
+// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
+// from the current timezone to UTC for storage, and from UTC to the current
+// timezone for retrieval.
+//
+// This causes no problems providing that:
+// a) cltt is given in local time
+// b) We let the system take care of timezone conversion when converting
+// from a time read from the database into a local time.
+
+void
+MySqlConnection::convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime,
+ MYSQL_TIME& expire) {
+
+ // Calculate expiry time. Store it in the 64-bit value so as we can detect
+ // overflows.
+ int64_t expire_time_64 = static_cast<int64_t>(cltt) +
+ static_cast<int64_t>(valid_lifetime);
+
+ // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
+ // beyond the max value of int32_t.
+ if (expire_time_64 > DataSource::MAX_DB_TIME) {
+ isc_throw(BadValue, "Time value is too large: " << expire_time_64);
+ }
+
+ const time_t expire_time = static_cast<const time_t>(expire_time_64);
+
+ // Convert to broken-out time
+ struct tm expire_tm;
+ (void) localtime_r(&expire_time, &expire_tm);
+
+ // Place in output expire structure.
+ expire.year = expire_tm.tm_year + 1900;
+ expire.month = expire_tm.tm_mon + 1; // Note different base
+ expire.day = expire_tm.tm_mday;
+ expire.hour = expire_tm.tm_hour;
+ expire.minute = expire_tm.tm_min;
+ expire.second = expire_tm.tm_sec;
+ expire.second_part = 0; // No fractional seconds
+ expire.neg = my_bool(0); // Not negative
+}
+
+void
+MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime, time_t& cltt) {
+
+ // Copy across fields from MYSQL_TIME structure.
+ struct tm expire_tm;
+ memset(&expire_tm, 0, sizeof(expire_tm));
+
+ expire_tm.tm_year = expire.year - 1900;
+ expire_tm.tm_mon = expire.month - 1;
+ expire_tm.tm_mday = expire.day;
+ expire_tm.tm_hour = expire.hour;
+ expire_tm.tm_min = expire.minute;
+ expire_tm.tm_sec = expire.second;
+ expire_tm.tm_isdst = -1; // Let the system work out about DST
+
+ // Convert to local time
+ cltt = mktime(&expire_tm) - valid_lifetime;
}
} // namespace isc::dhcp
/// @throw DbOpenError Error opening the database
void openDatabase();
+ ///@{
+ /// The following methods are used to convert between times and time
+ /// intervals stored in the Lease object, and the times stored in the
+ /// database. The reason for the difference is because in the DHCP server,
+ /// the cltt (Client Time Since Last Transmission) is the natural data; in
+ /// the lease file - which may be read by the user - it is the expiry time
+ /// of the lease.
+
+ /// @brief Convert Lease Time to Database Times
+ ///
+ /// Within the DHCP servers, times are stored as client last transmit time
+ /// and valid lifetime. In the database, the information is stored as
+ /// valid lifetime and "expire" (time of expiry of the lease). They are
+ /// related by the equation:
+ ///
+ /// - expire = client last transmit time + valid lifetime
+ ///
+ /// This method converts from the times in the lease object into times
+ /// able to be added to the database.
+ ///
+ /// @param cltt Client last transmit time
+ /// @param valid_lifetime Valid lifetime
+ /// @param expire Reference to MYSQL_TIME object where the expiry time of
+ /// the lease will be put.
+ ///
+ /// @throw isc::BadValue if the sum of the calculated expiration time is
+ /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
+ static
+ void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
+ MYSQL_TIME& expire);
+
+ /// @brief Convert Database Time to Lease Times
+ ///
+ /// Within the database, time is stored as "expire" (time of expiry of the
+ /// lease) and valid lifetime. In the DHCP server, the information is
+ /// stored client last transmit time and valid lifetime. These are related
+ /// by the equation:
+ ///
+ /// - client last transmit time = expire - valid_lifetime
+ ///
+ /// This method converts from the times in the database into times
+ /// able to be inserted into the lease object.
+ ///
+ /// @param expire Reference to MYSQL_TIME object from where the expiry
+ /// time of the lease is taken.
+ /// @param valid_lifetime lifetime of the lease.
+ /// @param cltt Reference to location where client last transmit time
+ /// is put.
+ static
+ void convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime, time_t& cltt);
+ ///@}
+
/// @brief Prepared statements
///
/// This field is public, because it is used heavily from MySqlConnection
--- /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 <boost/static_assert.hpp>
+#include <mysqld_error.h>
+
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace {
+/// @brief Maximum length of classes stored in a dhcp4/6_client_classes field
+///
+const size_t CLIENT_CLASSES_MAX_LEN = 255;
+
+/// @brief Maximum length of the hostname stored in DNS.
+///
+/// This length is restricted by the length of the domain-name carried
+/// in the Client FQDN %Option (see RFC4702 and RFC4704).
+const size_t HOSTNAME_MAX_LEN = 255;
+
+/// @brief Maximum length of dhcp identifier field
+///
+const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
+
+TaggedStatement tagged_statements[] = {
+ {MySqlHostDataSource::INSERT_HOST,
+ "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, "
+ "hostname, dhcp4_client_classes, dhcp6_client_classes) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
+ {MySqlHostDataSource::GET_HOST_HWADDR_DUID,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+ "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE dhcp_identifier = ?"},
+ {MySqlHostDataSource::GET_HOST_ADDR,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+ "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE ipv4_address = ?"},
+ {MySqlHostDataSource::GET_HOST_SUBID4_DHCPID,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+ "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE dhcp4_subnet_id = ? AND dhcp_identifier = ?"},
+ {MySqlHostDataSource::GET_HOST_SUBID6_DHCPID,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+ "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE dhcp6_subnet_id = ? AND dhcp_identifier = ?"},
+ {MySqlHostDataSource::GET_HOST_SUBID_ADDR,
+ "SELECT host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+ "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts "
+ "WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
+ {MySqlHostDataSource::GET_HOST_PREFIX,
+ "SELECT h.host_id, dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, "
+ "dhcp6_subnet_id, ipv4_address, hostname, dhcp4_client_classes, dhcp6_client_classes "
+ "FROM hosts h "
+ "LEFT JOIN ipv6_reservations r ON h.host_id = r.host_id "
+ "WHERE r.prefix_len = ? AND r.address = ?"},
+ {MySqlHostDataSource::GET_VERSION,
+ "SELECT version, minor FROM schema_version"},
+ {MySqlHostDataSource::NUM_STATEMENTS, NULL}
+};
+
+};
+
+namespace isc {
+namespace dhcp {
+
+class MySqlHostReservationExchange {
+ /// @brief Set number of database columns for this host structure
+ static const size_t HOST_COLUMNS = 9;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// The initialization of the variables here is only to satisfy cppcheck -
+ /// all variables are initialized/set in the methods before they are used.
+ MySqlHostReservationExchange() : host_id_(0), dhcp_identifier_length_(0),
+ dhcp_identifier_type_(0), dhcp4_subnet_id_(0),
+ dhcp6_subnet_id_(0), ipv4_address_(0),
+ hostname_length_(0), dhcp4_client_classes_length_(0),
+ dhcp6_client_classes_length_(0), dhcp4_subnet_id_null_(MLM_FALSE),
+ dhcp6_subnet_id_null_(MLM_FALSE), ipv4_address_null_(MLM_FALSE),
+ hostname_null_(MLM_FALSE), dhcp4_client_classes_null_(MLM_FALSE),
+ dhcp6_client_classes_null_(MLM_FALSE){
+
+ memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
+ memset(hostname_, 0, sizeof(hostname_));
+ memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
+ memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
+ std::fill(&error_[0], &error_[HOST_COLUMNS], MLM_FALSE);
+
+ // Set the column names (for error messages)
+ columns_[0] = "host_id";
+ columns_[1] = "dhcp_identifier";
+ columns_[2] = "dhcp_identifier_type";
+ columns_[3] = "dhcp4_subnet_id";
+ columns_[4] = "dhcp6_subnet_id";
+ columns_[5] = "ipv4_address";
+ columns_[6] = "hostname";
+ columns_[7] = "dhcp4_client_classes";
+ columns_[8] = "dhcp6_client_classes";
+ BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
+ }
+
+ /// @brief Set error indicators
+ ///
+ /// Sets the error indicator for each of the MYSQL_BIND elements. It points
+ /// the "error" field within an element of the input array to the
+ /// corresponding element of the passed error array.
+ ///
+ /// @param bind Array of BIND elements
+ /// @param error Array of error elements. If there is an error in getting
+ /// data associated with one of the "bind" elements, the
+ /// corresponding element in the error array is set to MLM_TRUE.
+ /// @param count Size of each of the arrays.
+ static void setErrorIndicators(MYSQL_BIND* bind, my_bool* error,
+ size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ error[i] = MLM_FALSE;
+ bind[i].error = reinterpret_cast<char*>(&error[i]);
+ }
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @param error Array of error elements. An element is set to MLM_TRUE
+ /// if the corresponding column in the database is the source of
+ /// the error.
+ /// @param names Array of column names, the same size as the error array.
+ /// @param count Size of each of the arrays.
+ static std::string getColumnsInError(my_bool* error, std::string* names,
+ size_t count) {
+ std::string result = "";
+
+ // Accumulate list of column names
+ for (size_t i = 0; i < count; ++i) {
+ if (error[i] == MLM_TRUE) {
+ if (!result.empty()) {
+ result += ", ";
+ }
+ result += names[i];
+ }
+ }
+
+ if (result.empty()) {
+ result = "(None)";
+ }
+
+ return (result);
+ }
+
+ /// @brief Create MYSQL_BIND objects for Host Pointer
+ ///
+ /// Fills in the MYSQL_BIND array for sending data in the Host object to
+ /// the database.
+ ///
+ /// @param host Host object to be added to the database. None of the
+ /// fields in the host reservation are modified - the host data is only read.
+ ///
+ /// @return Vector of MySQL BIND objects representing the data to be added.
+ std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
+
+ // Store host object to ensure it remains valid.
+ host_ = host;
+
+ // Initialize prior to constructing the array of MYSQL_BIND structures.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ memset(bind_, 0, sizeof(bind_));
+
+ // Set up the structures for the various components of the host structure.
+
+ try {
+ // host_id : INT UNSIGNED NOT NULL
+ // The host_id is auto_incremented by MySQL database,
+ // so we need to pass the NULL value
+ host_id_ = static_cast<uint32_t>(NULL);
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[0].is_unsigned = MLM_TRUE;
+ // bind_[0].is_null = &MLM_TRUE; //compile problems here!
+
+ // dhcp_identifier : VARBINARY(128) NOT NULL
+ // Check which of the identifiers is used and set values accordingly
+ if (host_->getDuid()) {
+ dhcp_identifier_length_ = host_->getDuid()->getDuid().size();
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(host_->getDuid()->getDuid()[0]);
+ bind_[1].buffer_length = dhcp_identifier_length_;
+ bind_[1].length = &dhcp_identifier_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+ } else if (host_->getHWAddress()){
+ dhcp_identifier_length_ = host_->getHWAddress()->hwaddr_.size();
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(&(host_->getHWAddress()->hwaddr_[0]));
+ bind_[1].buffer_length = dhcp_identifier_length_;
+ bind_[1].length = &dhcp_identifier_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+ }
+
+ // dhcp_identifier_type : TINYINT NOT NULL
+ // Check which of the identifier types is used and set values accordingly
+ if (host_->getHWAddress()) {
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(0); // 0 = IDENT_HWADDR
+ bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+ } else if (host_->getDuid()) {
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(1); // 1 = IDENT_DUID
+ bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+ }
+
+ // dhcp4_subnet_id : INT UNSIGNED NULL
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(host_->getIPv4SubnetID());
+ bind_[3].is_unsigned = MLM_TRUE;
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // dhcp6_subnet_id : INT UNSIGNED NULL
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(host_->getIPv6SubnetID());
+ bind_[4].is_unsigned = MLM_TRUE;
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // ipv4_address : INT UNSIGNED NULL
+ // The address in the Host structure is an IOAddress object. Convert
+ // this to an integer for storage.
+ ipv4_address_ = static_cast<uint32_t>(host_->getIPv4Reservation());
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hostname : VARCHAR(255) NULL
+ bind_[6].buffer_type = MYSQL_TYPE_STRING;
+ bind_[6].buffer = reinterpret_cast<char*>(host_->getHostname()[0]);
+ bind_[6].buffer_length = host_->getHostname().length();
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // dhcp4_client_classes : VARCHAR(255) NULL
+ // TODO this should probably be converted in some way... Don't know how yet,
+ // also size method might be wrong
+ bind_[7].buffer_type = MYSQL_TYPE_STRING;
+ //bind_[7].buffer = reinterpret_cast<char*>(host_->getClientClasses4());
+ bind_[7].buffer_length = sizeof(host_->getClientClasses4());
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // dhcp6_client_classes : VARCHAR(255) NULL
+ bind_[8].buffer_type = MYSQL_TYPE_STRING;
+ //bind_[8].buffer = reinterpret_cast<char*>(host_->getClientClasses6());
+ bind_[8].buffer_length = sizeof(host_->getClientClasses6());
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from Host: "
+ << host_->getHostname() << ", reason: " << ex.what());
+ }
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[HOST_COLUMNS]));
+ }
+
+ /// @brief Create BIND array to receive data
+ ///
+ /// Creates a MYSQL_BIND array to receive Lease4 data from the database.
+ /// After data is successfully received, getLeaseData() can be used to copy
+ /// it to a Lease6 object.
+ ///
+ std::vector<MYSQL_BIND> createBindForReceive() {
+ // Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ memset(bind_, 0, sizeof(bind_));
+
+ // host_id : INT UNSIGNED NOT NULL
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[0].is_unsigned = MLM_TRUE;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // dhcp_identifier : VARBINARY(128) NOT NULL
+ dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
+ bind_[1].buffer_length = dhcp_identifier_length_;
+ bind_[1].length = &dhcp_identifier_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // dhcp_identifier_type : TINYINT NOT NULL
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+ bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // dhcp4_subnet_id : INT UNSIGNED NULL
+ dhcp4_subnet_id_null_ = MLM_FALSE;
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
+ bind_[3].is_unsigned = MLM_TRUE;
+ bind_[3].is_null = &dhcp4_subnet_id_null_;
+
+ // dhcp6_subnet_id : INT UNSIGNED NULL
+ dhcp6_subnet_id_null_ = MLM_FALSE;
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
+ bind_[4].is_unsigned = MLM_TRUE;
+ bind_[4].is_null = &dhcp6_subnet_id_null_;
+
+ // ipv4_address : INT UNSIGNED NULL
+ ipv4_address_null_ = MLM_FALSE;
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ bind_[5].is_null = &ipv4_address_null_;
+
+ // hostname : VARCHAR(255) NULL
+ hostname_null_ = MLM_FALSE;
+ hostname_length_ = sizeof(hostname_);
+ bind_[6].buffer_type = MYSQL_TYPE_STRING;
+ bind_[6].buffer = reinterpret_cast<char*>(hostname_);
+ bind_[6].buffer_length = hostname_length_;
+ bind_[6].length = &hostname_length_;
+ bind_[6].is_null = &hostname_null_;
+
+ // dhcp4_client_classes : VARCHAR(255) NULL
+ dhcp4_client_classes_null_ = MLM_FALSE;
+ dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
+ bind_[7].buffer_type = MYSQL_TYPE_STRING;
+ bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
+ bind_[7].buffer_length = dhcp4_client_classes_length_;
+ bind_[7].length = &dhcp4_client_classes_length_;
+ bind_[7].is_null = &dhcp4_client_classes_null_;
+
+ // dhcp6_client_classes : VARCHAR(255) NULL
+ dhcp6_client_classes_null_ = MLM_FALSE;
+ dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
+ bind_[8].buffer_type = MYSQL_TYPE_STRING;
+ bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
+ bind_[8].buffer_length = dhcp6_client_classes_length_;
+ bind_[8].length = &dhcp6_client_classes_length_;
+ bind_[8].is_null = &dhcp6_client_classes_null_;
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, HOST_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[HOST_COLUMNS]));
+
+ }
+
+ /// @brief Copy Received Data into Host Object
+ ///
+ /// Called after the MYSQL_BIND array created by createBindForReceive()
+ /// has been used, this copies data from the internal member variables
+ /// into a Host objec.
+ ///
+ /// @return Host Pointer to a Lease6 object holding the relevant data.
+ HostPtr getHostData(){
+
+ // Set the dhcp identifier type in a variable of the appropriate data type, which
+ // has been initialized with an arbitrary (but valid) value.
+ Host::IdentifierType type = Host::IDENT_HWADDR;
+
+ switch (dhcp_identifier_type_) {
+ case 0:
+ type = Host::IDENT_HWADDR;
+ break;
+
+ case 1:
+ type = Host::IDENT_DUID;
+ break;
+
+ default:
+ isc_throw(BadValue, "invalid dhcp identifier type returned (" <<
+ static_cast<int>(dhcp_identifier_type_) << ") for host with "
+ << "identifier " << reinterpret_cast<char*>(dhcp_identifier_[0]) << ". Only 0 or 1 are allowed.");
+ }
+
+ // Set subnets ID's if they are given, if not, leave an empty object
+ SubnetID ipv4_subnet_id = static_cast<SubnetID>(NULL); // This is probably wrong way to do it...
+ if (dhcp4_subnet_id_null_ == MLM_FALSE)
+ ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
+
+ SubnetID ipv6_subnet_id = static_cast<SubnetID>(NULL);
+ if (dhcp6_subnet_id_null_ == MLM_FALSE)
+ ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
+
+ // Set IPv4 address reservation if it was given, if not, set IPv4 zero address
+ asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+ if (ipv4_address_null_ == MLM_FALSE)
+ //ipv4_reservation = static_cast<asiolink::IOAddress>(ipv4_address_);
+ ipv4_reservation = asiolink::IOAddress(ipv4_address_);
+
+ // Set hostname if it was given, if not lease empty string
+ std::string hostname;
+ if (hostname_null_ == MLM_FALSE)
+ hostname = std::string (hostname_, hostname_ + hostname_length_);
+
+ // Not sure if this is necessary yet, since Host constructor takes strings, not ClientClasses objects
+ // Set client classes if they were given, if not, set empty client classes
+ ClientClasses dhcp4_client_classes;
+ if (dhcp4_client_classes_null_ == MLM_FALSE)
+ dhcp4_client_classes.insert(dhcp4_client_classes_);
+
+ ClientClasses dhcp6_client_classes;
+ if (dhcp6_client_classes_null_ == MLM_FALSE)
+ dhcp6_client_classes.insert(dhcp6_client_classes_);
+
+ // Returning Host object with set fields
+ return (HostPtr(new Host(dhcp_identifier_buffer_,
+ dhcp_identifier_length_,
+ type,
+ ipv4_subnet_id,
+ ipv6_subnet_id,
+ ipv4_reservation,
+ hostname,
+ dhcp4_client_classes_,
+ dhcp6_client_classes_)));
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @return Comma-separated list of columns in error, or the string
+ /// "(None)".
+ std::string getErrorColumns() {
+ return (getColumnsInError(error_, columns_, HOST_COLUMNS));
+ }
+
+private:
+ //std::string addr6_; //< String form of address
+ //char addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1]; // array form of V6 address
+ uint32_t host_id_; // Unique identifier of the host
+ std::vector<uint8_t> dhcp_identifier_; // DHCP identifier, can be HW address (0) or DUID (1)
+ uint8_t dhcp_identifier_buffer_[DHCP_IDENTIFIER_MAX_LEN]; // Buffer form of dhcp identifier
+ size_t dhcp_identifier_length_; // Length of the dhcp identifier
+ uint8_t dhcp_identifier_type_; // Type of the dhcp_identifier (HW address or DUID)
+ uint32_t dhcp4_subnet_id_; // Subnet identifier for the DHCPv4 client.
+ uint32_t dhcp6_subnet_id_; // Subnet identifier for the DHCPv6 client.
+ uint32_t ipv4_address_; // Reserved IPv4 address.
+ IPv6ResrvCollection ipv6_reservations_; // Collection of IPv6 reservations for the host.
+ char hostname_[HOSTNAME_MAX_LEN];// Name reserved for the host.
+ unsigned long hostname_length_; // hostname length
+ char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN]; // Collection of classes associated with a DHCPv4 client.
+ unsigned long dhcp4_client_classes_length_;// dhcp4_client_classes length
+ char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN]; // Collection of classes associated with a DHCPv6 client.
+ unsigned long dhcp6_client_classes_length_;// dhcp6_client_classes length
+ HWAddrPtr hw_address_; // Pointer to the hardware address associated with the reservations for the host.
+ DuidPtr duid_; // Pointer to the DUID associated with the reservations for the host.
+
+ // NULL flags for subnets id, ipv4 address, hostname and client classes
+ my_bool dhcp4_subnet_id_null_;
+ my_bool dhcp6_subnet_id_null_;
+ my_bool ipv4_address_null_;
+ my_bool hostname_null_;
+ my_bool dhcp4_client_classes_null_;
+ my_bool dhcp6_client_classes_null_;
+
+ MYSQL_BIND bind_[HOST_COLUMNS];
+ std::string columns_[HOST_COLUMNS]; // Column names
+ my_bool error_[HOST_COLUMNS]; // Error array
+ HostPtr host_; // Pointer to Host object
+};
+
+// MySqlHostDataSource Constructor and Destructor
+
+MySqlHostDataSource::MySqlHostDataSource(
+ const MySqlConnection::ParameterMap& parameters) :
+ MySqlConnection(parameters) {
+
+ // Open the database.
+ openDatabase();
+
+ // Enable autocommit. To avoid a flush to disk on every commit, the global
+ // parameter innodb_flush_log_at_trx_commit should be set to 2. This will
+ // cause the changes to be written to the log, but flushed to disk in the
+ // background every second. Setting the parameter to that value will speed
+ // up the system, but at the risk of losing data if the system crashes.
+ my_bool result = mysql_autocommit(mysql_, 1);
+ if (result != 0) {
+ isc_throw(DbOperationError, mysql_error(mysql_));
+ }
+
+ // Prepare all statements likely to be used.
+ prepareStatements(tagged_statements, MySqlHostDataSource::NUM_STATEMENTS);
+
+ // Create the exchange objects for use in exchanging data between the
+ // program and the database.
+ hostExchange_.reset(new MySqlHostReservationExchange());
+}
+
+MySqlHostDataSource::~MySqlHostDataSource() {
+ // Free up the prepared statements, ignoring errors. (What would we do
+ // about them? We're destroying this object and are not really concerned
+ // with errors on a database connection that is about to go away.)
+ for (int i = 0; i < statements_.size(); ++i) {
+ if (statements_[i] != NULL) {
+ (void) mysql_stmt_close(statements_[i]);
+ statements_[i] = NULL;
+ }
+ }
+
+ // There is no need to close the database in this destructor: it is
+ // closed in the destructor of the mysql_ member variable.
+}
+
+// Time conversion methods.
+//
+// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
+// from the current timezone to UTC for storage, and from UTC to the current
+// timezone for retrieval.
+//
+// This causes no problems providing that:
+// a) cltt is given in local time
+// b) We let the system take care of timezone conversion when converting
+// from a time read from the database into a local time.
+
+void
+MySqlHostDataSource::add(const HostPtr& host) {
+ // Create the MYSQL_BIND array for the host
+ std::vector<MYSQL_BIND> bind = hostExchange_->createBindForSend(host);
+
+ // ... and drop to add code.
+ if (!addHost(INSERT_HOST, bind)) {
+ // Throw an failure exception here
+ }
+}
+
+bool
+MySqlHostDataSource::addHost(StatementIndex stindex,
+ std::vector<MYSQL_BIND>& bind) {
+
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[stindex], &bind[0]);
+ checkError(status, stindex, "unable to bind parameters");
+
+ // Execute the statement
+ status = mysql_stmt_execute(statements_[stindex]);
+ if (status != 0) {
+
+ // Failure: check for the special case of duplicate entry. If this is
+ // the case, we return false to indicate that the row was not added.
+ // Otherwise we throw an exception.
+ if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
+ return (false);
+ }
+ checkError(status, stindex, "unable to execute");
+ }
+
+ // Insert succeeded
+ return (true);
+}
+
+void
+MySqlHostDataSource::getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
+ boost::shared_ptr<MySqlHostReservationExchange> exchange,
+ ConstHostCollection& result, bool single) const {
+
+ // Bind the selection parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[stindex], bind);
+ checkError(status, stindex, "unable to bind WHERE clause parameter");
+
+ // Set up the MYSQL_BIND array for the data being returned and bind it to
+ // the statement.
+ std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
+ status = mysql_stmt_bind_result(statements_[stindex], &outbind[0]);
+ checkError(status, stindex, "unable to bind SELECT clause parameters");
+
+ // Execute the statement
+ status = mysql_stmt_execute(statements_[stindex]);
+ checkError(status, stindex, "unable to execute");
+
+ // Ensure that all the lease information is retrieved in one go to avoid
+ // overhead of going back and forth between client and server.
+ status = mysql_stmt_store_result(statements_[stindex]);
+ checkError(status, stindex, "unable to set up for storing all results");
+
+ // Set up the fetch "release" object to release resources associated
+ // with the call to mysql_stmt_fetch when this method exits, then
+ // retrieve the data.
+ MySqlFreeResult fetch_release(statements_[stindex]);
+ int count = 0;
+ while ((status = mysql_stmt_fetch(statements_[stindex])) == 0) {
+ try {
+ result.push_back(exchange->getHostData());
+
+ } catch (const isc::BadValue& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ text_statements_[stindex] << ">");
+ }
+
+ if (single && (++count > 1)) {
+ isc_throw(MultipleRecords, "multiple records were found in the "
+ "database where only one was expected for query "
+ << text_statements_[stindex]);
+ }
+ }
+
+ // How did the fetch end?
+ if (status == 1) {
+ // Error - unable to fetch results
+ checkError(status, stindex, "unable to fetch results");
+ } else if (status == MYSQL_DATA_TRUNCATED) {
+ // Data truncated - throw an exception indicating what was at fault
+ isc_throw(DataTruncated, text_statements_[stindex]
+ << " returned truncated data: columns affected are "
+ << exchange->getErrorColumns());
+ }
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // DUID
+ const vector<uint8_t>& duid_vector = duid->getDuid();
+ unsigned long duid_length = duid_vector.size();
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer =
+ reinterpret_cast<char*>(const_cast<uint8_t*>(&duid_vector[0]));
+ inbind[0].buffer_length = duid_length;
+ inbind[0].length = &duid_length;
+
+ // HW Address
+ const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+ unsigned long hwaddr_length = hwaddr_vector.size();
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer =
+ reinterpret_cast<char*>(const_cast<uint8_t*>(&hwaddr_vector[0]));
+ inbind[1].buffer_length = hwaddr_length;
+ inbind[1].length = &hwaddr_length;
+
+ ConstHostCollection result;
+ getHostCollection(GET_HOST_HWADDR_DUID, inbind, hostExchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll4(
+ const asiolink::IOAddress& address) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t addr4 = static_cast<uint32_t>(address);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ getHostCollection(GET_HOST_ADDR, inbind, hostExchange_, result, false);
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id,
+ const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(subnet_id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Choosing one of the identifiers
+ if (hwaddr) {
+ // HW Address
+ const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+ unsigned long hwaddr_length = hwaddr_vector.size();
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer =
+ reinterpret_cast<char*>(const_cast<uint8_t*>(&hwaddr_vector[0]));
+ inbind[1].buffer_length = hwaddr_length;
+ inbind[1].length = &hwaddr_length;
+ } else if (duid) {
+ // DUID
+ const vector<uint8_t>& duid_vector = duid->getDuid();
+ unsigned long duid_length = duid_vector.size();
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer =
+ reinterpret_cast<char*>(const_cast<uint8_t*>(&duid_vector[0]));
+ inbind[1].buffer_length = duid_length;
+ inbind[1].length = &duid_length;
+ }
+ // if none of the identifiers was given, this field should remain null
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_SUBID4_DHCPID, inbind, hostExchange_, collection,
+ true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(subnet_id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ uint32_t addr4 = static_cast<uint32_t>(address);
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_SUBID_ADDR, inbind, hostExchange_, collection,
+ true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const SubnetID& subnet_id,
+ const DuidPtr& duid, const HWAddrPtr& hwaddr) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(subnet_id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Choosing one of the identifiers
+ if (hwaddr) {
+ // HW Address
+ const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
+ unsigned long hwaddr_length = hwaddr_vector.size();
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer =
+ reinterpret_cast<char*>(const_cast<uint8_t*>(&hwaddr_vector[0]));
+ inbind[1].buffer_length = hwaddr_length;
+ inbind[1].length = &hwaddr_length;
+ } else if (duid) {
+ // DUID
+ const vector<uint8_t>& duid_vector = duid->getDuid();
+ unsigned long duid_length = duid_vector.size();
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer =
+ reinterpret_cast<char*>(const_cast<uint8_t*>(&duid_vector[0]));
+ inbind[1].buffer_length = duid_length;
+ inbind[1].length = &duid_length;
+ }
+ // if none of the identifiers was given, this field should remain null
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_SUBID6_DHCPID, inbind, hostExchange_, collection,
+ true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t pref = static_cast<uint32_t>(prefix);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&pref);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(prefix_len);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_PREFIX, inbind, hostExchange_, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(subnet_id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ uint32_t addr6 = static_cast<uint32_t>(address);
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr6);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ getHostCollection(GET_HOST_SUBID_ADDR, inbind, hostExchange_, collection,
+ true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+// Miscellaneous database methods.
+
+std::string MySqlHostDataSource::getName() const {
+ std::string name = "";
+ try {
+ name = getParameter("name");
+ } catch (...) {
+ // Return an empty name
+ }
+ return (name);
+}
+
+std::string MySqlHostDataSource::getDescription() const {
+ return (std::string("MySQL Database"));
+}
+
+std::pair<uint32_t, uint32_t> MySqlHostDataSource::getVersion() const {
+ const StatementIndex stindex = GET_VERSION;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_VERSION);
+
+ uint32_t major; // Major version number
+ uint32_t minor; // Minor version number
+
+ // Execute the prepared statement
+ int status = mysql_stmt_execute(statements_[stindex]);
+ if (status != 0) {
+ isc_throw(DbOperationError,
+ "unable to execute <" << text_statements_[stindex] << "> - reason: " << mysql_error(mysql_));
+ }
+
+ // Bind the output of the statement to the appropriate variables.
+ MYSQL_BIND bind[2];
+ memset(bind, 0, sizeof(bind));
+
+ bind[0].buffer_type = MYSQL_TYPE_LONG;
+ bind[0].is_unsigned = 1;
+ bind[0].buffer = &major;
+ bind[0].buffer_length = sizeof(major);
+
+ bind[1].buffer_type = MYSQL_TYPE_LONG;
+ bind[1].is_unsigned = 1;
+ bind[1].buffer = &minor;
+ bind[1].buffer_length = sizeof(minor);
+
+ status = mysql_stmt_bind_result(statements_[stindex], bind);
+ if (status != 0) {
+ isc_throw(DbOperationError,
+ "unable to bind result set: " << mysql_error(mysql_));
+ }
+
+ // Fetch the data and set up the "release" object to release associated
+ // resources when this method exits then retrieve the data.
+ MySqlFreeResult fetch_release(statements_[stindex]);
+ status = mysql_stmt_fetch(statements_[stindex]);
+ if (status != 0) {
+ isc_throw(DbOperationError,
+ "unable to obtain result set: " << mysql_error(mysql_));
+ }
+
+ return (std::make_pair(major, minor));
+}
+
+void MySqlHostDataSource::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
+ if (mysql_commit(mysql_) != 0) {
+ isc_throw(DbOperationError, "commit failed: " << mysql_error(mysql_));
+ }
+}
+
+void MySqlHostDataSource::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
+ if (mysql_rollback(mysql_) != 0) {
+ isc_throw(DbOperationError, "rollback failed: " << mysql_error(mysql_));
+ }
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
--- /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.h>
+
+
+namespace isc {
+namespace dhcp {
+
+// Define the current database schema values
+
+const uint32_t CURRENT_VERSION_VERSION = 3;
+const uint32_t CURRENT_VERSION_MINOR = 0;
+
+
+// Forward declaration of the Host exchange objects. These classes are defined
+// in the .cc file.
+class MySqlHostReservationExchange;
+
+
+class MySqlHostDataSource : public BaseHostDataSource, public MySqlConnection {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Uses the following keywords in the parameters passed to it to
+ /// connect to the database:
+ /// - name - Name of the database to which to connect (mandatory)
+ /// - host - Host to which to connect (optional, defaults to "localhost")
+ /// - user - Username under which to connect (optional)
+ /// - password - Password for "user" on the database (optional)
+ ///
+ /// If the database is successfully opened, the version number in the
+ /// schema_version table will be checked against hard-coded value in
+ /// the implementation file.
+ ///
+ /// Finally, all the SQL commands are pre-compiled.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+ /// @throw isc::dhcp::DbOpenError Error opening the database
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ MySqlHostDataSource(const ParameterMap& parameters);
+
+ /// @brief Destructor (closes database)
+ virtual ~MySqlHostDataSource();
+
+ /// @brief Return all hosts for the specified HW address or DUID.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// for the specified HW address or DUID. Note, that this method may
+ /// return multiple reservations because a particular client may have
+ /// reservations in multiple subnets and the same client may be identified
+ /// by HW address or DUID. The server is unable to verify that the specific
+ /// DUID and HW address belong to the same client, until the client sends
+ /// a DHCP message.
+ ///
+ /// Specifying both hardware address and DUID is allowed for this method
+ /// and results in returning all objects that are associated with hardware
+ /// address OR duid. For example: if one host is associated with the
+ /// specified hardware address and another host is associated with the
+ /// specified DUID, two hosts will be returned.
+ ///
+ /// @param hwaddr HW address of the client or NULL if no HW address
+ /// available.
+ /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// Implementations of this method should guard against the case when
+ /// mutliple instances of the @c Host are present, e.g. when two
+ /// @c Host objects are found, one for the DUID, another one for the
+ /// HW address. In such case, an implementation of this method
+ /// should throw an exception.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param hwaddr HW address of the client or NULL if no HW address
+ /// available.
+ /// @param duid client id or NULL if not available.
+ ///
+ /// @return Const @c Host object using a specified HW address or DUID.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+ const DuidPtr& duid = DuidPtr()) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// One of the use cases for this method is to detect collisions between
+ /// dynamically allocated addresses and reserved addresses. When the new
+ /// address is assigned to a client, the allocation mechanism should check
+ /// if this address is not reserved for some other host and do not allocate
+ /// this address if reservation is present.
+ ///
+ /// Implementations of this method should guard against invalid addresses,
+ /// such as IPv6 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Const @c Host object using a specified IPv4 address.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// Implementations of this method should guard against the case when
+ /// mutliple instances of the @c Host are present, e.g. when two
+ /// @c Host objects are found, one for the DUID, another one for the
+ /// HW address. In such case, an implementation of this method
+ /// should throw an exception.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param hwaddr HW address of the client or NULL if no HW address
+ /// available.
+ /// @param duid DUID or NULL if not available.
+ ///
+ /// @return Const @c Host object using a specified HW address or DUID.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const DuidPtr& duid,
+ const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ ///
+ /// @return Const @c Host object using a specified HW address or DUID.
+ virtual ConstHostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+ /// @brief Returns a host from specific subnet and reserved address.
+ ///
+ /// @param subnet_id subnet identfier.
+ /// @param address specified address.
+ ///
+ /// @return Const @c host object that has a reservation for specified address.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// The implementations of this method should guard against duplicate
+ /// reservations for the same host, where possible. For example, when the
+ /// reservation for the same HW address and subnet id is added twice, the
+ /// implementation should throw an exception. Note, that usually it is
+ /// impossible to guard against adding duplicated host, where one instance
+ /// is identified by HW address, another one by DUID.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ virtual void add(const HostPtr& host);
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return (std::string("mysql"));
+ }
+
+ /// @brief Returns backend name.
+ ///
+ /// Each backend have specific name, e.g. "mysql" or "sqlite".
+ ///
+ /// @return Name of the backend.
+ virtual std::string getName() const;
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const;
+
+ /// @brief Returns backend version.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// @throw DbOperationError If the commit failed.
+ virtual void commit();
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// @throw DbOperationError If the rollback failed.
+ virtual void rollback();
+
+ /// @brief Statement Tags
+ ///
+ /// The contents of the enum are indexes into the list of SQL statements
+ enum StatementIndex {
+ INSERT_HOST, // Insert new host to collection
+ GET_HOST_HWADDR_DUID, // Get hosts identified by DUID and/or HW address
+ GET_HOST_ADDR, // Get hosts with specified IPv4 address
+ GET_HOST_SUBID4_DHCPID, // Get host with specified IPv4 SubnetID and HW address and/or DUID
+ GET_HOST_SUBID6_DHCPID, // Get host with specified IPv6 SubnetID and HW address and/or DUID
+ GET_HOST_SUBID_ADDR, // Get host with specified IPv4 SubnetID and IPv4 address
+ GET_HOST_PREFIX, // Get host with specified IPv6 prefix
+ GET_VERSION, // Obtain version number
+ NUM_STATEMENTS // Number of statements
+ };
+
+private:
+ /// @brief Add Host Code
+ ///
+ /// This method performs adding a host operation.
+ /// It binds the contents of the host object to
+ /// the prepared statement and adds it to the database.
+ ///
+ /// @param stindex Index of statemnent being executed
+ /// @param bind MYSQL_BIND array that has been created for the host
+ ///
+ /// @return true if the host was added, false if it was not.
+ bool addHost(StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
+
+ /// @brief Get Host Collection Code
+ ///
+ /// This method obtains multiple hosts from the database.
+ ///
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param exchange Exchange object to use
+ /// @param result ConstHostCollection object returned. Note that any hosts in
+ /// the collection when this method is called are not erased: the
+ /// new data is appended to the end.
+ /// @param single If true, only a single data item is to be retrieved.
+ /// If more than one is present, a MultipleRecords exception will
+ /// be thrown.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ void getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
+ boost::shared_ptr<MySqlHostReservationExchange> exchange,
+ ConstHostCollection& result, bool single = false) const;
+
+ /// @brief Check Error and Throw Exception
+ ///
+ /// Virtually all MySQL functions return a status which, if non-zero,
+ /// indicates an error. This inline function conceals a lot of error
+ /// checking/exception-throwing code.
+ ///
+ /// @param status Status code: non-zero implies an error
+ /// @param index Index of statement that caused the error
+ /// @param what High-level description of the error
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ inline void checkError(int status, StatementIndex index,
+ const char* what) const {
+ if (status != 0) {
+ isc_throw(DbOperationError,
+ what << " for <" << text_statements_[index] << ">, reason: "
+ << mysql_error(mysql_) << " (error code " << mysql_errno(mysql_) << ")");
+ }
+ }
+
+
+ // Members
+
+ /// The exchange objects are used for transfer of data to/from the database.
+ /// They are pointed-to objects as the contents may change in "const" calls,
+ /// while the rest of this object does not. (At alternative would be to
+ /// declare them as "mutable".)
+ boost::shared_ptr<MySqlHostReservationExchange> hostExchange_; ///< Exchange object
+
+};
+
+}
+}
+
+#endif // MYSQL_HOST_DATA_SOURCE_H
return (tmp.str());
}
-
-// Time conversion methods.
-//
-// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
-// from the current timezone to UTC for storage, and from UTC to the current
-// timezone for retrieval.
-//
-// This causes no problems providing that:
-// a) cltt is given in local time
-// b) We let the system take care of timezone conversion when converting
-// from a time read from the database into a local time.
-
-void
-MySqlLeaseMgr::convertToDatabaseTime(const time_t cltt,
- const uint32_t valid_lifetime,
- MYSQL_TIME& expire) {
-
- // Calculate expiry time. Store it in the 64-bit value so as we can detect
- // overflows.
- int64_t expire_time_64 = static_cast<int64_t>(cltt) +
- static_cast<int64_t>(valid_lifetime);
-
- // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
- // beyond the max value of int32_t.
- if (expire_time_64 > LeaseMgr::MAX_DB_TIME) {
- isc_throw(BadValue, "Time value is too large: " << expire_time_64);
- }
-
- const time_t expire_time = static_cast<const time_t>(expire_time_64);
-
- // Convert to broken-out time
- struct tm expire_tm;
- (void) localtime_r(&expire_time, &expire_tm);
-
- // Place in output expire structure.
- expire.year = expire_tm.tm_year + 1900;
- expire.month = expire_tm.tm_mon + 1; // Note different base
- expire.day = expire_tm.tm_mday;
- expire.hour = expire_tm.tm_hour;
- expire.minute = expire_tm.tm_min;
- expire.second = expire_tm.tm_sec;
- expire.second_part = 0; // No fractional seconds
- expire.neg = my_bool(0); // Not negative
-}
-
-void
-MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
- uint32_t valid_lifetime, time_t& cltt) {
-
- // Copy across fields from MYSQL_TIME structure.
- struct tm expire_tm;
- memset(&expire_tm, 0, sizeof(expire_tm));
-
- expire_tm.tm_year = expire.year - 1900;
- expire_tm.tm_mon = expire.month - 1;
- expire_tm.tm_mday = expire.day;
- expire_tm.tm_hour = expire.hour;
- expire_tm.tm_min = expire.minute;
- expire_tm.tm_sec = expire.second;
- expire_tm.tm_isdst = -1; // Let the system work out about DST
-
- // Convert to local time
- cltt = mktime(&expire_tm) - valid_lifetime;
-}
-
-
// Add leases to the database. The two public methods accept a lease object
// (either V4 of V6), bind the contents to the appropriate prepared
// statement, then call common code to execute the statement.
/// @throw DbOperationError If the rollback failed.
virtual void rollback();
- ///@{
- /// The following methods are used to convert between times and time
- /// intervals stored in the Lease object, and the times stored in the
- /// database. The reason for the difference is because in the DHCP server,
- /// the cltt (Client Time Since Last Transmission) is the natural data; in
- /// the lease file - which may be read by the user - it is the expiry time
- /// of the lease.
-
- /// @brief Convert Lease Time to Database Times
- ///
- /// Within the DHCP servers, times are stored as client last transmit time
- /// and valid lifetime. In the database, the information is stored as
- /// valid lifetime and "expire" (time of expiry of the lease). They are
- /// related by the equation:
- ///
- /// - expire = client last transmit time + valid lifetime
- ///
- /// This method converts from the times in the lease object into times
- /// able to be added to the database.
- ///
- /// @param cltt Client last transmit time
- /// @param valid_lifetime Valid lifetime
- /// @param expire Reference to MYSQL_TIME object where the expiry time of
- /// the lease will be put.
- ///
- /// @throw isc::BadValue if the sum of the calculated expiration time is
- /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
- static
- void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
- MYSQL_TIME& expire);
-
- /// @brief Convert Database Time to Lease Times
- ///
- /// Within the database, time is stored as "expire" (time of expiry of the
- /// lease) and valid lifetime. In the DHCP server, the information is
- /// stored client last transmit time and valid lifetime. These are related
- /// by the equation:
- ///
- /// - client last transmit time = expire - valid_lifetime
- ///
- /// This method converts from the times in the database into times
- /// able to be inserted into the lease object.
- ///
- /// @param expire Reference to MYSQL_TIME object from where the expiry
- /// time of the lease is taken.
- /// @param valid_lifetime lifetime of the lease.
- /// @param cltt Reference to location where client last transmit time
- /// is put.
- static
- void convertFromDatabaseTime(const MYSQL_TIME& expire,
- uint32_t valid_lifetime, time_t& cltt);
- ///@}
-
/// @brief Statement Tags
///
/// The contents of the enum are indexes into the list of SQL statements
///
/// @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 > DataSource::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
if HAVE_PGSQL
libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_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/data_source.h>
+#include <asiolink/io_address.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+GenericHostDataSourceTest::GenericHostDataSourceTest()
+ :hdsptr_(NULL){
+
+}
+
+GenericHostDataSourceTest::~GenericHostDataSourceTest(){}
+
+
+
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
--- /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 <gtest/gtest.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+class GenericHostDataSourceTest : public ::testing::Test {
+public:
+
+ /// @brief Universe (V4 or V6).
+ enum Universe {
+ V4,
+ V6
+ };
+
+ /// @brief Default constructor.
+ GenericHostDataSourceTest();
+
+ /// @brief Virtual destructor.
+ virtual ~GenericHostDataSourceTest();
+
+
+ /// @brief Pointer to the host data source
+ BaseHostDataSource* hdsptr_;
+
+};
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
+
+#endif
// Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
// time too large to store.
lease->valid_lft_ = 24*60*60;
- lease->cltt_ = LeaseMgr::MAX_DB_TIME;
+ lease->cltt_ = DataSource::MAX_DB_TIME;
// Insert should throw.
ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
// Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
// time too large to store.
lease->valid_lft_ = 24*60*60;
- lease->cltt_ = LeaseMgr::MAX_DB_TIME;
+ lease->cltt_ = DataSource::MAX_DB_TIME;
// Insert should throw.
ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
--- /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 hdsptr_ will close
+ /// the database. Then reopen it and delete everything created by the test.
+ virtual ~MySqlHostDataSourceTest() {
+ //hdsptr_->rollback(); // need to decide where to implement rollback method (HostMgr or BaseHostDataSource maybe?)
+ HostDataSourceFactory::destroy();
+ destroySchema();
+ }
+
+ /// @brief Reopen the database
+ ///
+ /// Closes the database and re-open it. Anything committed should be
+ /// visible.
+ ///
+ /// Parameter is ignored for MySQL backend as the v4 and v6 leases share
+ /// the same database.
+ void reopen(Universe) {
+ HostDataSourceFactory::destroy();
+ HostDataSourceFactory::create(validConnectionString());
+ hdsptr_ = &(HostDataSourceFactory::instance());
+ }
+
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the MySqlLeaseMgr can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// MySqlLeaseMgr test fixure set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+
+TEST(MySqlOpenTest, OpenDatabase) {
+
+ // Schema needs to be created for the test to work.
+ destroySchema();
+ createSchema();
+
+ // Check that lease manager open the database opens correctly and tidy up.
+ // If it fails, print the error message.
+ try {
+ HostDataSourceFactory::create(validConnectionString());
+ EXPECT_NO_THROW((void) HostDataSourceFactory::instance());
+ HostDataSourceFactory::destroy();
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Check that attempting to get an instance of the lease manager when
+ // none is set throws an exception.
+ EXPECT_THROW(HostDataSourceFactory::instance(), NoHostDataSourceManager);
+
+ // Check that wrong specification of backend throws an exception.
+ // (This is really a check on LeaseMgrFactory, but is convenient to
+ // perform here.)
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ InvalidParameter);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ InvalidType);
+
+ // Check that invalid login data causes an exception.
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+ DbOpenError);
+
+ // Check for missing parameters
+ EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+ VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ NoDatabaseName);
+
+ // Tidy up after the test
+ destroySchema();
+}
+
+/// @brief Check conversion functions
+///
+/// The server works using cltt and valid_filetime. In the database, the
+/// information is stored as expire_time and valid-lifetime, which are
+/// related by
+///
+/// expire_time = cltt + valid_lifetime
+///
+/// This test checks that the conversion is correct. It does not check that the
+/// data is entered into the database correctly, only that the MYSQL_TIME
+/// structure used for the entry is correctly set up.
+TEST_F(MySqlHostDataSourceTest, checkTimeConversion) {
+ const time_t cltt = time(NULL);
+ const uint32_t valid_lft = 86400; // 1 day
+ struct tm tm_expire;
+ MYSQL_TIME mysql_expire;
+
+ // Work out what the broken-down time will be for one day
+ // after the current time.
+ time_t expire_time = cltt + valid_lft;
+ (void) localtime_r(&expire_time, &tm_expire);
+
+ // Convert to the database time
+ MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
+
+ // Are the times the same?
+ EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
+ EXPECT_EQ(tm_expire.tm_mon + 1, mysql_expire.month);
+ EXPECT_EQ(tm_expire.tm_mday, mysql_expire.day);
+ EXPECT_EQ(tm_expire.tm_hour, mysql_expire.hour);
+ EXPECT_EQ(tm_expire.tm_min, mysql_expire.minute);
+ EXPECT_EQ(tm_expire.tm_sec, mysql_expire.second);
+ EXPECT_EQ(0, mysql_expire.second_part);
+ EXPECT_EQ(0, mysql_expire.neg);
+
+ // Convert back
+ time_t converted_cltt = 0;
+ MySqlConnection::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
+ EXPECT_EQ(cltt, converted_cltt);
+}
+
+}; // Of anonymous namespace
#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);
}