From: Marcin Siodelski Date: Wed, 12 Jul 2017 17:01:26 +0000 (+0200) Subject: [5329] Added methods to register control commands as hooks. X-Git-Tag: trac5124a_base~42^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e61d01e5fcb24dba3d60575df8808d5e5179abf7;p=thirdparty%2Fkea.git [5329] Added methods to register control commands as hooks. --- diff --git a/src/lib/hooks/callout_manager.cc b/src/lib/hooks/callout_manager.cc index 48910e2ca1..a4ea856668 100644 --- a/src/lib/hooks/callout_manager.cc +++ b/src/lib/hooks/callout_manager.cc @@ -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 diff --git a/src/lib/hooks/callout_manager.h b/src/lib/hooks/callout_manager.h index 843e1b9cc8..886ec0fc75 100644 --- a/src/lib/hooks/callout_manager.h +++ b/src/lib/hooks/callout_manager.h @@ -1,4 +1,4 @@ -// 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 diff --git a/src/lib/hooks/library_handle.cc b/src/lib/hooks/library_handle.cc index 4aebf6fe80..1df6de5d61 100644 --- a/src/lib/hooks/library_handle.cc +++ b/src/lib/hooks/library_handle.cc @@ -1,4 +1,4 @@ -// 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 #include +#include + 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(); diff --git a/src/lib/hooks/library_handle.h b/src/lib/hooks/library_handle.h index 2fe6f0e125..e66c7bd39b 100644 --- a/src/lib/hooks/library_handle.h +++ b/src/lib/hooks/library_handle.h @@ -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 diff --git a/src/lib/hooks/server_hooks.cc b/src/lib/hooks/server_hooks.cc index 412b7e2c1f..e5e14f2d8a 100644 --- a/src/lib/hooks/server_hooks.cc +++ b/src/lib/hooks/server_hooks.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -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 diff --git a/src/lib/hooks/server_hooks.h b/src/lib/hooks/server_hooks.h index 3c052ad8f6..1ad7a36f61 100644 --- a/src/lib/hooks/server_hooks.h +++ b/src/lib/hooks/server_hooks.h @@ -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 /// diff --git a/src/lib/hooks/tests/callout_manager_unittest.cc b/src/lib/hooks/tests/callout_manager_unittest.cc index 33357a2ae1..1b96585b42 100644 --- a/src/lib/hooks/tests/callout_manager_unittest.cc +++ b/src/lib/hooks/tests/callout_manager_unittest.cc @@ -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. diff --git a/src/lib/hooks/tests/server_hooks_unittest.cc b/src/lib/hooks/tests/server_hooks_unittest.cc index 3d92a058da..8e4849c9c0 100644 --- a/src/lib/hooks/tests/server_hooks_unittest.cc +++ b/src/lib/hooks/tests/server_hooks_unittest.cc @@ -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