]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#729,!434] fixed missing commit of runtime option defs on v4
authorRazvan Becheriu <razvan@isc.org>
Tue, 23 Jul 2019 16:51:33 +0000 (19:51 +0300)
committerRazvan Becheriu <razvan@isc.org>
Fri, 6 Sep 2019 06:08:55 +0000 (09:08 +0300)
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

index 4987613534b02ad50388f2f137a2f9660b7c7990..591427c54916e76bcab5ef46b4026bb299700a82 100644 (file)
@@ -8,13 +8,14 @@
 #include <cc/data.h>
 #include <cc/command_interpreter.h>
 #include <config/command_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_db_access.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4to6_ipc.h>
-#include <dhcp4/parser_context.h>
 #include <dhcp4/json_config_parser.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/cfg_db_access.h>
+#include <dhcp4/parser_context.h>
 #include <hooks/hooks.h>
 #include <hooks/hooks_manager.h>
 #include <stats/stats_mgr.h>
 #include <signal.h>
 #include <sstream>
 
-using namespace isc::data;
+using namespace isc::config;
 using namespace isc::db;
+using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::hooks;
-using namespace isc::config;
 using namespace isc::stats;
 using namespace std;
 
@@ -746,6 +747,10 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
         }
     }
 
+    // Finally, we can commit runtime option definitions in libdhcp++. This is
+    // exception free.
+    LibDHCP::commitRuntimeOptionDefs();
+
     // This hook point notifies hooks libraries that the configuration of the
     // DHCPv4 server has completed. It provides the hook library with the pointer
     // to the common IO service object, new server configuration in the JSON
index 14439e52a21dcad48e407374d090a70d9a4e035b..30d00cc791e5ddd9037b5d33d39e5bfa996697c9 100644 (file)
 #include <config/command_mgr.h>
 #include <config/timeouts.h>
 #include <dhcp/dhcp4.h>
-#include <dhcp4/ctrl_dhcp4_srv.h>
-#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
 #include <hooks/hooks_manager.h>
 #include <log/logger_support.h>
 #include <stats/stats_mgr.h>
@@ -136,7 +137,7 @@ public:
     }
 
     void createUnixChannelServer() {
-        ::remove(socket_path_.c_str());
+        static_cast<void>(::remove(socket_path_.c_str()));
 
         // Just a simple config. The important part here is the socket
         // location information.
@@ -179,7 +180,6 @@ public:
         // changed.
         CfgMgr::instance().commit();
 
-
         ASSERT_TRUE(answer);
 
         int status = 0;
@@ -208,7 +208,7 @@ public:
         CfgMgr::instance().clear();
 
         // Remove unix socket file
-        ::remove(socket_path_.c_str());
+        static_cast<void>(::remove(socket_path_.c_str()));
     }
 
     /// @brief Conducts a command/response exchange via UnixCommandSocket
@@ -412,7 +412,6 @@ TEST_F(CtrlChannelDhcpv4SrvTest, commands) {
 }
 
 // Check that the "libreload" command will reload libraries
-
 TEST_F(CtrlChannelDhcpv4SrvTest, libreload) {
     createUnixChannelServer();
 
@@ -427,9 +426,9 @@ TEST_F(CtrlChannelDhcpv4SrvTest, libreload) {
     HooksManager::loadLibraries(libraries);
 
     // Check they are loaded.
-    std::vector<std::string> loaded_libraries =
-        HooksManager::getLibraryNames();
-    ASSERT_TRUE(extractNames(libraries) == loaded_libraries);
+    HookLibsCollection loaded_libraries =
+        HooksManager::getLibraryInfo();
+    ASSERT_TRUE(libraries == loaded_libraries);
 
     // ... which also included checking that the marker file created by the
     // load functions exists and holds the correct value (of "12" - the
@@ -453,303 +452,188 @@ TEST_F(CtrlChannelDhcpv4SrvTest, libreload) {
     EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
 }
 
-// This test checks which commands are registered by the DHCPv4 server.
-TEST_F(CtrlChannelDhcpv4SrvTest, commandsRegistration) {
+// Check that the "config-set" command will replace current configuration
+TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
+    createUnixChannelServer();
 
-    ConstElementPtr list_cmds = createCommand("list-commands");
-    ConstElementPtr answer;
+    // Define strings to permutate the config arguments
+    // (Note the line feeds makes errors easy to find)
+    string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string args_txt = " \"arguments\": { \n";
+    string dhcp4_cfg_txt =
+        "    \"Dhcp4\": { \n"
+        "        \"interfaces-config\": { \n"
+        "            \"interfaces\": [\"*\"] \n"
+        "        },   \n"
+        "        \"valid-lifetime\": 4000, \n"
+        "        \"renew-timer\": 1000, \n"
+        "        \"rebind-timer\": 2000, \n"
+        "        \"lease-database\": { \n"
+        "           \"type\": \"memfile\", \n"
+        "           \"persist\":false, \n"
+        "           \"lfc-interval\": 0  \n"
+        "        }, \n"
+        "       \"expired-leases-processing\": { \n"
+        "            \"reclaim-timer-wait-time\": 0, \n"
+        "            \"hold-reclaimed-time\": 0, \n"
+        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
+        "        },"
+        "        \"subnet4\": [ \n";
+    string subnet1 =
+        "               {\"subnet\": \"192.2.0.0/24\", \n"
+        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+    string subnet2 =
+        "               {\"subnet\": \"192.2.1.0/24\", \n"
+        "                \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
+    string bad_subnet =
+        "               {\"comment\": \"192.2.2.0/24\", \n"
+        "                \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\n";
+    string subnet_footer =
+        "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp4\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
+    string control_socket_header =
+        "       ,\"control-socket\": { \n"
+        "       \"socket-type\": \"unix\", \n"
+        "       \"socket-name\": \"";
+    string control_socket_footer =
+        "\"   \n} \n";
+    string logger_txt =
+        "    \"Logging\": { \n"
+        "        \"loggers\": [ { \n"
+        "            \"name\": \"kea\", \n"
+        "            \"severity\": \"FATAL\", \n"
+        "            \"output_options\": [{ \n"
+        "                \"output\": \"/dev/null\" \n"
+        "            }] \n"
+        "        }] \n"
+        "    } \n";
 
-    // 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());
+    std::ostringstream os;
 
-    // Created server should register several additional commands.
-    ASSERT_NO_THROW(
-        server_.reset(new NakedControlledDhcpv4Srv());
-    );
+    // Create a valid config with all the parts should parse
+    os << set_config_txt << ","
+        << args_txt
+        << dhcp4_cfg_txt
+        << subnet1
+        << subnet_footer
+        << option_def
+        << option_data
+        << control_socket_header
+        << socket_path_
+        << control_socket_footer
+        << "}\n"                      // close dhcp4
+        << ","
+        << logger_txt
+        << "}}";
 
-    EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
-    ASSERT_TRUE(answer);
-    ASSERT_TRUE(answer->get("arguments"));
-    std::string command_list = answer->get("arguments")->str();
+    // Send the config-set command
+    std::string response;
+    sendUnixCommand(os.str(), response);
 
-    EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"build-report\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"config-get\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"libreload\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"server-tag-get\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-remove\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-remove-all\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-reset\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-reset-all\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-sample-age-set\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-sample-age-set-all\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-sample-count-set\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"statistic-sample-count-set-all\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"version-get\"") != string::npos);
+    // Verify the configuration was successful.
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+              response);
 
