Browse Source

[master] Merge branch 'trac3975'

Marcin Siodelski 9 years ago
parent
commit
3bd8891c0b

+ 1 - 0
src/bin/admin/scripts/mysql/.gitignore

@@ -1,2 +1,3 @@
 /upgrade_1.0_to_2.0.sh
 /upgrade_1.0_to_2.0.sh
 /upgrade_2.0_to_3.0.sh
 /upgrade_2.0_to_3.0.sh
+/upgrade_3.0_to_4.0.sh

+ 48 - 15
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -135,20 +135,6 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
 
 
     ConstElementPtr answer = configureDhcp4Server(*srv, config);
     ConstElementPtr answer = configureDhcp4Server(*srv, config);
 
 
-    // Start worker thread if there are any timers installed. Note that
-    // we also start worker thread when the reconfiguration failed, because
-    // in that case we continue using an old configuration and the server
-    // should still run installed timers.
-    if (TimerMgr::instance()->timersCount() > 0) {
-        try {
-            TimerMgr::instance()->startThread();
-        } catch (const std::exception& ex) {
-            err << "Unable to start worker thread running timers: "
-                << ex.what() << ".";
-            return (isc::config::createAnswer(1, err.str()));
-        }
-    }
-
     // Check that configuration was successful. If not, do not reopen sockets
     // Check that configuration was successful. If not, do not reopen sockets
     // and don't bother with DDNS stuff.
     // and don't bother with DDNS stuff.
     try {
     try {
@@ -181,6 +167,32 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
     CfgMgr::instance().getStagingCfg()->getCfgIface()->
     CfgMgr::instance().getStagingCfg()->getCfgIface()->
         openSockets(AF_INET, srv->getPort(), getInstance()->useBroadcast());
         openSockets(AF_INET, srv->getPort(), getInstance()->useBroadcast());
 
 
+    // Install the timers for handling leases reclamation.
+    try {
+        CfgMgr::instance().getStagingCfg()->getCfgExpiration()->
+            setupTimers(&ControlledDhcpv4Srv::reclaimExpiredLeases,
+                        &ControlledDhcpv4Srv::deleteExpiredReclaimedLeases,
+                        server_);
+
+    } catch (const std::exception& ex) {
+        err << "unable to setup timers for periodically running the"
+            " reclamation of the expired leases: "
+            << ex.what() << ".";
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
+    // Start worker thread if there are any timers installed.
+    if (TimerMgr::instance()->timersCount() > 0) {
+        try {
+            TimerMgr::instance()->startThread();
+        } catch (const std::exception& ex) {
+            err << "Unable to start worker thread running timers: "
+                << ex.what() << ".";
+            return (isc::config::createAnswer(1, err.str()));
+        }
+    }
+
+
 
 
     return (answer);
     return (answer);
 }
 }
@@ -229,8 +241,10 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
     try {
     try {
         cleanup();
         cleanup();
 
 
-        // Stop worker thread running timers, if it is running.
+        // Stop worker thread running timers, if it is running. Then
+        // unregister any timers.
         timer_mgr_->stopThread();
         timer_mgr_->stopThread();
+        timer_mgr_->unregisterTimers();
 
 
         // Close the command socket (if it exists).
         // Close the command socket (if it exists).
         CommandMgr::instance().closeCommandSocket();
         CommandMgr::instance().closeCommandSocket();
@@ -262,5 +276,24 @@ void ControlledDhcpv4Srv::sessionReader(void) {
     }
     }
 }
 }
 
 
+void
+ControlledDhcpv4Srv::reclaimExpiredLeases(const size_t max_leases,
+                                          const uint16_t timeout,
+                                          const bool remove_lease,
+                                          const uint16_t max_unwarned_cycles) {
+    server_->alloc_engine_->reclaimExpiredLeases4(max_leases, timeout,
+                                                  remove_lease,
+                                                  max_unwarned_cycles);
+    // We're using the ONE_SHOT timer so there is a need to re-schedule it.
+    TimerMgr::instance()->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
+}
+
+void
+ControlledDhcpv4Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
+    server_->alloc_engine_->deleteExpiredReclaimedLeases4(secs);
+    // We're using the ONE_SHOT timer so there is a need to re-schedule it.
+    TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
+}
+
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 }; // end of isc namespace

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

@@ -150,6 +150,37 @@ private:
     commandConfigReloadHandler(const std::string& command,
     commandConfigReloadHandler(const std::string& command,
                                isc::data::ConstElementPtr args);
                                isc::data::ConstElementPtr args);
 
 
+
+    /// @brief Reclaims expired IPv4 leases and reschedules timer.
+    ///
+    /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases4.
+    /// It reschedules the timer for leases reclamation upon completion of
+    /// this method.
+    ///
+    /// @param max_leases Maximum number of leases to be reclaimed.
+    /// @param timeout Maximum amount of time that the reclaimation routine
+    /// may be processing expired leases, expressed in milliseconds.
+    /// @param remove_lease A boolean value indicating if the lease should
+    /// be removed when it is reclaimed (if true) or it should be left in the
+    /// database in the "expired-reclaimed" state (if false).
+    /// @param max_unwarned_cycles A number of consecutive processing cycles
+    /// of expired leases, after which the system issues a warning if there
+    /// are still expired leases in the database. If this value is 0, the
+    /// warning is never issued.
+    void reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
+                              const bool remove_lease,
+                              const uint16_t max_unwarned_cycles);
+
+    /// @brief Deletes reclaimed leases and reschedules the timer.
+    ///
+    /// This is a wrapper method for @c AllocEngine::deleteExpiredReclaimed4.
+    /// It reschedules the timer for leases reclamation upon completion of
+    /// this method.
+    ///
+    /// @param secs Minimum number of seconds after which a lease can be
+    /// deleted.
+    void deleteExpiredReclaimedLeases(const uint32_t secs);
+
     /// @brief Static pointer to the sole instance of the DHCP server.
     /// @brief Static pointer to the sole instance of the DHCP server.
     ///
     ///
     /// This is required for config and command handlers to gain access to
     /// This is required for config and command handlers to gain access to

+ 100 - 0
src/bin/dhcp4/tests/kea_controller_unittest.cc

@@ -14,11 +14,17 @@
 
 
 #include <config.h>
 #include <config.h>
 
 
+#include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
 #include <cc/command_interpreter.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp4.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
+#include <util/stopwatch.h>
 
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -58,6 +64,7 @@ public:
     }
     }
 
 
     ~JSONFileBackendTest() {
     ~JSONFileBackendTest() {
+        LeaseMgrFactory::destroy();
         isc::log::setDefaultLoggingOutput();
         isc::log::setDefaultLoggingOutput();
         static_cast<void>(remove(TEST_FILE));
         static_cast<void>(remove(TEST_FILE));
     };
     };
@@ -77,6 +84,21 @@ public:
         out.close();
         out.close();
     }
     }
 
 
+    /// @brief Runs timers for specified time.
+    ///
+    /// Internally, this method calls @c IfaceMgr::receive4 to run the
+    /// callbacks for the installed timers.
+    ///
+    /// @param timeout_ms Amount of time after which the method returns.
+    void runTimersWithTimeout(const long timeout_ms) {
+        isc::util::Stopwatch stopwatch;
+        while (stopwatch.getTotalMilliseconds() < timeout_ms) {
+            // Block for up to one millisecond waiting for the timers'
+            // callbacks to be executed.
+            IfaceMgr::instancePtr()->receive4(0, 1000);
+        }
+    }
+
     /// Name of a config file used during tests
     /// Name of a config file used during tests
     static const char* TEST_FILE;
     static const char* TEST_FILE;
 };
 };
@@ -303,4 +325,82 @@ TEST_F(JSONFileBackendTest, DISABLED_loadAllConfigs) {
     }
     }
 }
 }
 
 
+// This test verifies that the DHCP server installs the timers for reclaiming
+// and flushing expired leases.
+TEST_F(JSONFileBackendTest, timers) {
+    // This is a basic configuration which enables timers for reclaiming
+    // expired leases and flushing them after 500 seconds since they expire.
+    // Both timers run at 1 second intervals.
+    string config =
+        "{ \"Dhcp4\": {"
+        "\"interfaces-config\": {"
+        "    \"interfaces\": [ ]"
+        "},"
+        "\"lease-database\": {"
+        "     \"type\": \"memfile\","
+        "     \"persist\": false"
+        "},"
+        "\"expired-leases-processing\": {"
+        "     \"reclaim-timer-wait-time\": 1,"
+        "     \"hold-reclaimed-time\": 500,"
+        "     \"flush-reclaimed-timer-wait-time\": 1"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "\"subnet4\": [ ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+    writeFile(config);
+
+    // Create an instance of the server and intialize it.
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new ControlledDhcpv4Srv(0)));
+    ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+    // Create an expired lease. The lease is expired by 40 seconds ago
+    // (valid lifetime = 60, cltt = now - 100). The lease will be reclaimed
+    // but shouldn't be flushed in the database because the reclaimed are
+    // held in the database 500 seconds after reclamation, according to the
+    // current configuration.
+    HWAddrPtr hwaddr_expired(new HWAddr(HWAddr::fromText("00:01:02:03:04:05")));
+    Lease4Ptr lease_expired(new Lease4(IOAddress("10.0.0.1"), hwaddr_expired,
+                                       ClientIdPtr(), 60, 10, 20,
+                                       time(NULL) - 100, SubnetID(1)));
+
+    // Create expired-reclaimed lease. The lease has expired 1000 - 60 seconds
+    // ago. It should be removed from the lease database when the "flush" timer
+    // goes off.
+    HWAddrPtr hwaddr_reclaimed(new HWAddr(HWAddr::fromText("01:02:03:04:05:06")));
+    Lease4Ptr lease_reclaimed(new Lease4(IOAddress("10.0.0.2"), hwaddr_reclaimed,
+                                         ClientIdPtr(), 60, 10, 20,
+                                         time(NULL) - 1000, SubnetID(1)));
+    lease_reclaimed->state_ = Lease4::STATE_EXPIRED_RECLAIMED;
+
+    // Add leases to the database.
+    LeaseMgr& lease_mgr = LeaseMgrFactory().instance();
+    ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
+    ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
+
+    // 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")));
+
+    // Poll the timers for a while to make sure that each of them is executed
+    // at least once.
+    ASSERT_NO_THROW(runTimersWithTimeout(5000));
+
+    // Verify that the leases in the database have been processed as expected.
+
+    // First lease should be reclaimed, but not removed.
+    ASSERT_NO_THROW(lease_expired = lease_mgr.getLease4(IOAddress("10.0.0.1")));
+    ASSERT_TRUE(lease_expired);
+    EXPECT_TRUE(lease_expired->stateExpiredReclaimed());
+
+    // Second lease should have been removed.
+    ASSERT_NO_THROW(
+        lease_reclaimed = lease_mgr.getLease4(IOAddress("10.0.0.2"));
+    );
+    EXPECT_FALSE(lease_reclaimed);
+}
+
 } // End of anonymous namespace
 } // End of anonymous namespace

