Commit 65efc0da authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰

[3682_rebase] Merge branch 'trac3682' into trac3682_rebase

# Conflicts:
#	src/lib/dhcpsrv/mysql_lease_mgr.cc
#	src/lib/dhcpsrv/mysql_lease_mgr.h
parent 0d4a7120
......@@ -103,9 +103,11 @@ libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
libkea_dhcpsrv_la_SOURCES += 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
......@@ -122,6 +124,7 @@ libkea_dhcpsrv_la_SOURCES += memfile_lease_storage.h
if HAVE_MYSQL
libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
libkea_dhcpsrv_la_SOURCES += mysql_connection.cc mysql_connection.h
libkea_dhcpsrv_la_SOURCES += mysql_host_data_source.cc mysql_host_data_source.h
endif
libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
......
......@@ -64,6 +64,20 @@ public:
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() { }
......@@ -180,6 +194,24 @@ public:
/// @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() {};
};
}
......
......@@ -237,6 +237,15 @@ public:
/// 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.
......
......@@ -25,6 +25,8 @@ using namespace std;
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);
......
......@@ -55,6 +55,15 @@ public:
/// @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;
......
// 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
// 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
// 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
......@@ -17,6 +17,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/hosts_log.h>
#include <dhcpsrv/host_data_source_factory.h>
namespace {
......@@ -37,6 +38,8 @@ namespace dhcp {
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;
......@@ -44,11 +47,15 @@ HostMgr::getHostMgrPtr() {
}
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&
......@@ -152,7 +159,6 @@ HostMgr::get6(const SubnetID& subnet_id,
return (host);
}
void
HostMgr::add(const HostPtr&) {
isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");
......
......@@ -198,6 +198,15 @@ public:
/// @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.
......@@ -206,7 +215,7 @@ private:
/// @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.
......
......@@ -19,6 +19,10 @@ This debug message is issued when new host (with reservations) is added to
the server's configuration. The argument describes the host and its
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
......
......@@ -35,8 +35,6 @@ using namespace std;
namespace isc {
namespace dhcp {
const time_t LeaseMgr::MAX_DB_TIME = 2147483647;
Lease6Ptr
LeaseMgr::getLease6(Lease::Type type, const DUID& duid,
uint32_t iaid, SubnetID subnet_id) const {
......
......@@ -22,7 +22,7 @@
#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>
......@@ -68,28 +68,6 @@
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
......@@ -102,10 +80,6 @@ public:
/// of those classes for details.
class LeaseMgr {
public:
/// @brief Defines maximum value for time that can be reliably stored.
// If I'm still alive I'll be too old to care. You fix it.
static const time_t MAX_DB_TIME;
/// @brief Constructor
///
LeaseMgr()
......
......@@ -192,8 +192,106 @@ MySqlConnection::~MySqlConnection() {
}
statements_.clear();
text_statements_.clear();
}
// Time conversion methods.
//
// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
// from the current timezone to UTC for storage, and from UTC to the current
// timezone for retrieval.
//
// This causes no problems providing that:
// a) cltt is given in local time
// b) We let the system take care of timezone conversion when converting
// from a time read from the database into a local time.
void
MySqlConnection::convertToDatabaseTime(const time_t 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
......@@ -17,7 +17,7 @@
#include <dhcpsrv/database_connection.h>
#include <boost/scoped_ptr.hpp>