"Fossies" - the Fresh Open Source Software Archive

Member "kea-1.6.2/src/lib/config/tests/command_mgr_unittests.cc" (21 Feb 2020, 22025 Bytes) of package /linux/misc/kea-1.6.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "command_mgr_unittests.cc": 1.6.1_vs_1.6.2.

    1 // Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
    2 //
    3 // This Source Code Form is subject to the terms of the Mozilla Public
    4 // License, v. 2.0. If a copy of the MPL was not distributed with this
    5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
    6 
    7 #include <config.h>
    8 
    9 #include <gtest/gtest.h>
   10 
   11 #include <testutils/sandbox.h>
   12 #include <asiolink/io_service.h>
   13 #include <config/base_command_mgr.h>
   14 #include <config/command_mgr.h>
   15 #include <config/hooked_command_mgr.h>
   16 #include <cc/command_interpreter.h>
   17 #include <hooks/hooks_manager.h>
   18 #include <hooks/callout_handle.h>
   19 #include <hooks/library_handle.h>
   20 #include <string>
   21 #include <vector>
   22 
   23 using namespace isc::asiolink;
   24 using namespace isc::config;
   25 using namespace isc::data;
   26 using namespace isc::hooks;
   27 using namespace std;
   28 
   29 // Test class for Command Manager
   30 class CommandMgrTest : public ::testing::Test {
   31 public:
   32     isc::test::Sandbox sandbox;
   33 
   34     /// Default constructor
   35     CommandMgrTest()
   36         : io_service_(new IOService()) {
   37 
   38         CommandMgr::instance().setIOService(io_service_);
   39 
   40         handler_name_ = "";
   41         handler_params_ = ElementPtr();
   42         handler_called_ = false;
   43         callout_name_ = "";
   44         callout_argument_names_.clear();
   45         std::string processed_log_ = "";
   46 
   47         CommandMgr::instance().deregisterAll();
   48         CommandMgr::instance().closeCommandSocket();
   49 
   50         resetCalloutIndicators();
   51     }
   52 
   53     /// Default destructor
   54     virtual ~CommandMgrTest() {
   55         CommandMgr::instance().deregisterAll();
   56         CommandMgr::instance().closeCommandSocket();
   57         resetCalloutIndicators();
   58     }
   59 
   60     /// @brief Returns socket path (using either hardcoded path or env variable)
   61     /// @return path to the unix socket
   62     std::string getSocketPath() {
   63         std::string socket_path;
   64         const char* env = getenv("KEA_SOCKET_TEST_DIR");
   65         if (env) {
   66             socket_path = std::string(env) + "/test-socket";
   67         } else {
   68             socket_path = sandbox.join("test-socket");
   69         }
   70         return (socket_path);
   71     }
   72 
   73     /// @brief Resets indicators related to callout invocation.
   74     ///
   75     /// It also removes any registered callouts.
   76     static void resetCalloutIndicators() {
   77         callout_name_ = "";
   78         callout_argument_names_.clear();
   79 
   80         // Iterate over existing hook points and for each of them remove
   81         // callouts registered.
   82         std::vector<std::string> hooks = ServerHooks::getServerHooksPtr()->getHookNames();
   83         for (auto h = hooks.cbegin(); h != hooks.cend(); ++h) {
   84                 HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(*h);
   85         }
   86     }
   87 
   88     /// @brief A simple command handler that always returns an eror
   89     static ConstElementPtr my_handler(const std::string& name,
   90                                       const ConstElementPtr& params) {
   91 
   92         handler_name_ = name;
   93         handler_params_ = params;
   94         handler_called_ = true;
   95 
   96         return (createAnswer(123, "test error message"));
   97     }
   98 
   99     /// @brief Test callback which stores callout name and passed arguments and
  100     /// which handles the command.
  101     ///
  102     /// @param callout_handle Handle passed by the hooks framework.
  103     /// @return Always 0.
  104     static int
  105     hook_lib_callout(CalloutHandle& callout_handle) {
  106         callout_name_ = "hook_lib_callout";
  107 
  108         ConstElementPtr command;
  109         callout_handle.getArgument("command", command);
  110 
  111         ConstElementPtr arg;
  112         std::string command_name = parseCommand(arg, command);
  113 
  114         callout_handle.setArgument("response",
  115                                    createAnswer(234, "text generated by hook handler"));
  116 
  117         callout_argument_names_ = callout_handle.getArgumentNames();
  118         // Sort arguments alphabetically, so as we can access them on
  119         // expected positions and verify.
  120         std::sort(callout_argument_names_.begin(), callout_argument_names_.end());
  121         return (0);
  122     }
  123 
  124     /// @brief Test callback which stores callout name and passed arguments and
  125     /// which handles the command.
  126     ///
  127     /// @param callout_handle Handle passed by the hooks framework.
  128     /// @return Always 0.
  129     static int
  130     command_processed_callout(CalloutHandle& callout_handle) {
  131         callout_name_ = "command_processed_handler";
  132 
  133         std::string name;
  134         callout_handle.getArgument("name", name);
  135 
  136         ConstElementPtr arguments;
  137         callout_handle.getArgument("arguments", arguments);
  138 
  139         ConstElementPtr response;
  140         callout_handle.getArgument("response", response);
  141         std::ostringstream os;
  142         os << name << ":" << arguments->str() << ":" << response->str();
  143         processed_log_ = os.str();
  144 
  145         if (name == "change-response") {
  146             callout_handle.setArgument("response",
  147                 createAnswer(777, "replaced response text"));
  148         }
  149 
  150         return (0);
  151     }
  152 
  153     /// @brief IO service used by these tests.
  154     IOServicePtr io_service_;
  155 
  156     /// @brief Name of the command (used in my_handler)
  157     static std::string handler_name_;
  158 
  159     /// @brief Parameters passed to the handler (used in my_handler)
  160     static ConstElementPtr handler_params_;
  161 
  162     /// @brief Indicates whether my_handler was called
  163     static bool handler_called_;
  164 
  165     /// @brief Holds invoked callout name.
  166     static std::string callout_name_;
  167 
  168     /// @brief Holds a list of arguments passed to the callout.
  169     static std::vector<std::string> callout_argument_names_;
  170 
  171     /// @brief Holds the generated command process log
  172     static std::string processed_log_;
  173 };
  174 
  175 /// Name passed to the handler (used in my_handler)
  176 std::string CommandMgrTest::handler_name_("");
  177 
  178 /// Parameters passed to the handler (used in my_handler)
  179 ConstElementPtr CommandMgrTest::handler_params_;
  180 
  181 /// Indicates whether my_handler was called
  182 bool CommandMgrTest::handler_called_(false);
  183 
  184 /// Holds invoked callout name.
  185 std::string CommandMgrTest::callout_name_("");
  186 
  187 /// @brief Holds the generated command process log
  188 std::string CommandMgrTest::processed_log_;
  189 
  190 /// Holds a list of arguments passed to the callout.
  191 std::vector<std::string> CommandMgrTest::callout_argument_names_;
  192 
  193 // Test checks whether the internal command 'list-commands'
  194 // is working properly.
  195 TEST_F(CommandMgrTest, listCommandsEmpty) {
  196 
  197     ConstElementPtr command = createCommand("list-commands");
  198 
  199     ConstElementPtr answer;
  200 
  201     EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
  202 
  203     ASSERT_TRUE(answer);
  204 
  205     EXPECT_EQ("{ \"arguments\": [ \"list-commands\" ], \"result\": 0 }",
  206               answer->str());
  207 }
  208 
  209 // Test checks whether calling a bogus command is handled properly.
  210 TEST_F(CommandMgrTest, bogusCommand) {
  211 
  212     ConstElementPtr command = createCommand("no-such-command");
  213 
  214     ConstElementPtr answer;
  215 
  216     EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
  217 
  218     // Make sure the status code is non-zero
  219     ASSERT_TRUE(answer);
  220     int status_code;
  221     parseAnswer(status_code, answer);
  222     EXPECT_EQ(CONTROL_RESULT_COMMAND_UNSUPPORTED, status_code);
  223 }
  224 
  225 // Test checks whether handlers installation is sanitized. In particular,
  226 // whether NULL handler and attempt to install handlers for the same
  227 // command twice are rejected.
  228 TEST_F(CommandMgrTest, handlerInstall) {
  229 
  230     // Check that it's not allowed to install NULL pointer instead of a real
  231     // command.
  232     EXPECT_THROW(CommandMgr::instance().registerCommand("my-command", 0),
  233                  InvalidCommandHandler);
  234 
  235     // This registration should succeed.
  236     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
  237                                                            my_handler));
  238 
  239     // Check that it's not possible to install handlers for the same
  240     // command twice.
  241     EXPECT_THROW(CommandMgr::instance().registerCommand("my-command",
  242                  my_handler), InvalidCommandName);
  243 }
  244 
  245 // Test checks whether the internal list-commands command is working
  246 // correctly. Also, checks installation and deinstallation of other
  247 // command handlers.
  248 TEST_F(CommandMgrTest, listCommands) {
  249 
  250     // Let's install two custom commands.
  251     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("make-a-coffee",
  252                                                            my_handler));
  253     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("do-the-dishes",
  254                                                            my_handler));
  255 
  256     // And then run 'list-commands'
  257     ConstElementPtr list_all = createCommand("list-commands");
  258     ConstElementPtr answer;
  259 
  260     // Now check that the command is returned by list-commands
  261     EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_all));
  262     ASSERT_TRUE(answer);
  263     EXPECT_EQ("{ \"arguments\": [ \"do-the-dishes\", \"list-commands\", "
  264               "\"make-a-coffee\" ], \"result\": 0 }", answer->str());
  265 
  266     // Now unregister one command
  267     EXPECT_NO_THROW(CommandMgr::instance().deregisterCommand("do-the-dishes"));
  268 
  269     // Now check that the command is returned by list-commands
  270     EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_all));
  271     ASSERT_TRUE(answer);
  272     EXPECT_EQ("{ \"arguments\": [ \"list-commands\", "
  273               "\"make-a-coffee\" ], \"result\": 0 }", answer->str());
  274 
  275     // Now test deregistration. It should work the first time.
  276     EXPECT_NO_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"));
  277 
  278     // Second time should throw an exception as the handler is no longer there.
  279     EXPECT_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"),
  280                  InvalidCommandName);
  281 
  282     // You can't uninstall list-commands as it's the internal handler.
  283     // It always must be there.
  284     EXPECT_THROW(CommandMgr::instance().deregisterCommand("list-commands"),
  285                  InvalidCommandName);
  286 
  287     // Attempt to register a handler for existing command should fail.
  288     EXPECT_THROW(CommandMgr::instance().registerCommand("list-commands",
  289                  my_handler), InvalidCommandName);
  290 }
  291 
  292 // Test checks whether deregisterAll method uninstalls all handlers,
  293 // except list-commands.
  294 TEST_F(CommandMgrTest, deregisterAll) {
  295 
  296     // Install a couple handlers.
  297     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command1",
  298                                                            my_handler));
  299     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command2",
  300                                                            my_handler));
  301 
  302     EXPECT_NO_THROW(CommandMgr::instance().deregisterAll());
  303 
  304     ConstElementPtr answer;
  305     EXPECT_NO_THROW(answer = CommandMgr::instance()
  306                     .processCommand(createCommand("list-commands")));
  307     ASSERT_TRUE(answer);
  308     EXPECT_EQ("{ \"arguments\": [ \"list-commands\" ], \"result\": 0 }",
  309               answer->str());
  310 }
  311 
  312 // Test checks whether a command handler can be installed and then
  313 // runs through processCommand to check that it's indeed called.
  314 TEST_F(CommandMgrTest, processCommand) {
  315     // Install my handler
  316     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
  317                                                            my_handler));
  318 
  319     // Now tell CommandMgr to process a command 'my-command' with the
  320     // specified parameter.
  321     ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
  322     ConstElementPtr command = createCommand("my-command", my_params);
  323     ConstElementPtr answer;
  324     EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
  325 
  326     // There should be an answer.
  327     ASSERT_TRUE(answer);
  328 
  329     // my_handler remembers all passed parameters and returns status code of 123.
  330     ConstElementPtr answer_arg;
  331     int status_code;
  332     // Check that the returned status code is correct.
  333     EXPECT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
  334     EXPECT_EQ(123, status_code);
  335 
  336     // Check that the parameters passed are correct.
  337     EXPECT_EQ(true, handler_called_);
  338     EXPECT_EQ("my-command", handler_name_);
  339     ASSERT_TRUE(handler_params_);
  340     EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", handler_params_->str());
  341 
  342     // Command handlers not installed so expecting that callouts weren't
  343     // called.
  344     EXPECT_TRUE(callout_name_.empty());
  345 }
  346 
  347 // Verify that processing a command can be delegated to a hook library.
  348 TEST_F(CommandMgrTest, delegateProcessCommand) {
  349     // Register callout so as we can check that it is called before
  350     // processing the command by the manager.
  351     HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
  352         "my-command", hook_lib_callout);
  353 
  354     // Install local handler
  355     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
  356                                                            my_handler));
  357 
  358     // Now tell CommandMgr to process a command 'my-command' with the
  359     // specified parameter.
  360     ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
  361     ConstElementPtr command = createCommand("my-command", my_params);
  362     ConstElementPtr answer;
  363     ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
  364 
  365     // There should be an answer.
  366     ASSERT_TRUE(answer);
  367 
  368     // Local handler shouldn't be called because the command is handled by the
  369     // hook library.
  370     ASSERT_FALSE(handler_called_);
  371 
  372     // Returned status should be unique for the hook library.
  373     ConstElementPtr answer_arg;
  374     int status_code;
  375     ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
  376     EXPECT_EQ(234, status_code);
  377 
  378     EXPECT_EQ("hook_lib_callout", callout_name_);
  379 
  380     // Check that the appropriate arguments have been set. Include the
  381     // 'response' which should have been set by the callout.
  382     ASSERT_EQ(2, callout_argument_names_.size());
  383     EXPECT_EQ("command", callout_argument_names_[0]);
  384     EXPECT_EQ("response", callout_argument_names_[1]);
  385 }
  386 
  387 // Verify that 'list-command' command returns combined list of supported
  388 // commands from hook library and from the Kea Command Manager.
  389 TEST_F(CommandMgrTest, delegateListCommands) {
  390     // Register callout so as we can check that it is called before
  391     // processing the command by the manager.
  392     HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
  393         "my-command", hook_lib_callout);
  394 
  395     // Create my-command-bis which is unique for the local Command Manager,
  396     // i.e. not supported by the hook library. This command should also
  397     // be returned as a result of processing 'list-commands'.
  398     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command-bis",
  399                                                            my_handler));
  400 
  401     // Process command. The command should be routed to the hook library
  402     // and the hook library should return the commands it supports.
  403     ConstElementPtr command = createCommand("list-commands");
  404     ConstElementPtr answer;
  405     ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
  406 
  407     // There should be an answer.
  408     ASSERT_TRUE(answer);
  409 
  410     ConstElementPtr answer_arg;
  411     int status_code;
  412     ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
  413     EXPECT_EQ(0, status_code);
  414 
  415     // The hook library supports: my-command and list-commands commands. The
  416     // local Command Manager supports list-commands and my-command-bis. The
  417     // combined list should include 3 unique commands.
  418     const std::vector<ElementPtr>& commands_list = answer_arg->listValue();
  419     ASSERT_EQ(3, commands_list.size());
  420     std::vector<std::string> command_names_list;
  421     for (auto cmd = commands_list.cbegin(); cmd != commands_list.cend();
  422          ++cmd) {
  423         command_names_list.push_back((*cmd)->stringValue());
  424     }
  425     std::sort(command_names_list.begin(), command_names_list.end());
  426     EXPECT_EQ("list-commands", command_names_list[0]);
  427     EXPECT_EQ("my-command", command_names_list[1]);
  428     EXPECT_EQ("my-command-bis", command_names_list[2]);
  429 }
  430 
  431 // This test verifies that a Unix socket can be opened properly and that input
  432 // parameters (socket-type and socket-name) are verified.
  433 TEST_F(CommandMgrTest, unixCreate) {
  434     // Null pointer is obviously a bad idea.
  435     EXPECT_THROW(CommandMgr::instance().openCommandSocket(ConstElementPtr()),
  436                  isc::config::BadSocketInfo);
  437 
  438     // So is passing no parameters.
  439     ElementPtr socket_info = Element::createMap();
  440     EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
  441                  isc::config::BadSocketInfo);
  442 
  443     // We don't support ipx sockets
  444     socket_info->set("socket-type", Element::create("ipx"));
  445     EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
  446                  isc::config::BadSocketInfo);
  447 
  448     socket_info->set("socket-type", Element::create("unix"));
  449     EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
  450                  isc::config::BadSocketInfo);
  451 
  452     socket_info->set("socket-name", Element::create(getSocketPath()));
  453     EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
  454     EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
  455 
  456     // It should be possible to close the socket.
  457     EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
  458 }
  459 
  460 // This test checks that when unix path is too long, the socket cannot be opened.
  461 TEST_F(CommandMgrTest, unixCreateTooLong) {
  462     ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
  463         "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
  464         "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
  465         "\" }");
  466 
  467     EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
  468                  SocketError);
  469 }
  470 
  471 // This test verifies that a registered callout for the command_processed
  472 // hookpoint is invoked and passed the correct information.
  473 TEST_F(CommandMgrTest, commandProcessedHook) {
  474     // Register callout so as we can check that it is called before
  475     // processing the command by the manager.
  476     HooksManager::preCalloutsLibraryHandle().registerCallout(
  477         "command_processed", command_processed_callout);
  478 
  479     // Install local handler
  480     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
  481                                                            my_handler));
  482 
  483     // Now tell CommandMgr to process a command 'my-command' with the
  484     // specified parameter.
  485     ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
  486     ConstElementPtr command = createCommand("my-command", my_params);
  487     ConstElementPtr answer;
  488     ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
  489 
  490     // There should be an answer.
  491     ASSERT_TRUE(answer);
  492 
  493     // Local handler should be called
  494     ASSERT_TRUE(handler_called_);
  495 
  496     // Verify that the response came through intact
  497     EXPECT_EQ("{ \"result\": 123, \"text\": \"test error message\" }",
  498               answer->str());
  499 
  500     // Make sure invoked the command_processed callout
  501     EXPECT_EQ("command_processed_handler", callout_name_);
  502 
  503     // Verify the callout could extract all the context arguments
  504     EXPECT_EQ("my-command:[ \"just\", \"some\", \"data\" ]:"
  505               "{ \"result\": 123, \"text\": \"test error message\" }",
  506               processed_log_);
  507 }
  508 
  509 // This test verifies that a registered callout for the command_processed
  510 // hookpoint is invoked and can replace the command response content.
  511 TEST_F(CommandMgrTest, commandProcessedHookReplaceResponse) {
  512     // Register callout so as we can check that it is called before
  513     // processing the command by the manager.
  514     HooksManager::preCalloutsLibraryHandle().registerCallout(
  515         "command_processed", command_processed_callout);
  516 
  517     // Install local handler
  518     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
  519                                                            my_handler));
  520 
  521     // Now tell CommandMgr to process a command 'my-command' with the
  522     // specified parameter.
  523     ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
  524     ConstElementPtr command = createCommand("change-response", my_params);
  525     ConstElementPtr answer;
  526     ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
  527 
  528     // There should be an answer.
  529     ASSERT_TRUE(answer);
  530 
  531     // Local handler should not have been called, command isn't recognized
  532     ASSERT_FALSE(handler_called_);
  533 
  534     // Verify that we overrode response came
  535     EXPECT_EQ("{ \"result\": 777, \"text\": \"replaced response text\" }",
  536               answer->str());
  537 
  538     // Make sure invoked the command_processed callout
  539     EXPECT_EQ("command_processed_handler", callout_name_);
  540 
  541     // Verify the callout could extract all the context arguments
  542     EXPECT_EQ("change-response:[ \"just\", \"some\", \"data\" ]:"
  543              "{ \"result\": 2, \"text\": \"'change-response' command not supported.\" }",
  544               processed_log_);
  545 }
  546 
  547 // Verifies that a socket cannot be concurrently opened more than once.
  548 TEST_F(CommandMgrTest, exclusiveOpen) {
  549     // Pass in valid parameters.
  550     ElementPtr socket_info = Element::createMap();
  551     socket_info->set("socket-type", Element::create("unix"));
  552     socket_info->set("socket-name", Element::create(getSocketPath()));
  553 
  554     EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
  555     EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
  556 
  557     // Should not be able to open it twice.
  558     EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
  559                  isc::config::SocketError);
  560 
  561     // Now let's close it.
  562     EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
  563 
  564     // Should be able to re-open it now.
  565     EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
  566     EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
  567 
  568     // Now let's close it.
  569     EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
  570 }