Browse Source

[master] Merged trac3978 (new leases-reclaim command)

Francis Dupont 9 years ago
parent
commit
53dd16c29d

+ 32 - 0
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -72,6 +72,32 @@ ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
 }
 
 ConstElementPtr
+ControlledDhcpv4Srv::commandLeasesReclaimHandler(const string& command,
+                                                 ConstElementPtr args) {
+    int status_code = 1;
+    string message;
+
+    // args must be { "remove": <bool> }
+    if (!args) {
+        message = "Missing mandatory 'remove' parameter.";
+    } else {
+        ConstElementPtr remove_name = args->get("remove");
+        if (!remove_name) {
+            message = "Missing mandatory 'remove' parameter.";
+        } else if (remove_name->getType() != Element::boolean) {
+            message = "'remove' parameter expected to be a boolean.";
+        } else {
+            bool remove_lease = remove_name->boolValue();
+            server_->alloc_engine_->reclaimExpiredLeases4(0, 0, remove_lease);
+            status_code = 0;
+            message = "Reclamation of expired leases is complete.";
+        }
+    }
+    ConstElementPtr answer = isc::config::createAnswer(status_code, message);
+    return (answer);
+}
+
+ConstElementPtr
 ControlledDhcpv4Srv::processCommand(const string& command,
                                     ConstElementPtr args) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
@@ -96,6 +122,8 @@ ControlledDhcpv4Srv::processCommand(const string& command,
         } else if (command == "config-reload") {
             return (srv->commandConfigReloadHandler(command, args));
 
+        } else if (command == "leases-reclaim") {
+            return (srv->commandLeasesReclaimHandler(command, args));
         }
         ConstElementPtr answer = isc::config::createAnswer(1,
                                  "Unrecognized command:" + command);
@@ -212,6 +240,9 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
     /// @todo: register config-reload (see CtrlDhcpv4Srv::commandConfigReloadHandler)
     /// @todo: register libreload (see CtrlDhcpv4Srv::commandLibReloadHandler)
 
+    CommandMgr::instance().registerCommand("leases-reclaim",
+        boost::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, _1, _2));
+
     // Register statistic related commands
     CommandMgr::instance().registerCommand("statistic-get",
         boost::bind(&StatsMgr::statisticGetHandler, _1, _2));
@@ -251,6 +282,7 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
 
         // Deregister any registered commands
         CommandMgr::instance().deregisterCommand("shutdown");
+        CommandMgr::instance().deregisterCommand("leases-reclaim");
         CommandMgr::instance().deregisterCommand("statistic-get");
         CommandMgr::instance().deregisterCommand("statistic-reset");
         CommandMgr::instance().deregisterCommand("statistic-remove");

+ 18 - 0
src/bin/dhcp4/ctrl_dhcp4_srv.h

@@ -70,6 +70,7 @@ public:
     /// - shutdown
     /// - libreload
     /// - config-reload
+    /// - leases-reclaim
     ///
     /// @note It never throws.
     ///
@@ -151,6 +152,23 @@ private:
                                isc::data::ConstElementPtr args);
 
 
+    /// @brief Handler for processing 'leases-reclaim' command
+    ///
+    /// This handler processes leases-reclaim command, which triggers
+    /// the leases reclamation immediately.
+    /// No limit for processing time or number of processed leases applies.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args arguments map { "remove": <bool> }
+    ///        if true a lease is removed when it is reclaimed,
+    ///        if false its state is changed to "expired-reclaimed".
+    ///
+    /// @return status of the command (should be success unless args
+    ///         was not a Bool Element).
+    isc::data::ConstElementPtr
+    commandLeasesReclaimHandler(const std::string& command,
+                                isc::data::ConstElementPtr args);
+
     /// @brief Reclaims expired IPv4 leases and reschedules timer.
     ///
     /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases4.

+ 4 - 4
src/bin/dhcp4/kea_controller.cc

@@ -69,11 +69,11 @@ void configure(const std::string& file_name) {
         // works only for map.
         if (json->getType() != isc::data::Element::map) {
             isc_throw(isc::BadValue, "Configuration file is expected to be "
-		      "a map, i.e., start with { and end with } and contain "
-		      "at least an entry called 'Dhcp4' that itself is a map. "
-		      << file_name
+                      "a map, i.e., start with { and end with } and contain "
+                      "at least an entry called 'Dhcp4' that itself is a map. "
+                      << file_name
                       << " is a valid JSON, but its top element is not a map."
-		      " Did you forget to add { } around your configuration?");
+                      " Did you forget to add { } around your configuration?");
         }
 
         // If there's no logging element, we'll just pass NULL pointer,

+ 3 - 3
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -3665,7 +3665,7 @@ TEST_F(Dhcp4ParserTest, declineTimerDefault) {
     // returned value should be 0 (success)
     checkResult(status, 0);
 
-    // The value of decline-probation-perion must be equal to the
+    // The value of decline-probation-period must be equal to the
     // default value.
     EXPECT_EQ(DEFAULT_DECLINE_PROBATION_PERIOD,
               CfgMgr::instance().getStagingCfg()->getDeclinePeriod());
@@ -3687,7 +3687,7 @@ TEST_F(Dhcp4ParserTest, declineTimer) {
     // returned value should be 0 (success)
     checkResult(status, 0);
 
-    // The value of decline-probation-perion must be equal to the
+    // The value of decline-probation-period must be equal to the
     // value specified.
     EXPECT_EQ(12345,
               CfgMgr::instance().getStagingCfg()->getDeclinePeriod());
@@ -3738,7 +3738,7 @@ TEST_F(Dhcp4ParserTest, expiredLeasesProcessing) {
     // Returned value should be 0 (success)
     checkResult(status, 0);
 
-    // The value of decline-probation-perion must be equal to the
+    // The value of decline-probation-period must be equal to the
     // value specified.
     CfgExpirationPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration();
     ASSERT_TRUE(cfg);

+ 109 - 2
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013, 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
@@ -19,7 +19,10 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <hooks/hooks_manager.h>
+#include <stats/stats_mgr.h>
 #include <testutils/unix_control_client.h>
 
 #include "marker_file.h"
@@ -43,6 +46,7 @@ using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::hooks;
+using namespace isc::stats;
 
 namespace {
 
@@ -80,6 +84,8 @@ public:
 
     /// @brief Destructor
     ~CtrlChannelDhcpv4SrvTest() {
+        LeaseMgrFactory::destroy();
+        StatsMgr::instance().removeAll();
         server_.reset();
         reset();
     };
@@ -94,6 +100,11 @@ public:
             "    \"interfaces-config\": {"
             "        \"interfaces\": [ \"*\" ]"
             "    },"
+            "    \"expired-leases-processing\": {"
+            "         \"reclaim-timer-wait-time\": 60,"
+            "         \"hold-reclaimed-time\": 500,"
+            "         \"flush-reclaimed-timer-wait-time\": 60"
+            "    },"
             "    \"rebind-timer\": 2000, "
             "    \"renew-timer\": 1000, "
             "    \"subnet4\": [ ],"
@@ -333,6 +344,103 @@ TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelShutdown) {
     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();
+
+    // 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, 10, 20,
+                                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, 10, 20,
+                                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")));
+
+    // 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());
+}
+
+// Thist 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, 10, 20,
+                                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, 10, 20,
+                                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
@@ -379,5 +487,4 @@ TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelStats) {
               response);
 }
 
-
 } // End of anonymous namespace

+ 33 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -69,6 +69,32 @@ ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr a
     return (processConfig(args));
 }
 
