// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <mysql_cb_dhcp4.h>
+#include <mysql_cb_impl.h>
#include <cc/data.h>
#include <database/db_exceptions.h>
#include <dhcp/classify.h>
#include <dhcpsrv/network.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/lease.h>
-#include <mysql_cb_dhcp4.h>
#include <mysql/mysql_connection.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/lexical_cast.hpp>
namespace dhcp {
/// @brief Implementation of the MySQL Configuration Backend.
-class MySqlConfigBackendDHCPv4Impl {
+class MySqlConfigBackendDHCPv4Impl : public MySqlConfigBackendImpl {
public:
/// @brief Statement tags.
///
/// @param parameters A data structure relating keywords and values
/// concerned with the database.
- MySqlConfigBackendDHCPv4Impl(const DatabaseConnection::ParameterMap& parameters);
-
- /// @brief Destructor.
- ~MySqlConfigBackendDHCPv4Impl();
+ explicit MySqlConfigBackendDHCPv4Impl(const DatabaseConnection::ParameterMap&
+ parameters);
/// @brief Sends query to the database to retrieve multiple subnets.
///
conn_.insertQuery(INSERT_POOL4, in_bindings);
}
- /// @brief Sends query to delete rows from a table.
- ///
- /// @param index Index of the statement to be executed.
- void deleteFromTable(const StatementIndex& index) {
- MySqlBindingCollection in_bindings;
- conn_.updateDeleteQuery(index, in_bindings);
- }
-
- /// @brief Sends query to delete rows from a table.
- ///
- /// @param index Index of the statement to be executed.
- /// @param key String value to be used as input binding to the delete
- /// statement.
- void deleteFromTable(const StatementIndex& index,
- const std::string& key) {
- MySqlBindingCollection in_bindings = {
- MySqlBinding::createString(key)
- };
- conn_.updateDeleteQuery(index, in_bindings);
- }
-
/// @brief Sends query to delete subnet by id.
///
/// @param selector Server selector.
/// if the query contains no WHERE clause.
/// @param [out] option_defs Reference to the container where fetched
/// option definitions will be inserted.
- void getOptionDefs4(const StatementIndex& index,
- const MySqlBindingCollection& in_bindings,
- OptionDefContainer& option_defs) {
- // Create output bindings. The order must match that in the prepared
- // statement.
- MySqlBindingCollection out_bindings = {
- MySqlBinding::createInteger<uint64_t>(), // id
- MySqlBinding::createInteger<uint8_t>(), // code
- MySqlBinding::createString(128), // name
- MySqlBinding::createString(128), // space
- MySqlBinding::createInteger<uint8_t>(), // type
- MySqlBinding::createTimestamp(), // modification_ts
- MySqlBinding::createInteger<uint8_t>(), // array
- MySqlBinding::createString(128), // encapsulate
- MySqlBinding::createString(512), // record_types
- MySqlBinding::createString(65536) // user_context
- };
-
- uint64_t last_def_id = 0;
-
- // Run select query.
- conn_.selectQuery(index, in_bindings, out_bindings,
- [&option_defs, &last_def_id]
- (MySqlBindingCollection& out_bindings) {
- // Get pointer to last fetched option definition.
- OptionDefinitionPtr last_def;
- if (!option_defs.empty()) {
- last_def = *option_defs.rbegin();
- }
-
- // See if the last fetched definition is the one for which we now got
- // the row of data. If not, it means that we need to create new option
- // definition.
- if ((last_def_id == 0) ||
- (last_def_id != out_bindings[0]->getInteger<uint64_t>())) {
-
- last_def_id = out_bindings[0]->getInteger<uint64_t>();
-
- // Check array type, because depending on this value we have to use
- // different constructor.
- bool array_type = static_cast<bool>(out_bindings[6]->getInteger<uint8_t>());
- if (array_type) {
- // Create array option.
- last_def.reset(new OptionDefinition(out_bindings[2]->getString(),
- out_bindings[1]->getInteger<uint8_t>(),
- static_cast<OptionDataType>
- (out_bindings[4]->getInteger<uint8_t>()),
- array_type));
- } else {
- // Create non-array option.
- last_def.reset(new OptionDefinition(out_bindings[2]->getString(),
- out_bindings[1]->getInteger<uint8_t>(),
- static_cast<OptionDataType>
- (out_bindings[4]->getInteger<uint8_t>()),
- out_bindings[7]->getStringOrDefault("").c_str()));
- }
-
- // space
- last_def->setOptionSpaceName(out_bindings[3]->getStringOrDefault(""));
-
- // record_types
- ElementPtr record_types_element = out_bindings[8]->getJSON();
- if (record_types_element) {
- if (record_types_element->getType() != Element::list) {
- isc_throw(BadValue, "invalid record_types value "
- << out_bindings[8]->getString());
- }
- // This element must contain a list of integers specifying
- // types of the record fields.
- for (auto i = 0; i < record_types_element->size(); ++i) {
- auto type_element = record_types_element->get(i);
- if (type_element->getType() != Element::integer) {
- isc_throw(BadValue, "record type values must be integers");
- }
- last_def->addRecordField(static_cast<OptionDataType>
- (type_element->intValue()));
- }
- }
-
- // Update modification time.
- last_def->setModificationTime(out_bindings[5]->getTimestamp());
-
- // Store created option definition.
- option_defs.push_back(last_def);
- }
- });
- }
/// @brief Sends query to retrieve single option definition by code and
/// option space.
MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(code)),
MySqlBinding::createString(space)
};
- getOptionDefs4(GET_OPTION_DEF4_CODE_SPACE, in_bindings, option_defs);
+ getOptionDefs(GET_OPTION_DEF4_CODE_SPACE, in_bindings, option_defs);
return (option_defs.empty() ? OptionDefinitionPtr() : *option_defs.begin());
}
+ /// @brief Sends query to retrieve all option definitions.
+ ///
+ /// @param selector Server selector.
+ /// @return Container holding returned option definitions.
+ OptionDefContainer getAllOptionDefs4(const ServerSelector& selector) {
+ OptionDefContainer option_defs;
+ MySqlBindingCollection in_bindings;
+ getOptionDefs(MySqlConfigBackendDHCPv4Impl::GET_ALL_OPTION_DEFS4,
+ in_bindings, option_defs);
+ return (option_defs);
+ }
+
+ /// @brief Sends query to retrieve option definitions with modification
+ /// time later than specified timestamp.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Container holding returned option definitions.
+ OptionDefContainer
+ getModifiedOptionDefs4(const ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) {
+ OptionDefContainer option_defs;
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createTimestamp(modification_time)
+ };
+ getOptionDefs(MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_OPTION_DEFS4,
+ in_bindings, option_defs);
+ return (option_defs);
+ }
+
/// @brief Sends query to insert or update option definition.
///
/// @param selector Server selector.
// Run DELETE.
conn_.updateDeleteQuery(DELETE_OPTION_DEF4_CODE_NAME, in_bindings);
}
-
- /// @brief Creates input binding for relay addresses.
- ///
- /// @param network Pointer to a shared network or subnet for which binding
- /// should be created.
- /// @return Pointer to the binding (possibly null binding if there are no
- /// relay addresses specified).
- MySqlBindingPtr createInputRelayBinding(const NetworkPtr& network) {
- ElementPtr relay_element = Element::createList();
- const auto& addresses = network->getRelayAddresses();
- if (!addresses.empty()) {
- for (const auto& address : addresses) {
- relay_element->add(Element::create(address.toText()));
- }
- }
-
- return (relay_element->empty() ? MySqlBinding::createNull() :
- MySqlBinding::condCreateString(relay_element->str()));
- }
-
- /// @brief Creates input binding for 'require_client_classes' parameter.
- ///
- /// @param network Pointer to a shared network or subnet for which binding
- /// should be created.
- /// @return Pointer to the binding (possibly null binding if there are no
- /// required classes specified).
- MySqlBindingPtr createInputRequiredClassesBinding(const NetworkPtr& network) {
- // Create JSON list of required classes.
- ElementPtr required_classes_element = Element::createList();
- const auto& required_classes = network->getRequiredClasses();
- for (auto required_class = required_classes.cbegin();
- required_class != required_classes.cend();
- ++required_class) {
- required_classes_element->add(Element::create(*required_class));
- }
-
- return (required_classes_element ?
- MySqlBinding::createString(required_classes_element->str()) :
- MySqlBinding::createNull());
- }
-
- /// @brief Creates input binding for user context parameter.
- ///
- /// @param network Pointer to a shared network, subnet or other configuration
- /// element for which binding should be created.
- /// @return Pointer to the binding (possibly null binding if context is
- /// null).
- template<typename T>
- MySqlBindingPtr createInputContextBinding(const T& config_element) {
- // Create user context binding if user context exists.
- auto context_element = config_element->getContext();
- return (context_element ? MySqlBinding::createString(context_element->str()) :
- MySqlBinding::createNull());
- }
-
- /// @brief Represents connection to the MySQL database.
- MySqlConnection conn_;
};
/// @brief Array of tagged statements.
MySqlConfigBackendDHCPv4Impl::
MySqlConfigBackendDHCPv4Impl(const DatabaseConnection::ParameterMap& parameters)
- : conn_(parameters) {
- // Open the database.
- conn_.openDatabase();
-
- // Test schema version before we try to prepare statements.
- std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
- MYSQL_SCHEMA_VERSION_MINOR);
-/* std::pair<uint32_t, uint32_t> db_version = getVersion();
- if (code_version != db_version) {
- isc_throw(DbOpenError, "MySQL schema version mismatch: need version: "
- << code_version.first << "." << code_version.second
- << " found version: " << db_version.first << "."
- << db_version.second);
- } */
-
- // Enable autocommit. In case transaction is explicitly used, this
- // setting will be overwritten for the transaction. However, there are
- // cases when lack of autocommit could cause transactions to hang
- // until commit or rollback is explicitly called. This already
- // caused issues for some unit tests which were unable to cleanup
- // the database after the test because of pending transactions.
- // Use of autocommit will eliminate this problem.
- my_bool result = mysql_autocommit(conn_.mysql_, 1);
- if (result != 0) {
- isc_throw(DbOperationError, mysql_error(conn_.mysql_));
- }
-
+ : MySqlConfigBackendImpl(parameters) {
// Prepare query statements. Those are will be only used to retrieve
// information from the database, so they can be used even if the
// database is read only for the current user.
// tagged_statements.begin() + WRITE_STMTS_BEGIN);
}
-MySqlConfigBackendDHCPv4Impl::~MySqlConfigBackendDHCPv4Impl() {
- // 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;
- }
- }
-}
-
MySqlConfigBackendDHCPv4::
MySqlConfigBackendDHCPv4(const DatabaseConnection::ParameterMap& parameters)
: impl_(new MySqlConfigBackendDHCPv4Impl(parameters)) {
OptionDefContainer
MySqlConfigBackendDHCPv4::getAllOptionDefs4(const ServerSelector& selector) const {
- OptionDefContainer option_defs;
- MySqlBindingCollection in_bindings;
- impl_->getOptionDefs4(MySqlConfigBackendDHCPv4Impl::GET_ALL_OPTION_DEFS4,
- in_bindings, option_defs);
- return (option_defs);
+ return (impl_->getAllOptionDefs4(selector));
}
OptionDefContainer
MySqlConfigBackendDHCPv4::
getModifiedOptionDefs4(const ServerSelector& selector,
const boost::posix_time::ptime& modification_time) const {
- OptionDefContainer option_defs;
- MySqlBindingCollection in_bindings = {
- MySqlBinding::createTimestamp(modification_time)
- };
- impl_->getOptionDefs4(MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_OPTION_DEFS4,
- in_bindings, option_defs);
- return (option_defs);
+ return (impl_->getModifiedOptionDefs4(selector, modification_time));
}
util::OptionalValue<std::string>
--- /dev/null
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <mysql_cb_impl.h>
+#include <asiolink/io_address.h>
+#include <mysql.h>
+#include <mysqld_error.h>
+#include <cstdint>
+#include <utility>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+
+MySqlConfigBackendImpl::
+MySqlConfigBackendImpl(const DatabaseConnection::ParameterMap& parameters)
+ : conn_(parameters) {
+ // Open the database.
+ conn_.openDatabase();
+
+ // Test schema version before we try to prepare statements.
+ std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
+ MYSQL_SCHEMA_VERSION_MINOR);
+/* std::pair<uint32_t, uint32_t> db_version = getVersion();
+ if (code_version != db_version) {
+ isc_throw(DbOpenError, "MySQL schema version mismatch: need version: "
+ << code_version.first << "." << code_version.second
+ << " found version: " << db_version.first << "."
+ << db_version.second);
+ } */
+
+ // Enable autocommit. In case transaction is explicitly used, this
+ // setting will be overwritten for the transaction. However, there are
+ // cases when lack of autocommit could cause transactions to hang
+ // until commit or rollback is explicitly called. This already
+ // caused issues for some unit tests which were unable to cleanup
+ // the database after the test because of pending transactions.
+ // Use of autocommit will eliminate this problem.
+ my_bool result = mysql_autocommit(conn_.mysql_, 1);
+ if (result != 0) {
+ isc_throw(DbOperationError, mysql_error(conn_.mysql_));
+ }
+}
+
+MySqlConfigBackendImpl::~MySqlConfigBackendImpl() {
+ // 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;
+ }
+ }
+}
+
+void
+MySqlConfigBackendImpl::deleteFromTable(const int index) {
+ MySqlBindingCollection in_bindings;
+ conn_.updateDeleteQuery(index, in_bindings);
+}
+
+void
+MySqlConfigBackendImpl::deleteFromTable(const int index, const std::string& key) {
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createString(key)
+ };
+ conn_.updateDeleteQuery(index, in_bindings);
+}
+
+void
+MySqlConfigBackendImpl::getOptionDefs(const int index,
+ const MySqlBindingCollection& in_bindings,
+ OptionDefContainer& option_defs) {
+ // Create output bindings. The order must match that in the prepared
+ // statement.
+ MySqlBindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint64_t>(), // id
+ MySqlBinding::createInteger<uint8_t>(), // code
+ MySqlBinding::createString(128), // name
+ MySqlBinding::createString(128), // space
+ MySqlBinding::createInteger<uint8_t>(), // type
+ MySqlBinding::createTimestamp(), // modification_ts
+ MySqlBinding::createInteger<uint8_t>(), // array
+ MySqlBinding::createString(128), // encapsulate
+ MySqlBinding::createString(512), // record_types
+ MySqlBinding::createString(65536) // user_context
+ };
+
+ uint64_t last_def_id = 0;
+
+ // Run select query.
+ conn_.selectQuery(index, in_bindings, out_bindings,
+ [&option_defs, &last_def_id]
+ (MySqlBindingCollection& out_bindings) {
+ // Get pointer to last fetched option definition.
+ OptionDefinitionPtr last_def;
+ if (!option_defs.empty()) {
+ last_def = *option_defs.rbegin();
+ }
+
+ // See if the last fetched definition is the one for which we now got
+ // the row of data. If not, it means that we need to create new option
+ // definition.
+ if ((last_def_id == 0) ||
+ (last_def_id != out_bindings[0]->getInteger<uint64_t>())) {
+
+ last_def_id = out_bindings[0]->getInteger<uint64_t>();
+
+ // Check array type, because depending on this value we have to use
+ // different constructor.
+ bool array_type = static_cast<bool>(out_bindings[6]->getInteger<uint8_t>());
+ if (array_type) {
+ // Create array option.
+ last_def.reset(new OptionDefinition(out_bindings[2]->getString(),
+ out_bindings[1]->getInteger<uint8_t>(),
+ static_cast<OptionDataType>
+ (out_bindings[4]->getInteger<uint8_t>()),
+ array_type));
+ } else {
+ // Create non-array option.
+ last_def.reset(new OptionDefinition(out_bindings[2]->getString(),
+ out_bindings[1]->getInteger<uint8_t>(),
+ static_cast<OptionDataType>
+ (out_bindings[4]->getInteger<uint8_t>()),
+ out_bindings[7]->getStringOrDefault("").c_str()));
+ }
+
+ // space
+ last_def->setOptionSpaceName(out_bindings[3]->getStringOrDefault(""));
+
+ // record_types
+ ElementPtr record_types_element = out_bindings[8]->getJSON();
+ if (record_types_element) {
+ if (record_types_element->getType() != Element::list) {
+ isc_throw(BadValue, "invalid record_types value "
+ << out_bindings[8]->getString());
+ }
+ // This element must contain a list of integers specifying
+ // types of the record fields.
+ for (auto i = 0; i < record_types_element->size(); ++i) {
+ auto type_element = record_types_element->get(i);
+ if (type_element->getType() != Element::integer) {
+ isc_throw(BadValue, "record type values must be integers");
+ }
+ last_def->addRecordField(static_cast<OptionDataType>
+ (type_element->intValue()));
+ }
+ }
+
+ // Update modification time.
+ last_def->setModificationTime(out_bindings[5]->getTimestamp());
+
+ // Store created option definition.
+ option_defs.push_back(last_def);
+ }
+ });
+}
+
+MySqlBindingPtr
+MySqlConfigBackendImpl::createInputRelayBinding(const NetworkPtr& network) {
+ ElementPtr relay_element = Element::createList();
+ const auto& addresses = network->getRelayAddresses();
+ if (!addresses.empty()) {
+ for (const auto& address : addresses) {
+ relay_element->add(Element::create(address.toText()));
+ }
+ }
+
+ return (relay_element->empty() ? MySqlBinding::createNull() :
+ MySqlBinding::condCreateString(relay_element->str()));
+}
+
+MySqlBindingPtr
+MySqlConfigBackendImpl::createInputRequiredClassesBinding(const NetworkPtr& network) {
+ // Create JSON list of required classes.
+ ElementPtr required_classes_element = Element::createList();
+ const auto& required_classes = network->getRequiredClasses();
+ for (auto required_class = required_classes.cbegin();
+ required_class != required_classes.cend();
+ ++required_class) {
+ required_classes_element->add(Element::create(*required_class));
+ }
+
+ return (required_classes_element ?
+ MySqlBinding::createString(required_classes_element->str()) :
+ MySqlBinding::createNull());
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MYSQL_CONFIG_BACKEND_IMPL_H
+#define MYSQL_CONFIG_BACKEND_IMPL_H
+
+#include <database/database_connection.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/network.h>
+#include <mysql/mysql_binding.h>
+#include <mysql/mysql_connection.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Base class for MySQL Config Backend implementations.
+///
+/// This class contains common methods for manipulating data in the
+/// MySQL database, used by all servers.
+class MySqlConfigBackendImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ explicit MySqlConfigBackendImpl(const db::DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Destructor.
+ ~MySqlConfigBackendImpl();
+
+ /// @brief Sends query to delete rows from a table.
+ ///
+ /// @param index Index of the statement to be executed.
+ void deleteFromTable(const int index);
+
+ /// @brief Sends query to delete rows from a table.
+ ///
+ /// @param index Index of the statement to be executed.
+ /// @param key String value to be used as input binding to the delete
+ /// statement
+ void deleteFromTable(const int index,
+ const std::string& key);
+
+ /// @brief Sends query to the database to retrieve multiple option
+ /// definitions.
+ ///
+ /// Query should order option definitions by id.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param in_bindings Input bindings specifying selection criteria. The
+ /// size of the bindings collection must match the number of placeholders
+ /// in the prepared statement. The input bindings collection must be empty
+ /// if the query contains no WHERE clause.
+ /// @param [out] option_defs Reference to the container where fetched
+ /// option definitions will be inserted.
+ void getOptionDefs(const int index,
+ const db::MySqlBindingCollection& in_bindings,
+ OptionDefContainer& option_defs);
+
+ /// @brief Creates input binding for relay addresses.
+ ///
+ /// @param network Pointer to a shared network or subnet for which binding
+ /// should be created.
+ /// @return Pointer to the binding (possibly null binding if there are no
+ /// relay addresses specified).
+ db::MySqlBindingPtr createInputRelayBinding(const NetworkPtr& network);
+
+ /// @brief Creates input binding for 'require_client_classes' parameter.
+ ///
+ /// @param network Pointer to a shared network or subnet for which binding
+ /// should be created.
+ /// @return Pointer to the binding (possibly null binding if there are no
+ /// required classes specified).
+ db::MySqlBindingPtr createInputRequiredClassesBinding(const NetworkPtr& network);
+
+ /// @brief Creates input binding for user context parameter.
+ ///
+ /// @param T Type of the configuration element to which context belongs.
+ /// @param network Pointer to a shared network, subnet or other configuration
+ /// element for which binding should be created.
+ /// @return Pointer to the binding (possibly null binding if context is
+ /// null).
+ template<typename T>
+ db::MySqlBindingPtr createInputContextBinding(const T& config_element) {
+ // Create user context binding if user context exists.
+ auto context_element = config_element->getContext();
+ return (context_element ? db::MySqlBinding::createString(context_element->str()) :
+ db::MySqlBinding::createNull());
+ }
+
+
+ /// @brief Represents connection to the MySQL database.
+ db::MySqlConnection conn_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif