diff options
Diffstat (limited to 'src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc')
-rw-r--r-- | src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc | 1102 |
1 files changed, 1102 insertions, 0 deletions
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc new file mode 100644 index 0000000..f4a0563 --- /dev/null +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc @@ -0,0 +1,1102 @@ +// Copyright (C) 2018-2022 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 <config.h> +#include <mysql_cb_impl.h> +#include <mysql_cb_log.h> +#include <asiolink/io_address.h> +#include <config_backend/constants.h> +#include <dhcp/option_space.h> +#include <util/buffer.h> + +#include <mysql.h> +#include <mysqld_error.h> +#include <cstdint> +#include <utility> + +using namespace isc::asiolink; +using namespace isc::cb; +using namespace isc::data; +using namespace isc::db; +using namespace isc::log; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +isc::asiolink::IOServicePtr MySqlConfigBackendImpl::io_service_ = isc::asiolink::IOServicePtr(); + +MySqlConfigBackendImpl:: +ScopedAuditRevision::ScopedAuditRevision(MySqlConfigBackendImpl* impl, + const int index, + const ServerSelector& server_selector, + const std::string& log_message, + bool cascade_transaction) + : impl_(impl) { + impl_->createAuditRevision(index, server_selector, + boost::posix_time::microsec_clock::local_time(), + log_message, + cascade_transaction); +} + +MySqlConfigBackendImpl:: +ScopedAuditRevision::~ScopedAuditRevision() { + impl_->clearAuditRevision(); +} + +MySqlConfigBackendImpl:: +MySqlConfigBackendImpl(const DatabaseConnection::ParameterMap& parameters, + const DbCallback db_reconnect_callback) + : conn_(parameters, + IOServiceAccessorPtr(new IOServiceAccessor(MySqlConfigBackendImpl::getIOService)), + db_reconnect_callback), timer_name_(""), + audit_revision_ref_count_(0), parameters_(parameters) { + // Test schema version first. + std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR, + MYSQL_SCHEMA_VERSION_MINOR); + std::pair<uint32_t, uint32_t> db_version = + MySqlConnection::getVersion(parameters); + 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); + } + + // Open the database. + conn_.openDatabase(); + + // Check if we have TLS when we required it. + if (conn_.getTls()) { + std::string cipher = conn_.getTlsCipher(); + if (cipher.empty()) { + LOG_ERROR(mysql_cb_logger, MYSQL_CB_NO_TLS); + } else { + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, + MYSQL_CB_TLS_CIPHER) + .arg(cipher); + } + } +} + +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; + } + } +} + +MySqlBindingPtr +MySqlConfigBackendImpl::createBinding(const Triplet<uint32_t>& triplet) { + if (triplet.unspecified()) { + return (MySqlBinding::createNull()); + } + return (MySqlBinding::createInteger<uint32_t>(triplet.get())); +} + +MySqlBindingPtr +MySqlConfigBackendImpl::createMinBinding(const Triplet<uint32_t>& triplet) { + if (triplet.unspecified() || (triplet.getMin() == triplet.get())) { + return (MySqlBinding::createNull()); + } + return (MySqlBinding::createInteger<uint32_t>(triplet.getMin())); +} + +MySqlBindingPtr +MySqlConfigBackendImpl::createMaxBinding(const Triplet<uint32_t>& triplet) { + if (triplet.unspecified() || (triplet.getMax() == triplet.get())) { + return (MySqlBinding::createNull()); + } + return (MySqlBinding::createInteger<uint32_t>(triplet.getMax())); +} + +Triplet<uint32_t> +MySqlConfigBackendImpl::createTriplet(const MySqlBindingPtr& binding) { + if (!binding) { + isc_throw(Unexpected, "MySQL configuration backend internal error: " + "binding pointer is NULL when creating a triplet value"); + } + + if (binding->amNull()) { + return (Triplet<uint32_t>()); + } + + return (Triplet<uint32_t>(binding->getInteger<uint32_t>())); +} + +Triplet<uint32_t> +MySqlConfigBackendImpl::createTriplet(const MySqlBindingPtr& def_binding, + const MySqlBindingPtr& min_binding, + const MySqlBindingPtr& max_binding) { + if (!def_binding || !min_binding || !max_binding) { + isc_throw(Unexpected, "MySQL configuration backend internal error: " + "binding pointer is NULL when creating a triplet value"); + } + + // This code assumes the database was filled using the API, e.g. it + // is not possible (so not handled) to have only the min_binding not NULL. + if (def_binding->amNull()) { + return (Triplet<uint32_t>()); + } + + uint32_t value = def_binding->getInteger<uint32_t>(); + uint32_t min_value = value; + if (!min_binding->amNull()) { + min_value = min_binding->getInteger<uint32_t>(); + } + uint32_t max_value = value; + if (!max_binding->amNull()) { + max_value = max_binding->getInteger<uint32_t>(); + } + + return (Triplet<uint32_t>(min_value, value, max_value)); +} + +void +MySqlConfigBackendImpl::createAuditRevision(const int index, + const ServerSelector& server_selector, + const boost::posix_time::ptime& audit_ts, + const std::string& log_message, + const bool cascade_transaction) { + // Do not touch existing audit revision in case of the cascade update. + if (++audit_revision_ref_count_ > 1) { + return; + } + + /// @todo The audit trail is not really well prepared to handle multiple server + /// tags or no server tags. Therefore, if the server selector appears to be + /// pointing to multiple servers, no servers or any server we simply associate the + /// audit revision with all servers. The only case when we create a dedicated + /// audit entry is when there is a single server tag, i.e. "all" or explicit + /// server name. In fact, these are the most common two cases. + std::string tag = ServerTag::ALL; + auto const& tags = server_selector.getTags(); + if (tags.size() == 1) { + tag = tags.begin()->get(); + } + + MySqlBindingCollection in_bindings = { + MySqlBinding::createTimestamp(audit_ts), + MySqlBinding::createString(tag), + MySqlBinding::createString(log_message), + MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(cascade_transaction)) + }; + conn_.insertQuery(index, in_bindings); +} + +void +MySqlConfigBackendImpl::clearAuditRevision() { + if (audit_revision_ref_count_ <= 0) { + isc_throw(Unexpected, "attempted to clear audit revision that does not exist - coding error"); + } + --audit_revision_ref_count_; +} + +void +MySqlConfigBackendImpl::getRecentAuditEntries(const int index, + const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time, + const uint64_t& modification_id, + AuditEntryCollection& audit_entries) { + // Create the output bindings for receiving the data. + MySqlBindingCollection out_bindings = { + MySqlBinding::createInteger<uint64_t>(), // id + MySqlBinding::createString(AUDIT_ENTRY_OBJECT_TYPE_BUF_LENGTH), // object_type + MySqlBinding::createInteger<uint64_t>(), // object_id + MySqlBinding::createInteger<uint8_t>(), // modification_type + MySqlBinding::createTimestamp(), // modification_time + MySqlBinding::createInteger<uint64_t>(), // revision_id + MySqlBinding::createString(AUDIT_ENTRY_LOG_MESSAGE_BUF_LENGTH) // log_message + }; + + auto const& tags = server_selector.getTags(); + for (auto const& tag : tags) { + + // There are only a few input bindings + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag.get()), + MySqlBinding::createTimestamp(modification_time), + MySqlBinding::createInteger<uint64_t>(modification_id) + }; + + // Execute select. + conn_.selectQuery(index, in_bindings, out_bindings, + [&audit_entries] (MySqlBindingCollection& out_bindings) { + // Convert the numeric modification type into modification type enum. + AuditEntry::ModificationType mod_type = + static_cast<AuditEntry::ModificationType>(out_bindings[3]->getInteger<uint8_t>()); + + // Create new audit entry and add it to the collection of received + // entries. + AuditEntryPtr audit_entry = + AuditEntry::create(out_bindings[1]->getString(), + out_bindings[2]->getInteger<uint64_t>(), + mod_type, + out_bindings[4]->getTimestamp(), + out_bindings[5]->getInteger<uint64_t>(), + out_bindings[6]->getStringOrDefault("")); + audit_entries.insert(audit_entry); + }); + } +} + +uint64_t +MySqlConfigBackendImpl::deleteFromTable(const int index, + const ServerSelector& server_selector, + const std::string& operation) { + // When deleting multiple objects we must not use ANY server. + if (server_selector.amAny()) { + isc_throw(InvalidOperation, "deleting multiple objects for ANY server is not" + " supported"); + } + + MySqlBindingCollection in_bindings; + return (deleteFromTable(index, server_selector, operation, in_bindings)); +} + +void +MySqlConfigBackendImpl::getGlobalParameters(const int index, + const MySqlBindingCollection& in_bindings, + StampedValueCollection& parameters) { + // The following parameters from the dhcp[46]_global_parameter table are + // returned: + // - id + // - name - parameter name + // - value - parameter value + // - modification_ts - modification timestamp. + MySqlBindingCollection out_bindings = { + MySqlBinding::createInteger<uint64_t>(), // id + MySqlBinding::createString(GLOBAL_PARAMETER_NAME_BUF_LENGTH), // name + MySqlBinding::createString(GLOBAL_PARAMETER_VALUE_BUF_LENGTH), // value + MySqlBinding::createInteger<uint8_t>(), // parameter_type + MySqlBinding::createTimestamp(), // modification_ts + MySqlBinding::createString(SERVER_TAG_BUF_LENGTH) // server_tag + }; + + StampedValuePtr last_param; + + StampedValueCollection local_parameters; + + conn_.selectQuery(index, in_bindings, out_bindings, + [&last_param, &local_parameters] + (MySqlBindingCollection& out_bindings) { + + uint64_t id = out_bindings[0]->getInteger<uint64_t>(); + + // If we're starting or if this is new parameter being processed... + if (!last_param || (last_param->getId() != id)) { + + // parameter name + std::string name = out_bindings[1]->getString(); + + if (!name.empty()) { + last_param = StampedValue::create(name, + out_bindings[2]->getString(), + static_cast<Element::types> + (out_bindings[3]->getInteger<uint8_t>())); + + // id + last_param->setId(id); + + // modification_ts + last_param->setModificationTime(out_bindings[4]->getTimestamp()); + + // server_tag + ServerTag last_param_server_tag(out_bindings[5]->getString()); + last_param->setServerTag(last_param_server_tag.get()); + + // If we're fetching parameters for a given server (explicit server + // tag is provided), it takes precedence over the same parameter + // specified for all servers. Therefore, we check if the given + // parameter already exists and belongs to 'all'. + auto& index = local_parameters.get<StampedValueNameIndexTag>(); + auto existing = index.find(name); + if (existing != index.end()) { + // This parameter was already fetched. Let's check if we should + // replace it or not. + if (!last_param_server_tag.amAll() && (*existing)->hasAllServerTag()) { + // Replace parameter specified for 'all' with the one associated + // with the particular server tag. + local_parameters.replace(existing, last_param); + return; + } + + } + + // If there is no such parameter yet or the existing parameter + // belongs to a different server and the inserted parameter is + // not for all servers. + if ((existing == index.end()) || + (!(*existing)->hasServerTag(last_param_server_tag) && + !last_param_server_tag.amAll())) { + local_parameters.insert(last_param); + } + } + } + }); + + parameters.insert(local_parameters.begin(), local_parameters.end()); +} + +OptionDefinitionPtr +MySqlConfigBackendImpl::getOptionDef(const int index, + const ServerSelector& server_selector, + const uint16_t code, + const std::string& space) { + + if (server_selector.amUnassigned()) { + isc_throw(NotImplemented, "managing configuration for no particular server" + " (unassigned) is unsupported at the moment"); + } + + auto tag = getServerTag(server_selector, "fetching option definition"); + + OptionDefContainer option_defs; + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createInteger<uint16_t>(code), + MySqlBinding::createString(space) + }; + getOptionDefs(index, in_bindings, option_defs); + return (option_defs.empty() ? OptionDefinitionPtr() : *option_defs.begin()); +} + +void +MySqlConfigBackendImpl::getAllOptionDefs(const int index, + const ServerSelector& server_selector, + OptionDefContainer& option_defs) { + auto const& tags = server_selector.getTags(); + for (auto const& tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag.get()) + }; + getOptionDefs(index, in_bindings, option_defs); + } +} + +void +MySqlConfigBackendImpl::getModifiedOptionDefs(const int index, + const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time, + OptionDefContainer& option_defs) { + auto const& tags = server_selector.getTags(); + for (auto const& tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag.get()), + MySqlBinding::createTimestamp(modification_time) + }; + getOptionDefs(index, in_bindings, option_defs); + } +} + +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<uint16_t>(), // code + MySqlBinding::createString(OPTION_NAME_BUF_LENGTH), // name + MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // space + MySqlBinding::createInteger<uint8_t>(), // type + MySqlBinding::createTimestamp(), // modification_ts + MySqlBinding::createInteger<uint8_t>(), // array + MySqlBinding::createString(OPTION_ENCAPSULATE_BUF_LENGTH), // encapsulate + MySqlBinding::createString(OPTION_RECORD_TYPES_BUF_LENGTH), // record_types + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // user_context + MySqlBinding::createString(SERVER_TAG_BUF_LENGTH) // server_tag + }; + + uint64_t last_def_id = 0; + + OptionDefContainer local_option_defs; + + // Run select query. + conn_.selectQuery(index, in_bindings, out_bindings, + [this, &local_option_defs, &last_def_id] + (MySqlBindingCollection& out_bindings) { + // Get pointer to last fetched option definition. + OptionDefinitionPtr last_def; + if (!local_option_defs.empty()) { + last_def = *local_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>(); + + last_def = processOptionDefRow(out_bindings.begin()); + + // server_tag + ServerTag last_def_server_tag(out_bindings[10]->getString()); + last_def->setServerTag(last_def_server_tag.get()); + + // If we're fetching option definitions for a given server + // (explicit server tag is provided), it takes precedence over + // the same option definition specified for all servers. + // Therefore, we check if the given option already exists and + // belongs to 'all'. + auto& index = local_option_defs.get<1>(); + auto existing_it_pair = index.equal_range(last_def->getCode()); + auto existing_it = existing_it_pair.first; + bool found = false; + for ( ; existing_it != existing_it_pair.second; ++existing_it) { + if ((*existing_it)->getOptionSpaceName() == last_def->getOptionSpaceName()) { + found = true; + // This option definition was already fetched. Let's check + // if we should replace it or not. + if (!last_def_server_tag.amAll() && (*existing_it)->hasAllServerTag()) { + index.replace(existing_it, last_def); + return; + } + break; + } + } + + // If there is no such option definition yet or the existing option + // definition belongs to a different server and the inserted option + // definition is not for all servers. + if (!found || + (!(*existing_it)->hasServerTag(last_def_server_tag) && + !last_def_server_tag.amAll())) { + static_cast<void>(local_option_defs.push_back(last_def)); + } + } + }); + + // Append the option definition fetched by this function into the container + // supplied by the caller. The container supplied by the caller may already + // hold some option definitions fetched for other server tags. + option_defs.insert(option_defs.end(), local_option_defs.begin(), + local_option_defs.end()); +} + +void +MySqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_selector, + const OptionDefinitionPtr& option_def, + const std::string& /*space*/, + const int& /*get_option_def_code_space*/, + const int& insert_option_def, + const int& update_option_def, + const int& create_audit_revision, + const int& insert_option_def_server, + const std::string& client_class_name) { + + if (server_selector.amUnassigned()) { + isc_throw(NotImplemented, "managing configuration for no particular server" + " (unassigned) is unsupported at the moment"); + } + + auto tag = getServerTag(server_selector, "creating or updating option definition"); + + ElementPtr record_types = Element::createList(); + for (auto field : option_def->getRecordFields()) { + record_types->add(Element::create(static_cast<int>(field))); + } + MySqlBindingPtr record_types_binding = record_types->empty() ? + MySqlBinding::createNull() : MySqlBinding::createString(record_types->str()); + + MySqlBindingPtr client_class_binding = client_class_name.empty() ? + MySqlBinding::createNull() : MySqlBinding::createString(client_class_name); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger<uint16_t>(option_def->getCode()), + MySqlBinding::createString(option_def->getName()), + MySqlBinding::createString(option_def->getOptionSpaceName()), + MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(option_def->getType())), + MySqlBinding::createTimestamp(option_def->getModificationTime()), + MySqlBinding::createBool(option_def->getArrayType()), + MySqlBinding::createString(option_def->getEncapsulatedSpace()), + record_types_binding, + createInputContextBinding(option_def), + client_class_binding, + MySqlBinding::createString(tag), + MySqlBinding::createInteger<uint16_t>(option_def->getCode()), + MySqlBinding::createString(option_def->getOptionSpaceName()), + }; + + MySqlTransaction transaction(conn_); + + // Create scoped audit revision. As long as this instance exists + // no new audit revisions are created in any subsequent calls. + ScopedAuditRevision audit_revision(this, + create_audit_revision, + server_selector, + "option definition set", + true); + + if (conn_.updateDeleteQuery(update_option_def, in_bindings) == 0) { + // Remove the bindings used only during the update. + in_bindings.resize(in_bindings.size() - 3); + conn_.insertQuery(insert_option_def, in_bindings); + + // Fetch unique identifier of the inserted option definition and use it + // as input to the next query. + uint64_t id = mysql_insert_id(conn_.mysql_); + + // Insert associations of the option definition with servers. + attachElementToServers(insert_option_def_server, + server_selector, + MySqlBinding::createInteger<uint64_t>(id), + MySqlBinding::createTimestamp(option_def->getModificationTime())); + } + + transaction.commit(); +} + +OptionDescriptorPtr +MySqlConfigBackendImpl::getOption(const int index, + const Option::Universe& universe, + const ServerSelector& server_selector, + const uint16_t code, + const std::string& space) { + + if (server_selector.amUnassigned()) { + isc_throw(NotImplemented, "managing configuration for no particular server" + " (unassigned) is unsupported at the moment"); + } + + auto tag = getServerTag(server_selector, "fetching global option"); + + OptionContainer options; + MySqlBindingCollection in_bindings; + in_bindings.push_back(MySqlBinding::createString(tag)); + if (universe == Option::V4) { + in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(code))); + } else { + in_bindings.push_back(MySqlBinding::createInteger<uint16_t>(code)); + } + in_bindings.push_back(MySqlBinding::createString(space)); + getOptions(index, in_bindings, universe, options); + return (options.empty() ? OptionDescriptorPtr() : + OptionDescriptor::create(*options.begin())); +} + +OptionContainer +MySqlConfigBackendImpl::getAllOptions(const int index, + const Option::Universe& universe, + const ServerSelector& server_selector) { + OptionContainer options; + + auto const& tags = server_selector.getTags(); + for (auto const& tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag.get()) + }; + getOptions(index, in_bindings, universe, options); + } + + return (options); +} + +OptionContainer +MySqlConfigBackendImpl::getModifiedOptions(const int index, + const Option::Universe& universe, + const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) { + OptionContainer options; + + auto const& tags = server_selector.getTags(); + for (auto const& tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag.get()), + MySqlBinding::createTimestamp(modification_time) + }; + getOptions(index, in_bindings, universe, options); + } + + return (options); +} + +OptionDescriptorPtr +MySqlConfigBackendImpl::getOption(const int index, + const Option::Universe& universe, + const ServerSelector& server_selector, + const SubnetID& subnet_id, + const uint16_t code, + const std::string& space) { + + if (server_selector.amUnassigned()) { + isc_throw(NotImplemented, "managing configuration for no particular server" + " (unassigned) is unsupported at the moment"); + } + + auto tag = getServerTag(server_selector, "fetching subnet level option"); + + OptionContainer options; + MySqlBindingCollection in_bindings; + in_bindings.push_back(MySqlBinding::createString(tag)); + uint32_t id = static_cast<uint32_t>(subnet_id); + in_bindings.push_back(MySqlBinding::createInteger<uint32_t>(id)); + if (universe == Option::V4) { + in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(code))); + } else { + in_bindings.push_back(MySqlBinding::createInteger<uint16_t>(code)); + } + in_bindings.push_back(MySqlBinding::createString(space)); + getOptions(index, in_bindings, universe, options); + return (options.empty() ? OptionDescriptorPtr() : + OptionDescriptor::create(*options.begin())); +} + +OptionDescriptorPtr +MySqlConfigBackendImpl::getOption(const int index, + const ServerSelector& server_selector, + const Lease::Type& pool_type, + const uint64_t pool_id, + const uint16_t code, + const std::string& space) { + + if (server_selector.amUnassigned()) { + isc_throw(NotImplemented, "managing configuration for no particular server" + " (unassigned) is unsupported at the moment"); + } + + std::string msg = "fetching "; + if (pool_type == Lease::TYPE_PD) { + msg += "prefix delegation"; + } else { + msg += "address"; + } + msg += " pool level option"; + auto tag = getServerTag(server_selector, msg); + + Option::Universe universe = Option::V4; + OptionContainer options; + MySqlBindingCollection in_bindings; + in_bindings.push_back(MySqlBinding::createString(tag)); + in_bindings.push_back(MySqlBinding::createInteger<uint64_t>(pool_id)); + if (pool_type == Lease::TYPE_V4) { + in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(code))); + } else { + in_bindings.push_back(MySqlBinding::createInteger<uint16_t>(code)); + universe = Option::V6; + } + in_bindings.push_back(MySqlBinding::createString(space)); + getOptions(index, in_bindings, universe, options); + return (options.empty() ? OptionDescriptorPtr() : + OptionDescriptor::create(*options.begin())); +} + +OptionDescriptorPtr +MySqlConfigBackendImpl::getOption(const int index, + const Option::Universe& universe, + const ServerSelector& server_selector, + const std::string& shared_network_name, + const uint16_t code, + const std::string& space) { + + if (server_selector.amUnassigned()) { + isc_throw(NotImplemented, "managing configuration for no particular server" + " (unassigned) is unsupported at the moment"); + } + + auto tag = getServerTag(server_selector, "fetching shared network level option"); + + OptionContainer options; + MySqlBindingCollection in_bindings; + in_bindings.push_back(MySqlBinding::createString(tag)); + in_bindings.push_back(MySqlBinding::createString(shared_network_name)); + if (universe == Option::V4) { + in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(code))); + } else { + in_bindings.push_back(MySqlBinding::createInteger<uint16_t>(code)); + } + in_bindings.push_back(MySqlBinding::createString(space)); + getOptions(index, in_bindings, universe, options); + return (options.empty() ? OptionDescriptorPtr() : + OptionDescriptor::create(*options.begin())); +} + +void +MySqlConfigBackendImpl::getOptions(const int index, + const db::MySqlBindingCollection& in_bindings, + const Option::Universe& universe, + OptionContainer& options) { + // Create output bindings. The order must match that in the prepared + // statement. + MySqlBindingCollection out_bindings; + // option_id + out_bindings.push_back(MySqlBinding::createInteger<uint64_t>()); + // code + if (universe == Option::V4) { + out_bindings.push_back(MySqlBinding::createInteger<uint8_t>()); + } else { + out_bindings.push_back(MySqlBinding::createInteger<uint16_t>()); + } + // value + out_bindings.push_back(MySqlBinding::createBlob(OPTION_VALUE_BUF_LENGTH)); + // forma\tted_value + out_bindings.push_back(MySqlBinding::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH)); + // space + out_bindings.push_back(MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH)); + // persistent + out_bindings.push_back(MySqlBinding::createInteger<uint8_t>()); + // dhcp[46]_subnet_id + out_bindings.push_back(MySqlBinding::createInteger<uint32_t>()); + // scope_id + out_bindings.push_back(MySqlBinding::createInteger<uint8_t>()); + // user_context + out_bindings.push_back(MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH)); + // shared_network_name + out_bindings.push_back(MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH)); + // pool_id + out_bindings.push_back(MySqlBinding::createInteger<uint64_t>()); + // modification_ts + out_bindings.push_back(MySqlBinding::createTimestamp()); + // server_tag + out_bindings.push_back(MySqlBinding::createString(SERVER_TAG_BUF_LENGTH)); + // pd_pool_id + if (universe == Option::V6) { + out_bindings.push_back(MySqlBinding::createInteger<uint64_t>()); + } + + uint64_t last_option_id = 0; + + OptionContainer local_options; + + conn_.selectQuery(index, in_bindings, out_bindings, + [this, universe, &local_options, &last_option_id] + (MySqlBindingCollection& out_bindings) { + // Parse option. + if (!out_bindings[0]->amNull() && + ((last_option_id == 0) || + (last_option_id < out_bindings[0]->getInteger<uint64_t>()))) { + last_option_id = out_bindings[0]->getInteger<uint64_t>(); + + OptionDescriptorPtr desc = processOptionRow(universe, out_bindings.begin()); + if (desc) { + // server_tag for the global option + ServerTag last_option_server_tag(out_bindings[12]->getString()); + desc->setServerTag(last_option_server_tag.get()); + + // If we're fetching options for a given server (explicit server + // tag is provided), it takes precedence over the same option + // specified for all servers. Therefore, we check if the given + // option already exists and belongs to 'all'. + auto& index = local_options.get<1>(); + auto existing_it_pair = index.equal_range(desc->option_->getType()); + auto existing_it = existing_it_pair.first; + bool found = false; + for ( ; existing_it != existing_it_pair.second; ++existing_it) { + if (existing_it->space_name_ == desc->space_name_) { + found = true; + // This option was already fetched. Let's check if we should + // replace it or not. + if (!last_option_server_tag.amAll() && existing_it->hasAllServerTag()) { + index.replace(existing_it, *desc); + return; + } + break; + } + } + + // If there is no such global option yet or the existing option + // belongs to a different server and the inserted option is not + // for all servers. + if (!found || + (!existing_it->hasServerTag(last_option_server_tag) && + !last_option_server_tag.amAll())) { + static_cast<void>(local_options.push_back(*desc)); + } + } + } + }); + + // Append the options fetched by this function into the container supplied + // by the caller. The container supplied by the caller may already hold + // some options fetched for other server tags. + options.insert(options.end(), local_options.begin(), local_options.end()); +} + +OptionDescriptorPtr +MySqlConfigBackendImpl::processOptionRow(const Option::Universe& universe, + MySqlBindingCollection::iterator first_binding) { + // Some of the options have standard or custom definitions. + // Depending whether the option has a definition or not a different + // C++ class may be used to represent the option. Therefore, the + // first thing to do is to see if there is a definition for our + // parsed option. The option code and space is needed for it. + std::string space = (*(first_binding + 4))->getString(); + uint16_t code; + if (universe == Option::V4) { + code = (*(first_binding + 1))->getInteger<uint8_t>(); + } else { + code = (*(first_binding + 1))->getInteger<uint16_t>(); + } + + + // Get formatted value if available. + std::string formatted_value = (*(first_binding + 3))->getStringOrDefault(""); + + OptionPtr option = Option::create(universe, code); + + // If we don't have a formatted value, check for a blob. Add it to the + // option if it exists. + if (formatted_value.empty()) { + std::vector<uint8_t> blob; + if (!(*(first_binding + 2))->amNull()) { + blob = (*(first_binding + 2))->getBlob(); + } + option->setData(blob.begin(), blob.end()); + } + + // Check if the option is persistent. + bool persistent = static_cast<bool>((*(first_binding + 5))->getIntegerOrDefault<uint8_t>(0)); + + // Create option descriptor which encapsulates our option and adds + // additional information, i.e. whether the option is persistent, + // its option space and timestamp. + OptionDescriptorPtr desc = OptionDescriptor::create(option, persistent, formatted_value); + desc->space_name_ = space; + desc->setModificationTime((*(first_binding + 11))->getTimestamp()); + + // Set database id for the option. + if (!(*first_binding)->amNull()) { + desc->setId((*first_binding)->getInteger<uint64_t>()); + } + + return (desc); +} + +OptionDefinitionPtr +MySqlConfigBackendImpl::processOptionDefRow(MySqlBindingCollection::iterator first_binding) { + OptionDefinitionPtr def; + + // Check array type, because depending on this value we have to use + // different constructor. + bool array_type = static_cast<bool>((*(first_binding + 6))->getInteger<uint8_t>()); + if (array_type) { + // Create array option. + def = OptionDefinition::create((*(first_binding + 2))->getString(), + (*(first_binding + 1))->getInteger<uint16_t>(), + (*(first_binding + 3))->getString(), + static_cast<OptionDataType> + ((*(first_binding + 4))->getInteger<uint8_t>()), + array_type); + } else { + // Create non-array option. + def = OptionDefinition::create((*(first_binding + 2))->getString(), + (*(first_binding + 1))->getInteger<uint16_t>(), + (*(first_binding + 3))->getString(), + static_cast<OptionDataType> + ((*(first_binding + 4))->getInteger<uint8_t>()), + (*(first_binding + 7))->getStringOrDefault("").c_str()); + } + + // id + def->setId((*(first_binding))->getInteger<uint64_t>()); + + // record_types + ElementPtr record_types_element = (*(first_binding + 8))->getJSON(); + if (record_types_element) { + if (record_types_element->getType() != Element::list) { + isc_throw(BadValue, "invalid record_types value " + << (*(first_binding + 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"); + } + + def->addRecordField(static_cast<OptionDataType>(type_element->intValue())); + } + } + + // Update modification time. + def->setModificationTime((*(first_binding + 5))->getTimestamp()); + + return (def); +} + +void +MySqlConfigBackendImpl::attachElementToServers(const int index, + const ServerSelector& server_selector, + const MySqlBindingPtr& first_binding, + const MySqlBindingPtr& in_bindings) { + // Create the vector from the parameter pack. + MySqlBindingCollection in_server_bindings = { first_binding, in_bindings }; + for (auto const& tag : server_selector.getTags()) { + in_server_bindings.push_back(MySqlBinding::createString(tag.get())); + // Handles the case where the server does not exists. + try { + conn_.insertQuery(index, in_server_bindings); + } catch (const NullKeyError&) { + // The message should give the tag value. + isc_throw(NullKeyError, + "server '" << tag.get() << "' does not exist"); + } + in_server_bindings.pop_back(); + } +} + +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::createOptionValueBinding(const OptionDescriptorPtr& option) { + OptionPtr opt = option->option_; + if (option->formatted_value_.empty() && (opt->len() > opt->getHeaderLen())) { + OutputBuffer buf(opt->len()); + opt->pack(buf); + const char* buf_ptr = static_cast<const char*>(buf.getData()); + std::vector<uint8_t> blob(buf_ptr + opt->getHeaderLen(), + buf_ptr + buf.getLength()); + return (MySqlBinding::createBlob(blob.begin(), blob.end())); + + } + + return (MySqlBinding::createNull()); +} + +ServerPtr +MySqlConfigBackendImpl::getServer(const int index, const ServerTag& server_tag) { + ServerCollection servers; + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(server_tag.get()) + }; + getServers(index, in_bindings, servers); + + return (servers.empty() ? ServerPtr() : *servers.begin()); +} + +void +MySqlConfigBackendImpl::getAllServers(const int index, db::ServerCollection& servers) { + MySqlBindingCollection in_bindings; + getServers(index, in_bindings, servers); +} + +void +MySqlConfigBackendImpl::getServers(const int index, + const MySqlBindingCollection& in_bindings, + ServerCollection& servers) { + MySqlBindingCollection out_bindings = { + MySqlBinding::createInteger<uint64_t>(), + MySqlBinding::createString(SERVER_TAG_BUF_LENGTH), + MySqlBinding::createString(SERVER_DESCRIPTION_BUF_LENGTH), + MySqlBinding::createTimestamp() + }; + + conn_.selectQuery(index, in_bindings, out_bindings, + [&servers](MySqlBindingCollection& out_bindings) { + + ServerPtr last_server; + uint64_t id = out_bindings[0]->getInteger<uint64_t>(); + if (!last_server || (last_server->getId() != id)) { + + // Set description if it is non-null. + auto desc = (out_bindings[2]->amNull() ? "" : out_bindings[2]->getString()); + last_server = Server::create(ServerTag(out_bindings[1]->getString()), + desc); + + // id + last_server->setId(id); + + // modification_ts + last_server->setModificationTime(out_bindings[3]->getTimestamp()); + + // New server fetched. Let's store it. + servers.insert(last_server); + } + }); +} + +void +MySqlConfigBackendImpl::createUpdateServer(const int& create_audit_revision, + const int& create_index, + const int& update_index, + const ServerPtr& server) { + // The server tag 'all' is reserved. + if (server->getServerTag().amAll()) { + isc_throw(InvalidOperation, + "'all' is a name reserved for the server tag which" + " associates the configuration elements with all servers connecting" + " to the database and a server with this name may not be created"); + } + + // Create scoped audit revision. As long as this instance exists + // no new audit revisions are created in any subsequent calls. + ScopedAuditRevision audit_revision(this, + create_audit_revision, + ServerSelector::ALL(), + "server set", + true); + + MySqlTransaction transaction(conn_); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(server->getServerTagAsText()), + MySqlBinding::createString(server->getDescription()), + MySqlBinding::createTimestamp(server->getModificationTime()) + }; + + try { + conn_.insertQuery(create_index, in_bindings); + + } catch (const DuplicateEntry&) { + in_bindings.push_back(MySqlBinding::createString(server->getServerTagAsText())); + conn_.updateDeleteQuery(update_index, in_bindings); + } + + transaction.commit(); +} + +std::string +MySqlConfigBackendImpl::getType() const { + return ("mysql"); +} + +std::string +MySqlConfigBackendImpl::getHost() const { + std::string host = "localhost"; + try { + host = conn_.getParameter("host"); + } catch (...) { + // No host parameter. Return localhost as a default. + } + return (host); +} + +uint16_t +MySqlConfigBackendImpl::getPort() const { + try { + std::string sport = conn_.getParameter("port"); + return (boost::lexical_cast<uint16_t>(sport)); + + } catch (...) { + // No port parameter or parameter invalid. + } + return (0); +} + +} // end of namespace isc::dhcp +} // end of namespace isc |