Commit 966cc24a authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac5329'

parents 1b27ce48 4f8118c5
// Copyright (C) 2013-2015,2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2017 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
......@@ -104,6 +104,27 @@ CalloutManager::calloutsPresent(int hook_index) const {
return (!hook_vector_[hook_index].empty());
}
bool
CalloutManager::commandHandlersPresent(const std::string& command_name) const {
// Check if the hook point for the specified command exists.
int index = ServerHooks::getServerHooks().findIndex(
ServerHooks::commandToHookName(command_name));
if (index >= 0) {
// The hook point exits but it is possible that there are no
// callouts/command handlers. This is possible if there was a
// hook library supporting this command attached, but it was
// later unloaded. The hook points are not deregistered in
// this case. Only callouts are deregistered.
// Let's check if callouts are present for this hook point.
return (calloutsPresent(index));
}
// Hook point not created, so we don't support this command in
// any of the hooks libraries.
return (false);
}
// Call all the callouts for a given hook.
void
......@@ -191,6 +212,20 @@ CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
}
}
void
CalloutManager::callCommandHandlers(const std::string& command_name,
CalloutHandle& callout_handle) {
// Get the index of the hook point for the specified command.
// This will throw an exception if the hook point doesn't exist.
// The caller should check if the hook point exists by calling
// commandHandlersPresent.
int index = ServerHooks::getServerHooks().getIndex(
ServerHooks::commandToHookName(command_name));
// Call the handlers for this command.
callCallouts(index, callout_handle);
}
// Deregister a callout registered by the current library on a particular hook.
bool
......@@ -272,5 +307,21 @@ CalloutManager::deregisterAllCallouts(const std::string& name) {
return (removed);
}
void
CalloutManager::registerCommandHook(const std::string& command_name) {
ServerHooks& hooks = ServerHooks::getServerHooks();
int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
if (hook_index < 0) {
// Hook for this command doesn't exist. Let's create one.
hooks.registerHook(ServerHooks::commandToHookName(command_name));
// Callout Manager's vector of hooks have to be resized to hold the
// information about callouts for this new hook point. This should
// add new element at the end of the hook_vector_. The index of this
// element will match the index of the hook point in the ServerHooks
// because ServerHooks allocates indexes incrementally.
hook_vector_.resize(server_hooks_.getCount());
}
}
} // namespace util
} // namespace isc
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2017 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
......@@ -96,6 +96,36 @@ public:
/// - INT_MAX: used for server-registered callouts called after
/// user-registered callouts.
///
/// Since Kea 1.3.0 release hook libraries can register callouts as control
/// command handlers. Such handlers are associated with dynamically created
/// hook points which names are created after command names. For example,
/// if a command name is 'foo-bar', the name of the hook point to which
/// callouts/command handlers are registered is '$foo_bar'. Prefixing the
/// hook point name with the dollar sign eliminates potential conflicts
/// between hook points dedicated to commands handling and other (fixed)
/// hook points.
///
/// Prefixing hook names for command handlers with a dollar sign precludes
/// auto registration of command handlers, i.e. hooks framework is unable
/// to match hook points with names of functions implementing command
/// handlers, because the dollar sign is not legal in C++ function names.
/// This is intended because we want hook libraries to explicitly register
/// commands handlers for supported commands and not rely on Kea to register
/// hook points for them. Should we find use cases for auto registration of
/// command handlers, we may modify the
/// @ref ServerHooks::commandToHookName to use an encoding of hook
/// point names for command handlers that would only contain characters
/// allowed in function names.
///
/// The @ref CalloutManager::registerCommandHook has been added to allow for
/// dynamically creating hook points for which command handlers are registered.
/// This method is called from the @ref LibraryHandle::registerCommandHandler
/// as a result of registering the command handlers by the hook library in
/// its @c load() function. If the hook point for the given command already
/// exists, this function doesn't do anything. The
/// @ref LibraryHandle::registerCommandHandler can install callouts on this
/// hook point.
///
/// Note that the callout functions do not access the CalloutManager: instead,
/// they use a LibraryHandle object. This contains an internal pointer to
/// the CalloutManager, but provides a restricted interface. In that way,
......@@ -184,6 +214,17 @@ public:
/// @throw NoSuchHook Given index does not correspond to a valid hook.
bool calloutsPresent(int hook_index) const;
/// @brief Checks if control command handlers are present for the
/// specified command.
///
/// @param command_name Command name for which handlers' presence should
/// be checked.
///
/// @return true if there is a hook point associated with the specified
/// command and callouts/command handlers are installed for this hook
/// point, false otherwise.
bool commandHandlersPresent(const std::string& command_name) const;
/// @brief Calls the callouts for a given hook
///
/// Iterates through the libray handles and calls the callouts associated
......@@ -197,6 +238,34 @@ public:
/// current object being processed.
void callCallouts(int hook_index, CalloutHandle& callout_handle);
/// @brief Calls the callouts/command handlers for a given command name.
///
/// Iterates through the library handles and calls the command handlers
/// associated with the given command. It expects that the hook point
/// for this command exists (with a name being a command_name prefixed
/// with a dollar sign and with hyphens replaced with underscores).
///
/// @param command_name Command name for which handlers should be called.
/// @param callout_handle Reference to the CalloutHandle object for the
/// current object being processed.
///
/// @throw NoSuchHook if the hook point for the specified command does
/// not exist.
void callCommandHandlers(const std::string& command_name,
CalloutHandle& callout_handle);
/// @brief Registers a hook point for the specified command name.
///
/// If the hook point for such command already exists, this function
/// doesn't do anything. The registered hook point name is created
/// after command_name by prefixing it with a dollar sign and replacing
/// all hyphens with underscores, e.g. for the 'foo-bar' command the
/// following hook point name will be generated: '$foo_bar'.
///
/// @param command_name Command name for which the hook point should be
/// registered.
void registerCommandHook(const std::string& command_name);
/// @brief Get current hook index
///
/// Made available during callCallouts, this is the index of the hook
......
......@@ -48,19 +48,44 @@ HooksManager::calloutsPresent(int index) {
return (getHooksManager().calloutsPresentInternal(index));
}
bool
HooksManager::commandHandlersPresentInternal(const std::string& command_name) {
conditionallyInitialize();
return (callout_manager_->commandHandlersPresent(command_name));
}
bool
HooksManager::commandHandlersPresent(const std::string& command_name) {
return (getHooksManager().commandHandlersPresentInternal(command_name));
}
// Call the callouts
void
HooksManager::callCalloutsInternal(int index, CalloutHandle& handle) {
conditionallyInitialize();
return (callout_manager_->callCallouts(index, handle));
callout_manager_->callCallouts(index, handle);
}
void
HooksManager::callCallouts(int index, CalloutHandle& handle) {
return (getHooksManager().callCalloutsInternal(index, handle));
getHooksManager().callCalloutsInternal(index, handle);
}
void
HooksManager::callCommandHandlersInternal(const std::string& command_name,
CalloutHandle& handle) {
conditionallyInitialize();
callout_manager_->callCommandHandlers(command_name, handle);
}
void
HooksManager::callCommandHandlers(const std::string& command_name,
CalloutHandle& handle) {
getHooksManager().callCommandHandlersInternal(command_name, handle);
}
// Load the libraries. This will delete the previously-loaded libraries
// (if present) and load new ones.
......
......@@ -90,6 +90,17 @@ public:
/// @throw NoSuchHook Given index does not correspond to a valid hook.
static bool calloutsPresent(int index);
/// @brief Checks if control command handlers are present for the
/// specified command.
///
/// @param command_name Command name for which handlers' presence should
/// be checked.
///
/// @return true if there is a hook point associated with the specified
/// command and callouts/command handlers are installed for this hook
/// point, false otherwise.
static bool commandHandlersPresent(const std::string& command_name);
/// @brief Calls the callouts for a given hook
///
/// Iterates through the library handles and calls the callouts associated
......@@ -103,6 +114,22 @@ public:
/// object being processed.
static void callCallouts(int index, CalloutHandle& handle);
/// @brief Calls the callouts/command handlers for a given command name.
///
/// Iterates through the library handles and calls the command handlers
/// associated with the given command. It expects that the hook point
/// for this command exists (with a name being a command_name prefixed
/// with a dollar sign and with hyphens replaced with underscores).
///
/// @param command_name Command name for which handlers should be called.
/// @param handle Reference to the CalloutHandle object for the current
/// object being processed.
///
/// @throw NoSuchHook if the hook point for the specified command does
/// not exist.
static void callCommandHandlers(const std::string& command_name,
CalloutHandle& handle);
/// @brief Return pre-callouts library handle
///
/// Returns a library handle that can be used by the server to register
......@@ -253,6 +280,17 @@ private:
/// @throw NoSuchHook Given index does not correspond to a valid hook.
bool calloutsPresentInternal(int index);
/// @brief Checks if control command handlers are present for the
/// specified command.
///
/// @param command_name Command name for which handlers' presence should
/// be checked.
///
/// @return true if there is a hook point associated with the specified
/// command and callouts/command handlers are installed for this hook
/// point, false otherwise.
bool commandHandlersPresentInternal(const std::string& command_name);
/// @brief Calls the callouts for a given hook
///
/// @param index Index of the hook to call.
......@@ -260,6 +298,17 @@ private:
/// object being processed.
void callCalloutsInternal(int index, CalloutHandle& handle);
/// @brief Calls the callouts/command handlers for a given command name.
///
/// @param command_name Command name for which handlers should be called.
/// @param handle Reference to the CalloutHandle object for the current
/// object being processed.
///
/// @throw NoSuchHook if the hook point for the specified command does
/// not exist.
void callCommandHandlersInternal(const std::string& command_name,
CalloutHandle& handle);
/// @brief Return callout handle
///
/// @return Shared pointer to a CalloutHandle object.
......
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2017 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,8 @@
#include <hooks/library_handle.h>
#include <hooks/hooks_manager.h>
#include <iostream>
namespace isc {
namespace hooks {
......@@ -33,6 +35,16 @@ LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
}
}
void
LibraryHandle::registerCommandHandler(const std::string& command_name,
CalloutPtr callout) {
// Register hook point for this command, if one doesn't exist.
callout_manager_->registerCommandHook(command_name);
// Register the command handler as a callout.
registerCallout(ServerHooks::commandToHookName(command_name), callout);
}
bool
LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
int saved_index = callout_manager_->getLibraryIndex();
......
......@@ -40,6 +40,22 @@ extern "C" {
/// called, the CalloutManager uses that information to set the "current
/// library": the registration functions only operator on data whose
/// associated library is equal to the "current library".)
///
/// As of Kea 1.3.0 release, the @ref LibraryHandle can be used by the hook
/// libraries to install control command handlers and dynamically register
/// hook points with which the handlers are associated. For example, if the
/// hook library supports control-command 'foo-bar' it should register its
/// handler similarly to this:
/// @code
/// int load(LibraryHandle& libhandle) {
/// libhandle.registerCommandHandler("foo-bar", foo_bar_handler);
/// return (0);
/// }
/// @endcode
///
/// which will result in automatic creation of the hook point for the command
/// (if one doesn't exist) and associating the callout 'foo_bar_handler' with
/// this hook point as a handler for the command.
class LibraryHandle {
public:
......@@ -79,6 +95,17 @@ public:
/// is of the wrong size.
void registerCallout(const std::string& name, CalloutPtr callout);
/// @brief Register control command handler
///
/// Registers control command handler by creating a hook point for this
/// command (if it doesn't exist) and associating the callout as a command
/// handler. It is possible to register multiple command handlers for the
/// same control command because command handlers are implemented as callouts.
///
/// @param command_name Command name for which handler should be installed.
/// @param callout Pointer to the command handler implemented as a callout.
void registerCommandHandler(const std::string& command_name, CalloutPtr callout);
/// @brief De-Register a callout on a hook
///
/// Searches through the functions registered by the current library with
......
......@@ -8,6 +8,7 @@
#include <hooks/hooks_log.h>
#include <hooks/server_hooks.h>
#include <algorithm>
#include <utility>
#include <vector>
......@@ -137,6 +138,13 @@ ServerHooks::getIndex(const string& name) const {
return (i->second);
}
int
ServerHooks::findIndex(const std::string& name) const {
// Get iterator to matching element.
auto i = hooks_.find(name);
return ((i == hooks_.end()) ? -1 : i->second);
}
// Return vector of hook names. The names are not sorted - it is up to the
// caller to perform sorting if required.
......@@ -165,6 +173,16 @@ ServerHooks::getServerHooksPtr() {
return (hooks);
}
std::string
ServerHooks::commandToHookName(const std::string& command_name) {
// Prefix the command name with a dollar sign.
std::string hook_name = std::string("$") + command_name;
// Replace all hyphens with underscores.
std::replace(hook_name.begin(), hook_name.end(), '-', '_');
return (hook_name);
}
} // namespace util
} // namespace isc
......@@ -113,6 +113,17 @@ public:
/// @throw NoSuchHook if the hook name is unknown to the caller.
int getIndex(const std::string& name) const;
/// @brief Find hook index
///
/// Provides exception safe method of retrieving an index of the
/// specified hook.
///
/// @param name Name of the hook
///
/// @return Index of the hook if the hook point exists, or -1 if the
/// hook point doesn't exist.
int findIndex(const std::string& name) const;
/// @brief Return number of hooks
///
/// Returns the total number of hooks registered.
......@@ -141,6 +152,23 @@ public:
/// @return Pointer to the global ServerHooks object.
static ServerHooksPtr getServerHooksPtr();
/// @brief Generates hook point name for the given control command name.
///
/// This function is called to generate the name of the hook point
/// when the hook point is used to install command handlers for the
/// given control command.
///
/// The name of the hook point is generated as follows:
/// - command name is prefixed with a dollar sign,
/// - all hyphens are replaced with underscores.
///
/// For example, if the command_name is 'foo-bar', the resulting hook
/// point name will be '$foo_bar'.
///
/// @param command_name Command name for which the hook point name is
/// to be generated.
static std::string commandToHookName(const std::string& command_name);
private:
/// @brief Constructor
///
......
......@@ -867,6 +867,56 @@ TEST_F(CalloutManagerTest, LibraryHandlePrePostUserLibrary) {
EXPECT_EQ(154, callout_value_);
}
// Test that control command handlers can be installed as callouts.
TEST_F(CalloutManagerTest, LibraryHandleRegisterCommandHandler) {
CalloutHandle handle(getCalloutManager());
// Simulate creation of the two hook libraries. Fist library implements two
// handlers for the control command 'command-one'. Second library implements
// two control command handlers: one for the 'command-one', another one for
// 'command-two'. Each of the handlers for the 'command-one' must be called
// and they must be called in the appropriate order. Command handler for
// 'command-two' should also be called.
getCalloutManager()->setLibraryIndex(0);
getCalloutManager()->getLibraryHandle().registerCommandHandler("command-one",
callout_one);
getCalloutManager()->getLibraryHandle().registerCommandHandler("command-one",
callout_four);
getCalloutManager()->setLibraryIndex(1);
getCalloutManager()->getLibraryHandle().registerCommandHandler("command-one",
callout_two);
getCalloutManager()->getLibraryHandle().registerCommandHandler("command-two",
callout_three);
// Command handlers are installed for commands: 'command-one' and 'command-two'.
EXPECT_TRUE(getCalloutManager()->commandHandlersPresent("command-one"));
EXPECT_TRUE(getCalloutManager()->commandHandlersPresent("command-two"));
// There should be no handlers installed for 'command-three' and 'command-four'.
EXPECT_FALSE(getCalloutManager()->commandHandlersPresent("command-three"));
EXPECT_FALSE(getCalloutManager()->commandHandlersPresent("command-four"));
// Call handlers for 'command-one'. There should be three handlers called in
// the following order: 1, 4, 2.
callout_value_ = 0;
ASSERT_NO_THROW(getCalloutManager()->callCommandHandlers("command-one", handle));
EXPECT_EQ(142, callout_value_);
// There should be one handler invoked for the 'command-two'. This handler has
// index of 3.
callout_value_ = 0;
ASSERT_NO_THROW(getCalloutManager()->callCommandHandlers("command-two", handle));
EXPECT_EQ(3, callout_value_);
// An attempt to call handlers for the commands for which no hook points
// were created should result in exception.
EXPECT_THROW(getCalloutManager()->callCommandHandlers("command-three", handle),
NoSuchHook);
EXPECT_THROW(getCalloutManager()->callCommandHandlers("command-four", handle),
NoSuchHook);
}
// The setting of the hook index is checked in the handles_unittest
// set of tests, as access restrictions mean it is not easily tested
// on its own.
......
// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2017 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
......@@ -125,6 +125,46 @@ public:
EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
}
/// @brief Call command handlers test.
///
/// This test is similar to @c executeCallCallouts but it uses
/// @ref CalloutManager::callCommandHandlers to execute the command
/// handlers for the following commands: 'command-one' and 'command-two'.
///
/// @param manager CalloutManager to use for the test
/// @param r1..r2, d1..d2 Data (dN) and expected results (rN).
void executeCallCommandHandlers(
const boost::shared_ptr<isc::hooks::CalloutManager>& manager,
int d1, int r1, int d2, int r2) {
static const char* COMMON_TEXT = " command handler returned the wrong value";
static const char* RESULT = "result";
int result;
// Set up a callout handle for the calls.
isc::hooks::CalloutHandle handle(manager);
// Initialize the argument RESULT. This simplifies testing by
// eliminating the generation of an exception when we try the unload
// test. In that case, RESULT is unchanged.
handle.setArgument(RESULT, -1);
// Perform the first calculation: it should assign the data to the
// result.
handle.setArgument("data_1", d1);
manager->callCommandHandlers("command-one", handle);
handle.getArgument(RESULT, result);
EXPECT_EQ(d1, result) << "command-one" << COMMON_TEXT;
// Perform the second calculation: it should multiply the data by 10
// and return in the result.
handle.setArgument("data_2", d2);
manager->callCommandHandlers("command-two", handle);
handle.getArgument(RESULT, result);
EXPECT_EQ(r2, result) << "command-two" << COMMON_TEXT;
}
/// Hook indexes. These are are made public for ease of reference.
int hookpt_one_index_;
int hookpt_two_index_;
......
......@@ -101,6 +101,38 @@ hook_nonstandard_three(CalloutHandle& handle) {
return (0);
}
// First command handler assigns data to a result.
static int
command_handler_one(CalloutHandle& handle) {
int data;
handle.getArgument("data_1", data);
int result;
handle.getArgument("result", result);
result = data;
handle.setArgument("result", result);
return (0);
}
// Second command handler multiples the result by data by 10.
static int
command_handler_two(CalloutHandle& handle) {
int data;
handle.getArgument("data_2", data);
int result;
handle.getArgument("result", result);
result *= data * 10;
handle.setArgument("result", result);
return (0);
}
// Framework functions
int
......@@ -118,6 +150,10 @@ load(LibraryHandle& handle) {
handle.registerCallout("hookpt_two", hook_nonstandard_two);
handle.registerCallout("hookpt_three", hook_nonstandard_three);
// Register command_handler_one as control command handler.
handle.registerCommandHandler("command-one", command_handler_one);
handle.registerCommandHandler("command-two", command_handler_two);
return (0);
}
......
......@@ -93,6 +93,42 @@ public:
EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
}
/// @brief Call command handlers test.
///
/// This test is similar to @c executeCallCallouts but it uses
/// @ref HooksManager::callCommandHandlers to execute the command
/// handlers for the following commands: 'command-one' and 'command-two'.
///
/// @param r1..r2, d1..d2 Data (dN) and expected results (rN).
void executeCallCommandHandlers(int d1, int r1, int d2, int r2) {