-    // Ok, and now delete the server. It should deregister its commands.
-    server_.reset();
+    // Check that the config was indeed applied.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
 
-    // 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());
-}
+    OptionDefinitionPtr def = LibDHCP::getRuntimeOptionDef("dhcp4", 163);
+    ASSERT_TRUE(def);
 
-// Tests that the server properly responds to invalid commands sent
-// via ControlChannel
-TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelNegative) {
-    createUnixChannelServer();
-    std::string response;
+    // Create a config with malformed subnet that should fail to parse.
+    os.str("");
+    os << set_config_txt << ","
+        << args_txt
+        << dhcp4_cfg_txt
+        << bad_subnet
+        << subnet_footer
+        << control_socket_header
+        << socket_path_
+        << control_socket_footer
+        << "}\n"                      // close dhcp4
+        "}}";
 
-    sendUnixCommand("{ \"command\": \"bogus\" }", response);
-    EXPECT_EQ("{ \"result\": 2,"
-              " \"text\": \"'bogus' command not supported.\" }", response);
+    // Send the config-set command
+    sendUnixCommand(os.str(), response);
 
-    sendUnixCommand("utter nonsense", response);
+    // Should fail with a syntax error
     EXPECT_EQ("{ \"result\": 1, "
-              "\"text\": \"invalid first character u\" }",
+              "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+              "parameter is missing for a subnet being configured (<wire>:19:17)\" }",
               response);
-}
-
-// Tests that the server properly responds to shtudown command sent
-// via ControlChannel
-TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelShutdown) {
-    createUnixChannelServer();
-    std::string response;
-
-    sendUnixCommand("{ \"command\": \"shutdown\" }", response);
-    EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
-}
 
