static_cast<int64_t>(1));
}
+ bool packet_park = false;
+
if (ctx && HooksManager::calloutsPresent(Hooks.hook_index_leases4_committed_)) {
CalloutHandlePtr callout_handle = getCalloutHandle(query);
// 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
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
return (names);
}
+ParkingLotHandlePtr
+CalloutHandle::getParkingLotHandlePtr() const {
+ return (boost::make_shared<ParkingLotHandle>(server_hooks_.getParkingLotPtr(manager_->getHookIndex())));
+}
+
// Return the library handle allowing the callout to access the CalloutManager
// registration/deregistration functions.
-// 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
#include <exceptions/exceptions.h>
#include <hooks/library_handle.h>
+#include <hooks/parking_lots.h>
#include <boost/any.hpp>
#include <boost/shared_ptr.hpp>
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
};
/// 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.
///
/// @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
// ease debugging.
lm_collection_.reset();
callout_manager_.reset();
+ ServerHooks::getServerHooks().getParkingLotsPtr()->clear();
}
void HooksManager::unloadLibraries() {
-// 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
/// @return A reference to the shared callout manager
static boost::shared_ptr<CalloutManager>& 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<typename T>
+ static void park(const std::string& hook_name, T parked_object,
+ std::function<void()> 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<typename T>
+ 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<typename T>
+ static void reference(const std::string& hook_name, T parked_object) {
+ getHooksManager().referenceInternal(hook_name, parked_object);
+ }
+
private:
/// @brief Constructor
/// 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<typename T>
+ void parkInternal(const std::string& hook_name, T parked_object,
+ std::function<void()> 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<typename T>
+ 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<typename T>
+ 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.
--- /dev/null
+// 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 <exceptions/exceptions.h>
+#include <boost/any.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <list>
+#include <map>
+
+#include <iostream>
+
+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<typename T>
+ void park(T parked_object, std::function<void()> 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<typename T>
+ 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<typename T>
+ 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<void()> 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<void()> 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<void()> 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<void()> callback) {
+ parked_object_ = parked_object;
+ unpark_callback_ = callback;
+ }
+ };
+
+ /// @brief Type of list of parked objects.
+ typedef std::list<ParkingInfo> 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<typename T>
+ ParkingInfoListIterator find(T parked_object) {
+ for (auto it = parking_.begin(); it != parking_.end(); ++it) {
+ if (boost::any_cast<T>(it->parked_object_) == parked_object) {
+ return (it);
+ }
+ }
+ return (parking_.end());
+ }
+};
+
+/// @brief Type of the pointer to the pakring lot.
+typedef boost::shared_ptr<ParkingLot> 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<typename T>
+ 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<typename T>
+ 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<ParkingLotHandle> 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<ParkingLot>();
+ }
+ return (parking_lots_[hook_index]);
+ }
+
+private:
+
+ /// @brief Container holding parking lots for various hook points.
+ std::map<int, ParkingLotPtr> parking_lots_;
+
+};
+
+/// @brief Type of the pointer to the parking lots.
+typedef boost::shared_ptr<ParkingLots> ParkingLotsPtr;
+
+} // end of namespace hooks
+} // end of namespace isc
+
+#endif
// 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");
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.
-// 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
#define SERVER_HOOKS_H
#include <exceptions/exceptions.h>
+#include <hooks/parking_lots.h>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
/// @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
/// 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
# 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
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
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
--- /dev/null
+// 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 <config.h>
+#include <hooks/hooks.h>
+#include <hooks/parking_lots.h>
+#include <log/logger.h>
+#include <log/macros.h>
+#include <log/message_initializer.h>
+#include <algorithm>
+#include <functional>
+#include <sstream>
+#include <string>
+#include <vector>
+
+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<void()> 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<std::string> 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);
+}
+
+}
+
-// 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
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:
case CalloutHandle::NEXT_STEP_DROP:
HandlesTest::common_string_ += DROP; // drop
break;
+ case CalloutHandle::NEXT_STEP_PARK:
+ HandlesTest::common_string_ += PARK; // park
+ break;
}
return (0);
}
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.
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.
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
-// 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
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<std::string>("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<void()> 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<void()> 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<std::string>("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<std::string>("hookpt_one", "foo",
+ [this, &unparked] {
+ unparked = true;
+ });
+
+ // Server can force unparking the object.
+ EXPECT_TRUE(HooksManager::unpark<std::string>("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<std::string>("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<std::string>("hookpt_one", "foo"));
+
+ // Callback should not be called.
+ EXPECT_FALSE(unparked);
+}
+
} // Anonymous namespace
--- /dev/null
+// 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 <exceptions/exceptions.h>
+#include <hooks/parking_lots.h>
+#include <gtest/gtest.h>
+#include <string>
+
+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<ParkingLot>();
+ ParkingLotHandlePtr parking_lot_handle =
+ boost::make_shared<ParkingLotHandle>(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));
+}
+
+}
-// 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
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
// 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