From: Marcin Siodelski Date: Tue, 3 Jan 2017 19:01:33 +0000 (+0100) Subject: [5100] Implemented HookedCommandMgr class. X-Git-Tag: trac5099_base~1^2~5 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4e2fb1e163766c1944fbd49cee6140517863a42c;p=thirdparty%2Fkea.git [5100] Implemented HookedCommandMgr class. --- diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am index d01a6b8f64..1b0493169d 100644 --- a/src/lib/config/Makefile.am +++ b/src/lib/config/Makefile.am @@ -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 diff --git a/src/lib/config/base_command_mgr.cc b/src/lib/config/base_command_mgr.cc index 7a1a8f66a0..30f123bcc6 100644 --- a/src/lib/config/base_command_mgr.cc +++ b/src/lib/config/base_command_mgr.cc @@ -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) { diff --git a/src/lib/config/base_command_mgr.h b/src/lib/config/base_command_mgr.h index 389027f112..78606837b3 100644 --- a/src/lib/config/base_command_mgr.h +++ b/src/lib/config/base_command_mgr.h @@ -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 HandlerContainer; diff --git a/src/lib/config/command_mgr.cc b/src/lib/config/command_mgr.cc index 1239df650e..1abe07070c 100644 --- a/src/lib/config/command_mgr.cc +++ b/src/lib/config/command_mgr.cc @@ -18,7 +18,7 @@ namespace isc { namespace config { CommandMgr::CommandMgr() - : BaseCommandMgr() { + : HookedCommandMgr() { } CommandSocketPtr diff --git a/src/lib/config/command_mgr.h b/src/lib/config/command_mgr.h index 31a41ad814..d05e0eb262 100644 --- a/src/lib/config/command_mgr.h +++ b/src/lib/config/command_mgr.h @@ -8,7 +8,7 @@ #define COMMAND_MGR_H #include -#include +#include #include #include #include @@ -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 diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes index 2a4558fd85..8afcfd68a5 100644 --- a/src/lib/config/config_messages.mes +++ b/src/lib/config/config_messages.mes @@ -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 index 0000000000..477c86a4a6 --- /dev/null +++ b/src/lib/config/hooked_command_mgr.cc @@ -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 +#include +#include + +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 index 0000000000..88d7fd5f8e --- /dev/null +++ b/src/lib/config/hooked_command_mgr.h @@ -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 +#include +#include + +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 diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am index b2e547ad44..532aa0d81b 100644 --- a/src/lib/config/tests/Makefile.am +++ b/src/lib/config/tests/Makefile.am @@ -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 diff --git a/src/lib/config/tests/command_mgr_unittests.cc b/src/lib/config/tests/command_mgr_unittests.cc index 3f9a937e16..7c8346556c 100644 --- a/src/lib/config/tests/command_mgr_unittests.cc +++ b/src/lib/config/tests/command_mgr_unittests.cc @@ -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 #include +#include #include +#include +#include +#include +#include +#include -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 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 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]); }