-// This test verifies that the DHCP server immediately reclaims expired
-// leases on leases-reclaim command
-TEST_F(CtrlChannelDhcpv4SrvTest, controlLeasesReclaim) {
-    createUnixChannelServer();
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(1, subnets->size());
 
-    // Create expired leases. Leases are expired by 40 seconds ago
-    // (valid lifetime = 60, cltt = now - 100).
-    HWAddrPtr hwaddr0(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
-    Lease4Ptr lease0(new Lease4(IOAddress("10.0.0.1"), hwaddr0,
-                                ClientIdPtr(), 60,
-                                time(NULL) - 100, SubnetID(1)));
-    HWAddrPtr hwaddr1(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
-    Lease4Ptr lease1(new Lease4(IOAddress("10.0.0.2"), hwaddr1,
-                                ClientIdPtr(), 60,
-                                time(NULL) - 100, SubnetID(1)));
+    def = LibDHCP::getRuntimeOptionDef("dhcp4", 163);
+    ASSERT_TRUE(def);
 
-    // Add leases to the database.
-    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
-    ASSERT_NO_THROW(lease_mgr.addLease(lease0));
-    ASSERT_NO_THROW(lease_mgr.addLease(lease1));
+    // Create a valid config with two subnets and no command channel.
+    // It should succeed, client should still receive the response
+    os.str("");
+    os << set_config_txt << ","
+        << args_txt
+        << dhcp4_cfg_txt
+        << subnet1
+        << ",\n"
+        << subnet2
+        << subnet_footer
+        << "}\n"                      // close dhcp4
+        << "}}";
 
-    // Make sure they have been added.
-    ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.1")));
-    ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.2")));
+    // Verify the control channel socket exists.
+    ASSERT_TRUE(fileExists(socket_path_));
 
-    // No arguments
-    std::string response;
-    sendUnixCommand("{ \"command\": \"leases-reclaim\" }", response);
-    EXPECT_EQ("{ \"result\": 1, \"text\": "
-              "\"Missing mandatory 'remove' parameter.\" }", response);
+    // Send the config-set command.
+    sendUnixCommand(os.str(), response);
 
-    // Bad argument name
-    sendUnixCommand("{ \"command\": \"leases-reclaim\", "
-                    "\"arguments\": { \"reclaim\": true } }", response);
-    EXPECT_EQ("{ \"result\": 1, \"text\": "
-              "\"Missing mandatory 'remove' parameter.\" }", response);
+    // Verify the control channel socket no longer exists.
+    EXPECT_FALSE(fileExists(socket_path_));
 
-    // Bad remove argument type
-    sendUnixCommand("{ \"command\": \"leases-reclaim\", "
-                    "\"arguments\": { \"remove\": \"bogus\" } }", response);
-    EXPECT_EQ("{ \"result\": 1, \"text\": "
-              "\"'remove' parameter expected to be a boolean.\" }", response);
+    // With no command channel, should still receive the response.
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+              response);
 
-    // Send the command
-    sendUnixCommand("{ \"command\": \"leases-reclaim\", "
-                    "\"arguments\": { \"remove\": false } }", response);
-    EXPECT_EQ("{ \"result\": 0, \"text\": "
-              "\"Reclamation of expired leases is complete.\" }", response);
+    // Check that the config was not lost
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    EXPECT_EQ(2, subnets->size());
 
-    // Leases should be reclaimed, but not removed
-    ASSERT_NO_THROW(lease0 = lease_mgr.getLease4(IOAddress("10.0.0.1")));
-    ASSERT_NO_THROW(lease1 = lease_mgr.getLease4(IOAddress("10.0.0.2")));
-    ASSERT_TRUE(lease0);
-    ASSERT_TRUE(lease1);
-    EXPECT_TRUE(lease0->stateExpiredReclaimed());
-    EXPECT_TRUE(lease1->stateExpiredReclaimed());
+    // Clean up after the test.
+    CfgMgr::instance().clear();
 }
 
-// This test verifies that the DHCP server handles version-get commands
-TEST_F(CtrlChannelDhcpv4SrvTest, getversion) {
-    createUnixChannelServer();
-
-    std::string response;
-
-    // Send the version-get command
-    sendUnixCommand("{ \"command\": \"version-get\" }", response);
-    EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
-    EXPECT_TRUE(response.find("log4cplus") != string::npos);
-    EXPECT_FALSE(response.find("GTEST_VERSION") != string::npos);
-
-    // Send the build-report command
-    sendUnixCommand("{ \"command\": \"build-report\" }", response);
-    EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
-    EXPECT_TRUE(response.find("GTEST_VERSION") != string::npos);
-}
-
-// This test verifies that the DHCP server handles server-tag-get command
-TEST_F(CtrlChannelDhcpv4SrvTest, serverTagGet) {
-    createUnixChannelServer();
-
-    std::string response;
-    std::string expected;
-
-    // Send the server-tag-get command
-    sendUnixCommand("{ \"command\": \"server-tag-get\" }", response);
-    expected = "{ \"arguments\": { \"server-tag\": \"\" }, \"result\": 0 }";
-    EXPECT_EQ(expected, response);
-
-    // Set a value to the server tag
-    CfgMgr::instance().getCurrentCfg()->setServerTag("foobar");
-
-    // Retry...
-    sendUnixCommand("{ \"command\": \"server-tag-get\" }", response);
-    expected = "{ \"arguments\": { \"server-tag\": \"foobar\" }, \"result\": 0 }";
-}
-
-// This test verifies that the DHCP server immediately removed expired
-// This test verifies that the DHCP server immediately removed expired
-// leases on leases-reclaim command with remove = true
-TEST_F(CtrlChannelDhcpv4SrvTest, controlLeasesReclaimRemove) {
-    createUnixChannelServer();
-
-    // Create expired leases. Leases are expired by 40 seconds ago
-    // (valid lifetime = 60, cltt = now - 100).
-    HWAddrPtr hwaddr0(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
-    Lease4Ptr lease0(new Lease4(IOAddress("10.0.0.1"), hwaddr0,
-                                ClientIdPtr(), 60,
-                                time(NULL) - 100, SubnetID(1)));
-    HWAddrPtr hwaddr1(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
-    Lease4Ptr lease1(new Lease4(IOAddress("10.0.0.2"), hwaddr1,
-                                ClientIdPtr(), 60,
-                                time(NULL) - 100, SubnetID(1)));
-
-    // Add leases to the database.
-    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
-    ASSERT_NO_THROW(lease_mgr.addLease(lease0));
-    ASSERT_NO_THROW(lease_mgr.addLease(lease1));
-
-    // Make sure they have been added.
-    ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.1")));
-    ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.2")));
-
-    // Send the command
-    std::string response;
-    sendUnixCommand("{ \"command\": \"leases-reclaim\", "
-                    "\"arguments\": { \"remove\": true } }", response);
-    EXPECT_EQ("{ \"result\": 0, \"text\": "
-              "\"Reclamation of expired leases is complete.\" }", response);
-
-    // Leases should have been removed.
-    ASSERT_NO_THROW(lease0 = lease_mgr.getLease4(IOAddress("10.0.0.1")));
-    ASSERT_NO_THROW(lease1 = lease_mgr.getLease4(IOAddress("10.0.0.2")));
-    EXPECT_FALSE(lease0);
-    EXPECT_FALSE(lease1);
-}
-
-// 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 Dhcpv4 statistics.
-TEST_F(CtrlChannelDhcpv4SrvTest, 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);
-
-    // Check statistic-sample-age-set
-    sendUnixCommand("{ \"command\" : \"statistic-sample-age-set\", "
-                    "  \"arguments\": {"
-                    "  \"name\":\"bogus\", \"duration\": 1245 }}", response);
-    EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
-              response);
-
-    // Check statistic-sample-age-set-all
-    sendUnixCommand("{ \"command\" : \"statistic-sample-age-set-all\", "
-                    "  \"arguments\": {"
-                    "  \"duration\": 1245 }}", response);
-    EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics duration limit are set.\" }",
-              response);
-
-    // Check statistic-sample-count-set
-    sendUnixCommand("{ \"command\" : \"statistic-sample-count-set\", "
-                    "  \"arguments\": {"
-                    "  \"name\":\"bogus\", \"max-samples\": 100 }}", response);
-    EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
-              response);
-
-    // Check statistic-sample-count-set-all
-    sendUnixCommand("{ \"command\" : \"statistic-sample-count-set-all\", "
-                    "  \"arguments\": {"
-                    "  \"max-samples\": 100 }}", response);
-    EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics count limit are set.\" }",
-              response);
-}
-
-// Check that the "config-set" command will replace current configuration
-TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
+// Verify that the "config-test" command will do what we expect.
+TEST_F(CtrlChannelDhcpv4SrvTest, configTest) {
     createUnixChannelServer();
 
     // Define strings to permutate the config arguments
     // (Note the line feeds makes errors easy to find)
     string set_config_txt = "{ \"command\": \"config-set\" \n";
+    string config_test_txt = "{ \"command\": \"config-test\" \n";
     string args_txt = " \"arguments\": { \n";
     string dhcp4_cfg_txt =
         "    \"Dhcp4\": { \n"
@@ -829,7 +713,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
 
     // Create a config with malformed subnet that should fail to parse.
     os.str("");
-    os << set_config_txt << ","
+    os << config_test_txt << ","
         << args_txt
         << dhcp4_cfg_txt
         << bad_subnet
@@ -840,13 +724,13 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
         << "}\n"                      // close dhcp4
         "}}";
 
-    // Send the config-set command
+    // Send the config-test command
     sendUnixCommand(os.str(), response);
 
     // Should fail with a syntax error
     EXPECT_EQ("{ \"result\": 1, "
-              "\"text\": \"subnet configuration failed: mandatory 'subnet' "
-              "parameter is missing for a subnet being configured (<wire>:19:17)\" }",
+              "\"text\": \"subnet configuration failed: mandatory 'subnet' parameter "
+              "is missing for a subnet being configured (<wire>:19:17)\" }",
               response);
 
     // Check that the config was not lost
@@ -854,9 +738,8 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
     EXPECT_EQ(1, subnets->size());
 
     // Create a valid config with two subnets and no command channel.
-    // It should succeed, client should still receive the response
     os.str("");
-    os << set_config_txt << ","
+    os << config_test_txt << ","
         << args_txt
         << dhcp4_cfg_txt
         << subnet1
@@ -869,231 +752,357 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
     // Verify the control channel socket exists.
     ASSERT_TRUE(fileExists(socket_path_));
 
-    // Send the config-set command.
+    // Send the config-test command.
     sendUnixCommand(os.str(), response);
 
-    // Verify the control channel socket no longer exists.
-    EXPECT_FALSE(fileExists(socket_path_));
+    // Verify the control channel socket still exists.
+    EXPECT_TRUE(fileExists(socket_path_));
 
-    // With no command channel, should still receive the response.
-    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+    // Verify the configuration was successful.
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration seems sane. "
+              "Control-socket, hook-libraries, and D2 configuration were "
+              "sanity checked, but not applied.\" }",
               response);
 
