mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 05:55:28 +00:00
[3681] MySQLConnection class implemented
- patch as sent by Adam Kalmus
This commit is contained in:
404
src/lib/dhcpsrv/mysql_lease_mgr.cc
Normal file → Executable file
404
src/lib/dhcpsrv/mysql_lease_mgr.cc
Normal file → Executable file
@@ -19,6 +19,7 @@
|
||||
#include <dhcp/hwaddr.h>
|
||||
#include <dhcpsrv/dhcpsrv_log.h>
|
||||
#include <dhcpsrv/mysql_lease_mgr.h>
|
||||
#include <dhcpsrv/mysql_connection.h>
|
||||
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <mysqld_error.h>
|
||||
@@ -75,147 +76,6 @@ using namespace std;
|
||||
/// - If there is output, copy the data from the bound variables to the output
|
||||
/// lease object.
|
||||
|
||||
namespace {
|
||||
///@{
|
||||
|
||||
/// @brief Maximum size of database fields
|
||||
///
|
||||
/// The following constants define buffer sizes for variable length database
|
||||
/// fields. The values should be greater than or equal to the length set in
|
||||
/// the schema definition.
|
||||
///
|
||||
/// The exception is the length of any VARCHAR fields: buffers for these should
|
||||
/// be set greater than or equal to the length of the field plus 1: this allows
|
||||
/// for the insertion of a trailing null whatever data is returned.
|
||||
|
||||
/// @brief Maximum size of an IPv6 address represented as a text string.
|
||||
///
|
||||
/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
|
||||
/// colon separators.
|
||||
const size_t ADDRESS6_TEXT_MAX_LEN = 39;
|
||||
|
||||
/// @brief MySQL True/False constants
|
||||
///
|
||||
/// Declare typed values so as to avoid problems of data conversion. These
|
||||
/// are local to the file but are given the prefix MLM (MySql Lease Manager) to
|
||||
/// avoid any likely conflicts with variables in header files named TRUE or
|
||||
/// FALSE.
|
||||
|
||||
const my_bool MLM_FALSE = 0; ///< False value
|
||||
const my_bool MLM_TRUE = 1; ///< True value
|
||||
|
||||
/// @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 MySQL Selection Statements
|
||||
///
|
||||
/// Each statement is associated with an index, which is used to reference the
|
||||
/// associated prepared statement.
|
||||
|
||||
struct TaggedStatement {
|
||||
MySqlLeaseMgr::StatementIndex index;
|
||||
const char* text;
|
||||
};
|
||||
|
||||
TaggedStatement tagged_statements[] = {
|
||||
{MySqlLeaseMgr::DELETE_LEASE4,
|
||||
"DELETE FROM lease4 WHERE address = ?"},
|
||||
{MySqlLeaseMgr::DELETE_LEASE6,
|
||||
"DELETE FROM lease6 WHERE address = ?"},
|
||||
{MySqlLeaseMgr::GET_LEASE4_ADDR,
|
||||
"SELECT address, hwaddr, client_id, "
|
||||
"valid_lifetime, expire, subnet_id, "
|
||||
"fqdn_fwd, fqdn_rev, hostname "
|
||||
"FROM lease4 "
|
||||
"WHERE address = ?"},
|
||||
{MySqlLeaseMgr::GET_LEASE4_CLIENTID,
|
||||
"SELECT address, hwaddr, client_id, "
|
||||
"valid_lifetime, expire, subnet_id, "
|
||||
"fqdn_fwd, fqdn_rev, hostname "
|
||||
"FROM lease4 "
|
||||
"WHERE client_id = ?"},
|
||||
{MySqlLeaseMgr::GET_LEASE4_CLIENTID_SUBID,
|
||||
"SELECT address, hwaddr, client_id, "
|
||||
"valid_lifetime, expire, subnet_id, "
|
||||
"fqdn_fwd, fqdn_rev, hostname "
|
||||
"FROM lease4 "
|
||||
"WHERE client_id = ? AND subnet_id = ?"},
|
||||
{MySqlLeaseMgr::GET_LEASE4_HWADDR,
|
||||
"SELECT address, hwaddr, client_id, "
|
||||
"valid_lifetime, expire, subnet_id, "
|
||||
"fqdn_fwd, fqdn_rev, hostname "
|
||||
"FROM lease4 "
|
||||
"WHERE hwaddr = ?"},
|
||||
{MySqlLeaseMgr::GET_LEASE4_HWADDR_SUBID,
|
||||
"SELECT address, hwaddr, client_id, "
|
||||
"valid_lifetime, expire, subnet_id, "
|
||||
"fqdn_fwd, fqdn_rev, hostname "
|
||||
"FROM lease4 "
|
||||
"WHERE hwaddr = ? AND subnet_id = ?"},
|
||||
{MySqlLeaseMgr::GET_LEASE6_ADDR,
|
||||
"SELECT address, duid, valid_lifetime, "
|
||||
"expire, subnet_id, pref_lifetime, "
|
||||
"lease_type, iaid, prefix_len, "
|
||||
"fqdn_fwd, fqdn_rev, hostname, "
|
||||
"hwaddr, hwtype, hwaddr_source "
|
||||
"FROM lease6 "
|
||||
"WHERE address = ? AND lease_type = ?"},
|
||||
{MySqlLeaseMgr::GET_LEASE6_DUID_IAID,
|
||||
"SELECT address, duid, valid_lifetime, "
|
||||
"expire, subnet_id, pref_lifetime, "
|
||||
"lease_type, iaid, prefix_len, "
|
||||
"fqdn_fwd, fqdn_rev, hostname, "
|
||||
"hwaddr, hwtype, hwaddr_source "
|
||||
"FROM lease6 "
|
||||
"WHERE duid = ? AND iaid = ? AND lease_type = ?"},
|
||||
{MySqlLeaseMgr::GET_LEASE6_DUID_IAID_SUBID,
|
||||
"SELECT address, duid, valid_lifetime, "
|
||||
"expire, subnet_id, pref_lifetime, "
|
||||
"lease_type, iaid, prefix_len, "
|
||||
"fqdn_fwd, fqdn_rev, hostname, "
|
||||
"hwaddr, hwtype, hwaddr_source "
|
||||
"FROM lease6 "
|
||||
"WHERE duid = ? AND iaid = ? AND subnet_id = ? "
|
||||
"AND lease_type = ?"},
|
||||
{MySqlLeaseMgr::GET_VERSION,
|
||||
"SELECT version, minor FROM schema_version"},
|
||||
{MySqlLeaseMgr::INSERT_LEASE4,
|
||||
"INSERT INTO lease4(address, hwaddr, client_id, "
|
||||
"valid_lifetime, expire, subnet_id, "
|
||||
"fqdn_fwd, fqdn_rev, hostname) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
|
||||
{MySqlLeaseMgr::INSERT_LEASE6,
|
||||
"INSERT INTO lease6(address, duid, valid_lifetime, "
|
||||
"expire, subnet_id, pref_lifetime, "
|
||||
"lease_type, iaid, prefix_len, "
|
||||
"fqdn_fwd, fqdn_rev, hostname, "
|
||||
"hwaddr, hwtype, hwaddr_source) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
|
||||
{MySqlLeaseMgr::UPDATE_LEASE4,
|
||||
"UPDATE lease4 SET address = ?, hwaddr = ?, "
|
||||
"client_id = ?, valid_lifetime = ?, expire = ?, "
|
||||
"subnet_id = ?, fqdn_fwd = ?, fqdn_rev = ?, "
|
||||
"hostname = ? "
|
||||
"WHERE address = ?"},
|
||||
{MySqlLeaseMgr::UPDATE_LEASE6,
|
||||
"UPDATE lease6 SET address = ?, duid = ?, "
|
||||
"valid_lifetime = ?, expire = ?, subnet_id = ?, "
|
||||
"pref_lifetime = ?, lease_type = ?, iaid = ?, "
|
||||
"prefix_len = ?, fqdn_fwd = ?, fqdn_rev = ?, "
|
||||
"hostname = ?, hwaddr = ?, hwtype = ?, hwaddr_source = ? "
|
||||
"WHERE address = ?"},
|
||||
// End of list sentinel
|
||||
{MySqlLeaseMgr::NUM_STATEMENTS, NULL}
|
||||
};
|
||||
|
||||
}; // Anonymous namespace
|
||||
|
||||
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
@@ -1181,51 +1041,12 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/// @brief Fetch and Release MySQL Results
|
||||
///
|
||||
/// When a MySQL statement is expected, to fetch the results the function
|
||||
/// mysql_stmt_fetch() must be called. As well as getting data, this
|
||||
/// allocates internal state. Subsequent calls to mysql_stmt_fetch can be
|
||||
/// made, but when all the data is retrieved, mysql_stmt_free_result must be
|
||||
/// called to free up the resources allocated.
|
||||
///
|
||||
/// Created prior to the first fetch, this class's destructor calls
|
||||
/// mysql_stmt_free_result, so eliminating the need for an explicit release
|
||||
/// in the method calling mysql_stmt_free_result. In this way, it guarantees
|
||||
/// that the resources are released even if the MySqlLeaseMgr method concerned
|
||||
/// exits via an exception.
|
||||
|
||||
class MySqlFreeResult {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// Store the pointer to the statement for which data is being fetched.
|
||||
///
|
||||
/// Note that according to the MySQL documentation, mysql_stmt_free_result
|
||||
/// only releases resources if a cursor has been allocated for the
|
||||
/// statement. This implies that it is a no-op if none have been. Either
|
||||
/// way, any error from mysql_stmt_free_result is ignored. (Generating
|
||||
/// an exception is not much help, as it will only confuse things if the
|
||||
/// method calling mysql_stmt_fetch is exiting via an exception.)
|
||||
MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
|
||||
{}
|
||||
|
||||
/// @brief Destructor
|
||||
///
|
||||
/// Frees up fetch context if a fetch has been successfully executed.
|
||||
~MySqlFreeResult() {
|
||||
(void) mysql_stmt_free_result(statement_);
|
||||
}
|
||||
|
||||
private:
|
||||
MYSQL_STMT* statement_; ///< Statement for which results are freed
|
||||
};
|
||||
|
||||
// MySqlLeaseMgr Constructor and Destructor
|
||||
|
||||
MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
|
||||
: LeaseMgr(parameters) {
|
||||
MySqlLeaseMgr::MySqlLeaseMgr(const MySqlConnection::ParameterMap& parameters)
|
||||
: MySqlConnection(parameters) {
|
||||
|
||||
// Open the database.
|
||||
openDatabase();
|
||||
@@ -1340,139 +1161,6 @@ MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Open the database using the parameters passed to the constructor.
|
||||
|
||||
void
|
||||
MySqlLeaseMgr::openDatabase() {
|
||||
|
||||
// Set up the values of the parameters
|
||||
const char* host = "localhost";
|
||||
string shost;
|
||||
try {
|
||||
shost = getParameter("host");
|
||||
host = shost.c_str();
|
||||
} catch (...) {
|
||||
// No host. Fine, we'll use "localhost"
|
||||
}
|
||||
|
||||
const char* user = NULL;
|
||||
string suser;
|
||||
try {
|
||||
suser = getParameter("user");
|
||||
user = suser.c_str();
|
||||
} catch (...) {
|
||||
// No user. Fine, we'll use NULL
|
||||
}
|
||||
|
||||
const char* password = NULL;
|
||||
string spassword;
|
||||
try {
|
||||
spassword = getParameter("password");
|
||||
password = spassword.c_str();
|
||||
} catch (...) {
|
||||
// No password. Fine, we'll use NULL
|
||||
}
|
||||
|
||||
const char* name = NULL;
|
||||
string sname;
|
||||
try {
|
||||
sname = getParameter("name");
|
||||
name = sname.c_str();
|
||||
} catch (...) {
|
||||
// No database name. Throw a "NoName" exception
|
||||
isc_throw(NoDatabaseName, "must specified a name for the database");
|
||||
}
|
||||
|
||||
// Set options for the connection:
|
||||
//
|
||||
// Automatic reconnection: after a period of inactivity, the client will
|
||||
// disconnect from the database. This option causes it to automatically
|
||||
// reconnect when another operation is about to be done.
|
||||
my_bool auto_reconnect = MLM_TRUE;
|
||||
int result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
|
||||
if (result != 0) {
|
||||
isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<
|
||||
mysql_error(mysql_));
|
||||
}
|
||||
|
||||
// Set SQL mode options for the connection: SQL mode governs how what
|
||||
// constitutes insertable data for a given column, and how to handle
|
||||
// invalid data. We want to ensure we get the strictest behavior and
|
||||
// to reject invalid data with an error.
|
||||
const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
|
||||
result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
|
||||
if (result != 0) {
|
||||
isc_throw(DbOpenError, "unable to set SQL mode options: " <<
|
||||
mysql_error(mysql_));
|
||||
}
|
||||
|
||||
// Open the database.
|
||||
//
|
||||
// The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
|
||||
// the affected rows are the number of rows found that match the
|
||||
// WHERE clause of the SQL statement, not the rows changed. The reason
|
||||
// here is that MySQL apparently does not update a row if data has not
|
||||
// changed and so the "affected rows" (retrievable from MySQL) is zero.
|
||||
// This makes it hard to distinguish whether the UPDATE changed no rows
|
||||
// because no row matching the WHERE clause was found, or because a
|
||||
// row was found but no data was altered.
|
||||
MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
|
||||
0, NULL, CLIENT_FOUND_ROWS);
|
||||
if (status != mysql_) {
|
||||
isc_throw(DbOpenError, mysql_error(mysql_));
|
||||
}
|
||||
}
|
||||
|
||||
// Prepared statement setup. The textual form of an SQL statement is stored
|
||||
// in a vector of strings (text_statements_) and is used in the output of
|
||||
// error messages. The SQL statement is also compiled into a "prepared
|
||||
// statement" (stored in statements_), which avoids the overhead of compilation
|
||||
// during use. As prepared statements have resources allocated to them, the
|
||||
// class destructor explicitly destroys them.
|
||||
|
||||
void
|
||||
MySqlLeaseMgr::prepareStatement(StatementIndex index, const char* text) {
|
||||
// Validate that there is space for the statement in the statements array
|
||||
// and that nothing has been placed there before.
|
||||
if ((index >= statements_.size()) || (statements_[index] != NULL)) {
|
||||
isc_throw(InvalidParameter, "invalid prepared statement index (" <<
|
||||
static_cast<int>(index) << ") or indexed prepared " <<
|
||||
"statement is not null");
|
||||
}
|
||||
|
||||
// All OK, so prepare the statement
|
||||
text_statements_[index] = std::string(text);
|
||||
statements_[index] = mysql_stmt_init(mysql_);
|
||||
if (statements_[index] == NULL) {
|
||||
isc_throw(DbOperationError, "unable to allocate MySQL prepared "
|
||||
"statement structure, reason: " << mysql_error(mysql_));
|
||||
}
|
||||
|
||||
int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
|
||||
if (status != 0) {
|
||||
isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<
|
||||
text << ">, reason: " << mysql_error(mysql_));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MySqlLeaseMgr::prepareStatements() {
|
||||
// Allocate space for all statements
|
||||
statements_.clear();
|
||||
statements_.resize(NUM_STATEMENTS, NULL);
|
||||
|
||||
text_statements_.clear();
|
||||
text_statements_.resize(NUM_STATEMENTS, std::string(""));
|
||||
|
||||
// Created the MySQL prepared statements for each DML statement.
|
||||
for (int i = 0; tagged_statements[i].text != NULL; ++i) {
|
||||
prepareStatement(tagged_statements[i].index,
|
||||
tagged_statements[i].text);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -2071,93 +1759,7 @@ MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
|
||||
}
|
||||
}
|
||||
|
||||
// Miscellaneous database methods.
|
||||
|
||||
std::string
|
||||
MySqlLeaseMgr::getName() const {
|
||||
std::string name = "";
|
||||
try {
|
||||
name = getParameter("name");
|
||||
} catch (...) {
|
||||
// Return an empty name
|
||||
}
|
||||
return (name);
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
MySqlLeaseMgr::getDescription() const {
|
||||
return (std::string("MySQL Database"));
|
||||
}
|
||||
|
||||
|
||||
std::pair<uint32_t, uint32_t>
|
||||
MySqlLeaseMgr::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
|
||||
MySqlLeaseMgr::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
|
||||
MySqlLeaseMgr::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
|
||||
|
Reference in New Issue
Block a user