From: Marcin Siodelski Date: Fri, 19 Jan 2018 16:15:23 +0000 (+0100) Subject: [5457] Implemented PoC for packets parking. X-Git-Tag: ha_checkpoints12~3^2~18 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=735f414d4a247bbde2a7e6665430fccb3e129d87;p=thirdparty%2Fkea.git [5457] Implemented PoC for packets parking. --- diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index ba129663b0..05acc17048 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -1100,6 +1100,8 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) { static_cast(1)); } + bool packet_park = false; + if (ctx && HooksManager::calloutsPresent(Hooks.hook_index_leases4_committed_)) { CalloutHandlePtr callout_handle = getCalloutHandle(query); @@ -1131,13 +1133,37 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) { // Call all installed callouts HooksManager::callCallouts(Hooks.hook_index_leases4_committed_, *callout_handle); + + + switch (callout_handle->getStatus()) { + // If next step is set to "skip" we simply terminate here, because the + // next step would be to send the packet. + case CalloutHandle::NEXT_STEP_SKIP: + return; + + case CalloutHandle::NEXT_STEP_PARK: + packet_park = true; + break; + default: + ; + } } if (!rsp) { return; } - leases4CommittedContinue(getCalloutHandle(query), query, rsp); + if (packet_park) { + // Park the packet. The function we bind here will be executed when the hook + // library unparks the packet. + HooksManager::park("leases4_committed", query, + std::bind(&Dhcpv4Srv::leases4CommittedContinue, this, + getCalloutHandle(query), query, rsp)); + + } else { + // Packet is not to be parked, so let's continue processing. + leases4CommittedContinue(getCalloutHandle(query), query, rsp); + } } void diff --git a/src/lib/hooks/Makefile.am b/src/lib/hooks/Makefile.am index 1a3ba61857..b13a9a4c44 100644 --- a/src/lib/hooks/Makefile.am +++ b/src/lib/hooks/Makefile.am @@ -42,6 +42,7 @@ libkea_hooks_la_SOURCES += libinfo.cc libinfo.h libkea_hooks_la_SOURCES += library_handle.cc library_handle.h libkea_hooks_la_SOURCES += library_manager.cc library_manager.h libkea_hooks_la_SOURCES += library_manager_collection.cc library_manager_collection.h +libkea_hooks_la_SOURCES += parking_lots.h libkea_hooks_la_SOURCES += pointer_converter.h libkea_hooks_la_SOURCES += server_hooks.cc server_hooks.h diff --git a/src/lib/hooks/callout_handle.cc b/src/lib/hooks/callout_handle.cc index 34c2c4b1fc..88d58940cf 100644 --- a/src/lib/hooks/callout_handle.cc +++ b/src/lib/hooks/callout_handle.cc @@ -72,6 +72,11 @@ CalloutHandle::getArgumentNames() const { return (names); } +ParkingLotHandlePtr +CalloutHandle::getParkingLotHandlePtr() const { + return (boost::make_shared(server_hooks_.getParkingLotPtr(manager_->getHookIndex()))); +} + // Return the library handle allowing the callout to access the CalloutManager // registration/deregistration functions. diff --git a/src/lib/hooks/callout_handle.h b/src/lib/hooks/callout_handle.h index 637f4dbaed..45d36fc893 100644 --- a/src/lib/hooks/callout_handle.h +++ b/src/lib/hooks/callout_handle.h @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -91,7 +92,8 @@ public: enum CalloutNextStep { NEXT_STEP_CONTINUE = 0, ///< continue normally NEXT_STEP_SKIP = 1, ///< skip the next processing step - NEXT_STEP_DROP = 2 ///< drop the packet + NEXT_STEP_DROP = 2, ///< drop the packet + NEXT_STEP_PARK = 3 ///< park the packet }; @@ -223,6 +225,11 @@ public: /// NEXT_STEP_DROP - tells the server to unconditionally drop the packet /// and do not process it further. /// + /// NEXT_STEP_PARK - tells the server to "park" the packet. The packet will + /// wait in the queue for being unparked, e.g. as a result + /// of completion of the asynchronous performed by the + /// hooks library operation. + /// /// This variable is interrogated by the server to see if the remaining /// callouts associated with the current hook should be bypassed. /// @@ -337,7 +344,11 @@ public: /// @return Name of the current hook or the empty string if none. std::string getHookName() const; + /// @brief Returns pointer to the parking lot for this hook point. + ParkingLotHandlePtr getParkingLotHandlePtr() const; + private: + /// @brief Check index /// /// Gets the current library index, throwing an exception if it is not set diff --git a/src/lib/hooks/hooks_manager.cc b/src/lib/hooks/hooks_manager.cc index b4702baff0..a2e11dd2ae 100644 --- a/src/lib/hooks/hooks_manager.cc +++ b/src/lib/hooks/hooks_manager.cc @@ -128,6 +128,7 @@ HooksManager::unloadLibrariesInternal() { // ease debugging. lm_collection_.reset(); callout_manager_.reset(); + ServerHooks::getServerHooks().getParkingLotsPtr()->clear(); } void HooksManager::unloadLibraries() { diff --git a/src/lib/hooks/hooks_manager.h b/src/lib/hooks/hooks_manager.h index 3eae0a95fb..5db4fe0db7 100644 --- a/src/lib/hooks/hooks_manager.h +++ b/src/lib/hooks/hooks_manager.h @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -234,6 +234,71 @@ public: /// @return A reference to the shared callout manager static boost::shared_ptr& getSharedCalloutManager(); + /// @brief Park an object (packet). + /// + /// The typical use case for parking an object is when the server needs to + /// suspend processing of a packet to perform an asynchronous operation, + /// before the response is sent to a client. In this case, the object type + /// is a pointer to the processed packet. Therefore, further in this + /// description we're going to refer to the parked objects as "parked + /// packets". However, any other object can be parked if neccessary. + /// + /// The following is the typical flow when packets are parked. The callouts + /// responsible for performing an asynchronous operation signal this need + /// to the server by returning the status @c NEXT_STEP_PARK, which instructs + /// the server to call this function. This function stops processing the + /// packet and puts it in, so called, parking lot. In order to be able to + /// resume the packet processing when instructed by the hooks library, the + /// parked packet is associated with the callback which, when called, will + /// resume packet processing. + /// + /// The hook library must increase a reference count on the parked object + /// by calling @c ParkingLotHandle::reference prior to returning the + /// @c NEXT_STEP_PARK status. This is important when multiple callouts + /// are installed on the same hook point and each of them schedules an + /// asynchronous operation. In this case, the packet must not be unparked + /// until all hook libraries call @c ParkingLotHandle::unpark to mark + /// that respective asynchronous operations are completed. + /// + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object packet to be parked. + /// @param unpark_callback callback invoked when the packet is unparked. + /// @tparam Type of the parked object. + template + static void park(const std::string& hook_name, T parked_object, + std::function unpark_callback) { + getHooksManager().parkInternal(hook_name, parked_object, unpark_callback); + } + + /// @brief Forces unparking the object (packet). + /// + /// This method unparks the object regardless of the reference counting + /// value. This is used in the situations when the callouts fail to unpark + /// the packet for some reason. + /// + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object parked object to be unparked. + /// @tparam Type of the parked object. + /// @return true if the specified object has been found, false otherwise. + template + static bool unpark(const std::string& hook_name, T parked_object) { + return (getHooksManager().unparkInternal(hook_name, parked_object)); + } + + /// @brief Increases reference counter for the parked object. + /// + /// Reference counter must be increased at least to 1 before the @c park() + /// method can be called. + /// + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object parked object for which reference counter should + /// be increased. + /// @tparam Type of the parked object. + template + static void reference(const std::string& hook_name, T parked_object) { + getHooksManager().referenceInternal(hook_name, parked_object); + } + private: /// @brief Constructor @@ -242,6 +307,43 @@ private: /// through the getHooksManager() static method. HooksManager(); + /// @brief Park an object (packet). + /// + /// @param hook_name Name of the hook point for which the packet is parked. + /// @param parked_object packet to be parked. + /// @param unpark_callback callback invoked when the packet is unparked. + /// @tparam Type of the parked object. + template + void parkInternal(const std::string& hook_name, T parked_object, + std::function unpark_callback) { + ServerHooks::getServerHooks(). + getParkingLotPtr(hook_name)->park(parked_object, unpark_callback); + } + + /// @brief Signals that the object (packet) should be unparked. + /// + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object parked object to be unparked. + /// @tparam Type of the parked object. + /// @return true if the specified object has been found, false otherwise. + template + bool unparkInternal(const std::string& hook_name, T parked_object) { + return (ServerHooks::getServerHooks(). + getParkingLotPtr(hook_name)->unpark(parked_object, true)); + } + + /// @brief Increases reference counter for the parked object. + /// + /// @param hook_name name of the hook point for which the packet is parked. + /// @param parked_object parked object for which reference counter should + /// be increased. + /// @tparam Type of the parked object. + template + void referenceInternal(const std::string& hook_name, T parked_object) { + ServerHooks::getServerHooks(). + getParkingLotPtr(hook_name)->reference(parked_object); + } + //@{ /// The following methods correspond to similarly-named static methods, /// but actually do the work on the singleton instance of the HooksManager. diff --git a/src/lib/hooks/parking_lots.h b/src/lib/hooks/parking_lots.h new file mode 100644 index 0000000000..38adeff89a --- /dev/null +++ b/src/lib/hooks/parking_lots.h @@ -0,0 +1,289 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PARKING_LOTS_H +#define PARKING_LOTS_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace isc { +namespace hooks { + +/// @brief Parking lot for objects, e.g. packets, for a hook point. +/// +/// Callouts may instruct the servers to "park" processed packets, i.e. suspend +/// their processing until explicitly unparked. This is useful in cases when +/// callouts need to perform asynchronous operations related to the packet +/// processing and the packet must not be further processed until the +/// asynchronous operations are completed. While the packet is parked, the +/// new packets can be processed, so the server remains responsive to the +/// new requests. +/// +/// Parking lots are created per hook point, so the callouts installed on the +/// particular hook point only have access to the parking lots dedicated to +/// them. +/// +/// The parking lot object supports 3 actions: "park", "unpark" and "reference". +/// In the typical case, the server parks the object and the callouts unpark and +/// reference the objects. Therefore, the @ref ParkingLot object is not passed +/// directly to the callouts. Instead, a ParkingLotHandle object is provided +/// to the callout, which only provides access to "unpark" and "reference" +/// operations. +/// +/// Referencing an object is performed by the callouts before the +/// @c CalloutHandle::NEXT_STEP_PARK is returned to the server and before the +/// server parks the object. Trying to park unreferenced object will result +/// in error. Referencing (reference counting) as an important part of the +/// parking mechanism, which allows multiple callouts, installed on the same +/// hook point, to perform asynchronous operations and guarantees that the +/// object remains parked until all those asynchronous operations complete. +/// Each such callout must call @c unpark() when it desires the object to +/// be unparked, but the object will be unparked when all callouts call this +/// function, i.e. when all callouts signal completion of their respective +/// asynchronous operations. +class ParkingLot { +public: + + /// @brief Parks an object. + /// + /// @param parked_object object to be parked, e.g. pointer to a packet. + /// @param unpark_callback callback function to be invoked when the object + /// is unparked. + /// @tparam Type of the parked object. + /// @throw InvalidOperation if the @c reference() wasn't called prior to + /// parking the object. + template + void park(T parked_object, std::function unpark_callback) { + auto it = find(parked_object); + if (it == parking_.end() || it->refcount_ <= 0) { + isc_throw(InvalidOperation, "unable to park an object because" + " reference count for this object hasn't been increased." + " Call ParkingLot::reference() first"); + } else { + it->update(parked_object, unpark_callback); + } + } + + /// @brief Increases reference counter for the parked object. + /// + /// This method is called by the callouts to increase a reference count + /// on the object to be parked. It must be called before the object is + /// actually parked. + /// + /// @param parked_object object which will be parked. + /// @tparam Type of the parked object. + template + void reference(T parked_object) { + auto it = find(parked_object); + if (it == parking_.end()) { + ParkingInfo parking_info(parked_object); + parking_.push_back(parking_info); + + } else { + ++it->refcount_; + } + } + + /// @brief Signals that the object should be unparked. + /// + /// If the specified object is parked in this parking lot, the reference + /// count is decreased as a result of this method. If the reference count + /// is 0, the object is unparked and the callback is invoked. Typically, the + /// callback points to a function which resumes processing of a packet. + /// + /// @param parked_object parked object to be unparked. + /// @param force boolean value indicating if the reference counting should + /// be ignored and the object should be unparked immediatelly. + /// @return false if the object couldn't be unparked because there is + /// no such object, true otherwise. + template + bool unpark(T parked_object, bool force = false) { + auto it = find(parked_object); + if (it != parking_.end()) { + if (force) { + it->refcount_ = 0; + + } else { + --it->refcount_; + } + + if (it->refcount_ <= 0) { + // Unpark the packet and invoke the callback. + std::function cb = it->unpark_callback_; + parking_.erase(it); + cb(); + } + + // Parked object found, so return true to indicate that the + // operation was successful. It doesn't neccessarily mean + // that the object was unparked, but at least the reference + // count was decreased. + return (true); + } + + // No such parked object. + return (false); + } + +private: + + /// @brief Holds information about parked object. + struct ParkingInfo { + boost::any parked_object_; ///< parked object + std::function unpark_callback_; ///< pointer to the callback + int refcount_; ///< current reference count + + /// @brief Constructor. + /// + /// @param parked_object object being parked. + /// @param callback pointer to the callback. + ParkingInfo(const boost::any& parked_object, + std::function callback = 0) + : parked_object_(parked_object), unpark_callback_(callback), + refcount_(1) { + } + + /// @brief Update parking information. + /// + /// @param parked_object parked object. + /// @param callback pointer to the callback. + void update(const boost::any& parked_object, + std::function callback) { + parked_object_ = parked_object; + unpark_callback_ = callback; + } + }; + + /// @brief Type of list of parked objects. + typedef std::list ParkingInfoList; + /// @brief Type of the iterator in the list of parked objects. + typedef ParkingInfoList::iterator ParkingInfoListIterator; + + /// @brief Container holding parked objects for this parking lot. + ParkingInfoList parking_; + + /// @brief Search for the information about the parked object. + /// + /// @param parked_object object for which the information should be found. + /// @tparam T parked object type. + /// @return Iterator pointing to the parked object, or @c parking_.end() if + /// no such object found. + template + ParkingInfoListIterator find(T parked_object) { + for (auto it = parking_.begin(); it != parking_.end(); ++it) { + if (boost::any_cast(it->parked_object_) == parked_object) { + return (it); + } + } + return (parking_.end()); + } +}; + +/// @brief Type of the pointer to the pakring lot. +typedef boost::shared_ptr ParkingLotPtr; + +/// @brief Provides a limited view to the @c ParkingLot. +/// +/// The handle is provided to the callouts which can reference and unpark +/// parked objects. The callouts should not park objects, therefore this +/// operation is not available. +class ParkingLotHandle { +public: + + /// @brief Constructor. + /// + /// @param parking_lot pointer to the parking lot for which the handle is + /// created. + ParkingLotHandle(const ParkingLotPtr& parking_lot) + : parking_lot_(parking_lot) { + } + + /// @brief Increases reference counter for the parked object. + /// + /// This method is called by the callouts to increase a reference count + /// on the object to be parked. It must be called before the object is + /// actually parked. + /// + /// @param parked_object object which will be parked. + /// @tparam Type of the parked object. + template + void reference(T parked_object) { + parking_lot_->reference(parked_object); + } + + /// @brief Signals that the object should be unparked. + /// + /// If the specified object is parked in this parking lot, the reference + /// count is decreased as a result of this method. If the reference count + /// is 0, the object is unparked and the callback is invoked. Typically, the + /// callback points to a function which resumes processing of a packet. + /// + /// @param parked_object parked object to be unparked. + /// @return false if the object couldn't be unparked because there is + /// no such object, true otherwise. + template + bool unpark(T parked_object) { + return (parking_lot_->unpark(parked_object)); + } + +private: + + /// @brief Parking lot to which this handle points. + ParkingLotPtr parking_lot_; + +}; + +/// @brief Pointer to the parking lot handle. +typedef boost::shared_ptr ParkingLotHandlePtr; + +/// @brief Collection of parking lots for various hook points. +class ParkingLots { +public: + + /// @brief Removes all parked objects. + /// + /// It doesn't invoke callbacks associated with the removed objects. + void clear() { + parking_lots_.clear(); + } + + /// @brief Returns pointer to the parking lot for a hook points. + /// + /// If the parking lot for the specified hook point doesn't exist, it is + /// created. + /// + /// @param hook_index index of the hook point with which the parking + /// lot is associated. + /// @return Pointer to the parking lot. + ParkingLotPtr getParkingLotPtr(const int hook_index) { + if (parking_lots_.count(hook_index) == 0) { + parking_lots_[hook_index] = boost::make_shared(); + } + return (parking_lots_[hook_index]); + } + +private: + + /// @brief Container holding parking lots for various hook points. + std::map parking_lots_; + +}; + +/// @brief Type of the pointer to the parking lots. +typedef boost::shared_ptr ParkingLotsPtr; + +} // end of namespace hooks +} // end of namespace isc + +#endif diff --git a/src/lib/hooks/server_hooks.cc b/src/lib/hooks/server_hooks.cc index d7cfcdf901..c669ae93f0 100644 --- a/src/lib/hooks/server_hooks.cc +++ b/src/lib/hooks/server_hooks.cc @@ -82,6 +82,7 @@ ServerHooks::initialize() { // Clear out the name->index and index->name maps. hooks_.clear(); inverse_hooks_.clear(); + parking_lots_.reset(new ParkingLots()); // Register the pre-defined hooks. int create = registerHook("context_create"); @@ -173,6 +174,21 @@ ServerHooks::getServerHooksPtr() { return (hooks); } +ParkingLotsPtr +ServerHooks::getParkingLotsPtr() const { + return (parking_lots_); +} + +ParkingLotPtr +ServerHooks::getParkingLotPtr(const int hook_index) { + return (parking_lots_->getParkingLotPtr(hook_index)); +} + +ParkingLotPtr +ServerHooks::getParkingLotPtr(const std::string& hook_name) { + return (parking_lots_->getParkingLotPtr(getServerHooks().getIndex(hook_name))); +} + std::string ServerHooks::commandToHookName(const std::string& command_name) { // Prefix the command name with a dollar sign. diff --git a/src/lib/hooks/server_hooks.h b/src/lib/hooks/server_hooks.h index 9fc60bcdce..2144dd059a 100644 --- a/src/lib/hooks/server_hooks.h +++ b/src/lib/hooks/server_hooks.h @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -8,6 +8,7 @@ #define SERVER_HOOKS_H #include +#include #include #include @@ -152,6 +153,23 @@ public: /// @return Pointer to the global ServerHooks object. static ServerHooksPtr getServerHooksPtr(); + /// @brief Returns pointer to all parking lots. + ParkingLotsPtr getParkingLotsPtr() const; + + /// @brief Returns pointer to the ParkingLot for the specified hook index. + /// + /// @param hook_index index of the hook point for which the parking lot + /// should be returned. + /// @return Pointer to the ParkingLot object. + ParkingLotPtr getParkingLotPtr(const int hook_index); + + /// @brief Returns pointer to the ParkingLot for the specified hook name. + /// + /// @param hook_name name of the hook point for which the parking lot + /// should be returned. + /// @return Pointer to the ParkingLot object. + ParkingLotPtr getParkingLotPtr(const std::string& hook_name); + /// @brief Generates hook point name for the given control command name. /// /// This function is called to generate the name of the hook point @@ -216,6 +234,8 @@ private: /// simpler than using a multi-indexed container.) HookCollection hooks_; ///< Hook name/index collection InverseHookCollection inverse_hooks_; ///< Hook index/name collection + + ParkingLotsPtr parking_lots_; }; } // namespace util diff --git a/src/lib/hooks/tests/Makefile.am b/src/lib/hooks/tests/Makefile.am index 70d22497a6..1c1b830080 100644 --- a/src/lib/hooks/tests/Makefile.am +++ b/src/lib/hooks/tests/Makefile.am @@ -40,7 +40,7 @@ if HAVE_GTEST # ignored for unit tests built here. noinst_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la \ - liblecl.la libucl.la libfcl.la libpcl.la + liblecl.la libucl.la libfcl.la libpcl.la libacl.la # -rpath /nowhere is a hack to trigger libtool to not create a # convenience archive, resulting in shared modules @@ -102,6 +102,12 @@ libpcl_la_CPPFLAGS = $(AM_CPPFLAGS) libpcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere libpcl_la_LDFLAGS += $(top_builddir)/src/lib/util/libkea-util.la +# The async callout library - parks object for asynchronous task +libacl_la_SOURCES = async_callout_library.cc +libacl_la_CXXFLAGS = $(AM_CXXFLAGS) +libacl_la_CPPFLAGS = $(AM_CPPFLAGS) +libacl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere + TESTS += run_unittests run_unittests_SOURCES = run_unittests.cc run_unittests_SOURCES += callout_handle_unittest.cc @@ -111,6 +117,7 @@ run_unittests_SOURCES += handles_unittest.cc run_unittests_SOURCES += hooks_manager_unittest.cc run_unittests_SOURCES += library_manager_collection_unittest.cc run_unittests_SOURCES += library_manager_unittest.cc +run_unittests_SOURCES += parking_lots_unittest.cc run_unittests_SOURCES += server_hooks_unittest.cc nodist_run_unittests_SOURCES = marker_file.h diff --git a/src/lib/hooks/tests/async_callout_library.cc b/src/lib/hooks/tests/async_callout_library.cc new file mode 100644 index 0000000000..ffe0f683c0 --- /dev/null +++ b/src/lib/hooks/tests/async_callout_library.cc @@ -0,0 +1,153 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Async callout library +/// +/// This is source of a test library for testing a "parking" feature, i.e. +/// the callouts can schedule asynchronous operation and indicate that the +/// packet should be parked until the asynchronous operation completes and +/// the hooks library indicates that packet processing should be resumed. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace isc::hooks; +using namespace isc::log; +using namespace std; + +namespace { + +/// @brief Logger used by the library. +isc::log::Logger logger("acl"); + +/// @brief Log messages. +const char* log_messages[] = { + "ACL_LOAD_START", "async callout load %1", + "ACL_LOAD_END", "async callout load end", + "ACL_LOAD_END", "duplicate of async callout load end", + NULL +}; + +/// @brief Initializer for log messages. +const MessageInitializer message_initializer(log_messages); + +/// @brief Simple callback which unparks parked object. +/// +/// @param parking_lot parking lot where the object is parked. +/// @param parked_object parked object. +void unpark(ParkingLotHandlePtr parking_lot, const std::string& parked_object) { + parking_lot->unpark(parked_object); +} + +} // end of anonymous namespace + +extern "C" { + +/// @brief Callout scheduling object parking and providing function to unpark +/// it. +/// +/// This callout is crafted to test the following scenario. The callout returns +/// status "park" to indicate that the packet should be parked. The callout +/// performs asynchronous operation and indicates that the packet should be +/// unparked when this operation completes. Unparing the packet triggers a +/// function associated with the parked packet, e.g. a function which continues +/// processing of this packet. +/// +/// This test callout "parks" a string object instead of a packet. It assumes +/// that there might be multiple callouts installed on this hook point, which +/// all trigger asynchronous operation. The object must be unparked when the +/// last asynchronous operation completes. Therefore, it calls the @c reference +/// function on the parking lot object to increase the reference count. The +/// object remains parked as long as the reference counter is greater than +/// 0. +/// +/// The callout returns 1 or more pointers to the functions which should be +/// called by the unit tests to simulate completion of the asynchronous tasks. +/// When the test calls those functions, @c unpark function is called, which +/// decreases reference count on the parked object, or actually unparks the +/// object when the reference count reaches 0. +/// +/// @param handle Reference to callout handle used to set/get arguments. +int +hookpt_one(CalloutHandle& handle) { + // Using a string as "parked" object. + std::string parked_object; + handle.getArgument("parked_object", parked_object); + + // Retrieve the parking lot handle for this hook point. It allows for + // increasing a reference count on the parked object and also for + // scheduling packet unparking. + ParkingLotHandlePtr parking_lot = handle.getParkingLotHandlePtr(); + + // Increase the reference count to indicate that this callout needs the + // object to remain parked until the asynchronous operation completes. + // Otherwise, other callouts could potentially call unpark and cause the + // packet processing to continue before the asynchronous operation + // completes. + parking_lot->reference(parked_object); + + // Create pointer to the function that the test should call to simulate + // completion of the asynchronous operation scheduled by this callout. + std::function unpark_trigger_func = + std::bind(unpark, parking_lot, parked_object); + + // Every callout (if multiple callouts installed on this hook point) should + // return the function pointer under unique name. The base name is + // "unpark_trigger" and the callouts append consecutive numbers to this + // base name, e.g. "unpark_trigger1", "unpark_trigger2" etc. + + std::string fun_name; + std::vector args = handle.getArgumentNames(); + unsigned i = 1; + do { + std::ostringstream candidate_name; + candidate_name << "unpark_trigger" << i; + if (std::find(args.begin(), args.end(), candidate_name.str()) == + args.end()) { + fun_name = candidate_name.str(); + + } else { + ++i; + } + } while (fun_name.empty()); + + handle.setArgument(fun_name, unpark_trigger_func); + + handle.setStatus(CalloutHandle::NEXT_STEP_PARK); + + return (0); +} + +// Framework functions. + +int +version() { + return (KEA_HOOKS_VERSION); +} + +// load() initializes the user library if the main image was statically linked. +int +load(isc::hooks::LibraryHandle&) { +#ifdef USE_STATIC_LINK + hooksStaticLinkInit(); +#endif + LOG_INFO(logger, "ACL_LOAD_START").arg("argument"); + LOG_INFO(logger, "ACL_LOAD_END"); + return (0); +} + +} + diff --git a/src/lib/hooks/tests/handles_unittest.cc b/src/lib/hooks/tests/handles_unittest.cc index de742e5ba3..2563a354c9 100644 --- a/src/lib/hooks/tests/handles_unittest.cc +++ b/src/lib/hooks/tests/handles_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -748,6 +748,7 @@ calloutPrintSkip(CalloutHandle& handle) { static const std::string YES("Y"); static const std::string NO("N"); static const std::string DROP("D"); + static const std::string PARK("P"); switch (handle.getStatus()) { case CalloutHandle::NEXT_STEP_CONTINUE: @@ -759,6 +760,9 @@ calloutPrintSkip(CalloutHandle& handle) { case CalloutHandle::NEXT_STEP_DROP: HandlesTest::common_string_ += DROP; // drop break; + case CalloutHandle::NEXT_STEP_PARK: + HandlesTest::common_string_ += PARK; // park + break; } return (0); } @@ -891,6 +895,11 @@ calloutSetArgumentDrop(CalloutHandle& handle) { return (calloutSetArgumentCommon(handle, "D")); } +int +calloutSetArgumentPark(CalloutHandle& handle) { + return (calloutSetArgumentCommon(handle, "P")); +} + // ... and a callout to just copy the argument to the "common_string_" variable // but otherwise not alter it. @@ -918,7 +927,9 @@ TEST_F(HandlesTest, CheckModifiedArgument) { getCalloutManager()->setLibraryIndex(2); getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip); getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark); getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip); + getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark); // Create the argument with an initial empty string value. Then call the // sequence of callouts above. @@ -933,7 +944,7 @@ TEST_F(HandlesTest, CheckModifiedArgument) { EXPECT_EQ(std::string("SCC" "SD"), common_string_); callout_handle.getArgument(MODIFIED_ARG, modified_arg); - EXPECT_EQ(std::string("SCC" "SDDC" "SCS"), modified_arg); + EXPECT_EQ(std::string("SCC" "SDDC" "SCPSP"), modified_arg); } // Test that the CalloutHandle provides the name of the hook to which the diff --git a/src/lib/hooks/tests/hooks_manager_unittest.cc b/src/lib/hooks/tests/hooks_manager_unittest.cc index 947263e645..61c5199b6b 100644 --- a/src/lib/hooks/tests/hooks_manager_unittest.cc +++ b/src/lib/hooks/tests/hooks_manager_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -753,5 +753,159 @@ TEST_F(HooksManagerTest, LibraryParameters) { EXPECT_NO_THROW(HooksManager::unloadLibraries()); } +// This test verifies that an object can be parked in two different +// callouts and that it is unparked when the last callout calls +// unpark function. +TEST_F(HooksManagerTest, Parking) { + // Load the same library twice. Both installed callouts will trigger + // asynchronous operation. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + + // Load the libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // We could be parked any object. Typically it will be a pointer to the + // packet. In this case, however, it is simpler to just use a string. + std::string parked_object = "foo"; + handle->setArgument("parked_object", parked_object); + + // Call both installed callouts. + HooksManager::callCallouts(hookpt_one_index_, *handle); + + // This boolean value will be set to true when the packet gets unparked. + bool unparked = false; + + // The callouts instruct us to park the object. We associated the callback + // function with the parked object, which sets "unparked" flag to true. We + // can later test the value of this flag to verify when exactly the packet + // got unparked. + ASSERT_NO_THROW( + HooksManager::park("hookpt_one", "foo", + [this, &unparked] { + unparked = true; + }) + ); + + // We have two callouts which should have returned pointers to the + // functions which we can call to siumulate completion of asynchronous + // tasks. + std::function unpark_trigger_func1; + handle->getArgument("unpark_trigger1", unpark_trigger_func1); + // Call the first function. It should cause the hook library to call the + // "unpark" function. However, the object should not be unparked yet, + // because the other callout hasn't completed its scheduled asynchronous + // operation (keeps a reference on the parked object). + unpark_trigger_func1(); + EXPECT_FALSE(unparked); + + // Call the second function. This should decrease the reference count to + // 0 and the packet should be unparked. + std::function unpark_trigger_func2; + handle->getArgument("unpark_trigger2", unpark_trigger_func2); + unpark_trigger_func2(); + EXPECT_TRUE(unparked); + + // Try unloading the libraries. + EXPECT_NO_THROW(HooksManager::unloadLibraries()); +} + +// This test verifies that the server can also unpark the packet. +TEST_F(HooksManagerTest, ServerUnpark) { + // Load the same library twice. Both installed callouts will trigger + // asynchronous operation. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + // Load libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // We could be parked any object. Typically it will be a pointer to the + // packet. In this case, however, it is simpler to just use a string. + std::string parked_object = "foo"; + handle->setArgument("parked_object", parked_object); + + // Call installed callout. + HooksManager::callCallouts(hookpt_one_index_, *handle); + + // This boolean value will be set to true when the packet gets unparked. + bool unparked = false; + + // It should be possible for the server to increase reference counter. + ASSERT_NO_THROW(HooksManager::reference("hookpt_one", "foo")); + + // The callouts instruct us to park the object. We associated the callback + // function with the parked object, which sets "unparked" flag to true. We + // can later test the value of this flag to verify when exactly the packet + // got unparked. + HooksManager::park("hookpt_one", "foo", + [this, &unparked] { + unparked = true; + }); + + // Server can force unparking the object. + EXPECT_TRUE(HooksManager::unpark("hookpt_one", "foo")); + + EXPECT_TRUE(unparked); + + // Try unloading the libraries. + EXPECT_NO_THROW(HooksManager::unloadLibraries()); +} + +// This test verifies that parked objects are removed when libraries are +// unloaded. +TEST_F(HooksManagerTest, UnloadBeforeUnpark) { + // Load the same library twice. Both installed callouts will trigger + // asynchronous operation. + HookLibsCollection library_names; + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY), + data::ConstElementPtr())); + // Load libraries. + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + CalloutHandlePtr handle = HooksManager::createCalloutHandle(); + + // We could be parked any object. Typically it will be a pointer to the + // packet. In this case, however, it is simpler to just use a string. + std::string parked_object = "foo"; + handle->setArgument("parked_object", parked_object); + + // Call installed callout. + HooksManager::callCallouts(hookpt_one_index_, *handle); + + // This boolean value will be set to true when the packet gets unparked. + bool unparked = false; + + // The callouts instruct us to park the object. We associated the callback + // function with the parked object, which sets "unparked" flag to true. We + // can later test the value of this flag to verify when exactly the packet + // got unparked. + HooksManager::park("hookpt_one", "foo", + [this, &unparked] { + unparked = true; + }); + + // Try reloading the libraries. + EXPECT_NO_THROW(HooksManager::unloadLibraries()); + EXPECT_TRUE(HooksManager::loadLibraries(library_names)); + + // Parked object should have been removed. + EXPECT_FALSE(HooksManager::unpark("hookpt_one", "foo")); + + // Callback should not be called. + EXPECT_FALSE(unparked); +} + } // Anonymous namespace diff --git a/src/lib/hooks/tests/parking_lots_unittest.cc b/src/lib/hooks/tests/parking_lots_unittest.cc new file mode 100644 index 0000000000..51acc0473c --- /dev/null +++ b/src/lib/hooks/tests/parking_lots_unittest.cc @@ -0,0 +1,84 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include + +using namespace isc; +using namespace isc::hooks; + +namespace { + +// Test that it is possible to create and retrieve parking lots for +// specified hook points. +TEST(ParkingLotsTest, createGetParkingLot) { + ParkingLots parking_lots; + + ParkingLotPtr parking_lot0 = parking_lots.getParkingLotPtr(1); + ParkingLotPtr parking_lot1 = parking_lots.getParkingLotPtr(2); + ParkingLotPtr parking_lot2 = parking_lots.getParkingLotPtr(1); + + ASSERT_TRUE(parking_lot0); + ASSERT_TRUE(parking_lot1); + ASSERT_TRUE(parking_lot2); + + EXPECT_FALSE(parking_lot0 == parking_lot1); + EXPECT_TRUE(parking_lot0 == parking_lot2); + + ASSERT_NO_THROW(parking_lots.clear()); + + ParkingLotPtr parking_lot3 = parking_lots.getParkingLotPtr(1); + ASSERT_TRUE(parking_lot0); + + EXPECT_FALSE(parking_lot3 == parking_lot0); +} + +// Test that object can't be parked if it hasn't been referenced on the +// parking lot. +TEST(ParkingLotTest, parkWithoutReferencing) { + ParkingLot parking_lot; + std::string parked_object = "foo"; + EXPECT_THROW(parking_lot.park(parked_object, [this] { + }), InvalidOperation); +} + +// Test that object can be parked and then unparked. +TEST(ParkingLotTest, unpark) { + ParkingLotPtr parking_lot = boost::make_shared(); + ParkingLotHandlePtr parking_lot_handle = + boost::make_shared(parking_lot); + + std::string parked_object = "foo"; + + // Reference the parked object twice because we're going to test that + // reference counting works fine. + ASSERT_NO_THROW(parking_lot_handle->reference(parked_object)); + ASSERT_NO_THROW(parking_lot_handle->reference(parked_object)); + + // This flag will indicate if the callback has been called. + bool unparked = false; + ASSERT_NO_THROW(parking_lot->park(parked_object, [this, &unparked] { + unparked = true; + })); + + // Try to unpark the object. It should decrease the reference count, but not + // unpark the packet yet. + EXPECT_TRUE(parking_lot_handle->unpark(parked_object)); + EXPECT_FALSE(unparked); + + // Try to unpark the object. This time it should be successful, because the + // reference count goes to 0. + EXPECT_TRUE(parking_lot_handle->unpark(parked_object)); + EXPECT_TRUE(unparked); + + // Calling unpark again should return false to indicate that the object is + // not parked. + EXPECT_FALSE(parking_lot_handle->unpark(parked_object)); +} + +} diff --git a/src/lib/hooks/tests/server_hooks_unittest.cc b/src/lib/hooks/tests/server_hooks_unittest.cc index 6dd6e38d0f..6ff604bf0c 100644 --- a/src/lib/hooks/tests/server_hooks_unittest.cc +++ b/src/lib/hooks/tests/server_hooks_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -217,4 +217,14 @@ TEST(ServerHooksTest, HookToCommandName) { EXPECT_TRUE(ServerHooks::hookToCommandName("abc").empty()); } +TEST(ServerHooksTest, getParkingLots) { + ServerHooks& hooks = ServerHooks::getServerHooks(); + hooks.reset(); + int alpha_hook = hooks.registerHook("alpha"); + + ASSERT_TRUE(hooks.getParkingLotsPtr()); + ASSERT_TRUE(hooks.getParkingLotPtr(alpha_hook)); + ASSERT_TRUE(hooks.getParkingLotPtr("alpha")); +} + } // Anonymous namespace diff --git a/src/lib/hooks/tests/test_libraries.h.in b/src/lib/hooks/tests/test_libraries.h.in index a66ce60c3b..6562acae20 100644 --- a/src/lib/hooks/tests/test_libraries.h.in +++ b/src/lib/hooks/tests/test_libraries.h.in @@ -49,6 +49,10 @@ static const char* UNLOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libucl.so"; // Library where parameters are checked. static const char* CALLOUT_PARAMS_LIBRARY = "@abs_builddir@/.libs/libpcl.so"; +// Library which tests objects parking. +static const char* ASYNC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libacl.so"; + + } // anonymous namespace