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
}
}
+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
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
/// - 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,
/// @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
/// 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
#include <hooks/library_handle.h>
#include <hooks/hooks_manager.h>
+#include <iostream>
+
namespace isc {
namespace hooks {
}
}
+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();
/// 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:
/// 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
#include <hooks/hooks_log.h>
#include <hooks/server_hooks.h>
+#include <algorithm>
#include <utility>
#include <vector>
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
/// @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
///
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.
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