+ConstElementPtr
+ControlledDhcpv6Srv::commandLeasesReclaimHandler(const string& command,
+                                                 ConstElementPtr args) {
+    int status_code = 1;
+    string message;
+
+    // args must be { "remove": <bool> }
+    if (!args) {
+        message = "Missing mandatory 'remove' parameter.";
+    } else {
+        ConstElementPtr remove_name = args->get("remove");
+        if (!remove_name) {
+            message = "Missing mandatory 'remove' parameter.";
+        } else if (remove_name->getType() != Element::boolean) {
+            message = "'remove' parameter expected to be a boolean.";
+        } else {
+            bool remove_lease = remove_name->boolValue();
+            server_->alloc_engine_->reclaimExpiredLeases6(0, 0, remove_lease);
+            status_code = 0;
+            message = "Reclamation of expired leases is complete.";
+        }
+    }
+    ConstElementPtr answer = isc::config::createAnswer(status_code, message);
+    return (answer);
+}
+
 isc::data::ConstElementPtr
 ControlledDhcpv6Srv::processCommand(const std::string& command,
                                     isc::data::ConstElementPtr args) {
@@ -93,6 +119,9 @@ ControlledDhcpv6Srv::processCommand(const std::string& command,
 
         } else if (command == "config-reload") {
             return (srv->commandConfigReloadHandler(command, args));
+
+        } else if (command == "leases-reclaim") {
+            return (srv->commandLeasesReclaimHandler(command, args));
         }
 
         return (isc::config::createAnswer(1, "Unrecognized command:"
@@ -208,6 +237,9 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
     /// @todo: register config-reload (see CtrlDhcpv4Srv::commandConfigReloadHandler)
     /// @todo: register libreload (see CtrlDhcpv4Srv::commandLibReloadHandler)
 
+    CommandMgr::instance().registerCommand("leases-reclaim",
+        boost::bind(&ControlledDhcpv6Srv::commandLeasesReclaimHandler, this, _1, _2));
+
     // Register statistic related commands
     CommandMgr::instance().registerCommand("statistic-get",
         boost::bind(&StatsMgr::statisticGetHandler, _1, _2));
@@ -247,6 +279,7 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
 
         // Deregister any registered commands
         CommandMgr::instance().deregisterCommand("shutdown");
+        CommandMgr::instance().deregisterCommand("leases-reclaim");
         CommandMgr::instance().deregisterCommand("statistic-get");
         CommandMgr::instance().deregisterCommand("statistic-reset");
         CommandMgr::instance().deregisterCommand("statistic-remove");

+ 18 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -70,6 +70,7 @@ public:
     /// - shutdown
     /// - libreload
     /// - config-reload
+    /// - leases-reclaim
     ///
     /// @note It never throws.
     ///
@@ -150,6 +151,23 @@ private:
     commandConfigReloadHandler(const std::string& command,
                                isc::data::ConstElementPtr args);
 
+    /// @brief Handler for processing 'leases-reclaim' command
+    ///
+    /// This handler processes leases-reclaim command, which triggers
+    /// the leases reclamation immediately.
+    /// No limit for processing time or number of processed leases applies.
+    ///
+    /// @param command (parameter ignored)
+    /// @param args arguments map { "remove": <bool> }
+    ///        if true a lease is removed when it is reclaimed,
+    ///        if false its state is changed to "expired-reclaimed".
+    ///
+    /// @return status of the command (should be success unless args
+    ///         was not a Bool Element).
+    isc::data::ConstElementPtr
+    commandLeasesReclaimHandler(const std::string& command,
+                                isc::data::ConstElementPtr args);
+
     /// @brief Reclaims expired IPv6 leases and reschedules timer.
     ///
     /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases6.

+ 4 - 4
src/bin/dhcp6/kea_controller.cc

@@ -73,11 +73,11 @@ void configure(const std::string& file_name) {
         // works only for map.
         if (json->getType() != isc::data::Element::map) {
             isc_throw(isc::BadValue, "Configuration file is expected to be "
-		      "a map, i.e., start with { and end with } and contain "
-		      "at least an entry called 'Dhcp6' that itself is a map. "
-		      << file_name
+                      "a map, i.e., start with { and end with } and contain "
+                      "at least an entry called 'Dhcp6' that itself is a map. "
+                      << file_name
                       << " is a valid JSON, but its top element is not a map."
-		      " Did you forget to add { } around your configuration?");
+                      " Did you forget to add { } around your configuration?");
         }
 
         // Let's configure logging before applying the configuration,

+ 3 - 3
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -3999,7 +3999,7 @@ TEST_F(Dhcp6ParserTest, declineTimerDefault) {
     // returned value should be 0 (success)
     checkResult(status, 0);
 
-    // The value of decline-probation-perion must be equal to the
+    // The value of decline-probation-period must be equal to the
     // default value.
     EXPECT_EQ(DEFAULT_DECLINE_PROBATION_PERIOD,
               CfgMgr::instance().getStagingCfg()->getDeclinePeriod());
@@ -4021,7 +4021,7 @@ TEST_F(Dhcp6ParserTest, declineTimer) {
     // returned value should be 0 (success)
     checkResult(status, 0);
 
-    // The value of decline-probation-perion must be equal to the
+    // The value of decline-probation-period must be equal to the
     // value specified.
     EXPECT_EQ(12345,
               CfgMgr::instance().getStagingCfg()->getDeclinePeriod());
@@ -4072,7 +4072,7 @@ TEST_F(Dhcp6ParserTest, expiredLeasesProcessing) {
     // Returned value should be 0 (success)
     checkResult(status, 0);
 
-    // The value of decline-probation-perion must be equal to the
+    // The value of decline-probation-period must be equal to the
     // value specified.
     CfgExpirationPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration();
     ASSERT_TRUE(cfg);

+ 118 - 0
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -14,11 +14,15 @@
 
 #include <config.h>
 
+#include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
 #include <config/command_mgr.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <hooks/hooks_manager.h>
+#include <stats/stats_mgr.h>
 #include <testutils/unix_control_client.h>
 
 #include "marker_file.h"
@@ -32,11 +36,13 @@
 #include <cstdlib>
 
 using namespace std;
+using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::hooks;
+using namespace isc::stats;
 
 namespace {
 
@@ -59,6 +65,8 @@ public:
     }
 
     virtual ~CtrlDhcpv6SrvTest() {
+        LeaseMgrFactory::destroy();
+        StatsMgr::instance().removeAll();
         reset();
     };
 
@@ -118,6 +126,11 @@ public:
             "    \"interfaces-config\": {"
             "        \"interfaces\": [ \"*\" ]"
             "    },"
+            "    \"expired-leases-processing\": {"
+            "         \"reclaim-timer-wait-time\": 60,"
+            "         \"hold-reclaimed-time\": 500,"
+            "         \"flush-reclaimed-timer-wait-time\": 60"
+            "    },"
             "    \"rebind-timer\": 2000, "
             "    \"renew-timer\": 1000, "
             "    \"subnet6\": [ ],"
@@ -406,6 +419,111 @@ TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelShutdown) {
     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(CtrlChannelDhcpv6SrvTest, controlLeasesReclaim) {
+    createUnixChannelServer();
+
+    // Create expired leases. Leases are expired by 40 seconds ago
+    // (valid lifetime = 60, cltt = now - 100).
+    DuidPtr duid0(new DUID(DUID::fromText("00:01:02:03:04:05:06").getDuid()));
+    Lease6Ptr lease0(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"),
+                                duid0, 1, 50, 60, 10, 20, SubnetID(1)));
+    lease0->cltt_ = time(NULL) - 100;
+    DuidPtr duid1(new DUID(DUID::fromText("01:02:03:04:05:06:07").getDuid()));
+    Lease6Ptr lease1(new Lease6(Lease::TYPE_NA, IOAddress("3000::2"),
+                                duid1, 1, 50, 60, 10, 20, SubnetID(1)));
+    lease1->cltt_ = time(NULL) - 100;
+
+    // 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.getLease6(Lease::TYPE_NA, IOAddress("3000::1")));
+    ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::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.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
+    );
+    ASSERT_NO_THROW(
+        lease1 = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::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(CtrlChannelDhcpv6SrvTest, controlLeasesReclaimRemove) {
+    createUnixChannelServer();
+
+    // Create expired leases. Leases are expired by 40 seconds ago
+    // (valid lifetime = 60, cltt = now - 100).
+    DuidPtr duid0(new DUID(DUID::fromText("00:01:02:03:04:05:06").getDuid()));
+    Lease6Ptr lease0(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"),
+                                duid0, 1, 50, 60, 10, 20, SubnetID(1)));
+    lease0->cltt_ = time(NULL) - 100;
+    DuidPtr duid1(new DUID(DUID::fromText("01:02:03:04:05:06:07").getDuid()));
+    Lease6Ptr lease1(new Lease6(Lease::TYPE_NA, IOAddress("3000::2"),
+                                duid1, 1, 50, 60, 10, 20, SubnetID(1)));
+    lease1->cltt_ = time(NULL) - 100;
+
+    // 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.getLease6(Lease::TYPE_NA, IOAddress("3000::1")));
+    ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::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.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
+    );
+    ASSERT_NO_THROW(
+        lease1 = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2"))
+    );
+    ASSERT_FALSE(lease0);
+    ASSERT_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

+ 1 - 1
src/lib/config/command_mgr.h

@@ -65,7 +65,7 @@ public:
 ///     }
 /// }
 ///
-/// CommandsMgr does not implement the commands (except one, "commands-list")
+/// CommandsMgr does not implement the commands (except one, "list-commands")
 /// 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