Browse Source

[3797] Added support for Control Channel to DHCPv6

src/bin/dhcp6/ctrl_dhcp6_srv.cc
    ControlledDhcpv6Srv::ControlledDhcpv6Srv()
        added CommandMgr init and handler registration

    ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
        added CommandMgr shutdown and handler deregistration

src/bin/dhcp6/json_config_parser.cc
    - createGlobal6DhcpConfigParser()
        added support for "control-socket" element

    - configureDhcp6Server()
        added logic to configure CommandMgr based on
        control-socket configuration element

src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
    - UnixControlClient  - new class that acts as UnixCommandSocket client    - CtrlChannelDhcpv6SrvTest - new test fixture for testing a DHCPv6 server
    with a Control Channel

    - Added the following tests:
    TEST_F(CtrlDhcpv6SrvTest, commandsRegistration)
    TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelNegative)
    TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelShutdown)
    TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelStats)
Thomas Markwalder 9 years ago
parent
commit
e103da1162

+ 44 - 2
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -14,14 +14,18 @@
 
 
 #include <config.h>
 #include <config.h>
 #include <cc/data.h>
 #include <cc/data.h>
+#include <config/command_mgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
-#include <hooks/hooks_manager.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/json_config_parser.h>
+#include <hooks/hooks_manager.h>
+#include <stats/stats_mgr.h>
 
 
+using namespace isc::config;
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::hooks;
 using namespace isc::hooks;
+using namespace isc::stats;
 using namespace std;
 using namespace std;
 
 
 namespace isc {
 namespace isc {
@@ -158,6 +162,32 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
                   "There is another Dhcpv6Srv instance already.");
                   "There is another Dhcpv6Srv instance already.");
     }
     }
     server_ = this; // remember this instance for use in callback
     server_ = this; // remember this instance for use in callback