-    // Check that the config was not lost
+    // Check that the config was not applied.
     subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
-    EXPECT_EQ(2, subnets->size());
+    EXPECT_EQ(1, subnets->size());
 
     // Clean up after the test.
     CfgMgr::instance().clear();
 }
+// This test checks which commands are registered by the DHCPv4 server.
+TEST_F(CtrlChannelDhcpv4SrvTest, commandsRegistration) {
 
-// Tests that the server properly responds to shtudown command sent
-// via ControlChannel
-TEST_F(CtrlChannelDhcpv4SrvTest, listCommands) {
-    createUnixChannelServer();
-    std::string response;
-
-    sendUnixCommand("{ \"command\": \"list-commands\" }", response);
+    ConstElementPtr list_cmds = createCommand("list-commands");
+    ConstElementPtr answer;
 
-    ConstElementPtr rsp;
-    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+    // 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());
 
-    // We expect the server to report at least the following commands:
-    checkListCommands(rsp, "build-report");
-    checkListCommands(rsp, "config-get");
-    checkListCommands(rsp, "config-reload");
-    checkListCommands(rsp, "config-set");
-    checkListCommands(rsp, "config-write");
-    checkListCommands(rsp, "list-commands");
-    checkListCommands(rsp, "leases-reclaim");
-    checkListCommands(rsp, "libreload");
-    checkListCommands(rsp, "server-tag-get");
-    checkListCommands(rsp, "shutdown");
-    checkListCommands(rsp, "statistic-get");
-    checkListCommands(rsp, "statistic-get-all");
-    checkListCommands(rsp, "statistic-remove");
-    checkListCommands(rsp, "statistic-remove-all");
-    checkListCommands(rsp, "statistic-reset");
-    checkListCommands(rsp, "statistic-reset-all");
-    checkListCommands(rsp, "statistic-sample-age-set");
-    checkListCommands(rsp, "statistic-sample-age-set-all");
-    checkListCommands(rsp, "statistic-sample-count-set");
-    checkListCommands(rsp, "statistic-sample-count-set-all");
-    checkListCommands(rsp, "version-get");
-}
+    // Created server should register several additional commands.
+    ASSERT_NO_THROW(
+        server_.reset(new NakedControlledDhcpv4Srv());
+    );
 
-// Tests if the server returns its configuration using config-get.
-// Note there are separate tests that verify if toElement() called by the
-// config-get handler are actually converting the configuration correctly.
-TEST_F(CtrlChannelDhcpv4SrvTest, configGet) {
-    createUnixChannelServer();
-    std::string response;
+    EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+    ASSERT_TRUE(answer);
 
-    sendUnixCommand("{ \"command\": \"config-get\" }", response);
-    ConstElementPtr rsp;
+    ASSERT_TRUE(answer->get("arguments"));
+    std::string command_list = answer->get("arguments")->str();
 
-    // The response should be a valid JSON.
-    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
-    ASSERT_TRUE(rsp);
+    EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"build-report\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"config-get\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"libreload\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"server-tag-get\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-remove\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-remove-all\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-reset\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-reset-all\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-sample-age-set\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-sample-age-set-all\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-sample-count-set\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"statistic-sample-count-set-all\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"version-get\"") != string::npos);
 
-    int status;
-    ConstElementPtr cfg = parseAnswer(status, rsp);
-    EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+    // Ok, and now delete the server. It should deregister its commands.
+    server_.reset();
 
-    // Ok, now roughly check if the response seems legit.
-    ASSERT_TRUE(cfg);
-    ASSERT_EQ(Element::map, cfg->getType());
-    EXPECT_TRUE(cfg->get("Dhcp4"));
+    // 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());
 }
 
