]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5100] Implemented HookedCommandMgr class.
authorMarcin Siodelski <marcin@isc.org>
Tue, 3 Jan 2017 19:01:33 +0000 (20:01 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 3 Jan 2017 19:01:33 +0000 (20:01 +0100)
src/lib/config/Makefile.am
src/lib/config/base_command_mgr.cc
src/lib/config/base_command_mgr.h
src/lib/config/command_mgr.cc
src/lib/config/command_mgr.h
src/lib/config/config_messages.mes
src/lib/config/hooked_command_mgr.cc [new file with mode: 0644]
src/lib/config/hooked_command_mgr.h [new file with mode: 0644]
src/lib/config/tests/Makefile.am
src/lib/config/tests/command_mgr_unittests.cc

index d01a6b8f644467f0a66e97ccfe9ffd878de01907..1b0493169d6fc1776b0f0c60c303fcbb8e02b473 100644 (file)
@@ -20,12 +20,14 @@ libkea_cfgclient_la_SOURCES += command_mgr.cc command_mgr.h
 libkea_cfgclient_la_SOURCES += command_socket.cc command_socket.h
 libkea_cfgclient_la_SOURCES += command_socket_factory.cc command_socket_factory.h
 libkea_cfgclient_la_SOURCES += config_log.h config_log.cc
+libkea_cfgclient_la_SOURCES += hooked_command_mgr.cc hooked_command_mgr.h
 
 libkea_cfgclient_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
index 7a1a8f66a05adabd7b2850511f7ca6b015c725e5..30f123bcc645ed2d1de69402a2192fb979b85df4 100644 (file)
@@ -76,15 +76,7 @@ BaseCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
 
         LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
 
-        HandlerContainer::const_iterator it = handlers_.find(name);
-        if (it == handlers_.end()) {
-            // Ok, there's no such command.
-            return (createAnswer(CONTROL_RESULT_ERROR,
-                                 "'" + name + "' command not supported."));
-        }
-
-        // Call the actual handler and return whatever it returned
-        return (it->second(name, arg));
+        return (handleCommand(name, arg));
 
     } catch (const Exception& e) {
         LOG_WARN(command_logger, COMMAND_PROCESS_ERROR2).arg(e.what());
@@ -94,6 +86,20 @@ BaseCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
     }
 }
 