+ 50 - 16
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -131,21 +131,6 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
 
 
     ConstElementPtr answer = configureDhcp6Server(*srv, config);
     ConstElementPtr answer = configureDhcp6Server(*srv, config);
 
 
-    // Start worker thread if there are any timers installed. Note that
-    // we also start worker thread when the reconfiguration failed, because
-    // in that case we continue using an old configuration and the server
-    // should still run installed timers.
-    if (TimerMgr::instance()->timersCount() > 0) {
-        try {
-            TimerMgr::instance()->startThread();
-        } catch (const std::exception& ex) {
-            std::ostringstream err;
-            err << "Unable to start worker thread running timers: "
-                << ex.what() << ".";
-            return (isc::config::createAnswer(1, err.str()));
-        }
-    }
-
     // Check that configuration was successful. If not, do not reopen sockets
     // Check that configuration was successful. If not, do not reopen sockets
     // and don't bother with DDNS stuff.
     // and don't bother with DDNS stuff.
     try {
     try {
@@ -178,6 +163,33 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
     // of the interfaces.
     // of the interfaces.
     CfgMgr::instance().getStagingCfg()->getCfgIface()->openSockets(AF_INET6, srv->getPort());
     CfgMgr::instance().getStagingCfg()->getCfgIface()->openSockets(AF_INET6, srv->getPort());
 
 
+    // Install the timers for handling leases reclamation.
+    try {
+        CfgMgr::instance().getStagingCfg()->getCfgExpiration()->
+            setupTimers(&ControlledDhcpv6Srv::reclaimExpiredLeases,
+                        &ControlledDhcpv6Srv::deleteExpiredReclaimedLeases,
+                        server_);
+
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "unable to setup timers for periodically running the"
+            " reclamation of the expired leases: "
+            << ex.what() << ".";
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
+    // Start worker thread if there are any timers installed.
+    if (TimerMgr::instance()->timersCount() > 0) {
+        try {
+            TimerMgr::instance()->startThread();
+        } catch (const std::exception& ex) {
+            std::ostringstream err;
+            err << "Unable to start worker thread running timers: "
+                << ex.what() << ".";
+            return (isc::config::createAnswer(1, err.str()));
+        }
+    }
+
     return (answer);
     return (answer);
 }
 }
 
 
@@ -225,8 +237,10 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
     try {
     try {
         cleanup();
         cleanup();
 
 
-        // Stop worker thread running timers, if it is running.
+        // Stop worker thread running timers, if it is running. Then
+        // unregister any timers.
         timer_mgr_->stopThread();
         timer_mgr_->stopThread();
+        timer_mgr_->unregisterTimers();
 
 
         // Close the command socket (if it exists).
         // Close the command socket (if it exists).
         CommandMgr::instance().closeCommandSocket();
         CommandMgr::instance().closeCommandSocket();
@@ -258,5 +272,25 @@ void ControlledDhcpv6Srv::sessionReader(void) {
     }
     }
 }
 }
 
 
+void
+ControlledDhcpv6Srv::reclaimExpiredLeases(const size_t max_leases,
+                                          const uint16_t timeout,
+                                          const bool remove_lease,
+                                          const uint16_t max_unwarned_cycles) {
+    server_->alloc_engine_->reclaimExpiredLeases6(max_leases, timeout,
+                                                  remove_lease,
+                                                  max_unwarned_cycles);
+    // We're using the ONE_SHOT timer so there is a need to re-schedule it.
+    TimerMgr::instance()->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
+}
+
+void
+ControlledDhcpv6Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
+    server_->alloc_engine_->deleteExpiredReclaimedLeases6(secs);
+    // We're using the ONE_SHOT timer so there is a need to re-schedule it.
+    TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
+}
+
+
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 }; // end of isc namespace

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

@@ -150,6 +150,37 @@ private:
     commandConfigReloadHandler(const std::string& command,
     commandConfigReloadHandler(const std::string& command,
                                isc::data::ConstElementPtr args);
                                isc::data::ConstElementPtr args);
 
 
+    /// @brief Reclaims expired IPv6 leases and reschedules timer.
+    ///
+    /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases6.
+    /// It reschedules the timer for leases reclamation upon completion of
+    /// this method.
+    ///
+    /// @param max_leases Maximum number of leases to be reclaimed.
+    /// @param timeout Maximum amount of time that the reclaimation routine
+    /// may be processing expired leases, expressed in milliseconds.
+    /// @param remove_lease A boolean value indicating if the lease should
+    /// be removed when it is reclaimed (if true) or it should be left in the
+    /// database in the "expired-reclaimed" state (if false).
+    /// @param max_unwarned_cycles A number of consecutive processing cycles
+    /// of expired leases, after which the system issues a warning if there
+    /// are still expired leases in the database. If this value is 0, the
+    /// warning is never issued.
+    void reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
+                              const bool remove_lease,
+                              const uint16_t max_unwarned_cycles);
+
+
+    /// @brief Deletes reclaimed leases and reschedules the timer.
+    ///
+    /// This is a wrapper method for @c AllocEngine::deleteExpiredReclaimed6.
+    /// It reschedules the timer for leases reclamation upon completion of
+    /// this method.
+    ///
+    /// @param secs Minimum number of seconds after which a lease can be
+    /// deleted.
+    void deleteExpiredReclaimedLeases(const uint32_t secs);
+
     /// @brief Static pointer to the sole instance of the DHCP server.
     /// @brief Static pointer to the sole instance of the DHCP server.
     ///
     ///
     /// This is required for config and command handlers to gain access to
     /// This is required for config and command handlers to gain access to

+ 1 - 1
src/bin/dhcp6/dhcp6_srv.cc

@@ -180,7 +180,7 @@ const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
 static const char* SERVER_DUID_FILE = "kea-dhcp6-serverid";
 static const char* SERVER_DUID_FILE = "kea-dhcp6-serverid";
 
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
-:alloc_engine_(), serverid_(), port_(port), shutdown_(true)
+    : serverid_(), port_(port), shutdown_(true), alloc_engine_()
 {
 {
 
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);

+ 6 - 6
src/bin/dhcp6/dhcp6_srv.h

@@ -823,12 +823,6 @@ private:
     /// @param query packet transmitted
     /// @param query packet transmitted
     static void processStatsSent(const Pkt6Ptr& response);
     static void processStatsSent(const Pkt6Ptr& response);
 
 
-    /// @brief Allocation Engine.
-    /// Pointer to the allocation engine that we are currently using
-    /// It must be a pointer, because we will support changing engines
-    /// during normal operation (e.g. to use different allocators)
-    boost::shared_ptr<AllocEngine> alloc_engine_;
-
     /// Server DUID (to be sent in server-identifier option)
     /// Server DUID (to be sent in server-identifier option)
     OptionPtr serverid_;
     OptionPtr serverid_;
 
 
@@ -841,6 +835,12 @@ protected:
     /// initiate server shutdown procedure.
     /// initiate server shutdown procedure.
     volatile bool shutdown_;
     volatile bool shutdown_;
 
 
+    /// @brief Allocation Engine.
+    /// Pointer to the allocation engine that we are currently using
+    /// It must be a pointer, because we will support changing engines
+    /// during normal operation (e.g. to use different allocators)
+    boost::shared_ptr<AllocEngine> alloc_engine_;
+
     /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects, which
     /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects, which
     /// are waiting for sending to kea-dhcp-ddns module.
     /// are waiting for sending to kea-dhcp-ddns module.
     std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;
     std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;

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

@@ -26,6 +26,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/timer_mgr.h>
 #include <dhcpsrv/triplet.h>
 #include <dhcpsrv/triplet.h>
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <dhcpsrv/parsers/dhcp_config_parser.h>
 #include <dhcpsrv/parsers/dhcp_config_parser.h>
@@ -745,6 +746,9 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
     // so newly recreated configuration starts with first subnet-id equal 1.
     // so newly recreated configuration starts with first subnet-id equal 1.
     Subnet::resetSubnetID();
     Subnet::resetSubnetID();
 
 
+    // Remove any existing timers.
+    TimerMgr::instance()->unregisterTimers();
+
     // Some of the values specified in the configuration depend on
     // Some of the values specified in the configuration depend on
     // other values. Typically, the values in the subnet6 structure
     // other values. Typically, the values in the subnet6 structure
     // depend on the global values. Also, option values configuration
     // depend on the global values. Also, option values configuration

+ 104 - 0
src/bin/dhcp6/tests/kea_controller_unittest.cc

@@ -14,11 +14,17 @@
 
 
 #include <config.h>
 #include <config.h>
 
 
+#include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
 #include <cc/command_interpreter.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/duid.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
+#include <util/stopwatch.h>
 
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -53,6 +59,7 @@ public:
     }
     }
 
 
     ~JSONFileBackendTest() {
     ~JSONFileBackendTest() {
+        LeaseMgrFactory::destroy();
         isc::log::setDefaultLoggingOutput();
         isc::log::setDefaultLoggingOutput();
         static_cast<void>(remove(TEST_FILE));
         static_cast<void>(remove(TEST_FILE));
     };
     };
@@ -66,6 +73,21 @@ public:
         out.close();
         out.close();
     }
     }
 
 
+    /// @brief Runs timers for specified time.
+    ///
+    /// Internally, this method calls @c IfaceMgr::receive6 to run the
+    /// callbacks for the installed timers.
+    ///
+    /// @param timeout_ms Amount of time after which the method returns.
+    void runTimersWithTimeout(const long timeout_ms) {
+        isc::util::Stopwatch stopwatch;
+        while (stopwatch.getTotalMilliseconds() < timeout_ms) {
+            // Block for up to one millisecond waiting for the timers'
+            // callbacks to be executed.
+            IfaceMgr::instancePtr()->receive6(0, 1000);
+        }
+    }
+
     static const char* TEST_FILE;
     static const char* TEST_FILE;
 };
 };
 
 
@@ -245,4 +267,86 @@ TEST_F(JSONFileBackendTest, configBroken) {
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
 }
 }
 
 
+// This test verifies that the DHCP server installs the timers for reclaiming
+// and flushing expired leases.
+TEST_F(JSONFileBackendTest, timers) {
+    // This is a basic configuration which enables timers for reclaiming
+    // expired leases and flushing them after 500 seconds since they expire.
+    // Both timers run at 1 second intervals.
+    string config =
+        "{ \"Dhcp6\": {"
+        "\"interfaces-config\": {"
+        "    \"interfaces\": [ ]"
+        "},"
+        "\"lease-database\": {"
+        "     \"type\": \"memfile\","
+        "     \"persist\": false"
+        "},"
+        "\"expired-leases-processing\": {"
+        "     \"reclaim-timer-wait-time\": 1,"
+        "     \"hold-reclaimed-time\": 500,"
+        "     \"flush-reclaimed-timer-wait-time\": 1"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ ],"
+        "\"preferred-lifetime\": 3000, "
+        "\"valid-lifetime\": 4000 }"
+        "}";
+    writeFile(TEST_FILE, config);
+
+    // Create an instance of the server and intialize it.
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new ControlledDhcpv6Srv(0)));
+    ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+    // Create an expired lease. The lease is expired by 40 seconds ago
+    // (valid lifetime = 60, cltt = now - 100). The lease will be reclaimed
+    // but shouldn't be flushed in the database because the reclaimed are
+    // held in the database 500 seconds after reclamation, according to the
+    // current configuration.
+    DuidPtr duid_expired(new DUID(DUID::fromText("00:01:02:03:04:05:06").getDuid()));
+    Lease6Ptr lease_expired(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"),
+                                       duid_expired, 1, 50, 60, 10, 20, SubnetID(1)));
+    lease_expired->cltt_ = time(NULL) - 100;
+
+
+    // Create expired-reclaimed lease. The lease has expired 1000 - 60 seconds
+    // ago. It should be removed from the lease database when the "flush" timer
+    // goes off.
+    DuidPtr duid_reclaimed(new DUID(DUID::fromText("01:02:03:04:05:06:07").getDuid()));
+    Lease6Ptr lease_reclaimed(new Lease6(Lease::TYPE_NA, IOAddress("3000::2"),
+                                         duid_reclaimed, 1, 50, 60, 10, 20, SubnetID(1)));
+    lease_reclaimed->cltt_ = time(NULL) - 1000;
+    lease_reclaimed->state_ = Lease6::STATE_EXPIRED_RECLAIMED;
+
+    // Add leases to the database.
+    LeaseMgr& lease_mgr = LeaseMgrFactory().instance();
+    ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
+    ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
+
+    // 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")));
+
+    // Poll the timers for a while to make sure that each of them is executed
+    // at least once.
+    ASSERT_NO_THROW(runTimersWithTimeout(5000));
+
+    // Verify that the leases in the database have been processed as expected.
+
+    // First lease should be reclaimed, but not removed.
+    ASSERT_NO_THROW(
+        lease_expired = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
+    );
+    ASSERT_TRUE(lease_expired);
+    EXPECT_TRUE(lease_expired->stateExpiredReclaimed());
+
+    // Second lease should have been removed.
+    ASSERT_NO_THROW(
+        lease_reclaimed = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2"))
+    );
+    EXPECT_FALSE(lease_reclaimed);
+}
+
 } // End of anonymous namespace
 } // End of anonymous namespace

+ 32 - 0
src/bin/keactrl/kea.conf.pre

@@ -19,6 +19,22 @@
     "type": "memfile"
     "type": "memfile"
   },
   },
 
 
+# Setup reclamation of the expired leases and leases affinity.
+# Expired leases will be reclaimed every 10 seconds. Every 25
+# seconds reclaimed leases, which have expired more than 3600
+# seconds ago, will be removed. The limits for leases reclamation
+# are 100 leases or 250 ms for a single cycle. A warning message
+# will be logged if there are still expired leases in the
+# database after 5 consecutive reclamation cycles.
+  "expired-leases-processing": {
+    "reclaim-timer-wait-time": 10,
+    "flush-reclaimed-timer-wait-time": 25,
+    "hold-reclaimed-time": 3600,
+    "max-reclaim-leases": 100,
+    "max-reclaim-time": 250,
+    "unwarned-reclaim-cycles": 5
+  },
+
 # Global (inherited by all subnets) lease lifetime is mandatory parameter.
 # Global (inherited by all subnets) lease lifetime is mandatory parameter.
   "valid-lifetime": 4000,
   "valid-lifetime": 4000,
 
 
@@ -46,6 +62,22 @@
     "type": "memfile"
     "type": "memfile"
   },
   },
 
 
+# Setup reclamation of the expired leases and leases affinity.
+# Expired leases will be reclaimed every 10 seconds. Every 25
+# seconds reclaimed leases, which have expired more than 3600
+# seconds ago, will be removed. The limits for leases reclamation
+# are 100 leases or 250 ms for a single cycle. A warning message
+# will be logged if there are still expired leases in the
+# database after 5 consecutive reclamation cycles.
+  "expired-leases-processing": {
+    "reclaim-timer-wait-time": 10,
+    "flush-reclaimed-timer-wait-time": 25,
+    "hold-reclaimed-time": 3600,
+    "max-reclaim-leases": 100,
+    "max-reclaim-time": 250,
+    "unwarned-reclaim-cycles": 5
+  },
+
 # Addresses will be assigned with preferred and valid lifetimes
 # Addresses will be assigned with preferred and valid lifetimes
 # being 3000 and 4000, respectively. Client is told to start
 # being 3000 and 4000, respectively. Client is told to start
 # renewing after 1000 seconds. If the server does not respond
 # renewing after 1000 seconds. If the server does not respond

+ 163 - 6
src/lib/dhcpsrv/alloc_engine.cc

@@ -247,7 +247,8 @@ AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
 
 
 AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
 AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
                          bool ipv6)
                          bool ipv6)
-    : attempts_(attempts) {
+    : attempts_(attempts), incomplete_v4_reclamations_(0),
+      incomplete_v6_reclamations_(0) {
 
 
     // Choose the basic (normal address) lease type
     // Choose the basic (normal address) lease type
     Lease::Type basic_type = ipv6 ? Lease::TYPE_NA : Lease::TYPE_V4;
     Lease::Type basic_type = ipv6 ? Lease::TYPE_NA : Lease::TYPE_V4;
@@ -1303,7 +1304,8 @@ AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases
 
 
 void
 void
 AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
 AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
-                                   const bool remove_lease) {
+                                   const bool remove_lease,
+                                   const uint16_t max_unwarned_cycles) {
 
 
     LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
     LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
               ALLOC_ENGINE_V6_LEASES_RECLAMATION_START)
               ALLOC_ENGINE_V6_LEASES_RECLAMATION_START)
@@ -1316,8 +1318,31 @@ AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeo
 
 
     LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
     LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
 
 
+    // This value indicates if we have been able to deal with all expired
+    // leases in this pass.
+    bool incomplete_reclamation = false;
     Lease6Collection leases;
     Lease6Collection leases;
-    lease_mgr.getExpiredLeases6(leases, max_leases);
+    // The value of 0 has a special meaning - reclaim all.
+    if (max_leases > 0) {
+        // If the value is non-zero, the caller has limited the number of
+        // leases to reclaim. We obtain one lease more to see if there will
+        // be still leases left after this pass.
+        lease_mgr.getExpiredLeases6(leases, max_leases + 1);
+        // There are more leases expired leases than we will process in this
+        // pass, so we should mark it as an incomplete reclamation. We also
+        // remove this extra lease (which we don't want to process anyway)
+        // from the collection.
+        if (leases.size() > max_leases) {
+            leases.pop_back();
+            incomplete_reclamation = true;
+        }
+
+    } else {
+        // If there is no limitation on the number of leases to reclaim,
+        // we will try to process all. Hence, we don't mark it as incomplete
+        // reclamation just yet.
+        lease_mgr.getExpiredLeases6(leases, max_leases);
+    }
 
 
     // Do not initialize the callout handle until we know if there are any
     // Do not initialize the callout handle until we know if there are any
     // lease6_expire callouts installed.
     // lease6_expire callouts installed.
@@ -1422,6 +1447,15 @@ AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeo
         // return if we have. We're checking it here, because we always want to
         // return if we have. We're checking it here, because we always want to
         // allow reclaiming at least one lease.
         // allow reclaiming at least one lease.
         if ((timeout > 0) && (stopwatch.getTotalMilliseconds() >= timeout)) {
         if ((timeout > 0) && (stopwatch.getTotalMilliseconds() >= timeout)) {
+            // Timeout. This will likely mean that we haven't been able to process
+            // all leases we wanted to process. The reclamation pass will be
+            // probably marked as incomplete.
+            if (!incomplete_reclamation) {
+                if (leases_processed < leases.size()) {
+                    incomplete_reclamation = true;
+                }
+            }
+
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT)
                       ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT)
                 .arg(timeout);
                 .arg(timeout);
@@ -1437,11 +1471,57 @@ AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeo
               ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE)
               ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE)
         .arg(leases_processed)
         .arg(leases_processed)
         .arg(stopwatch.logFormatTotalDuration());
         .arg(stopwatch.logFormatTotalDuration());
+
+    // Check if this was an incomplete reclamation and increase the number of
+    // consecutive incomplete reclamations.
+    if (incomplete_reclamation) {
+        ++incomplete_v6_reclamations_;
+        // If the number of incomplete reclamations is beyond the threshold, we
+        // need to issue a warning.
+        if ((max_unwarned_cycles > 0) &&
+            (incomplete_v6_reclamations_ > max_unwarned_cycles)) {
+            LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW)
+                .arg(max_unwarned_cycles);
+            // We issued a warning, so let's now reset the counter.
+            incomplete_v6_reclamations_ = 0;
+        }
+
+    } else {
+        // This was a complete reclamation, so let's reset the counter.
+        incomplete_v6_reclamations_ = 0;
+
+        LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+                  ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES);
+    }
 }
 }
 
 
 void
 void
