Commit 68886478 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[3880] Initial implementation:

 - CommandMgr class implemented
 - unit-tests for CommandMgr implemented
 - Stub implementation for CommandSocketFactory
parent d3da062d
......@@ -17,6 +17,9 @@ BUILT_SOURCES = config_messages.h config_messages.cc
lib_LTLIBRARIES = libkea-cfgclient.la
libkea_cfgclient_la_SOURCES = config_data.h config_data.cc
libkea_cfgclient_la_SOURCES += module_spec.h module_spec.cc
libkea_cfgclient_la_SOURCES += command_interpreter.cc command_interpreter.h
libkea_cfgclient_la_SOURCES += command_mgr.cc command_mgr.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_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la
......
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config/command_mgr.h>
#include <config/command_socket_factory.h>
#include <cc/data.h>
#include <boost/bind.hpp>
namespace isc {
namespace config {
CommandMgr::CommandMgr() {
registerCommand("list-commands",
boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
}
void CommandMgr::configureCtrlSocket(const isc::data::ConstElementPtr& socket_info) {
if (socket_info_) {
isc_throw(CommandSocketError, "There is already a control socket open");
}
socket_ = CommandSocketFactory::create(socket_info);
socket_info_ = socket_info;
/// @todo: install socket in IfaceMgr
///CommandSocketFactory::install(socket_, socket_info);
}
void CommandMgr::closeCtrlSocket() {
CommandSocketFactory::close(socket_, socket_info_);
socket_ = 0;
socket_info_.reset();
}
CommandMgr&
CommandMgr::instance() {
static CommandMgr cmd_mgr;
return (cmd_mgr);
}
void CommandMgr::registerCommand(const std::string& cmd, CommandHandler handler) {
if (!handler) {
isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
}
HandlerContainer::const_iterator it = handlers_.find(cmd);
if (it != handlers_.end()) {
isc_throw(InvalidCommandName, "Handler for command '" << cmd
<< "' is already installed.");
}
handlers_.insert(make_pair(cmd, handler));
}
void CommandMgr::deregisterCommand(const std::string& cmd) {
if (cmd == "list-commands") {
isc_throw(InvalidCommandName,
"Can't uninstall internal command 'list-commands'");
}
HandlerContainer::iterator it = handlers_.find(cmd);
if (it == handlers_.end()) {
isc_throw(InvalidCommandName, "Handler for command '" << cmd
<< "' not found.");
}
handlers_.erase(it);
}
void CommandMgr::deregisterAll() {
handlers_.clear();
registerCommand("list-commands",
boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
}
isc::data::ConstElementPtr
CommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
if (!cmd) {
return (createAnswer(CONTROL_RESULT_ERROR,
"Command processing failed: NULL command parameter"));
}
try {
isc::data::ConstElementPtr arg;
std::string name = parseCommand(arg, cmd);
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));
} catch (const Exception& e) {
return (createAnswer(CONTROL_RESULT_ERROR,
std::string("Error during command processing:")
+ e.what()));
}
}
isc::data::ConstElementPtr
CommandMgr::listCommandsHandler(const std::string& name,
const isc::data::ConstElementPtr& params) {
using namespace isc::data;
ElementPtr commands = Element::createList();
for (HandlerContainer::const_iterator it = handlers_.begin();
it != handlers_.end(); ++it) {
commands->add(Element::create(it->first));
}
return (createAnswer(CONTROL_RESULT_SUCCESS, commands));
}
}; // end of isc::config
}; // end of isc
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef COMMAND_MGR_H
#define COMMAND_MGR_H
#include <config/command_interpreter.h>
#include <cc/data.h>
#include <boost/noncopyable.hpp>
#include <boost/any.hpp>
#include <string>
#include <map>
namespace isc {
namespace config {
/// @brief CommandMgr exception indicating that the handler specified is not valid
class InvalidCommandHandler : public Exception {
public:
InvalidCommandHandler(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief CommandMgr exception indicating that the command name is not valid
class InvalidCommandName : public Exception {
public:
InvalidCommandName(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief An exception indicating that operation on the command socket failed
class CommandSocketError : public Exception {
public:
CommandSocketError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Commands Manager, responsible for processing external commands
///
/// Commands Manager is a generic interface for handling external commands.
/// Commands can be received over control sockets. Currently unix socket is
/// supported, but additional type (udp, tcp, https etc.) may be added later.
/// The commands and responses are sent in JSON format.
/// See http://kea.isc.org/wiki/StatsDesign for details.
///
/// In general, the command has the following format:
/// {
/// "command": "statistic-get",
/// "arguments": {
/// "name": "received-packets"
/// }
/// }
///
/// And the response is:
///
/// {
/// "result": 0,
/// "observations": {
/// "received-packets": [ [ 1234, "2015-04-15 12:34:45.123" ] ]
/// }
/// }
///
/// CommandsMgr does not implement the commands (except one, "commands-list")
/// itself, but rather provides an interface (see @ref registerCommand,
/// @ref deregisterCommand, @ref processCommand) for other components to use
/// it. The @ref CommandHandler type is specified in a way to easily use
/// existing command handlers in DHCPv4 and DHCPv6 components.
class CommandMgr : public boost::noncopyable {
public:
/// @brief Defines command handler type
///
/// Command handlers are expected to use this format.
/// @param name name of the commands
/// @param params parameters specific to the command
/// @return response (created with createAnswer())
typedef boost::function<isc::data::ConstElementPtr (const std::string& name,
const isc::data::ConstElementPtr& params)> CommandHandler;
/// @brief CommandMgr is a singleton class. This method returns reference
/// to its sole instance.
///
/// @return the only existing instance of the manager
static CommandMgr& instance();
/// @brief Configured control socket with paramters specified in socket_info
///
/// Currently supported types are:
/// - unix (required parameters: socket-type: unix, socket-name:/unix/path)
///
/// @throw CommandSocketError if socket creation fails
///
/// @param socket_info describes control socket parameters
void configureCtrlSocket(const isc::data::ConstElementPtr& socket_info);
/// @brief Shuts down any open control sockets
void closeCtrlSocket();
/// @brief Registers specified command handler for a given command
///
/// @param cmd name of the command to be handled
/// @param handler pointer to the method that will handle the command
void registerCommand(const std::string& cmd, CommandHandler handler);
/// @brief Deregisters specified command handler
///
/// @param cmd name of the command that's no longer handled
void deregisterCommand(const std::string& cmd);
/// @brief Triggers command processing
///
/// This method processes specified command. The command is specified using
/// a single Element. See @ref CommandMgr for description of its syntax.
/// Typically, this method is called internally, when there's a new data
/// received over control socket. However, in some cases (e.g. signal received)
/// it may be called by external code explicitly. Hence this method is public.
isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr& cmd);
/// @brief Auxiliary method that removes all installed commands.
///
/// The only unwipeable method is list-commands, which is internally
/// handled at all times.
void deregisterAll();
private:
/// @brief Private constructor
///
/// Registers internal 'list-commands' command.
CommandMgr();
/// @brief 'list-commands' command handler
///
/// This method implements command 'list-commands'. It returns a list of all
/// currently supported commands.
/// @param name name of the command (should always be 'list-commands')
/// @param params additional parameters (ignored)
/// @return structure that includes all currently supported commands
isc::data::ConstElementPtr
listCommandsHandler(const std::string& name,
const isc::data::ConstElementPtr& params);
typedef std::map<std::string, CommandHandler> HandlerContainer;
/// @brief Container for command handlers
HandlerContainer handlers_;
/// @brief Socket file descriptor
int socket_;
/// @brief Parameters for control socket
isc::data::ConstElementPtr socket_info_;
};
}; // end of isc::config namespace
}; // end of isc namespace
#endif
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config/command_socket_factory.h>
namespace isc {
namespace config {
int
CommandSocketFactory::create(const isc::data::ConstElementPtr& socket_info) {
}
int CommandSocketFactory::close(int socket_fd,
const isc::data::ConstElementPtr& socket_info) {
}
};
};
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef COMMAND_SOCKET_FACTORY_H
#define COMMAND_SOCKET_FACTORY_H
#include <cc/data.h>
namespace isc {
namespace config {
/// A factory class for opening command socket
///
/// This class provides an interface for opening command socket.
class CommandSocketFactory {
public:
/// @brief Creates a socket specified by socket_info structure
///
///
/// Currently supported types are:
/// - unix
///
/// See @ref CommandMgr::configureCtrlSocket for detailed description.
/// @throw CommandSocketError
///
/// @param socket_info structure that describes the socket
/// @return socket descriptor
static int create(const isc::data::ConstElementPtr& socket_info);
/// @brief Closes specified socket
///
/// In most cases it will be a simple close() call, but in more complex
/// (e.g. https) it may perform certain shutdown operations before
/// closing.
/// @param socket_fd file descriptor of the socket
/// @param socket_info structure that was used to open the socket
static int close(int socket_fd, const isc::data::ConstElementPtr& socket_info);
};
};
};
#endif
......@@ -19,6 +19,7 @@ if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = module_spec_unittests.cc
run_unittests_SOURCES += config_data_unittests.cc run_unittests.cc
run_unittests_SOURCES += command_mgr_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
......
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <gtest/gtest.h>
#include <config/command_mgr.h>
#include <config/command_interpreter.h>
using namespace isc::data;
using namespace isc::config;
using namespace std;
// Test class for Command Manager
class CommandMgrTest : public ::testing::Test {
public:
/// Default constructor
CommandMgrTest() {
handler_name = "";
handler_params = ElementPtr();
handler_called = false;
CommandMgr::instance().deregisterAll();
CommandMgr::instance().closeCtrlSocket();
}
/// Default destructor
~CommandMgrTest() {
CommandMgr::instance().deregisterAll();
CommandMgr::instance().closeCtrlSocket();
}
/// @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 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;
};
/// 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);
// 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_ERROR, 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",
NULL), 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 unistall 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));