From: Francis Dupont Date: Fri, 22 Feb 2019 23:53:24 +0000 (+0100) Subject: [397-cb-implement-mysqlconfigbackenddhcpv6] Added DHCPv6 MySQL CB hook support X-Git-Tag: Kea-1.6.0-beta~251 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8aee9487e8957c8a4961c01df83beb123202c3ce;p=thirdparty%2Fkea.git [397-cb-implement-mysqlconfigbackenddhcpv6] Added DHCPv6 MySQL CB hook support --- diff --git a/src/hooks/dhcp/mysql_cb/Makefile.am b/src/hooks/dhcp/mysql_cb/Makefile.am index f09469216c..f965573afc 100644 --- a/src/hooks/dhcp/mysql_cb/Makefile.am +++ b/src/hooks/dhcp/mysql_cb/Makefile.am @@ -15,6 +15,7 @@ noinst_LTLIBRARIES = libmysqlcb.la libmysqlcb_la_SOURCES = mysql_cb_callouts.cc libmysqlcb_la_SOURCES += mysql_cb_dhcp4.cc mysql_cb_dhcp4.h +libmysqlcb_la_SOURCES += mysql_cb_dhcp6.cc mysql_cb_dhcp6.h libmysqlcb_la_SOURCES += mysql_cb_impl.cc mysql_cb_impl.h libmysqlcb_la_SOURCES += mysql_cb_messages.cc mysql_cb_messages.h libmysqlcb_la_SOURCES += mysql_query_macros_dhcp.h diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc new file mode 100644 index 0000000000..83c3f4e624 --- /dev/null +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc @@ -0,0 +1,2911 @@ +// Copyright (C) 2019 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace isc::cb; +using namespace isc::db; +using namespace isc::data; +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +/// @brief Implementation of the MySQL Configuration Backend. +class MySqlConfigBackendDHCPv6Impl : public MySqlConfigBackendImpl { +public: + + /// @brief Statement tags. + /// + /// The contents of the enum are indexes into the list of SQL statements. + /// It is assumed that the order is such that the indices of statements + /// reading the database are less than those of statements modifying the + /// database. + enum StatementIndex { + CREATE_AUDIT_REVISION, + GET_GLOBAL_PARAMETER6, + GET_ALL_GLOBAL_PARAMETERS6, + GET_MODIFIED_GLOBAL_PARAMETERS6, + GET_SUBNET6_ID, + GET_SUBNET6_PREFIX, + GET_ALL_SUBNETS6, + GET_MODIFIED_SUBNETS6, + GET_SHARED_NETWORK_SUBNETS6, + GET_POOL6_RANGE, + GET_PD_POOL, + GET_SHARED_NETWORK6_NAME, + GET_ALL_SHARED_NETWORKS6, + GET_MODIFIED_SHARED_NETWORKS6, + GET_OPTION_DEF6_CODE_SPACE, + GET_ALL_OPTION_DEFS6, + GET_MODIFIED_OPTION_DEFS6, + GET_OPTION6_CODE_SPACE, + GET_ALL_OPTIONS6, + GET_MODIFIED_OPTIONS6, + GET_OPTION6_SUBNET_ID_CODE_SPACE, + GET_OPTION6_POOL_ID_CODE_SPACE, + GET_OPTION6_PD_POOL_ID_CODE_SPACE, + GET_OPTION6_SHARED_NETWORK_CODE_SPACE, + GET_AUDIT_ENTRIES6_TIME, + INSERT_GLOBAL_PARAMETER6, + INSERT_GLOBAL_PARAMETER6_SERVER, + INSERT_SUBNET6, + INSERT_SUBNET6_SERVER, + INSERT_POOL6, + INSERT_PD_POOL, + INSERT_SHARED_NETWORK6, + INSERT_SHARED_NETWORK6_SERVER, + INSERT_OPTION_DEF6, + INSERT_OPTION_DEF6_SERVER, + INSERT_OPTION6, + INSERT_OPTION6_SERVER, + UPDATE_GLOBAL_PARAMETER6, + UPDATE_SUBNET6, + UPDATE_SHARED_NETWORK6, + UPDATE_OPTION_DEF6, + UPDATE_OPTION6, + UPDATE_OPTION6_SUBNET_ID, + UPDATE_OPTION6_POOL_ID, + UPDATE_OPTION6_PD_POOL_ID, + UPDATE_OPTION6_SHARED_NETWORK, + DELETE_GLOBAL_PARAMETER6, + DELETE_ALL_GLOBAL_PARAMETERS6, + DELETE_SUBNET6_ID, + DELETE_SUBNET6_PREFIX, + DELETE_ALL_SUBNETS6, + DELETE_POOLS6_SUBNET_ID, + DELETE_PD_POOLS_SUBNET_ID, + DELETE_SHARED_NETWORK6_NAME, + DELETE_ALL_SHARED_NETWORKS6, + DELETE_OPTION_DEF6_CODE_NAME, + DELETE_ALL_OPTION_DEFS6, + DELETE_OPTION6, + DELETE_OPTION6_SUBNET_ID, + DELETE_OPTION6_POOL_RANGE, + DELETE_OPTION6_PD_POOL, + DELETE_OPTION6_SHARED_NETWORK, + DELETE_OPTIONS6_SUBNET_ID, + DELETE_OPTIONS6_SHARED_NETWORK, + NUM_STATEMENTS + }; + + /// @brief Constructor. + /// + /// @param parameters A data structure relating keywords and values + /// concerned with the database. + explicit MySqlConfigBackendDHCPv6Impl(const DatabaseConnection::ParameterMap& + parameters); + + /// @brief Sends query to retrieve global parameter. + /// + /// @param server_selector Server selector. + /// @param name Name of the parameter to be retrieved. + /// + /// @return Pointer to the retrieved value or null if such parameter + /// doesn't exist. + StampedValuePtr getGlobalParameter6(const ServerSelector& server_selector, + const std::string& name) { + StampedValueCollection parameters; + + auto tags = getServerTags(server_selector); + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createString(name) + }; + + getGlobalParameters(GET_GLOBAL_PARAMETER6, in_bindings, parameters); + } + + return (parameters.empty() ? StampedValuePtr() : *parameters.begin()); + } + + /// @brief Sends query to insert or update global parameter. + /// + /// @param server_selector Server selector. + /// @param name Name of the global parameter. + /// @param value Value of the global parameter. + void createUpdateGlobalParameter6(const db::ServerSelector& server_selector, + const StampedValuePtr& value) { + + 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 global parameter"); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(value->getName()), + MySqlBinding::createString(value->getValue()), + MySqlBinding::createInteger(value->getType()), + MySqlBinding::createTimestamp(value->getModificationTime()), + MySqlBinding::createString(tag), + MySqlBinding::createString(value->getName()) + }; + + 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, MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, "global parameter set", false); + + // Try to update the existing row. + if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_GLOBAL_PARAMETER6, + in_bindings) == 0) { + + // No such parameter found, so let's insert it. We have to adjust the + // bindings collection to match the prepared statement for insert. + in_bindings.pop_back(); + in_bindings.pop_back(); + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_GLOBAL_PARAMETER6, + in_bindings); + + // Successfully inserted global parameter. Now, we have to associate it + // with the server tag. + + // Let's first get the primary key of the global parameter. + uint64_t id = mysql_insert_id(conn_.mysql_); + + // Create bindings for inserting the association into + // dhcp6_global_parameter_server table. + MySqlBindingCollection in_server_bindings = { + MySqlBinding::createInteger(id), // parameter_id + MySqlBinding::createString(tag), // tag used to obtain server_id + MySqlBinding::createTimestamp(value->getModificationTime()), // modification_ts + }; + + // Insert association. + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_GLOBAL_PARAMETER6_SERVER, + in_server_bindings); + + } + + transaction.commit(); + } + + /// @brief Sends query to the database to retrieve multiple subnets. + /// + /// Query should order subnets by subnet_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] subnets Reference to the container where fetched subnets + /// will be inserted. + void getSubnets6(const StatementIndex& index, + const MySqlBindingCollection& in_bindings, + Subnet6Collection& subnets) { + // Create output bindings. The order must match that in the prepared + // statement. + MySqlBindingCollection out_bindings = { + MySqlBinding::createInteger(), // subnet_id + MySqlBinding::createString(SUBNET6_PREFIX_BUF_LENGTH), // subnet_prefix + MySqlBinding::createString(CLIENT_CLASS_BUF_LENGTH), // client_class + MySqlBinding::createString(INTERFACE_BUF_LENGTH), // interface + MySqlBinding::createTimestamp(), // modification_ts + MySqlBinding::createInteger(), // preferred_lifetime + MySqlBinding::createInteger(), // rapid_commit + MySqlBinding::createInteger(), // rebind_timer + MySqlBinding::createString(RELAY_BUF_LENGTH), // relay + MySqlBinding::createInteger(), // renew_timer + MySqlBinding::createString(REQUIRE_CLIENT_CLASSES_BUF_LENGTH), // require_client_classes + MySqlBinding::createInteger(), // reservation_mode + MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // shared_network_name + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // user_context + MySqlBinding::createInteger(), // valid_lifetime + MySqlBinding::createInteger(), // pool: id + MySqlBinding::createString(POOL_ADDRESS6_BUF_LENGTH), // pool: start_address + MySqlBinding::createString(POOL_ADDRESS6_BUF_LENGTH), // pool: end_address + MySqlBinding::createInteger(), // pool: subnet_id + MySqlBinding::createTimestamp(), // pool: modification_ts + MySqlBinding::createInteger(), // pd pool: id + MySqlBinding::createString(POOL_ADDRESS6_BUF_LENGTH), // pd pool: prefix + MySqlBinding::createInteger(), // pd pool: prefix_length + MySqlBinding::createInteger(), // pd pool: delegated_prefix_length + MySqlBinding::createInteger(), // pd pool: subnet_id + MySqlBinding::createTimestamp(), // pd pool: modification_ts + MySqlBinding::createInteger(), // pool option: option_id + MySqlBinding::createInteger(), // pool option: code + MySqlBinding::createBlob(OPTION_VALUE_BUF_LENGTH), // pool option: value + MySqlBinding::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH), // pool option: formatted_value + MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // pool option: space + MySqlBinding::createInteger(), // pool option: persistent + MySqlBinding::createInteger(), // pool option: dhcp6_subnet_id + MySqlBinding::createInteger(), // pool option: scope_id + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // pool option: user_context + MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // pool option: shared_network_name + MySqlBinding::createInteger(), // pool option: pool_id + MySqlBinding::createTimestamp(), // pool option: modification_ts + MySqlBinding::createInteger(), // pool option: pd_pool_id + MySqlBinding::createInteger(), // pd pool option: option_id + MySqlBinding::createInteger(), // pd pool option: code + MySqlBinding::createBlob(OPTION_VALUE_BUF_LENGTH), // pd pool option: value + MySqlBinding::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH), // pd pool option: formatted_value + MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // pd pool option: space + MySqlBinding::createInteger(), // pd pool option: persistent + MySqlBinding::createInteger(), // pd pool option: dhcp6_subnet_id + MySqlBinding::createInteger(), // pd pool option: scope_id + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // pd pool option: user_context + MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // pd pool option: shared_network_name + MySqlBinding::createInteger(), // pd pool option: pool_id + MySqlBinding::createTimestamp(), // pd pool option: modification_ts + MySqlBinding::createInteger(), // pd pool option: pd_pool_id + MySqlBinding::createInteger(), // option: option_id + MySqlBinding::createInteger(), // option: code + MySqlBinding::createBlob(OPTION_VALUE_BUF_LENGTH), // option: value + MySqlBinding::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH), // option: formatted_value + MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // option: space + MySqlBinding::createInteger(), // option: persistent + MySqlBinding::createInteger(), // option: dhcp6_subnet_id + MySqlBinding::createInteger(), // option: scope_id + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // option: user_context + MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // option: shared_network_name + MySqlBinding::createInteger(), // option: pool_id + MySqlBinding::createTimestamp(), // option: modification_ts + MySqlBinding::createInteger() // option: pd_pool_id + }; + + uint64_t last_pool_id = 0; + uint64_t last_pd_pool_id = 0; + uint64_t last_pool_option_id = 0; + uint64_t last_pd_pool_option_id = 0; + uint64_t last_option_id = 0; + + Pool6Ptr last_pool; + Pool6Ptr last_pd_pool; + + // Execute actual query. + conn_.selectQuery(index, in_bindings, out_bindings, + [this, &subnets, &last_pool, &last_pd_pool, + &last_pool_id, &last_pd_pool_id, + &last_pool_option_id, &last_pd_pool_option_id, + &last_option_id] + (MySqlBindingCollection& out_bindings) { + // Get pointer to the last subnet in the collection. + Subnet6Ptr last_subnet; + if (!subnets.empty()) { + last_subnet = *subnets.rbegin(); + } + + // Subnet has been returned. Assuming that subnets are ordered by + // subnet identifier, if the subnet identifier of the current row + // is different than the subnet identifier of the previously returned + // row, it means that we have to construct new subnet object. + if (!last_subnet || (last_subnet->getID() != out_bindings[0]->getInteger())) { + + // Reset pool ids, because current row defines new subnet. Subsequent + // rows will contain pool information. + last_pool_id = 0; + last_pd_pool_id = 0; + + // subnet_id + SubnetID subnet_id(out_bindings[0]->getInteger()); + + // subnet_prefix + std::string subnet_prefix = out_bindings[1]->getString(); + auto prefix_pair = Subnet6::parsePrefix(subnet_prefix); + + // preferred_lifetime + auto preferred_lifetime = createTriplet(out_bindings[5]); + + // renew_timer + auto renew_timer = createTriplet(out_bindings[9]); + + // rebind_timer + auto rebind_timer = createTriplet(out_bindings[7]); + + // valid_lifetime + auto valid_lifetime = createTriplet(out_bindings[14]); + + // Create subnet with basic settings. + last_subnet.reset(new Subnet6(prefix_pair.first, prefix_pair.second, + renew_timer, rebind_timer, + preferred_lifetime, + valid_lifetime, subnet_id)); + + // client_class + if (!out_bindings[2]->amNull()) { + last_subnet->allowClientClass(out_bindings[2]->getString()); + } + + // interface + last_subnet->setIface(out_bindings[3]->getStringOrDefault("")); + + // modification_ts + last_subnet->setModificationTime(out_bindings[4]->getTimestamp()); + // 5 is preferred_lifetime + + // rapid_commit + last_subnet->setRapidCommit(static_cast + (out_bindings[6]->getIntegerOrDefault(0))); + + // 7 is rebind_timer + + // relay + ElementPtr relay_element = out_bindings[8]->getJSON(); + if (relay_element) { + if (relay_element->getType() != Element::list) { + isc_throw(BadValue, "invalid relay value " + << out_bindings[8]->getString()); + } + for (auto i = 0; i < relay_element->size(); ++i) { + auto relay_address_element = relay_element->get(i); + if (relay_address_element->getType() != Element::string) { + isc_throw(BadValue, "relay address must be a string"); + } + last_subnet->addRelayAddress(IOAddress(relay_element->get(i)->stringValue())); + } + } + + // 9 is renew_timer + + // require_client_classes + ElementPtr require_element = out_bindings[10]->getJSON(); + if (require_element) { + if (require_element->getType() != Element::list) { + isc_throw(BadValue, "invalid require_client_classes value " + << out_bindings[10]->getString()); + } + for (auto i = 0; i < require_element->size(); ++i) { + auto require_item = require_element->get(i); + if (require_item->getType() != Element::string) { + isc_throw(BadValue, "elements of require_client_classes list must" + "be valid strings"); + } + last_subnet->requireClientClass(require_item->stringValue()); + } + } + + // reservation_mode + last_subnet->setHostReservationMode(static_cast + (out_bindings[11]->getIntegerOrDefault(Subnet6::HR_ALL))); + + // shared_network_name + last_subnet->setSharedNetworkName(out_bindings[12]->getStringOrDefault("")); + + // user_context + ElementPtr user_context = out_bindings[13]->getJSON(); + if (user_context) { + last_subnet->setContext(user_context); + } + + // 14 is valid_lifetime + + // Subnet ready. Add it to the list. + subnets.push_back(last_subnet); + } + + // Pool is between 15 and 19 + + // If the row contains information about the pool and it + // appears to be new pool entry (checked by comparing pool + // id), let's create the new pool and add it to the + // subnet. + if (!out_bindings[15]->amNull() && + !out_bindings[16]->getString().empty() && + !out_bindings[17]->getString().empty() && + (out_bindings[15]->getInteger() > last_pool_id)) { + last_pool_id = out_bindings[15]->getInteger(); + last_pool.reset(new Pool6(Lease::TYPE_NA, + IOAddress(out_bindings[16]->getString()), + IOAddress(out_bindings[17]->getString()))); + last_subnet->addPool(last_pool); + } + + // Pd Pool is between 20 and 25 + + // If the row contains information about the pd pool and + // it appears to be new pd pool entry (checked by + // comparing pd pool id), let's create the new pd pool and + // add it to the subnet. + if (!out_bindings[20]->amNull() && + !out_bindings[21]->getString().empty() && + (out_bindings[22]->getInteger() != 0) && + (out_bindings[23]->getInteger() != 0) && + (out_bindings[20]->getInteger() > last_pd_pool_id)) { + last_pd_pool_id = out_bindings[20]->getInteger(); + last_pd_pool.reset(new Pool6(Lease::TYPE_PD, + IOAddress(out_bindings[21]->getString()), + out_bindings[22]->getInteger(), + out_bindings[23]->getInteger())); + last_subnet->addPool(last_pd_pool); + } + + // Parse pool specific option between 26 and 38 + if (last_pool && !out_bindings[26]->amNull() && + (last_pool_option_id < out_bindings[26]->getInteger())) { + last_pool_option_id = out_bindings[26]->getInteger(); + + OptionDescriptorPtr desc = processOptionRow(Option::V6, out_bindings.begin() + 26); + if (desc) { + last_pool->getCfgOption()->add(*desc, desc->space_name_); + } + } + + // Parse pd pool specific option between 39 and 52 + if (last_pd_pool && !out_bindings[39]->amNull() && + (last_pd_pool_option_id < out_bindings[39]->getInteger())) { + last_pd_pool_option_id = out_bindings[39]->getInteger(); + + OptionDescriptorPtr desc = processOptionRow(Option::V6, out_bindings.begin() + 39); + if (desc) { + last_pd_pool->getCfgOption()->add(*desc, desc->space_name_); + } + } + + // Parse subnet specific option between 53 and 66 + if (!out_bindings[52]->amNull() && + (last_option_id < out_bindings[52]->getInteger())) { + last_option_id = out_bindings[52]->getInteger(); + + OptionDescriptorPtr desc = processOptionRow(Option::V6, out_bindings.begin() + 52); + if (desc) { + last_subnet->getCfgOption()->add(*desc, desc->space_name_); + } + } + + }); + } + + /// @brief Sends query to retrieve single subnet by id. + /// + /// @param server_selector Server selector. + /// @param subnet_id Subnet identifier. + /// + /// @return Pointer to the returned subnet or NULL if such subnet + /// doesn't exist. + Subnet6Ptr getSubnet6(const ServerSelector& server_selector, + const SubnetID& subnet_id) { + Subnet6Collection subnets; + + auto tags = getServerTags(server_selector); + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createInteger(subnet_id) + }; + + getSubnets6(GET_SUBNET6_ID, in_bindings, subnets); + } + + return (subnets.empty() ? Subnet6Ptr() : *subnets.begin()); + } + + /// @brief Sends query to retrieve single subnet by prefix. + /// + /// The prefix should be in the following format: "2001:db8:1::/64". + /// + /// @param server_selector Server selector. + /// @param subnet_id Subnet identifier. + /// + /// @return Pointer to the returned subnet or NULL if such subnet + /// doesn't exist. + Subnet6Ptr getSubnet6(const ServerSelector& server_selector, + const std::string& subnet_prefix) { + Subnet6Collection subnets; + + auto tags = getServerTags(server_selector); + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createString(subnet_prefix) + }; + + getSubnets6(GET_SUBNET6_PREFIX, in_bindings, subnets); + } + + return (subnets.empty() ? Subnet6Ptr() : *subnets.begin()); + } + + /// @brief Sends query to retrieve all subnets. + /// + /// @param server_selector Server selector. + /// @param [out] subnets Reference to the subnet collection structure where + /// subnets should be inserted. + void getAllSubnets6(const ServerSelector& server_selector, + Subnet6Collection& subnets) { + auto tags = getServerTags(server_selector); + + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag) + }; + + getSubnets6(GET_ALL_SUBNETS6, in_bindings, subnets); + } + } + + /// @brief Sends query to retrieve modified subnets. + /// + /// @param server_selector Server selector. + /// @param modification_ts Lower bound modification timestamp. + /// @param [out] subnets Reference to the subnet collection structure where + /// subnets should be inserted. + void getModifiedSubnets6(const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_ts, + Subnet6Collection& subnets) { + auto tags = getServerTags(server_selector); + + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createTimestamp(modification_ts) + }; + + getSubnets6(GET_MODIFIED_SUBNETS6, in_bindings, subnets); + } + } + + /// @brief Sends query to retrieve all subnets belonging to a shared network. + /// + /// @param server_selector Server selector. + /// @param shared_network_name Name of the shared network for which the + /// subnets should be retrieved. + /// @param [out] subnets Reference to the subnet collection structure where + /// subnets should be inserted. + void getSharedNetworkSubnets6(const ServerSelector& server_selector, + const std::string& shared_network_name, + Subnet6Collection& subnets) { + auto tags = getServerTags(server_selector); + + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createString(shared_network_name) + }; + + getSubnets6(GET_SHARED_NETWORK_SUBNETS6, in_bindings, subnets); + } + } + + /// @brief Sends query to retrieve multiple pools. + /// + /// Query should order pools 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] pools Reference to the container where fetched pools + /// will be inserted. + /// @param [out] pool_ids Identifiers of the pools returned in @c pools + /// argument. + void getPools(const StatementIndex& index, + const MySqlBindingCollection& in_bindings, + PoolCollection& pools, + std::vector& pool_ids) { + MySqlBindingCollection out_bindings = { + MySqlBinding::createInteger(), // pool: id + MySqlBinding::createString(POOL_ADDRESS6_BUF_LENGTH), // pool: start_address + MySqlBinding::createString(POOL_ADDRESS6_BUF_LENGTH), // pool: end_address + MySqlBinding::createInteger(), // pool: subnet_id + MySqlBinding::createTimestamp(), // pool: modification_ts + MySqlBinding::createInteger(), // pool option: option_id + MySqlBinding::createInteger(), // pool option: code + MySqlBinding::createBlob(OPTION_VALUE_BUF_LENGTH), // pool option: value + MySqlBinding::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH), // pool option: formatted_value + MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // pool option: space + MySqlBinding::createInteger(), // pool option: persistent + MySqlBinding::createInteger(), // pool option: dhcp6_subnet_id + MySqlBinding::createInteger(), // pool option: scope_id + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // pool option: user_context + MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // pool option: shared_network_name + MySqlBinding::createInteger(), // pool option: pool_id + MySqlBinding::createTimestamp(), //pool option: modification_ts + MySqlBinding::createInteger(), // pool option: pd_pool_id + }; + + uint64_t last_pool_id = 0; + uint64_t last_pool_option_id = 0; + Pool6Ptr last_pool; + + conn_.selectQuery(index, in_bindings, out_bindings, + [this, &last_pool_id, &last_pool_option_id, &last_pool, + &pools, &pool_ids] + (MySqlBindingCollection& out_bindings) { + if (out_bindings[0]->getInteger() > last_pool_id) { + + last_pool_id = out_bindings[0]->getInteger(); + + last_pool.reset(new Pool6(Lease::TYPE_NA, + IOAddress(out_bindings[1]->getString()), + IOAddress(out_bindings[2]->getString()))); + pools.push_back(last_pool); + pool_ids.push_back(last_pool_id); + } + + // Parse pool specific option. + if (last_pool && !out_bindings[5]->amNull() && + (last_pool_option_id < out_bindings[5]->getInteger())) { + last_pool_option_id = out_bindings[5]->getInteger(); + + OptionDescriptorPtr desc = processOptionRow(Option::V6, out_bindings.begin() + 5); + if (desc) { + last_pool->getCfgOption()->add(*desc, desc->space_name_); + } + } + }); + } + + /// @brief Sends query to retrieve multiple pd pools. + /// + /// Query should order pd pools 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] pd_pools Reference to the container where fetched pools + /// will be inserted. + /// @param [out] pd_pool_ids Identifiers of the pd pools returned in + /// @c pd_pools argument. + void getPdPools(const StatementIndex& index, + const MySqlBindingCollection& in_bindings, + PoolCollection& pd_pools, + std::vector& pd_pool_ids) { + MySqlBindingCollection out_bindings = { + MySqlBinding::createInteger(), // pd pool: id + MySqlBinding::createString(POOL_ADDRESS6_BUF_LENGTH), // pd pool: prefix + MySqlBinding::createInteger(), // pd pool: prefix_length + MySqlBinding::createInteger(), // pd pool: delegated_prefix_length + MySqlBinding::createInteger(), // pd pool: subnet_id + MySqlBinding::createTimestamp(), // pd pool: modification_ts + MySqlBinding::createInteger(), // pd pool option: option_id + MySqlBinding::createInteger(), // pd pool option: code + MySqlBinding::createBlob(OPTION_VALUE_BUF_LENGTH), // pd pool option: value + MySqlBinding::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH), // pd pool option: formatted_value + MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // pd pool option: space + MySqlBinding::createInteger(), // pd pool option: persistent + MySqlBinding::createInteger(), // pd pool option: dhcp6_subnet_id + MySqlBinding::createInteger(), // pd pool option: scope_id + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // pd pool option: user_context + MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // pd pool option: shared_network_name + MySqlBinding::createInteger(), // pd pool option: pool_id + MySqlBinding::createTimestamp(), // pd pool option: modification_ts + MySqlBinding::createInteger() // pd pool option: pd_pool_id + }; + + uint64_t last_pd_pool_id = 0; + uint64_t last_pd_pool_option_id = 0; + Pool6Ptr last_pd_pool; + + conn_.selectQuery(index, in_bindings, out_bindings, + [this, &last_pd_pool_id, &last_pd_pool_option_id, + &last_pd_pool, &pd_pools, &pd_pool_ids] + (MySqlBindingCollection& out_bindings) { + if (out_bindings[0]->getInteger() > last_pd_pool_id) { + + last_pd_pool_id = out_bindings[0]->getInteger(); + + last_pd_pool.reset(new Pool6(Lease::TYPE_PD, + IOAddress(out_bindings[1]->getString()), + out_bindings[2]->getInteger(), + out_bindings[3]->getInteger())); + pd_pools.push_back(last_pd_pool); + pd_pool_ids.push_back(last_pd_pool_id); + } + + // Parse pool specific option between 6 and 19 + if (last_pd_pool && !out_bindings[6]->amNull() && + (last_pd_pool_option_id < out_bindings[6]->getInteger())) { + last_pd_pool_option_id = out_bindings[6]->getInteger(); + + OptionDescriptorPtr desc = processOptionRow(Option::V6, out_bindings.begin() + 6); + if (desc) { + last_pd_pool->getCfgOption()->add(*desc, desc->space_name_); + } + } + }); + } + + /// @brief Sends query to retrieve single pool by address range. + /// + /// @param server_selector Server selector. + /// @param pool_start_address Lower bound pool address. + /// @param pool_end_address Upper bound pool address. + /// @param pool_id Pool identifier for the returned pool. + /// @return Pointer to the pool or null if no such pool found. + Pool6Ptr getPool6(const ServerSelector& /* server_selector */, + const IOAddress& pool_start_address, + const IOAddress& pool_end_address, + uint64_t& pool_id) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(pool_start_address.toText()), + MySqlBinding::createString(pool_end_address.toText()) + }; + + PoolCollection pools; + std::vector pool_ids; + getPools(GET_POOL6_RANGE, in_bindings, pools, pool_ids); + + if (!pools.empty()) { + pool_id = pool_ids[0]; + return (boost::dynamic_pointer_cast(*pools.begin())); + } + + pool_id = 0; + + return (Pool6Ptr()); + } + + /// @brief Sends query to retrieve single pd pool. + /// + /// @param server_selector Server selector. + /// @param pd_pool_prefix Address part of the pd pool prefix. + /// @param pd_pool_prefix_length Length of the pd pool prefix. + /// @param pd_pool_id Pool identifier for the returned pool. + /// @return Pointer to the pool or null if no such pool found. + Pool6Ptr getPdPool6(const ServerSelector& /* server_selector */, + const asiolink::IOAddress& pd_pool_prefix, + const uint8_t pd_pool_prefix_length, + uint64_t& pd_pool_id) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(pd_pool_prefix.toText()), + MySqlBinding::createInteger(pd_pool_prefix_length) + }; + + PoolCollection pd_pools; + std::vector pd_pool_ids; + getPdPools(GET_PD_POOL, in_bindings, pd_pools, pd_pool_ids); + + if (!pd_pools.empty()) { + pd_pool_id = pd_pool_ids[0]; + return (boost::dynamic_pointer_cast(*pd_pools.begin())); + } + + pd_pool_id = 0; + + return (Pool6Ptr()); + } + + /// @brief Sends query to insert or update subnet. + /// + /// @param server_selector Server selector. + /// @param subnet Pointer to the subnet to be inserted or updated. + void createUpdateSubnet6(const ServerSelector& server_selector, + const Subnet6Ptr& subnet) { + + 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 subnet"); + + // Create JSON list of required classes. + ElementPtr required_classes_element = Element::createList(); + const auto& required_classes = subnet->getRequiredClasses(); + for (auto required_class = required_classes.cbegin(); + required_class != required_classes.cend(); + ++required_class) { + required_classes_element->add(Element::create(*required_class)); + } + + // Create binding with shared network name if the subnet belongs to a + // shared network. + MySqlBindingPtr shared_network_binding; + + SharedNetwork6Ptr shared_network; + subnet->getSharedNetwork(shared_network); + + // Check if the subnet is associated with a shared network instance. + // If it is, create the binding using the name of the shared network. + if (shared_network) { + shared_network_binding = MySqlBinding::createString(shared_network->getName()); + + // If the subnet is associated with a shared network by name (no + // shared network instance), use this name to create the binding. + // This may be the case if the subnet is added as a result of + // receiving a control command that merely specifies shared + // network name. In that case, it is expected that the shared + // network data is already stored in the database. + } else if (!subnet->getSharedNetworkName().empty()) { + shared_network_binding = MySqlBinding::createString(subnet->getSharedNetworkName()); + + // If the subnet is not associated with a shared network, create + // null binding. + } else { + shared_network_binding = MySqlBinding::createNull(); + } + + // Create input bindings. + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(subnet->getID()), + MySqlBinding::createString(subnet->toText()), + MySqlBinding::condCreateString(subnet->getClientClass()), + MySqlBinding::condCreateString(subnet->getIface()), + MySqlBinding::createTimestamp(subnet->getModificationTime()), + MySqlBinding::createInteger(subnet->getPreferred()), + MySqlBinding::createInteger(static_cast(subnet->getRapidCommit())), + createBinding(subnet->getT2()), + createInputRelayBinding(subnet), + createBinding(subnet->getT1()), + createInputRequiredClassesBinding(subnet), + MySqlBinding::createInteger(static_cast(subnet->getHostReservationMode())), + shared_network_binding, + createInputContextBinding(subnet), + createBinding(subnet->getValid()) + }; + + 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, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, "subnet set", true); + + try { + + // Try to insert subnet. If this duplicates primary key, i.e. this + // subnet already exists it will throw DuplicateEntry exception in + // which case we'll try an update. + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_SUBNET6, + in_bindings); + + // Create bindings for inserting the association into + // dhcp6_subnet_server table. + MySqlBindingCollection in_server_bindings = { + MySqlBinding::createInteger(subnet->getID()), // subnet_id + MySqlBinding::createString(tag), // tag used to obtain server_id + MySqlBinding::createTimestamp(subnet->getModificationTime()), // modification_ts + }; + + // Insert association. + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_SUBNET6_SERVER, + in_server_bindings); + + + } catch (const DuplicateEntry&) { + deletePools6(subnet); + deletePdPools6(subnet); + deleteOptions6(server_selector, subnet); + + // Need to add one more binding for WHERE clause. + in_bindings.push_back(MySqlBinding::createInteger(subnet->getID())); + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_SUBNET6, + in_bindings); + } + + // (Re)create pools. + for (auto pool : subnet->getPools(Lease::TYPE_NA)) { + createPool6(server_selector, boost::dynamic_pointer_cast(pool), + subnet); + } + + // (Re)create pd pools. + for (auto pd_pool : subnet->getPools(Lease::TYPE_PD)) { + createPdPool6(server_selector, boost::dynamic_pointer_cast(pd_pool), + subnet); + } + + // (Re)create options. + auto option_spaces = subnet->getCfgOption()->getOptionSpaceNames(); + for (auto option_space : option_spaces) { + OptionContainerPtr options = subnet->getCfgOption()->getAll(option_space); + for (auto desc = options->begin(); desc != options->end(); ++desc) { + OptionDescriptorPtr desc_copy(new OptionDescriptor(*desc)); + desc_copy->space_name_ = option_space; + createUpdateOption6(server_selector, subnet->getID(), desc_copy, + true); + } + } + + transaction.commit(); + } + + /// @brief Inserts new IPv6 pool to the database. + /// + /// @param server_selector Server selector. + /// @param pool Pointer to the pool to be inserted. + /// @param subnet Pointer to the subnet that this pool belongs to. + void createPool6(const ServerSelector& server_selector, const Pool6Ptr& pool, + const Subnet6Ptr& subnet) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(pool->getFirstAddress().toText()), + MySqlBinding::createString(pool->getLastAddress().toText()), + MySqlBinding::createInteger(static_cast(subnet->getID())), + MySqlBinding::createTimestamp(subnet->getModificationTime()) + }; + + // Run INSERT. + conn_.insertQuery(INSERT_POOL6, in_bindings); + + uint64_t pool_id = mysql_insert_id(conn_.mysql_); + auto option_spaces = pool->getCfgOption()->getOptionSpaceNames(); + for (auto option_space : option_spaces) { + OptionContainerPtr options = pool->getCfgOption()->getAll(option_space); + for (auto desc = options->begin(); desc != options->end(); ++desc) { + OptionDescriptorPtr desc_copy(new OptionDescriptor(*desc)); + desc_copy->space_name_ = option_space; + createUpdateOption6(server_selector, Lease::TYPE_NA, + pool_id, desc_copy, true); + } + } + } + + /// @brief Inserts new IPv6 pd pool to the database. + /// + /// @param server_selector Server selector. + /// @param pd_pool Pointer to the pd pool to be inserted. + /// @param subnet Pointer to the subnet that this pd pool belongs to. + void createPdPool6(const ServerSelector& server_selector, + const Pool6Ptr& pd_pool, + const Subnet6Ptr& subnet) { + int plen = prefixLengthFromRange(pd_pool->getFirstAddress(), + pd_pool->getLastAddress()); + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(pd_pool->getFirstAddress().toText()), + MySqlBinding::createInteger(static_cast(plen)), + MySqlBinding::createInteger(pd_pool->getLength()), + MySqlBinding::createInteger(static_cast(subnet->getID())), + MySqlBinding::createTimestamp(subnet->getModificationTime()) + }; + + // Run INSERT. + conn_.insertQuery(INSERT_PD_POOL, in_bindings); + + uint64_t pd_pool_id = mysql_insert_id(conn_.mysql_); + auto option_spaces = pd_pool->getCfgOption()->getOptionSpaceNames(); + for (auto option_space : option_spaces) { + OptionContainerPtr options = pd_pool->getCfgOption()->getAll(option_space); + for (auto desc = options->begin(); desc != options->end(); ++desc) { + OptionDescriptorPtr desc_copy(new OptionDescriptor(*desc)); + desc_copy->space_name_ = option_space; + createUpdateOption6(server_selector, Lease::TYPE_PD, + pd_pool_id, desc_copy, true); + } + } + } + + /// @brief Sends a query to delete data from a table. + /// + /// If creates a new audit revision for this change if such audit + /// revision doesn't exist yet (using ScopedAuditRevision mechanism). + /// + /// @tparam Args type of the arguments to be passed to one of the existing + /// @c deleteFromTable methods. + /// @param server_selector server selector. + /// @param operation operation which results in calling this function. This is + /// used for logging purposes. + /// @param log_message log message to be associated with the audit revision. + /// @param cascade_delete boolean flag indicating if we're performing + /// cascade delete. If set to true, the audit entries for the child + /// objects (e.g. DHCPoptions) won't be created. + /// @param keys arguments to be passed to one of the existing + /// @c deleteFromTable methods. + /// + /// @return Number of deleted entries. + template + uint64_t deleteTransactional(const int index, + const db::ServerSelector& server_selector, + const std::string& operation, + const std::string& log_message, + const bool cascade_delete, + Args&&... keys) { + + 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, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, log_message, cascade_delete); + + auto count = deleteFromTable(index, server_selector, operation, keys...); + + transaction.commit(); + + return (count); + } + + /// @brief Sends query to delete subnet by id. + /// + /// @param server_selector Server selector. + /// @param subnet_id Identifier of the subnet to be deleted. + /// @return Number of deleted subnets. + uint64_t deleteSubnet6(const ServerSelector& server_selector, + const SubnetID& subnet_id) { + return (deleteTransactional(DELETE_SUBNET6_ID, server_selector, + "deleting a subnet", + "subnet deleted", + true, + static_cast(subnet_id))); + } + + /// @brief Deletes pools belonging to a subnet from the database. + /// + /// @param subnet Pointer to the subnet for which pools should be + /// deleted. + uint64_t deletePools6(const Subnet6Ptr& subnet) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(subnet->getID()) + }; + + // Run DELETE. + return (conn_.updateDeleteQuery(DELETE_POOLS6_SUBNET_ID, in_bindings)); + } + + /// @brief Deletes pd pools belonging to a subnet from the database. + /// + /// @param subnet Pointer to the subnet for which pd pools should be + /// deleted. + uint64_t deletePdPools6(const Subnet6Ptr& subnet) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(subnet->getID()) + }; + + // Run DELETE. + return (conn_.updateDeleteQuery(DELETE_PD_POOLS_SUBNET_ID, in_bindings)); + } + + /// @brief Sends query to the database to retrieve multiple shared + /// networks. + /// + /// Query should order shared networks 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] shared_networks Reference to the container where fetched + /// shared networks will be inserted. + void getSharedNetworks6(const StatementIndex& index, + const MySqlBindingCollection& in_bindings, + SharedNetwork6Collection& shared_networks) { + // Create output bindings. The order must match that in the prepared + // statement. + MySqlBindingCollection out_bindings = { + MySqlBinding::createInteger(), // id + MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // name + MySqlBinding::createString(CLIENT_CLASS_BUF_LENGTH), // client_class + MySqlBinding::createString(INTERFACE_BUF_LENGTH), // interface + MySqlBinding::createTimestamp(), // modification_ts + MySqlBinding::createInteger(), // preferred_lifetime + MySqlBinding::createInteger(), // rapid_commit + MySqlBinding::createInteger(), // rebind_timer + MySqlBinding::createString(RELAY_BUF_LENGTH), // relay + MySqlBinding::createInteger(), // renew_timer + MySqlBinding::createString(REQUIRE_CLIENT_CLASSES_BUF_LENGTH), // require_client_classes + MySqlBinding::createInteger(), // reservation_mode + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // user_context + MySqlBinding::createInteger(), // valid_lifetime + MySqlBinding::createInteger(), // option: option_id + MySqlBinding::createInteger(), // option: code + MySqlBinding::createBlob(OPTION_VALUE_BUF_LENGTH), // option: value + MySqlBinding::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH), // option: formatted_value + MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // option: space + MySqlBinding::createInteger(), // option: persistent + MySqlBinding::createInteger(), // option: dhcp6_subnet_id + MySqlBinding::createInteger(), // option: scope_id + MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // option: user_context + MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // option: shared_network_name + MySqlBinding::createInteger(), // option: pool_id + MySqlBinding::createTimestamp(), // option: modification_ts + MySqlBinding::createInteger() // option: pd_pool_id + }; + + uint64_t last_network_id = 0; + uint64_t last_option_id = 0; + + conn_.selectQuery(index, in_bindings, out_bindings, + [this, &shared_networks, &last_network_id, &last_option_id] + (MySqlBindingCollection& out_bindings) { + SharedNetwork6Ptr last_network; + if (!shared_networks.empty()) { + last_network = *shared_networks.rbegin(); + } + + // If this is the first shared network or the shared network id in this + // row points to the next shared network we use the data in the + // row to create the new shared network instance. + if (last_network_id != out_bindings[0]->getInteger()) { + + last_network_id = out_bindings[0]->getInteger(); + last_network.reset(new SharedNetwork6(out_bindings[1]->getString())); + + // client_class + if (!out_bindings[2]->amNull()) { + last_network->allowClientClass(out_bindings[2]->getString()); + } + + // interface + last_network->setIface(out_bindings[3]->getStringOrDefault("")); + + // modification_ts + last_network->setModificationTime(out_bindings[4]->getTimestamp()); + + // preferred_lifetime + if (!out_bindings[5]->amNull()) { + last_network->setPreferred(createTriplet(out_bindings[5])); + } + + // rapid_commit + last_network->setRapidCommit(static_cast + (out_bindings[6]->getIntegerOrDefault(0))); + + // rebind_timer + if (!out_bindings[7]->amNull()) { + last_network->setT2(createTriplet(out_bindings[7])); + } + + // relay + ElementPtr relay_element = out_bindings[8]->getJSON(); + if (relay_element) { + if (relay_element->getType() != Element::list) { + isc_throw(BadValue, "invalid relay value " + << out_bindings[8]->getString()); + } + for (auto i = 0; i < relay_element->size(); ++i) { + auto relay_address_element = relay_element->get(i); + if (relay_address_element->getType() != Element::string) { + isc_throw(BadValue, "relay address must be a string"); + } + last_network->addRelayAddress(IOAddress(relay_element->get(i)->stringValue())); + } + } + + // renew_timer + if (!out_bindings[9]->amNull()) { + last_network->setT1(createTriplet(out_bindings[9])); + } + + // require_client_classes + ElementPtr require_element = out_bindings[10]->getJSON(); + if (require_element) { + if (require_element->getType() != Element::list) { + isc_throw(BadValue, "invalid require_client_classes value " + << out_bindings[10]->getString()); + } + for (auto i = 0; i < require_element->size(); ++i) { + auto require_item = require_element->get(i); + if (require_item->getType() != Element::string) { + isc_throw(BadValue, "elements of require_client_classes list must" + "be valid strings"); + } + last_network->requireClientClass(require_item->stringValue()); + } + } + + // reservation_mode + last_network->setHostReservationMode(static_cast + (out_bindings[11]->getIntegerOrDefault(Subnet6::HR_ALL))); + + // user_context + ElementPtr user_context = out_bindings[12]->getJSON(); + if (user_context) { + last_network->setContext(user_context); + } + + // valid_lifetime + if (!out_bindings[13]->amNull()) { + last_network->setValid(createTriplet(out_bindings[13])); + } + + shared_networks.push_back(last_network); + } + + // Parse option from 14. + if (!out_bindings[14]->amNull() && + (last_option_id < out_bindings[14]->getInteger())) { + last_option_id = out_bindings[14]->getInteger(); + + OptionDescriptorPtr desc = processOptionRow(Option::V6, out_bindings.begin() + 14); + if (desc) { + last_network->getCfgOption()->add(*desc, desc->space_name_); + } + } + }); + } + + /// @brief Sends query to retrieve single shared network by name. + /// + /// @param server_selector Server selector. + /// @param name Shared network name. + /// + /// @return Pointer to the returned shared network or NULL if such shared + /// network doesn't exist. + SharedNetwork6Ptr getSharedNetwork6(const ServerSelector& server_selector, + const std::string& 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, "fetching shared network"); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createString(name) + }; + + SharedNetwork6Collection shared_networks; + getSharedNetworks6(GET_SHARED_NETWORK6_NAME, in_bindings, shared_networks); + + return (shared_networks.empty() ? SharedNetwork6Ptr() : *shared_networks.begin()); + } + + /// @brief Sends query to retrieve all shared networks. + /// + /// @param server_selector Server selector. + /// @param [out] shared_networks Reference to the shared networks collection + /// structure where shared networks should be inserted. + void getAllSharedNetworks6(const ServerSelector& server_selector, + SharedNetwork6Collection& shared_networks) { + auto tags = getServerTags(server_selector); + + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag) + }; + + getSharedNetworks6(GET_ALL_SHARED_NETWORKS6, in_bindings, shared_networks); + } + } + + /// @brief Sends query to retrieve modified shared networks. + /// + /// @param server_selector Server selector. + /// @param modification_ts Lower bound modification timestamp. + /// @param [out] shared_networks Reference to the shared networks collection + /// structure where shared networks should be inserted. + void getModifiedSharedNetworks6(const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_ts, + SharedNetwork6Collection& shared_networks) { + auto tags = getServerTags(server_selector); + + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createTimestamp(modification_ts) + }; + + getSharedNetworks6(GET_MODIFIED_SHARED_NETWORKS6, in_bindings, + shared_networks); + } + } + + /// @brief Sends query to insert or update shared network. + /// + /// @param server_selector Server selector. + /// @param subnet Pointer to the shared network to be inserted or updated. + void createUpdateSharedNetwork6(const ServerSelector& server_selector, + const SharedNetwork6Ptr& shared_network) { + + 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 shared network"); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(shared_network->getName()), + MySqlBinding::condCreateString(shared_network->getClientClass()), + MySqlBinding::condCreateString(shared_network->getIface()), + MySqlBinding::createTimestamp(shared_network->getModificationTime()), + MySqlBinding::createInteger(shared_network->getPreferred()), + MySqlBinding::createInteger(static_cast(shared_network->getRapidCommit())), + createBinding(shared_network->getT2()), + createInputRelayBinding(shared_network), + createBinding(shared_network->getT1()), + createInputRequiredClassesBinding(shared_network), + MySqlBinding::createInteger(static_cast + (shared_network->getHostReservationMode())), + createInputContextBinding(shared_network), + createBinding(shared_network->getValid()) + }; + + 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, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, "shared network set", true); + + try { + + // Try to insert shared network. The shared network name must be unique, + // so if inserting fails with DuplicateEntry exception we'll need to + // update existing shared network entry. + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_SHARED_NETWORK6, + in_bindings); + + // Create bindings for inserting association into dhcp6_shared_network_server + // table. + MySqlBindingCollection in_server_bindings = { + MySqlBinding::createString(shared_network->getName()), // shared network name + MySqlBinding::createString(tag), // server tag + MySqlBinding::createTimestamp(shared_network->getModificationTime()), // modification_ts + }; + + // Insert association. + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_SHARED_NETWORK6_SERVER, + in_server_bindings); + + + } catch (const DuplicateEntry&) { + deleteOptions6(server_selector, shared_network); + + // Need to add one more binding for WHERE clause. + in_bindings.push_back(MySqlBinding::createString(shared_network->getName())); + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_SHARED_NETWORK6, + in_bindings); + } + + // (Re)create options. + auto option_spaces = shared_network->getCfgOption()->getOptionSpaceNames(); + for (auto option_space : option_spaces) { + OptionContainerPtr options = shared_network->getCfgOption()->getAll(option_space); + for (auto desc = options->begin(); desc != options->end(); ++desc) { + OptionDescriptorPtr desc_copy(new OptionDescriptor(*desc)); + desc_copy->space_name_ = option_space; + createUpdateOption6(server_selector, shared_network->getName(), + desc_copy, true); + } + } + + transaction.commit(); + } + + + /// @brief Sends query to insert DHCP option. + /// + /// This method expects that the server selector contains exactly one + /// server tag. + /// + /// @param server_selector Server selector. + /// @param in_bindings Collection of bindings representing an option. + void insertOption6(const ServerSelector& server_selector, + const MySqlBindingCollection& in_bindings) { + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_OPTION6, + in_bindings); + + // Fetch unique identifier of the inserted option. + uint64_t id = mysql_insert_id(conn_.mysql_); + + // Create bindings needed to insert association of that option with + // a server into the dhcp6_options_server table. + MySqlBindingCollection in_server_bindings = { + MySqlBinding::createInteger(id), // option_id + MySqlBinding::createString(*getServerTags(server_selector).begin()), // server_tag + in_bindings[11] // copy modification timestamp from option + }; + + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_OPTION6_SERVER, + in_server_bindings); + } + + /// @brief Sends query to insert or update global DHCP option. + /// + /// @param server_selector Server selector. + /// @param option Pointer to the option descriptor encapsulating the option. + void createUpdateOption6(const ServerSelector& server_selector, + const OptionDescriptorPtr& option) { + + 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 global option"); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(option->option_->getType()), + createOptionValueBinding(option), + MySqlBinding::condCreateString(option->formatted_value_), + MySqlBinding::condCreateString(option->space_name_), + MySqlBinding::createInteger(static_cast(option->persistent_)), + MySqlBinding::createNull(), + MySqlBinding::createNull(), + MySqlBinding::createInteger(0), + createInputContextBinding(option), + MySqlBinding::createNull(), + MySqlBinding::createNull(), + MySqlBinding::createTimestamp(option->getModificationTime()), + MySqlBinding::createNull() + }; + + MySqlTransaction transaction(conn_); + OptionDescriptorPtr existing_option = + getOption(GET_OPTION6_CODE_SPACE, Option::V6, server_selector, + option->option_->getType(), option->space_name_); + + // Create scoped audit revision. As long as this instance exists + // no new audit revisions are created in any subsequent calls. + ScopedAuditRevision + audit_revision(this, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, "global option set", false); + + if (existing_option) { + in_bindings.push_back(MySqlBinding::createString(tag)); + in_bindings.push_back(MySqlBinding::createInteger(option->option_->getType())); + in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_)); + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6, + in_bindings); + + } else { + insertOption6(server_selector, in_bindings); + + } + + transaction.commit(); + } + + /// @brief Sends query to insert or update DHCP option in a subnet. + /// + /// @param server_selector Server selector. + /// @param subnet_id Identifier of the subnet the option belongs to. + /// @param option Pointer to the option descriptor encapsulating the option. + /// @param cascade_update Boolean value indicating whether the update is + /// performed as part of the ownining element, e.g. subnet. + void createUpdateOption6(const ServerSelector& server_selector, + const SubnetID& subnet_id, + const OptionDescriptorPtr& option, + const bool cascade_update) { + + 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 subnet level option"); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(option->option_->getType()), + createOptionValueBinding(option), + MySqlBinding::condCreateString(option->formatted_value_), + MySqlBinding::condCreateString(option->space_name_), + MySqlBinding::createInteger(static_cast(option->persistent_)), + MySqlBinding::createNull(), + MySqlBinding::createInteger(static_cast(subnet_id)), + MySqlBinding::createInteger(1), + createInputContextBinding(option), + MySqlBinding::createNull(), + MySqlBinding::createNull(), + MySqlBinding::createTimestamp(option->getModificationTime()), + MySqlBinding::createNull() + }; + + + boost::scoped_ptr transaction; + // Only start new transaction if specified to do so. This function may + // be called from within an existing transaction in which case we + // don't start the new one. + if (!cascade_update) { + transaction.reset(new MySqlTransaction(conn_)); + } + + OptionDescriptorPtr existing_option = + getOption(GET_OPTION6_SUBNET_ID_CODE_SPACE, Option::V6, + server_selector, subnet_id, + option->option_->getType(), + option->space_name_); + + // Create scoped audit revision. As long as this instance exists + // no new audit revisions are created in any subsequent calls. + ScopedAuditRevision + audit_revision(this, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, "subnet specific option set", + cascade_update); + + if (existing_option) { + in_bindings.push_back(MySqlBinding::createString(tag)); + in_bindings.push_back(MySqlBinding::createInteger(static_cast(subnet_id))); + in_bindings.push_back(MySqlBinding::createInteger(option->option_->getType())); + in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_)); + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6_SUBNET_ID, + in_bindings); + + } else { + insertOption6(server_selector, in_bindings); + } + + if (transaction) { + transaction->commit(); + } + } + + /// @brief Sends query to insert or update DHCP option in a pool. + /// + /// @param server_selector Server selector. + /// @param pool_start_address Lower bound address of the pool. + /// @param pool_end_address Upper bound address of the pool. + /// @param option Pointer to the option descriptor encapsulating the option. + void createUpdateOption6(const ServerSelector& server_selector, + const IOAddress& pool_start_address, + const IOAddress& pool_end_address, + const OptionDescriptorPtr& option) { + uint64_t pool_id = 0; + Pool6Ptr pool = getPool6(server_selector, pool_start_address, pool_end_address, + pool_id); + if (!pool) { + isc_throw(BadValue, "no pool found for range of " + << pool_start_address << " : " + << pool_end_address); + } + + createUpdateOption6(server_selector, Lease::TYPE_NA, + pool_id, option, false); + } + + + /// @brief Sends query to insert or update DHCP option in a pd pool. + /// + /// @param server_selector Server selector. + /// @param pd_pool_prefix Address part of the pd pool prefix. + /// @param pd_pool_prefix_length Length of the pd pool prefix. + /// @param option Pointer to the option descriptor encapsulating the option. + void createUpdateOption6(const ServerSelector& server_selector, + const asiolink::IOAddress& pd_pool_prefix, + const uint8_t pd_pool_prefix_length, + const OptionDescriptorPtr& option) { + uint64_t pd_pool_id = 0; + Pool6Ptr pd_pool = getPdPool6(server_selector, + pd_pool_prefix, + pd_pool_prefix_length, + pd_pool_id); + if (!pd_pool) { + isc_throw(BadValue, "no prefix delegation pool found for prefix " + << "of " << pd_pool_prefix << "/" + << static_cast(pd_pool_prefix_length)); + } + + createUpdateOption6(server_selector, Lease::TYPE_PD, + pd_pool_id, option, false); + } + + + /// @brief Sends query to insert or update DHCP option in an address + /// or prefix delegation pool. + /// + /// @param selector Server selector. + /// @param pool_type Pool type (Lease::TYPE_NA or Lease::TYPE_PD). + /// @param pool_id Identifier of the address or prefix delegation pool + /// the option belongs to. + /// @param option Pointer to the option descriptor encapsulating the option. + /// @param cascade_update Boolean value indicating whether the update is + /// performed as part of the ownining element, e.g. subnet. + void createUpdateOption6(const ServerSelector& server_selector, + const Lease::Type& pool_type, + const uint64_t pool_id, + const OptionDescriptorPtr& option, + const bool cascade_update) { + + if (server_selector.amUnassigned()) { + isc_throw(NotImplemented, "managing configuration for no particular server" + " (unassigned) is unsupported at the moment"); + } + + std::string msg = "creating or updating "; + if (pool_type == Lease::TYPE_PD) { + msg += "prefix delegation"; + } else { + msg += "address"; + } + msg += " pool level option"; + auto tag = getServerTag(server_selector, msg); + + MySqlBindingCollection in_bindings; + // code + in_bindings.push_back(MySqlBinding::createInteger(option->option_->getType())); + // value + in_bindings.push_back(createOptionValueBinding(option)); + // formatted_value + in_bindings.push_back(MySqlBinding::condCreateString(option->formatted_value_)); + // space + in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_)); + // persistent + in_bindings.push_back(MySqlBinding::createInteger(static_cast(option->persistent_))); + // dhcp_client_class + in_bindings.push_back(MySqlBinding::createNull()); + // dhcp[46]_subnet_id + in_bindings.push_back(MySqlBinding::createNull()); + // scope_id + if (pool_type == Lease::TYPE_NA) { + in_bindings.push_back(MySqlBinding::createInteger(5)); + } else { + in_bindings.push_back(MySqlBinding::createInteger(6)); + } + // user_context + in_bindings.push_back(createInputContextBinding(option)); + // shared_network_name + in_bindings.push_back(MySqlBinding::createNull()); + // pool_id + if (pool_type == Lease::TYPE_NA) { + in_bindings.push_back(MySqlBinding::createInteger(pool_id)); + } else { + in_bindings.push_back(MySqlBinding::createNull()); + } + // modification_ts + in_bindings.push_back(MySqlBinding::createTimestamp(option->getModificationTime())); + // pd_pool_id + if (pool_type == Lease::TYPE_PD) { + in_bindings.push_back(MySqlBinding::createInteger(pool_id)); + } else { + in_bindings.push_back(MySqlBinding::createNull()); + } + + MySqlTransaction transaction(conn_); + + const int index = (pool_type == Lease::TYPE_NA ? + GET_OPTION6_POOL_ID_CODE_SPACE : + GET_OPTION6_PD_POOL_ID_CODE_SPACE); + OptionDescriptorPtr existing_option = + getOption(index, server_selector, pool_type, pool_id, + option->option_->getType(), option->space_name_); + + // Create scoped audit revision. As long as this instance exists + // no new audit revisions are created in any subsequent calls. + if (pool_type == Lease::TYPE_PD) { + msg = "prefix delegation"; + } else { + msg = "address"; + } + msg += " pool specific option set"; + ScopedAuditRevision + audit_revision(this, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, msg, cascade_update); + + if (existing_option) { + in_bindings.push_back(MySqlBinding::createString(tag)); + in_bindings.push_back(MySqlBinding::createInteger(pool_id)); + in_bindings.push_back(MySqlBinding::createInteger(option->option_->getType())); + in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_)); + const int index = (pool_type == Lease::TYPE_NA ? + MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6_POOL_ID : + MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6_PD_POOL_ID); + conn_.updateDeleteQuery(index, in_bindings); + + } else { + insertOption6(server_selector, in_bindings); + } + + transaction.commit(); + } + + /// @brief Sends query to insert or update DHCP option in a shared network. + /// + /// @param selector Server selector. + /// @param shared_network_name Name of the shared network the option + /// belongs to. + /// @param option Pointer to the option descriptor encapsulating the option. + /// @param cascade_update Boolean value indicating whether the update is + /// performed as part of the ownining element, e.g. shared network. + void createUpdateOption6(const ServerSelector& server_selector, + const std::string& shared_network_name, + const OptionDescriptorPtr& option, + const bool cascade_update) { + + 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 shared" + " network level option"); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(option->option_->getType()), + createOptionValueBinding(option), + MySqlBinding::condCreateString(option->formatted_value_), + MySqlBinding::condCreateString(option->space_name_), + MySqlBinding::createInteger(static_cast(option->persistent_)), + MySqlBinding::createNull(), + MySqlBinding::createNull(), + MySqlBinding::createInteger(4), + createInputContextBinding(option), + MySqlBinding::createString(shared_network_name), + MySqlBinding::createNull(), + MySqlBinding::createTimestamp(option->getModificationTime()), + MySqlBinding::createNull() + }; + + boost::scoped_ptr transaction; + // Only start new transaction if specified to do so. This function may + // be called from within an existing transaction in which case we + // don't start the new one. + if (!cascade_update) { + transaction.reset(new MySqlTransaction(conn_)); + } + + OptionDescriptorPtr existing_option = + getOption(GET_OPTION6_SHARED_NETWORK_CODE_SPACE, Option::V6, + server_selector, shared_network_name, + option->option_->getType(), option->space_name_); + + // Create scoped audit revision. As long as this instance exists + // no new audit revisions are created in any subsequent calls. + ScopedAuditRevision + audit_revision(this, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, "shared network specific option set", + cascade_update); + + if (existing_option) { + in_bindings.push_back(MySqlBinding::createString(tag)); + in_bindings.push_back(MySqlBinding::createString(shared_network_name)); + in_bindings.push_back(MySqlBinding::createInteger(option->option_->getType())); + in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_)); + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: + UPDATE_OPTION6_SHARED_NETWORK, + in_bindings); + } else { + insertOption6(server_selector, in_bindings); + } + + if (transaction) { + transaction->commit(); + } + } + + /// @brief Sends query to insert or update option definition. + /// + /// @param server_selector Server selector. + /// @param option_def Pointer to the option definition to be inserted or updated. + void createUpdateOptionDef6(const ServerSelector& server_selector, + const OptionDefinitionPtr& option_def) { + + 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))); + } + MySqlBindingPtr record_types_binding = record_types->empty() ? + MySqlBinding::createNull() : MySqlBinding::createString(record_types->str()); + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(option_def->getCode()), + MySqlBinding::createString(option_def->getName()), + MySqlBinding::createString(option_def->getOptionSpaceName().empty() ? + "dhcp6" : option_def->getOptionSpaceName()), + MySqlBinding::createInteger(static_cast(option_def->getType())), + MySqlBinding::createTimestamp(option_def->getModificationTime()), + MySqlBinding::createInteger(static_cast(option_def->getArrayType())), + MySqlBinding::createString(option_def->getEncapsulatedSpace()), + record_types_binding, + createInputContextBinding(option_def) + }; + + MySqlTransaction transaction(conn_); + + // Need to check if this definition already exists. We can't follow + // the same pattern as for shared networks and subnets, to try to insert + // the definition first and fall back to update if the DuplicateEntry + // exception is thrown, because the option code/space is not unique + // within the dhcp6_option_def table. Inserting another option definition + // with existing option code/name would not violate the key and the + // option definition instance would be inserted successfully. Therefore, + // we first fetch the option definition for the given server, code and + // space name. If it exists, we simply update it. + OptionDefinitionPtr existing_definition = + getOptionDef(GET_OPTION_DEF6_CODE_SPACE, + server_selector, + option_def->getCode(), + option_def->getOptionSpaceName()); + + // Create scoped audit revision. As long as this instance exists + // no new audit revisions are created in any subsequent calls. + ScopedAuditRevision + audit_revision(this, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, "option definition set", + true); + + if (existing_definition) { + // Need to add three more bindings for WHERE clause. + in_bindings.push_back(MySqlBinding::createString(tag)); + in_bindings.push_back(MySqlBinding::createInteger(existing_definition->getCode())); + in_bindings.push_back(MySqlBinding::createString(existing_definition->getOptionSpaceName())); + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION_DEF6, + in_bindings); + + } else { + // If the option definition doesn't exist, let's insert it. + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6, + 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_); + + MySqlBindingCollection in_server_bindings = { + MySqlBinding::createInteger(id), // option_def_id + MySqlBinding::createString(tag), // tag used to obtain server_id + MySqlBinding::createTimestamp(option_def->getModificationTime()), // modification_ts + }; + + // Insert association. + conn_.insertQuery(MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6_SERVER, + in_server_bindings); + } + + transaction.commit(); + } + + /// @brief Sends query to delete option definition by code and + /// option space name. + /// + /// @param server_selector Server selector. + /// @param code Option code. + /// @param name Option name. + /// @return Number of deleted option definitions. + uint64_t deleteOptionDef6(const ServerSelector& server_selector, + const uint16_t code, + const std::string& space) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(code), + MySqlBinding::createString(space) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTION_DEF6_CODE_NAME, server_selector, + "deleting option definition", + "option definition deleted", + false, + in_bindings)); + } + + /// @brief Deletes global option. + /// + /// @param server_selector Server selector. + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @return Number of deleted options. + uint64_t deleteOption6(const ServerSelector& server_selector, + const uint16_t code, + const std::string& space) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(code), + MySqlBinding::createString(space) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTION6, server_selector, + "deleting global option", + "global option deleted", + false, + in_bindings)); + } + + /// @brief Deletes subnet level option. + /// + /// @param server_selector Server selector. + /// @param subnet_id Identifier of the subnet to which deleted option + /// belongs. + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @return Number of deleted options. + uint64_t deleteOption6(const ServerSelector& server_selector, + const SubnetID& subnet_id, + const uint16_t code, + const std::string& space) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(static_cast(subnet_id)), + MySqlBinding::createInteger(code), + MySqlBinding::createString(space) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTION6_SUBNET_ID, server_selector, + "deleting option for a subnet", + "subnet specific option deleted", + false, + in_bindings)); + } + + /// @brief Deletes pool level option. + /// + /// @param server_selector Server selector. + /// @param pool_start_address Lower bound pool address. + /// @param pool_end_address Upper bound pool address. + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @return Number of deleted options. + uint64_t deleteOption6(const db::ServerSelector& server_selector, + const IOAddress& pool_start_address, + const IOAddress& pool_end_address, + const uint16_t code, + const std::string& space) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(code), + MySqlBinding::createString(space), + MySqlBinding::createString(pool_start_address.toText()), + MySqlBinding::createString(pool_end_address.toText()) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTION6_POOL_RANGE, server_selector, + "deleting option for an address pool", + "address pool specific option deleted", + false, + in_bindings)); + } + + /// @brief Deletes pd pool level option. + /// + /// @param server_selector Server selector. + /// @param pd_pool_prefix Address part of the pd pool prefix. + /// @param pd_pool_prefix_length Length of the pd pool prefix. + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @return Number of deleted options. + uint64_t deleteOption6(const db::ServerSelector& server_selector, + const asiolink::IOAddress& pd_pool_prefix, + const uint8_t pd_pool_prefix_length, + const uint16_t code, + const std::string& space) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(code), + MySqlBinding::createString(space), + MySqlBinding::createString(pd_pool_prefix.toText()), + MySqlBinding::createInteger(pd_pool_prefix_length) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTION6_PD_POOL, server_selector, + "deleting option for a prefix delegation pool", + "prefix delegation pool specific option deleted", + false, + in_bindings)); + } + + /// @brief Deletes shared network level option. + /// + /// @param server_selector Server selector. + /// @param shared_network_name Name of the shared network which deleted + /// option belongs to + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @return Number of deleted options. + uint64_t deleteOption6(const db::ServerSelector& server_selector, + const std::string& shared_network_name, + const uint16_t code, + const std::string& space) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(shared_network_name), + MySqlBinding::createInteger(code), + MySqlBinding::createString(space) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTION6_SHARED_NETWORK, server_selector, + "deleting option for a shared network", + "shared network specific option deleted", + false, + in_bindings)); + } + + /// @brief Deletes options belonging to a subnet from the database. + /// + /// @param server_selector Server selector. + /// @param subnet Pointer to the subnet for which options should be + /// deleted. + /// @return Number of deleted options. + uint64_t deleteOptions6(const ServerSelector& server_selector, + const Subnet6Ptr& subnet) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createInteger(subnet->getID()) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTIONS6_SUBNET_ID, server_selector, + "deleting options for a subnet", + "subnet specific options deleted", + true, + in_bindings)); + } + + /// @brief Deletes options belonging to a shared network from the database. + /// + /// @param server_selector Server selector. + /// @param subnet Pointer to the subnet for which options should be + /// deleted. + /// @return Number of deleted options. + uint64_t deleteOptions6(const ServerSelector& server_selector, + const SharedNetwork6Ptr& shared_network) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(shared_network->getName()) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTIONS6_SHARED_NETWORK, server_selector, + "deleting options for a shared network", + "shared network specific options deleted", + true, + in_bindings)); + } +}; + +namespace { + +/// @brief Array of tagged statements. +typedef std::array +TaggedStatementArray; + +/// @brief Prepared MySQL statements used by the backend to insert and +/// retrieve data from the database. +TaggedStatementArray tagged_statements = { { + { MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + "CALL createAuditRevisionDHCP6(?, ?, ?, ?)" + }, + + // Select global parameter by name. + { MySqlConfigBackendDHCPv6Impl::GET_GLOBAL_PARAMETER6, + MYSQL_GET_GLOBAL_PARAMETER(dhcp6, AND g.name = ?) + }, + + // Select all global parameters. + { MySqlConfigBackendDHCPv6Impl::GET_ALL_GLOBAL_PARAMETERS6, + MYSQL_GET_GLOBAL_PARAMETER(dhcp6) + }, + + // Select modified global parameters. + { MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_GLOBAL_PARAMETERS6, + MYSQL_GET_GLOBAL_PARAMETER(dhcp6, AND g.modification_ts > ?) + }, + + // Select subnet by id. + { MySqlConfigBackendDHCPv6Impl::GET_SUBNET6_ID, + MYSQL_GET_SUBNET6(AND s.subnet_id = ?) + }, + + // Select subnet by prefix. + { MySqlConfigBackendDHCPv6Impl::GET_SUBNET6_PREFIX, + MYSQL_GET_SUBNET6(AND s.subnet_prefix = ?) + }, + + // Select all subnets. + { MySqlConfigBackendDHCPv6Impl::GET_ALL_SUBNETS6, + MYSQL_GET_SUBNET6() + }, + + // Select subnets having modification time later than X. + { MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_SUBNETS6, + MYSQL_GET_SUBNET6(AND s.modification_ts > ?) + }, + + // Select subnets belonging to a shared network. + { MySqlConfigBackendDHCPv6Impl::GET_SHARED_NETWORK_SUBNETS6, + MYSQL_GET_SUBNET6(AND s.shared_network_name = ?) + }, + + // Select pool by address range. + { MySqlConfigBackendDHCPv6Impl::GET_POOL6_RANGE, + "SELECT" + " p.id," + " p.start_address," + " p.end_address," + " p.subnet_id," + " p.modification_ts," + " x.option_id," + " x.code," + " x.value," + " x.formatted_value," + " x.space," + " x.persistent," + " x.dhcp6_subnet_id," + " x.scope_id," + " x.user_context," + " x.shared_network_name," + " x.pool_id," + " x.modification_ts," + " x.pd_pool_id " + "FROM dhcp6_pool AS p " + "LEFT JOIN dhcp6_options AS x ON x.scope_id = 5 AND p.id = x.pool_id " + "WHERE p.start_address = ? AND p.end_address = ? " + "ORDER BY p.id, x.option_id" + }, + + // Select prefix delegation pool. + { MySqlConfigBackendDHCPv6Impl::GET_PD_POOL, + "SELECT" + " p.id," + " p.prefix," + " p.prefix_length," + " p.delegated_prefix_length," + " p.subnet_id," + " p.modification_ts," + " x.option_id," + " x.code," + " x.value," + " x.formatted_value," + " x.space," + " x.persistent," + " x.dhcp6_subnet_id," + " x.scope_id," + " x.user_context," + " x.shared_network_name," + " x.pool_id," + " x.modification_ts," + " x.pd_pool_id " + "FROM dhcp6_pd_pool AS p " + "LEFT JOIN dhcp6_options AS x ON x.scope_id = 6 AND p.id = x.pd_pool_id " + "WHERE p.prefix = ? AND p.prefix_length = ? " + "ORDER BY p.id, x.option_id" + }, + + // Select shared network by name. + { MySqlConfigBackendDHCPv6Impl::GET_SHARED_NETWORK6_NAME, + MYSQL_GET_SHARED_NETWORK6(AND n.name = ?) + }, + + // Select all shared networks. + { MySqlConfigBackendDHCPv6Impl::GET_ALL_SHARED_NETWORKS6, + MYSQL_GET_SHARED_NETWORK6() + }, + + // Select modified shared networks. + { MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_SHARED_NETWORKS6, + MYSQL_GET_SHARED_NETWORK6(AND n.modification_ts > ?) + }, + + // Retrieves option definition by code and space. + { MySqlConfigBackendDHCPv6Impl::GET_OPTION_DEF6_CODE_SPACE, + MYSQL_GET_OPTION_DEF(dhcp6, AND d.code = ? AND d.space = ?) + }, + + // Retrieves all option definitions. + { MySqlConfigBackendDHCPv6Impl::GET_ALL_OPTION_DEFS6, + MYSQL_GET_OPTION_DEF(dhcp6) + }, + + // Retrieves modified option definitions. + { MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_OPTION_DEFS6, + MYSQL_GET_OPTION_DEF(dhcp6, AND d.modification_ts > ?) + }, + + // Retrieves global option by code and space. + { MySqlConfigBackendDHCPv6Impl::GET_OPTION6_CODE_SPACE, + MYSQL_GET_OPTION6(AND o.scope_id = 0 AND o.code = ? AND o.space = ?) + }, + + // Retrieves all global options. + { MySqlConfigBackendDHCPv6Impl::GET_ALL_OPTIONS6, + MYSQL_GET_OPTION6(AND o.scope_id = 0) + }, + + // Retrieves modified options. + { MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_OPTIONS6, + MYSQL_GET_OPTION6(AND o.scope_id = 0 AND o.modification_ts > ?) + }, + + // Retrieves an option for a given subnet, option code and space. + { MySqlConfigBackendDHCPv6Impl::GET_OPTION6_SUBNET_ID_CODE_SPACE, + MYSQL_GET_OPTION6(AND o.scope_id = 1 AND o.dhcp6_subnet_id = ? AND o.code = ? AND o.space = ?) + }, + + // Retrieves an option for a given pool, option code and space. + { MySqlConfigBackendDHCPv6Impl::GET_OPTION6_POOL_ID_CODE_SPACE, + MYSQL_GET_OPTION6(AND o.scope_id = 5 AND o.pool_id = ? AND o.code = ? AND o.space = ?) + }, + + // Retrieves an option for a given pd pool, option code and space. + { MySqlConfigBackendDHCPv6Impl::GET_OPTION6_PD_POOL_ID_CODE_SPACE, + MYSQL_GET_OPTION6(AND o.scope_id = 6 AND o.pd_pool_id = ? AND o.code = ? AND o.space = ?) + }, + + // Retrieves an option for a given shared network, option code and space. + { MySqlConfigBackendDHCPv6Impl::GET_OPTION6_SHARED_NETWORK_CODE_SPACE, + MYSQL_GET_OPTION6(AND o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?) + }, + + // Retrieves the most recent audit entries. + { MySqlConfigBackendDHCPv6Impl::GET_AUDIT_ENTRIES6_TIME, + MYSQL_GET_AUDIT_ENTRIES_TIME(dhcp6) + }, + + // Insert global parameter. + { MySqlConfigBackendDHCPv6Impl::INSERT_GLOBAL_PARAMETER6, + MYSQL_INSERT_GLOBAL_PARAMETER(dhcp6) + }, + + // Insert association of the global parameter with a server. + { MySqlConfigBackendDHCPv6Impl::INSERT_GLOBAL_PARAMETER6_SERVER, + MYSQL_INSERT_GLOBAL_PARAMETER_SERVER(dhcp6) + }, + + // Insert a subnet. + { MySqlConfigBackendDHCPv6Impl::INSERT_SUBNET6, + "INSERT INTO dhcp6_subnet(" + " subnet_id," + " subnet_prefix," + " client_class," + " interface," + " modification_ts," + " preferred_lifetime," + " rapid_commit," + " rebind_timer," + " relay," + " renew_timer," + " require_client_classes," + " reservation_mode," + " shared_network_name," + " user_context," + " valid_lifetime" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" }, + + // Insert association of the subnet with a server. + { MySqlConfigBackendDHCPv6Impl::INSERT_SUBNET6_SERVER, + MYSQL_INSERT_SUBNET_SERVER(dhcp6) + }, + + // Insert pool for a subnet. + { MySqlConfigBackendDHCPv6Impl::INSERT_POOL6, + MYSQL_INSERT_POOL(dhcp6) + }, + + // Insert pd pool for a subnet. + { MySqlConfigBackendDHCPv6Impl::INSERT_PD_POOL, + MYSQL_INSERT_PD_POOL() + }, + + // Insert a shared network. + { MySqlConfigBackendDHCPv6Impl::INSERT_SHARED_NETWORK6, + "INSERT INTO dhcp6_shared_network(" + " name," + " client_class," + " interface," + " modification_ts," + " preferred_lifetime," + " rapid_commit," + " rebind_timer," + " relay," + " renew_timer," + " require_client_classes," + " reservation_mode," + " user_context," + " valid_lifetime" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" }, + + // Insert association of the shared network with a server. + { MySqlConfigBackendDHCPv6Impl::INSERT_SHARED_NETWORK6_SERVER, + MYSQL_INSERT_SHARED_NETWORK_SERVER(dhcp6) + }, + + // Insert option definition. + { MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6, + MYSQL_INSERT_OPTION_DEF(dhcp6) + }, + + // Insert association of the option definition with a server. + { MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6_SERVER, + MYSQL_INSERT_OPTION_DEF_SERVER(dhcp6) + }, + + // Insert subnet specific option. + { MySqlConfigBackendDHCPv6Impl::INSERT_OPTION6, + MYSQL_INSERT_OPTION6() + }, + + // Insert association of the DHCP option with a server. + { MySqlConfigBackendDHCPv6Impl::INSERT_OPTION6_SERVER, + MYSQL_INSERT_OPTION_SERVER(dhcp6) + }, + + // Update existing global parameter. + { MySqlConfigBackendDHCPv6Impl::UPDATE_GLOBAL_PARAMETER6, + MYSQL_UPDATE_GLOBAL_PARAMETER(dhcp6) + }, + + // Update existing subnet. + { MySqlConfigBackendDHCPv6Impl::UPDATE_SUBNET6, + "UPDATE dhcp6_subnet SET" + " subnet_id = ?," + " subnet_prefix = ?," + " client_class = ?," + " interface = ?," + " modification_ts = ?," + " preferred_lifetime = ?," + " rapid_commit = ?," + " rebind_timer = ?," + " relay = ?," + " renew_timer = ?," + " require_client_classes = ?," + " reservation_mode = ?," + " shared_network_name = ?," + " user_context = ?," + " valid_lifetime = ? " + "WHERE subnet_id = ?" }, + + // Update existing shared network. + { MySqlConfigBackendDHCPv6Impl::UPDATE_SHARED_NETWORK6, + "UPDATE dhcp6_shared_network SET" + " name = ?," + " client_class = ?," + " interface = ?," + " modification_ts = ?," + " preferred_lifetime = ?," + " rapid_commit = ?," + " rebind_timer = ?," + " relay = ?," + " renew_timer = ?," + " require_client_classes = ?," + " reservation_mode = ?," + " user_context = ?," + " valid_lifetime = ? " + "WHERE name = ?" }, + + // Update existing option definition. + { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION_DEF6, + MYSQL_UPDATE_OPTION_DEF(dhcp6) + }, + + // Update existing global option. + { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6, + MYSQL_UPDATE_OPTION6(AND o.scope_id = 0 AND o.code = ? AND o.space = ?) + }, + + // Update existing subnet level option. + { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6_SUBNET_ID, + MYSQL_UPDATE_OPTION6(AND o.scope_id = 1 AND o.dhcp6_subnet_id = ? AND o.code = ? AND o.space = ?) + }, + + // Update existing pool level option. + { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6_POOL_ID, + MYSQL_UPDATE_OPTION6(AND o.scope_id = 5 AND o.pool_id = ? AND o.code = ? AND o.space = ?) + }, + + // Update existing pd pool level option. + { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6_PD_POOL_ID, + MYSQL_UPDATE_OPTION6(AND o.scope_id = 6 AND o.pd_pool_id = ? AND o.code = ? AND o.space = ?) + }, + + // Update existing shared network level option. + { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6_SHARED_NETWORK, + MYSQL_UPDATE_OPTION6(AND o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?) + }, + + // Delete global parameter by name. + { MySqlConfigBackendDHCPv6Impl::DELETE_GLOBAL_PARAMETER6, + MYSQL_DELETE_GLOBAL_PARAMETER(dhcp6, AND g.name = ?) + }, + + // Delete all global parameters. + { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_GLOBAL_PARAMETERS6, + MYSQL_DELETE_GLOBAL_PARAMETER(dhcp6) + }, + + // Delete subnet by id. + { MySqlConfigBackendDHCPv6Impl::DELETE_SUBNET6_ID, + MYSQL_DELETE_SUBNET(dhcp6, AND s.subnet_id = ?) + }, + + // Delete subnet by prefix. + { MySqlConfigBackendDHCPv6Impl::DELETE_SUBNET6_PREFIX, + MYSQL_DELETE_SUBNET(dhcp6, AND s.subnet_prefix = ?) + }, + + // Delete all subnets. + { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_SUBNETS6, + MYSQL_DELETE_SUBNET(dhcp6) + }, + + // Delete pools for a subnet. + { MySqlConfigBackendDHCPv6Impl::DELETE_POOLS6_SUBNET_ID, + MYSQL_DELETE_POOLS(dhcp6) + }, + + // Delete pd pools for a subnet. + { MySqlConfigBackendDHCPv6Impl::DELETE_PD_POOLS_SUBNET_ID, + MYSQL_DELETE_PD_POOLS() + }, + + // Delete shared network by name. + { MySqlConfigBackendDHCPv6Impl::DELETE_SHARED_NETWORK6_NAME, + MYSQL_DELETE_SHARED_NETWORK(dhcp6, AND n.name = ?) + }, + + // Delete all shared networks. + { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_SHARED_NETWORKS6, + MYSQL_DELETE_SHARED_NETWORK(dhcp6) + }, + + // Delete option definition. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION_DEF6_CODE_NAME, + MYSQL_DELETE_OPTION_DEF(dhcp6, AND code = ? AND space = ?) + }, + + // Delete all option definitions. + { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_OPTION_DEFS6, + MYSQL_DELETE_OPTION_DEF(dhcp6) + }, + + // Delete single global option. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6, + MYSQL_DELETE_OPTION(dhcp6, AND o.scope_id = 0 AND o.code = ? AND o.space = ?) + }, + + // Delete single option from a subnet. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6_SUBNET_ID, + MYSQL_DELETE_OPTION(dhcp6, + AND o.scope_id = 1 AND o.dhcp6_subnet_id = ? AND o.code = ? AND o.space = ?) + }, + + // Delete single option from a pool. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6_POOL_RANGE, + MYSQL_DELETE_OPTION_POOL_RANGE(dhcp6, AND o.scope_id = 5 AND o.code = ? AND o.space = ?) + }, + + // Delete single option from a pd pool. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6_PD_POOL, + MYSQL_DELETE_OPTION_PD_POOL(AND o.scope_id = 6 AND o.code = ? AND o.space = ?) + }, + + // Delete single option from a shared network. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6_SHARED_NETWORK, + MYSQL_DELETE_OPTION(dhcp6, + AND o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?) + }, + + // Delete options belonging to a subnet. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTIONS6_SUBNET_ID, + MYSQL_DELETE_OPTION(dhcp6, AND o.scope_id = 1 AND o.dhcp6_subnet_id = ?) + }, + + // Delete options belonging to a shared_network. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTIONS6_SHARED_NETWORK, + MYSQL_DELETE_OPTION(dhcp6, AND o.scope_id = 4 AND o.shared_network_name = ?) + } +} +}; + +}; // end anonymous namespace + +MySqlConfigBackendDHCPv6Impl:: +MySqlConfigBackendDHCPv6Impl(const DatabaseConnection::ParameterMap& parameters) + : MySqlConfigBackendImpl(parameters) { + // Prepare query statements. Those are will be only used to retrieve + // information from the database, so they can be used even if the + // database is read only for the current user. + conn_.prepareStatements(tagged_statements.begin(), + tagged_statements.end()); +// tagged_statements.begin() + WRITE_STMTS_BEGIN); +} + +MySqlConfigBackendDHCPv6:: +MySqlConfigBackendDHCPv6(const DatabaseConnection::ParameterMap& parameters) + : impl_(new MySqlConfigBackendDHCPv6Impl(parameters)) { +} + +Subnet6Ptr +MySqlConfigBackendDHCPv6::getSubnet6(const ServerSelector& server_selector, + const std::string& subnet_prefix) const { + return (impl_->getSubnet6(server_selector, subnet_prefix)); +} + +Subnet6Ptr +MySqlConfigBackendDHCPv6::getSubnet6(const ServerSelector& server_selector, + const SubnetID& subnet_id) const { + return (impl_->getSubnet6(server_selector, subnet_id)); +} + +Subnet6Collection +MySqlConfigBackendDHCPv6::getAllSubnets6(const ServerSelector& server_selector) const { + Subnet6Collection subnets; + impl_->getAllSubnets6(server_selector, subnets); + return (subnets); +} + +Subnet6Collection +MySqlConfigBackendDHCPv6::getModifiedSubnets6(const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const { + Subnet6Collection subnets; + impl_->getModifiedSubnets6(server_selector, modification_time, subnets); + return (subnets); +} + +Subnet6Collection +MySqlConfigBackendDHCPv6::getSharedNetworkSubnets6(const ServerSelector& server_selector, + const std::string& shared_network_name) const { + Subnet6Collection subnets; + impl_->getSharedNetworkSubnets6(server_selector, shared_network_name, subnets); + return (subnets); +} + +SharedNetwork6Ptr +MySqlConfigBackendDHCPv6::getSharedNetwork6(const ServerSelector& server_selector, + const std::string& name) const { + return (impl_->getSharedNetwork6(server_selector, name)); +} + +SharedNetwork6Collection +MySqlConfigBackendDHCPv6::getAllSharedNetworks6(const ServerSelector& server_selector) const { + SharedNetwork6Collection shared_networks; + impl_->getAllSharedNetworks6(server_selector, shared_networks); + return (shared_networks); +} + +SharedNetwork6Collection +MySqlConfigBackendDHCPv6:: +getModifiedSharedNetworks6(const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const { + SharedNetwork6Collection shared_networks; + impl_->getModifiedSharedNetworks6(server_selector, modification_time, shared_networks); + return (shared_networks); +} + +OptionDefinitionPtr +MySqlConfigBackendDHCPv6::getOptionDef6(const ServerSelector& server_selector, + const uint16_t code, + const std::string& space) const { + return (impl_->getOptionDef(MySqlConfigBackendDHCPv6Impl::GET_OPTION_DEF6_CODE_SPACE, + server_selector, code, space)); +} + +OptionDefContainer +MySqlConfigBackendDHCPv6::getAllOptionDefs6(const ServerSelector& server_selector) const { + OptionDefContainer option_defs; + impl_->getAllOptionDefs(MySqlConfigBackendDHCPv6Impl::GET_ALL_OPTION_DEFS6, + server_selector, option_defs); + return (option_defs); +} + +OptionDefContainer +MySqlConfigBackendDHCPv6:: +getModifiedOptionDefs6(const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const { + OptionDefContainer option_defs; + impl_->getModifiedOptionDefs(MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_OPTION_DEFS6, + server_selector, modification_time, option_defs); + return (option_defs); +} + +OptionDescriptorPtr +MySqlConfigBackendDHCPv6::getOption6(const ServerSelector& server_selector, + const uint16_t code, + const std::string& space) const { + return (impl_->getOption(MySqlConfigBackendDHCPv6Impl::GET_OPTION6_CODE_SPACE, + Option::V6, server_selector, code, space)); +} + +OptionContainer +MySqlConfigBackendDHCPv6::getAllOptions6(const ServerSelector& server_selector) const { + return (impl_->getAllOptions(MySqlConfigBackendDHCPv6Impl::GET_ALL_OPTIONS6, + Option::V6, server_selector)); +} + +OptionContainer +MySqlConfigBackendDHCPv6:: +getModifiedOptions6(const ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const { + return (impl_->getModifiedOptions(MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_OPTIONS6, + Option::V6, server_selector, modification_time)); +} + +StampedValuePtr +MySqlConfigBackendDHCPv6::getGlobalParameter6(const ServerSelector& server_selector, + const std::string& name) const { + return (impl_->getGlobalParameter6(server_selector, name)); +} + +StampedValueCollection +MySqlConfigBackendDHCPv6::getAllGlobalParameters6(const ServerSelector& server_selector) const { + StampedValueCollection parameters; + + auto tags = impl_->getServerTags(server_selector); + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { MySqlBinding::createString(tag) }; + impl_->getGlobalParameters(MySqlConfigBackendDHCPv6Impl::GET_ALL_GLOBAL_PARAMETERS6, + in_bindings, parameters); + } + return (parameters); +} + +StampedValueCollection +MySqlConfigBackendDHCPv6:: +getModifiedGlobalParameters6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const { + StampedValueCollection parameters; + + auto tags = impl_->getServerTags(server_selector); + for (auto tag : tags) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(tag), + MySqlBinding::createTimestamp(modification_time) + }; + impl_->getGlobalParameters(MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_GLOBAL_PARAMETERS6, + in_bindings, parameters); + } + + return (parameters); +} + +AuditEntryCollection +MySqlConfigBackendDHCPv6:: +getRecentAuditEntries6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const { + AuditEntryCollection audit_entries; + impl_->getRecentAuditEntries(MySqlConfigBackendDHCPv6Impl::GET_AUDIT_ENTRIES6_TIME, + server_selector, modification_time, audit_entries); + + return (audit_entries); +} + +void +MySqlConfigBackendDHCPv6::createUpdateSubnet6(const ServerSelector& server_selector, + const Subnet6Ptr& subnet) { + impl_->createUpdateSubnet6(server_selector, subnet); +} + +void +MySqlConfigBackendDHCPv6::createUpdateSharedNetwork6(const ServerSelector& server_selector, + const SharedNetwork6Ptr& shared_network) { + impl_->createUpdateSharedNetwork6(server_selector, shared_network); +} + +void +MySqlConfigBackendDHCPv6::createUpdateOptionDef6(const ServerSelector& server_selector, + const OptionDefinitionPtr& option_def) { + impl_->createUpdateOptionDef6(server_selector, option_def); +} + +void +MySqlConfigBackendDHCPv6::createUpdateOption6(const ServerSelector& server_selector, + const OptionDescriptorPtr& option) { + impl_->createUpdateOption6(server_selector, option); +} + +void +MySqlConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector, + const std::string& shared_network_name, + const OptionDescriptorPtr& option) { + impl_->createUpdateOption6(server_selector, shared_network_name, option, false); +} + +void +MySqlConfigBackendDHCPv6::createUpdateOption6(const ServerSelector& server_selector, + const SubnetID& subnet_id, + const OptionDescriptorPtr& option) { + impl_->createUpdateOption6(server_selector, subnet_id, option, false); +} + +void +MySqlConfigBackendDHCPv6::createUpdateOption6(const ServerSelector& server_selector, + const asiolink::IOAddress& pool_start_address, + const asiolink::IOAddress& pool_end_address, + const OptionDescriptorPtr& option) { + impl_->createUpdateOption6(server_selector, pool_start_address, pool_end_address, + option); +} + +void +MySqlConfigBackendDHCPv6::createUpdateOption6(const ServerSelector& server_selector, + const asiolink::IOAddress& pd_pool_prefix, + const uint8_t pd_pool_prefix_length, + const OptionDescriptorPtr& option) { + impl_->createUpdateOption6(server_selector, pd_pool_prefix, + pd_pool_prefix_length, option); +} + +void +MySqlConfigBackendDHCPv6::createUpdateGlobalParameter6(const ServerSelector& server_selector, + const StampedValuePtr& value) { + impl_->createUpdateGlobalParameter6(server_selector, value); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteSubnet6(const ServerSelector& server_selector, + const std::string& subnet_prefix) { + return(impl_->deleteTransactional(MySqlConfigBackendDHCPv6Impl::DELETE_SUBNET6_PREFIX, + server_selector, "deleting a subnet by prefix", + "subnet deleted", true, + subnet_prefix)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteSubnet6(const ServerSelector& server_selector, + const SubnetID& subnet_id) { + return (impl_->deleteSubnet6(server_selector, subnet_id)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteAllSubnets6(const ServerSelector& server_selector) { + return (impl_->deleteTransactional(MySqlConfigBackendDHCPv6Impl::DELETE_ALL_SUBNETS6, + server_selector, "deleting all subnets", + "deleted all subnets", true)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteSharedNetwork6(const ServerSelector& server_selector, + const std::string& name) { + return (impl_->deleteTransactional(MySqlConfigBackendDHCPv6Impl::DELETE_SHARED_NETWORK6_NAME, + server_selector, "deleting a shared network", + "shared network deleted", true, + name)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteAllSharedNetworks6(const ServerSelector& server_selector) { + return (impl_->deleteTransactional(MySqlConfigBackendDHCPv6Impl::DELETE_ALL_SHARED_NETWORKS6, + server_selector, "deleting all shared networks", + "deleted all shared networks", true)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteOptionDef6(const ServerSelector& server_selector, + const uint16_t code, + const std::string& space) { + return (impl_->deleteOptionDef6(server_selector, code, space)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteAllOptionDefs6(const ServerSelector& server_selector) { + return (impl_->deleteTransactional(MySqlConfigBackendDHCPv6Impl::DELETE_ALL_OPTION_DEFS6, + server_selector, "deleting all option definitions", + "deleted all option definitions", true)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteOption6(const ServerSelector& server_selector, + const uint16_t code, + const std::string& space) { + return (impl_->deleteOption6(server_selector, code, space)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteOption6(const ServerSelector& server_selector, + const std::string& shared_network_name, + const uint16_t code, + const std::string& space) { + return (impl_->deleteOption6(server_selector, shared_network_name, + code, space)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteOption6(const ServerSelector& server_selector, + const SubnetID& subnet_id, + const uint16_t code, + const std::string& space) { + return (impl_->deleteOption6(server_selector, subnet_id, code, space)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteOption6(const ServerSelector& server_selector, + const asiolink::IOAddress& pool_start_address, + const asiolink::IOAddress& pool_end_address, + const uint16_t code, + const std::string& space) { + return (impl_->deleteOption6(server_selector, pool_start_address, pool_end_address, + code, space)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteOption6(const ServerSelector& server_selector, + const asiolink::IOAddress& pd_pool_prefix, + const uint8_t pd_pool_prefix_length, + const uint16_t code, + const std::string& space) { + return (impl_->deleteOption6(server_selector, pd_pool_prefix, + pd_pool_prefix_length, code, space)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteGlobalParameter6(const ServerSelector& server_selector, + const std::string& name) { + return (impl_->deleteTransactional(MySqlConfigBackendDHCPv6Impl::DELETE_GLOBAL_PARAMETER6, + server_selector, "deleting global parameter", + "global parameter deleted", false, + name)); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteAllGlobalParameters6(const ServerSelector& server_selector) { + return (impl_->deleteTransactional(MySqlConfigBackendDHCPv6Impl::DELETE_ALL_GLOBAL_PARAMETERS6, + server_selector, "deleting all global parameters", + "all global parameters deleted", true)); +} + +std::string +MySqlConfigBackendDHCPv6::getType() const { + return (impl_->getType()); +} + +std::string +MySqlConfigBackendDHCPv6::getHost() const { + return (impl_->getHost()); +} + +uint16_t +MySqlConfigBackendDHCPv6::getPort() const { + return (impl_->getPort()); +} + +bool +MySqlConfigBackendDHCPv6::registerBackendType() { + return ( + dhcp::ConfigBackendDHCPv6Mgr::instance().registerBackendFactory("mysql", + [](const db::DatabaseConnection::ParameterMap& params) -> dhcp::ConfigBackendDHCPv6Ptr { + return (dhcp::MySqlConfigBackendDHCPv6Ptr(new dhcp::MySqlConfigBackendDHCPv6(params))); + }) + ); +} + +void +MySqlConfigBackendDHCPv6::unregisterBackendType() { + dhcp::ConfigBackendDHCPv6Mgr::instance().unregisterBackendFactory("mysql"); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h new file mode 100644 index 0000000000..561d084d11 --- /dev/null +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h @@ -0,0 +1,498 @@ +// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef MYSQL_CONFIG_BACKEND_DHCP6_H +#define MYSQL_CONFIG_BACKEND_DHCP6_H + +#include +#include +#include + +namespace isc { +namespace dhcp { + +class MySqlConfigBackendDHCPv6Impl; + +/// @brief Implementation of the MySql Configuration Backend for +/// Kea DHCPv6 server. +/// +/// All POSIX times specified in the methods belonging to this +/// class must be local times. +class MySqlConfigBackendDHCPv6 : public ConfigBackendDHCPv6 { +public: + + /// @brief Constructor. + /// + /// @param parameters A data structure relating keywords and values + /// concerned with the database. + explicit MySqlConfigBackendDHCPv6(const db::DatabaseConnection::ParameterMap& parameters); + + /// @brief Retrieves a single subnet by subnet_prefix. + /// + /// @param server_selector Server selector. + /// @param subnet_prefix Prefix of the subnet to be retrieved. + /// @return Pointer to the retrieved subnet or NULL if not found. + /// @throw NotImplemented if server selector is "unassigned". + virtual Subnet6Ptr + getSubnet6(const db::ServerSelector& server_selector, + const std::string& subnet_prefix) const; + + /// @brief Retrieves a single subnet by subnet identifier. + /// + /// @param server_selector Server selector. + /// @param subnet_id Identifier of a subnet to be retrieved. + /// @return Pointer to the retrieved subnet or NULL if not found. + /// @throw NotImplemented if server selector is "unassigned". + virtual Subnet6Ptr + getSubnet6(const db::ServerSelector& server_selector, const SubnetID& subnet_id) const; + + /// @brief Retrieves all subnets. + /// + /// @param server_selector Server selector. + /// @return Collection of subnets or empty collection if no subnet found. + virtual Subnet6Collection + getAllSubnets6(const db::ServerSelector& server_selector) const; + + /// @brief Retrieves subnets modified after specified time. + /// + /// @param server_selector Server selector. + /// @param modification_time Lower bound subnet modification time. + /// @return Collection of subnets or empty collection if no subnet found. + virtual Subnet6Collection + getModifiedSubnets6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const; + + /// @brief Retrieves all subnets belonging to a specified shared network. + /// + /// @param server_selector Server selector. + /// @param shared_network_name Name of the shared network for which the + /// subnets should be retrieved. + /// @return Collection of subnets or empty collection if no subnet found. + virtual Subnet6Collection + getSharedNetworkSubnets6(const db::ServerSelector& server_selector, + const std::string& shared_network_name) const; + + /// @brief Retrieves shared network by name. + /// + /// @param server_selector Server selector. + /// @param name Name of the shared network to be retrieved. + /// @return Pointer to the shared network or NULL if not found. + /// @throw NotImplemented if server selector is "unassigned". + virtual SharedNetwork6Ptr + getSharedNetwork6(const db::ServerSelector& server_selector, + const std::string& name) const; + + /// @brief Retrieves all shared networks. + /// + /// @param server_selector Server selector. + /// @return Collection of shared network or empty collection if + /// no shared network found. + virtual SharedNetwork6Collection + getAllSharedNetworks6(const db::ServerSelector& server_selector) const; + + /// @brief Retrieves shared networks modified after specified time. + /// + /// @param server_selector Server selector. + /// @param modification_time Lower bound shared network modification time. + /// @return Collection of shared network or empty collection if + /// no shared network found. + virtual SharedNetwork6Collection + getModifiedSharedNetworks6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const; + + /// @brief Retrieves single option definition by code and space. + /// + /// @param server_selector Server selector. + /// @param code Code of the option to be retrieved. + /// @param space Option space of the option to be retrieved. + /// @return Pointer to the option definition or NULL if not found. + /// @throw NotImplemented if server selector is "unassigned". + virtual OptionDefinitionPtr + getOptionDef6(const db::ServerSelector& server_selector, const uint16_t code, + const std::string& space) const; + + /// @brief Retrieves all option definitions. + /// + /// @param server_selector Server selector. + /// @return Collection of option definitions or empty collection if + /// no option definition found. + virtual OptionDefContainer + getAllOptionDefs6(const db::ServerSelector& server_selector) const; + + /// @brief Retrieves option definitions modified after specified time. + /// + /// @param server_selector Server selector. + /// @param modification_time Lower bound option definition modification + /// time. + /// @return Collection of option definitions or empty collection if + /// no option definition found. + virtual OptionDefContainer + getModifiedOptionDefs6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const; + + /// @brief Retrieves single option by code and space. + /// + /// @param server_selector Server selector. + /// @return Pointer to the retrieved option descriptor or null if + /// no option was found. + /// @throw NotImplemented if server selector is "unassigned". + virtual OptionDescriptorPtr + getOption6(const db::ServerSelector& server_selector, const uint16_t code, + const std::string& space) const; + + /// @brief Retrieves all global options. + /// + /// @param server_selector Server selector. + /// @return Collection of global options or empty collection if no + /// option found. + virtual OptionContainer + getAllOptions6(const db::ServerSelector& server_selector) const; + + /// @brief Retrieves option modified after specified time. + /// + /// @param server_selector Server selector. + /// @param modification_time Lower bound option modification time. + /// @return Collection of global options or empty collection if no + /// option found. + virtual OptionContainer + getModifiedOptions6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const; + + /// @brief Retrieves global parameter value. + /// + /// @param server_selector Server selector. + /// @param name Name of the global parameter to be retrieved. + /// @return Value of the global parameter. + /// @throw NotImplemented if server selector is "unassigned". + virtual data::StampedValuePtr + getGlobalParameter6(const db::ServerSelector& server_selector, + const std::string& name) const; + + /// @brief Retrieves all global parameters. + /// + /// @param server_selector Server selector. + virtual data::StampedValueCollection + getAllGlobalParameters6(const db::ServerSelector& server_selector) const; + + /// @brief Retrieves global parameters modified after specified time. + /// + /// @param modification_time Lower bound modification time. + /// @return Collection of modified global parameters. + virtual data::StampedValueCollection + getModifiedGlobalParameters6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const; + + /// @brief Retrieves the most recent audit entries. + /// + /// @param 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. + /// @return Collection of audit entries. + virtual db::AuditEntryCollection + getRecentAuditEntries6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const; + + /// @brief Creates or updates a subnet. + /// + /// @param server_selector Server selector. + /// @param subnet Subnet to be added or updated. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateSubnet6(const db::ServerSelector& server_selector, + const Subnet6Ptr& subnet); + + /// @brief Creates or updates a shared network. + /// + /// @param server_selector Server selector. + /// @param shared_network Shared network to be added or updated. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateSharedNetwork6(const db::ServerSelector& server_selector, + const SharedNetwork6Ptr& shared_network); + + /// @brief Creates or updates an option definition. + /// + /// @param server_selector Server selector. + /// @param option_def Option definition to be added or updated. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateOptionDef6(const db::ServerSelector& server_selector, + const OptionDefinitionPtr& option_def); + + /// @brief Creates or updates global option. + /// + /// @param server_selector Server selector. + /// @param option Option to be added or updated. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateOption6(const db::ServerSelector& server_selector, + const OptionDescriptorPtr& option); + + /// @brief Creates or updates shared network level option. + /// + /// @param server_selector Server selector. + /// @param shared_network_name Name of a shared network to which option + /// belongs. + /// @param option Option to be added or updated. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateOption6(const db::ServerSelector& server_selector, + const std::string& shared_network_name, + const OptionDescriptorPtr& option); + + /// @brief Creates or updates subnet level option. + /// + /// @param server_selector Server selector. + /// @param subnet_id Identifier of a subnet to which option belongs. + /// @param option Option to be added or updated. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateOption6(const db::ServerSelector& server_selector, + const SubnetID& subnet_id, + const OptionDescriptorPtr& option); + + /// @brief Creates or updates pool level option. + /// + /// @param server_selector Server selector. + /// @param pool_start_address Lower bound address of the pool to which + /// the option belongs. + /// @param pool_end_address Upper bound address of the pool to which the + /// option belongs. + /// @param option Option to be added or updated. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateOption6(const db::ServerSelector& server_selector, + const asiolink::IOAddress& pool_start_address, + const asiolink::IOAddress& pool_end_address, + const OptionDescriptorPtr& option); + + /// @brief Creates or updates pd pool level option. + /// + /// @param server_selector Server selector. + /// @param pd_pool_prefix Address part of the prefix of the pd pool + /// to which the option belongs. + /// @param pd_pool_prefix_length Prefix length of the pd pool to which + /// the option belongs. + /// @param option Option to be added or updated. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateOption6(const db::ServerSelector& server_selector, + const asiolink::IOAddress& pd_pool_prefix, + const uint8_t pd_pool_prefix_length, + const OptionDescriptorPtr& option); + + /// @brief Creates or updates global parameter. + /// + /// @param server_selector Server selector. + /// @param name Name of the global parameter. + /// @param value Value of the global parameter. + /// @throw NotImplemented if server selector is "unassigned". + virtual void + createUpdateGlobalParameter6(const db::ServerSelector& server_selector, + const data::StampedValuePtr& value); + + /// @brief Deletes subnet by prefix. + /// + /// @param server_selector Server selector. + /// @param subnet_prefix Prefix of the subnet to be deleted. + /// @return Number of deleted subnets. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteSubnet6(const db::ServerSelector& server_selector, + const std::string& subnet_prefix); + + /// @brief Deletes subnet by identifier. + /// + /// @param server_selector Server selector. + /// @param subnet_id Identifier of the subnet to be deleted. + /// @return Number of deleted subnets. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteSubnet6(const db::ServerSelector& server_selector, const SubnetID& subnet_id); + + /// @brief Deletes all subnets. + /// + /// @param server_selector Server selector. + /// @return Number of deleted subnets. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteAllSubnets6(const db::ServerSelector& server_selector); + + /// @brief Deletes shared network by name. + /// + /// @param server_selector Server selector. + /// @param name Name of the shared network to be deleted. + /// @return Number of deleted shared networks. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteSharedNetwork6(const db::ServerSelector& server_selector, + const std::string& name); + + /// @brief Deletes all shared networks. + /// + /// @param server_selector Server selector. + /// @return Number of deleted shared networks. + virtual uint64_t + deleteAllSharedNetworks6(const db::ServerSelector& server_selector); + + /// @brief Deletes option definition. + /// + /// @param server_selector Server selector. + /// @param code Code of the option to be deleted. + /// @param space Option space of the option to be deleted. + /// @return Number of deleted option definitions. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteOptionDef6(const db::ServerSelector& server_selector, const uint16_t code, + const std::string& space); + + /// @brief Deletes all option definitions. + /// + /// @param server_selector Server selector. + /// @return Number of deleted option definitions. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteAllOptionDefs6(const db::ServerSelector& server_selector); + + /// @brief Deletes global option. + /// + /// @param server_selector Server selector. + /// @param code Code of the option to be deleted. + /// @param space Option space of the option to be deleted. + /// @return Number of deleted options. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteOption6(const db::ServerSelector& server_selector, const uint16_t code, + const std::string& space); + + /// @brief Deletes shared network level option. + /// + /// @param server_selector Server selector. + /// @param shared_network_name Name of the shared network which deleted + /// option belongs to + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteOption6(const db::ServerSelector& server_selector, + const std::string& shared_network_name, + const uint16_t code, + const std::string& space); + + /// @brief Deletes subnet level option. + /// + /// @param server_selector Server selector. + /// @param subnet_id Identifier of the subnet to which deleted option + /// belongs. + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @return Number of deleted options. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteOption6(const db::ServerSelector& server_selector, const SubnetID& subnet_id, + const uint16_t code, const std::string& space); + + /// @brief Deletes pool level option. + /// + /// @param server_selector Server selector. + /// @param pool_start_address Lower bound address of the pool to which + /// deleted option belongs. + /// @param pool_end_address Upper bound address of the pool to which the + /// deleted option belongs. + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @return Number of deleted options. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteOption6(const db::ServerSelector& server_selector, + const asiolink::IOAddress& pool_start_address, + const asiolink::IOAddress& pool_end_address, + const uint16_t code, + const std::string& space); + + /// @brief Deletes pd pool level option. + /// + /// @param server_selector Server selector. + /// @param pool_start_address Lower bound address of the pool to which + /// deleted option belongs. + /// @param pool_end_address Upper bound address of the pool to which the + /// deleted option belongs. + /// @param code Code of the deleted option. + /// @param space Option space of the deleted option. + /// @return Number of deleted options. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteOption6(const db::ServerSelector& server_selector, + const asiolink::IOAddress& pd_pool_prefix, + const uint8_t pd_pool_prefix_length, + const uint16_t code, + const std::string& space); + + /// @brief Deletes global parameter. + /// + /// @param server_selector Server selector. + /// @param name Name of the global parameter to be deleted. + /// @return Number of deleted global parameters. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteGlobalParameter6(const db::ServerSelector& server_selector, + const std::string& name); + + /// @brief Deletes all global parameters. + /// + /// @param server_selector Server selector. + /// @return Number of deleted global parameters. + /// @throw NotImplemented if server selector is "unassigned". + virtual uint64_t + deleteAllGlobalParameters6(const db::ServerSelector& server_selector); + + /// @brief Returns backend type in the textual format. + /// + /// @return "mysql". + virtual 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. + virtual 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. + virtual uint16_t getPort() const; + + /// @brief Registers the MySQL backend factory with backend config manager + /// + /// This should be called by the hook lib load() function. + /// @return True if the factory was registered successfully, false otherwise. + static bool registerBackendType(); + + /// @brief Unregisters the MySQL backend factory and discards MySQL backends + /// + /// This should be called by the hook lib unload() function. + static void unregisterBackendType(); + +private: + + /// @brief Pointer to the implementation of the @c MySqlConfigBackendDHCPv6 + /// class. + boost::shared_ptr impl_; + +}; + +/// @brief Pointer to the @c MySqlConfigBackendDHCPv6 class. +typedef boost::shared_ptr MySqlConfigBackendDHCPv6Ptr; + +} // end of namespace isc::cb +} // end of namespace isc + +#endif // MYSQL_CONFIG_BACKEND_DHCP6_H diff --git a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h index 7976fc87f5..fed2073af5 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h +++ b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h @@ -330,6 +330,8 @@ namespace { #define MYSQL_GET_OPTION4(...) \ MYSQL_GET_OPTION_COMMON(dhcp4, "", __VA_ARGS__) +#define MYSQL_GET_OPTION6(...) \ + MYSQL_GET_OPTION_COMMON(dhcp6, ", o.pd_pool_id ", __VA_ARGS__) #endif #ifndef MYSQL_GET_AUDIT_ENTRIES_TIME @@ -455,6 +457,8 @@ namespace { #define MYSQL_INSERT_OPTION4() \ MYSQL_INSERT_OPTION_COMMON(dhcp4, "", "") +#define MYSQL_INSERT_OPTION6() \ + MYSQL_INSERT_OPTION_COMMON(dhcp6, ", pd_pool_id ", ", ?") #endif #ifndef MYSQL_INSERT_OPTION_SERVER @@ -526,6 +530,8 @@ namespace { #define MYSQL_UPDATE_OPTION4(...) \ MYSQL_UPDATE_OPTION_COMMON(dhcp4, "", __VA_ARGS__) +#define MYSQL_UPDATE_OPTION6(...) \ + MYSQL_UPDATE_OPTION_COMMON(dhcp6, ", o.pd_pool_id = ? ", __VA_ARGS__) #endif #ifndef MYSQL_DELETE_GLOBAL_PARAMETER diff --git a/src/hooks/dhcp/mysql_cb/tests/Makefile.am b/src/hooks/dhcp/mysql_cb/tests/Makefile.am index 251575aeec..dd36169920 100644 --- a/src/hooks/dhcp/mysql_cb/tests/Makefile.am +++ b/src/hooks/dhcp/mysql_cb/tests/Makefile.am @@ -25,6 +25,8 @@ TESTS += mysql_cb_unittests mysql_cb_unittests_SOURCES = mysql_cb_dhcp4_unittest.cc mysql_cb_unittests_SOURCES += mysql_cb_dhcp4_mgr_unittest.cc +mysql_cb_unittests_SOURCES += mysql_cb_dhcp6_unittest.cc +mysql_cb_unittests_SOURCES += mysql_cb_dhcp6_mgr_unittest.cc mysql_cb_unittests_SOURCES += mysql_cb_impl_unittest.cc mysql_cb_unittests_SOURCES += run_unittests.cc diff --git a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_mgr_unittest.cc b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_mgr_unittest.cc new file mode 100644 index 0000000000..c4f8a48a1e --- /dev/null +++ b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_mgr_unittest.cc @@ -0,0 +1,87 @@ +// Copyright (C) 2019 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 +#include +#include +#include +#include +#include +#include + +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using namespace isc::db; +using namespace isc::db::test; + +namespace { + +/// @brief Test fixture class for @c MySqlConfigBackendDHCPv6Mgr. +class MySqlConfigBackendDHCPv6MgrTest : public GenericBackendTest { +public: + /// @brief Constructor. + MySqlConfigBackendDHCPv6MgrTest() { + // Recreate a fresh mgr. + ConfigBackendDHCPv6Mgr::create(); + + // Recreate database schema. + destroyMySQLSchema(); + createMySQLSchema(); + } + + /// @brief Destructor. + virtual ~MySqlConfigBackendDHCPv6MgrTest() { + // Destroy the mgr. + ConfigBackendDHCPv6Mgr::destroy(); + destroyMySQLSchema(); + } +}; + +// This test verifies that MySQL backend can be registered with and +// unregistered from the Config Backend Manager. +TEST_F(MySqlConfigBackendDHCPv6MgrTest, factoryRegistration) { + + // Get the mgr singleton. + ConfigBackendDHCPv6Mgr& mgr = ConfigBackendDHCPv6Mgr::instance(); + + // With no factory registered, attempting to add a MySQL db should fail. + ASSERT_THROW(mgr.addBackend(validMySQLConnectionString()), InvalidType); + + // Now we'll register the MySQL factory. + ASSERT_NO_THROW(MySqlConfigBackendDHCPv6::registerBackendType()); + + // With the factory registered, attempting to add a MySQL db should succeed. + ASSERT_NO_THROW(mgr.addBackend(validMySQLConnectionString())); + + // Create a MySQL backend selector for convenience. + BackendSelector mysql(BackendSelector::Type::MYSQL); + + // Should be able to create a global parameter. + StampedValuePtr server_tag = StampedValue::create("server-tag", "whale"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(mysql, ServerSelector::ALL(), + server_tag)); + // Verify parameter can be fetched. + server_tag.reset(); + ASSERT_NO_THROW(server_tag = mgr.getPool()->getGlobalParameter6(mysql, ServerSelector::ALL(), + "server-tag")); + ASSERT_TRUE(server_tag); + EXPECT_EQ("server-tag", server_tag->getName()); + EXPECT_EQ("whale", server_tag->getValue()); + + // Now we'll unregister MySQL. + ASSERT_NO_THROW(MySqlConfigBackendDHCPv6::unregisterBackendType()); + + // With no factory registered, attempting to add a MySQL db should fail. + ASSERT_THROW(mgr.addBackend(validMySQLConnectionString()), InvalidType); + + // Attempting to read the global parameter should fail. + ASSERT_THROW(mgr.getPool()->getGlobalParameter6(mysql, ServerSelector::ALL(), "server-tag"), + NoSuchDatabase); +} + +} diff --git a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc new file mode 100644 index 0000000000..2e1b23e767 --- /dev/null +++ b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc @@ -0,0 +1,1796 @@ +// Copyright (C) 2019 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace isc::asiolink; +using namespace isc::db; +using namespace isc::db::test; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief Test fixture class for @c MySqlConfigBackendDHCPv6. +/// +/// @todo The tests we're providing here only test cases when the +/// server selector is set to 'ALL' (configuration elements belong to +/// all servers). Currently we have no API to insert servers into +/// the database, and therefore we can't test the case when +/// configuration elements are assigned to particular servers by +/// server tags. We will have to expand existing tests when +/// the API is extended allowing for inserting servers to the +/// database. +class MySqlConfigBackendDHCPv6Test : public GenericBackendTest { +public: + + /// @brief Constructor. + MySqlConfigBackendDHCPv6Test() + : test_subnets_(), test_networks_(), timestamps_(), audit_entries_() { + // Recreate database schema. + destroyMySQLSchema(); + createMySQLSchema(); + + try { + // Create MySQL connection and use it to start the backend. + DatabaseConnection::ParameterMap params = + DatabaseConnection::parse(validMySQLConnectionString()); + cbptr_.reset(new MySqlConfigBackendDHCPv6(params)); + + } catch (...) { + std::cerr << "*** ERROR: unable to open database. The test\n" + "*** environment is broken and must be fixed before\n" + "*** the MySQL tests will run correctly.\n" + "*** The reason for the problem is described in the\n" + "*** accompanying exception output.\n"; + throw; + } + + // Create test data. + initTestOptions(); + initTestSubnets(); + initTestSharedNetworks(); + initTestOptionDefs(); + initTimestamps(); + } + + /// @brief Destructor. + virtual ~MySqlConfigBackendDHCPv6Test() { + cbptr_.reset(); + destroyMySQLSchema(); + } + + /// @brief Creates several subnets used in tests. + void initTestSubnets() { + // First subnet includes all parameters. + std::string interface_id_text = "whale"; + OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end()); + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), + 64, 30, 40, 50, 60, 1024)); + subnet->allowClientClass("home"); + subnet->setIface("eth1"); + subnet->setT2(323212); + subnet->addRelayAddress(IOAddress("2001:db8:1::2")); + subnet->addRelayAddress(IOAddress("2001:db8:3::4")); + subnet->setT1(1234); + subnet->requireClientClass("required-class1"); + subnet->requireClientClass("required-class2"); + subnet->setHostReservationMode(Subnet4::HR_DISABLED); + subnet->setContext(user_context); + subnet->setValid(555555); + + Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8::10"), + IOAddress("2001:db8::20"))); + subnet->addPool(pool1); + + Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8::50"), + IOAddress("2001:db8::60"))); + subnet->addPool(pool2); + + Pool6Ptr pdpool1(new Pool6(Lease::TYPE_PD, + IOAddress("2001:db8:a::"), 48, 64)); + subnet->addPool(pdpool1); + + Pool6Ptr pdpool2(new Pool6(Lease::TYPE_PD, + IOAddress("2001:db8:b::"), 48, 64)); + subnet->addPool(pdpool2); + + // Add several options to the subnet. + subnet->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_); + + subnet->getCfgOption()->add(test_options_[1]->option_, + test_options_[1]->persistent_, + test_options_[1]->space_name_); + + subnet->getCfgOption()->add(test_options_[2]->option_, + test_options_[2]->persistent_, + test_options_[2]->space_name_); + + test_subnets_.push_back(subnet); + + // Adding another subnet with the same subnet id to test + // cases that this second instance can override existing + // subnet instance. + subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), + 48, 20, 30, 40, 50, 1024)); + + pool1.reset(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8:1::10"), + IOAddress("2001:db8:1::20"))); + subnet->addPool(pool1); + + pool1->getCfgOption()->add(test_options_[3]->option_, + test_options_[3]->persistent_, + test_options_[3]->space_name_); + + pool1->getCfgOption()->add(test_options_[4]->option_, + test_options_[4]->persistent_, + test_options_[4]->space_name_); + + pool2.reset(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8:1::50"), + IOAddress("2001:db8:1::60"))); + subnet->addPool(pool2); + + pdpool1.reset(new Pool6(Lease::TYPE_PD, + IOAddress("2001:db8:c::"), 48, 64)); + subnet->addPool(pdpool1); + + pdpool1->getCfgOption()->add(test_options_[3]->option_, + test_options_[3]->persistent_, + test_options_[3]->space_name_); + + pdpool1->getCfgOption()->add(test_options_[4]->option_, + test_options_[4]->persistent_, + test_options_[4]->space_name_); + + pdpool2.reset(new Pool6(Lease::TYPE_PD, + IOAddress("2001:db8:d::"), 48, 64)); + subnet->addPool(pdpool2); + + test_subnets_.push_back(subnet); + + subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), + 64, 20, 30, 40, 50, 2048)); + Triplet null_timer; + subnet->setPreferred(null_timer); + subnet->setT1(null_timer); + subnet->setT2(null_timer); + subnet->setValid(null_timer); + test_subnets_.push_back(subnet); + + subnet.reset(new Subnet6(IOAddress("2001:db8:4::"), + 64, 30, 40, 50, 60, 4096)); + test_subnets_.push_back(subnet); + } + + /// @brief Creates several subnets used in tests. + void initTestSharedNetworks() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + SharedNetwork6Ptr shared_network(new SharedNetwork6("level1")); + shared_network->allowClientClass("foo"); + shared_network->setIface("eth1"); + shared_network->setT2(323212); + shared_network->addRelayAddress(IOAddress("2001:db8:1::2")); + shared_network->addRelayAddress(IOAddress("2001:db8:3::4")); + shared_network->setT1(1234); + shared_network->requireClientClass("required-class1"); + shared_network->requireClientClass("required-class2"); + shared_network->setHostReservationMode(Subnet6::HR_DISABLED); + shared_network->setContext(user_context); + shared_network->setValid(5555); + + // Add several options to the shared network. + shared_network->getCfgOption()->add(test_options_[2]->option_, + test_options_[2]->persistent_, + test_options_[2]->space_name_); + + shared_network->getCfgOption()->add(test_options_[3]->option_, + test_options_[3]->persistent_, + test_options_[3]->space_name_); + + shared_network->getCfgOption()->add(test_options_[4]->option_, + test_options_[4]->persistent_, + test_options_[4]->space_name_); + + test_networks_.push_back(shared_network); + + // Adding another shared network called "level1" to test + // cases that this second instance can override existing + // "level1" instance. + shared_network.reset(new SharedNetwork6("level1")); + test_networks_.push_back(shared_network); + + // Add more shared networks. + shared_network.reset(new SharedNetwork6("level2")); + Triplet null_timer; + shared_network->setPreferred(null_timer); + shared_network->setT1(null_timer); + shared_network->setT2(null_timer); + shared_network->setValid(null_timer); + test_networks_.push_back(shared_network); + + shared_network.reset(new SharedNetwork6("level3")); + test_networks_.push_back(shared_network); + } + + /// @brief Creates several option definitions used in tests. + void initTestOptionDefs() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + OptionDefinitionPtr option_def(new OptionDefinition("foo", 234, "string", + "espace")); + option_def->setOptionSpaceName("dhcp6"); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("bar", 234, "uint32", true)); + option_def->setOptionSpaceName("dhcp6"); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("fish", 235, "record", true)); + option_def->setOptionSpaceName("dhcp6"); + option_def->addRecordField("uint32"); + option_def->addRecordField("string"); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("whale", 2236, "string")); + option_def->setOptionSpaceName("xyz"); + test_option_defs_.push_back(option_def); + } + + /// @brief Creates several DHCP options used in tests. + void initTestOptions() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + OptionDefSpaceContainer defs; + + OptionDescriptor desc = + createOption(Option::V6, D6O_NEW_POSIX_TIMEZONE, + true, false, "my-timezone"); + desc.space_name_ = DHCP6_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption(Option::V6, D6O_PREFERENCE, + false, true, 64); + desc.space_name_ = DHCP6_OPTION_SPACE; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption(Option::V6, 1, false, false, 312131), + desc.space_name_ = "vendor-encapsulated-options"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createAddressOption(1254, true, true, + "2001:db8::3"); + desc.space_name_ = DHCP6_OPTION_SPACE; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createEmptyOption(Option::V6, 1, true); + desc.space_name_ = "isc"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createAddressOption(2, false, true, + "2001:db8:1::5", + "2001:db8:1::3", + "2001:db8:3::4"); + desc.space_name_ = "isc"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + // Add definitions for DHCPv6 non-standard options. + defs.addItem(OptionDefinitionPtr(new OptionDefinition( + "vendor-encapsulated-1", 1, "uint32")), + "vendor-encapsulated-options"); + defs.addItem(OptionDefinitionPtr(new OptionDefinition( + "option-1254", 1254, "ipv6-address", true)), + DHCP6_OPTION_SPACE); + defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "empty")), "isc"); + defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "ipv6-address", true)), + "isc"); + + // Register option definitions. + LibDHCP::setRuntimeOptionDefs(defs); + } + + /// @brief Initialize posix time values used in tests. + void initTimestamps() { + // Current time minus 1 hour to make sure it is in the past. + timestamps_["today"] = boost::posix_time::second_clock::local_time() + - boost::posix_time::hours(1); + // Yesterday. + timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24); + // Two days ago. + timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48); + // Tomorrow. + timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24); + } + + /// @brief Logs audit entries in the @c audit_entries_ member. + /// + /// This function is called in case of an error. + std::string logExistingAuditEntries() { + std::ostringstream s; + + auto& mod_time_idx = audit_entries_.get(); + + for (auto audit_entry_it = mod_time_idx.begin(); + audit_entry_it != mod_time_idx.end(); + ++audit_entry_it) { + auto audit_entry = *audit_entry_it; + s << audit_entry->getObjectType() << ", " + << audit_entry->getObjectId() << ", " + << static_cast(audit_entry->getModificationType()) << ", " + << audit_entry->getModificationTime() << ", " + << audit_entry->getLogMessage() + << std::endl; + } + + return (s.str()); + } + + /// @brief Tests that the new audit entry is added. + /// + /// This method retrieves a collection of the existing audit entries and + /// checks that the new one has been added at the end of this collection. + /// It then verifies the values of the audit entry against the values + /// specified by the caller. + /// + /// @param exp_object_type Expected object type. + /// @param exp_modification_time Expected modification time. + /// @param exp_log_message Expected log message. + /// @param new_entries_num Number of the new entries expected to be inserted. + void testNewAuditEntry(const std::string& exp_object_type, + const AuditEntry::ModificationType& exp_modification_type, + const std::string& exp_log_message, + const size_t new_entries_num = 1) { + auto audit_entries_size_save = audit_entries_.size(); + audit_entries_ = cbptr_->getRecentAuditEntries6(ServerSelector::ALL(), + timestamps_["two days ago"]); + ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_.size()) + << logExistingAuditEntries(); + + auto& mod_time_idx = audit_entries_.get(); + + // Iterate over specified number of entries starting from the most recent + // one and check they have correct values. + for (auto audit_entry_it = mod_time_idx.rbegin(); + std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num; + ++audit_entry_it) { + auto audit_entry = *audit_entry_it; + EXPECT_EQ(exp_object_type, audit_entry->getObjectType()) + << logExistingAuditEntries(); + EXPECT_EQ(exp_modification_type, audit_entry->getModificationType()) + << logExistingAuditEntries(); + EXPECT_EQ(exp_log_message, audit_entry->getLogMessage()) + << logExistingAuditEntries(); + } + } + + /// @brief Holds pointers to subnets used in tests. + std::vector test_subnets_; + + /// @brief Holds pointers to shared networks used in tests. + std::vector test_networks_; + + /// @brief Holds pointers to option definitions used in tests. + std::vector test_option_defs_; + + /// @brief Holds pointers to options used in tests. + std::vector test_options_; + + /// @brief Holds timestamp values used in tests. + std::map timestamps_; + + /// @brief Holds pointer to the backend. + boost::shared_ptr cbptr_; + + /// @brief Holds the most recent audit entries. + AuditEntryCollection audit_entries_; +}; + +// This test verifies that the expected backend type is returned. +TEST_F(MySqlConfigBackendDHCPv6Test, getType) { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW(cbptr_.reset(new MySqlConfigBackendDHCPv6(params))); + EXPECT_EQ("mysql", cbptr_->getType()); +} + +// This test verifies that by default localhost is returned as MySQL connection +// host. +TEST_F(MySqlConfigBackendDHCPv6Test, getHost) { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW(cbptr_.reset(new MySqlConfigBackendDHCPv6(params))); + EXPECT_EQ("localhost", cbptr_->getHost()); +} + +// This test verifies that by default port of 0 is returned as MySQL connection +// port. +TEST_F(MySqlConfigBackendDHCPv6Test, getPort) { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW(cbptr_.reset(new MySqlConfigBackendDHCPv6(params))); + EXPECT_EQ(0, cbptr_->getPort()); +} + +// This test verifies that the global parameter can be added, updated and +// deleted. +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteGlobalParameter6) { + StampedValuePtr global_parameter = StampedValue::create("global", "whale"); + + // Explicitly set modification time to make sure that the time + // returned from the database is correct. + global_parameter->setModificationTime(timestamps_["yesterday"]); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + global_parameter); + + { + SCOPED_TRACE("CREATE audit entry for global parameter"); + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::CREATE, + "global parameter set"); + } + + // Verify returned parameter and the modification time. + StampedValuePtr returned_global_parameter = + cbptr_->getGlobalParameter6(ServerSelector::ALL(), "global"); + ASSERT_TRUE(returned_global_parameter); + EXPECT_EQ("global", returned_global_parameter->getName()); + EXPECT_EQ("whale", returned_global_parameter->getValue()); + EXPECT_TRUE(returned_global_parameter->getModificationTime() == + global_parameter->getModificationTime()); + + // Because we have added the global parameter for all servers, it + // should be also returned for the explicitly specified server. + returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ONE("server1"), + "global"); + ASSERT_TRUE(returned_global_parameter); + EXPECT_EQ("global", returned_global_parameter->getName()); + EXPECT_EQ("whale", returned_global_parameter->getValue()); + EXPECT_TRUE(returned_global_parameter->getModificationTime() == + global_parameter->getModificationTime()); + + // Check that the parameter is udpated when selector is specified correctly. + global_parameter = StampedValue::create("global", "fish"); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + global_parameter); + returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ALL(), + "global"); + ASSERT_TRUE(returned_global_parameter); + EXPECT_EQ("global", returned_global_parameter->getName()); + EXPECT_EQ("fish", returned_global_parameter->getValue()); + EXPECT_TRUE(returned_global_parameter->getModificationTime() == + global_parameter->getModificationTime()); + + { + SCOPED_TRACE("UPDATE audit entry for the global parameter"); + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::UPDATE, + "global parameter set"); + } + + // Should not delete parameter specified for all servers if explicit + // server name is provided. + EXPECT_EQ(0, cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server1"), + "global")); + + // Delete parameter and make sure it is gone. + cbptr_->deleteGlobalParameter6(ServerSelector::ALL(), "global"); + returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ALL(), + "global"); + EXPECT_FALSE(returned_global_parameter); + + { + SCOPED_TRACE("DELETE audit entry for the global parameter"); + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::DELETE, + "global parameter deleted"); + } +} + +// This test verifies that all global parameters can be retrieved and deleted. +TEST_F(MySqlConfigBackendDHCPv6Test, getAllGlobalParameters6) { + // Create 3 parameters and put them into the database. + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name1", "value1")); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name2", Element::create(static_cast(65)))); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name3", "value3")); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name4", Element::create(static_cast(true)))); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name5", Element::create(static_cast(1.65)))); + + // Fetch all parameters. + auto parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL()); + ASSERT_EQ(5, parameters.size()); + + const auto& parameters_index = parameters.get(); + + // Verify their values. + EXPECT_EQ("value1", (*parameters_index.find("name1"))->getValue()); + EXPECT_EQ(65, (*parameters_index.find("name2"))->getIntegerValue()); + EXPECT_EQ("value3", (*parameters_index.find("name3"))->getValue()); + EXPECT_TRUE((*parameters_index.find("name4"))->getBoolValue()); + EXPECT_EQ(1.65, (*parameters_index.find("name5"))->getDoubleValue()); + + // Should be able to fetch these parameters when explicitly providing + // the server tag. + parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ONE("server1")); + EXPECT_EQ(5, parameters.size()); + + // Deleting global parameters with non-matching server selector + // should fail. + EXPECT_EQ(0, cbptr_->deleteAllGlobalParameters6(ServerSelector::ONE("server1"))); + + // Delete all parameters and make sure they are gone. + EXPECT_EQ(5, cbptr_->deleteAllGlobalParameters6(ServerSelector::ALL())); + parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL()); + EXPECT_TRUE(parameters.empty()); +} + +// This test verifies that modified global parameters can be retrieved. +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedGlobalParameters6) { + // Create 3 global parameters and assign modification times: + // "yesterday", "today" and "tomorrow" respectively. + StampedValuePtr value = StampedValue::create("name1", "value1"); + value->setModificationTime(timestamps_["yesterday"]); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + value); + + value = StampedValue::create("name2", Element::create(static_cast(65))); + value->setModificationTime(timestamps_["today"]); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + value); + + value = StampedValue::create("name3", "value3"); + value->setModificationTime(timestamps_["tomorrow"]); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + value); + + // Get parameters modified after "today". + auto parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ALL(), + timestamps_["today"]); + + const auto& parameters_index = parameters.get(); + + // It should be the one modified "tomorrow". + ASSERT_EQ(1, parameters_index.size()); + + auto parameter = parameters_index.find("name3"); + ASSERT_FALSE(parameter == parameters_index.end()); + + ASSERT_TRUE(*parameter); + EXPECT_EQ("value3", (*parameter)->getValue()); + + // Should be able to fetct these parameters when explicitly providing + // the server tag. + parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ONE("server1"), + timestamps_["today"]); + EXPECT_EQ(1, parameters.size()); +} + +// Test that subnet can be inserted, fetched, updated and then fetched again. +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6) { + // Insert new subnet. + Subnet6Ptr subnet = test_subnets_[0]; + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // Fetch this subnet by subnet identifier. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + test_subnets_[0]->getID()); + ASSERT_TRUE(returned_subnet); + + // The easiest way to verify whether the returned subnet matches the inserted + // subnet is to convert both to text. + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + + { + SCOPED_TRACE("CREATE audit entry for the subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + // Update the subnet in the database (both use the same ID). + Subnet6Ptr subnet2 = test_subnets_[1]; + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet2); + + // Fetch updated subnet and see if it matches. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + SubnetID(1024)); + EXPECT_EQ(subnet2->toElement()->str(), returned_subnet->toElement()->str()); + + // Fetching the subnet for an explicitly specified server tag should + // succeeed too. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"), + SubnetID(1024)); + EXPECT_EQ(subnet2->toElement()->str(), returned_subnet->toElement()->str()); + + { + SCOPED_TRACE("UPDATE audit entry for the subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet set"); + } +} + +// Test that subnet can be associated with a shared network. +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6SharedNetwork) { + Subnet6Ptr subnet = test_subnets_[0]; + SharedNetwork6Ptr shared_network = test_networks_[0]; + + // Add subnet to a shared network. + shared_network->add(subnet); + + // Store shared network in the database. + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network); + + // Store subnet associated with the shared network in the database. + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // Fetch this subnet by subnet identifier. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + test_subnets_[0]->getID()); + ASSERT_TRUE(returned_subnet); + + // The easiest way to verify whether the returned subnet matches the inserted + // subnet is to convert both to text. + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + + // However, the check above doesn't verify whether shared network name was + // correctly returned from the database. + EXPECT_EQ(shared_network->getName(), returned_subnet->getSharedNetworkName()); +} + +// Test that subnet can be fetched by prefix. +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6ByPrefix) { + // Insert subnet to the database. + Subnet6Ptr subnet = test_subnets_[0]; + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // Fetch the subnet by prefix. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + "2001:db8::/64"); + ASSERT_TRUE(returned_subnet); + + // Verify subnet contents. + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + + // Fetching the subnet for an explicitly specified server tag should + // succeeed too. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"), + "2001:db8::/64"); + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); +} + +// Test that all subnets can be fetched and then deleted. +TEST_F(MySqlConfigBackendDHCPv6Test, getAllSubnets6) { + // Insert test subnets into the database. Note that the second subnet will + // overwrite the first subnet as they use the same ID. + for (auto subnet : test_subnets_) { + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // That subnet overrides the first subnet so the audit entry should + // indicate an update. + if (subnet->toText() == "2001:db8:1::/48") { + SCOPED_TRACE("UPDATE audit entry for the subnet " + subnet->toText()); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet set"); + + } else { + SCOPED_TRACE("CREATE audit entry for the subnet " + subnet->toText()); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + } + + // Fetch all subnets. + Subnet6Collection subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()); + ASSERT_EQ(test_subnets_.size() - 1, subnets.size()); + + // All subnets should also be returned for explicitly specified server tag. + subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1")); + ASSERT_EQ(test_subnets_.size() - 1, subnets.size()); + + // See if the subnets are returned ok. + for (auto i = 0; i < subnets.size(); ++i) { + EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(), + subnets[i]->toElement()->str()); + } + + // Attempt to remove the non existing subnet should return 0. + EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ALL(), 22)); + EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ALL(), + "2001:db8:555::/64")); + // All subnets should be still there. + ASSERT_EQ(test_subnets_.size() - 1, subnets.size()); + + // Should not delete the subnet for explicit server tag because + // our subnet is for all servers. + EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), + test_subnets_[1]->getID())); + + // Also, verify that behavior when deleting by prefix. + EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), + test_subnets_[2]->toText())); + + // Same for all subnets. + EXPECT_EQ(0, cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1"))); + + // Delete first subnet by id and verify that it is gone. + EXPECT_EQ(1, cbptr_->deleteSubnet6(ServerSelector::ALL(), + test_subnets_[1]->getID())); + + { + SCOPED_TRACE("DELETE first subnet audit entry"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::DELETE, + "subnet deleted"); + } + + subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()); + ASSERT_EQ(test_subnets_.size() - 2, subnets.size()); + + // Delete second subnet by prefix and verify it is gone. + EXPECT_EQ(1, cbptr_->deleteSubnet6(ServerSelector::ALL(), + test_subnets_[2]->toText())); + subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()); + ASSERT_EQ(test_subnets_.size() - 3, subnets.size()); + + { + SCOPED_TRACE("DELETE second subnet audit entry"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::DELETE, + "subnet deleted"); + } + + // Delete all. + EXPECT_EQ(1, cbptr_->deleteAllSubnets6(ServerSelector::ALL())); + subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()); + ASSERT_TRUE(subnets.empty()); + + { + SCOPED_TRACE("DELETE all subnets audit entry"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::DELETE, + "deleted all subnets"); + } +} + +// Test that subnets modified after given time can be fetched. +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSubnets6) { + // Explicitly set timestamps of subnets. First subnet has a timestamp + // pointing to the future. Second subnet has timestamp pointing to the + // past (yesterday). Third subnet has a timestamp pointing to the + // past (an hour ago). + test_subnets_[1]->setModificationTime(timestamps_["tomorrow"]); + test_subnets_[2]->setModificationTime(timestamps_["yesterday"]); + test_subnets_[3]->setModificationTime(timestamps_["today"]); + + // Insert subnets into the database. + for (int i = 1; i < test_subnets_.size(); ++i) { + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), + test_subnets_[i]); + } + + // Fetch subnets with timestamp later than today. Only one subnet + // should be returned. + Subnet6Collection + subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(), + timestamps_["today"]); + ASSERT_EQ(1, subnets.size()); + + // All subnets should also be returned for explicitly specified server tag. + subnets = cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"), + timestamps_["today"]); + ASSERT_EQ(1, subnets.size()); + + // Fetch subnets with timestamp later than yesterday. We should get + // two subnets. + subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(), + timestamps_["yesterday"]); + ASSERT_EQ(2, subnets.size()); + + // Fetch subnets with timestamp later than tomorrow. Nothing should + // be returned. + subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(), + timestamps_["tomorrow"]); + ASSERT_TRUE(subnets.empty()); +} + +// Test that subnets belonging to a shared network can be retrieved. +TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetworkSubnets6) { + // Assign test subnets to shared networks level1 and level2. + test_subnets_[1]->setSharedNetworkName("level1"); + test_subnets_[2]->setSharedNetworkName("level2"); + test_subnets_[3]->setSharedNetworkName("level2"); + + // Store shared networks in the database. + for (auto network : test_networks_) { + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network); + } + + // Store subnets in the database. + for (auto subnet : test_subnets_) { + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + } + + // Fetch all subnets belonging to shared network level1. + Subnet6Collection subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ALL(), + "level1"); + ASSERT_EQ(1, subnets.size()); + + // Returned subnet should match test subnet #1. + EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(), + subnets[0]->toElement())); + + // All subnets should also be returned for explicitly specified server tag. + subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ONE("server1"), "level1"); + ASSERT_EQ(1, subnets.size()); + + // Returned subnet should match test subnet #1. + EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(), + subnets[0]->toElement())); + + // Fetch all subnets belonging to shared network level2. + subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ALL(), "level2"); + ASSERT_EQ(2, subnets.size()); + + ElementPtr test_list = Element::createList(); + test_list->add(test_subnets_[2]->toElement()); + test_list->add(test_subnets_[3]->toElement()); + + ElementPtr returned_list = Element::createList(); + returned_list->add(subnets[0]->toElement()); + returned_list->add(subnets[1]->toElement()); + + EXPECT_TRUE(isEquivalent(returned_list, test_list)); + + // All subnets should also be returned for explicitly specified server tag. + subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ONE("server1"), "level2"); + ASSERT_EQ(2, subnets.size()); + + returned_list = Element::createList(); + returned_list->add(subnets[0]->toElement()); + returned_list->add(subnets[1]->toElement()); + + EXPECT_TRUE(isEquivalent(returned_list, test_list)); +} + +// Test that shared network can be inserted, fetched, updated and then +// fetched again. +TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetwork6) { + // Insert new shared network. + SharedNetwork6Ptr shared_network = test_networks_[0]; + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network); + + // Fetch this shared network by name. + SharedNetwork6Ptr + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + test_networks_[0]->getName()); + ASSERT_TRUE(returned_network); + + // The easiest way to verify whether the returned shared network matches the + // inserted shared network is to convert both to text. + EXPECT_EQ(shared_network->toElement()->str(), + returned_network->toElement()->str()); + + { + SCOPED_TRACE("CREATE audit entry for a shared network"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::CREATE, + "shared network set"); + } + + // Update shared network in the database. + SharedNetwork6Ptr shared_network2 = test_networks_[1]; + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network2); + + // Fetch updated shared network and see if it matches. + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + test_networks_[1]->getName()); + EXPECT_EQ(shared_network2->toElement()->str(), + returned_network->toElement()->str()); + + { + SCOPED_TRACE("UPDATE audit entry for a shared network"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network set"); + } + + // Fetching the shared network for an explicitly specified server tag should + // succeed too. + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"), + shared_network2->getName()); + EXPECT_EQ(shared_network2->toElement()->str(), + returned_network->toElement()->str()); +} + +// Test that all shared networks can be fetched. +TEST_F(MySqlConfigBackendDHCPv6Test, getAllSharedNetworks6) { + // Insert test shared networks into the database. Note that the second shared + // network will overwrite the first shared network as they use the same name. + for (auto network : test_networks_) { + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network); + + // That shared network overrides the first one so the audit entry should + // indicate an update. + if ((network->getName() == "level1") && (!audit_entries_.empty())) { + SCOPED_TRACE("UPDATE audit entry for the shared network " + + network->getName()); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network set"); + + } else { + SCOPED_TRACE("CREATE audit entry for the shared network " + + network->getName()); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::CREATE, + "shared network set"); + } + } + + // Fetch all shared networks. + SharedNetwork6Collection networks = + cbptr_->getAllSharedNetworks6(ServerSelector::ALL()); + ASSERT_EQ(test_networks_.size() - 1, networks.size()); + + // All shared networks should also be returned for explicitly specified + // server tag. + networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1")); + ASSERT_EQ(test_networks_.size() - 1, networks.size()); + + // See if shared networks are returned ok. + for (auto i = 0; i < networks.size(); ++i) { + EXPECT_EQ(test_networks_[i + 1]->toElement()->str(), + networks[i]->toElement()->str()); + } + + // Deleting non-existing shared network should return 0. + EXPECT_EQ(0, cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), + "big-fish")); + // All shared networks should be still there. + ASSERT_EQ(test_networks_.size() - 1, networks.size()); + + // Should not delete the subnet for explicit server tag because + // our shared network is for all servers. + EXPECT_EQ(0, cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"), + test_networks_[1]->getName())); + + // Same for all shared networks. + EXPECT_EQ(0, cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1"))); + + // Delete first shared network and verify it is gone. + EXPECT_EQ(1, cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), + test_networks_[1]->getName())); + networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL()); + ASSERT_EQ(test_networks_.size() - 2, networks.size()); + + { + SCOPED_TRACE("DELETE audit entry for the first shared network"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::DELETE, + "shared network deleted"); + } + + // Delete all. + EXPECT_EQ(2, cbptr_->deleteAllSharedNetworks6(ServerSelector::ALL())); + networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL()); + ASSERT_TRUE(networks.empty()); + + { + SCOPED_TRACE("DELETE audit entry for the remaining two shared networks"); + // The last parameter indicates that we expect two new audit entries. + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::DELETE, + "deleted all shared networks", 2); + } +} + +// Test that shared networks modified after given time can be fetched. +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSharedNetworks6) { + // Explicitly set timestamps of shared networks. First shared + // network has a timestamp pointing to the future. Second shared + // network has timestamp pointing to the past (yesterday). + // Third shared network has a timestamp pointing to the + // past (an hour ago). + test_networks_[1]->setModificationTime(timestamps_["tomorrow"]); + test_networks_[2]->setModificationTime(timestamps_["yesterday"]); + test_networks_[3]->setModificationTime(timestamps_["today"]); + + // Insert shared networks into the database. + for (int i = 1; i < test_networks_.size(); ++i) { + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + test_networks_[i]); + } + + // Fetch shared networks with timestamp later than today. Only one + // shared network should be returned. + SharedNetwork6Collection + networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(), + timestamps_["today"]); + ASSERT_EQ(1, networks.size()); + + // Fetch shared networks with timestamp later than yesterday. We + // should get two shared networks. + networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(), + timestamps_["yesterday"]); + ASSERT_EQ(2, networks.size()); + + // Fetch shared networks with timestamp later than tomorrow. Nothing + // should be returned. + networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(), + timestamps_["tomorrow"]); + ASSERT_TRUE(networks.empty()); +} + +// Test that option definition can be inserted, fetched, updated and then +// fetched again. +TEST_F(MySqlConfigBackendDHCPv6Test, getOptionDef6) { + // Insert new option definition. + OptionDefinitionPtr option_def = test_option_defs_[0]; + cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def); + + // Fetch this option_definition by subnet identifier. + OptionDefinitionPtr returned_option_def = + cbptr_->getOptionDef6(ServerSelector::ALL(), + test_option_defs_[0]->getCode(), + test_option_defs_[0]->getOptionSpaceName()); + ASSERT_TRUE(returned_option_def); + + EXPECT_TRUE(returned_option_def->equals(*option_def)); + + { + SCOPED_TRACE("CREATE audit entry for an option definition"); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::CREATE, + "option definition set"); + } + + // Update the option definition in the database. + OptionDefinitionPtr option_def2 = test_option_defs_[1]; + cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def2); + + // Fetch updated option definition and see if it matches. + returned_option_def = cbptr_->getOptionDef6(ServerSelector::ALL(), + test_option_defs_[1]->getCode(), + test_option_defs_[1]->getOptionSpaceName()); + EXPECT_TRUE(returned_option_def->equals(*option_def2)); + + // Fetching option definition for an explicitly specified server tag + // should succeed too. + returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server1"), + test_option_defs_[1]->getCode(), + test_option_defs_[1]->getOptionSpaceName()); + EXPECT_TRUE(returned_option_def->equals(*option_def2)); + + { + SCOPED_TRACE("UPDATE audit entry for an option definition"); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::UPDATE, + "option definition set"); + } +} + +// Test that all option definitions can be fetched. +TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptionDefs6) { + // Insert test option definitions into the database. Note that the second + // option definition will overwrite the first option definition as they use + // the same code and space. + for (auto option_def : test_option_defs_) { + cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def); + + // That option definition overrides the first one so the audit entry should + // indicate an update. + if (option_def->getName() == "bar") { + SCOPED_TRACE("UPDATE audit entry for the option definition " + + option_def->getName()); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::UPDATE, + "option definition set"); + + } else { + SCOPED_TRACE("CREATE audit entry for the option defnition " + + option_def->getName()); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::CREATE, + "option definition set"); + } + } + + // Fetch all option_definitions. + OptionDefContainer option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL()); + ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size()); + + // All option definitions should also be returned for explicitly specified + // server tag. + option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ONE("server1")); + ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size()); + + // See if option definitions are returned ok. + for (auto def = option_defs.begin(); def != option_defs.end(); ++def) { + bool success = false; + for (auto i = 1; i < test_option_defs_.size(); ++i) { + if ((*def)->equals(*test_option_defs_[i])) { + success = true; + } + } + ASSERT_TRUE(success) << "failed for option definition " << (*def)->getCode() + << ", option space " << (*def)->getOptionSpaceName(); + } + + // Deleting non-existing option definition should return 0. + EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ALL(), + 99, "non-exiting-space")); + // All option definitions should be still there. + ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size()); + + // Should not delete option definition for explicit server tag + // because our option definition is for all servers. + EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ONE("server1"), + test_option_defs_[1]->getCode(), + test_option_defs_[1]->getOptionSpaceName())); + + // Same for all option definitions. + EXPECT_EQ(0, cbptr_->deleteAllOptionDefs6(ServerSelector::ONE("server1"))); + + // Delete one of the option definitions and see if it is gone. + EXPECT_EQ(1, cbptr_->deleteOptionDef6(ServerSelector::ALL(), + test_option_defs_[2]->getCode(), + test_option_defs_[2]->getOptionSpaceName())); + ASSERT_FALSE(cbptr_->getOptionDef6(ServerSelector::ALL(), + test_option_defs_[2]->getCode(), + test_option_defs_[2]->getOptionSpaceName())); + + { + SCOPED_TRACE("DELETE audit entry for the first option definition"); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::DELETE, + "option definition deleted"); + } + + // Delete all remaining option definitions. + EXPECT_EQ(2, cbptr_->deleteAllOptionDefs6(ServerSelector::ALL())); + option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL()); + ASSERT_TRUE(option_defs.empty()); + + { + SCOPED_TRACE("DELETE audit entries for the remaining option definitions"); + // The last parameter indicates that we expect two new audit entries. + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::DELETE, + "deleted all option definitions", 2); + } +} + +// Test that option definitions modified after given time can be fetched. +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedOptionDefs6) { + // Explicitly set timestamps of option definitions. First option + // definition has a timestamp pointing to the future. Second option + // definition has timestamp pointing to the past (yesterday). + // Third option definitions has a timestamp pointing to the + // past (an hour ago). + test_option_defs_[1]->setModificationTime(timestamps_["tomorrow"]); + test_option_defs_[2]->setModificationTime(timestamps_["yesterday"]); + test_option_defs_[3]->setModificationTime(timestamps_["today"]); + + // Insert option definitions into the database. + for (int i = 1; i < test_networks_.size(); ++i) { + cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), + test_option_defs_[i]); + } + + // Fetch option definitions with timestamp later than today. Only one + // option definition should be returned. + OptionDefContainer + option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(), + timestamps_["today"]); + ASSERT_EQ(1, option_defs.size()); + + // Fetch option definitions with timestamp later than yesterday. We + // should get two option definitions. + option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(), + timestamps_["yesterday"]); + ASSERT_EQ(2, option_defs.size()); + + // Fetch option definitions with timestamp later than tomorrow. Nothing + // should be returned. + option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(), + timestamps_["tomorrow"]); + ASSERT_TRUE(option_defs.empty()); +} + +// This test verifies that global option can be added, updated and deleted. +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteOption6) { + // Add option to the database. + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + opt_posix_timezone); + + // Make sure we can retrieve this option and that it is equal to the + // option we have inserted into the database. + OptionDescriptorPtr returned_opt_posix_timezone = + cbptr_->getOption6(ServerSelector::ALL(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_); + ASSERT_TRUE(returned_opt_posix_timezone); + EXPECT_TRUE(returned_opt_posix_timezone->equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("CREATE audit entry for an option"); + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::CREATE, + "global option set"); + } + + // Modify option and update it in the database. + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + opt_posix_timezone); + + // Retrieve the option again and make sure that updates were + // properly propagated to the database. + returned_opt_posix_timezone = cbptr_->getOption6(ServerSelector::ALL(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_); + ASSERT_TRUE(returned_opt_posix_timezone); + EXPECT_TRUE(returned_opt_posix_timezone->equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for an option"); + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::UPDATE, + "global option set"); + } + + // Deleting an option with explicitly specified server tag should fail. + EXPECT_EQ(0, cbptr_->deleteOption6(ServerSelector::ONE("server1"), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Deleting option for all servers should succeed. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ALL(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + EXPECT_FALSE(cbptr_->getOption6(ServerSelector::ALL(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + { + SCOPED_TRACE("DELETE audit entry for an option"); + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::DELETE, + "global option deleted"); + } +} + +// This test verifies that all global options can be retrieved. +TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptions6) { + // Add three global options to the database. + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[0]); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[1]); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[5]); + + // Retrieve all these options. + OptionContainer returned_options = cbptr_->getAllOptions6(ServerSelector::ALL()); + ASSERT_EQ(3, returned_options.size()); + + // Fetching global options with explicitly specified server tag should return + // the same result. + returned_options = cbptr_->getAllOptions6(ServerSelector::ONE("server1")); + ASSERT_EQ(3, returned_options.size()); + + // Get the container index used to search options by option code. + const OptionContainerTypeIndex& index = returned_options.get<1>(); + + // Verify that all options we put into the database were + // returned. + auto option0 = index.find(test_options_[0]->option_->getType()); + ASSERT_FALSE(option0 == index.end()); + EXPECT_TRUE(option0->equals(*test_options_[0])); + + auto option1 = index.find(test_options_[1]->option_->getType()); + ASSERT_FALSE(option1 == index.end()); + EXPECT_TRUE(option1->equals(*test_options_[1])); + + auto option5 = index.find(test_options_[5]->option_->getType()); + ASSERT_FALSE(option5 == index.end()); + EXPECT_TRUE(option5->equals(*test_options_[5])); +} + +// This test verifies that modified global options can be retrieved. +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedOptions6) { + // Assign timestamps to the options we're going to store in the + // database. + test_options_[0]->setModificationTime(timestamps_["tomorrow"]); + test_options_[1]->setModificationTime(timestamps_["yesterday"]); + test_options_[5]->setModificationTime(timestamps_["today"]); + + // Put options into the database. + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[0]); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[1]); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[5]); + + // Get options with the timestamp later than today. Only + // one option should be returned. + OptionContainer returned_options = + cbptr_->getModifiedOptions6(ServerSelector::ALL(), + timestamps_["today"]); + ASSERT_EQ(1, returned_options.size()); + + // Fetching modified options with explicitly specified server selector + // should return the same result. + returned_options = cbptr_->getModifiedOptions6(ServerSelector::ONE("server1"), + timestamps_["today"]); + ASSERT_EQ(1, returned_options.size()); + + // The returned option should be the one with the timestamp + // set to tomorrow. + const OptionContainerTypeIndex& index = returned_options.get<1>(); + auto option0 = index.find(test_options_[0]->option_->getType()); + ASSERT_FALSE(option0 == index.end()); + EXPECT_TRUE(option0->equals(*test_options_[0])); +} + +// This test verifies that subnet level option can be added, updated and +// deleted. +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteSubnetOption6) { + // Insert new subnet. + Subnet6Ptr subnet = test_subnets_[1]; + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // Fetch this subnet by subnet identifier. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + { + SCOPED_TRACE("CREATE audit entry for a new subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + cbptr_->createUpdateOption6(ServerSelector::ALL(), subnet->getID(), + opt_posix_timezone); + + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + OptionDescriptor returned_opt_posix_timezone = + returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + EXPECT_TRUE(returned_opt_posix_timezone.equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for an added subnet option"); + // Instead of adding an audit entry for an option we add an audit + // entry for the entire subnet so as the server refreshes the + // subnet with the new option. Note that the server doesn't + // have means to retrieve only the newly added option. + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option set"); + } + + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ALL(), subnet->getID(), + opt_posix_timezone); + + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + returned_opt_posix_timezone = + returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + EXPECT_TRUE(returned_opt_posix_timezone.equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for an updated subnet option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option set"); + } + + // Deleting an option with explicitly specified server tag should fail. + EXPECT_EQ(0, cbptr_->deleteOption6(ServerSelector::ONE("server1"), + subnet->getID(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // It should succeed for all servers. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ALL(), subnet->getID(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE).option_); + + { + SCOPED_TRACE("UPDATE audit entry for a deleted subnet option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option deleted"); + } +} + +// This test verifies that option can be inserted, updated and deleted +// from the pool. +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeletePoolOption6) { + // Insert new subnet. + Subnet6Ptr subnet = test_subnets_[1]; + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + { + SCOPED_TRACE("CREATE audit entry for a subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + // Add an option into the pool. + const PoolPtr pool = subnet->getPool(Lease::TYPE_NA, + IOAddress("2001:db8::10")); + ASSERT_TRUE(pool); + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + pool->getFirstAddress(), + pool->getLastAddress(), + opt_posix_timezone); + + // Query for a subnet. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + // The returned subnet should include our pool. + const PoolPtr returned_pool = returned_subnet->getPool(Lease::TYPE_NA, + IOAddress("2001:db8::10")); + ASSERT_TRUE(returned_pool); + + // The pool should contain option we added earlier. + OptionDescriptor returned_opt_posix_timezone = + returned_pool->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + EXPECT_TRUE(returned_opt_posix_timezone.equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option " + "to the address pool"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "address pool specific option set"); + } + + + // Modify the option and update it in the database. + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + pool->getFirstAddress(), + pool->getLastAddress(), + opt_posix_timezone); + + // Fetch the subnet and the corresponding pool. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + const PoolPtr returned_pool1 = returned_subnet->getPool(Lease::TYPE_NA, + IOAddress("2001:db8::10")); + ASSERT_TRUE(returned_pool1); + + // Test that the option has been correctly updated in the database. + returned_opt_posix_timezone = + returned_pool1->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + EXPECT_TRUE(returned_opt_posix_timezone.equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for a subnet when updating " + "address pool specific option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "address pool specific option set"); + } + + // Deleting an option with explicitly specified server tag should fail. + EXPECT_EQ(0, cbptr_->deleteOption6(ServerSelector::ONE("server1"), + pool->getFirstAddress(), + pool->getLastAddress(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Delete option for all servers should succeed. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ALL(), + pool->getFirstAddress(), + pool->getLastAddress(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Fetch the subnet and the pool from the database again to make sure + // that the option is really gone. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + const PoolPtr returned_pool2 = returned_subnet->getPool(Lease::TYPE_NA, + IOAddress("2001:db8::10")); + ASSERT_TRUE(returned_pool2); + + // Option should be gone. + EXPECT_FALSE(returned_pool2->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_NEW_POSIX_TIMEZONE).option_); + + { + SCOPED_TRACE("UPDATE audit entry for a subnet when deleting " + "address pool specific option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "address pool specific option deleted"); + } +} + +// This test verifies that option can be inserted, updated and deleted +// from the pd pool. +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeletePdPoolOption6) { + // Insert new subnet. + Subnet6Ptr subnet = test_subnets_[1]; + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + { + SCOPED_TRACE("CREATE audit entry for a subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + // Add an option into the pd pool. + const PoolPtr pd_pool = subnet->getPool(Lease::TYPE_PD, + IOAddress("2001:db8:a:10::")); + ASSERT_TRUE(pd_pool); + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + int pd_pool_len = prefixLengthFromRange(pd_pool->getFirstAddress(), + pd_pool->getLastAddress()); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + pd_pool->getFirstAddress(), + static_cast(pd_pool_len), + opt_posix_timezone); + + // Query for a subnet. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + // The returned subnet should include our pool. + const PoolPtr returned_pd_pool = + returned_subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:a:10::")); + ASSERT_TRUE(returned_pd_pool); + + // The pd pool should contain option we added earlier. + OptionDescriptor returned_opt_posix_timezone = + returned_pd_pool->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + EXPECT_TRUE(returned_opt_posix_timezone.equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option " + "to the prefix delegation pool"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "prefix delegation pool specific option set"); + } + + + // Modify the option and update it in the database. + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + pd_pool->getFirstAddress(), + static_cast(pd_pool_len), + opt_posix_timezone); + + // Fetch the subnet and the corresponding pd pool. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + const PoolPtr returned_pd_pool1 = + returned_subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:a:10::")); + ASSERT_TRUE(returned_pd_pool1); + + // Test that the option has been correctly updated in the database. + returned_opt_posix_timezone = + returned_pd_pool1->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + EXPECT_TRUE(returned_opt_posix_timezone.equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for a subnet when updating " + "prefix delegation pool specific option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "prefix delegation pool specific option set"); + } + + // Deleting an option with explicitly specified server tag should fail. + EXPECT_EQ(0, cbptr_->deleteOption6(ServerSelector::ONE("server1"), + pd_pool->getFirstAddress(), + static_cast(pd_pool_len), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Delete option for all servers should succeed. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ALL(), + pd_pool->getFirstAddress(), + static_cast(pd_pool_len), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Fetch the subnet and the pool from the database again to make sure + // that the option is really gone. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + const PoolPtr returned_pd_pool2 = + returned_subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:a:10::")); + ASSERT_TRUE(returned_pd_pool2); + + // Option should be gone. + EXPECT_FALSE(returned_pd_pool2->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_NEW_POSIX_TIMEZONE).option_); + + { + SCOPED_TRACE("UPDATE audit entry for a subnet when deleting " + "prefix delegation pool specific option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "prefix delegation pool specific option deleted"); + } +} + +// This test verifies that shared network level option can be added, +// updated and deleted. +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteSharedNetworkOption6) { + // Insert new shared network. + SharedNetwork6Ptr shared_network = test_networks_[1]; + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network); + + // Fetch this shared network by name. + SharedNetwork6Ptr returned_network = + cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network->getName()); + ASSERT_TRUE(returned_network); + + { + SCOPED_TRACE("CREATE audit entry for the new shared network"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::CREATE, + "shared network set"); + } + + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + shared_network->getName(), + opt_posix_timezone); + + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network->getName()); + ASSERT_TRUE(returned_network); + + OptionDescriptor returned_opt_posix_timezone = + returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + EXPECT_TRUE(returned_opt_posix_timezone.equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for the added shared network option"); + // Instead of adding an audit entry for an option we add an audit + // entry for the entire shared network so as the server refreshes the + // shared network with the new option. Note that the server doesn't + // have means to retrieve only the newly added option. + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network specific option set"); + } + + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + shared_network->getName(), + opt_posix_timezone); + + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network->getName()); + ASSERT_TRUE(returned_network); + returned_opt_posix_timezone = + returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + EXPECT_TRUE(returned_opt_posix_timezone.equals(*opt_posix_timezone)); + + { + SCOPED_TRACE("UPDATE audit entry for the updated shared network option"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network specific option set"); + } + + // Deleting an option with explicitly specified server tag should fail. + EXPECT_EQ(0, cbptr_->deleteOption6(ServerSelector::ONE("server1"), + shared_network->getName(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Deleting an option for all servers should succeed. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ALL(), + shared_network->getName(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network->getName()); + ASSERT_TRUE(returned_network); + EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE).option_); + + { + SCOPED_TRACE("UPDATE audit entry for the deleted shared network option"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network specific option deleted"); + } +} + +}