From: Tomek Mrugalski Date: Fri, 18 Sep 2015 17:55:19 +0000 (+0200) Subject: [3682] Patch sent by Adam Kalmus X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ae51566ced97ca4e3becb4be680606b95f372fff;p=thirdparty%2Fkea.git [3682] Patch sent by Adam Kalmus The patch did not apply cleanly. The closest commit (with smallest number of conflicts was 66fb75897931e04b145c3b4e76239746df82a59c). I managed to resolve 2 conflicts. --- diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am old mode 100644 new mode 100755 index 69a80c935f..3aeefb2e03 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -96,6 +96,7 @@ libkea_dhcpsrv_la_SOURCES += database_connection.cc database_connection.h libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h libkea_dhcpsrv_la_SOURCES += host.cc host.h libkea_dhcpsrv_la_SOURCES += host_container.h +libkea_dhcpsrv_la_SOURCES += host_data_source_factory.cc host_data_source_factory.h libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h libkea_dhcpsrv_la_SOURCES += hosts_log.cc hosts_log.h libkea_dhcpsrv_la_SOURCES += key_from_key.h @@ -112,6 +113,7 @@ libkea_dhcpsrv_la_SOURCES += memfile_lease_storage.h if HAVE_MYSQL libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h libkea_dhcpsrv_la_SOURCES += mysql_connection.cc mysql_connection.h +libkea_dhcpsrv_la_SOURCES += mysql_host_data_source.cc mysql_host_data_source.h endif if HAVE_PGSQL libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h diff --git a/src/lib/dhcpsrv/database_connection.cc b/src/lib/dhcpsrv/database_connection.cc index 440a4d21cd..a64cf17ca2 100755 --- a/src/lib/dhcpsrv/database_connection.cc +++ b/src/lib/dhcpsrv/database_connection.cc @@ -25,6 +25,8 @@ using namespace std; namespace isc { namespace dhcp { +const time_t DataSource::MAX_DB_TIME = 2147483647; + std::string DatabaseConnection::getParameter(const std::string& name) const { ParameterMap::const_iterator param = parameters_.find(name); diff --git a/src/lib/dhcpsrv/database_connection.h b/src/lib/dhcpsrv/database_connection.h index 7e75debaeb..81a8c6f2b6 100755 --- a/src/lib/dhcpsrv/database_connection.h +++ b/src/lib/dhcpsrv/database_connection.h @@ -55,6 +55,11 @@ public: /// @ref BaseHostDataSource derived classes. class DatabaseConnection : public boost::noncopyable { public: + + /// @brief Defines maximum value for time that can be reliably stored. + // If I'm still alive I'll be too old to care. You fix it. + static const time_t MAX_DB_TIME; + /// @brief Database configuration parameter map typedef std::map ParameterMap; diff --git a/src/lib/dhcpsrv/host_data_source_factory.cc b/src/lib/dhcpsrv/host_data_source_factory.cc new file mode 100644 index 0000000000..9ccb929d37 --- /dev/null +++ b/src/lib/dhcpsrv/host_data_source_factory.cc @@ -0,0 +1,102 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace isc { +namespace dhcp { + + +boost::scoped_ptr& +HostDataSourceFactory::getHostDataSourcePtr() { + static boost::scoped_ptr hostDataSourcePtr; + return (hostDataSourcePtr); +} + +void +HostDataSourceFactory::create(const std::string& dbaccess) { + const std::string type = "type"; + + // Parse the access string and create a redacted string for logging. + DataSource::ParameterMap parameters = DataSource::parse(dbaccess); + std::string redacted = DataSource::redactedAccessString(parameters); + + // Is "type" present? + if (parameters.find(type) == parameters.end()) { + LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess); + isc_throw(InvalidParameter, "Database configuration parameters do not " + "contain the 'type' keyword"); + } + + + // Yes, check what it is. +#ifdef HAVE_MYSQL + if (parameters[type] == string("mysql")) { + LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_DB).arg(redacted); + getHostDataSourcePtr().reset(new MySqlHostDataSource(parameters)); + return; + } +#endif +#ifdef HAVE_PGSQL + if (parameters[type] == string("postgresql")) { + LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_DB).arg(redacted); + // set pgsql data source here, when it will be featured + return; + } +#endif + + // Get here on no match + LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]); + isc_throw(InvalidType, "Database access parameter 'type' does " + "not specify a supported database backend"); +} + +void +HostDataSourceFactory::destroy() { + // Destroy current host data source instance. This is a no-op if no host + // data source is available. + if (getHostDataSourcePtr()) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CLOSE_DB); + } + getHostDataSourcePtr().reset(); +} + +BaseHostDataSource& +HostDataSourceFactory::instance() { + BaseHostDataSource* hdsptr = getHostDataSourcePtr().get(); + if (hdsptr == NULL) { + isc_throw(NoHostDataSourceManager, "no current host data source instance is available"); + } + return (*hdsptr); +} + +}; // namespace dhcp +}; // namespace isc diff --git a/src/lib/dhcpsrv/host_data_source_factory.h b/src/lib/dhcpsrv/host_data_source_factory.h new file mode 100644 index 0000000000..d485025776 --- /dev/null +++ b/src/lib/dhcpsrv/host_data_source_factory.h @@ -0,0 +1,73 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef HOST_DATA_SOURCE_FACTORY_H +#define HOST_DATA_SOURCE_FACTORY_H + +#include +#include +#include + +#include + +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& getHostDataSourcePtr(); + +}; + + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc index a0a3e455cf..5b7d8577fc 100644 --- a/src/lib/dhcpsrv/host_mgr.cc +++ b/src/lib/dhcpsrv/host_mgr.cc @@ -17,6 +17,7 @@ #include #include #include +#include namespace { @@ -47,6 +48,7 @@ void HostMgr::create(const std::string&) { getHostMgrPtr().reset(new HostMgr()); + //alternate_source.reset(new MySqlHostDataSource()); /// @todo Initialize alternate_source here, using the parameter. /// For example: alternate_source.reset(new MysqlHostDataSource(access)). } @@ -152,7 +154,6 @@ HostMgr::get6(const SubnetID& subnet_id, return (host); } - void HostMgr::add(const HostPtr&) { isc_throw(isc::NotImplemented, "HostMgr::add is not implemented"); diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index 55a4f4c05e..82c6cd6308 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc index 4153771131..2067e7a74e 100755 --- a/src/lib/dhcpsrv/lease_mgr.cc +++ b/src/lib/dhcpsrv/lease_mgr.cc @@ -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 { diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h index d7536a9d12..54497f08d0 100755 --- a/src/lib/dhcpsrv/lease_mgr.h +++ b/src/lib/dhcpsrv/lease_mgr.h @@ -112,10 +112,6 @@ public: /// of those classes for details. class LeaseMgr { public: - /// @brief Defines maximum value for time that can be reliably stored. - // If I'm still alive I'll be too old to care. You fix it. - static const time_t MAX_DB_TIME; - /// @brief Constructor /// LeaseMgr() : io_service_(new asiolink::IOService()) diff --git a/src/lib/dhcpsrv/mysql_connection.cc b/src/lib/dhcpsrv/mysql_connection.cc index b9d3e05e68..3921d9497b 100755 --- a/src/lib/dhcpsrv/mysql_connection.cc +++ b/src/lib/dhcpsrv/mysql_connection.cc @@ -193,6 +193,68 @@ MySqlConnection::~MySqlConnection() { statements_.clear(); text_statements_.clear(); +// Time conversion methods. +// +// Note that the MySQL TIMESTAMP data type (used for "expire") converts data +// from the current timezone to UTC for storage, and from UTC to the current +// timezone for retrieval. +// +// This causes no problems providing that: +// a) cltt is given in local time +// b) We let the system take care of timezone conversion when converting +// from a time read from the database into a local time. + +void +MySqlConnection::convertToDatabaseTime(const time_t cltt, + const uint32_t valid_lifetime, + MYSQL_TIME& expire) { + + // Calculate expiry time. Store it in the 64-bit value so as we can detect + // overflows. + int64_t expire_time_64 = static_cast(cltt) + + static_cast(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(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 diff --git a/src/lib/dhcpsrv/mysql_connection.h b/src/lib/dhcpsrv/mysql_connection.h index 0824d4955a..e940c754fa 100755 --- a/src/lib/dhcpsrv/mysql_connection.h +++ b/src/lib/dhcpsrv/mysql_connection.h @@ -189,6 +189,59 @@ public: /// @throw DbOpenError Error opening the database void openDatabase(); + ///@{ + /// The following methods are used to convert between times and time + /// intervals stored in the Lease object, and the times stored in the + /// database. The reason for the difference is because in the DHCP server, + /// the cltt (Client Time Since Last Transmission) is the natural data; in + /// the lease file - which may be read by the user - it is the expiry time + /// of the lease. + + /// @brief Convert Lease Time to Database Times + /// + /// Within the DHCP servers, times are stored as client last transmit time + /// and valid lifetime. In the database, the information is stored as + /// valid lifetime and "expire" (time of expiry of the lease). They are + /// related by the equation: + /// + /// - expire = client last transmit time + valid lifetime + /// + /// This method converts from the times in the lease object into times + /// able to be added to the database. + /// + /// @param cltt Client last transmit time + /// @param valid_lifetime Valid lifetime + /// @param expire Reference to MYSQL_TIME object where the expiry time of + /// the lease will be put. + /// + /// @throw isc::BadValue if the sum of the calculated expiration time is + /// greater than the value of @c LeaseMgr::MAX_DB_TIME. + static + void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime, + MYSQL_TIME& expire); + + /// @brief Convert Database Time to Lease Times + /// + /// Within the database, time is stored as "expire" (time of expiry of the + /// lease) and valid lifetime. In the DHCP server, the information is + /// stored client last transmit time and valid lifetime. These are related + /// by the equation: + /// + /// - client last transmit time = expire - valid_lifetime + /// + /// This method converts from the times in the database into times + /// able to be inserted into the lease object. + /// + /// @param expire Reference to MYSQL_TIME object from where the expiry + /// time of the lease is taken. + /// @param valid_lifetime lifetime of the lease. + /// @param cltt Reference to location where client last transmit time + /// is put. + static + void convertFromDatabaseTime(const MYSQL_TIME& expire, + uint32_t valid_lifetime, time_t& cltt); + ///@} + /// @brief Prepared statements /// /// This field is public, because it is used heavily from MySqlConnection diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc new file mode 100644 index 0000000000..b2f0732515 --- /dev/null +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -0,0 +1,1002 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +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(&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 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(NULL); + bind_[0].buffer_type = MYSQL_TYPE_LONG; + bind_[0].buffer = reinterpret_cast(&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(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(&(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(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(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(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(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(host_->getIPv4Reservation()); + bind_[5].buffer_type = MYSQL_TYPE_LONG; + bind_[5].buffer = reinterpret_cast(&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(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(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(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(&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 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(&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(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(&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(&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(&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(&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(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(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(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(&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(dhcp_identifier_type_) << ") for host with " + << "identifier " << reinterpret_cast(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(NULL); // This is probably wrong way to do it... + if (dhcp4_subnet_id_null_ == MLM_FALSE) + ipv4_subnet_id = static_cast(dhcp4_subnet_id_); + + SubnetID ipv6_subnet_id = static_cast(NULL); + if (dhcp6_subnet_id_null_ == MLM_FALSE) + ipv6_subnet_id = static_cast(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(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 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 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& 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 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 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& duid_vector = duid->getDuid(); + unsigned long duid_length = duid_vector.size(); + inbind[0].buffer_type = MYSQL_TYPE_BLOB; + inbind[0].buffer = + reinterpret_cast(const_cast(&duid_vector[0])); + inbind[0].buffer_length = duid_length; + inbind[0].length = &duid_length; + + // HW Address + const vector& hwaddr_vector = hwaddr->hwaddr_; + unsigned long hwaddr_length = hwaddr_vector.size(); + inbind[1].buffer_type = MYSQL_TYPE_BLOB; + inbind[1].buffer = + reinterpret_cast(const_cast(&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(address); + inbind[0].buffer_type = MYSQL_TYPE_LONG; + inbind[0].buffer = reinterpret_cast(&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(subnet_id); + inbind[0].is_unsigned = MLM_TRUE; + + // Choosing one of the identifiers + if (hwaddr) { + // HW Address + const vector& hwaddr_vector = hwaddr->hwaddr_; + unsigned long hwaddr_length = hwaddr_vector.size(); + inbind[1].buffer_type = MYSQL_TYPE_BLOB; + inbind[1].buffer = + reinterpret_cast(const_cast(&hwaddr_vector[0])); + inbind[1].buffer_length = hwaddr_length; + inbind[1].length = &hwaddr_length; + } else if (duid) { + // DUID + const vector& duid_vector = duid->getDuid(); + unsigned long duid_length = duid_vector.size(); + inbind[1].buffer_type = MYSQL_TYPE_BLOB; + inbind[1].buffer = + reinterpret_cast(const_cast(&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(subnet_id); + inbind[0].is_unsigned = MLM_TRUE; + + uint32_t addr4 = static_cast(address); + inbind[1].buffer_type = MYSQL_TYPE_LONG; + inbind[1].buffer = reinterpret_cast(&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(subnet_id); + inbind[0].is_unsigned = MLM_TRUE; + + // Choosing one of the identifiers + if (hwaddr) { + // HW Address + const vector& hwaddr_vector = hwaddr->hwaddr_; + unsigned long hwaddr_length = hwaddr_vector.size(); + inbind[1].buffer_type = MYSQL_TYPE_BLOB; + inbind[1].buffer = + reinterpret_cast(const_cast(&hwaddr_vector[0])); + inbind[1].buffer_length = hwaddr_length; + inbind[1].length = &hwaddr_length; + } else if (duid) { + // DUID + const vector& duid_vector = duid->getDuid(); + unsigned long duid_length = duid_vector.size(); + inbind[1].buffer_type = MYSQL_TYPE_BLOB; + inbind[1].buffer = + reinterpret_cast(const_cast(&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(prefix); + inbind[0].buffer_type = MYSQL_TYPE_LONG; + inbind[0].buffer = reinterpret_cast(&pref); + inbind[0].is_unsigned = MLM_TRUE; + + inbind[1].buffer_type = MYSQL_TYPE_LONG; + inbind[1].buffer = reinterpret_cast(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(subnet_id); + inbind[0].is_unsigned = MLM_TRUE; + + uint32_t addr6 = static_cast(address); + inbind[1].buffer_type = MYSQL_TYPE_LONG; + inbind[1].buffer = reinterpret_cast(&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 MySqlHostDataSource::getVersion() const { + const StatementIndex stindex = GET_VERSION; + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_MYSQL_GET_VERSION); + + uint32_t major; // Major version number + uint32_t minor; // Minor version number + + // Execute the prepared statement + int status = mysql_stmt_execute(statements_[stindex]); + if (status != 0) { + isc_throw(DbOperationError, + "unable to execute <" << text_statements_[stindex] << "> - reason: " << mysql_error(mysql_)); + } + + // Bind the output of the statement to the appropriate variables. + MYSQL_BIND bind[2]; + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_LONG; + bind[0].is_unsigned = 1; + bind[0].buffer = &major; + bind[0].buffer_length = sizeof(major); + + bind[1].buffer_type = MYSQL_TYPE_LONG; + bind[1].is_unsigned = 1; + bind[1].buffer = &minor; + bind[1].buffer_length = sizeof(minor); + + status = mysql_stmt_bind_result(statements_[stindex], bind); + if (status != 0) { + isc_throw(DbOperationError, + "unable to bind result set: " << mysql_error(mysql_)); + } + + // Fetch the data and set up the "release" object to release associated + // resources when this method exits then retrieve the data. + MySqlFreeResult fetch_release(statements_[stindex]); + status = mysql_stmt_fetch(statements_[stindex]); + if (status != 0) { + isc_throw(DbOperationError, + "unable to obtain result set: " << mysql_error(mysql_)); + } + + return (std::make_pair(major, minor)); +} + +void MySqlHostDataSource::commit() { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT); + if (mysql_commit(mysql_) != 0) { + isc_throw(DbOperationError, "commit failed: " << mysql_error(mysql_)); + } +} + +void MySqlHostDataSource::rollback() { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK); + if (mysql_rollback(mysql_) != 0) { + isc_throw(DbOperationError, "rollback failed: " << mysql_error(mysql_)); + } +} + + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h new file mode 100644 index 0000000000..f78d9bb302 --- /dev/null +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -0,0 +1,325 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef MYSQL_HOST_DATA_SOURCE_H +#define MYSQL_HOST_DATA_SOURCE_H + +#include +#include +#include + +#include +#include +#include + + +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 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& 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 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 hostExchange_; ///< Exchange object + +}; + +} +} + +#endif // MYSQL_HOST_DATA_SOURCE_H diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc index e1e8bb360f..543f7d3478 100755 --- a/src/lib/dhcpsrv/mysql_lease_mgr.cc +++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc @@ -1190,72 +1190,6 @@ MySqlLeaseMgr::getDBVersion() { return (tmp.str()); } - -// Time conversion methods. -// -// Note that the MySQL TIMESTAMP data type (used for "expire") converts data -// from the current timezone to UTC for storage, and from UTC to the current -// timezone for retrieval. -// -// This causes no problems providing that: -// a) cltt is given in local time -// b) We let the system take care of timezone conversion when converting -// from a time read from the database into a local time. - -void -MySqlLeaseMgr::convertToDatabaseTime(const time_t cltt, - const uint32_t valid_lifetime, - MYSQL_TIME& expire) { - - // Calculate expiry time. Store it in the 64-bit value so as we can detect - // overflows. - int64_t expire_time_64 = static_cast(cltt) + - static_cast(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(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. diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h index 1e25ed4826..56e2a72177 100755 --- a/src/lib/dhcpsrv/mysql_lease_mgr.h +++ b/src/lib/dhcpsrv/mysql_lease_mgr.h @@ -356,59 +356,6 @@ public: /// @throw DbOperationError If the rollback failed. virtual void rollback(); - ///@{ - /// The following methods are used to convert between times and time - /// intervals stored in the Lease object, and the times stored in the - /// database. The reason for the difference is because in the DHCP server, - /// the cltt (Client Time Since Last Transmission) is the natural data; in - /// the lease file - which may be read by the user - it is the expiry time - /// of the lease. - - /// @brief Convert Lease Time to Database Times - /// - /// Within the DHCP servers, times are stored as client last transmit time - /// and valid lifetime. In the database, the information is stored as - /// valid lifetime and "expire" (time of expiry of the lease). They are - /// related by the equation: - /// - /// - expire = client last transmit time + valid lifetime - /// - /// This method converts from the times in the lease object into times - /// able to be added to the database. - /// - /// @param cltt Client last transmit time - /// @param valid_lifetime Valid lifetime - /// @param expire Reference to MYSQL_TIME object where the expiry time of - /// the lease will be put. - /// - /// @throw isc::BadValue if the sum of the calculated expiration time is - /// greater than the value of @c LeaseMgr::MAX_DB_TIME. - static - void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime, - MYSQL_TIME& expire); - - /// @brief Convert Database Time to Lease Times - /// - /// Within the database, time is stored as "expire" (time of expiry of the - /// lease) and valid lifetime. In the DHCP server, the information is - /// stored client last transmit time and valid lifetime. These are related - /// by the equation: - /// - /// - client last transmit time = expire - valid_lifetime - /// - /// This method converts from the times in the database into times - /// able to be inserted into the lease object. - /// - /// @param expire Reference to MYSQL_TIME object from where the expiry - /// time of the lease is taken. - /// @param valid_lifetime lifetime of the lease. - /// @param cltt Reference to location where client last transmit time - /// is put. - static - void convertFromDatabaseTime(const MYSQL_TIME& expire, - uint32_t valid_lifetime, time_t& cltt); - ///@} - /// @brief Statement Tags /// /// The contents of the enum are indexes into the list of SQL statements diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index 511fcfc754..0ed10ae556 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -307,7 +307,7 @@ public: /// /// @return std::string containing the stringified time /// @throw isc::BadValue if the sum of the calculated expiration time is - /// greater than the value of @c LeaseMgr::MAX_DB_TIME. + /// greater than the value of @c DataSource::MAX_DB_TIME. static std::string convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime) { // Calculate expiry time. Store it in the 64-bit value so as we can detect @@ -316,11 +316,11 @@ public: static_cast(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); } diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am old mode 100644 new mode 100755 index 59120f4c7c..891f462bee --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -88,10 +88,12 @@ libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc libdhcpsrv_unittests_SOURCES += logging_unittest.cc libdhcpsrv_unittests_SOURCES += logging_info_unittest.cc libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc generic_lease_mgr_unittest.h +libdhcpsrv_unittests_SOURCES += generic_host_data_source_unittest.cc generic_host_data_source_unittest.h libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc if HAVE_MYSQL libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc +libdhcpsrv_unittests_SOURCES += mysql_host_data_source_unittest.cc endif if HAVE_PGSQL libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc new file mode 100644 index 0000000000..a03e83e1c4 --- /dev/null +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc @@ -0,0 +1,41 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { +namespace test { + +GenericHostDataSourceTest::GenericHostDataSourceTest() + :hdsptr_(NULL){ + +} + +GenericHostDataSourceTest::~GenericHostDataSourceTest(){} + + + + +}; // namespace test +}; // namespace dhcp +}; // namespace isc diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h new file mode 100644 index 0000000000..8789f6b2d7 --- /dev/null +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h @@ -0,0 +1,51 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef GENERIC_HOST_DATA_SOURCE_UNITTEST_H +#define GENERIC_HOST_DATA_SOURCE_UNITTEST_H + +#include +#include +#include + +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 diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc index 956c359c1b..060e18b145 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc @@ -693,7 +693,7 @@ GenericLeaseMgrTest::testMaxDate4() { // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire // time too large to store. lease->valid_lft_ = 24*60*60; - lease->cltt_ = LeaseMgr::MAX_DB_TIME; + lease->cltt_ = DataSource::MAX_DB_TIME; // Insert should throw. ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError); @@ -862,7 +862,7 @@ GenericLeaseMgrTest::testMaxDate6() { // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire // time too large to store. lease->valid_lft_ = 24*60*60; - lease->cltt_ = LeaseMgr::MAX_DB_TIME; + lease->cltt_ = DataSource::MAX_DB_TIME; // Insert should throw. ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError); diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc new file mode 100644 index 0000000000..79680ac453 --- /dev/null +++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc @@ -0,0 +1,301 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include +#include +#include +#include +#include +#include + + +#include + +#include +#include +#include +#include +#include + +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 diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc index 0e1cf7282e..781efe3102 100755 --- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -289,7 +290,7 @@ TEST_F(MySqlLeaseMgrTest, checkTimeConversion) { (void) localtime_r(&expire_time, &tm_expire); // Convert to the database time - MySqlLeaseMgr::convertToDatabaseTime(cltt, valid_lft, mysql_expire); + MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire); // Are the times the same? EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year); @@ -303,7 +304,7 @@ TEST_F(MySqlLeaseMgrTest, checkTimeConversion) { // Convert back time_t converted_cltt = 0; - MySqlLeaseMgr::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt); + MySqlConnection::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt); EXPECT_EQ(cltt, converted_cltt); }