-// Verify that the "config-test" command will do what we expect.
-TEST_F(CtrlChannelDhcpv4SrvTest, configTest) {
+// Tests that the server properly responds to invalid commands sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelNegative) {
     createUnixChannelServer();
+    std::string response;
 
-    // Define strings to permutate the config arguments
-    // (Note the line feeds makes errors easy to find)
-    string set_config_txt = "{ \"command\": \"config-set\" \n";
-    string config_test_txt = "{ \"command\": \"config-test\" \n";
-    string args_txt = " \"arguments\": { \n";
-    string dhcp4_cfg_txt =
-        "    \"Dhcp4\": { \n"
-        "        \"interfaces-config\": { \n"
-        "            \"interfaces\": [\"*\"] \n"
-        "        },   \n"
-        "        \"valid-lifetime\": 4000, \n"
-        "        \"renew-timer\": 1000, \n"
-        "        \"rebind-timer\": 2000, \n"
-        "        \"lease-database\": { \n"
-        "           \"type\": \"memfile\", \n"
-        "           \"persist\":false, \n"
-        "           \"lfc-interval\": 0  \n"
-        "        }, \n"
-        "       \"expired-leases-processing\": { \n"
-        "            \"reclaim-timer-wait-time\": 0, \n"
-        "            \"hold-reclaimed-time\": 0, \n"
-        "            \"flush-reclaimed-timer-wait-time\": 0 \n"
-        "        },"
-        "        \"subnet4\": [ \n";
-    string subnet1 =
-        "               {\"subnet\": \"192.2.0.0/24\", \n"
-        "                \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
-    string subnet2 =
-        "               {\"subnet\": \"192.2.1.0/24\", \n"
-        "                \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
-    string bad_subnet =
-        "               {\"comment\": \"192.2.2.0/24\", \n"
-        "                \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\n";
-    string subnet_footer =
-        "          ] \n";
-    string control_socket_header =
-        "       ,\"control-socket\": { \n"
-        "       \"socket-type\": \"unix\", \n"
-        "       \"socket-name\": \"";
-    string control_socket_footer =
-        "\"   \n} \n";
-    string logger_txt =
-        "    \"Logging\": { \n"
-        "        \"loggers\": [ { \n"
-        "            \"name\": \"kea\", \n"
-        "            \"severity\": \"FATAL\", \n"
-        "            \"output_options\": [{ \n"
-        "                \"output\": \"/dev/null\" \n"
-        "            }] \n"
-        "        }] \n"
-        "    } \n";
+    sendUnixCommand("{ \"command\": \"bogus\" }", response);
+    EXPECT_EQ("{ \"result\": 2,"
+              " \"text\": \"'bogus' command not supported.\" }", response);
 
-    std::ostringstream os;
+    sendUnixCommand("utter nonsense", response);
+    EXPECT_EQ("{ \"result\": 1, "
+              "\"text\": \"invalid first character u\" }",
+              response);
+}
 
-    // Create a valid config with all the parts should parse
-    os << set_config_txt << ","
-        << args_txt
-        << dhcp4_cfg_txt
-        << subnet1
-        << subnet_footer
-        << control_socket_header
-        << socket_path_
-        << control_socket_footer
-        << "}\n"                      // close dhcp4
-        << ","
-        << logger_txt
-        << "}}";
+// Tests that the server properly responds to shtudown command sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelShutdown) {
+    createUnixChannelServer();
+    std::string response;
+
+    sendUnixCommand("{ \"command\": \"shutdown\" }", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
+}
+
+// This test verifies that the DHCP server handles version-get commands
+TEST_F(CtrlChannelDhcpv4SrvTest, getversion) {
+    createUnixChannelServer();
 
-    // Send the config-set command
     std::string response;
-    sendUnixCommand(os.str(), response);
 
-    // Verify the configuration was successful.
-    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
-              response);
+    // Send the version-get command
+    sendUnixCommand("{ \"command\": \"version-get\" }", response);
+    EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
+    EXPECT_TRUE(response.find("log4cplus") != string::npos);
+    EXPECT_FALSE(response.find("GTEST_VERSION") != string::npos);
 
-    // Check that the config was indeed applied.
-    const Subnet4Collection* subnets =
-        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
-    EXPECT_EQ(1, subnets->size());
+    // Send the build-report command
+    sendUnixCommand("{ \"command\": \"build-report\" }", response);
+    EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
+    EXPECT_TRUE(response.find("GTEST_VERSION") != string::npos);
+}
 
