]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5457] Implemented PoC for packets parking.
authorMarcin Siodelski <marcin@isc.org>
Fri, 19 Jan 2018 16:15:23 +0000 (17:15 +0100)
committerMarcin Siodelski <marcin@isc.org>
Mon, 22 Jan 2018 09:47:11 +0000 (10:47 +0100)
16 files changed:
src/bin/dhcp4/dhcp4_srv.cc
src/lib/hooks/Makefile.am
src/lib/hooks/callout_handle.cc
src/lib/hooks/callout_handle.h
src/lib/hooks/hooks_manager.cc
src/lib/hooks/hooks_manager.h
src/lib/hooks/parking_lots.h [new file with mode: 0644]
src/lib/hooks/server_hooks.cc
src/lib/hooks/server_hooks.h
src/lib/hooks/tests/Makefile.am
src/lib/hooks/tests/async_callout_library.cc [new file with mode: 0644]
src/lib/hooks/tests/handles_unittest.cc
src/lib/hooks/tests/hooks_manager_unittest.cc
src/lib/hooks/tests/parking_lots_unittest.cc [new file with mode: 0644]
src/lib/hooks/tests/server_hooks_unittest.cc
src/lib/hooks/tests/test_libraries.h.in

index ba129663b08e6ab71d7bbc591e16aef35e09d116..05acc1704830c9ed9c45849b394f1a87d669aaca 100644 (file)
@@ -1100,6 +1100,8 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
                                                   static_cast<int64_t>(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
index 1a3ba6185731b4a1b5dd051925c2ec6d40881373..b13a9a4c443329ebd5fd24c47417ab1f7ba4ee99 100644 (file)
@@ -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
 
index 34c2c4b1fce0bed3a435ffc5403d89eda1c15c15..88d58940cf74b2c65f23fe969800df6be907d647 100644 (file)
@@ -72,6 +72,11 @@ CalloutHandle::getArgumentNames() const {
     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.
 
index 637f4dbaede9bec105f3e605da59f448ab0bd27b..45d36fc89328b7d6ade5c55527878b699d1ef369 100644 (file)
@@ -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 <exceptions/exceptions.h>
 #include <hooks/library_handle.h>
+#include <hooks/parking_lots.h>
 
 #include <boost/any.hpp>
 #include <boost/shared_ptr.hpp>
@@ -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
index b4702baff028a1b58ebc7297cd9768f0a3ac9dce..a2e11dd2aec5e02790149af331450a981b2179b3 100644 (file)
@@ -128,6 +128,7 @@ HooksManager::unloadLibrariesInternal() {
     // ease debugging.
     lm_collection_.reset();
     callout_manager_.reset();
+    ServerHooks::getServerHooks().getParkingLotsPtr()->clear();
 }
 
 void HooksManager::unloadLibraries() {
index 3eae0a95fb01f6638bede14233778976a98cc8c1..5db4fe0db7195b4d15d1473af0ba724063e7e51c 100644 (file)
@@ -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<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
@@ -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<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.
diff --git a/src/lib/hooks/parking_lots.h b/src/lib/hooks/parking_lots.h
new file mode 100644 (file)
index 0000000..38adeff
--- /dev/null
@@ -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 <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
index d7cfcdf901aae7a17fffc887d9b5fdbb3ecd4fa5..c669ae93f0b000374f578b0107f4a948a7e786b8 100644 (file)
@@ -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.
index 9fc60bcdcea8f51954bc499c5c4dc65c9651eec4..2144dd059acb578ea35ed816396eca56ccf013e7 100644 (file)
@@ -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 <exceptions/exceptions.h>
+#include <hooks/parking_lots.h>
 
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
@@ -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
index 70d22497a691fa966aa965052533565435402e89..1c1b83008002f48b3a8f89483e30fd8bad8ee565 100644 (file)
@@ -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 (file)
index 0000000..ffe0f68
--- /dev/null
@@ -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 <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);
+}
+
+}
+
index de742e5ba3a7b372205db8da4bbeadb3d2fa98d6..2563a354c9e43433e1ecd7ad77b208048bda9765 100644 (file)
@@ -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
index 947263e6459b864221fe63241498e5f9f8ca479e..61c5199b6bb2df9c9d5e90e5de06f9858cb76e6b 100644 (file)
@@ -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<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
diff --git a/src/lib/hooks/tests/parking_lots_unittest.cc b/src/lib/hooks/tests/parking_lots_unittest.cc
new file mode 100644 (file)
index 0000000..51acc04
--- /dev/null
@@ -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 <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));
+}
+
+}
index 6dd6e38d0fe68f8fc2eb00c8dd71713d516adbe6..6ff604bf0c1396cbae3c38d0eb2a9719a181b5e4 100644 (file)
@@ -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
index a66ce60c3b231455ea72e7891ecd16560f784cc3..6562acae205b8e06655454ad9a5ad643a0a32305 100644 (file)
@@ -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