Browse Source

[3880] Initial implementation:

 - CommandMgr class implemented
 - unit-tests for CommandMgr implemented
 - Stub implementation for CommandSocketFactory
Tomek Mrugalski 10 years ago
parent
commit
6888647893

+ 3 - 0
src/lib/config/Makefile.am

@@ -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

+ 130 - 0
src/lib/config/command_mgr.cc

@@ -0,0 +1,130 @@
+// 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

+ 171 - 0
src/lib/config/command_mgr.h

@@ -0,0 +1,171 @@
+// 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

+ 31 - 0
src/lib/config/command_socket_factory.cc

@@ -0,0 +1,31 @@
+// 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) {
+
+}
+
+};
+};

+ 56 - 0
src/lib/config/command_socket_factory.h

@@ -0,0 +1,56 @@
+// 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

+ 1 - 0
src/lib/config/tests/Makefile.am

@@ -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)

+ 223 - 0
src/lib/config/tests/command_mgr_unittests.cc

@@ -0,0 +1,223 @@
+// 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));
+
+    // 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());
+}