]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5100] Combine commands lists returned by hook libraries.
authorMarcin Siodelski <marcin@isc.org>
Wed, 4 Jan 2017 14:56:52 +0000 (15:56 +0100)
committerMarcin Siodelski <marcin@isc.org>
Wed, 4 Jan 2017 16:20:26 +0000 (17:20 +0100)
src/lib/config/base_command_mgr.cc
src/lib/config/base_command_mgr.h
src/lib/config/hooked_command_mgr.cc
src/lib/config/tests/command_mgr_unittests.cc

index 30f123bcc645ed2d1de69402a2192fb979b85df4..94c46e01a74f81610598ae5406d64a9ef7dbe50b 100644 (file)
@@ -8,6 +8,7 @@
 #include <config/base_command_mgr.h>
 #include <config/config_log.h>
 #include <boost/bind.hpp>
+#include <set>
 
 using namespace isc::data;
 
@@ -86,6 +87,57 @@ BaseCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
     }
 }
 
+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) {
index 78606837b3210348364dbb9ad25ae8d8737828cf..2332ac7a9f24c91c907fbc8a6426891a59310ca1 100644 (file)
@@ -118,6 +118,27 @@ public:
 
 protected:
 
+    /// @brief Combines lists of commands carried in two responses.
+    ///
+    /// This method is used to combine list of commands returned by the
+    /// hook library with the commands supported by the local Command
+    /// Manager. This method should also be used within the hook library
+    /// to combine commands supported by this hook library with the
+    /// commands returned by other hook libraries attached to the server
+    /// at the same time.
+    ///
+    /// If the same command appears in two responses only a single
+    /// instance is returned in the combined response.
+    ///
+    /// @param response1 First command response.
+    /// @param response2 Second command response.
+    ///
+    /// @return Pointer to the 'list-commands' response holding combined
+    /// list of commands.
+    isc::data::ConstElementPtr
+    combineCommandsLists(const isc::data::ConstElementPtr& response1,
+                         const isc::data::ConstElementPtr& response2) const;
+
     /// @brief Handles the command having a given name and arguments.
     ///
     /// This method can be overriden in the derived classes to provide
index 477c86a4a6771ef5649a1d673255c06e35038f8d..8e865892020410a597705d1e715a2e3e3050dd07 100644 (file)
@@ -48,21 +48,33 @@ HookedCommandMgr::handleCommand(const std::string& cmd_name,
                   "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)
@@ -70,7 +82,19 @@ HookedCommandMgr::handleCommand(const std::string& cmd_name,
         }
     }
 
-    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);
 }
 
 
index 7c8346556cad51c7af4cfea615fd90cbdb5e178c..1787743630f6ac144451d02a75fd991c7e1a7451 100644 (file)
@@ -6,6 +6,7 @@
 
 #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>
@@ -62,6 +63,15 @@ public:
         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,
@@ -95,12 +105,23 @@ public:
     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
@@ -299,10 +320,9 @@ TEST_F(CommandMgrTest, processCommand) {
 
     // 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.
@@ -334,15 +354,57 @@ TEST_F(CommandMgrTest, delegateProcessCommand) {
     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]);
 }