From: Tomek Mrugalski Date: Wed, 30 Jun 2021 18:52:52 +0000 (+0200) Subject: [#1848] skeleton Postgres CB implementation added X-Git-Tag: Kea-2.1.1~67 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6d82d310ab43045441ee66069f71a2cc5d987c8d;p=thirdparty%2Fkea.git [#1848] skeleton Postgres CB implementation added --- diff --git a/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc b/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc new file mode 100644 index 0000000000..70ed23fe0b --- /dev/null +++ b/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.cc @@ -0,0 +1,1072 @@ +// Copyright (C) 2018-2021 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 +#include "pgsql_cb_impl.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace isc::asiolink; +using namespace isc::cb; +using namespace isc::data; +using namespace isc::db; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +isc::asiolink::IOServicePtr PgSqlConfigBackendImpl::io_service_ = isc::asiolink::IOServicePtr(); + +PgSqlConfigBackendImpl:: +ScopedAuditRevision::ScopedAuditRevision(PgSqlConfigBackendImpl* 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); +} + +PgSqlConfigBackendImpl:: +ScopedAuditRevision::~ScopedAuditRevision() { + impl_->clearAuditRevision(); +} + +PgSqlConfigBackendImpl:: +PgSqlConfigBackendImpl(const DatabaseConnection::ParameterMap& parameters, + const DbCallback db_reconnect_callback) + : conn_(parameters, + IOServiceAccessorPtr(new IOServiceAccessor(PgSqlConfigBackendImpl::getIOService)), + db_reconnect_callback), timer_name_(""), + audit_revision_created_(false), parameters_(parameters) { + // Test schema version first. + std::pair code_version(PG_SCHEMA_VERSION_MAJOR, + PG_SCHEMA_VERSION_MINOR); + std::pair db_version = + PgSqlConnection::getVersion(parameters); + if (code_version != db_version) { + isc_throw(DbOpenError, "Postgres schema version mismatch: need version: " + << code_version.first << "." << code_version.second + << " found version: " << db_version.first << "." + << db_version.second); + } + + // Open the database. + conn_.openDatabase(); +} + +PgSqlConfigBackendImpl::~PgSqlConfigBackendImpl() { + /// nothing to do there. The conn_ connection will be deleted and its dtor + /// will take care of releasing the compiled statements and similar. +} + +#if 0 +PsqlBindArrayPtr +PgSqlConfigBackendImpl::createBinding(const Triplet& triplet) { + PsqlBindArrayPtr bind(new PsqlBindArray()); + + if (triplet.unspecified()) { + bind->addNull(); + } else { + bind->add(triplet.get()); + } + return (bind); +} + +PsqlBindArrayPtr +PgSqlConfigBackendImpl::createMinBinding(const Triplet& triplet) { + PsqlBindArrayPtr bind(new PsqlBindArray()); + if (triplet.unspecified() || (triplet.getMin() == triplet.get())) { + bind->addNull(); + } else { + bind->add(triplet.getMin()); + } + return (bind); +} + +PsqlBindArrayPtr +PgSqlConfigBackendImpl::createMaxBinding(const Triplet& triplet) { + PsqlBindArrayPtr bind(new PsqlBindArray()); + if (triplet.unspecified() || (triplet.getMax() == triplet.get())) { + bind->addNull(); + } else { + bind->add(triplet.getMax()); + } + return (bind); +} +#endif + +/* Triplet +PgSqlConfigBackendImpl::createTriplet(const PsqlBindArrayPtr& binding) { + if (!binding) { + isc_throw(Unexpected, "Postgres configuration backend internal error: " + "binding pointer is NULL when creating a triplet value"); + } + + if (binding->empty()) { + return (Triplet()); + } + + return (Triplet(binding->getInteger())); +} */ + +/* Triplet +PgSqlConfigBackendImpl::createTriplet(const PsqlBindArrayPtr& def_binding, + const PsqlBindArrayPtr& min_binding, + const PsqlBindArrayPtr& max_binding) { + if (!def_binding || !min_binding || !max_binding) { + isc_throw(Unexpected, "Postgres 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->empty()) { + return (Triplet()); + } + + uint32_t value = def_binding->getInteger(); + uint32_t min_value = value; + if (!min_binding->empty()) { + min_value = min_binding->getInteger(); + } + uint32_t max_value = value; + if (!max_binding->empty()) { + max_value = max_binding->getInteger(); + } + + return (Triplet(min_value, value, max_value)); +} */ + +void +PgSqlConfigBackendImpl::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_created_) { + 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 tags = server_selector.getTags(); + if (tags.size() == 1) { + tag = tags.begin()->get(); + } + +#if 0 + PsqlBindArray in_bindings = { + PsqlBindArray::createTimestamp(audit_ts), + PsqlBindArray::createString(tag), + PsqlBindArray::createString(log_message), + PsqlBindArray::createInteger(static_cast(cascade_transaction)) + }; + conn_.insertQuery(index, in_bindings); + audit_revision_created_ = true; +#endif +} + +void +PgSqlConfigBackendImpl::clearAuditRevision() { + audit_revision_created_ = false; +} + +void +PgSqlConfigBackendImpl::getRecentAuditEntries(const int index, + const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time, + const uint64_t& modification_id, + AuditEntryCollection& audit_entries) { + isc_throw(NotImplemented, ""); +} + +uint64_t +PgSqlConfigBackendImpl::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"); + } + + PsqlBindArray in_bindings; + return (deleteFromTable(index, server_selector, operation, in_bindings)); +} + +void +PgSqlConfigBackendImpl::getGlobalParameters(const int index, + const PsqlBindArray& in_bindings, + StampedValueCollection& parameters) { +#if 0 + // The following parameters from the dhcp[46]_global_parameter table are + // returned: + // - id + // - name - parameter name + // - value - parameter value + // - modification_ts - modification timestamp. + PsqlBindArray out_bindings = { + PsqlBindArray::createInteger(), // id + PsqlBindArray::createString(GLOBAL_PARAMETER_NAME_BUF_LENGTH), // name + PsqlBindArray::createString(GLOBAL_PARAMETER_VALUE_BUF_LENGTH), // value + PsqlBindArray::createInteger(), // parameter_type + PsqlBindArray::createTimestamp(), // modification_ts + PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH) // server_tag + }; + + StampedValuePtr last_param; + + StampedValueCollection local_parameters; + + conn_.selectQuery(index, in_bindings, out_bindings, + [&last_param, &local_parameters] + (PsqlBindArray& out_bindings) { + + uint64_t id = out_bindings[0]->getInteger(); + + // 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 + (out_bindings[3]->getInteger())); + + // 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(); + 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()); +#endif +} + +OptionDefinitionPtr +PgSqlConfigBackendImpl::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; +#if 0 + PsqlBindArray in_bindings = { + PsqlBindArray::createString(tag), + PsqlBindArray::createInteger(code), + PsqlBindArray::createString(space) + }; + getOptionDefs(index, in_bindings, option_defs); +#endif + return (option_defs.empty() ? OptionDefinitionPtr() : *option_defs.begin()); +} + +void +PgSqlConfigBackendImpl::getAllOptionDefs(const int index, + const ServerSelector& server_selector, + OptionDefContainer& option_defs) { + auto tags = server_selector.getTags(); +#if 0 + for (auto tag : tags) { + PsqlBindArray in_bindings = { + PsqlBindArray::createString(tag.get()) + }; + getOptionDefs(index, in_bindings, option_defs); + } +#endif +} + +void +PgSqlConfigBackendImpl::getModifiedOptionDefs(const int index, + const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time, + OptionDefContainer& option_defs) { + auto tags = server_selector.getTags(); +#if 0 + for (auto tag : tags) { + PsqlBindArray in_bindings = { + PsqlBindArray::createString(tag.get()), + PsqlBindArray::createTimestamp(modification_time) + }; + getOptionDefs(index, in_bindings, option_defs); + } +#endif +} + +void +PgSqlConfigBackendImpl::getOptionDefs(const int index, + const PsqlBindArray& in_bindings, + OptionDefContainer& option_defs) { + // Create output bindings. The order must match that in the prepared + // statement. +#if 0 + PsqlBindArray out_bindings = { + PsqlBindArray::createInteger(), // id + PsqlBindArray::createInteger(), // code + PsqlBindArray::createString(OPTION_NAME_BUF_LENGTH), // name + PsqlBindArray::createString(OPTION_SPACE_BUF_LENGTH), // space + PsqlBindArray::createInteger(), // type + PsqlBindArray::createTimestamp(), // modification_ts + PsqlBindArray::createInteger(), // array + PsqlBindArray::createString(OPTION_ENCAPSULATE_BUF_LENGTH), // encapsulate + PsqlBindArray::createString(OPTION_RECORD_TYPES_BUF_LENGTH), // record_types + PsqlBindArray::createString(USER_CONTEXT_BUF_LENGTH), // user_context + PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH) // server_tag + }; +#endif + + uint64_t last_def_id = 0; + + OptionDefContainer local_option_defs; + +#if 0 + // Run select query. + conn_.selectQuery(index, in_bindings, out_bindings, + [&local_option_defs, &last_def_id] + (PsqlBindArray& 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())) { + + last_def_id = out_bindings[0]->getInteger(); + + // Check array type, because depending on this value we have to use + // different constructor. + bool array_type = static_cast(out_bindings[6]->getInteger()); + if (array_type) { + // Create array option. + last_def = OptionDefinition::create(out_bindings[2]->getString(), + out_bindings[1]->getInteger(), + out_bindings[3]->getString(), + static_cast + (out_bindings[4]->getInteger()), + array_type); + } else { + // Create non-array option. + last_def = OptionDefinition::create(out_bindings[2]->getString(), + out_bindings[1]->getInteger(), + out_bindings[3]->getString(), + static_cast + (out_bindings[4]->getInteger()), + out_bindings[7]->getStringOrDefault("").c_str()); + } + + // id + last_def->setId(last_def_id); + + // 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 + (type_element->intValue())); + } + } + + // Update modification time. + last_def->setModificationTime(out_bindings[5]->getTimestamp()); + + // 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(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()); +#endif +} + +void +PgSqlConfigBackendImpl::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) { + + 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(field))); + } +#if 0 + PsqlBindArrayPtr record_types_binding = record_types->empty() ? + PsqlBindArray::createNull() : PsqlBindArray::createString(record_types->str()); + + PsqlBindArray in_bindings = { + PsqlBindArray::createInteger(option_def->getCode()), + PsqlBindArray::createString(option_def->getName()), + PsqlBindArray::createString(option_def->getOptionSpaceName()), + PsqlBindArray::createInteger(static_cast(option_def->getType())), + PsqlBindArray::createTimestamp(option_def->getModificationTime()), + PsqlBindArray::createBool(option_def->getArrayType()), + PsqlBindArray::createString(option_def->getEncapsulatedSpace()), + record_types_binding, + createInputContextBinding(option_def), + PsqlBindArray::createString(tag), + PsqlBindArray::createInteger(option_def->getCode()), + PsqlBindArray::createString(option_def->getOptionSpaceName()) + }; + + PgSqlTransaction 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 = pgsql_insert_id(conn_.pgsql_); + + // Insert associations of the option definition with servers. + attachElementToServers(insert_option_def_server, + server_selector, + PsqlBindArray::createInteger(id), + PsqlBindArray::createTimestamp(option_def->getModificationTime())); + } + + transaction.commit(); +#endif +} + +OptionDescriptorPtr +PgSqlConfigBackendImpl::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"); + +#if 0 + OptionContainer options; + PsqlBindArray in_bindings; + in_bindings.push_back(PsqlBindArray::createString(tag)); + if (universe == Option::V4) { + in_bindings.push_back(PsqlBindArray::createInteger(static_cast(code))); + } else { + in_bindings.push_back(PsqlBindArray::createInteger(code)); + } + in_bindings.push_back(PsqlBindArray::createString(space)); + getOptions(index, in_bindings, universe, options); + return (options.empty() ? OptionDescriptorPtr() : + OptionDescriptor::create(*options.begin())); +#endif +} + +OptionContainer +PgSqlConfigBackendImpl::getAllOptions(const int index, + const Option::Universe& universe, + const ServerSelector& server_selector) { + OptionContainer options; +#if 0 + auto tags = server_selector.getTags(); + for (auto tag : tags) { + PsqlBindArray in_bindings = { + PsqlBindArray::createString(tag.get()) + }; + getOptions(index, in_bindings, universe, options); + } +#endif + + return (options); +} + +OptionContainer +PgSqlConfigBackendImpl::getModifiedOptions(const int index, + const Option::Universe& universe, + const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) { + OptionContainer options; + + auto tags = server_selector.getTags(); + for (auto tag : tags) { + PsqlBindArray in_bindings; +#if 0 + PsqlBindArray in_bindings = { + PsqlBindArray::createString(tag.get()), + PsqlBindArray::createTimestamp(modification_time) + }; +#endif + getOptions(index, in_bindings, universe, options); + } + + return (options); +} + +OptionDescriptorPtr +PgSqlConfigBackendImpl::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; + PsqlBindArray in_bindings; +#if 0 + in_bindings.push_back(PsqlBindArray::createString(tag)); + uint32_t id = static_cast(subnet_id); + in_bindings.push_back(PsqlBindArray::createInteger(id)); + if (universe == Option::V4) { + in_bindings.push_back(PsqlBindArray::createInteger(static_cast(code))); + } else { + in_bindings.push_back(PsqlBindArray::createInteger(code)); + } + in_bindings.push_back(PsqlBindArray::createString(space)); +#endif + getOptions(index, in_bindings, universe, options); + return (options.empty() ? OptionDescriptorPtr() : + OptionDescriptor::create(*options.begin())); +} + +OptionDescriptorPtr +PgSqlConfigBackendImpl::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; + PsqlBindArray in_bindings; +#if 0 + in_bindings.push_back(PsqlBindArray::createString(tag)); + in_bindings.push_back(PsqlBindArray::createInteger(pool_id)); + if (pool_type == Lease::TYPE_V4) { + in_bindings.push_back(PsqlBindArray::createInteger(static_cast(code))); + } else { + in_bindings.push_back(PsqlBindArray::createInteger(code)); + universe = Option::V6; + } + in_bindings.push_back(PsqlBindArray::createString(space)); +#endif + getOptions(index, in_bindings, universe, options); + return (options.empty() ? OptionDescriptorPtr() : + OptionDescriptor::create(*options.begin())); +} + +OptionDescriptorPtr +PgSqlConfigBackendImpl::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; + PsqlBindArray in_bindings; +#if 0 + in_bindings.push_back(PsqlBindArray::createString(tag)); + in_bindings.push_back(PsqlBindArray::createString(shared_network_name)); + if (universe == Option::V4) { + in_bindings.push_back(PsqlBindArray::createInteger(static_cast(code))); + } else { + in_bindings.push_back(PsqlBindArray::createInteger(code)); + } + in_bindings.push_back(PsqlBindArray::createString(space)); +#endif + getOptions(index, in_bindings, universe, options); + return (options.empty() ? OptionDescriptorPtr() : + OptionDescriptor::create(*options.begin())); +} + +void +PgSqlConfigBackendImpl::getOptions(const int index, + const db::PsqlBindArray& in_bindings, + const Option::Universe& universe, + OptionContainer& options) { +#if 0 + // Create output bindings. The order must match that in the prepared + // statement. + PsqlBindArray out_bindings; + // option_id + out_bindings.push_back(PsqlBindArray::createInteger()); + // code + if (universe == Option::V4) { + out_bindings.push_back(PsqlBindArray::createInteger()); + } else { + out_bindings.push_back(PsqlBindArray::createInteger()); + } + // value + out_bindings.push_back(PsqlBindArray::createBlob(OPTION_VALUE_BUF_LENGTH)); + // forma\tted_value + out_bindings.push_back(PsqlBindArray::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH)); + // space + out_bindings.push_back(PsqlBindArray::createString(OPTION_SPACE_BUF_LENGTH)); + // persistent + out_bindings.push_back(PsqlBindArray::createInteger()); + // dhcp[46]_subnet_id + out_bindings.push_back(PsqlBindArray::createInteger()); + // scope_id + out_bindings.push_back(PsqlBindArray::createInteger()); + // user_context + out_bindings.push_back(PsqlBindArray::createString(USER_CONTEXT_BUF_LENGTH)); + // shared_network_name + out_bindings.push_back(PsqlBindArray::createString(SHARED_NETWORK_NAME_BUF_LENGTH)); + // pool_id + out_bindings.push_back(PsqlBindArray::createInteger()); + // modification_ts + out_bindings.push_back(PsqlBindArray::createTimestamp()); + // server_tag + out_bindings.push_back(PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH)); + // pd_pool_id + if (universe == Option::V6) { + out_bindings.push_back(PsqlBindArray::createInteger()); + } + + uint64_t last_option_id = 0; + + OptionContainer local_options; + + conn_.selectQuery(index, in_bindings, out_bindings, + [this, universe, &local_options, &last_option_id] + (PsqlBindArray& out_bindings) { + // Parse option. + if (!out_bindings[0]->amNull() && + ((last_option_id == 0) || + (last_option_id < out_bindings[0]->getInteger()))) { + last_option_id = out_bindings[0]->getInteger(); + + 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(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()); +#endif +} + +#if 0 +OptionDescriptorPtr +PgSqlConfigBackendImpl::processOptionRow(const Option::Universe& universe, + PsqlBindArray::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(); + } else { + code = (*(first_binding + 1))->getInteger(); + } + + + // 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 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((*(first_binding + 5))->getIntegerOrDefault(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()); + } + + return (desc); +} + +void +PgSqlConfigBackendImpl::attachElementToServers(const int index, + const ServerSelector& server_selector, + const PsqlBindArrayPtr& first_binding, + const PsqlBindArrayPtr& in_bindings...) { + // Create the vector from the parameter pack. + PsqlBindArray in_server_bindings = { first_binding, in_bindings }; + for (auto tag : server_selector.getTags()) { + in_server_bindings.push_back(PsqlBindArray::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(); + } +} +#endif + + +PsqlBindArrayPtr +PgSqlConfigBackendImpl::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())); + } + } + +#if 0 + return (relay_element->empty() ? PsqlBindArray::createNull() : + PsqlBindArray::condCreateString(relay_element->str())); +#endif +} + +PsqlBindArrayPtr +PgSqlConfigBackendImpl::createOptionValueBinding(const OptionDescriptorPtr& option) { + + PsqlBindArrayPtr p(new PsqlBindArray()); + 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(buf.getData()); + std::vector blob(buf_ptr + opt->getHeaderLen(), + buf_ptr + buf.getLength()); + + // return (PsqlBindArray::createBlob(blob.begin(), blob.end())); + } + + // return (PsqlBindArray::createNull()); + return (p); +} + +ServerPtr +PgSqlConfigBackendImpl::getServer(const int index, const ServerTag& server_tag) { + ServerCollection servers; + PsqlBindArray in_bindings; /* = { + PsqlBindArray::createString(server_tag.get()) + }; */ + getServers(index, in_bindings, servers); + + return (servers.empty() ? ServerPtr() : *servers.begin()); +} + +void +PgSqlConfigBackendImpl::getAllServers(const int index, db::ServerCollection& servers) { + PsqlBindArray in_bindings; + getServers(index, in_bindings, servers); +} + +void +PgSqlConfigBackendImpl::getServers(const int index, + const PsqlBindArray& in_bindings, + ServerCollection& servers) { + PsqlBindArray out_bindings; /*= { + PsqlBindArray::createInteger(), + PsqlBindArray::createString(SERVER_TAG_BUF_LENGTH), + PsqlBindArray::createString(SERVER_DESCRIPTION_BUF_LENGTH), + PsqlBindArray::createTimestamp() + }; */ +#if 0 + conn_.selectQuery(index, in_bindings, out_bindings, + [&servers](PsqlBindArray& out_bindings) { + + ServerPtr last_server; + uint64_t id = out_bindings[0]->getInteger(); + 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); + } + }); +#endif +} + +void +PgSqlConfigBackendImpl::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); + + PgSqlTransaction transaction(conn_); + + PsqlBindArray in_bindings; /* = { + PsqlBindArray::createString(server->getServerTagAsText()), + PsqlBindArray::createString(server->getDescription()), + PsqlBindArray::createTimestamp(server->getModificationTime()) + }; + + try { + conn_.insertQuery(create_index, in_bindings); + + } catch (const DuplicateEntry&) { + in_bindings.push_back(PsqlBindArray::createString(server->getServerTagAsText())); + conn_.updateDeleteQuery(update_index, in_bindings); + }*/ + + transaction.commit(); +} + +std::string +PgSqlConfigBackendImpl::getType() const { + return ("mysql"); +} + +std::string +PgSqlConfigBackendImpl::getHost() const { + std::string host = "localhost"; + try { + host = conn_.getParameter("host"); + } catch (...) { + // No host parameter. Return localhost as a default. + } + return (host); +} + +uint16_t +PgSqlConfigBackendImpl::getPort() const { + try { + std::string sport = conn_.getParameter("port"); + return (boost::lexical_cast(sport)); + + } catch (...) { + // No port parameter or parameter invalid. + } + return (0); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.h b/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.h new file mode 100644 index 0000000000..aad7b57887 --- /dev/null +++ b/src/hooks/dhcp/pgsql_cb/pgsql_cb_impl.h @@ -0,0 +1,617 @@ +// Copyright (C) 2021 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 PGSQL_CONFIG_BACKEND_IMPL_H +#define PGSQL_CONFIG_BACKEND_IMPL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Base class for PostgreSQL Config Backend implementations. +/// +/// This class contains common methods for manipulating data in the +/// Postgres database, used by all servers. +/// +/// All POSIX times specified in the methods belonging to this +/// class must be local times. +class PgSqlConfigBackendImpl { +protected: + + /// @brief RAII object used to protect against creating multiple + /// audit revisions during cascade configuration updates. + /// + /// Audit revision is created per a single database transaction. + /// It includes log message associated with the configuration + /// change. Single command sent over the control API should + /// result in a single audit revision entry in the database. + /// A single configuration update often consists of multiple + /// insertions, updates and/or deletes in the database. For + /// example, a subnet contains pools and DHCP options which are + /// inserted to their respective tables. We refer to such update + /// as a cascade update. Cascade update should result in a single + /// audit revision and an audit entry for a subnet, rather than + /// multiple audit revisions and audit entries for the subnet, + /// pools and child DHCP options. + /// + /// Creating an instance of the @c ScopedAuditRevision guards + /// against creation of multiple audit revisions when child + /// objects are inserted or updated in the database. When the + /// instance of this object goes out of scope the new audit + /// revisions can be created. The caller must ensure that + /// the instance of this object exists throughout the whole + /// transaction with the database. + class ScopedAuditRevision { + public: + + /// @brief Constructor. + /// + /// Creates new audit revision and sets the flag in the + /// Postgres CB implementation object which prevents new audit + /// revisions to be created while this instance exists. + /// + /// @param impl pointer to the Postgres CB implementation. + /// @param index index of the query to set session variables + /// used for creation of the audit revision and the audit + /// entries. + /// @param server_selector Server selector. + /// @param log_message log message associated with the audit + /// revision to be inserted into the database. + /// @param cascade_transaction boolean flag indicating if + /// we're performing cascade transaction. If set to true, + /// the audit entries for the child objects (e.g. DHCP + /// options) won't be created. + ScopedAuditRevision(PgSqlConfigBackendImpl* impl, + const int index, + const db::ServerSelector& server_selector, + const std::string& log_message, + bool cascade_transaction); + + /// @brief Destructor. + /// + /// Clears the flag which is blocking creation of the new + /// audit revisions. + ~ScopedAuditRevision(); + + private: + + /// @brief Pointer to the Postgres CB implementation. + PgSqlConfigBackendImpl* impl_; + }; + +public: + + /// @brief Constructor. + /// + /// @param parameters A data structure relating keywords and values + /// concerned with the database. + /// @param db_reconnect_callback The connection recovery callback. + explicit PgSqlConfigBackendImpl(const db::DatabaseConnection::ParameterMap& parameters, + const db::DbCallback db_reconnect_callback); + + /// @brief Destructor. + virtual ~PgSqlConfigBackendImpl(); + + /// @todo: implement condCreateInteger(const util::Optional& value) +#if 0 + static db::PsqlBindArrayPtr createBinding(const Triplet& triplet); + static db::PsqlBindArrayPtr createMaxBinding(const Triplet& triplet); + static db::PsqlBindArrayPtr createMinBinding(const Triplet& triplet); + static Triplet createTriplet(const db::PsqlBindArrayPtr& binding); + static Triplet createTriplet(const db::PsqlBindArrayPtr& def_binding, + const db::PsqlBindArrayPtr& min_binding, const db::PsqlBindArrayPtr& max_binding); +#endif + std::string getServerTag(const db::ServerSelector& server_selector, + const std::string& operation ); + + /// @brief Returns server tags associated with the particular selector + /// as text. + /// + /// This method is useful for logging purposes. + std::string getServerTagsAsText(const db::ServerSelector& server_selector) const { + std::ostringstream s; + auto server_tags = server_selector.getTags(); + for (auto tag : server_tags) { + if (s.tellp() != 0) { + s << ", "; + } + s << tag.get(); + } + + return (s.str()); + } + + + + /// @brief Invokes the corresponding stored procedure in MySQL. + /// + /// The @c createAuditRevision stored procedure creates new audit + /// revision and initializes several session variables to be used when + /// the audit entries will be created for the inserted, updated or + /// deleted configuration elements. + /// + /// @param index query index. + /// @param server_selector Server selector. + /// @param audit_ts Timestamp to be associated with the audit + /// revision. + /// @param log_message log message to be used for the audit revision. + /// @param cascade_transaction Boolean value indicating whether the + /// configuration modification is performed as part of the owning + /// element modification, e.g. subnet is modified resulting in + /// modification of the DHCP options it owns. In that case only the + /// audit entry for the owning element should be created. + void createAuditRevision(const int index, + const db::ServerSelector& server_selector, + const boost::posix_time::ptime& audit_ts, + const std::string& log_message, + const bool cascade_transaction); + + /// @brief Clears the flag blocking creation of the new audit revisions. + /// + /// This is used by the @c ScopedAuditRevision object. + void clearAuditRevision(); + + /// @brief Sends query to the database to retrieve most recent audit entries. + /// + /// @param index Index of the query to be used. + /// @param server_selector Server selector. + /// @param modification_time Timestamp being a lower limit for the returned + /// result set, i.e. entries later than specified time are returned. + /// @param modification_id Identifier being a lower limit for the returned + /// result set, used when two (or more) revisions have the same + /// modification_time. + /// @param [out] audit_entries Reference to the container where fetched audit + /// entries will be inserted. + void getRecentAuditEntries(const int index, + const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time, + const uint64_t& modification_id, + db::AuditEntryCollection& audit_entries); + + /// @todo: implement uint64_t deleteFromTable(const int index, ...) + + /// @brief Sends query to delete rows from a table. + /// + /// @param index Index of the statement to be executed. + /// @param server_selector Server selector. + /// @param operation Operation which results in calling this function. This is + /// used for error reporting purposes. + /// @return Number of deleted rows. + uint64_t deleteFromTable(const int index, + const db::ServerSelector& server_selector, + const std::string& operation); + + uint64_t deleteFromTable(const int index, + const db::ServerSelector& server_selector, + const std::string& operation, + db::PsqlBindArray& bindings); + + /// @brief Sends query to retrieve multiple global parameters. + /// + /// @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] parameters Reference to the container where fetched parameters + /// will be inserted. + void getGlobalParameters(const int index, + const db::PsqlBindArray& in_bindings, + data::StampedValueCollection& parameters); + + /// @brief Sends query to retrieve single option definition by code and + /// option space. + /// + /// @param index Index of the query to be used. + /// @param server_selector Server selector. + /// @param code Option code. + /// @param space Option space name. + /// + /// @return Pointer to the returned option definition or NULL if such + /// option definition doesn't exist. + OptionDefinitionPtr getOptionDef(const int index, + const db::ServerSelector& server_selector, + const uint16_t code, + const std::string& space); + + /// @brief Sends query to retrieve all option definitions. + /// + /// @param index Index of the query to be used. + /// @param server_selector Server selector. + /// @param [out] option_defs Reference to the container where option + /// definitions are to be stored. + void getAllOptionDefs(const int index, + const db::ServerSelector& server_selector, + OptionDefContainer& option_defs); + + /// @brief Sends query to retrieve option definitions with modification + /// time later than specified timestamp. + /// + /// @param index Index of the query to be used. + /// @param server_selector Server selector. + /// @param modification_time Lower bound subnet modification time. + /// @param [out] option_defs Reference to the container where option + /// definitions are to be stored. + void getModifiedOptionDefs(const int index, + const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time, + OptionDefContainer& option_defs); + + /// @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::PsqlBindArray& in_bindings, + OptionDefContainer& option_defs); + + /// @brief Creates or updates an option definition. + /// + /// @param server_selector Server selector. + /// @param option_def Option definition to be added or updated. + /// @param space Default option space + /// @param get_option_def_code_space Statement getting option + /// definition by code and space. + /// @param insert_option_def Statement inserting option definition. + /// @param update_option_def Statement updating option definition. + /// @param create_audit_revision Statement creating audit revision. + /// @param insert_option_def_server Statement associating option + /// definition with a server. + /// @throw NotImplemented if server selector is "unassigned". + void 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); + + /// @brief Sends query to retrieve single global option by code and + /// option space. + /// + /// @param index Index of the query to be used. + /// @param universe Option universe, i.e. V4 or V6. + /// @param server_selector Server selector. + /// @param code Option code. + /// @param space Option space name. + /// + /// @return Pointer to the returned option or NULL if such option + /// doesn't exist. + OptionDescriptorPtr + getOption(const int index, const Option::Universe& universe, + const db::ServerSelector& server_selector, const uint16_t code, + const std::string& space); + + /// @brief Sends query to retrieve all global options. + /// + /// @param index Index of the query to be used. + /// @param universe Option universe, i.e. V4 or V6. + /// @param server_selector Server selector. + /// @return Container holding returned options. + OptionContainer + getAllOptions(const int index, const Option::Universe& universe, + const db::ServerSelector& server_selector); + + /// @brief Sends query to retrieve global options with modification + /// time later than specified timestamp. + /// + /// @param index Index of the query to be used. + /// @param universe Option universe, i.e. V4 or V6. + /// @param server_selector Server selector. + /// @return Container holding returned options. + OptionContainer + getModifiedOptions(const int index, const Option::Universe& universe, + const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time); + + /// @brief Sends query to retrieve single option by code and option space + /// for a given subnet id. + /// + /// @param index Index of the query to be used. + /// @param universe Option universe, i.e. V4 or V6. + /// @param server_selector Server selector. + /// @param subnet_id Subnet identifier. + /// @param code Option code. + /// @param space Option space name. + /// + /// @return Pointer to the returned option descriptor or NULL if such + /// option doesn't exist. + OptionDescriptorPtr getOption(const int index, + const Option::Universe& universe, + const db::ServerSelector& server_selector, + const dhcp::SubnetID& subnet_id, + const uint16_t code, + const std::string& space); + + /// @brief Sends query to retrieve single option by code and option space + /// for a given address or prefix delegation (v6) pool id. + /// + /// @param index Index of the query to be used. + /// @param server_selector Server selector. + /// @param pool_type Pool type (Lease::TYPE_V4, TYPE_NA or TYPE_PD). + /// @param pool_id Pool identifier in the database. + /// @param code Option code. + /// @param space Option space name. + /// + /// @return Pointer to the returned option descriptor or NULL if such + /// option doesn't exist. + OptionDescriptorPtr getOption(const int index, + const db::ServerSelector& server_selector, + const dhcp::Lease::Type& pool_type, + const uint64_t pool_id, + const uint16_t code, + const std::string& space); + + /// @brief Sends query to retrieve single option by code and option space + /// for a given shared network. + /// + /// @param index Index of the query to be used. + /// @param universe Option universe, i.e. V4 or V6. + /// @param server_selector Server selector. + /// @param shared_network_name Shared network name. + /// @param code Option code. + /// @param space Option space name. + /// + /// @return Pointer to the returned option descriptor or NULL if such + /// option doesn't exist. + OptionDescriptorPtr getOption(const int index, + const Option::Universe& universe, + const db::ServerSelector& server_selector, + const std::string& shared_network_name, + const uint16_t code, + const std::string& space); + + /// @brief Sends query to the database to retrieve multiple options. + /// + /// Query should order by option_id. + /// + /// @note The universe is reused to switch between DHCPv4 and DHCPv6 + /// option layouts. + /// @param family Address family (either AF_INET or AF_INET6). + /// @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 universe Option universe, i.e. V4 or V6. + /// @param [out] options Reference to the container where fetched options + /// will be inserted. + void getOptions(const int index, + const db::PsqlBindArray& in_bindings, + const Option::Universe& universe, + OptionContainer& options); + + /// @todo implement OptionDescriptorPtr processOptionRow(const Option::Universe& universe, ...) + + /// @todo implement void attachElementToServers(const int index, ...) + + /// @todo implement + db::PsqlBindArrayPtr createInputRelayBinding(const NetworkPtr& network); + + /// @todo implement template db::MySqlBindingPtr createInputRequiredClassesBinding(const T& object) + + /// @todo implement db::MySqlBindingPtr createInputContextBinding(const T& config_element) { + + /// @todo implement + db::PsqlBindArrayPtr createOptionValueBinding(const OptionDescriptorPtr& option); + + /// @brief Retrieves a server. + /// + /// @param index Index of the query to be used. + /// @param server_tag Server tag of the server to be retrieved. + /// @return Pointer to the @c Server object representing the server or + /// null if such server doesn't exist. + db::ServerPtr getServer(const int index, const data::ServerTag& server_tag); + + /// @brief Retrieves all servers. + /// + /// @param index Index of the query to be used. + /// @param [out] servers Reference to the container where fetched servers + /// will be inserted. + void getAllServers(const int index, db::ServerCollection& servers); + + /// @todo implement + /// @brief Sends query to retrieve servers. + /// + /// @param index Index of the query to be used. + /// @param bindings Reference to the MySQL input bindings. + /// @param [out] servers Reference to the container where fetched servers + /// will be inserted. + void getServers(const int index, + const db::PsqlBindArray& bindings, + db::ServerCollection& servers); + + /// @brief Creates or updates a server. + /// + /// This method attempts to insert a new server into the database using + /// the query identified by @c create_index. If the insertion fails because + /// the server with the given tag already exists in the database, the + /// existing server is updated using the query identified by the + /// @c update_index. + /// + /// @param create_audit_revision Index of the query inserting audit + /// revision. + /// @param create_index Index of the INSERT query to be used. + /// @param update_index Index of the UPDATE query to be used. + /// @param server Pointer to the server to be inserted or updated. + /// @throw InvalidOperation when trying to create a duplicate or + /// update the logical server 'all'. + void createUpdateServer(const int& create_audit_revision, + const int& create_index, + const int& update_index, + const db::ServerPtr& server); + + /// @todo implement template void multipleUpdateDeleteQueries(T first_index, R... other_indexes) + + /// @brief Removes configuration elements from the index which don't match + /// the specified server selector. + /// + /// This is a generic function which removes configuration elements which + /// don't match the specified selector. In order to fetch all server tags + /// for the returned configuration element, the query must not limit the + /// results to the given server tag. Instead, it must post process the + /// result to eliminate those configuration elements for which the desired + /// server tag wasn't found. + /// + /// If the server selector is set to ANY, this method is no-op. + /// + /// @tparam CollectionIndex Type of the collection to be processed. + /// @param server_selector Server selector. + /// @param index Reference to the index holding the returned configuration + /// elements to be processed. + template + void tossNonMatchingElements(const db::ServerSelector& server_selector, + CollectionIndex& index) { + // Don't filter the matching server tags if the server selector is + // set to ANY. + if (server_selector.amAny()) { + return; + } + + // Go over the collection of elements. + for (auto elem = index.begin(); elem != index.end(); ) { + + // If we're asking for shared networks matching all servers, + // we have to make sure that the fetched element has "all" + // server tag. + if (server_selector.amAll()) { + if (!(*elem)->hasAllServerTag()) { + // It doesn't so let's remove it. + elem = index.erase(elem); + continue; + } + + } else if (server_selector.amUnassigned()) { + // Returned element has server tags but we expect that the + // elements are unassigned. + if (!(*elem)->getServerTags().empty()) { + elem = index.erase(elem); + continue; + } + + } else { + // Server selector contains explicit server tags, so + // let's see if the returned elements includes any of + // them. + auto tags = server_selector.getTags(); + bool tag_found = false; + for (auto tag : tags) { + if ((*elem)->hasServerTag(tag) || + (*elem)->hasAllServerTag()) { + tag_found = true; + break; + } + } + if (!tag_found) { + // Tag not matching, so toss the element. + elem = index.erase(elem); + continue; + } + } + + // Go to the next element if we didn't toss the current one. + // Otherwise, the erase() function should have already taken + // us to the next one. + ++elem; + } + } + + /// @brief Returns backend type in the textual format. + /// + /// @return "pgsql". + std::string getType() const; + + /// @brief Returns backend host. + /// + /// This is used by the @c BaseConfigBackendPool to select backend + /// when @c BackendSelector is specified. + /// + /// @return host on which the database is located. + std::string getHost() const; + + /// @brief Returns backend port number. + /// + /// This is used by the @c BaseConfigBackendPool to select backend + /// when @c BackendSelector is specified. + /// + /// @return Port number on which database service is available. + uint16_t getPort() const; + + /// @brief Return backend parameters + /// + /// Returns the backend parameters + /// + /// @return Parameters of the backend. + const isc::db::DatabaseConnection::ParameterMap& getParameters() { + return (parameters_); + } + + /// @brief Sets IO service to be used by the Postgres config backend. + /// + /// @param IOService object, used for all ASIO operations. + static void setIOService(const isc::asiolink::IOServicePtr& io_service) { + io_service_ = io_service; + } + + /// @brief Returns pointer to the IO service. + static isc::asiolink::IOServicePtr& getIOService() { + return (io_service_); + } + + /// @brief Represents connection to the Postgres database. + db::PgSqlConnection conn_; + +protected: + + /// @brief Timer name used to register database reconnect timer. + std::string timer_name_; + +private: + + /// @brief Boolean flag indicating if audit revision has been created + /// using @c ScopedAuditRevision object. + bool audit_revision_created_; + + /// @brief Connection parameters + isc::db::DatabaseConnection::ParameterMap parameters_; + + /// The IOService object, used for all ASIO operations. + static isc::asiolink::IOServicePtr io_service_; +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif