From: Marcin Siodelski Date: Fri, 21 Jun 2019 15:38:01 +0000 (+0200) Subject: [#683,!390] Implemented lease6-bulk-apply command. X-Git-Tag: Kea-1.6.0-beta2~261 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1deff74f1d28ca5fd2d8495047c6aafc8b6c0f5b;p=thirdparty%2Fkea.git [#683,!390] Implemented lease6-bulk-apply command. --- diff --git a/src/hooks/dhcp/lease_cmds/lease_cmds.cc b/src/hooks/dhcp/lease_cmds/lease_cmds.cc index 7f900e40d7..f555f89c81 100644 --- a/src/hooks/dhcp/lease_cmds/lease_cmds.cc +++ b/src/hooks/dhcp/lease_cmds/lease_cmds.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-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 @@ -122,6 +122,16 @@ public: int leaseAddHandler(CalloutHandle& handle); + /// @brief lease6-bulk-apply command handler + /// + /// Provides the implementation for the @ref isc::lease_cmds::LeaseCmds::lease6BulkHandler. + /// + /// @param handle Callout context - which is expected to contain the + /// add command JSON text in the "command" argument + /// @return 0 upon success, non-zero otherwise + int + lease6BulkApplyHandler(CalloutHandle& handle); + /// @brief lease4-get, lease6-get command handler /// /// Provides the implementation for @ref isc::lease_cmds::LeaseCmds::leaseGetHandler @@ -232,6 +242,39 @@ public: /// @return parsed parameters /// @throw BadValue if input arguments don't make sense. Parameters getParameters(bool v6, const ConstElementPtr& args); + + /// @brief Convenience function fetching IPv6 address to be used to + /// delete a lease. + /// + /// The returned parameter depends on the @c query_type value stored + /// in the passed object. Note that the HW address is not allowed and + /// this query type results in an exception. If the query type is of + /// the address type, the address is returned. If the type is set to + /// DUID, this function will try to find the lease for this DUID + /// and return the corresponding address. + /// + /// @param parameters parameters extracted from the command. + /// + /// @return Address of the lease to be deleted. + /// @throw InvalidParameter if the DUID is not found when needed to + /// find the lease or if the query type is by HW address. + /// @throw InvalidOperation if the query type is unknown. + IOAddress getIPv6AddressForDelete(const Parameters& parameters) const; + + /// @brief Returns a map holding brief information about a lease which + /// failed to be deleted, updated or added. + /// + /// The DUID is only included if it is non-null. The address is only + /// included if it is non-zero. + /// + /// @param subnet_id identifier of the subnet where the lease belongs. + /// @param lease_type lease type. + /// @param lease_address lease address. + /// @param duid DUID of the client. + ElementPtr getFailedLeaseMap(const SubnetID& subnet_id, + const Lease::Type& lease_type, + const IOAddress& lease_address, + const DuidPtr&duid) const; }; int @@ -773,6 +816,187 @@ LeaseCmdsImpl::lease4DelHandler(CalloutHandle& handle) { return (0); } +int +LeaseCmdsImpl::lease6BulkApplyHandler(CalloutHandle& handle) { + try { + extractCommand(handle); + + // Arguments are mandatory. + if (!cmd_args_ || (cmd_args_->getType() != Element::map)) { + isc_throw(BadValue, "Command arguments missing or a not a map."); + } + + // At least one of the 'deleted-leases' or 'leases' must be present. + auto deleted_leases = cmd_args_->get("deleted-leases"); + auto leases = cmd_args_->get("leases"); + + if (!deleted_leases && !leases) { + isc_throw(BadValue, "neither 'deleted-leases' nor 'leases' parameter" + " specified"); + } + + // Make sure that 'deleted-leases' is a list, if present. + if (deleted_leases && (deleted_leases->getType() != Element::list)) { + isc_throw(BadValue, "the 'deleted-leases' parameter must be a list"); + } + + // Make sure that 'leases' is a list, if present. + if (leases && (leases->getType() != Element::list)) { + isc_throw(BadValue, "the 'leases' parameter must be a list"); + } + + // Parse deleted leases without deleting them from the database + // yet. If any of the deleted leases or new leases appears to be + // malformed we can easily rollback. + std::list > parsed_deleted_list; + if (deleted_leases) { + auto leases_list = deleted_leases->listValue(); + + // Iterate over leases to be deleted. + for (auto lease_params : leases_list) { + // Parsing the lease may throw and it means that the lease + // information is malformed. + Parameters p = getParameters(true, lease_params); + auto lease_addr = getIPv6AddressForDelete(p); + parsed_deleted_list.push_back(std::make_pair(p, lease_addr)); + } + } + + // Parse new/updated leases without affecting the database to detect + // any errors that should cause an error response. + std::list parsed_leases_list; + if (leases) { + ConstSrvConfigPtr config = CfgMgr::instance().getCurrentCfg(); + + // Iterate over all leases. + auto leases_list = leases->listValue(); + for (auto lease_params : leases_list) { + + Lease6Parser parser; + bool force_update; + + // If parsing the lease fails we throw, as it indicates that the + // command is malformed. + Lease6Ptr lease6 = parser.parse(config, lease_params, force_update); + parsed_leases_list.push_back(lease6); + } + } + + // Count successful deletions and updates. + size_t success_count = 0; + + ElementPtr failed_deleted_list; + if (!parsed_deleted_list.empty()) { + + // Iterate over leases to be deleted. + for (auto lease_params_pair : parsed_deleted_list) { + + // This part is outside of the try-catch because an exception + // indicates that the command is malformed. + Parameters p = lease_params_pair.first; + auto lease_addr = lease_params_pair.second; + + try { + if (!lease_addr.isV6Zero()) { + // This may throw if the lease couldn't be deleted for + // any reason, but we still want to proceed with other + // leases. + if (LeaseMgrFactory::instance().deleteLease(lease_addr)) { + ++success_count; + + } else { + // If the lease doesn't exist we also want to put it + // on the list of leases which failed to delete. That + // corresponds to the lease6-del command which returns + // an error when the lease doesn't exist. + isc_throw(InvalidOperation, "no such lease for address " + << lease_addr.toText()); + } + } + + } catch (...) { + // Lazy creation of the list of leases which failed to delete. + if (!failed_deleted_list) { + failed_deleted_list = Element::createList(); + } + failed_deleted_list->add(getFailedLeaseMap(p.subnet_id, p.lease_type, + p.addr, p.duid)); + } + } + } + + // Process leases to be added or/and updated. + ElementPtr failed_leases_list; + if (!parsed_leases_list.empty()) { + ConstSrvConfigPtr config = CfgMgr::instance().getCurrentCfg(); + + // Iterate over all leases. + for (auto lease : parsed_leases_list) { + + Lease6Parser parser; + bool force_update; + + try { + // Check if the lease already exists. + auto existing_lease = LeaseMgrFactory::instance().getLease6(lease->type_, + lease->addr_); + // If the lease exists, we should update it. Otherwise, we add + // the new lease. + if (existing_lease) { + LeaseMgrFactory::instance().updateLease6(lease); + + } else { + LeaseMgrFactory::instance().addLease(lease); + } + ++success_count; + + } catch (...) { + // Lazy creation of the list of leases which failed to add/update. + if (!failed_leases_list) { + failed_leases_list = Element::createList(); + } + failed_leases_list->add(getFailedLeaseMap(lease->subnet_id_, + lease->type_, + lease->addr_, + lease->duid_)); + } + } + } + + // Start preparing the response. + ElementPtr args; + + if (failed_deleted_list || failed_leases_list) { + // If there are any failed leases, let's include them in the response. + args = Element::createMap(); + + // failed-deleted-leases + if (failed_deleted_list) { + args->set("failed-deleted-leases", failed_deleted_list); + } + + // failed-leases + if (failed_leases_list) { + args->set("failed-deleted-leases", failed_leases_list); + } + } + + // Send the success response and include failed leases. + std::ostringstream resp_text; + resp_text << "Bulk apply of " << success_count << " IPv6 leases completed."; + auto answer = createAnswer(success_count > 0 ? CONTROL_RESULT_SUCCESS : + CONTROL_RESULT_EMPTY, resp_text.str(), args); + setResponse(handle, answer); + + } catch (const std::exception& ex) { + // Unable to parse the command and similar issues. + setErrorResponse(handle, ex.what()); + return (CONTROL_RESULT_ERROR); + } + + return (CONTROL_RESULT_SUCCESS); +} + int LeaseCmdsImpl::lease6DelHandler(CalloutHandle& handle) { Parameters p; @@ -998,11 +1222,77 @@ LeaseCmdsImpl::lease6WipeHandler(CalloutHandle& handle) { return (0); } +IOAddress +LeaseCmdsImpl::getIPv6AddressForDelete(const Parameters& parameters) const { + IOAddress addr = IOAddress::IPV6_ZERO_ADDRESS(); + Lease6Ptr lease6; + + switch (parameters.query_type) { + case Parameters::TYPE_ADDR: { + + // If address was specified explicitly, let's use it as is. + addr = parameters.addr; + break; + } + case Parameters::TYPE_HWADDR: + isc_throw(InvalidParameter, "Delete by hw-address is not allowed in v6."); + break; + + case Parameters::TYPE_DUID: + if (!parameters.duid) { + isc_throw(InvalidParameter, "Program error: Query by duid " + "requires duid to be specified"); + } + + // Let's see if there's such a lease at all. + lease6 = LeaseMgrFactory::instance().getLease6(parameters.lease_type, + *parameters.duid, + parameters.iaid, + parameters.subnet_id); + if (lease6) { + addr = lease6->addr_; + } + + break; + + default: + isc_throw(InvalidOperation, "Unknown query type: " + << static_cast(parameters.query_type)); + } + + return (addr); +} + +ElementPtr +LeaseCmdsImpl::getFailedLeaseMap(const SubnetID& subnet_id, + const Lease::Type& lease_type, + const IOAddress& lease_address, + const DuidPtr&duid) const { + auto failed_lease_map = Element::createMap(); + failed_lease_map->set("subnet-id", + Element::create(static_cast(subnet_id))); + failed_lease_map->set("type", Element::create(Lease::typeToText(lease_type))); + + if (!lease_address.isV6Zero()) { + failed_lease_map->set("ip-address", Element::create(lease_address.toText())); + + } else if (duid) { + failed_lease_map->set("duid", Element::create(duid->toText())); + } + + return (failed_lease_map); +} + int LeaseCmds::leaseAddHandler(CalloutHandle& handle) { return(impl_->leaseAddHandler(handle)); } +int +LeaseCmds::lease6BulkApplyHandler(CalloutHandle& handle) { + return (impl_->lease6BulkApplyHandler(handle)); +} + int LeaseCmds::leaseGetHandler(CalloutHandle& handle) { return(impl_->leaseGetHandler(handle)); diff --git a/src/hooks/dhcp/lease_cmds/lease_cmds.h b/src/hooks/dhcp/lease_cmds/lease_cmds.h index ff842480f1..66c2851a9c 100644 --- a/src/hooks/dhcp/lease_cmds/lease_cmds.h +++ b/src/hooks/dhcp/lease_cmds/lease_cmds.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-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 @@ -81,6 +81,88 @@ public: int leaseAddHandler(hooks::CalloutHandle& handle); + /// @brief lease6-bulk-apply command handler + /// + /// This command conveys information about multiple leases to be added, + /// updated or deleted. This command should be used instead of lease6-add, + /// lease6-update and lease6-del when it is desired to apply multiple + /// lease changes within a single transaction. This is much faster and + /// should be used in cases when the performance is critical. This + /// command was added as a result of our experience with High Availability + /// where multiple IPv6 addresses and/or prefixes can be allocated for + /// a single DHCPv6 packet. + /// + /// Example structure of the command: + /// + /// { + /// "command": "lease6-bulk-apply", + /// "arguments": { + /// "deleted-leases": [ + /// { + /// "subnet-id": 66, + /// "ip-address": "2001:db8:abcd::", + /// "type": "IA_PD", + /// ... + /// }, + /// { + /// "subnet-id": 66, + /// "ip-address": "2001:db8:abcd::234", + /// "type": "IA_NA", + /// ... + /// } + /// ], + /// "leases": [ + /// { + /// "subnet-id": 66, + /// "ip-address": "2001:db8:cafe::", + /// "type": "IA_PD", + /// ... + /// }, + /// { + /// "subnet-id": 66, + /// "ip-address": "2001:db8:abcd::333", + /// "type": "IA_NA", + /// ... + /// } + /// ] + /// } + /// } + /// + /// The response indicates which of the leases failed to be applied. + /// For example: + /// + /// { + /// "result": 0, + /// "text": IPv6 leases bulk apply completed. + /// "arguments": { + /// "failed-deleted-leases": [ + /// { + /// "subnet-id": 66, + /// "ip-address": "2001:db8:abcd::", + /// "type": "IA_PD" + /// } + /// ], + /// "failed-leases": [ + /// { + /// "subnet-id": 66, + /// "ip-address": "2001:db8:cafe::", + /// "type": "IA_PD", + /// ... + /// } + /// ] + /// } + /// } + /// + /// The command handler first attempts to delete all leases listed in + /// the "deleted-leases" list. Next, it adds the leases listed in the + /// "leases" list. If any of these leases already exists, it is updated. + /// + /// @param handle Callout context - which is expected to contain the + /// add command JSON text in the "command" argument + /// @return result of the operation + int + lease6BulkApplyHandler(hooks::CalloutHandle& handle); + /// @brief lease4-get, lease6-get command handler /// /// This command attempts to retrieve a lease that match selected criteria. diff --git a/src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc b/src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc index 12115941e7..b610af68fe 100644 --- a/src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc +++ b/src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the End User License // Agreement. See COPYING file in the premium/ directory. @@ -44,6 +44,17 @@ int lease6_add(CalloutHandle& handle) { return(lease_cmds.leaseAddHandler(handle)); } +/// @brief This is a command callout for 'lease6-bulk-apply' command. +/// +/// @param handle Callout handle used to retrieve a command and +/// provide a response. +/// @return 0 if this callout has been invoked successfully, +/// 1 otherwise. +int lease6_bulk_apply(CalloutHandle& handle) { + LeaseCmds lease_cmds; + return (lease_cmds.lease6BulkApplyHandler(handle)); +} + /// @brief This is a command callout for 'lease4-get' command. /// /// @param handle Callout handle used to retrieve a command and @@ -183,6 +194,7 @@ int lease6_wipe(CalloutHandle& handle) { int load(LibraryHandle& handle) { handle.registerCommandCallout("lease4-add", lease4_add); handle.registerCommandCallout("lease6-add", lease6_add); + handle.registerCommandCallout("lease6-bulk-apply", lease6_bulk_apply); handle.registerCommandCallout("lease4-get", lease4_get); handle.registerCommandCallout("lease6-get", lease6_get); handle.registerCommandCallout("lease4-get-all", lease4_get_all);