+ConstElementPtr
+BaseCommandMgr::handleCommand(const std::string& cmd_name,
+                              const ConstElementPtr& params) {
+    auto it = handlers_.find(cmd_name);
+    if (it == handlers_.end()) {
+        // Ok, there's no such command.
+        return (createAnswer(CONTROL_RESULT_ERROR,
+                             "'" + cmd_name + "' command not supported."));
+    }
+
+    // Call the actual handler and return whatever it returned
+    return (it->second(cmd_name, params));
+}
+
 isc::data::ConstElementPtr
 BaseCommandMgr::listCommandsHandler(const std::string& name,
                                     const isc::data::ConstElementPtr& params) {
index 389027f11290c8548abb1551967bd5dd6741639c..78606837b3210348364dbb9ad25ae8d8737828cf 100644 (file)
@@ -96,7 +96,7 @@ public:
     ///
     /// @param cmd Pointer to the data element representing command in JSON
     /// format.
-    virtual isc::data::ConstElementPtr
+    isc::data::ConstElementPtr
     processCommand(const isc::data::ConstElementPtr& cmd);
 
     /// @brief Registers specified command handler for a given command
@@ -118,6 +118,22 @@ public:
 
 protected:
 
+    /// @brief Handles the command having a given name and arguments.
+    ///
+    /// This method can be overriden in the derived classes to provide
+    /// custom logic for processing commands. For example, the
+    /// @ref HookedCommandMgr extends this method to delegate commmands
+    /// processing to a hook library.
+    ///
+    /// @param cmd_name Command name.
+    /// @param params Command arguments.
+    ///
+    /// @return Pointer to the const data element representing response
+    /// to a command.
+    virtual isc::data::ConstElementPtr
+    handleCommand(const std::string& cmd_name,
+                  const isc::data::ConstElementPtr& params);
+
     /// @brief Type of the container for command handlers.
     typedef std::map<std::string, CommandHandler> HandlerContainer;
 
index 1239df650e6ccc3412ee7e6f1515212dc18ea37b..1abe07070c782c599cd7528253770d36a0343c3c 100644 (file)
@@ -18,7 +18,7 @@ namespace isc {
 namespace config {
 
 CommandMgr::CommandMgr()
-    : BaseCommandMgr() {
+    : HookedCommandMgr() {
 }
 
 CommandSocketPtr
index 31a41ad814b03ba7f811c49df4570d884ae9e84d..d05e0eb26208470d4dc02624052aeafd1595a172 100644 (file)
@@ -8,7 +8,7 @@
 #define COMMAND_MGR_H
 
 #include <cc/data.h>
-#include <config/base_command_mgr.h>
+#include <config/hooked_command_mgr.h>
 #include <config/command_socket.h>
 #include <boost/noncopyable.hpp>
 #include <list>
@@ -20,7 +20,7 @@ namespace config {
 ///
 /// This class extends @ref BaseCommandMgr with the ability to receive and
 /// respond to commands over unix domain sockets.
-class CommandMgr : public BaseCommandMgr, public boost::noncopyable {
+class CommandMgr : public HookedCommandMgr, public boost::noncopyable {
 public:
 
     /// @brief CommandMgr is a singleton class. This method returns reference
index 2a4558fd853c0b3566fe19910f03906ae5e4c9bb..8afcfd68a52fe02a77c6760fdb03289c5088a4a9 100644 (file)
@@ -11,6 +11,13 @@ This debug message indicates that the daemon stopped supporting specified
 command. This command can no longer be issued. If the command socket is
 open and this command is issued, the daemon will not be able to process it.
 
+% COMMAND_HOOK_RECEIVE_SKIP command %1 has been handled by the hook library which returned the skip state
+This debug message is issued when a hook library has processed the control
+command and returned the skip status. The callout should have set the
+'response' argument which contains the result of processing the command.
+The Command Manager skips processing of this command and simply returns
+the response generated by the hook library.
+
 % COMMAND_PROCESS_ERROR1 Error while processing command: %1
 This warning message indicates that the server encountered an error while
 processing received command. Additional information will be provided, if
diff --git a/src/lib/config/hooked_command_mgr.cc b/src/lib/config/hooked_command_mgr.cc
new file mode 100644 (file)
index 0000000..477c86a
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (C) 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config/hooked_command_mgr.h>
+#include <config/config_log.h>
+#include <hooks/hooks_manager.h>
+
+using namespace isc::data;
+using namespace isc::hooks;
+
+namespace {
+
+/// @brief Structure that holds registered hook indexes.
+struct CommandMgrHooks {
+    /// @brief Index for "control_command_receive" hook point.
+    int hook_index_control_command_receive_;
+
+    /// @brief Constructor that registers hook points for HookedCommandMgr
+    CommandMgrHooks() {
+        hook_index_control_command_receive_ =
+            HooksManager::registerHook("control_command_receive");
+    }
+};
+
+// Declare a hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+CommandMgrHooks Hooks;
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace config {
+
+HookedCommandMgr::HookedCommandMgr()
+    : BaseCommandMgr(), callout_handle_(HooksManager::createCalloutHandle()) {
+}
+
+ConstElementPtr
+HookedCommandMgr::handleCommand(const std::string& cmd_name,
+                                const ConstElementPtr& params) {
+    if (!callout_handle_) {
+        isc_throw(Unexpected, "callout handle not configured for the Command "
+                  "Manager: this is a programming error");
+    }
+
+    if (HooksManager::calloutsPresent(Hooks.hook_index_control_command_receive_)) {
+
+        // Delete previously set arguments.
+        callout_handle_->deleteAllArguments();
+
+        callout_handle_->setArgument("command", cmd_name);
+        callout_handle_->setArgument("arguments", params);
+
+        HooksManager::callCallouts(Hooks.hook_index_control_command_receive_,
+                                   *callout_handle_);
+
+        if (callout_handle_->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+            ConstElementPtr response;
+            callout_handle_->getArgument("response", response);
+            return (response);
+
+        } else {
+            LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HOOK_RECEIVE_SKIP)
+                .arg(cmd_name);
+        }
+    }
+
+    return (BaseCommandMgr::handleCommand(cmd_name, params));
+}
+
+
+} // end of namespace isc::config
+} // end of namespace isc
diff --git a/src/lib/config/hooked_command_mgr.h b/src/lib/config/hooked_command_mgr.h
new file mode 100644 (file)
index 0000000..88d7fd5
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (C) 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HOOKED_COMMAND_MGR_H
+#define HOOKED_COMMAND_MGR_H
+
+#include <cc/data.h>
+#include <config/base_command_mgr.h>
+#include <hooks/callout_handle.h>
+
+namespace isc {
+namespace config {
+
+/// @brief Command Manager which can delegate commands to a hook library.
+///
+/// This class extends @ref BaseCommandMgr with the logic to delegate the
+/// commands to a hook library if the hook library is installed and provides
+/// callouts for the control API.
+class HookedCommandMgr : public BaseCommandMgr {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes callout handle used by the Command Manager.
+    HookedCommandMgr();
+
+protected:
+
+    /// @brief Returns callout handle to the derived class.
+    ///
+    /// @return const pointer to the callout handle.
+    const isc::hooks::CalloutHandlePtr& getCalloutHandle() const {
+        return (callout_handle_);
+    }
+
+private:
+
+    /// @brief Handles the command having a given name and arguments.
+    ///
+    /// This method checks if the hook library is installed which implements
+    /// callouts for the 'control_command_receive' hook point, and calls them
+    /// if they exist. If the hook library supports the given command it creates
+    /// a response and returns it in the 'response' argument of the
+    /// @ref ConstElementPtr type. If the callout also sets the 'skip' status,
+    /// the response created by the callout is returned. Otherwise, the
+    /// @ref BaseCommandMgr::handleCommand is called.
+    ///
+    /// This method is private because it is its final implementation which
+    /// should not be overriden in the derived classes.
+    ///
+    /// @param cmd_name Command name.
+    /// @param params Command arguments.
+    ///
+    /// @return Pointer to the const data element representing response
+    /// to a command.
+    virtual isc::data::ConstElementPtr
+    handleCommand(const std::string& cmd_name,
+                  const isc::data::ConstElementPtr& params);
+
+    /// @brief Pointer to a callout handle used by this class.
+    isc::hooks::CalloutHandlePtr callout_handle_;
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
index b2e547ad44b65fc3eb0c330ac725d29ba5a323a8..532aa0d81b0e0e1e418cb2f09abc36c924d2f3c5 100644 (file)
@@ -32,6 +32,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
 run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
index 3f9a937e16a0c667759ff3c13edfb2e37fd03eee..7c8346556cad51c7af4cfea615fd90cbdb5e178c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-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
@@ -7,10 +7,17 @@
 #include <gtest/gtest.h>
 
 #include <config/command_mgr.h>
+#include <config/hooked_command_mgr.h>
 #include <cc/command_interpreter.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
+#include <hooks/library_handle.h>
+#include <string>
+#include <vector>
 
-using namespace isc::data;
 using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
 using namespace std;
 
 // Test class for Command Manager
@@ -25,12 +32,23 @@ public:
 
         CommandMgr::instance().deregisterAll();
         CommandMgr::instance().closeCommandSocket();
+
+        resetCalloutIndicators();
     }
 
     /// Default destructor
-    ~CommandMgrTest() {
+    virtual ~CommandMgrTest() {
         CommandMgr::instance().deregisterAll();
         CommandMgr::instance().closeCommandSocket();
+        resetCalloutIndicators();
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                "control_command_receive");
+    }
+
+    /// @brief Resets indicators related to callout invocation.
+    static void resetCalloutIndicators() {
+        callout_name = "";
+        callout_argument_names.clear();
     }
 
     /// @brief A simple command handler that always returns an eror
@@ -44,6 +62,53 @@ public:
         return (createAnswer(123, "test error message"));
     }
 
+    /// @brief Test callback which stores callout name and passed arguments.
+    ///
+    /// This callout doesn't indicate that the command has been processed,
+    /// allowing the Command Manager to process it.
+    ///
+    /// @param callout_handle Handle passed by the hooks framework.
+    /// @return Always 0.
+    static int
+    control_command_receive_callout(CalloutHandle& callout_handle) {
+        callout_name = "control_command_receive";
+
+        ConstElementPtr response;
+        callout_handle.setArgument("response", response);
+
+        callout_argument_names = callout_handle.getArgumentNames();
+        // Sort arguments alphabetically, so as we can access them on
+        // expected positions and verify.
+        std::sort(callout_argument_names.begin(), callout_argument_names.end());
+        return (0);
+    }
+
+    /// @brief Text callback which stores callout name and passed arguments and
+    /// which handles the command.
+    ///
+    /// This callout returns the skip status to indicate the the command has
+    /// been handled.
+    ///
+    /// @param callout_handle Handle passed by the hooks framework.
+    /// @return Always 0.
+    static int
+    control_command_receive_handle_callout(CalloutHandle& callout_handle) {
+        callout_name = "control_command_receive";
+
+        ConstElementPtr response = createAnswer(CONTROL_RESULT_SUCCESS,
+                                                "text produced by the callout");
+        callout_handle.setArgument("response", response);
+
+        // Set 'skip' status to indicate that the command has been handled.
+        callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+        callout_argument_names = callout_handle.getArgumentNames();
+        // Sort arguments alphabetically, so as we can access them on
+        // expected positions and verify.
+        std::sort(callout_argument_names.begin(), callout_argument_names.end());
+        return (0);
+    }
+
     /// @brief Name of the command (used in my_handler)
     static std::string handler_name;
 
@@ -52,6 +117,12 @@ public:
 
     /// @brief Indicates whether my_handler was called
     static bool handler_called;
+
+    /// @brief Holds invoked callout name.
+    static std::string callout_name;
+
+    /// @brief Holds a list of arguments passed to the callout.
+    static std::vector<std::string> callout_argument_names;
 };
 
 /// Name passed to the handler (used in my_handler)
@@ -63,6 +134,12 @@ ConstElementPtr CommandMgrTest::handler_params;
 /// Indicates whether my_handler was called
 bool CommandMgrTest::handler_called(false);
 
+/// Holds invoked callout name.
+std::string CommandMgrTest::callout_name("");
+
+/// Holds a list of arguments passed to the callout.
+std::vector<std::string> CommandMgrTest::callout_argument_names;
+
 // Test checks whether the internal command 'list-commands'
 // is working properly.
 TEST_F(CommandMgrTest, listCommandsEmpty) {
@@ -186,6 +263,11 @@ TEST_F(CommandMgrTest, deregisterAll) {
 // runs through processCommand to check that it's indeed called.
 TEST_F(CommandMgrTest, processCommand) {
 
+    // Register callout so as we can check that it is called before
+    // processing the command by the manager.
+    HooksManager::preCalloutsLibraryHandle().registerCallout(
+        "control_command_receive", control_command_receive_callout);
+
     // Install my handler
     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
                                                            my_handler));
@@ -212,4 +294,55 @@ TEST_F(CommandMgrTest, processCommand) {
     EXPECT_EQ("my-command", handler_name);
     ASSERT_TRUE(handler_params);
     EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", handler_params->str());
+
+    EXPECT_EQ("control_command_receive", callout_name);
+
+    // Check that the appropriate arguments have been set. Include the
+    // 'response' which should have been set by the callout.
+    ASSERT_EQ(3, callout_argument_names.size());
+    EXPECT_EQ("arguments", callout_argument_names[0]);
+    EXPECT_EQ("command", callout_argument_names[1]);
+    EXPECT_EQ("response", callout_argument_names[2]);
+}
+
+// Verify that processing a command can be delegated to a hook library.
+TEST_F(CommandMgrTest, delegateProcessCommand) {
+    // Register callout so as we can check that it is called before
+    // processing the command by the manager.
+    HooksManager::preCalloutsLibraryHandle().registerCallout(
+        "control_command_receive", control_command_receive_handle_callout);
+
+    // Install local handler
+    EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+                                                           my_handler));
+
+    // Now tell CommandMgr to process a command 'my-command' with the
+    // specified parameter.
+    ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+    ConstElementPtr command = createCommand("my-command", my_params);
+    ConstElementPtr answer;
+    ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+    // There should be an answer.
+    ASSERT_TRUE(answer);
+
+    // Local handler shouldn't be called because the command is handled by the
+    // hook library.
+    ASSERT_FALSE(handler_called);
+
+    // Returned status should be unique for the hook library.
+    ConstElementPtr answer_arg;
+    int status_code;
+    ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+    EXPECT_EQ(0, status_code);
+    EXPECT_EQ("text produced by the callout", answer_arg->stringValue());
+
+    EXPECT_EQ("control_command_receive", callout_name);
+
+    // Check that the appropriate arguments have been set. Include the
+    // 'response' which should have been set by the callout.
+    ASSERT_EQ(3, callout_argument_names.size());
+    EXPECT_EQ("arguments", callout_argument_names[0]);
+    EXPECT_EQ("command", callout_argument_names[1]);
+    EXPECT_EQ("response", callout_argument_names[2]);
 }