Parcourir la source

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

Marcin Siodelski il y a 7 ans
Parent
commit
e61d01e5fc

+ 61 - 0
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

+ 59 - 1
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

+ 13 - 1
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 <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();

+ 27 - 0
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

+ 11 - 0
src/lib/hooks/server_hooks.cc

@@ -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

+ 17 - 0
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
     ///

+ 50 - 0
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.

+ 7 - 0
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