Commit c92665ce authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac3669'

parents 814a6cea 2f072e19
......@@ -1469,6 +1469,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
......@@ -1491,6 +1492,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
chmod +x src/lib/log/tests/
chmod +x src/lib/log/tests/
chmod +x src/lib/util/python/
chmod +x src/lib/util/tests/
chmod +x tools/
......@@ -3,9 +3,12 @@ AUTOMAKE_OPTIONS = subdir-objects
SUBDIRS = . testutils tests
dhcp_data_dir = @localstatedir@/@PACKAGE@
kea_lfc_location = @prefix@/sbin/kea-lfc
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(dhcp_data_dir)\""
AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
# Set location of the kea-lfc binary.
AM_CPPFLAGS += -DKEA_LFC_EXECUTABLE="\"$(kea_lfc_location)\""
......@@ -292,15 +292,39 @@ replaced by those read from the file.
A debug message issued when DHCP lease is being loaded from the file to memory.
% DHCPSRV_MEMFILE_LFC_LEASE_FILE_RENAME_FAIL failed to rename the current lease file %1 to %2, reason: %3
An error message logged when the Memfile lease database backend fails to
move the current lease file to a new file on which the cleanup should
be performed. This effectively means that the lease file cleanup
will not take place.
% DHCPSRV_MEMFILE_LFC_LEASE_FILE_REOPEN_FAIL failed to reopen lease file %1 after preparing input file for lease file cleanup, reason: %2, new leases will not be persisted!
An error message logged when the Memfile lease database backend
failed to re-open or re-create the lease file after renaming the
lease file for lease file cleanup. The server will continue to
operate but leases will not be persisted to disk.
% DHCPSRV_MEMFILE_LFC_SETUP setting up the Lease File Cleanup interval to %1 sec
An informational message logged when the Memfile lease database backend
configures the LFC to be executed periodically. The argument holds the
interval in seconds in which the LFC will be executed.
% DHCPSRV_MEMFILE_LFC_SPAWN_FAIL lease file cleanup failed to run because kea-lfc process couldn't be spawned
This error message is logged when the the Kea server fails to run kea-lfc,
the program that cleans up the lease file. The server will try again the
next time a lease file cleanup is scheduled. Although this message should
not appear and the reason why it did investigated, the occasional failure
to start the lease file cleanup will not impact operations. Should the
failure persist however, the size of the lease file will increase without bound.
% DHCPSRV_MEMFILE_LFC_START starting Lease File Cleanup
An informational message issued when the Memfile lease database backend
starts the periodic Lease File Cleanup.
% DHCPSRV_MEMFILE_LFC_EXECUTE executing Lease File Cleanup using: %1
An informational message issued when the Memfile lease database backend
starts a new process to perform Lease File Cleanup.
% DHCPSRV_MEMFILE_NO_STORAGE running in non-persistent mode, leases will be lost after restart
A warning message issued when writes of leases to disk have been disabled
in the configuration. This mode is useful for some kinds of performance
......@@ -17,6 +17,12 @@
#include <dhcpsrv/lease_file_loader.h>
#include <dhcpsrv/memfile_lease_mgr.h>
#include <exceptions/exceptions.h>
#include <util/pid_file.h>
#include <util/process_spawn.h>
#include <util/signal_set.h>
#include <cstdio>
#include <cstring>
#include <errno.h>
#include <iostream>
#include <sstream>
......@@ -25,12 +31,201 @@ namespace {
/// @brief Maximum number of errors to read the leases from the lease file.
const uint32_t MAX_LEASE_ERRORS = 100;
/// @brief A name of the environmental variable specifying the kea-lfc
/// program location.
/// This variable can be set by tests to point to the location of the
/// kea-lfc program within a build directory. If this variable is not
/// set, the backend will use the location of the kea-lfc in the
/// Kea installation directory.
} // end of anonymous namespace
using namespace isc::dhcp;
using namespace isc::util;
namespace isc {
namespace dhcp {
/// @brief Represents a configuration for Lease File Cleanup.
/// This class is solely used by the @c Memfile_LeaseMgr as a configuration
/// information storage for %Lease File Cleanup. Internally, it creates
/// the interval timer and assigns a callback function (pointer to which is
/// passed in the constructor), which will be called at the specified
/// intervals to perform the cleanup. It is also responsible for creating
/// and maintaing the object which is used to spawn the new process which
/// executes the @c kea-lfc program.
/// This functionality is enclosed in a separate class so as the implementation
/// details are not exposed in the @c Memfile_LeaseMgr header file and
/// to maintain a single place with the LFC configuration, instead of multiple
/// members and functions scattered in the @c Memfile_LeaseMgr class.
class LFCSetup {
/// @brief Constructor.
/// Assigns a pointer to the function triggered to perform the cleanup.
/// This pointer should point to the appropriate method of the
/// @c Memfile_LeaseMgr class.
/// @param callback A pointer to the callback function.
/// @param io_service An io service used to create the interval timer.
LFCSetup(asiolink::IntervalTimer::Callback callback,
asiolink::IOService& io_service);
/// @brief Sets the new configuration for the %Lease File Cleanup.
/// @param lfc_interval An interval in seconds at which the cleanup should
/// be performed.
/// @param lease_file4 A pointer to the DHCPv4 lease file to be cleaned up
/// or NULL. If this is NULL, the @c lease_file6 must be non-null.
/// @param lease_file6 A pointer to the DHCPv6 lease file to be cleaned up
/// or NULL. If this is NULL, the @c lease_file4 must be non-null.
void setup(const uint32_t lfc_interval,
const boost::shared_ptr<CSVLeaseFile4>& lease_file4,
const boost::shared_ptr<CSVLeaseFile6>& lease_file6);
/// @brief Spawns a new process.
void execute();
/// @brief Returns interval at which the cleanup is performed.
/// @return Interval in milliseconds.
long getInterval() const;
/// @brief Checks if the lease file cleanup is in progress.
/// @return true if the lease file cleanup is being executed.
bool isRunning() const;
/// @brief Returns exit code of the last completed cleanup.
int getExitStatus() const;
/// @brief Interval timer for LFC.
asiolink::IntervalTimer timer_;
/// @brief A pointer to the @c ProcessSpawn object used to execute
/// the LFC.
boost::scoped_ptr<util::ProcessSpawn> process_;
/// @brief A pointer to the callback function executed by the timer.
asiolink::IntervalTimer::Callback callback_;
/// @brief A PID of the last executed LFC process.
pid_t pid_;
LFCSetup::LFCSetup(asiolink::IntervalTimer::Callback callback,
asiolink::IOService& io_service)
: timer_(io_service), process_(), callback_(callback), pid_(0) {
LFCSetup::setup(const uint32_t lfc_interval,
const boost::shared_ptr<CSVLeaseFile4>& lease_file4,
const boost::shared_ptr<CSVLeaseFile6>& lease_file6) {
// If LFC is enabled, we have to setup the interval timer and prepare for
// executing the kea-lfc process.
if (lfc_interval > 0) {
std::string executable;
char* c_executable = getenv(KEA_LFC_EXECUTABLE_ENV_NAME);
if (c_executable == NULL) {
executable = KEA_LFC_EXECUTABLE;
} else {
executable = c_executable;
// Set the timer to call callback function periodically.
LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SETUP).arg(lfc_interval);
// Multiple the lfc_interval value by 1000 as this value specifies
// a timeout in seconds, whereas the setup() method expects the
// timeout in milliseconds.
timer_.setup(callback_, lfc_interval * 1000);
// Start preparing the command line for kea-lfc.
// Gather the base file name.
std::string lease_file = lease_file4 ? lease_file4->getFilename() :
// Create the other names by appending suffixes to the base name.
util::ProcessArgs args;
// Universe: v4 or v6.
args.push_back(lease_file4 ? "-4" : "-6");
// Previous file.
// Input file.
// Output file.
// Finish file.
// PID file.
// The configuration file is currently unused.
// Create the process (do not start it yet).
process_.reset(new util::ProcessSpawn(executable, args));
LFCSetup::getInterval() const {
return (timer_.getInterval());
LFCSetup::execute() {
try {
pid_ = process_->spawn();
std::cout << process_->getCommandLine() << std::endl;
} catch (const ProcessSpawnError& ex) {
LFCSetup::isRunning() const {
return (process_ && process_->isRunning(pid_));
LFCSetup::getExitStatus() const {
if (!process_) {
isc_throw(InvalidOperation, "unable to obtain LFC process exit code: "
" the process is NULL");
return (process_->getExitStatus(pid_));
Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
: LeaseMgr(parameters), lfc_timer_(*getIOService()) {
: LeaseMgr(parameters),
lfc_setup_(new LFCSetup(boost::bind(&Memfile_LeaseMgr::lfcCallback, this),
// Check the universe and use v4 file or v6 file.
std::string universe = getParameter("universe");
if (universe == "4") {
......@@ -51,11 +246,11 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
// issue a warning. It is ok not to write leases to disk when
// doing testing, but it should not be done in normal server
// operation.
if (!persistLeases(V4) && !persistLeases(V6)) {
if (!persistLeases(V4) && !persistLeases(V6)) {
} else {
......@@ -418,9 +613,38 @@ Memfile_LeaseMgr::rollback() {
Memfile_LeaseMgr::appendSuffix(const std::string& file_name,
const LFCFileType& file_type) {
std::string name(file_name);
switch (file_type) {
name += ".1";
name += ".2";
name += ".output";
name += ".completed";
case FILE_PID:
name += ".pid";
// Do not append any suffix for the FILE_CURRENT.
return (name);
Memfile_LeaseMgr::getIOServiceExecInterval() const {
return (static_cast<uint32_t>(lfc_timer_.getInterval() / 1000));
return (static_cast<uint32_t>(lfc_setup_->getInterval() / 1000));
......@@ -482,43 +706,22 @@ Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
return (lease_file);
Memfile_LeaseMgr::initTimers() {
std::string lfc_interval_str = "0";
try {
lfc_interval_str = getParameter("lfc-interval");
} catch (const std::exception& ex) {
// Ignore and default to 0.
uint32_t lfc_interval = 0;
try {
lfc_interval = boost::lexical_cast<uint32_t>(lfc_interval_str);
} catch (boost::bad_lexical_cast& ex) {
isc_throw(isc::BadValue, "invalid value of the lfc-interval "
<< lfc_interval_str << " specified");
if (lfc_interval > 0) {
asiolink::IntervalTimer::Callback cb =
boost::bind(&Memfile_LeaseMgr::lfcCallback, this);
LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SETUP).arg(lfc_interval);
lfc_timer_.setup(cb, lfc_interval * 1000);
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
void Memfile_LeaseMgr::loadLeasesFromFiles(const std::string& filename,
boost::shared_ptr<LeaseFileType>& lease_file,
StorageType& storage) {
// Check if the instance of the LFC is running right now. If it is
// running, we refuse to load leases as the LFC may be writing to the
// lease files right now. When the user retries server configuration
// it should go through.
/// @todo Consider applying a timeout for an LFC and retry when this
/// timeout elapses.
PIDFile pid_file(appendSuffix(filename, FILE_PID));
if (pid_file.check()) {
isc_throw(DbOpenError, "unable to load leases from files while the "
"lease file cleanup is in progress");
Memfile_LeaseMgr::lfcCallback() {
/// @todo Extend this method to spawn the new process which will
/// perform the Lease File Cleanup in background.
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
void Memfile_LeaseMgr::
loadLeasesFromFiles(const std::string& filename,
boost::shared_ptr<LeaseFileType>& lease_file,
StorageType& storage) {
// Load the leasefile.completed, if exists.
......@@ -530,13 +733,13 @@ loadLeasesFromFiles(const std::string& filename,
} else {
// If the leasefile.completed doesn't exist, let's load the leases
// from leasefile.2 and leasefile.1, if they exist.
lease_file.reset(new LeaseFileType(std::string(filename + ".2")));
lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_PREVIOUS)));
if (lease_file->exists()) {
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
lease_file.reset(new LeaseFileType(std::string(filename + ".1")));
lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_INPUT)));
if (lease_file->exists()) {
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
......@@ -553,3 +756,112 @@ loadLeasesFromFiles(const std::string& filename,
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
Memfile_LeaseMgr::isLFCRunning() const {
return (lfc_setup_->isRunning());
Memfile_LeaseMgr::getLFCExitStatus() const {
return (lfc_setup_->getExitStatus());
Memfile_LeaseMgr::lfcCallback() {
// Check if we're in the v4 or v6 space and use the appropriate file.
if (lease_file4_) {
} else if (lease_file6_) {
Memfile_LeaseMgr::lfcSetup() {
std::string lfc_interval_str = "0";
try {
lfc_interval_str = getParameter("lfc-interval");
} catch (const std::exception& ex) {
// Ignore and default to 0.
uint32_t lfc_interval = 0;
try {
lfc_interval = boost::lexical_cast<uint32_t>(lfc_interval_str);
} catch (boost::bad_lexical_cast& ex) {
isc_throw(isc::BadValue, "invalid value of the lfc-interval "
<< lfc_interval_str << " specified");
if (lfc_interval > 0) {
lfc_setup_->setup(lfc_interval, lease_file4_, lease_file6_);
template<typename LeaseFileType>
void Memfile_LeaseMgr::lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file) {
bool do_lfc = true;
// This string will hold a reason for the failure to rote the lease files.
std::string error_string = "(no details)";
// Check if the copy of the lease file exists already. If it does, it
// is an indication that another LFC instance may be in progress or
// may be stalled. In that case we don't want to rotate the current
// lease file to avoid overriding the contents of the existing file.
CSVFile lease_file_copy(appendSuffix(lease_file->getFilename(), FILE_INPUT));
if (!lease_file_copy.exists()) {
// Close the current file so as we can move it to the copy file.
// Move the current file to the copy file. Remember the result
// because we don't want to run LFC if the rename failed.
do_lfc = (rename(lease_file->getFilename().c_str(),
lease_file_copy.getFilename().c_str()) == 0);
if (!do_lfc) {
// Regardless if we successfully moved the current file or not,
// we need to re-open the current file for the server to write
// new lease updates. If the file has been successfully moved,
// this will result in creation of the new file. Otherwise,
// an existing file will be opened.
try {
} catch (const CSVFileError& ex) {
// If we're unable to open the lease file this is a serious
// error because the server will not be able to persist
// leases.
/// @todo We need to better address this error. It should
/// trigger an alarm (once we have a monitoring system in
/// place) so as an administrator can correct it. In
/// practice it should be very rare that this happens and
/// is most likely related to a human error, e.g. changing
/// file permissions.
// Reset the pointer to the file so as the backend doesn't
// try to write leases to disk.
do_lfc = false;
// Once the files have been rotated, or untouched if another LFC had
// not finished, a new process is started.
if (do_lfc) {
} // end of namespace isc::dhcp
} // end of namespace isc
......@@ -21,12 +21,16 @@
#include <dhcpsrv/csv_lease_file6.h>
#include <dhcpsrv/memfile_lease_storage.h>
#include <dhcpsrv/lease_mgr.h>
#include <util/process_spawn.h>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
namespace isc {
namespace dhcp {
class LFCSetup;
/// @brief Concrete implementation of a lease database backend using flat file.
/// This class implements a lease database backend using CSV files to store
......@@ -103,7 +107,7 @@ public:
/// @brief Specifies universe (V4, V6)
/// This enumeration is used by various functions in Memfile Lease Manager,
/// This enumeration is used by various functions in Memfile %Lease Manager,
/// to identify the lease type referred to. In particular, it is used by
/// functions operating on the lease files to distinguish between lease
/// files for DHCPv4 and DHCPv6.
......@@ -112,6 +116,11 @@ public:
/// @name Methods implementing the API of the lease database backend.
/// The following methods are implementing the API of the
/// @c LeaseMgr to manage leases.
/// @brief The sole lease manager constructor
/// dbconfig is a generic way of passing parameters. Parameters
......@@ -320,10 +329,54 @@ public:
/// support transactions, this is a no-op.
virtual void rollback();
/// @name Public type and method used to determine file names for LFC.
/// @brief Types of the lease files used by the %Lease File Cleanup.
/// This enumeration is used by a method which appends the appropriate
/// suffix to the lease file name.
enum LFCFileType {
FILE_CURRENT, ///< %Lease File
FILE_INPUT, ///< %Lease File Copy
FILE_PREVIOUS, ///< Previous %Lease File
FILE_OUTPUT, ///< LFC Output File
FILE_FINISH, ///< LFC Finish File
FILE_PID ///< PID File
/// @brief Appends appropriate suffix to the file name.
/// The suffix is selected using the LFC file type specified as a
/// parameter. Each file type uses a unique suffix or no suffix:
/// - Current File: no suffix
/// - %Lease File Copy or Input File: ".1"
/// - Previous File: ".2"
/// - LFC Output File: ".output"
/// - LFC Finish File: ".completed"
/// - LFC PID File: ".pid"
/// See for details.
/// @param file_name A base file name to which suffix is appended.
/// @param file_type An LFC file type.
/// @return A lease file name with a suffix appended.
static std::string appendSuffix(const std::string& file_name,
const LFCFileType& file_type);
/// @name Miscellaneous public convenience methods.
/// The following methods allow for retrieving useful information
/// about the state of the backend.
/// @brief Returns the interval at which the @c IOService events should
/// be released.
/// The Memfile backend may install a timer to execute the Lease File
/// The Memfile backend may install a timer to execute the %Lease File
/// Cleanup periodically. If this timer is installed, the method returns
/// the LFC interval in milliseconds.