123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- // 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
- // file, You can obtain one at http://mozilla.org/MPL/2.0/.
- #include <gtest/gtest.h>
- #include <asiolink/io_service.h>
- #include <config/base_command_mgr.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::asiolink;
- using namespace isc::config;
- using namespace isc::data;
- using namespace isc::hooks;
- using namespace std;
- // Test class for Command Manager
- class CommandMgrTest : public ::testing::Test {
- public:
- /// Default constructor
- CommandMgrTest()
- : io_service_(new IOService()) {
- CommandMgr::instance().setIOService(io_service_);
- handler_name_ = "";
- handler_params_ = ElementPtr();
- handler_called_ = false;
- CommandMgr::instance().deregisterAll();
- CommandMgr::instance().closeCommandSocket();
- resetCalloutIndicators();
- }
- /// Default destructor
- virtual ~CommandMgrTest() {
- CommandMgr::instance().deregisterAll();
- CommandMgr::instance().closeCommandSocket();
- resetCalloutIndicators();
- }
- /// @brief Returns socket path (using either hardcoded path or env variable)
- /// @return path to the unix socket
- std::string getSocketPath() {
- std::string socket_path;
- const char* env = getenv("KEA_SOCKET_TEST_DIR");
- if (env) {
- socket_path = std::string(env) + "/test-socket";
- } else {
- socket_path = std::string(TEST_DATA_BUILDDIR) + "/test-socket";
- }
- return (socket_path);
- }
- /// @brief Resets indicators related to callout invocation.
- ///
- /// It also removes any registered callouts.
- static void resetCalloutIndicators() {
- callout_name_ = "";
- callout_argument_names_.clear();
- // Iterate over existing hook points and for each of them remove
- // callouts registered.
- std::vector<std::string> hooks = ServerHooks::getServerHooksPtr()->getHookNames();
- for (auto h = hooks.cbegin(); h != hooks.cend(); ++h) {
- HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(*h);
- }
- }
- /// @brief A simple command handler that always returns an eror
- static ConstElementPtr my_handler(const std::string& name,
- const ConstElementPtr& params) {
- handler_name_ = name;
- handler_params_ = params;
- handler_called_ = true;
- 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 and
- /// which handles the command.
- ///
- /// @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_handle";
- ConstElementPtr command;
- callout_handle.getArgument("command", command);
- ConstElementPtr arg;
- std::string command_name = parseCommand(arg, command);
- callout_handle.setArgument("response",
- createAnswer(234, "text generated by hook handler"));
- 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 IO service used by these tests.
- IOServicePtr io_service_;
- /// @brief Name of the command (used in my_handler)
- static std::string handler_name_;
- /// @brief Parameters passed to the handler (used in my_handler)
- static ConstElementPtr handler_params_;
- /// @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)
- std::string CommandMgrTest::handler_name_("");
- /// Parameters passed to the handler (used in my_handler)
- 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) {
- ConstElementPtr command = createCommand("list-commands");
- ConstElementPtr answer;
- EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
- ASSERT_TRUE(answer);
- EXPECT_EQ("{ \"arguments\": [ \"list-commands\" ], \"result\": 0 }",
- answer->str());
- }
- // Test checks whether calling a bogus command is handled properly.
- TEST_F(CommandMgrTest, bogusCommand) {
- ConstElementPtr command = createCommand("no-such-command");
- ConstElementPtr answer;
- EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
- // Make sure the status code is non-zero
- ASSERT_TRUE(answer);
- int status_code;
- parseAnswer(status_code, answer);
- EXPECT_EQ(CONTROL_RESULT_COMMAND_UNSUPPORTED, status_code);
- }
- // Test checks whether handlers installation is sanitized. In particular,
- // whether NULL handler and attempt to install handlers for the same
- // command twice are rejected.
- TEST_F(CommandMgrTest, handlerInstall) {
- // Check that it's not allowed to install NULL pointer instead of a real
- // command.
- EXPECT_THROW(CommandMgr::instance().registerCommand("my-command", 0),
- InvalidCommandHandler);
- // This registration should succeed.
- EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
- my_handler));
- // Check that it's not possible to install handlers for the same
- // command twice.
- EXPECT_THROW(CommandMgr::instance().registerCommand("my-command",
- my_handler), InvalidCommandName);
- }
- // Test checks whether the internal list-commands command is working
- // correctly. Also, checks installation and deinstallation of other
- // command handlers.
- TEST_F(CommandMgrTest, listCommands) {
- // Let's install two custom commands.
- EXPECT_NO_THROW(CommandMgr::instance().registerCommand("make-a-coffee",
- my_handler));
- EXPECT_NO_THROW(CommandMgr::instance().registerCommand("do-the-dishes",
- my_handler));
- // And then run 'list-commands'
- ConstElementPtr list_all = createCommand("list-commands");
- ConstElementPtr answer;
- // Now check that the command is returned by list-commands
- EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_all));
- ASSERT_TRUE(answer);
- EXPECT_EQ("{ \"arguments\": [ \"do-the-dishes\", \"list-commands\", "
- "\"make-a-coffee\" ], \"result\": 0 }", answer->str());
- // Now unregister one command
- EXPECT_NO_THROW(CommandMgr::instance().deregisterCommand("do-the-dishes"));
- // Now check that the command is returned by list-commands
- EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_all));
- ASSERT_TRUE(answer);
- EXPECT_EQ("{ \"arguments\": [ \"list-commands\", "
- "\"make-a-coffee\" ], \"result\": 0 }", answer->str());
- // Now test deregistration. It should work the first time.
- EXPECT_NO_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"));
- // Second time should throw an exception as the handler is no longer there.
- EXPECT_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"),
- InvalidCommandName);
- // You can't uninstall list-commands as it's the internal handler.
- // It always must be there.
- EXPECT_THROW(CommandMgr::instance().deregisterCommand("list-commands"),
- InvalidCommandName);
- // Attempt to register a handler for existing command should fail.
- EXPECT_THROW(CommandMgr::instance().registerCommand("list-commands",
- my_handler), InvalidCommandName);
- }
- // Test checks whether deregisterAll method uninstalls all handlers,
- // except list-commands.
- TEST_F(CommandMgrTest, deregisterAll) {
- // Install a couple handlers.
- EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command1",
- my_handler));
- EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command2",
- my_handler));
- EXPECT_NO_THROW(CommandMgr::instance().deregisterAll());
- ConstElementPtr answer;
- EXPECT_NO_THROW(answer = CommandMgr::instance()
- .processCommand(createCommand("list-commands")));
- ASSERT_TRUE(answer);
- EXPECT_EQ("{ \"arguments\": [ \"list-commands\" ], \"result\": 0 }",
- answer->str());
- }
- // Test checks whether a command handler can be installed and then
- // runs through processCommand to check that it's indeed called.
- TEST_F(CommandMgrTest, processCommand) {
- // Install my 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;
- EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
- // There should be an answer.
- ASSERT_TRUE(answer);
- // my_handler remembers all passed parameters and returns status code of 123.
- ConstElementPtr answer_arg;
- int status_code;
- // Check that the returned status code is correct.
- EXPECT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
- EXPECT_EQ(123, status_code);
- // Check that the parameters passed are correct.
- EXPECT_EQ(true, handler_called_);
- EXPECT_EQ("my-command", handler_name_);
- ASSERT_TRUE(handler_params_);
- EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", handler_params_->str());
- // Command handlers not installed so expecting that callouts weren't
- // called.
- EXPECT_TRUE(callout_name_.empty());
- }
- // 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().registerCommandCallout(
- "my-command", 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(234, status_code);
- EXPECT_EQ("control_command_receive_handle", callout_name_);
- // Check that the appropriate arguments have been set. Include the
- // 'response' which should have been set by the callout.
- 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().registerCommandCallout(
- "my-command", 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]);
- }
- // This test verifies that a Unix socket can be opened properly and that input
- // parameters (socket-type and socket-name) are verified.
- TEST_F(CommandMgrTest, unixCreate) {
- // Null pointer is obviously a bad idea.
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(ConstElementPtr()),
- isc::config::BadSocketInfo);
- // So is passing no parameters.
- ElementPtr socket_info = Element::createMap();
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- isc::config::BadSocketInfo);
- // We don't support ipx sockets
- socket_info->set("socket-type", Element::create("ipx"));
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- isc::config::BadSocketInfo);
- socket_info->set("socket-type", Element::create("unix"));
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- isc::config::BadSocketInfo);
- socket_info->set("socket-name", Element::create(getSocketPath()));
- EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
- EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
- // It should be possible to close the socket.
- EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
- }
- // This test checks that when unix path is too long, the socket cannot be opened.
- TEST_F(CommandMgrTest, unixCreateTooLong) {
- ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
- "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
- "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
- "\" }");
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- SocketError);
- }
|