+AllocEngine::deleteExpiredReclaimedLeases6(const uint32_t secs) {
+    LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+              ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE)
+        .arg(secs);
+
+    uint64_t deleted_leases = 0;
+    try {
+        // Try to delete leases from the lease database.
+        LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+        deleted_leases = lease_mgr.deleteExpiredReclaimedLeases6(secs);
+
+    } catch (const std::exception& ex) {
+        LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED)
+            .arg(ex.what());
+    }
+
+    LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+              ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE)
+        .arg(deleted_leases);
+}
+
+
+void
 AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeout,
 AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeout,
-                                   const bool remove_lease) {
+                                   const bool remove_lease,
+                                   const uint16_t max_unwarned_cycles) {
 
 
     LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
     LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
               ALLOC_ENGINE_V4_LEASES_RECLAMATION_START)
               ALLOC_ENGINE_V4_LEASES_RECLAMATION_START)
@@ -1454,8 +1534,32 @@ AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeo
 
 
     LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
     LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
 
 
+    // This value indicates if we have been able to deal with all expired
+    // leases in this pass.
+    bool incomplete_reclamation = false;
     Lease4Collection leases;
     Lease4Collection leases;
-    lease_mgr.getExpiredLeases4(leases, max_leases);
+    // The value of 0 has a special meaning - reclaim all.
+    if (max_leases > 0) {
+        // If the value is non-zero, the caller has limited the number of
+        // leases to reclaim. We obtain one lease more to see if there will
+        // be still leases left after this pass.
+        lease_mgr.getExpiredLeases4(leases, max_leases + 1);
+        // There are more leases expired leases than we will process in this
+        // pass, so we should mark it as an incomplete reclamation. We also
+        // remove this extra lease (which we don't want to process anyway)
+        // from the collection.
+        if (leases.size() > max_leases) {
+            leases.pop_back();
+            incomplete_reclamation = true;
+        }
+
+    } else {
+        // If there is no limitation on the number of leases to reclaim,
+        // we will try to process all. Hence, we don't mark it as incomplete
+        // reclamation just yet.
+        lease_mgr.getExpiredLeases4(leases, max_leases);
+    }
+
 
 
     // Do not initialize the callout handle until we know if there are any
     // Do not initialize the callout handle until we know if there are any
     // lease4_expire callouts installed.
     // lease4_expire callouts installed.
@@ -1554,8 +1658,17 @@ AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeo
         // return if we have. We're checking it here, because we always want to
         // return if we have. We're checking it here, because we always want to
         // allow reclaiming at least one lease.
         // allow reclaiming at least one lease.
         if ((timeout > 0) && (stopwatch.getTotalMilliseconds() >= timeout)) {
         if ((timeout > 0) && (stopwatch.getTotalMilliseconds() >= timeout)) {
+            // Timeout. This will likely mean that we haven't been able to process
+            // all leases we wanted to process. The reclamation pass will be
+            // probably marked as incomplete.
+            if (!incomplete_reclamation) {
+                if (leases_processed < leases.size()) {
+                    incomplete_reclamation = true;
+                }
+            }
+
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
-                      ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT)
+                      ALLOC_ENGINE_V4_LEASES_RECLAMATION_TIMEOUT)
                 .arg(timeout);
                 .arg(timeout);
             break;
             break;
         }
         }
@@ -1569,6 +1682,50 @@ AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeo
               ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE)
               ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE)
         .arg(leases_processed)
         .arg(leases_processed)
         .arg(stopwatch.logFormatTotalDuration());
         .arg(stopwatch.logFormatTotalDuration());
+
+    // Check if this was an incomplete reclamation and increase the number of
+    // consecutive incomplete reclamations.
+    if (incomplete_reclamation) {
+        ++incomplete_v4_reclamations_;
+        // If the number of incomplete reclamations is beyond the threshold, we
+        // need to issue a warning.
+        if ((max_unwarned_cycles > 0) &&
+            (incomplete_v4_reclamations_ > max_unwarned_cycles)) {
+            LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW)
+                .arg(max_unwarned_cycles);
+            // We issued a warning, so let's now reset the counter.
+            incomplete_v4_reclamations_ = 0;
+        }
+
+    } else {
+        // This was a complete reclamation, so let's reset the counter.
+        incomplete_v4_reclamations_ = 0;
+
+        LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+                  ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES);
+    }
+}
+
+void
+AllocEngine::deleteExpiredReclaimedLeases4(const uint32_t secs) {
+    LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+              ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE)
+        .arg(secs);
+
+    uint64_t deleted_leases = 0;
+    try {
+        // Try to delete leases from the lease database.
+        LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+        deleted_leases = lease_mgr.deleteExpiredReclaimedLeases4(secs);
+
+    } catch (const std::exception& ex) {
+        LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED)
+            .arg(ex.what());
+    }
+
+    LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+              ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE)
+        .arg(deleted_leases);
 }
 }
 
 
 void
 void

+ 37 - 2
src/lib/dhcpsrv/alloc_engine.h

@@ -534,8 +534,20 @@ public:
     /// @param remove_lease A boolean value indicating if the lease should
     /// @param remove_lease A boolean value indicating if the lease should
     /// be removed when it is reclaimed (if true) or it should be left in the
     /// be removed when it is reclaimed (if true) or it should be left in the
     /// database in the "expired-reclaimed" state (if false).
     /// database in the "expired-reclaimed" state (if false).
+    /// @param max_unwarned_cycles A number of consecutive processing cycles
+    /// of expired leases, after which the system issues a warning if there
+    /// are still expired leases in the database. If this value is 0, the
+    /// warning is never issued.
     void reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
     void reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
-                               const bool remove_lease);
+                               const bool remove_lease,
+                               const uint16_t max_unwarned_cycles = 0);
+
+    /// @brief Deletes reclaimed leases expired more than specified amount
+    /// of time ago.
+    ///
+    /// @param secs Minimum number of seconds after which the lease can be
+    /// deleted.
+    void deleteExpiredReclaimedLeases6(const uint32_t secs);
 
 
     /// @brief Reclaims expired IPv4 leases.
     /// @brief Reclaims expired IPv4 leases.
     ///
     ///
@@ -580,8 +592,21 @@ public:
     /// @param remove_lease A boolean value indicating if the lease should
     /// @param remove_lease A boolean value indicating if the lease should
     /// be removed when it is reclaimed (if true) or it should be left in the
     /// be removed when it is reclaimed (if true) or it should be left in the
     /// database in the "expired-reclaimed" state (if false).
     /// database in the "expired-reclaimed" state (if false).
+    /// @param max_unwarned_cycles A number of consecutive processing cycles
+    /// of expired leases, after which the system issues a warning if there
+    /// are still expired leases in the database. If this value is 0, the
+    /// warning is never issued.
     void reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeout,
     void reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeout,
-                               const bool remove_lease);
+                               const bool remove_lease,
+                               const uint16_t max_unwarned_cycles = 0);
+
+    /// @brief Deletes reclaimed leases expired more than specified amount
+    /// of time ago.
+    ///
+    /// @param secs Minimum number of seconds after which the lease can be
+    /// deleted.
+    void deleteExpiredReclaimedLeases4(const uint32_t secs);
+
 
 
     /// @brief Attempts to find appropriate host reservation.
     /// @brief Attempts to find appropriate host reservation.
     ///
     ///
@@ -1253,6 +1278,16 @@ private:
     /// otherwise.
     /// otherwise.
     bool conditionalExtendLifetime(Lease& lease) const;
     bool conditionalExtendLifetime(Lease& lease) const;
 
 
+private:
+
+    /// @brief Number of consecutive DHCPv4 leases' reclamations after
+    /// which there are still expired leases in the database.
+    uint16_t incomplete_v4_reclamations_;
+
+    /// @brief Number of consecutive DHCPv6 leases' reclamations after
+    /// which there are still expired leases in the database.
+    uint16_t incomplete_v6_reclamations_;
+
 };
 };
 
 
 /// @brief A pointer to the @c AllocEngine object.
 /// @brief A pointer to the @c AllocEngine object.

+ 78 - 0
src/lib/dhcpsrv/alloc_engine_messages.mes

@@ -90,6 +90,22 @@ the number of reclaimed leases may also be limited by the timeout
 value, configured with 'max-reclaim-time'. The message includes the
 value, configured with 'max-reclaim-time'. The message includes the
 number of reclaimed leases and the total time.
 number of reclaimed leases and the total time.
 
 
+% ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW expired leases still exist after %1 reclamations
+This warning message is issued when the server has been unable to
+reclaim all expired leases in a specified number of consecutive
+attempts. This indicates that the value of "reclaim-timer-wait-time"
+may be too high. However, if this is just a short burst of leases'
+expirations the value does not have to be modified and the server
+should deal with this in subsequent reclamation attempts. If this
+is a result of a permanent increase of the server load, the value
+of "reclaim-timer-wait-time" should be decreased, or the
+values of "max-reclaim-leases" and "max-reclaim-time" should be
+increased to allow processing more leases in a single cycle.
+Alternatively, these values may be set to 0 to remove the
+limitations on the number of leases and duration. However, this
+may result in longer periods of server's unresponsiveness to
+DHCP packets, while it processes the expired leases.
+
 % ALLOC_ENGINE_V4_LEASES_RECLAMATION_START starting reclamation of expired leases (limit = %1 leases or %2 seconds)
 % ALLOC_ENGINE_V4_LEASES_RECLAMATION_START starting reclamation of expired leases (limit = %1 leases or %2 seconds)
 This debug message is issued when the allocation engine starts the
 This debug message is issued when the allocation engine starts the
 reclamation of the expired leases. The maximum number of leases to
 reclamation of the expired leases. The maximum number of leases to
@@ -104,6 +120,10 @@ been reclaimed, because of the timeout, will be reclaimed when the
 next scheduled reclamation is started. The argument is the timeout
 next scheduled reclamation is started. The argument is the timeout
 value expressed in milliseconds.
 value expressed in milliseconds.
 
 
+% ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES all expired leases have been reclaimed
+This debug message is issued when the server reclaims all expired
+DHCPv4 leases in the database.
+
 % ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE allocation engine will try to offer existing lease to the client %1
 % ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE allocation engine will try to offer existing lease to the client %1
 This message is issued when the allocation engine determines that
 This message is issued when the allocation engine determines that
 the client has a lease in the lease database, it doesn't have
 the client has a lease in the lease database, it doesn't have
@@ -125,6 +145,25 @@ offer the lease specified in the hint. This situation may occur
 when: (a) client doesn't have any reservations, (b) client has
 when: (a) client doesn't have any reservations, (b) client has
 reservation but the reserved address is leased to another client.
 reservation but the reserved address is leased to another client.
 
 
+% ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE begin deletion of reclaimed leases expired more than %1 seconds ago
+This debug message is issued when the allocation engine begins
+deletion of the reclaimed leases which have expired more than
+a specified number of seconds ago. This operation is triggered
+periodically according to the "flush-reclaimed-timer-wait-time"
+parameter. The "hold-reclaimed-time" parameter defines a number
+of seconds for which the leases are stored before they are
+removed.
+
+% ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE successfully deleted %1 expired-reclaimed leases
+This debug message is issued when the server successfully deletes
+"expired-reclaimed" leases from the lease database. The number of
+deleted leases is included in the log message.
+
+% ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED deletion of expired-reclaimed leases failed: %1
+This error message is issued when the deletion of "expired-reclaimed"
+leases from the database failed. The error message is appended to
+the log message.
+
 % ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED %1: requested address %2 is reserved
 % ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED %1: requested address %2 is reserved
 This message is issued when the allocation engine refused to
 This message is issued when the allocation engine refused to
 allocate address requested by the client because this
 allocate address requested by the client because this
@@ -316,6 +355,22 @@ the number of reclaimed leases may also be limited by the timeout
 value, configured with 'max-reclaim-time'. The message includes the
 value, configured with 'max-reclaim-time'. The message includes the
 number of reclaimed leases and the total time.
 number of reclaimed leases and the total time.
 
 
+% ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW expired leases still exist after %1 reclamations
+This warning message is issued when the server has been unable to
+reclaim all expired leases in a specified number of consecutive
+attempts. This indicates that the value of "reclaim-timer-wait-time"
+may be too high. However, if this is just a short burst of leases'
+expirations the value does not have to be modified and the server
+should deal with this in subsequent reclamation attempts. If this
+is a result of a permanent increase of the server load, the value
+of "reclaim-timer-wait-time" should be decreased, or the
+values of "max-reclaim-leases" and "max-reclaim-time" should be
+increased to allow processing more leases in a single cycle.
+Alternatively, these values may be set to 0 to remove the
+limitations on the number of leases and duration. However, this
+may result in longer periods of server's unresponsiveness to
+DHCP packets, while it processes the expired leases.
+
 % ALLOC_ENGINE_V6_LEASES_RECLAMATION_START starting reclamation of expired leases (limit = %1 leases or %2 seconds)
 % ALLOC_ENGINE_V6_LEASES_RECLAMATION_START starting reclamation of expired leases (limit = %1 leases or %2 seconds)
 This debug message is issued when the allocation engine starts the
 This debug message is issued when the allocation engine starts the
 reclamation of the expired leases. The maximum number of leases to
 reclamation of the expired leases. The maximum number of leases to
@@ -330,6 +385,29 @@ been reclaimed, because of the timeout, will be reclaimed when the
 next scheduled reclamation is started. The argument is the timeout
 next scheduled reclamation is started. The argument is the timeout
 value expressed in milliseconds.
 value expressed in milliseconds.
 
 
+% ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES all expired leases have been reclaimed
+This debug message is issued when the server reclaims all expired
+DHCPv6 leases in the database.
+
+% ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE begin deletion of reclaimed leases expired more than %1 seconds ago
+This debug message is issued when the allocation engine begins
+deletion of the reclaimed leases which have expired more than
+a specified number of seconds ago. This operation is triggered
+periodically according to the "flush-reclaimed-timer-wait-time"
+parameter. The "hold-reclaimed-time" parameter defines a number
+of seconds for which the leases are stored before they are
+removed.
+
+% ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE successfully deleted %1 expired-reclaimed leases
+This debug message is issued when the server successfully deletes
+"expired-reclaimed" leases from the lease database. The number of
+deleted leases is included in the log message.
+
+% ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED deletion of expired-reclaimed leases failed: %1
+This error message is issued when the deletion of "expired-reclaimed"
+leases from the database failed. The error message is appended to
+the log message.
+
 % ALLOC_ENGINE_V6_RENEW_HR allocating leases reserved for the client %1 as a result of Renew
 % ALLOC_ENGINE_V6_RENEW_HR allocating leases reserved for the client %1 as a result of Renew
 This debug message is issued when the allocation engine tries to
 This debug message is issued when the allocation engine tries to
 allocate reserved leases for the client sending a Renew message.
 allocate reserved leases for the client sending a Renew message.

+ 15 - 8
src/lib/dhcpsrv/cfg_expiration.cc

@@ -40,13 +40,22 @@ const uint16_t CfgExpiration::LIMIT_MAX_RECLAIM_TIME = 10000;
 const uint16_t CfgExpiration::LIMIT_UNWARNED_RECLAIM_CYCLES =
 const uint16_t CfgExpiration::LIMIT_UNWARNED_RECLAIM_CYCLES =
     std::numeric_limits<uint16_t>::max();
     std::numeric_limits<uint16_t>::max();
 
 
-CfgExpiration::CfgExpiration()
+// Timers' names
+const std::string CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME =
+    "reclaim-expired-leases";
+
+const std::string CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME =
+    "flush-reclaimed-leases";
+
+CfgExpiration::CfgExpiration(const bool test_mode)
     : reclaim_timer_wait_time_(DEFAULT_RECLAIM_TIMER_WAIT_TIME),
     : reclaim_timer_wait_time_(DEFAULT_RECLAIM_TIMER_WAIT_TIME),
-    flush_reclaimed_timer_wait_time_(DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME),
-    hold_reclaimed_time_(DEFAULT_HOLD_RECLAIMED_TIME),
-    max_reclaim_leases_(DEFAULT_MAX_RECLAIM_LEASES),
-    max_reclaim_time_(DEFAULT_MAX_RECLAIM_TIME),
-    unwarned_reclaim_cycles_(DEFAULT_UNWARNED_RECLAIM_CYCLES) {
+      flush_reclaimed_timer_wait_time_(DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME),
+      hold_reclaimed_time_(DEFAULT_HOLD_RECLAIMED_TIME),
+      max_reclaim_leases_(DEFAULT_MAX_RECLAIM_LEASES),
+      max_reclaim_time_(DEFAULT_MAX_RECLAIM_TIME),
+      unwarned_reclaim_cycles_(DEFAULT_UNWARNED_RECLAIM_CYCLES),
+      timer_mgr_(TimerMgr::instance()),
+      test_mode_(test_mode) {
 }
 }
 
 
 void
 void
@@ -102,7 +111,5 @@ CfgExpiration::rangeCheck(const int64_t value, const uint64_t max_value,
     }
     }
 }
 }
 
 
-
-
 }
 }
 }
 }

+ 114 - 1
src/lib/dhcpsrv/cfg_expiration.h

@@ -15,6 +15,9 @@
 #ifndef CFG_EXPIRATION_H
 #ifndef CFG_EXPIRATION_H
 #define CFG_EXPIRATION_H
 #define CFG_EXPIRATION_H
 
 
+#include <asiolink/interval_timer.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 #include <stdint.h>
 #include <string>
 #include <string>
@@ -111,10 +114,26 @@ public:
 
 
     //@}
     //@}
 
 
+    /// @name Timers' names
+    //@{
+
+    /// @brief Name of the timer for reclaiming expired leases.
+    static const std::string RECLAIM_EXPIRED_TIMER_NAME;
+
+    /// @brief Name of the timer for flushing relclaimed leases.
+    static const std::string FLUSH_RECLAIMED_TIMER_NAME;
+
+    //@}
+
     /// @brief Constructor.
     /// @brief Constructor.
     ///
     ///
     /// Sets all parameters to their defaults.
     /// Sets all parameters to their defaults.
-    CfgExpiration();
+    ///
+    /// @param test_mode Indicates if the instance should be created in the
+    /// test mode. In this mode the intervals for the timers are considered to
+    /// be specified in milliseconds, rather than seconds. This facilitates
+    /// testing execution of timers without the delays.
+    CfgExpiration(const bool test_mode = false);
 
 
     /// @brief Returns reclaim-timer-wait-time
     /// @brief Returns reclaim-timer-wait-time
     uint16_t getReclaimTimerWaitTime() const {
     uint16_t getReclaimTimerWaitTime() const {
@@ -176,6 +195,42 @@ public:
     /// @param unwarned_reclaim_cycles New value.
     /// @param unwarned_reclaim_cycles New value.
     void setUnwarnedReclaimCycles(const int64_t unwarned_reclaim_cycles);
     void setUnwarnedReclaimCycles(const int64_t unwarned_reclaim_cycles);
 
 
+    /// @brief Setup timers for the reclamation of expired leases according
+    /// to the configuration parameters.
+    ///
+    /// This method includes the logic for setting the interval timers
+    /// performing the reclamation of the expired leases and the removal
+    /// of expired-reclaimed leases.
+    ///
+    /// The following is the sample code illustrating how to call this function
+    /// to setup the leases reclamation for the DHCPv4 server.
+    /// @code
+    ///     CfgExpiration cfg;
+    ///
+    ///     (set some cfg values here)
+    ///
+    ///     AllocEnginePtr alloc_engine(new AllocEngine(...));
+    ///     cfg.setupTimers(&AllocEngine::reclaimExpiredLeases4,
+    ///                     &AllocEngine::deleteExpiredReclaimedLeases4,
+    ///                     alloc_engine.get());
+    /// @endcode
+    ///
+    /// @param reclaim_fun Pointer to the leases reclamation routine.
+    /// @param delete_fun Pointer to the function which removes the
+    /// expired-reclaimed leases from the lease database.
+    /// @param instance_ptr Pointer to the instance of the object which
+    /// implements the lease reclamation routine. Typically it will be
+    /// the pointer to the @c AllocEngine. In case of unit tests it
+    /// will be a pointer to some test class which provides stub
+    /// implementation of the leases reclamation routines.
+    /// @tparam Instance Instance of the object in which both functions
+    /// are implemented.
+    template<typename Instance>
+    void setupTimers(void (Instance::*reclaim_fun)(const size_t, const uint16_t,
+                                                   const bool, const uint16_t),
+                     void (Instance::*delete_fun)(const uint32_t),
+                     Instance* instance_ptr) const;
+
 private:
 private:
 
 
     /// @brief Checks if the value being set by one of the modifiers is
     /// @brief Checks if the value being set by one of the modifiers is
@@ -209,6 +264,11 @@ private:
     /// @brief unwarned-reclaim-cycles.
     /// @brief unwarned-reclaim-cycles.
     uint16_t unwarned_reclaim_cycles_;
     uint16_t unwarned_reclaim_cycles_;
 
 
+    /// @brief Pointer to the instance of the Timer Manager.
+    TimerMgrPtr timer_mgr_;
+
+    /// @brief Indicates if the instance is in the test mode.
+    bool test_mode_;
 };
 };
 
 
 /// @name Pointers to the @c CfgExpiration objects.
 /// @name Pointers to the @c CfgExpiration objects.
