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

[3669] Re-organized the LFC in Memfile_LeaseMgr to use updated ProcessSpawn

parent 150e8fc3
......@@ -19,6 +19,7 @@
#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>
......@@ -41,13 +42,190 @@ const char* KEA_LFC_EXECUTABLE_ENV_NAME = "KEA_LFC_EXECUTABLE";
} // end of anonymous namespace
using namespace isc;
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 {
public:
/// @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;
private:
/// @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) {
}
void
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() :
lease_file6->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.
args.push_back("-x");
args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
Memfile_LeaseMgr::FILE_PREVIOUS));
// Input file.
args.push_back("-i");
args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
Memfile_LeaseMgr::FILE_INPUT));
// Output file.
args.push_back("-o");
args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
Memfile_LeaseMgr::FILE_OUTPUT));
// Finish file.
args.push_back("-f");
args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
Memfile_LeaseMgr::FILE_FINISH));
// PID file.
args.push_back("-p");
args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
Memfile_LeaseMgr::FILE_PID));
// The configuration file is currently unused.
args.push_back("-c");
args.push_back("ignored-path");
// Create the process (do not start it yet).
process_.reset(new util::ProcessSpawn(executable, args));
}
}
long
LFCSetup::getInterval() const {
return (timer_.getInterval());
}
void
LFCSetup::execute() {
try {
LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_EXECUTE)
.arg(process_->getCommandLine());
pid_ = process_->spawn();
std::cout << process_->getCommandLine() << std::endl;
} catch (const ProcessSpawnError& ex) {
LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SPAWN_FAIL);
}
}
bool
LFCSetup::isRunning() const {
return (process_ && process_->isRunning(pid_));
}
int
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()),
lfc_process_() {
: LeaseMgr(parameters),
lfc_setup_(new LFCSetup(boost::bind(&Memfile_LeaseMgr::lfcCallback, this),
*getIOService()))
{
// Check the universe and use v4 file or v6 file.
std::string universe = getParameter("universe");
if (universe == "4") {
......@@ -466,7 +644,7 @@ Memfile_LeaseMgr::appendSuffix(const std::string& file_name,
uint32_t
Memfile_LeaseMgr::getIOServiceExecInterval() const {
return (static_cast<uint32_t>(lfc_timer_.getInterval() / 1000));
return (static_cast<uint32_t>(lfc_setup_->getInterval() / 1000));
}
std::string
......@@ -528,79 +706,67 @@ Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
return (lease_file);
}
void
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.
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");
}
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");
}
storage.clear();
// 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;
// Load the leasefile.completed, if exists.
lease_file.reset(new LeaseFileType(std::string(filename + ".completed")));
if (lease_file->exists()) {
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
MAX_LEASE_ERRORS);
} else {
executable = c_executable;
} 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(appendSuffix(filename, FILE_PREVIOUS)));
if (lease_file->exists()) {
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
MAX_LEASE_ERRORS);
}
// Set the timer to call callback function periodically.
asiolink::IntervalTimer::Callback cb =
boost::bind(&Memfile_LeaseMgr::lfcCallback, this);
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.
lfc_timer_.setup(cb, 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() :
lease_file6_->getFilename();
lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_INPUT)));
if (lease_file->exists()) {
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
MAX_LEASE_ERRORS);
}
}
// 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.
args.push_back("-x");
args.push_back(appendSuffix(lease_file, FILE_PREVIOUS));
// Input file.
args.push_back("-i");
args.push_back(appendSuffix(lease_file, FILE_INPUT));
// Output file.
args.push_back("-o");
args.push_back(appendSuffix(lease_file, FILE_OUTPUT));
// Finish file.
args.push_back("-f");
args.push_back(appendSuffix(lease_file, FILE_FINISH));
// PID file.
args.push_back("-p");
args.push_back(appendSuffix(lease_file, FILE_PID));
// Always load leases from the primary lease file. If the lease file
// doesn't exist it will be created by the LeaseFileLoader. Note
// that the false value passed as the last parameter to load
// function causes the function to leave the file open after
// it is parsed. This file will be used by the backend to record
// future lease updates.
lease_file.reset(new LeaseFileType(filename));
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
MAX_LEASE_ERRORS, false);;
}
// The configuration file is currently unused.
args.push_back("-c");
args.push_back("ignored-path");
// Create the process (do not start it yet).
lfc_process_.reset(new util::ProcessSpawn(executable, args));
}
bool
Memfile_LeaseMgr::isLFCRunning() const {
return (lfc_setup_->isRunning());
}
int
Memfile_LeaseMgr::getLFCExitStatus() const {
return (lfc_setup_->getExitStatus());
}
void
Memfile_LeaseMgr::lfcCallback() {
......@@ -608,16 +774,37 @@ Memfile_LeaseMgr::lfcCallback() {
// Check if we're in the v4 or v6 space and use the appropriate file.
if (lease_file4_) {
leaseFileCleanup(lease_file4_);
lfcExecute(lease_file4_);
} else if (lease_file6_) {
leaseFileCleanup(lease_file6_);
lfcExecute(lease_file6_);
}
}
void
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::leaseFileCleanup(boost::shared_ptr<LeaseFileType>& lease_file) {
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)";
......@@ -665,14 +852,7 @@ void Memfile_LeaseMgr::leaseFileCleanup(boost::shared_ptr<LeaseFileType>& lease_
// Once we have rotated files as needed, start the new kea-lfc process
// to perform a cleanup.
if (do_lfc) {
try {
lfc_process_->spawn();
LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_EXECUTE)
.arg(lfc_process_->getCommandLine());
} catch (const ProcessSpawnError& ex) {
LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SPAWN_FAIL);
}
lfc_setup_->execute();
} else {
LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_ROTATION_FAIL)
......@@ -682,53 +862,6 @@ void Memfile_LeaseMgr::leaseFileCleanup(boost::shared_ptr<LeaseFileType>& lease_
}
}
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");
}
storage.clear();
// Load the leasefile.completed, if exists.
lease_file.reset(new LeaseFileType(std::string(filename + ".completed")));
if (lease_file->exists()) {
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
MAX_LEASE_ERRORS);
} 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(appendSuffix(filename, FILE_PREVIOUS)));
if (lease_file->exists()) {
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
MAX_LEASE_ERRORS);
}
lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_INPUT)));
if (lease_file->exists()) {
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
MAX_LEASE_ERRORS);
}
}
// Always load leases from the primary lease file. If the lease file
// doesn't exist it will be created by the LeaseFileLoader. Note
// that the false value passed as the last parameter to load
// function causes the function to leave the file open after
// it is parsed. This file will be used by the backend to record
// future lease updates.
lease_file.reset(new LeaseFileType(filename));
LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
MAX_LEASE_ERRORS, false);;
}
} // end of namespace isc::dhcp
} // end of namespace isc
......@@ -29,6 +29,8 @@
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
......@@ -105,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.
......@@ -114,18 +116,10 @@ public:
V6
};
/// @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,
FILE_INPUT,
FILE_PREVIOUS,
FILE_OUTPUT,
FILE_FINISH,
FILE_PID
};
/// @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
///
......@@ -335,12 +329,30 @@ 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,
FILE_INPUT,
FILE_PREVIOUS,
FILE_OUTPUT,
FILE_FINISH,
FILE_PID
};
/// @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"
/// - %Lease File Copy or Input File: ".1"
/// - Previous File: ".2"
/// - LFC Output File: ".output"
/// - LFC Finish File: ".completed"
......@@ -353,11 +365,18 @@ public:
/// @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.
///
......@@ -393,35 +412,10 @@ public:
/// server shut down.
bool persistLeases(Universe u) const;
protected:
/// @brief A callback function triggering Lease File Cleanup.
///
/// This method is virtual so as it can be overriden and customized in
/// the unit tests. In particular, the unit test which checks that the
/// callback function has been executed would override this function
/// to increase the execution counter each time it is executed.
///
/// @todo Once the callback is implemented, there is a need to
/// extend the documentation of this method. Currently, it simply
/// logs that it has been called.
virtual void lfcCallback();
//@}
private:
/// @brief Setup the periodic Lease File Cleanup.
///
/// This method checks if the @c lfc-interval configuration parameter
/// is set to a non-zero value and sets up the interval timer to
/// perform the Lease File Cleanup periodically. It also prepares the
/// path and arguments for the @c kea-lfc application which will be
/// executed to perform the cleanup. By default the backend will use
/// the path to the kea-lfc in the Kea installation directory. If
/// the unit tests need to override this path (with the path in the
/// Kea build directory, the @c KEA_LFC_EXECUTABLE environmental
/// variable should be set to hold an absolute path to the kea-lfc
/// excutable.
void lfcSetup();
/// @brief Initialize the location of the lease file.
///
......@@ -440,31 +434,6 @@ private:
/// argument to this function.
std::string initLeaseFilePath(Universe u);
/// @brief Performs a lease file cleanup for DHCPv4 or DHCPv6.
///
/// This method performs all the actions necessary to prepare for the
/// execution of the LFC and if these actions are sucessful, it executes
/// the @c kea-lfc application as a background process to process (cleanup)
/// the lease files.
///
/// For the design and the terminology used in this description refer to
/// the http://kea.isc.org/wiki/LFCDesign.
///
/// If the method finds that the Lease File Copy exists it simply runs
/// the @c kea-lfc application.
///
/// If the Lease File Copy doesn't exist it moves the Current Lease File
/// to Lease File Copy, and then recreates the Current Lease File without
/// any lease entries. If the file has been successfully moved, it runs
/// the @c kea-lfc application.
///
/// @param lease_file A pointer to the object representing the Current
/// Lease File (DHCPv4 or DHCPv6 lease file).
///
/// @tparam LeaseFileType One of @c CSVLeaseFile4 or @c CSVLeaseFile6.
template<typename LeaseFileType>
void leaseFileCleanup(boost::shared_ptr<LeaseFileType>& lease_file);
/// @brief Load leases from the persistent storage.
///
/// This method loads DHCPv4 or DHCPv6 leases from lease files in the
......@@ -492,7 +461,7 @@ private:
///
/// @note: When the server starts up or is reconfigured it will try to
/// read leases from the lease files using this method. It is possible
/// that the Lease File Cleanup is performed upon the lease files to
/// that the %Lease File Cleanup is performed upon the lease files to
/// be read by this method. This may result in conflicts between the
/// server process and the LFC. To prevent it, the method checks if the
/// instance of the @c kea-lfc is running (using the PID file) before it
......@@ -533,19 +502,93 @@ private: