#include <config/base_command_mgr.h>
#include <config/config_log.h>
#include <boost/bind.hpp>
+#include <set>
using namespace isc::data;
}
}
+ConstElementPtr
+BaseCommandMgr::combineCommandsLists(const ConstElementPtr& response1,
+ const ConstElementPtr& response2) const {
+ // Usually when this method is called there should be two non-null
+ // responses. If there is just a single response, return this
+ // response.
+ if (!response1 && response2) {
+ return (response2);
+
+ } else if (response1 && !response2) {
+ return (response1);
+
+ } else if (!response1 && !response2) {
+ return (ConstElementPtr());
+
+ } else {
+ // Both responses are non-null so we need to combine the lists
+ // of supported commands if the status codes are 0.
+ int status_code;
+ ConstElementPtr args1 = parseAnswer(status_code, response1);
+ if (status_code != 0) {
+ return (response1);
+ }
+
+ ConstElementPtr args2 = parseAnswer(status_code, response2);
+ if (status_code != 0) {
+ return (response2);
+ }
+
+ const std::vector<ElementPtr> vec1 = args1->listValue();
+ const std::vector<ElementPtr> vec2 = args2->listValue();
+
+ // Storing command names in a set guarantees that the non-unique
+ // command names are aggregated.
+ std::set<std::string> combined_set;
+ for (auto v = vec1.cbegin(); v != vec1.cend(); ++v) {
+ combined_set.insert((*v)->stringValue());
+ }
+ for (auto v = vec2.cbegin(); v != vec2.cend(); ++v) {
+ combined_set.insert((*v)->stringValue());
+ }
+
+ // Create a combined list of commands.
+ ElementPtr combined_list = Element::createList();
+ for (auto s = combined_set.cbegin(); s != combined_set.cend(); ++s) {
+ combined_list->add(Element::create(*s));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list));
+ }
+}
+
ConstElementPtr
BaseCommandMgr::handleCommand(const std::string& cmd_name,
const ConstElementPtr& params) {
"Manager: this is a programming error");
}
+ ConstElementPtr hook_response;
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);
+ // Being in this function we don't have access to the original data
+ // object holding the whole command (name and arguments). Let's
+ // recreate it.
+ ElementPtr original_command = Element::createMap();
+ original_command->set("command", Element::create(cmd_name));
+ original_command->set("arguments", params);
+
+ // And pass it to the hook library.
+ callout_handle_->setArgument("command", boost::dynamic_pointer_cast<
+ const Element>(original_command));
+ callout_handle_->setArgument("response", hook_response);
HooksManager::callCallouts(Hooks.hook_index_control_command_receive_,
*callout_handle_);
+ // The callouts should set the response.
+ callout_handle_->getArgument("response", hook_response);
+
+ // If the hook return 'skip' status, simply return the response.
if (callout_handle_->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
- ConstElementPtr response;
- callout_handle_->getArgument("response", response);
- return (response);
+ return (hook_response);
} else {
LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HOOK_RECEIVE_SKIP)
}
}
- return (BaseCommandMgr::handleCommand(cmd_name, params));
+ // If we're here it means that the callouts weren't called or the 'skip'
+ // status wasn't returned. The latter is the case when the 'list-commands'
+ // is being processed. Anyhow, we need to handle the command using local
+ // Command Mananger.
+ ConstElementPtr response = BaseCommandMgr::handleCommand(cmd_name, params);
+
+ // For the 'list-commands' case we will have to combine commands supported
+ // by the hook libraries with the commands that this Command Manager supports.
+ if ((cmd_name == "list-commands") && hook_response && response) {
+ response = combineCommandsLists(hook_response, response);
+ }
+
+ return (response);
}
#include <gtest/gtest.h>
+#include <config/base_command_mgr.h>
#include <config/command_mgr.h>
#include <config/hooked_command_mgr.h>
#include <cc/command_interpreter.h>
return (createAnswer(123, "test error message"));
}
+ /// @brief A simple command handler used from within hook library.
+ ///
+ /// @param name Command name.
+ /// @param params Command arguments.
+ static ConstElementPtr my_hook_handler(const std::string& name,
+ const ConstElementPtr& params) {
+ return (createAnswer(234, "text generated by hook handler"));
+ }
+
/// @brief Test callback which stores callout name and passed arguments.
///
/// This callout doesn't indicate that the command has been processed,
control_command_receive_handle_callout(CalloutHandle& callout_handle) {
callout_name = "control_command_receive";
- ConstElementPtr response = createAnswer(CONTROL_RESULT_SUCCESS,
- "text produced by the callout");
+ // Create a hooks specific command manager.
+ BaseCommandMgr callout_command_mgr;
+ callout_command_mgr.registerCommand("my-command", my_hook_handler);
+
+ ConstElementPtr command;
+ callout_handle.getArgument("command", command);
+
+ ConstElementPtr arg;
+ std::string command_name = parseCommand(arg, command);
+
+ ConstElementPtr response = callout_command_mgr.processCommand(command);
callout_handle.setArgument("response", response);
// Set 'skip' status to indicate that the command has been handled.
- callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+ if (command_name != "list-commands") {
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+ }
callout_argument_names = callout_handle.getArgumentNames();
// Sort arguments alphabetically, so as we can access them on
// 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]);
+ ASSERT_EQ(2, callout_argument_names.size());
+ EXPECT_EQ("command", callout_argument_names[0]);
+ EXPECT_EQ("response", callout_argument_names[1]);
}
// Verify that processing a command can be delegated to a 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(234, status_code);
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]);
+ ASSERT_EQ(2, callout_argument_names.size());
+ EXPECT_EQ("command", callout_argument_names[0]);
+ EXPECT_EQ("response", callout_argument_names[1]);
+}
+
+// Verify that 'list-command' command returns combined list of supported
+// commands from hook library and from the Kea Command Manager.
+TEST_F(CommandMgrTest, delegateListCommands) {
+ // 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);
+
+ // Create my-command-bis which is unique for the local Command Manager,
+ // i.e. not supported by the hook library. This command should also
+ // be returned as a result of processing 'list-commands'.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command-bis",
+ my_handler));
+
+ // Process command. The command should be routed to the hook library
+ // and the hook library should return the commands it supports.
+ ConstElementPtr command = createCommand("list-commands");
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ ConstElementPtr answer_arg;
+ int status_code;
+ ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(0, status_code);
+
+ // The hook library supports: my-command and list-commands commands. The
+ // local Command Manager supports list-commands and my-command-bis. The
+ // combined list should include 3 unique commands.
+ const std::vector<ElementPtr>& commands_list = answer_arg->listValue();
+ ASSERT_EQ(3, commands_list.size());
+ std::vector<std::string> command_names_list;
+ for (auto cmd = commands_list.cbegin(); cmd != commands_list.cend();
+ ++cmd) {
+ command_names_list.push_back((*cmd)->stringValue());
+ }
+ std::sort(command_names_list.begin(), command_names_list.end());
+ EXPECT_EQ("list-commands", command_names_list[0]);
+ EXPECT_EQ("my-command", command_names_list[1]);
+ EXPECT_EQ("my-command-bis", command_names_list[2]);
}