Browse Source

[3975] Scheduling lease expiration timers in the DHCP servers.

The timers are scheduled in the ControlledDhcpvXSrv instances. The
unit tests are located in the kea_controller_unittest.cc.
Marcin Siodelski 9 years ago
parent
commit
4517ba7bfa

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

@@ -135,20 +135,6 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr 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
     // and don't bother with DDNS stuff.
     try {
@@ -181,6 +167,32 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
     CfgMgr::instance().getStagingCfg()->getCfgIface()->
         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);
 }
@@ -230,7 +242,7 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
         cleanup();
 
         // Stop worker thread running timers, if it is running.
-        timer_mgr_->stopThread();
+        TimerMgr::instance()->stopThread();
 
         // Close the command socket (if it exists).
         CommandMgr::instance().closeCommandSocket();
@@ -262,5 +274,22 @@ void ControlledDhcpv4Srv::sessionReader(void) {
     }
 }
 
+void
+ControlledDhcpv4Srv::reclaimExpiredLeases(const size_t max_leases,
+                                          const uint16_t timeout,
+                                          const bool remove_lease) {
+    server_->alloc_engine_->reclaimExpiredLeases4(max_leases, timeout,
+                                                  remove_lease);
+    // 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 namespace

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

@@ -150,6 +150,32 @@ private:
     commandConfigReloadHandler(const std::string& command,
                                isc::data::ConstElementPtr args);
 
+
+    /// @brief Reclaims expired IPv4 leases and reschedules timer.
+    ///
+    /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases4.
+    /// 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).
+    void reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
+                              const bool remove_lease);
+
+    /// @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.
     ///
     /// This is required for config and command handlers to gain access to

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

@@ -14,11 +14,17 @@
 
 #include <config.h>
 
+#include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
 #include <dhcp/dhcp4.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <log/logger_support.h>
+#include <util/stopwatch.h>
 
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
@@ -58,6 +64,7 @@ public:
     }
 
     ~JSONFileBackendTest() {
+        LeaseMgrFactory::destroy();
         isc::log::setDefaultLoggingOutput();
         static_cast<void>(remove(TEST_FILE));
     };
@@ -77,6 +84,21 @@ public:
         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()->receive4(0, 1000);
+        }
+    }
+
     /// Name of a config file used during tests
     static const char* TEST_FILE;
 };
@@ -303,4 +325,79 @@ 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.
+    EXPECT_FALSE(lease_mgr.getLease4(IOAddress("10.0.0.2")));
+}
+
 } // End of anonymous namespace

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

@@ -131,21 +131,6 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr 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
     // and don't bother with DDNS stuff.
     try {
@@ -178,6 +163,33 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
     // of the interfaces.
     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);
 }
 
@@ -225,8 +237,10 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
     try {
         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_->unregisterTimers();
 
         // Close the command socket (if it exists).
         CommandMgr::instance().closeCommandSocket();
@@ -258,5 +272,23 @@ void ControlledDhcpv6Srv::sessionReader(void) {
     }
 }
 
+void
+ControlledDhcpv6Srv::reclaimExpiredLeases(const size_t max_leases,
+                                          const uint16_t timeout,
+                                          const bool remove_lease) {
+    server_->alloc_engine_->reclaimExpiredLeases6(max_leases, timeout,
+                                                  remove_lease);
+    // 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 namespace

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

@@ -150,6 +150,31 @@ private:
     commandConfigReloadHandler(const std::string& command,
                                isc::data::ConstElementPtr args);
 
+    /// @brief Reclaims expired IPv6 leases and reschedules timer.
+    ///
+    /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases6.
+    /// 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).
+    void reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
+                              const bool remove_lease);
+
+    /// @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.
     ///
     /// 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";
 
 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);

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

@@ -823,12 +823,6 @@ private:
     /// @param query packet transmitted
     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)
     OptionPtr serverid_;
 
@@ -841,6 +835,12 @@ protected:
     /// initiate server shutdown procedure.
     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
     /// are waiting for sending to kea-dhcp-ddns module.
     std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;

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

@@ -14,11 +14,17 @@
 
 #include <config.h>
 
+#include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/duid.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <log/logger_support.h>
+#include <util/stopwatch.h>
 
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
@@ -53,6 +59,7 @@ public:
     }
 
     ~JSONFileBackendTest() {
+        LeaseMgrFactory::destroy();
         isc::log::setDefaultLoggingOutput();
         static_cast<void>(remove(TEST_FILE));
     };
@@ -66,6 +73,21 @@ public:
         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;
 };
 
