From: Tomek Mrugalski Date: Sun, 18 Oct 2015 22:32:58 +0000 (+0200) Subject: [3682] Adam's patch applied as-is X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b7dbd1d1d7df97c3a3ec4dc50c26dfde6b9fe918;p=thirdparty%2Fkea.git [3682] Adam's patch applied as-is --- diff --git a/src/lib/dhcpsrv/base_host_data_source.h b/src/lib/dhcpsrv/base_host_data_source.h index aae1676e3c..67c5aed5ab 100644 --- a/src/lib/dhcpsrv/base_host_data_source.h +++ b/src/lib/dhcpsrv/base_host_data_source.h @@ -180,6 +180,13 @@ 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; + }; } diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h index e8b08b0213..c0e4151669 100644 --- a/src/lib/dhcpsrv/cfg_hosts.h +++ b/src/lib/dhcpsrv/cfg_hosts.h @@ -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. diff --git a/src/lib/dhcpsrv/db_exceptions.h b/src/lib/dhcpsrv/db_exceptions.h index 06bf97a410..3924e117c9 100644 --- a/src/lib/dhcpsrv/db_exceptions.h +++ b/src/lib/dhcpsrv/db_exceptions.h @@ -41,6 +41,13 @@ public: 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) {} +}; + }; }; diff --git a/src/lib/dhcpsrv/host_data_source_factory.cc b/src/lib/dhcpsrv/host_data_source_factory.cc index 4c6d755c61..a0ffb976f5 100644 --- a/src/lib/dhcpsrv/host_data_source_factory.cc +++ b/src/lib/dhcpsrv/host_data_source_factory.cc @@ -46,8 +46,10 @@ 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); + DatabaseConnection::ParameterMap parameters = + DatabaseConnection::parse(dbaccess); + std::string redacted = + DatabaseConnection::redactedAccessString(parameters); // Is "type" present? if (parameters.find(type) == parameters.end()) { @@ -84,7 +86,9 @@ 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); + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_CLOSE_HOST_DATA_SOURCE) + .arg(getHostDataSourcePtr()->getType()); } getHostDataSourcePtr().reset(); } @@ -93,7 +97,8 @@ BaseHostDataSource& HostDataSourceFactory::instance() { BaseHostDataSource* hdsptr = getHostDataSourcePtr().get(); if (hdsptr == NULL) { - isc_throw(NoHostDataSourceManager, "no current host data source instance is available"); + isc_throw(NoHostDataSourceManager, + "no current host data source instance is available"); } return (*hdsptr); } diff --git a/src/lib/dhcpsrv/host_data_source_factory.h b/src/lib/dhcpsrv/host_data_source_factory.h index 91b9ec7c4d..27958e65ea 100644 --- a/src/lib/dhcpsrv/host_data_source_factory.h +++ b/src/lib/dhcpsrv/host_data_source_factory.h @@ -43,25 +43,65 @@ public: 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 todo - /// + /// @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 always 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 instance - /// - /// todo - static void destroy(); + /// @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 instance - /// todo - static BaseHostDataSource& instance(); + /// @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 /// - /// todo + /// 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& getHostDataSourcePtr(); }; diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc index 5b7d8577fc..64dd65ee09 100644 --- a/src/lib/dhcpsrv/host_mgr.cc +++ b/src/lib/dhcpsrv/host_mgr.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include namespace { @@ -45,10 +45,19 @@ HostMgr::getHostMgrPtr() { } void -HostMgr::create(const std::string&) { +HostMgr::create(const std::string& /*access*/) { getHostMgrPtr().reset(new HostMgr()); +/* + try { + HostDataSourceFactory::create(access); + } catch (...) { + std::cerr << "Unable to open database."; + throw; + } +*/ + //alternate_source = &(HostDataSourceFactory::instance()); + //alternate_source.reset(&(HostDataSourceFactory::instance())); - //alternate_source.reset(new MySqlHostDataSource()); /// @todo Initialize alternate_source here, using the parameter. /// For example: alternate_source.reset(new MysqlHostDataSource(access)). } diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index 82c6cd6308..098468c6b2 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -199,6 +199,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. @@ -207,7 +216,7 @@ private: /// @brief Pointer to an alternate host data source. /// /// If this pointer is NULL, the source is not in use. - boost::scoped_ptr alternate_source; + boost::shared_ptr alternate_source; /// @brief Returns a pointer to the currently used instance of the /// @c HostMgr. diff --git a/src/lib/dhcpsrv/mysql_connection.cc b/src/lib/dhcpsrv/mysql_connection.cc index 9ea1afc517..c3770f6648 100755 --- a/src/lib/dhcpsrv/mysql_connection.cc +++ b/src/lib/dhcpsrv/mysql_connection.cc @@ -258,5 +258,22 @@ MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire, 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 diff --git a/src/lib/dhcpsrv/mysql_connection.h b/src/lib/dhcpsrv/mysql_connection.h index e940c754fa..a94e7e253c 100755 --- a/src/lib/dhcpsrv/mysql_connection.h +++ b/src/lib/dhcpsrv/mysql_connection.h @@ -32,6 +32,11 @@ namespace dhcp { extern const my_bool MLM_FALSE; extern const my_bool MLM_TRUE; +// Define the current database schema values + +const uint32_t CURRENT_VERSION_VERSION = 3; +const uint32_t CURRENT_VERSION_MINOR = 0; + /// @brief Fetch and Release MySQL Results /// /// When a MySQL statement is expected, to fetch the results the function @@ -190,57 +195,73 @@ public: 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); - ///@} + /// 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 Commit Transactions + /// + /// Commits all pending database operations. On databases that don't + /// support transactions, this is a no-op. + /// + /// @throw DbOperationError If the commit failed. + void commit(); + + /// @brief Rollback Transactions + /// + /// Rolls back all pending database operations. On databases that don't + /// support transactions, this is a no-op. + /// + /// @throw DbOperationError If the rollback failed. + void rollback(); + ///@} /// @brief Prepared statements /// diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index ea5eeb66fb..f5555dba27 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -45,49 +45,52 @@ const size_t CLIENT_CLASSES_MAX_LEN = 255; /// 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::INSERT_HOST, + "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, " + "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " + "dhcp4_client_classes, dhcp6_client_classes) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"}, + {MySqlHostDataSource::GET_HOST_HWADDR_DUID, + "SELECT host_id, dhcp_identifier, dhcp_identifier_type, " + "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " + "dhcp4_client_classes, dhcp6_client_classes " + "FROM hosts " + "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ?"}, + {MySqlHostDataSource::GET_HOST_ADDR, + "SELECT host_id, dhcp_identifier, dhcp_identifier_type, " + "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " + "dhcp4_client_classes, dhcp6_client_classes " + "FROM hosts " + "WHERE ipv4_address = ?"}, + {MySqlHostDataSource::GET_HOST_SUBID4_DHCPID, + "SELECT host_id, dhcp_identifier, dhcp_identifier_type, " + "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " + "dhcp4_client_classes, dhcp6_client_classes " + "FROM hosts " + "WHERE dhcp4_subnet_id = ? AND dhcp_identifier = ?"}, + {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, ipv6_reservations r " + "WHERE h.host_id = r.host_id AND r.prefix_len = ? " + " AND r.address = ?"}, {MySqlHostDataSource::GET_VERSION, - "SELECT version, minor FROM schema_version"}, + "SELECT version, minor FROM schema_version"}, {MySqlHostDataSource::NUM_STATEMENTS, NULL} }; @@ -107,13 +110,12 @@ public: /// 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){ + 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_)); @@ -136,7 +138,7 @@ public: /// @brief Set error indicators /// - /// Sets the error indicator for each of the MYSQL_BIND elements. It points + /// 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. /// @@ -192,8 +194,9 @@ public: /// 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. + /// @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) { @@ -212,48 +215,51 @@ public: try { // host_id : INT UNSIGNED NOT NULL - // The host_id is auto_incremented by MySQL database, - // so we need to pass the NULL value + // 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! + //bind_[0].is_null = &MLM_FALSE; // commented out for performance + // reasons, see memset() above // 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 + 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_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 + 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 + 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 @@ -421,8 +427,8 @@ public: /// @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. + // 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_) { @@ -435,17 +441,17 @@ public: 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."); + isc_throw(BadValue, "invalid dhcp identifier type returned: " + << static_cast(dhcp_identifier_type_) + << ". Only 0 or 1 are allowed."); } // Set subnets ID's if they are given, if not, leave an empty object - SubnetID ipv4_subnet_id = static_cast(NULL); // This is probably wrong way to do it... + SubnetID ipv4_subnet_id(0); if (dhcp4_subnet_id_null_ == MLM_FALSE) - ipv4_subnet_id = static_cast(dhcp4_subnet_id_); + ipv4_subnet_id = static_cast(dhcp4_subnet_id_); - SubnetID ipv6_subnet_id = static_cast(NULL); + SubnetID ipv6_subnet_id(0); if (dhcp6_subnet_id_null_ == MLM_FALSE) ipv6_subnet_id = static_cast(dhcp6_subnet_id_); @@ -460,7 +466,8 @@ public: 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 + // 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) @@ -471,15 +478,10 @@ public: 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_))); + 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 @@ -497,172 +499,155 @@ public: } 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. + uint32_t host_id_; /// Host unique identifier + std::vector dhcp_identifier_; /// HW address (0) / DUID (1) + uint8_t dhcp_identifier_buffer_[DUID::MAX_DUID_LEN]; + /// Buffer for dhcp identifier + size_t dhcp_identifier_length_; /// Length of dhcp identifier + uint8_t dhcp_identifier_type_; /// Type of dhcp_identifier + uint32_t dhcp4_subnet_id_; /// Subnet DHCPv4 identifier + uint32_t dhcp6_subnet_id_; /// Subnet DHCPv6 identifier + uint32_t ipv4_address_; /// Reserved IPv4 address. + IPv6ResrvCollection ipv6_reservations_; /// IPv6 reservations collection + char hostname_[HOSTNAME_MAX_LEN]; /// Name reserved for the host + unsigned long hostname_length_; /// hostname length + char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN]; + /// DHCPv4 client classes + unsigned long dhcp4_client_classes_length_; /// dhcp4_client_classes length + char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN]; + /// DHCPv6 client classes + unsigned long dhcp6_client_classes_length_; /// dhcp6_client_classes length + HWAddrPtr hw_address_; /// Pointer to hardware address + DuidPtr duid_; /// Pointer to DUID // NULL flags for subnets id, ipv4 address, hostname and client classes - my_bool dhcp4_subnet_id_null_; - my_bool dhcp6_subnet_id_null_; - my_bool ipv4_address_null_; - my_bool hostname_null_; - my_bool dhcp4_client_classes_null_; - my_bool dhcp6_client_classes_null_; - - MYSQL_BIND bind_[HOST_COLUMNS]; - std::string columns_[HOST_COLUMNS]; // Column names - my_bool error_[HOST_COLUMNS]; // Error array - HostPtr host_; // Pointer to Host object + 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_)); - } + const MySqlConnection::ParameterMap& parameters) : conn_(parameters) { + + // Open the database. + conn_.openDatabase(); + + // Enable autocommit. To avoid a flush to disk on every commit, the global + // parameter innodb_flush_log_at_trx_commit should be set to 2. This will + // cause the changes to be written to the log, but flushed to disk in the + // background every second. Setting the parameter to that value will speed + // up the system, but at the risk of losing data if the system crashes. + my_bool result = mysql_autocommit(conn_.mysql_, 1); + if (result != 0) { + isc_throw(DbOperationError, mysql_error(conn_.mysql_)); + } - // Prepare all statements likely to be used. - prepareStatements(tagged_statements, MySqlHostDataSource::NUM_STATEMENTS); + // Prepare all statements likely to be used. + conn_.prepareStatements(tagged_statements, + MySqlHostDataSource::NUM_STATEMENTS); - // Create the exchange objects for use in exchanging data between the - // program and the database. - hostExchange_.reset(new MySqlHostReservationExchange()); + // 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; - } - } + // Free up the prepared statements, ignoring errors. (What would we do + // about them? We're destroying this object and are not really concerned + // with errors on a database connection that is about to go away.) + for (int i = 0; i < conn_.statements_.size(); ++i) { + if (conn_.statements_[i] != NULL) { + (void) mysql_stmt_close(conn_.statements_[i]); + conn_.statements_[i] = NULL; + } + } - // There is no need to close the database in this destructor: it is - // closed in the destructor of the mysql_ member variable. + // 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 - } + addHost(INSERT_HOST, bind); } -bool +void MySqlHostDataSource::addHost(StatementIndex stindex, std::vector& bind) { // Bind the parameters to the statement - int status = mysql_stmt_bind_param(statements_[stindex], &bind[0]); + int status = mysql_stmt_bind_param(conn_.statements_[stindex], + &bind[0]); checkError(status, stindex, "unable to bind parameters"); // Execute the statement - status = mysql_stmt_execute(statements_[stindex]); + status = mysql_stmt_execute(conn_.statements_[stindex]); if (status != 0) { - - // Failure: check for the special case of duplicate entry. If this is - // the case, we return false to indicate that the row was not added. - // Otherwise we throw an exception. - if (mysql_errno(mysql_) == ER_DUP_ENTRY) { - return (false); - } - checkError(status, stindex, "unable to execute"); + // Failure: check for the special case of duplicate entry. + if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) { + isc_throw(DuplicateEntry, "Database duplicate entry error"); + } + checkError(status, stindex, "unable to execute"); } - - // Insert succeeded - return (true); } void MySqlHostDataSource::getHostCollection(StatementIndex stindex, MYSQL_BIND* bind, - boost::shared_ptr exchange, - ConstHostCollection& result, bool single) const { + 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); + int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind); checkError(status, stindex, "unable to bind WHERE clause parameter"); // Set up the MYSQL_BIND array for the data being returned and bind it to // the statement. std::vector outbind = exchange->createBindForReceive(); - status = mysql_stmt_bind_result(statements_[stindex], &outbind[0]); + status = mysql_stmt_bind_result(conn_.statements_[stindex], &outbind[0]); checkError(status, stindex, "unable to bind SELECT clause parameters"); // Execute the statement - status = mysql_stmt_execute(statements_[stindex]); + status = mysql_stmt_execute(conn_.statements_[stindex]); checkError(status, stindex, "unable to execute"); // Ensure that all the lease information is retrieved in one go to avoid // overhead of going back and forth between client and server. - status = mysql_stmt_store_result(statements_[stindex]); + status = mysql_stmt_store_result(conn_.statements_[stindex]); checkError(status, stindex, "unable to set up for storing all results"); // Set up the fetch "release" object to release resources associated // with the call to mysql_stmt_fetch when this method exits, then // retrieve the data. - MySqlFreeResult fetch_release(statements_[stindex]); + MySqlFreeResult fetch_release(conn_.statements_[stindex]); int count = 0; - while ((status = mysql_stmt_fetch(statements_[stindex])) == 0) { + while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) == 0) { try { result.push_back(exchange->getHostData()); } catch (const isc::BadValue& ex) { // Rethrow the exception with a bit more data. isc_throw(BadValue, ex.what() << ". Statement is <" << - text_statements_[stindex] << ">"); + conn_.text_statements_[stindex] << ">"); } if (single && (++count > 1)) { isc_throw(MultipleRecords, "multiple records were found in the " "database where only one was expected for query " - << text_statements_[stindex]); + << conn_.text_statements_[stindex]); } } @@ -672,7 +657,7 @@ MySqlHostDataSource::getHostCollection(StatementIndex stindex, MYSQL_BIND* bind, checkError(status, stindex, "unable to fetch results"); } else if (status == MYSQL_DATA_TRUNCATED) { // Data truncated - throw an exception indicating what was at fault - isc_throw(DataTruncated, text_statements_[stindex] + isc_throw(DataTruncated, conn_.text_statements_[stindex] << " returned truncated data: columns affected are " << exchange->getErrorColumns()); } @@ -685,26 +670,39 @@ MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const 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; + if (duid){ + // DUID + // set proper dhcp_identifier_type + inbind[1].buffer = reinterpret_cast(1); + + 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; + } else if (hwaddr) { + // HW Address + // set proper dhcp_identifier_type + inbind[1].buffer = reinterpret_cast(0); + + const vector& hwaddr_vector = hwaddr->hwaddr_; + unsigned long hwaddr_length = hwaddr_vector.size(); + inbind[0].buffer_type = MYSQL_TYPE_BLOB; + inbind[0].buffer = reinterpret_cast + (const_cast(&hwaddr_vector[0])); + inbind[0].buffer_length = hwaddr_length; + inbind[0].length = &hwaddr_length; + } + + // dhcp_identifier_type + inbind[1].buffer_type = MYSQL_TYPE_TINY; + inbind[1].is_unsigned = MLM_TRUE; ConstHostCollection result; - getHostCollection(GET_HOST_HWADDR_DUID, inbind, hostExchange_, result, false); + getHostCollection(GET_HOST_HWADDR_DUID, inbind, hostExchange_, + result, false); return (result); } @@ -746,8 +744,8 @@ MySqlHostDataSource::get4(const SubnetID& subnet_id, 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 = reinterpret_cast + (const_cast(&hwaddr_vector[0])); inbind[1].buffer_length = hwaddr_length; inbind[1].length = &hwaddr_length; } else if (duid) { @@ -755,24 +753,21 @@ MySqlHostDataSource::get4(const SubnetID& subnet_id, 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 = 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); + 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(); - } + if (!collection.empty()) + result = *collection.begin(); return (result); } @@ -794,16 +789,13 @@ MySqlHostDataSource::get4(const SubnetID& subnet_id, inbind[1].is_unsigned = MLM_TRUE; ConstHostCollection collection; - getHostCollection(GET_HOST_SUBID_ADDR, inbind, hostExchange_, collection, - true); + 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(); - } + if (!collection.empty()) + result = *collection.begin(); return (result); } @@ -826,8 +818,8 @@ MySqlHostDataSource::get6(const SubnetID& subnet_id, 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 = reinterpret_cast + (const_cast(&hwaddr_vector[0])); inbind[1].buffer_length = hwaddr_length; inbind[1].length = &hwaddr_length; } else if (duid) { @@ -835,24 +827,21 @@ MySqlHostDataSource::get6(const SubnetID& subnet_id, 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 = 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); + 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(); - } + if (!collection.empty()) + result = *collection.begin(); return (result); } @@ -875,15 +864,13 @@ MySqlHostDataSource::get6(const asiolink::IOAddress& prefix, inbind[1].is_unsigned = MLM_TRUE; ConstHostCollection collection; - getHostCollection(GET_HOST_PREFIX, inbind, hostExchange_, collection, true); + 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(); - } + if (!collection.empty()) + result = *collection.begin(); return (result); } @@ -906,16 +893,13 @@ MySqlHostDataSource::get6(const SubnetID& subnet_id, inbind[1].is_unsigned = MLM_TRUE; ConstHostCollection collection; - getHostCollection(GET_HOST_SUBID_ADDR, inbind, hostExchange_, collection, - true); + 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(); - } + if (!collection.empty()) + result = *collection.begin(); return (result); } @@ -925,7 +909,7 @@ MySqlHostDataSource::get6(const SubnetID& subnet_id, std::string MySqlHostDataSource::getName() const { std::string name = ""; try { - name = getParameter("name"); + name = conn_.getParameter("name"); } catch (...) { // Return an empty name } @@ -933,7 +917,8 @@ std::string MySqlHostDataSource::getName() const { } std::string MySqlHostDataSource::getDescription() const { - return (std::string("MySQL Database")); + return (std::string("Host data source that stores host information" + "in MySQL database")); } std::pair MySqlHostDataSource::getVersion() const { @@ -946,10 +931,11 @@ std::pair MySqlHostDataSource::getVersion() const { uint32_t minor; // Minor version number // Execute the prepared statement - int status = mysql_stmt_execute(statements_[stindex]); + int status = mysql_stmt_execute(conn_.statements_[stindex]); if (status != 0) { - isc_throw(DbOperationError, - "unable to execute <" << text_statements_[stindex] << "> - reason: " << mysql_error(mysql_)); + isc_throw(DbOperationError, "unable to execute <" + << conn_.text_statements_[stindex] + << "> - reason: " << mysql_error(conn_.mysql_)); } // Bind the output of the statement to the appropriate variables. @@ -966,38 +952,24 @@ std::pair MySqlHostDataSource::getVersion() const { bind[1].buffer = &minor; bind[1].buffer_length = sizeof(minor); - status = mysql_stmt_bind_result(statements_[stindex], bind); + status = mysql_stmt_bind_result(conn_.statements_[stindex], bind); if (status != 0) { - isc_throw(DbOperationError, - "unable to bind result set: " << mysql_error(mysql_)); + isc_throw(DbOperationError, "unable to bind result set: " + << mysql_error(conn_.mysql_)); } // Fetch the data and set up the "release" object to release associated // resources when this method exits then retrieve the data. - MySqlFreeResult fetch_release(statements_[stindex]); - status = mysql_stmt_fetch(statements_[stindex]); + MySqlFreeResult fetch_release(conn_.statements_[stindex]); + status = mysql_stmt_fetch(conn_.statements_[stindex]); if (status != 0) { - isc_throw(DbOperationError, - "unable to obtain result set: " << mysql_error(mysql_)); + isc_throw(DbOperationError, "unable to obtain result set: " + << mysql_error(conn_.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 index f78d9bb302..42f614d30a 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -23,22 +23,20 @@ #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; +/// @brief MySQL Host Data Source +/// +/// This class provides the \ref isc::dhcp::BaseHostDataSource interface to the MySQL +/// database. Use of this backend presupposes that a MySQL database is +/// available and that the Kea schema has been created within it. -class MySqlHostDataSource : public BaseHostDataSource, public MySqlConnection { +class MySqlHostDataSource: public BaseHostDataSource { public: /// @brief Constructor @@ -63,7 +61,7 @@ public: /// @throw isc::dhcp::DbOpenError Error opening the database /// @throw isc::dhcp::DbOperationError An operation on the open database has /// failed. - MySqlHostDataSource(const ParameterMap& parameters); + MySqlHostDataSource(const DatabaseConnection::ParameterMap& parameters); /// @brief Destructor (closes database) virtual ~MySqlHostDataSource(); @@ -93,221 +91,210 @@ public: 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; + /// + /// This method may return multiple @c Host objects if they are connected + /// to different subnets. + /// + /// @param address IPv4 address for which the @c Host object is searched. + /// + /// @return Collection of const @c Host objects. + virtual ConstHostCollection + getAll4(const asiolink::IOAddress& address) const; + + /// @brief Returns a host connected to the IPv4 subnet. + /// + /// Implementations of this method should guard against the case when + /// mutliple instances of the @c Host are present, e.g. when two + /// @c Host objects are found, one for the DUID, another one for the + /// HW address. In such case, an implementation of this method + /// should throw an MultipleRecords exception. + /// + /// @param subnet_id Subnet identifier. + /// @param hwaddr HW address of the client or NULL if no HW address + /// available. + /// @param duid client id or NULL if not available. + /// + /// @return Const @c Host object using a specified HW address or DUID. + virtual ConstHostPtr + get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr, + const DuidPtr& duid = DuidPtr()) const; + + /// @brief Returns a host connected to the IPv4 subnet and having + /// a reservation for a specified IPv4 address. + /// + /// One of the use cases for this method is to detect collisions between + /// dynamically allocated addresses and reserved addresses. When the new + /// address is assigned to a client, the allocation mechanism should check + /// if this address is not reserved for some other host and do not allocate + /// this address if reservation is present. + /// + /// Implementations of this method should guard against invalid addresses, + /// such as IPv6 address. + /// + /// @param subnet_id Subnet identifier. + /// @param address reserved IPv4 address. + /// + /// @return Const @c Host object using a specified IPv4 address. + virtual ConstHostPtr + get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + + /// @brief Returns a host connected to the IPv6 subnet. + /// + /// Implementations of this method should guard against the case when + /// mutliple instances of the @c Host are present, e.g. when two + /// @c Host objects are found, one for the DUID, another one for the + /// HW address. In such case, an implementation of this method + /// should throw an MultipleRecords exception. + /// + /// @param subnet_id Subnet identifier. + /// @param hwaddr HW address of the client or NULL if no HW address + /// available. + /// @param duid DUID or NULL if not available. + /// + /// @return Const @c Host object using a specified HW address or DUID. + virtual ConstHostPtr + get6(const SubnetID& subnet_id, const DuidPtr& duid, + const HWAddrPtr& hwaddr = HWAddrPtr()) const; + + /// @brief Returns a host using the specified IPv6 prefix. + /// + /// @param prefix IPv6 prefix for which the @c Host object is searched. + /// @param prefix_len IPv6 prefix length. + /// + /// @return Const @c Host object using a specified HW address or DUID. + virtual ConstHostPtr + get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const; + + /// @brief 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. + /// addHost method should throw an DuplicateEntry exception. Note, that + /// usually it is impossible to guard against adding duplicated host, where + /// one instance is identified by HW address, another one by DUID. /// /// @param host Pointer to the new @c Host object being added. virtual void add(const HostPtr& host); /// @brief Return backend type - /// - /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) - /// - /// @return Type of the backend. - virtual std::string getType() const { - return (std::string("mysql")); - } - - /// @brief Returns backend name. - /// - /// Each backend have specific name, e.g. "mysql" or "sqlite". - /// - /// @return Name of the backend. - virtual std::string getName() const; - - /// @brief Returns description of the backend. - /// - /// This description may be multiline text that describes the backend. - /// - /// @return Description of the backend. - virtual std::string getDescription() const; - - /// @brief Returns backend version. - /// - /// @return Version number 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(); + /// + /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) + /// + /// @return Type of the backend. + virtual std::string getType() const { + return (std::string("mysql")); + } + + /// @brief Returns backend name. + /// + /// Each backend have specific name, e.g. "mysql" or "sqlite". + /// + /// @return Name of the backend. + virtual std::string getName() const; + + /// @brief Returns description of the backend. + /// + /// This description may be multiline text that describes the backend. + /// + /// @return Description of the backend. + virtual std::string getDescription() const; + + /// @brief Returns backend version. + /// + /// @return Version number stored in the database, as a pair of unsigned + /// integers. "first" is the major version number, "second" the + /// minor number. + /// + /// @throw isc::dhcp::DbOperationError An operation on the open database + /// has failed. + virtual std::pair getVersion() const; + + MySqlConnection* getDatabaseConnection() { + return &conn_; + } /// @brief Statement Tags /// /// The contents of the enum are indexes into the list of SQL statements - enum StatementIndex { - INSERT_HOST, // Insert new host to collection - GET_HOST_HWADDR_DUID, // 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 - }; + enum StatementIndex { + INSERT_HOST, // Insert new host to collection + GET_HOST_HWADDR_DUID, // Gets hosts by DUID and/or HW address + GET_HOST_ADDR, // Gets hosts by IPv4 address + GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID + GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID + GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address + GET_HOST_PREFIX, // Gets host by IPv6 prefix + GET_VERSION, // Obtain version number + NUM_STATEMENTS // Number of statements + }; private: - /// @brief Add Host Code - /// - /// This method performs adding a host operation. - /// It binds the contents of the host object to - /// the prepared statement and adds it to the database. - /// - /// @param stindex Index of statemnent being executed - /// @param bind MYSQL_BIND array that has been created for the host - /// - /// @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_) << ")"); - } - } + /// @brief Add Host Code + /// + /// This method performs adding a host operation. + /// It binds the contents of the host object to + /// the prepared statement and adds it to the database. + /// + /// @param stindex Index of statemnent being executed + /// @param bind MYSQL_BIND array that has been created for the host + /// + /// @htrow isc::dhcp::DuplicateEntry Database throws duplicate entry error + void addHost(StatementIndex stindex, std::vector& 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 <" + << conn_.text_statements_[index] << ">, reason: " + << mysql_error(conn_.mysql_) << " (error code " + << mysql_errno(conn_.mysql_) << ")"); + } + } // Members @@ -315,7 +302,12 @@ private: /// 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 + + /// @brief MySQL Host Reservation Exchange object + boost::shared_ptr hostExchange_; + + /// @brief MySQL connection + MySqlConnection conn_; }; diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h index 56e2a72177..cebeb4bfe2 100755 --- a/src/lib/dhcpsrv/mysql_lease_mgr.h +++ b/src/lib/dhcpsrv/mysql_lease_mgr.h @@ -21,19 +21,13 @@ #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 Lease exchange objects. These classes are defined // in the .cc file. class MySqlLease4Exchange; diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index 0ed10ae556..8cb2d84566 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -320,7 +320,7 @@ public: // 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 > DataSource::MAX_DB_TIME) { + if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) { isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64); } diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h index 8789f6b2d7..e133406df5 100644 --- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h @@ -23,6 +23,10 @@ namespace isc { namespace dhcp { namespace test { +/// @brief Test Fixture class with utility functions for HostDataSource backends +/// +/// It contains utility functions for test purposes. +/// All concrete HostDataSource test classes should be derived from it. class GenericHostDataSourceTest : public ::testing::Test { public: diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc index 79680ac453..ece4422cf6 100644 --- a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc @@ -58,6 +58,8 @@ const char* INVALID_USER = "user=invaliduser"; const char* VALID_PASSWORD = "password=keatest"; const char* INVALID_PASSWORD = "password=invalid"; +MySqlHostDataSource* myhdsptr_; + // 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) { @@ -166,15 +168,15 @@ public: "*** accompanying exception output.\n"; throw; } - hdsptr_ = &(HostDataSourceFactory::instance()); + myhdsptr_ = (MySqlHostDataSource *) &(HostDataSourceFactory::instance()); } /// @brief Destructor /// - /// Rolls back all pending transactions. The deletion of hdsptr_ will close + /// Rolls back all pending transactions. The deletion of myhdsptr_ will close /// the database. Then reopen it and delete everything created by the test. virtual ~MySqlHostDataSourceTest() { - //hdsptr_->rollback(); // need to decide where to implement rollback method (HostMgr or BaseHostDataSource maybe?) + myhdsptr_->getDatabaseConnection()->rollback(); HostDataSourceFactory::destroy(); destroySchema(); } @@ -189,7 +191,7 @@ public: void reopen(Universe) { HostDataSourceFactory::destroy(); HostDataSourceFactory::create(validConnectionString()); - hdsptr_ = &(HostDataSourceFactory::instance()); + myhdsptr_ = (MySqlHostDataSource *) &(HostDataSourceFactory::instance()); } };