+
+    // Register supported commands in CommandMgr
+    CommandMgr::instance().registerCommand("shutdown",
+        boost::bind(&ControlledDhcpv6Srv::commandShutdownHandler, this, _1, _2));
+
+    /// @todo: register config-reload (see CtrlDhcpv4Srv::commandConfigReloadHandler)
+    /// @todo: register libreload (see CtrlDhcpv4Srv::commandLibReloadHandler)
+
+    // Register statistic related commands
+    CommandMgr::instance().registerCommand("statistic-get",
+        boost::bind(&StatsMgr::statisticGetHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("statistic-reset",
+        boost::bind(&StatsMgr::statisticResetHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("statistic-remove",
+        boost::bind(&StatsMgr::statisticRemoveHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("statistic-get-all",
+        boost::bind(&StatsMgr::statisticGetAllHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("statistic-reset-all",
+        boost::bind(&StatsMgr::statisticResetAllHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("statistic-remove-all",
+        boost::bind(&StatsMgr::statisticRemoveAllHandler, _1, _2));
 }
 }
 
 
 void ControlledDhcpv6Srv::shutdown() {
 void ControlledDhcpv6Srv::shutdown() {
@@ -168,6 +198,18 @@ void ControlledDhcpv6Srv::shutdown() {
 ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
 ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
     cleanup();
     cleanup();
 
 
+   // Close the command socket (if it exists).
+    CommandMgr::instance().closeCommandSocket();
+
+    // Deregister any registered commands
+    CommandMgr::instance().deregisterCommand("shutdown");
+    CommandMgr::instance().deregisterCommand("statistic-get");
+    CommandMgr::instance().deregisterCommand("statistic-reset");
+    CommandMgr::instance().deregisterCommand("statistic-remove");
+    CommandMgr::instance().deregisterCommand("statistic-get-all");
+    CommandMgr::instance().deregisterCommand("statistic-reset-all");
+    CommandMgr::instance().deregisterCommand("statistic-remove-all");
+
     server_ = NULL; // forget this instance. There should be no callback anymore
     server_ = NULL; // forget this instance. There should be no callback anymore
                     // at this stage anyway.
                     // at this stage anyway.
 }
 }

+ 23 - 0
src/bin/dhcp6/json_config_parser.cc

@@ -17,6 +17,7 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/data.h>
 #include <cc/command_interpreter.h>
 #include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
@@ -693,6 +694,8 @@ namespace dhcp {
                                                 globalContext());
                                                 globalContext());
     } else if (config_id.compare("relay-supplied-options") == 0) {
     } else if (config_id.compare("relay-supplied-options") == 0) {
         parser = new RSOOListConfigParser(config_id);
         parser = new RSOOListConfigParser(config_id);
+    } else if (config_id.compare("control-socket") == 0) {
+        parser = new ControlSocketParser(config_id);
     } else {
     } else {
         isc_throw(DhcpConfigError,
         isc_throw(DhcpConfigError,
                 "unsupported global configuration parameter: "
                 "unsupported global configuration parameter: "
@@ -815,6 +818,26 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
             subnet_parser->build(subnet_config->second);
             subnet_parser->build(subnet_config->second);
         }
         }
 
 
+        // Get command socket configuration from the config file.
+        // This code expects the following structure:
+        // {
+        //     "socket-type": "unix",
+        //     "socket-name": "/tmp/kea6.sock"
+        // }
+        ConstElementPtr sock_cfg =
+            CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
+
+        // Close existing socket (if any).
+        isc::config::CommandMgr::instance().closeCommandSocket();
+        if (sock_cfg) {
+            // This will create a control socket and will install external socket
+            // in IfaceMgr. That socket will be monitored when Dhcp4Srv::receivePacket()
+            // calls IfaceMgr::receive4() and callback in CommandMgr will be called,
+            // if necessary. If there were previously open command socket, it will
+            // be closed.
+            isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
+        }
+
         // The lease database parser is the last to be run.
         // The lease database parser is the last to be run.
         std::map<std::string, ConstElementPtr>::const_iterator leases_config =
         std::map<std::string, ConstElementPtr>::const_iterator leases_config =
             values_map.find("lease-database");
             values_map.find("lease-database");

+ 2 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -21,6 +21,8 @@ AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
 AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 
 CLEANFILES  = $(builddir)/logger_lockfile
 CLEANFILES  = $(builddir)/logger_lockfile

+ 358 - 3
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -15,6 +15,7 @@
 #include <config.h>
 #include <config.h>
 
 
 #include <cc/command_interpreter.h>
 #include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
@@ -25,7 +26,11 @@
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
+#include <sys/select.h>
+#include <sys/ioctl.h>
+
 using namespace std;
 using namespace std;
+using namespace isc::config;
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::dhcp::test;
@@ -33,10 +38,150 @@ using namespace isc::hooks;
 
 
 namespace {
 namespace {
 
 
+/// Class that acts as a UnixCommandSocket client
+/// It can connect to an open UnixCommandSocket  and exchange ControlChannel
+/// commands and responses.
+class UnixControlClient {
+public:
+    UnixControlClient() {
+        socket_fd_ = -1;
+    }
+
+    ~UnixControlClient() {
+        disconnectFromServer();
+    }
+
+    /// @brief Closes the Control Channel socket
+    void disconnectFromServer() {
+        if (socket_fd_ >= 0) {
+            close(socket_fd_);
+            socket_fd_ = -1;
+        }
+    }
+
+    /// @brief Connects to a Unix socket at the given path
+    /// @param socket_path pathname of the socket to open
+    /// @return true if the connect was successful, false otherwise
+    bool connectToServer(const std::string& socket_path) {
+        // Create UNIX socket
+        socket_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+        if (socket_fd_ < 0) {
+            const char* errmsg = strerror(errno);
+            ADD_FAILURE() << "Failed to open unix stream socket: " << errmsg;
+            return (false);
+        }
+
+        // Prepare socket address
+        struct sockaddr_un srv_addr;
+        memset(&srv_addr, 0, sizeof(struct sockaddr_un));
+        srv_addr.sun_family = AF_UNIX;
+        strncpy(srv_addr.sun_path, socket_path.c_str(),
+                sizeof(srv_addr.sun_path));
+        socklen_t len = sizeof(srv_addr);
+
+        // Connect to the specified UNIX socket
+        int status = connect(socket_fd_, (struct sockaddr*)&srv_addr, len);
+        if (status == -1) {
+            const char* errmsg = strerror(errno);
+            ADD_FAILURE() << "Failed to connect unix socket: fd=" << socket_fd_
+                          << ", path=" << socket_path << " : " << errmsg;
+            disconnectFromServer();
+            return (false);
+        }
+
+        return (true);
+    }
+
+    /// @brief Sends the given command across the open Control Channel
+    /// @param command the command text to execute in JSON form
+    /// @return true if the send succeeds, false otherwise
+    bool sendCommand(const std::string& command) {
+        // Send command
+        int bytes_sent = send(socket_fd_, command.c_str(), command.length(), 0);
+        if (bytes_sent < command.length()) {
+            const char* errmsg = strerror(errno);
+            ADD_FAILURE() << "Failed to send " << command.length()
+                      << " bytes, send() returned " << bytes_sent
+                      << " : " << errmsg;
+            return (false);
+        }
+
+        return (true);
+    }
+
+    /// @brief Reads the response text from the open Control Channel
+    /// @param response variable into which the received response should be
+    /// placed.
+    /// @return true if data was successfully read from the socket,
+    /// false otherwise
+    bool getResponse(std::string& response) {
+        // Receive response
+        // @todo implement select check to see if data is waiting
+        char buf[65536];
+        memset(buf, 0, sizeof(buf));
+        switch (selectCheck()) {
+        case -1: {
+            const char* errmsg = strerror(errno);
+            ADD_FAILURE() << "getResponse - select failed: " << errmsg;
+            return (false);
+        }
+        case 0:
+            ADD_FAILURE() << "No response data sent";
+            return (false);
+
+        default:
+            break;
+        }
+
+        int bytes_rcvd = recv(socket_fd_, buf, sizeof(buf), 0);
+        if (bytes_rcvd < 0) {
+            const char* errmsg = strerror(errno);
+            ADD_FAILURE() << "Failed to receive a response. recv() returned "
+                      << bytes_rcvd << " : " << errmsg;
+            return (false);
+        }
+
+        // Convert the response to a string
+        response = string(buf, bytes_rcvd);
+        return (true);
+    }
+
+
+    /// @brief Uses select to poll the Control Channel for data waiting
+    /// @return -1 on error, 0 if no data is available,  1 if data is ready
+    int selectCheck() {
+        fd_set read_fds;
+        int maxfd = 0;
+
+        FD_ZERO(&read_fds);
+
+        // Add this socket to listening set
+        FD_SET(socket_fd_,  &read_fds);
+        maxfd = socket_fd_;
+
+        struct timeval select_timeout;
+        select_timeout.tv_sec = 0;
+        select_timeout.tv_usec = 0;
+
+        return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+    }
+
+    /// @brief Retains the fd of the open socket
+    int socket_fd_;
+};
+
+
 class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
 class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
     // "Naked" DHCPv6 server, exposes internal fields
     // "Naked" DHCPv6 server, exposes internal fields
 public:
 public:
-    NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) { }
+    NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) {
+    }
+
+    /// @brief Exposes server's receivePacket method
+    virtual Pkt6Ptr receivePacket(int timeout) {
+        return(Dhcpv6Srv::receivePacket(timeout));
+    }
+
 };
 };
 
 
 class CtrlDhcpv6SrvTest : public ::testing::Test {
 class CtrlDhcpv6SrvTest : public ::testing::Test {
@@ -45,24 +190,124 @@ public:
         reset();
         reset();
     }
     }
 
 
-    ~CtrlDhcpv6SrvTest() {
+    virtual ~CtrlDhcpv6SrvTest() {
         reset();
         reset();
     };
     };
 
 
+
     /// @brief Reset hooks data
     /// @brief Reset hooks data
     ///
     ///
     /// Resets the data for the hooks-related portion of the test by ensuring
     /// Resets the data for the hooks-related portion of the test by ensuring
     /// that no libraries are loaded and that any marker files are deleted.
     /// that no libraries are loaded and that any marker files are deleted.
-    void reset() {
+    virtual void reset() {
         // Unload any previously-loaded libraries.
         // Unload any previously-loaded libraries.
         HooksManager::unloadLibraries();
         HooksManager::unloadLibraries();
 
 
         // Get rid of any marker files.
         // Get rid of any marker files.
         static_cast<void>(unlink(LOAD_MARKER_FILE));
         static_cast<void>(unlink(LOAD_MARKER_FILE));
         static_cast<void>(unlink(UNLOAD_MARKER_FILE));
         static_cast<void>(unlink(UNLOAD_MARKER_FILE));
+        IfaceMgr::instance().deleteAllExternalSockets();
+        CfgMgr::instance().clear();
+    }
+
+};
+
+class CtrlChannelDhcpv6SrvTest : public CtrlDhcpv6SrvTest {
+public:
+    std::string socket_path_;
+    boost::shared_ptr<NakedControlledDhcpv6Srv> server_;
+
+    CtrlChannelDhcpv6SrvTest() {
+        socket_path_ = string(TEST_DATA_DIR) + "/kea6.sock";
+        reset();
+    }
+
+    ~CtrlChannelDhcpv6SrvTest() {
+        server_.reset();
+        reset();
+    };
+
+    void createUnixChannelServer() {
+        ::remove(socket_path_.c_str());
+
+        // Just a simple config. The important part here is the socket
+        // location information.
+        std::string config_txt =
+            "{"
+            "    \"interfaces-config\": {"
+            "        \"interfaces\": [ \"*\" ]"
+            "    },"
+            "    \"rebind-timer\": 2000, "
+            "    \"renew-timer\": 1000, "
+            "    \"subnet6\": [ ],"
+            "    \"valid-lifetime\": 4000,"
+            "    \"control-socket\": {"
+            "        \"socket-type\": \"unix\","
+            "        \"socket-name\": \"" + socket_path_ + "\""
+            "    },"
+            "    \"lease-database\": {"
+            "       \"type\": \"memfile\", \"persist\": false }"
+            "}";
+
+        ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
+
+        ConstElementPtr config = Element::fromJSON(config_txt);
+        ConstElementPtr answer = server_->processConfig(config);
+        ASSERT_TRUE(answer);
+
+        int status = 0;
+        isc::config::parseAnswer(status, answer);
+        ASSERT_EQ(0, status);
+
+        // Now check that the socket was indeed open.
+        ASSERT_GT(isc::config::CommandMgr::instance().getControlSocketFD(), -1);
+    }
+
+    /// @brief Reset
+    void reset() {
+        CtrlDhcpv6SrvTest::reset();
+        ::remove(socket_path_.c_str());
+    }
+
+    /// @brief Conducts a command/response exchange via UnixCommandSocket
+    ///
+    /// This method connects to the given server over the given socket path.
+    /// If successful, it then sends the given command and retrieves the
+    /// server's response.  Note that it calls the server's receivePacket()
+    /// method where needed to cause the server to process IO events on
+    /// control channel the control channel sockets.
+    ///
+    /// @param command the command text to execute in JSON form
+    /// @param response variable into which the received response should be
+    /// placed.
+    void sendUnixCommand(const std::string& command, std::string& response) {
+        response = "";
+        boost::scoped_ptr<UnixControlClient> client;
+        client.reset(new UnixControlClient());
+        ASSERT_TRUE(client);
+
+        // Connect and then call server's receivePacket() so it can
+        // detect the control socket connect and call the  accept handler
+        ASSERT_TRUE(client->connectToServer(socket_path_));
+        ASSERT_NO_THROW(server_->receivePacket(0));
+
+        // Send the command and then call server's receivePacket() so it can
+        // detect the inbound data and call the read handler
+        ASSERT_TRUE(client->sendCommand(command));
+        ASSERT_NO_THROW(server_->receivePacket(0));
+
+        // Read the response generated by the server. Note that getResponse
+        // only fails if there an IO error or no response data was present.
+        // It is not based on the response content.
+        ASSERT_TRUE(client->getResponse(response));
+
+        // Now disconnect and process the close event
+        client->disconnectFromServer();
+        ASSERT_NO_THROW(server_->receivePacket(0));
     }
     }
 };
 };
 
 
+
 TEST_F(CtrlDhcpv6SrvTest, commands) {
 TEST_F(CtrlDhcpv6SrvTest, commands) {
 
 
     boost::scoped_ptr<ControlledDhcpv6Srv> srv;
     boost::scoped_ptr<ControlledDhcpv6Srv> srv;
@@ -202,4 +447,114 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
     CfgMgr::instance().clear();
     CfgMgr::instance().clear();
 }
 }
 
 
+// This test checks which commands are registered by the DHCPv4 server.
+TEST_F(CtrlDhcpv6SrvTest, commandsRegistration) {
+
+    ConstElementPtr list_cmds = createCommand("list-commands");
+    ConstElementPtr answer;
+
+    // By default the list should be empty (except the standard list-commands
+    // supported by the CommandMgr itself)
+    EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+    ASSERT_TRUE(answer);
+    ASSERT_TRUE(answer->get("arguments"));
+    EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+
+    // Created server should register several additional commands.
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0));
+    );
+
+    EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+    ASSERT_TRUE(answer);
+    ASSERT_TRUE(answer->get("arguments"));
+    EXPECT_EQ("[ \"list-commands\", \"shutdown\", "
+              "\"statistic-get\", \"statistic-get-all\", "
+              "\"statistic-remove\", \"statistic-remove-all\", "
+              "\"statistic-reset\", \"statistic-reset-all\" ]",
+              answer->get("arguments")->str());
+
+    // Ok, and now delete the server. It should deregister its commands.
+    srv.reset();
+
+    // The list should be (almost) empty again.
+    EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+    ASSERT_TRUE(answer);
+    ASSERT_TRUE(answer->get("arguments"));
+    EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+}
+
+// Tests that the server properly responds to invalid commands sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelNegative) {
+    createUnixChannelServer();
+    std::string response;
+
+    sendUnixCommand("{ \"command\": \"bogus\" }", response);
+    EXPECT_EQ("{ \"result\": 1,"
+              " \"text\": \"'bogus' command not supported.\" }", response);
+
+    sendUnixCommand("utter nonsense", response);
+    EXPECT_EQ("{ \"result\": 1, "
+              "\"text\": \"error: unexpected character u in <string>:1:2\" }",
+              response);
+}
+
+// Tests that the server properly responds to shtudown command sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelShutdown) {
+    createUnixChannelServer();
+    std::string response;
+
+    sendUnixCommand("{ \"command\": \"shutdown\" }", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
+}
+
+// Tests that the server properly responds to statistics commands.  Note this
+// is really only intended to verify that the appropriate Statistics handler
+// is called based on the command.  It is not intended to be an exhaustive
+// test of Dhcpv6 statistics.
+TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelStats) {
+    createUnixChannelServer();
+    std::string response;
+
+    // Check statistic-get
+    sendUnixCommand("{ \"command\" : \"statistic-get\", "
+                    "  \"arguments\": {"
+                    "  \"name\":\"bogus\" }}", response);
+    EXPECT_EQ("{ \"arguments\": {  }, \"result\": 0 }", response);
+
+    // Check statistic-get-all
+    sendUnixCommand("{ \"command\" : \"statistic-get-all\", "
+                    "  \"arguments\": {}}", response);
+    EXPECT_EQ("{ \"arguments\": {  }, \"result\": 0 }", response);
+
+    // Check statistic-reset
+    sendUnixCommand("{ \"command\" : \"statistic-reset\", "
+                    "  \"arguments\": {"
+                    "  \"name\":\"bogus\" }}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+              response);
+
+    // Check statistic-reset-all
+    sendUnixCommand("{ \"command\" : \"statistic-reset-all\", "
+                    "  \"arguments\": {}}", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": "
+              "\"All statistics reset to neutral values.\" }", response);
+
+    // Check statistic-remove
+    sendUnixCommand("{ \"command\" : \"statistic-remove\", "
+                    "  \"arguments\": {"
+                    "  \"name\":\"bogus\" }}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+              response);
+
+    // Check statistic-remove-all
+    sendUnixCommand("{ \"command\" : \"statistic-remove-all\", "
+                    "  \"arguments\": {}}", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics removed.\" }",
+              response);
+}
+
 } // End of anonymous namespace
 } // End of anonymous namespace