@@ -245,4 +267,83 @@ TEST_F(JSONFileBackendTest, configBroken) {
     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.
+    EXPECT_FALSE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2")));
+}
+
 } // End of anonymous namespace

+ 45 - 0
src/lib/dhcpsrv/alloc_engine.cc

@@ -1406,6 +1406,29 @@ AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeo
 }
 
 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,
                                    const bool remove_lease) {
 
@@ -1535,6 +1558,28 @@ AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeo
 }
 
 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
 AllocEngine::reclaimDeclined(const Lease4Ptr& lease) {
 
     if (!lease || (lease->state_ != Lease::STATE_DECLINED) ) {

+ 15 - 0
src/lib/dhcpsrv/alloc_engine.h

@@ -514,6 +514,13 @@ public:
     void reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
                                const bool remove_lease);
 
+    /// @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.
     ///
     /// This method retrieves a collection of expired leases and reclaims them.
@@ -560,6 +567,14 @@ public:
     void reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeout,
                                const bool remove_lease);
 
+    /// @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.
     ///
     /// Attempts to find appropriate host reservation in HostMgr. If found, it

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

@@ -68,6 +68,25 @@ client sending the DHCPDISCOVER has a reservation for the specified
 address. The allocation engine will try to offer this address to
 the 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_LEASE_RECLAMATION_FAILED failed to reclaim the lease %1: %2
 This error message is logged when the allocation engine fails to
 reclaim an expired lease. The reason for the failure is included in the
@@ -323,6 +342,25 @@ been reclaimed, because of the timeout, will be reclaimed when the
 next scheduled reclamation is started. The argument is the timeout
 value expressed in milliseconds.
 
+% 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
 This debug message is issued when the allocation engine tries to
 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 =
     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),
-    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
@@ -102,7 +111,5 @@ CfgExpiration::rangeCheck(const int64_t value, const uint64_t max_value,
     }
 }
 
-
-
 }
 }

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

@@ -15,6 +15,9 @@
 #ifndef 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 <stdint.h>
 #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.
     ///
     /// 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
     uint16_t getReclaimTimerWaitTime() const {
@@ -176,6 +195,42 @@ public:
     /// @param unwarned_reclaim_cycles New value.
     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),
+                     void (Instance::*delete_fun)(const uint32_t),
+                     Instance* instance_ptr) const;
+
 private:
 
     /// @brief Checks if the value being set by one of the modifiers is
@@ -209,6 +264,11 @@ private:
     /// @brief 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.
@@ -221,6 +281,57 @@ 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),
+                           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),
+                                  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 namespace
 

+ 74 - 0
src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc

@@ -250,6 +250,20 @@ public:
         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
     ///
     /// Sets specified lease to declined state and sets its probation-period.
@@ -291,6 +305,13 @@ public:
                                       const uint16_t timeout,
                                       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.
     ///
     /// This function picks leases from the range of 0 thru
@@ -905,6 +926,27 @@ public:
                                UpperBound(TEST_LEASES_NUM)));
     }
 
+    /// @brief This test verifies that expired-reclaimed leases are removed
+    /// from the lease database.
+    void testDeleteExpiredReclaimedLeases() {
+        for (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.
     ///
     /// This method allows controlling remove_leases parameter when calling
@@ -1086,6 +1128,15 @@ public:
         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.
     void testReclaimExpiredLeasesStats();
 
@@ -1283,6 +1334,13 @@ TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesShortTimeout) {
     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();
+}
+
 // *******************************************************
 //
 // DHCPv4 lease reclamation routine tests start here!
@@ -1346,6 +1404,15 @@ public:
         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
     /// identifier.
     ///
@@ -1647,6 +1714,13 @@ TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesShortTimeout) {
     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
 /// handles declined leases that have expired in case when it is told to
 /// remove leases.

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

@@ -13,14 +13,19 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/timer_mgr.h>
 #include <exceptions/exceptions.h>
+#include <util/stopwatch.h>
 #include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
 #include <gtest/gtest.h>
 #include <stdint.h>
 
 using namespace isc;
 using namespace isc::dhcp;
+using namespace isc::util;
 
 namespace {
 
@@ -158,4 +163,243 @@ TEST(CfgExpirationTest, 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 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.
+    void
+    reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
+                         const bool remove_lease) {
+        // 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;
+
+        // 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 parameters to some non-default values.
+    cfg_.setMaxReclaimLeases(1000);
+    cfg_.setMaxReclaimTime(1500);
+    cfg_.setHoldReclaimedTime(1800);
+
+    // 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);
+
+    // 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