-    // Create a config with malformed subnet that should fail to parse.
-    os.str("");
-    os << config_test_txt << ","
-        << args_txt
-        << dhcp4_cfg_txt
-        << bad_subnet
-        << subnet_footer
-        << control_socket_header
-        << socket_path_
-        << control_socket_footer
-        << "}\n"                      // close dhcp4
-        "}}";
+// This test verifies that the DHCP server immediately reclaims expired
+// leases on leases-reclaim command
+TEST_F(CtrlChannelDhcpv4SrvTest, controlLeasesReclaim) {
+    createUnixChannelServer();
 
-    // Send the config-test command
-    sendUnixCommand(os.str(), response);
+    // Create expired leases. Leases are expired by 40 seconds ago
+    // (valid lifetime = 60, cltt = now - 100).
+    HWAddrPtr hwaddr0(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
+    Lease4Ptr lease0(new Lease4(IOAddress("10.0.0.1"), hwaddr0,
+                                ClientIdPtr(), 60,
+                                time(NULL) - 100, SubnetID(1)));
+    HWAddrPtr hwaddr1(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
+    Lease4Ptr lease1(new Lease4(IOAddress("10.0.0.2"), hwaddr1,
+                                ClientIdPtr(), 60,
+                                time(NULL) - 100, SubnetID(1)));
 
-    // Should fail with a syntax error
-    EXPECT_EQ("{ \"result\": 1, "
-              "\"text\": \"subnet configuration failed: mandatory 'subnet' "
-              "parameter is missing for a subnet being configured (<wire>:19:17)\" }",
+    // Add leases to the database.
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+    ASSERT_NO_THROW(lease_mgr.addLease(lease0));
+    ASSERT_NO_THROW(lease_mgr.addLease(lease1));
+
+    // Make sure they have been added.
+    ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.1")));
+    ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.2")));
+
+    // No arguments
+    std::string response;
+    sendUnixCommand("{ \"command\": \"leases-reclaim\" }", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": "
+              "\"Missing mandatory 'remove' parameter.\" }", response);
+
+    // Bad argument name
+    sendUnixCommand("{ \"command\": \"leases-reclaim\", "
+                    "\"arguments\": { \"reclaim\": true } }", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": "
+              "\"Missing mandatory 'remove' parameter.\" }", response);
+
+    // Bad remove argument type
+    sendUnixCommand("{ \"command\": \"leases-reclaim\", "
+                    "\"arguments\": { \"remove\": \"bogus\" } }", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": "
+              "\"'remove' parameter expected to be a boolean.\" }", response);
+
+    // Send the command
+    sendUnixCommand("{ \"command\": \"leases-reclaim\", "
+                    "\"arguments\": { \"remove\": false } }", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": "
+              "\"Reclamation of expired leases is complete.\" }", response);
+
+    // Leases should be reclaimed, but not removed
+    ASSERT_NO_THROW(lease0 = lease_mgr.getLease4(IOAddress("10.0.0.1")));
+    ASSERT_NO_THROW(lease1 = lease_mgr.getLease4(IOAddress("10.0.0.2")));
+    ASSERT_TRUE(lease0);
+    ASSERT_TRUE(lease1);
+    EXPECT_TRUE(lease0->stateExpiredReclaimed());
+    EXPECT_TRUE(lease1->stateExpiredReclaimed());
+}
+
+// This test verifies that the DHCP server immediately reclaims expired
+// leases on leases-reclaim command with remove = true
+TEST_F(CtrlChannelDhcpv4SrvTest, controlLeasesReclaimRemove) {
+    createUnixChannelServer();
+
+    // Create expired leases. Leases are expired by 40 seconds ago
+    // (valid lifetime = 60, cltt = now - 100).
+    HWAddrPtr hwaddr0(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
+    Lease4Ptr lease0(new Lease4(IOAddress("10.0.0.1"), hwaddr0,
+                                ClientIdPtr(), 60,
+                                time(NULL) - 100, SubnetID(1)));
+    HWAddrPtr hwaddr1(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
+    Lease4Ptr lease1(new Lease4(IOAddress("10.0.0.2"), hwaddr1,
+                                ClientIdPtr(), 60,
+                                time(NULL) - 100, SubnetID(1)));
+
+    // Add leases to the database.
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+    ASSERT_NO_THROW(lease_mgr.addLease(lease0));
+    ASSERT_NO_THROW(lease_mgr.addLease(lease1));
+
+    // Make sure they have been added.
+    ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.1")));
+    ASSERT_TRUE(lease_mgr.getLease4(IOAddress("10.0.0.2")));
+
+    // Send the command
+    std::string response;
+    sendUnixCommand("{ \"command\": \"leases-reclaim\", "
+                    "\"arguments\": { \"remove\": true } }", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": "
+              "\"Reclamation of expired leases is complete.\" }", response);
+
+    // Leases should have been removed.
+    ASSERT_NO_THROW(lease0 = lease_mgr.getLease4(IOAddress("10.0.0.1")));
+    ASSERT_NO_THROW(lease1 = lease_mgr.getLease4(IOAddress("10.0.0.2")));
+    EXPECT_FALSE(lease0);
+    EXPECT_FALSE(lease1);
+}
+
+// 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 Dhcpv4 statistics.
+TEST_F(CtrlChannelDhcpv4SrvTest, 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 that the config was not lost
-    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
-    EXPECT_EQ(1, subnets->size());
+    // Check statistic-reset-all
+    sendUnixCommand("{ \"command\" : \"statistic-reset-all\", "
+                    "  \"arguments\": {}}", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": "
+              "\"All statistics reset to neutral values.\" }", response);
 
-    // Create a valid config with two subnets and no command channel.
-    os.str("");
-    os << config_test_txt << ","
-        << args_txt
-        << dhcp4_cfg_txt
-        << subnet1
-        << ",\n"
-        << subnet2
-        << subnet_footer
-        << "}\n"                      // close dhcp4
-        << "}}";
+    // Check statistic-remove
+    sendUnixCommand("{ \"command\" : \"statistic-remove\", "
+                    "  \"arguments\": {"
+                    "  \"name\":\"bogus\" }}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+              response);
 
-    // Verify the control channel socket exists.
-    ASSERT_TRUE(fileExists(socket_path_));
+    // Check statistic-remove-all
+    sendUnixCommand("{ \"command\" : \"statistic-remove-all\", "
+                    "  \"arguments\": {}}", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics removed.\" }",
+              response);
 
-    // Send the config-test command
-    sendUnixCommand(os.str(), response);
+    // Check statistic-sample-age-set
+    sendUnixCommand("{ \"command\" : \"statistic-sample-age-set\", "
+                    "  \"arguments\": {"
+                    "  \"name\":\"bogus\", \"duration\": 1245 }}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+              response);
 
-    // Verify the control channel socket still exists.
-    EXPECT_TRUE(fileExists(socket_path_));
+    // Check statistic-sample-age-set-all
+    sendUnixCommand("{ \"command\" : \"statistic-sample-age-set-all\", "
+                    "  \"arguments\": {"
+                    "  \"duration\": 1245 }}", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics duration limit are set.\" }",
+              response);
 
-    // Verify the configuration was successful.
-    EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration seems sane. "
-              "Control-socket, hook-libraries, and D2 configuration were "
-              "sanity checked, but not applied.\" }",
+    // Check statistic-sample-count-set
+    sendUnixCommand("{ \"command\" : \"statistic-sample-count-set\", "
+                    "  \"arguments\": {"
+                    "  \"name\":\"bogus\", \"max-samples\": 100 }}", response);
+    EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
               response);
 
-    // Check that the config was not applied
-    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
-    EXPECT_EQ(1, subnets->size());
+    // Check statistic-sample-count-set-all
+    sendUnixCommand("{ \"command\" : \"statistic-sample-count-set-all\", "
+                    "  \"arguments\": {"
+                    "  \"max-samples\": 100 }}", response);
+    EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics count limit are set.\" }",
+              response);
+}
 
-    // Clean up after the test.
-    CfgMgr::instance().clear();
+// Tests that the server properly responds to shtudown command sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv4SrvTest, listCommands) {
+    createUnixChannelServer();
+    std::string response;
+
+    sendUnixCommand("{ \"command\": \"list-commands\" }", response);
+
+    ConstElementPtr rsp;
+    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+
+    // We expect the server to report at least the following commands:
+    checkListCommands(rsp, "build-report");
+    checkListCommands(rsp, "config-get");
+    checkListCommands(rsp, "config-reload");
+    checkListCommands(rsp, "config-set");
+    checkListCommands(rsp, "config-test");
+    checkListCommands(rsp, "config-write");
+    checkListCommands(rsp, "list-commands");
+    checkListCommands(rsp, "leases-reclaim");
+    checkListCommands(rsp, "libreload");
+    checkListCommands(rsp, "version-get");
+    checkListCommands(rsp, "server-tag-get");
+    checkListCommands(rsp, "shutdown");
+    checkListCommands(rsp, "statistic-get");
+    checkListCommands(rsp, "statistic-get-all");
+    checkListCommands(rsp, "statistic-remove");
+    checkListCommands(rsp, "statistic-remove-all");
+    checkListCommands(rsp, "statistic-reset");
+    checkListCommands(rsp, "statistic-reset-all");
+    checkListCommands(rsp, "statistic-sample-age-set");
+    checkListCommands(rsp, "statistic-sample-age-set-all");
+    checkListCommands(rsp, "statistic-sample-count-set");
+    checkListCommands(rsp, "statistic-sample-count-set-all");
+}
+
+// Tests if the server returns its configuration using config-get.
+// Note there are separate tests that verify if toElement() called by the
+// config-get handler are actually converting the configuration correctly.
+TEST_F(CtrlChannelDhcpv4SrvTest, configGet) {
+    createUnixChannelServer();
+    std::string response;
+
+    sendUnixCommand("{ \"command\": \"config-get\" }", response);
+    ConstElementPtr rsp;
+
+    // The response should be a valid JSON.
+    EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+    ASSERT_TRUE(rsp);
+
+    int status;
+    ConstElementPtr cfg = parseAnswer(status, rsp);
+    EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+    // Ok, now roughly check if the response seems legit.
+    ASSERT_TRUE(cfg);
+    ASSERT_EQ(Element::map, cfg->getType());
+    EXPECT_TRUE(cfg->get("Dhcp4"));
 }
 
 // Tests if config-write can be called without any parameters.
-TEST_F(CtrlChannelDhcpv4SrvTest, writeConfigNoFilename) {
+TEST_F(CtrlChannelDhcpv4SrvTest, configWriteNoFilename) {
     createUnixChannelServer();
     std::string response;
 
@@ -1109,7 +1118,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, writeConfigNoFilename) {
 }
 
 // Tests if config-write can be called with a valid filename as parameter.
-TEST_F(CtrlChannelDhcpv4SrvTest, writeConfigFilename) {
+TEST_F(CtrlChannelDhcpv4SrvTest, configWriteFilename) {
     createUnixChannelServer();
     std::string response;
 
@@ -1472,7 +1481,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, longResponse) {
 }
 
 // This test verifies that the server signals timeout if the transmission
-// takes too long, after receiving a partial command.
+// takes too long, having received a partial command.
 TEST_F(CtrlChannelDhcpv4SrvTest, connectionTimeoutPartialCommand) {
     createUnixChannelServer();
 
@@ -1522,7 +1531,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, connectionTimeoutPartialCommand) {
     // Check that the server has signalled a timeout.
     EXPECT_EQ("{ \"result\": 1, \"text\": "
               "\"Connection over control channel timed out, "
-              "discarded partial command of 19 bytes\" }" , response);
+              "discarded partial command of 19 bytes\" }", response);
 }
 
 // This test verifies that the server signals timeout if the transmission
@@ -1552,6 +1561,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, connectionTimeoutNoData) {
         ASSERT_TRUE(client);
         ASSERT_TRUE(client->connectToServer(socket_path_));
 
+        // Having sent nothing let's just wait and see if Server times us out.
         // Let's wait up to 15s for the server's response. The response
         // should arrive sooner assuming that the timeout mechanism for
         // the server is working properly.
index 9e5947606cf24426de9b0ef140d366524d3fc9a0..f113106a2a26ac59a96128e9d3a73e2319f3ec2e 100644 (file)
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfg_db_access.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
-#include <dhcp6/dhcp6to4_ipc.h>
 #include <dhcp6/dhcp6_log.h>
+#include <dhcp6/dhcp6to4_ipc.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/parser_context.h>
+#include <hooks/hooks.h>
 #include <hooks/hooks_manager.h>
 #include <stats/stats_mgr.h>
 #include <cfgrpt/config_report.h>
@@ -24,8 +25,8 @@
 
 using namespace isc::config;
 using namespace isc::db;
-using namespace isc::dhcp;
 using namespace isc::data;
+using namespace isc::dhcp;
 using namespace isc::hooks;
 using namespace isc::stats;
 using namespace std;
index ee821e37a4e9f2399f8168b5ccb50bf91ed7eb16..245c244bdc489f4d80d5faa9cd2c89a27fd8ef2e 100644 (file)
@@ -9,6 +9,7 @@
 #include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
 #include <config/command_mgr.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -17,8 +18,8 @@
 #include <hooks/hooks_manager.h>
 #include <log/logger_support.h>
 #include <stats/stats_mgr.h>
-#include <testutils/unix_control_client.h>
 #include <testutils/io_utils.h>
+#include <testutils/unix_control_client.h>
 #include <testutils/sandbox.h>
 
 #include "marker_file.h"
@@ -38,6 +39,7 @@
 #include <thread>
 
 using namespace std;
+using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
@@ -219,6 +221,8 @@ public:
     /// @brief Reset
     void reset() {
         CtrlDhcpv6SrvTest::reset();
+
+        // Remove unix socket file
         static_cast<void>(::remove(socket_path_.c_str()));
     }
 
@@ -392,7 +396,6 @@ public:
     }
 };
 
-
 TEST_F(CtrlDhcpv6SrvTest, commands) {
 
     boost::scoped_ptr<ControlledDhcpv6Srv> srv;
@@ -406,12 +409,12 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
 
     // Case 1: send bogus command
     ConstElementPtr result = ControlledDhcpv6Srv::processCommand("blah", params);
-    ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
+    ConstElementPtr comment = parseAnswer(rcode, result);
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
 
     // Case 2: send shutdown command without any parameters
     result = ControlledDhcpv6Srv::processCommand("shutdown", params);
-    comment = isc::config::parseAnswer(rcode, result);
+    comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
 
     const pid_t pid(getpid());
@@ -420,8 +423,8 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
 
     // Case 3: send shutdown command with 1 parameter: pid
     result = ControlledDhcpv6Srv::processCommand("shutdown", params);
-    comment = isc::config::parseAnswer(rcode, result);
-    EXPECT_EQ(0, rcode); // Expect success
+    comment = parseAnswer(rcode, result);
+    EXPECT_EQ(0, rcode); // expect success
 }
 
 // Check that the "libreload" command will reload libraries
@@ -504,6 +507,28 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
         "                \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
     string subnet_footer =
         "          ] \n";
+    string option_def =
+        "    ,\"option-def\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"type\": \"uint32\",\n"
+        "        \"array\": false,\n"
+        "        \"record-types\": \"\",\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"encapsulate\": \"\"\n"
+        "    }\n"
+        "]\n";
+    string option_data =
+        "    ,\"option-data\": [\n"
+        "    {\n"
+        "        \"name\": \"foo\",\n"
+        "        \"code\": 163,\n"
+        "        \"space\": \"dhcp6\",\n"
+        "        \"csv-format\": true,\n"
+        "        \"data\": \"12345\"\n"
+        "    }\n"
+        "]\n";
     string control_socket_header =
         "       ,\"control-socket\": { \n"
         "       \"socket-type\": \"unix\", \n"
@@ -529,6 +554,8 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
         << dhcp6_cfg_txt
         << subnet1
         << subnet_footer
+        << option_def
+        << option_data
         << control_socket_header
         << socket_path_
         << control_socket_footer
@@ -550,6 +577,9 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
         CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     EXPECT_EQ(1, subnets->size());
 
+    OptionDefinitionPtr def = LibDHCP::getRuntimeOptionDef("dhcp6", 163);
+    ASSERT_TRUE(def);
+
     // Create a config with malformed subnet that should fail to parse.
     os.str("");
     os << set_config_txt << ","
@@ -568,15 +598,19 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
 
     // Should fail with a syntax error
     EXPECT_EQ("{ \"result\": 1, "
-              "\"text\": \"subnet configuration failed: mandatory 'subnet' parameter is missing for a subnet being configured (<wire>:20:17)\" }",
+              "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+              "parameter is missing for a subnet being configured (<wire>:20:17)\" }",
               response);
 
     // Check that the config was not lost
     subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     EXPECT_EQ(1, subnets->size());
 
+    def = LibDHCP::getRuntimeOptionDef("dhcp6", 163);
+    ASSERT_TRUE(def);
+
     // Create a valid config with two subnets and no command channel.
-    // It should succeed but client will not receive a the response
+    // It should succeed, client should still receive the response
     os.str("");
     os << set_config_txt << ","
         << args_txt
@@ -609,7 +643,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
     CfgMgr::instance().clear();
 }
 
-  // Verify that the "config-test" command will do what we expect.
+// Verify that the "config-test" command will do what we expect.
 TEST_F(CtrlChannelDhcpv6SrvTest, configTest) {
     createUnixChannelServer();
 
@@ -786,10 +820,10 @@ TEST_F(CtrlDhcpv6SrvTest, commandsRegistration) {
     EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"build-report\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"config-get\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"libreload\"") != string::npos);
-    EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"server-tag-get\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
@@ -1071,6 +1105,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, commandsList) {
     // We expect the server to report at least the following commands:
     checkListCommands(rsp, "build-report");
     checkListCommands(rsp, "config-get");
+    checkListCommands(rsp, "config-reload");
     checkListCommands(rsp, "config-set");
     checkListCommands(rsp, "config-test");
     checkListCommands(rsp, "config-write");
@@ -1177,7 +1212,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, configReloadBrokenFile) {
     // Although Kea is smart, its AI routines are not smart enough to handle
     // this one... at least not yet.
     ofstream f("test7.json", ios::trunc);
-    f << "gimme some addr, bro!";
+    f << "gimme some addrs, bro!";
     f.close();
 
     // Now tell Kea to reload its config.
@@ -1544,12 +1579,12 @@ TEST_F(CtrlChannelDhcpv6SrvTest, connectionTimeoutPartialCommand) {
 
     // Check that the server has signalled a timeout.
     EXPECT_EQ("{ \"result\": 1, \"text\": "
-              "\"Connection over control channel timed out,"
-              " discarded partial command of 19 bytes\" }", response);
+              "\"Connection over control channel timed out, "
+              "discarded partial command of 19 bytes\" }", response);
 }
 
 // This test verifies that the server signals timeout if the transmission
-// takes too long, having received no data.
+// takes too long, having received no data from the client.
 TEST_F(CtrlChannelDhcpv6SrvTest, connectionTimeoutNoData) {
     createUnixChannelServer();