@@ -221,6 +281,59 @@ typedef boost::shared_ptr<const CfgExpiration> ConstCfgExpirationPtr;
 
 
 //@}
 //@}
 
 
+template<typename Instance>
+void
+CfgExpiration::setupTimers(void (Instance::*reclaim_fun)(const size_t,
+                                                         const uint16_t,
+                                                         const bool,
+                                                         const uint16_t),
+                           void (Instance::*delete_fun)(const uint32_t),
+                           Instance* instance_ptr) const {
+    // One of the parameters passed to the leases' reclamation routine
+    // is a boolean value which indicates if reclaimed leases should
+    // be removed by the leases' reclamation routine. This is the case
+    // when the timer for flushing reclaimed leases is set to 0
+    // (disabled).
+    const bool flush_timer_disabled = (getFlushReclaimedTimerWaitTime() == 0);
+
+    // If the timer interval for the leases reclamation is non-zero
+    // the timer will be scheduled.
+    if (getReclaimTimerWaitTime() > 0) {
+        // In the test mode the interval is expressed in milliseconds.
+        // If this is not the test mode, the interval is in seconds.
+        const long reclaim_interval = test_mode_ ? getReclaimTimerWaitTime() :
+            1000 * getReclaimTimerWaitTime();
+        // Register timer for leases' reclamation routine.
+        timer_mgr_->registerTimer(RECLAIM_EXPIRED_TIMER_NAME,
+                                  boost::bind(reclaim_fun, instance_ptr,
+                                              getMaxReclaimLeases(),
+                                              getMaxReclaimTime(),
+                                              flush_timer_disabled,
+                                              getUnwarnedReclaimCycles()),
+                                  reclaim_interval,
+                                  asiolink::IntervalTimer::ONE_SHOT);
+        timer_mgr_->setup(RECLAIM_EXPIRED_TIMER_NAME);
+    }
+
+    // If the interval for the timer flusing expired-reclaimed leases
+    // is set we will schedule the timer.
+    if (!flush_timer_disabled) {
+        // The interval is specified in milliseconds if we're in the test mode.
+        // It is specified in seconds otherwise.
+        const long flush_interval = test_mode_ ?
+            getFlushReclaimedTimerWaitTime() :
+            1000 * getFlushReclaimedTimerWaitTime();
+        // Register and setup the timer.
+        timer_mgr_->registerTimer(FLUSH_RECLAIMED_TIMER_NAME,
+                                  boost::bind(delete_fun, instance_ptr,
+                                              getHoldReclaimedTime()),
+                                  flush_interval,
+                                  asiolink::IntervalTimer::ONE_SHOT);
+        timer_mgr_->setup(FLUSH_RECLAIMED_TIMER_NAME);
+    }
+}
+
+
 } // end of isc::dhcp namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace
 
 

+ 90 - 16
src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc

@@ -250,6 +250,20 @@ public:
         ASSERT_NO_THROW(updateLease(lease_index));
         ASSERT_NO_THROW(updateLease(lease_index));
     }
     }
 
 
+    /// @brief Marks lease as expired-reclaimed.
+    ///
+    /// @param lease_index Lease index. Must be between 0 and
+    /// @c TEST_LEASES_NUM.
+    /// @param secs Offset of the expiration time since now. For example
+    /// a value of 2 would set the lease expiration time to 2 seconds ago.
+    void reclaim(const uint16_t lease_index, const time_t secs) {
+        ASSERT_GT(leases_.size(), lease_index);
+        leases_[lease_index]->cltt_ = time(NULL) - secs -
+            leases_[lease_index]->valid_lft_;
+        leases_[lease_index]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+        ASSERT_NO_THROW(updateLease(lease_index));
+    }
+
     /// @brief Declines specified lease
     /// @brief Declines specified lease
     ///
     ///
     /// Sets specified lease to declined state and sets its probation-period.
     /// Sets specified lease to declined state and sets its probation-period.
@@ -291,6 +305,13 @@ public:
                                       const uint16_t timeout,
                                       const uint16_t timeout,
                                       const bool remove_lease) = 0;
                                       const bool remove_lease) = 0;
 
 
+    /// @brief Wrapper method for removing expired-reclaimed leases.
+    ///
+    /// @param secs The minimum amount of time, expressed in seconds,
+    /// for the lease to be left in the "expired-reclaimed" state
+    /// before it can be removed.
+    virtual void deleteExpiredReclaimedLeases(const uint32_t secs) = 0;
+
     /// @brief Test selected leases using the specified algorithms.
     /// @brief Test selected leases using the specified algorithms.
     ///
     ///
     /// This function picks leases from the range of 0 thru
     /// This function picks leases from the range of 0 thru
@@ -575,7 +596,7 @@ public:
 
 
     /// @brief Test that leases can be reclaimed without being removed.
     /// @brief Test that leases can be reclaimed without being removed.
     void testReclaimExpiredLeasesUpdateState() {
     void testReclaimExpiredLeasesUpdateState() {
-        for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
             // Mark leases with even indexes as expired.
             // Mark leases with even indexes as expired.
             if (evenLeaseIndex(i)) {
             if (evenLeaseIndex(i)) {
                 // The higher the index, the more expired the lease.
                 // The higher the index, the more expired the lease.
@@ -596,7 +617,7 @@ public:
 
 
     /// @brief Test that the leases may be reclaimed by being deleted.
     /// @brief Test that the leases may be reclaimed by being deleted.
     void testReclaimExpiredLeasesDelete() {
     void testReclaimExpiredLeasesDelete() {
-        for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
             // Mark leases with even indexes as expired.
             // Mark leases with even indexes as expired.
             if (evenLeaseIndex(i)) {
             if (evenLeaseIndex(i)) {
                 // The higher the index, the more expired the lease.
                 // The higher the index, the more expired the lease.
@@ -618,7 +639,7 @@ public:
     /// @brief Test that it is possible to specify the limit for the number
     /// @brief Test that it is possible to specify the limit for the number
     /// of reclaimed leases.
     /// of reclaimed leases.
     void testReclaimExpiredLeasesLimit() {
     void testReclaimExpiredLeasesLimit() {
-        for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
             // Mark all leaes as expired. The higher the index the less
             // Mark all leaes as expired. The higher the index the less
             // expired the lease.
             // expired the lease.
             expire(i, 1000 - i);
             expire(i, 1000 - i);
@@ -631,7 +652,7 @@ public:
         BOOST_STATIC_ASSERT(TEST_LEASES_NUM % reclamation_group_size == 0);
         BOOST_STATIC_ASSERT(TEST_LEASES_NUM % reclamation_group_size == 0);
 
 
         // Leases will be reclaimed in groups of 10.
         // Leases will be reclaimed in groups of 10.
-        for (int i = reclamation_group_size; i < TEST_LEASES_NUM;
+        for (unsigned int i = reclamation_group_size; i < TEST_LEASES_NUM;
              i += reclamation_group_size) {
              i += reclamation_group_size) {
 
 
             // Reclaim 10 most expired leases out of TEST_LEASES_NUM. Since
             // Reclaim 10 most expired leases out of TEST_LEASES_NUM. Since
@@ -659,7 +680,7 @@ public:
         // DNS must be started for the D2 client to accept NCRs.
         // DNS must be started for the D2 client to accept NCRs.
         ASSERT_NO_THROW(enableDDNS());
         ASSERT_NO_THROW(enableDDNS());
 
 
-        for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
             // Expire all leases with even indexes.
             // Expire all leases with even indexes.
             if (evenLeaseIndex(i)) {
             if (evenLeaseIndex(i)) {
                 // The higher the index, the more expired the lease.
                 // The higher the index, the more expired the lease.
@@ -688,7 +709,7 @@ public:
         // DNS must be started for the D2 client to accept NCRs.
         // DNS must be started for the D2 client to accept NCRs.
         ASSERT_NO_THROW(enableDDNS());
         ASSERT_NO_THROW(enableDDNS());
 
 
-        for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
             // Expire only leases with even indexes.
             // Expire only leases with even indexes.
             if (evenLeaseIndex(i)) {
             if (evenLeaseIndex(i)) {
                 // The higher the index, the more expired the lease.
                 // The higher the index, the more expired the lease.
@@ -700,7 +721,7 @@ public:
         BOOST_STATIC_ASSERT(TEST_LEASES_NUM % reclamation_group_size == 0);
         BOOST_STATIC_ASSERT(TEST_LEASES_NUM % reclamation_group_size == 0);
 
 
         // Leases will be reclaimed in groups of 10
         // Leases will be reclaimed in groups of 10
-        for (int i = 10; i < TEST_LEASES_NUM;  i += reclamation_group_size) {
+        for (unsigned int i = 10; i < TEST_LEASES_NUM;  i += reclamation_group_size) {
             // Reclaim 10 most expired leases. Note that the leases with the
             // Reclaim 10 most expired leases. Note that the leases with the
             // higher index are more expired. For example, if the
             // higher index are more expired. For example, if the
             // TEST_LEASES_NUM is equal to 100, the most expired lease will
             // TEST_LEASES_NUM is equal to 100, the most expired lease will
@@ -806,7 +827,7 @@ public:
     /// @brief This test verfies that callouts are executed for each expired
     /// @brief This test verfies that callouts are executed for each expired
     /// lease when installed.
     /// lease when installed.
     void testReclaimExpiredLeasesHooks() {
     void testReclaimExpiredLeasesHooks() {
-        for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
             if (evenLeaseIndex(i)) {
             if (evenLeaseIndex(i)) {
                 expire(i, 1000 - i);
                 expire(i, 1000 - i);
             }
             }
@@ -836,7 +857,7 @@ public:
     /// @brief This test verfies that callouts are executed for each expired
     /// @brief This test verfies that callouts are executed for each expired
     /// lease and that the lease is not reclaimed when skip flag is set.
     /// lease and that the lease is not reclaimed when skip flag is set.
     void testReclaimExpiredLeasesHooksWithSkip() {
     void testReclaimExpiredLeasesHooksWithSkip() {
-        for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
             if (evenLeaseIndex(i)) {
             if (evenLeaseIndex(i)) {
                 expire(i, 1000 - i);
                 expire(i, 1000 - i);
             }
             }
@@ -866,7 +887,7 @@ public:
     /// the execution of the lease reclamation routine.
     /// the execution of the lease reclamation routine.
     void testReclaimExpiredLeasesTimeout(const uint16_t timeout) {
     void testReclaimExpiredLeasesTimeout(const uint16_t timeout) {
         // Leases are segregated from the most expired to the least expired.
         // Leases are segregated from the most expired to the least expired.
-        for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
             expire(i, 2000 - i);
             expire(i, 2000 - i);
         }
         }
 
 
@@ -905,6 +926,27 @@ public:
                                UpperBound(TEST_LEASES_NUM)));
                                UpperBound(TEST_LEASES_NUM)));
     }
     }
 
 
+    /// @brief This test verifies that expired-reclaimed leases are removed
+    /// from the lease database.
+    void testDeleteExpiredReclaimedLeases() {
+        for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+            // Mark leases with even indexes as expired.
+            if (evenLeaseIndex(i)) {
+                // The higher the index, the more expired the lease.
+                reclaim(i, 10 + i);
+            }
+        }
+
+        // Run leases reclamation routine on all leases. This should result
+        // in removal of all leases with even indexes.
+        ASSERT_NO_THROW(deleteExpiredReclaimedLeases(10));
+
+        // Leases with odd indexes shouldn't be removed from the database.
+        EXPECT_TRUE(testLeases(&leaseExists, &oddLeaseIndex));
+        // Leases with even indexes should have been removed.
+        EXPECT_TRUE(testLeases(&leaseDoesntExist, &evenLeaseIndex));
+    }
+
     /// @brief Test that declined expired leases can be removed.
     /// @brief Test that declined expired leases can be removed.
     ///
     ///
     /// This method allows controlling remove_leases parameter when calling
     /// This method allows controlling remove_leases parameter when calling
@@ -1101,6 +1143,15 @@ public:
         engine_->reclaimExpiredLeases6(max_leases, timeout, remove_lease);
         engine_->reclaimExpiredLeases6(max_leases, timeout, remove_lease);
     }
     }
 
 
+    /// @brief Wrapper method for removing expired-reclaimed leases.
+    ///
+    /// @param secs The minimum amount of time, expressed in seconds,
+    /// for the lease to be left in the "expired-reclaimed" state
+    /// before it can be removed.
+    virtual void deleteExpiredReclaimedLeases(const uint32_t secs) {
+        engine_->deleteExpiredReclaimedLeases6(secs);
+    }
+
     /// @brief Test that statistics is updated when leases are reclaimed.
     /// @brief Test that statistics is updated when leases are reclaimed.
     void testReclaimExpiredLeasesStats();
     void testReclaimExpiredLeasesStats();
 
 
@@ -1189,7 +1240,7 @@ ExpirationAllocEngine6Test::testReclaimExpiredLeasesStats() {
     // This test requires that the number of leases is an even number.
     // This test requires that the number of leases is an even number.
     BOOST_STATIC_ASSERT(TEST_LEASES_NUM % 2 == 0);
     BOOST_STATIC_ASSERT(TEST_LEASES_NUM % 2 == 0);
 
 
-    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+    for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
         // Mark all leaes as expired. The higher the index the less
         // Mark all leaes as expired. The higher the index the less
         // expired the lease.
         // expired the lease.
         expire(i, 1000 - i);
         expire(i, 1000 - i);
@@ -1202,7 +1253,7 @@ ExpirationAllocEngine6Test::testReclaimExpiredLeasesStats() {
 
 
     // Leases will be reclaimed in groups of 8.
     // Leases will be reclaimed in groups of 8.
     const size_t reclamation_group_size = 8;
     const size_t reclamation_group_size = 8;
-    for (int i = reclamation_group_size; i < TEST_LEASES_NUM;
+    for (unsigned int i = reclamation_group_size; i < TEST_LEASES_NUM;
          i += reclamation_group_size) {
          i += reclamation_group_size) {
 
 
         // Reclaim 8 most expired leases out of TEST_LEASES_NUM.
         // Reclaim 8 most expired leases out of TEST_LEASES_NUM.
@@ -1298,9 +1349,16 @@ TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesShortTimeout) {
     testReclaimExpiredLeasesTimeout(1);
     testReclaimExpiredLeasesTimeout(1);
 }
 }
 
 
+// This test verifies that expired-reclaimed leases are removed from the
+// lease database.
+TEST_F(ExpirationAllocEngine6Test, deleteExpiredReclaimedLeases) {
+    BOOST_STATIC_ASSERT(TEST_LEASES_NUM >= 10);
+    testDeleteExpiredReclaimedLeases();
+}
+
 /// This test verifies that @ref AllocEngine::reclaimExpiredLeases6 properly
 /// This test verifies that @ref AllocEngine::reclaimExpiredLeases6 properly
 /// handles declined leases that have expired in case when it is told to
 /// handles declined leases that have expired in case when it is told to
-/// remove leases.
+/// remove leases.}
 TEST_F(ExpirationAllocEngine6Test, reclaimDeclined1) {
 TEST_F(ExpirationAllocEngine6Test, reclaimDeclined1) {
     testReclaimDeclined(true);
     testReclaimDeclined(true);
 }
 }
@@ -1382,6 +1440,15 @@ public:
         engine_->reclaimExpiredLeases4(max_leases, timeout, remove_lease);
         engine_->reclaimExpiredLeases4(max_leases, timeout, remove_lease);
     }
     }
 
 
+    /// @brief Wrapper method for removing expired-reclaimed leases.
+    ///
+    /// @param secs The minimum amount of time, expressed in seconds,
+    /// for the lease to be left in the "expired-reclaimed" state
+    /// before it can be removed.
+    virtual void deleteExpiredReclaimedLeases(const uint32_t secs) {
+        engine_->deleteExpiredReclaimedLeases4(secs);
+    }
+
     /// @brief Lease algorithm checking if NCR has been generated from client
     /// @brief Lease algorithm checking if NCR has been generated from client
     /// identifier.
     /// identifier.
     ///
     ///
@@ -1539,7 +1606,7 @@ ExpirationAllocEngine4Test::testReclaimExpiredLeasesWithDDNSAndClientId() {
     // DNS must be started for the D2 client to accept NCRs.
     // DNS must be started for the D2 client to accept NCRs.
     ASSERT_NO_THROW(enableDDNS());
     ASSERT_NO_THROW(enableDDNS());
 
 
-    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+    for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
         // Set client identifiers for leases with even indexes only.
         // Set client identifiers for leases with even indexes only.
         if (evenLeaseIndex(i)) {
         if (evenLeaseIndex(i)) {
             setUniqueClientId(i);
             setUniqueClientId(i);
@@ -1568,7 +1635,7 @@ ExpirationAllocEngine4Test::testReclaimExpiredLeasesStats() {
     // This test requires that the number of leases is an even number.
     // This test requires that the number of leases is an even number.
     BOOST_STATIC_ASSERT(TEST_LEASES_NUM % 2 == 0);
     BOOST_STATIC_ASSERT(TEST_LEASES_NUM % 2 == 0);
 
 
-    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+    for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
         // Mark all leaes as expired. The higher the index the less
         // Mark all leaes as expired. The higher the index the less
         // expired the lease.
         // expired the lease.
         expire(i, 1000 - i);
         expire(i, 1000 - i);
@@ -1580,7 +1647,7 @@ ExpirationAllocEngine4Test::testReclaimExpiredLeasesStats() {
 
 
     // Leases will be reclaimed in groups of 8.
     // Leases will be reclaimed in groups of 8.
     const size_t reclamation_group_size = 8;
     const size_t reclamation_group_size = 8;
-    for (int i = reclamation_group_size; i < TEST_LEASES_NUM;
+    for (unsigned int i = reclamation_group_size; i < TEST_LEASES_NUM;
          i += reclamation_group_size) {
          i += reclamation_group_size) {
 
 
         // Reclaim 8 most expired leases out of TEST_LEASES_NUM.
         // Reclaim 8 most expired leases out of TEST_LEASES_NUM.
@@ -1683,6 +1750,13 @@ TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesShortTimeout) {
     testReclaimExpiredLeasesTimeout(1);
     testReclaimExpiredLeasesTimeout(1);
 }
 }
 
 
+// This test verifies that expired-reclaimed leases are removed from the
+// lease database.
+TEST_F(ExpirationAllocEngine4Test, deleteExpiredReclaimedLeases) {
+    BOOST_STATIC_ASSERT(TEST_LEASES_NUM >= 10);
+    testDeleteExpiredReclaimedLeases();
+}
+
 /// This test verifies that @ref AllocEngine::reclaimExpiredLeases4 properly
 /// This test verifies that @ref AllocEngine::reclaimExpiredLeases4 properly
 /// handles declined leases that have expired in case when it is told to
 /// handles declined leases that have expired in case when it is told to
 /// remove leases.
 /// remove leases.

+ 266 - 0
src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc

@@ -13,14 +13,19 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <config.h>
 #include <config.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfg_expiration.h>
 #include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/timer_mgr.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <util/stopwatch.h>
 #include <boost/function.hpp>
 #include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <stdint.h>
 #include <stdint.h>
 
 
 using namespace isc;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::util;
 
 
 namespace {
 namespace {
 
 
@@ -158,4 +163,265 @@ TEST(CfgExpirationTest, getUnwarnedReclaimCycles) {
                            &CfgExpiration::getUnwarnedReclaimCycles);
                            &CfgExpiration::getUnwarnedReclaimCycles);
 }
 }
 
 
+/// @brief Implements test routines for leases reclamation.
+///
+/// This class implements two routines called by the @c CfgExpiration object
+/// instead of the typical routines for leases' reclamation in the
+/// @c AllocEngine. These methods do not perform the actual reclamation,
+/// but instead they record the number of calls to them and the parameters
+/// with which they were executed. This allows for checking if the
+/// @c CfgExpiration object calls the leases reclamation routine with the
+/// appropriate parameteres.
+class LeaseReclamationStub {
+public:
+
+    /// @brief Collection of parameters with which the @c reclaimExpiredLeases
+    /// method is called.
+    ///
+    /// Examination of these values allows for assesment if the @c CfgExpiration
+    /// calls the routine with the appropriate values.
+    struct RecordedParams {
+        /// @brief Maximum number of leases to be processed.
+        size_t max_leases;
+
+        /// @brief Timeout for processing leases in milliseconds.
+        uint16_t timeout;
+
+        /// @brief Boolean flag which indicates if the leases should be removed
+        /// when reclaimed.
+        bool remove_lease;
+
+        /// @brief Maximum number of reclamation attempts after which all leases
+        /// should be reclaimed.
+        uint16_t max_unwarned_cycles;
+
+        /// @brief Constructor
+        ///
+        /// Sets all numeric values to 0xFFFF and the boolean values to false.
+        RecordedParams()
+            : max_leases(0xFFFF), timeout(0xFFFF), remove_lease(false),
+              max_unwarned_cycles(0xFFFF) {
+        }
+    };
+
+    /// @brief Constructor.
+    ///
+    /// Resets recorded parameters and obtains the instance of the @c TimerMgr.
+    LeaseReclamationStub()
+        : reclaim_calls_count_(0), delete_calls_count_(0), reclaim_params_(),
+          secs_param_(0), timer_mgr_(TimerMgr::instance()) {
+    }
+
+    /// @brief Stub implementation of the leases' reclamation routine.
+    ///
+    /// @param max_leases Maximum number of leases to be processed.
+    /// @param timeout Timeout for processing leases in milliseconds.
+    /// @remove_lease Boolean flag which indicates if the leases should be
+    /// removed when it is reclaimed.
+    /// @param Maximum number of reclamation attempts after which all leases
+    /// should be reclaimed.
+    void
+    reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
+                         const bool remove_lease,
+                         const uint16_t max_unwarned_cycles) {
+        // Increase calls counter for this method.
+        ++reclaim_calls_count_;
+        // Record all parameters with which this method has been called.
+        reclaim_params_.max_leases = max_leases;
+        reclaim_params_.timeout = timeout;
+        reclaim_params_.remove_lease = remove_lease;
+        reclaim_params_.max_unwarned_cycles = max_unwarned_cycles;
+
+        // Leases' reclamation routine is responsible for re-scheduling
+        // the timer.
+        timer_mgr_->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
+    }
+
+    /// @brief Stub implementation of the routine which flushes
+    /// expired-reclaimed leases.
+    ///
+    /// @param secs Specifies the minimum amount of time, expressed in
+    /// seconds, that must elapse before the expired-reclaime lease is
+    /// deleted from the database.
+    void
+    deleteReclaimedLeases(const uint32_t secs) {
+        // Increase calls counter for this method.
+        ++delete_calls_count_;
+        // Record the value of the parameter.
+        secs_param_ = secs;
+
+        // Routine which flushes the reclaimed leases is responsible for
+        // re-scheduling the timer.
+        timer_mgr_->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
+    }
+
+    /// @brief Counter holding the number of calls to @c reclaimExpiredLeases.
+    long reclaim_calls_count_;
+
+    /// @brief Counter holding the number of calls to @c deleteReclaimedLeases.
+    long delete_calls_count_;
+
+    /// @brief Structure holding values of parameters with which the
+    /// @c reclaimExpiredLeases was called.
+    ///
+    /// These values are overriden on subsequent calls to this method.
+    RecordedParams reclaim_params_;
+
+    /// @brief Value of the parameter with which the @c deleteReclaimedLeases
+    /// was called.
+    uint32_t secs_param_;
+
+private:
+
+    /// @brief Pointer to the @c TimerMgr.
+    TimerMgrPtr timer_mgr_;
+
+};
+
+/// @brief Pointer to the @c LeaseReclamationStub.
+typedef boost::shared_ptr<LeaseReclamationStub> LeaseReclamationStubPtr;
+
+/// @brief Test fixture class for the @c CfgExpiration.
+class CfgExpirationTimersTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Creates instance of the test fixture class. Besides initialization
+    /// of the class members, it also stops the @c TimerMgr worker thread
+    /// and removes any registered timers.
+    CfgExpirationTimersTest()
+        : timer_mgr_(TimerMgr::instance()),
+          stub_(new LeaseReclamationStub()),
+          cfg_(true) {
+        cleanupTimerMgr();
+    }
+
+    /// @brief Destructor.
+    ///
+    /// It stops the @c TimerMgr worker thread and removes any registered
+    /// timers.
+    virtual ~CfgExpirationTimersTest() {
+        cleanupTimerMgr();
+    }
+
+    /// @brief Stop @c TimerMgr worker thread and remove the timers.
+    void cleanupTimerMgr() const {
+        timer_mgr_->stopThread();
+        timer_mgr_->unregisterTimers();
+    }
+
+    /// @brief Runs timers for specified time.
+    ///
+    /// Internally, this method calls @c IfaceMgr::receive6 to run the
+    /// callbacks for the installed timers.
+    ///
+    /// @param timeout_ms Amount of time after which the method returns.
+    void runTimersWithTimeout(const long timeout_ms) {
+        Stopwatch stopwatch;
+        while (stopwatch.getTotalMilliseconds() < timeout_ms) {
+            // Block for up to one millisecond waiting for the timers'
+            // callbacks to be executed.
+            IfaceMgr::instancePtr()->receive6(0, 1000);
+        }
+    }
+
+    /// @brief Setup timers according to the configuration and run them
+    /// for the specified amount of time.
+    ///
+    /// @param timeout_ms Timeout in milliseconds.
+    void setupAndRun(const long timeout_ms) {
+        cfg_.setupTimers(&LeaseReclamationStub::reclaimExpiredLeases,
+                         &LeaseReclamationStub::deleteReclaimedLeases,
+                         stub_.get());
+        // Run timers.
+        ASSERT_NO_THROW({
+            timer_mgr_->startThread();
+            runTimersWithTimeout(timeout_ms);
+            timer_mgr_->stopThread();
+        });
+    }
+
+    /// @brief Pointer to the @c TimerMgr.
+    TimerMgrPtr timer_mgr_;
+
+    /// @brief Pointer to the @c LeaseReclamationStub instance.
+    LeaseReclamationStubPtr stub_;
+
+    /// @brief Instance of the @c CfgExpiration class used by the tests.
+    CfgExpiration cfg_;
+};
+
+// Test that the reclamation routines are called with the appropriate parameters.
+TEST_F(CfgExpirationTimersTest, reclamationParameters) {
+    // Set this value to true, to make sure that the timer callback would
+    // modify this value to false.
+    stub_->reclaim_params_.remove_lease = true;
+
+    // Set parameters to some non-default values.
+    cfg_.setMaxReclaimLeases(1000);
+    cfg_.setMaxReclaimTime(1500);
+    cfg_.setHoldReclaimedTime(1800);
+    cfg_.setUnwarnedReclaimCycles(13);
+
+    // Run timers for 500ms.
+    ASSERT_NO_FATAL_FAILURE(setupAndRun(500));
+
+    // Make sure we had more than one call to the reclamation routine.
+    ASSERT_GT(stub_->reclaim_calls_count_, 1);
+    // Make sure it was called with appropriate arguments.
+    EXPECT_EQ(1000, stub_->reclaim_params_.max_leases);
+    EXPECT_EQ(1500, stub_->reclaim_params_.timeout);
+    EXPECT_FALSE(stub_->reclaim_params_.remove_lease);
+    EXPECT_EQ(13, stub_->reclaim_params_.max_unwarned_cycles);
+
+    // Make sure we had more than one call to the routine which flushes
+    // expired reclaimed leases.
+    ASSERT_GT(stub_->delete_calls_count_, 1);
+    // Make sure that the argument was correct.
+    EXPECT_EQ(1800, stub_->secs_param_);
+}
+
+// This test verifies that if the value of "flush-reclaimed-timer-wait-time"
+// configuration parameter is set to 0, the lease reclamation routine would
+// delete reclaimed leases from a lease database.
+TEST_F(CfgExpirationTimersTest, noLeaseAffinity) {
+    // Set the timer for flushing leases to 0. This effectively disables
+    // the timer.
+    cfg_.setFlushReclaimedTimerWaitTime(0);
+
+    // Run the lease reclamation timer for a while.
+    ASSERT_NO_FATAL_FAILURE(setupAndRun(500));
+
+    // Make sure that the lease reclamation routine has been executed a
+    // couple of times.
+    ASSERT_GT(stub_->reclaim_calls_count_, 1);
+    EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_LEASES,
+              stub_->reclaim_params_.max_leases);
+    EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_TIME,
+              stub_->reclaim_params_.timeout);
+    // When the "flush" timer is disabled, the lease reclamation routine is
+    // responsible for removal of reclaimed leases. This is controlled using
+    // the "remove_lease" parameter which should be set to true in this case.
+    EXPECT_TRUE(stub_->reclaim_params_.remove_lease);
+
+    // The routine flushing reclaimed leases should not be run at all.
+    EXPECT_EQ(0, stub_->delete_calls_count_);
+}
+
+// This test verfies that lease reclamation may be disabled.
+TEST_F(CfgExpirationTimersTest, noLeaseReclamation) {
+    // Disable both timers.
+    cfg_.setReclaimTimerWaitTime(0);
+    cfg_.setFlushReclaimedTimerWaitTime(0);
+
+    // Wait for 500ms.
+    ASSERT_NO_FATAL_FAILURE(setupAndRun(500));
+
+    // Make sure that neither leases' reclamation routine nor the routine
+    // flushing expired-reclaimed leases was run.
+    EXPECT_EQ(0, stub_->reclaim_calls_count_);
+    EXPECT_EQ(0, stub_->delete_calls_count_);
+}
+
 } // end of anonymous namespace
 } // end of anonymous namespace