Commit 4517ba7b authored by Marcin Siodelski's avatar Marcin Siodelski

[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.
parent 73fe42ad
......@@ -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
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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
......
......@@ -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);
......
......@@ -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_;
......
......@@ -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
......@@ -1405,6 +1405,29 @@ AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeo
.arg(stopwatch.logFormatTotalDuration());
}
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) {
......@@ -1534,6 +1557,28 @@ AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeo
.arg(stopwatch.logFormatTotalDuration());
}
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) {
......
......@@ -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: