Commit e61d01e5 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[5329] Added methods to register control commands as hooks.

parent 57a13270
......@@ -104,6 +104,29 @@ CalloutManager::calloutsPresent(int hook_index) const {
return (!hook_vector_[hook_index].empty());
}
bool
CalloutManager::commandHandlersPresent(const std::string& command_name) const {
try {
// Check if the hook point for the specified command exists.
// We don't want this to throw because this is not an error condition.
// We may simply not support this command in any of the attached
// hooks libraries. That's fine.
int index = ServerHooks::getServerHooks().getIndex(
ServerHooks::commandToHookName(command_name));
// The hook point may exist but 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.
return (calloutsPresent(index));
} catch (...) {
// 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 +214,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 may 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 +309,29 @@ CalloutManager::deregisterAllCallouts(const std::string& name) {
return (removed);
}
void
CalloutManager::registerCommandHook(const std::string& command_name) {
ServerHooks& hooks = ServerHooks::getServerHooks();
int hook_index = -1;
try {
hook_index = hooks.getIndex(ServerHooks::commandToHookName(command_name));
} catch (...) {
// Ignore an error whereby the hook doesn't exist for this command.
// In this case we're going to register a new hook.
}
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,28 @@ 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.
///
/// 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.
///
/// The @ref CalloutManager::registerCommandHandler is called from the
/// @ref LibraryHandle object when the hook library installs control command
/// handlers in its @c load() function.
///
/// 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 +206,14 @@ 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.
///
/// @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 +227,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
......
// 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
/// 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 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>
......@@ -165,6 +166,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
......@@ -141,6 +141,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.
......
......@@ -195,4 +195,11 @@ TEST(ServerHooksTest, HookCount) {
EXPECT_EQ(6, hooks.getCount());
}
// Check that the hook name is correctly generated for a control command name.
TEST(ServerHooksTest, CommandToHookName) {
EXPECT_EQ("$x_y_z", ServerHooks::commandToHookName("x-y-z"));
EXPECT_EQ("$foo_bar_foo", ServerHooks::commandToHookName("foo-bar_foo"));
}
} // Anonymous namespace
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment