2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 05:27:55 +00:00

[master] Servers now create PID files

Merge branch 'trac3769'
This commit is contained in:
Thomas Markwalder 2015-07-08 09:49:49 -04:00
commit cdce632add
30 changed files with 678 additions and 79 deletions

View File

@ -128,6 +128,35 @@ strings <userinput>path</userinput>/kea-dhcp-ddns | sed -n 's/;;;; //p'
Upon start up the module will load its configuration and begin listening
for NCRs based on that configuration.
</para>
<para>
During startup the server will attempt to create a PID file of the
form: [localstatedir]/[conf name].kea-dhcp-ddns.pid
where:
<itemizedlist>
<listitem>
<simpara><command>localstatedir</command>: The value as passed into the
build configure script. It defaults defaults to "/usr/local/var". Note
that this value may be overridden at run time by setting the environment
variable KEA_PIDFILE_DIR. This is intended primarily for testing purposes.
</simpara>
</listitem>
<listitem>
<simpara><command>conf name</command>: The confguration file name
used to start the server, minus all preceding path and file extension.
For example, given a pathname of "/usr/local/etc/kea/myconf.txt", the
portion used would be "myconf".
</simpara>
</listitem>
</itemizedlist>
If the file already exists and contains the PID of a live process,
the server will issue a DHCP_DDNS_ALREADY_RUNNING log message and exit. It
is possible, though unlikely, that the file is a remnant of a system crash
and the process to which the PID belongs is unrelated to Kea. In such a
case it would be necessary to manually delete the PID file.
</para>
</section> <!-- end start-stop -->
<section id="d2-configuration">
<title>Configuring the DHCP-DDNS Server</title>

View File

@ -106,6 +106,33 @@ strings <userinput>path</userinput>/kea-dhcp4 | sed -n 's/;;;; //p'
access. Make sure you run this daemon as root.
</para>
<para>
During startup the server will attempt to create a PID file of the
form: [localstatedir]/[conf name].kea-dhcp4.pid
where:
<itemizedlist>
<listitem>
<simpara><command>localstatedir</command>: The value as passed into the
build configure script. It defaults defaults to "/usr/local/var". Note
that this value may be overridden at run time by setting the environment
variable KEA_PIDFILE_DIR. This is intended primarily for testing purposes.
</simpara>
</listitem>
<listitem>
<simpara><command>conf name</command>: The confguration file name
used to start the server, minus all preceding path and file extension.
For example, given a pathname of "/usr/local/etc/kea/myconf.txt", the
portion used would be "myconf".
</simpara>
</listitem>
</itemizedlist>
If the file already exists and contains the PID of a live process,
the server will issue a DHCP4_ALREADY_RUNNING log message and exit. It
is possible, though unlikely, that the file is a remnant of a system crash
and the process to which the PID belongs is unrelated to Kea. In such a
case it would be necessary to manually delete the PID file.
</para>
</section>
<section id="dhcp4-configuration">

View File

@ -104,6 +104,32 @@ strings <userinput>path</userinput>/kea-dhcp6 | sed -n 's/;;;; //p'
access. Make sure you run this daemon as root.
</para>
<para>
During startup the server will attempt to create a PID file of the
form: [localstatedir]/[conf name].kea-dhcp6.pid
where:
<itemizedlist>
<listitem>
<simpara><command>localstatedir</command>: The value as passed into the
build configure script. It defaults defaults to "/usr/local/var". Note
that this value may be overridden at run time by setting the environment
variable KEA_PIDFILE_DIR. This is intended primarily for testing purposes.
</simpara>
</listitem>
<listitem>
<simpara><command>conf name</command>: The confguration file name
used to start the server, minus all preceding path and file extension.
For example, given a pathname of "/usr/local/etc/kea/myconf.txt", the
portion used would be "myconf".
</simpara>
</listitem>
</itemizedlist>
If the file already exists and contains the PID of a live process,
the server will issue a DHCP6_ALREADY_RUNNING log message and exit. It
is possible, though unlikely, that the file is a remnant of a system crash
and the process to which the PID belongs is unrelated to Kea. In such a
case it would be necessary to manually delete the PID file.
</para>
</section>
<section id="dhcp6-configuration">

View File

@ -100,6 +100,16 @@ documented in preceding log entries.
This is an informational message issued after DHCP_DDNS has submitted DNS
mapping additions which were received and accepted by an appropriate DNS server.
% DHCP_DDNS_ALREADY_RUNNING %1 already running? %2
This is an error message that occurs when DHCP_DDNS encounters a pre-existing
PID file which contains the PID of a running process. This most likely
indicates an attempt to start a second instance of DHCP_DDNS using the
same configuration file. It is possible, though unlikely, that the PID file
is a remnant left behind by a server crash or power failure and the PID
it contains refers to a process other than DHCP_DDNS. In such an event,
it would be necessary to manually remove the PID file. The first argument is
the DHCP_DDNS process name, the second contains the PID and PID file.
% DHCP_DDNS_AT_MAX_TRANSACTIONS application has %1 queued requests but has reached maximum number of %2 concurrent transactions
This is a debug message that indicates that the application has DHCP_DDNS
requests in the queue but is working as many concurrent requests as allowed.
@ -277,6 +287,15 @@ no configured DDNS domains in the DHCP_DDNS configuration. Either the DHCP_DDNS
configuration needs to be updated or the source of the FQDN itself should be
investigated.
% DHCP_DDNS_PID_FILE_ERROR %1 could not create a PID file: %2
This is an error message that occurs when DHCP_DDNS is unable to create
its PID file. The log message should contain details sufficient to
determine the underlying cause. The most likely culprits are that
some portion of the pathname does not exist or a permissions issue. The
default path is determined by --localstatedir configure paramter but
may be overridden by setting environment variable, KEA_PIDFILE_DIR. The
first argument is the DHCP_DDNS process name.
% DHCP_DDNS_PROCESS_INIT application init invoked
This is a debug message issued when the DHCP-DDNS application enters
its initialization method.

View File

@ -70,6 +70,8 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
throw; // rethrow it
}
setProcName(bin_name_);
// It is important that we set a default logger name because this name
// will be used when the user doesn't provide the logging configuration
// in the Kea configuration file.
@ -87,6 +89,18 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
Daemon::loggerInit(bin_name_.c_str(), verbose_);
}
try {
createPIDFile();
} catch (const dhcp::DaemonPIDExists& ex) {
LOG_FATAL(dctl_logger, DHCP_DDNS_ALREADY_RUNNING)
.arg(bin_name_).arg(ex.what());
isc_throw (LaunchError, "Launch Failed: " << ex.what());
} catch (const std::exception& ex) {
LOG_FATAL(dctl_logger, DHCP_DDNS_PID_FILE_ERROR)
.arg(app_name_).arg(ex.what());
isc_throw (LaunchError, "Launch failed: " << ex.what());
}
// Log the starting of the service. Although this is the controller
// module, use a "DHCP_DDNS_" prefix to the module (to conform to the
// principle of least astonishment).
@ -173,7 +187,7 @@ DControllerBase::parseArgs(int argc, char* argv[])
isc_throw(InvalidUsage, "configuration file name missing");
}
Daemon::init(optarg);
setConfigFile(optarg);
break;
case '?': {

View File

@ -50,6 +50,12 @@ public:
isc::Exception(file, line, what) { };
};
/// @brief Exception thrown when the controller launch fails.
class LaunchError: public isc::Exception {
public:
LaunchError (const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Exception thrown when the application process fails.
class ProcessInitError: public isc::Exception {

View File

@ -13,6 +13,7 @@ check-local:
for shtest in $(SHTESTS) ; do \
echo Running test: $$shtest ; \
export KEA_LOCKFILE_DIR=$(abs_top_builddir); \
export KEA_PIDFILE_DIR=$(abs_top_builddir); \
${SHELL} $(abs_builddir)/$$shtest || exit ; \
done

View File

@ -1,4 +1,4 @@
# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
# Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@ -235,6 +235,7 @@ shutdown_test() {
test_finish 0
}
server_pid_file_test "${CONFIG}" DHCP_DDNS_ALREADY_RUNNING
dynamic_reconfiguration_test
shutdown_test "dhcp-ddns.sigterm_test" 15
shutdown_test "dhcp-ddns.sigint_test" 2

View File

@ -25,6 +25,9 @@ main(int argc, char* argv[]) {
// src/lib/log/README for info on how to tweak logging
isc::log::initLogger();
// Override --localstatedir value for PID files
setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
int result = RUN_ALL_TESTS();
return (result);

View File

@ -229,7 +229,7 @@ TEST_F(DStubControllerTest, missingConfigFileArgument) {
int argc = 2;
// Record start time, and invoke launch().
EXPECT_THROW(launch(argc, argv), ProcessInitError);
EXPECT_THROW(launch(argc, argv), LaunchError);
}
/// @brief Tests launch with an operational error during application execution.

View File

@ -19,6 +19,17 @@ This message is printed when DHCPv4 server enabled an interface to be used
to receive DHCPv4 traffic. IPv4 socket on this interface will be opened once
Interface Manager starts up procedure of opening sockets.
% DHCP4_ALREADY_RUNNING %1 already running? %2
This is an error message that occurs when the DHCPv4 server encounters
a pre-existing PID file which contains the PID of a running process.
This most likely indicates an attempt to start a second instance of
the server using the same configuration file. It is possible, though
unlikely that the PID file is a remnant left behind by a server crash or
power failure and the PID it contains refers to a process other than
the server. In such an event, it would be necessary to manually remove
the PID file. The first argument is the DHCPv4 process name, the
second contains the PID and PID file.
% DHCP4_BUFFER_RECEIVED received buffer from %1:%2 to %3:%4 over interface %5
This debug message is logged when the server has received a packet
over the socket. When the message is logged the contents of the received

View File

@ -168,9 +168,6 @@ namespace dhcp {
void
ControlledDhcpv4Srv::init(const std::string& file_name) {
// Call parent class's init to initialize file name.
Daemon::init(file_name);
// Configure the server using JSON file.
configure(file_name);

View File

@ -118,6 +118,7 @@ main(int argc, char* argv[]) {
usage();
}
// Configuration file is required.
if (config_file.empty()) {
cerr << "Configuration file not specified." << endl;
@ -125,7 +126,6 @@ main(int argc, char* argv[]) {
}
int ret = EXIT_SUCCESS;
try {
// It is important that we set a default logger name because this name
// will be used when the user doesn't provide the logging configuration
@ -145,6 +145,11 @@ main(int argc, char* argv[]) {
// Remember verbose-mode
server.setVerbose(verbose_mode);
// Create our PID file.
server.setProcName(DHCP4_NAME);
server.setConfigFile(config_file);
server.createPIDFile();
try {
// Initialize the server.
server.init(config_file);
@ -173,8 +178,18 @@ main(int argc, char* argv[]) {
LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
} catch (const std::exception& ex) {
} catch (const isc::dhcp::DaemonPIDExists& ex) {
// First, we print the error on stderr (that should always work)
cerr << DHCP4_NAME << " already running? " << ex.what()
<< endl;
// Let's also try to log it using logging system, but we're not
// sure if it's usable (the exception may have been thrown from
// the logger subsystem)
LOG_FATAL(dhcp4_logger, DHCP4_ALREADY_RUNNING)
.arg(DHCP4_NAME).arg(ex.what());
ret = EXIT_FAILURE;
} catch (const std::exception& ex) {
// First, we print the error on stderr (that should always work)
cerr << DHCP4_NAME << ": Fatal error during start up: " << ex.what()
<< endl;

View File

@ -12,6 +12,7 @@ check-local:
for shtest in $(SHTESTS) ; do \
echo Running test: $$shtest ; \
export KEA_LOCKFILE_DIR=$(abs_top_builddir); \
export KEA_PIDFILE_DIR=$(abs_top_builddir); \
${SHELL} $(abs_builddir)/$$shtest || exit ; \
done

View File

@ -272,6 +272,7 @@ shutdown_test() {
test_finish 0
}
server_pid_file_test "${CONFIG}" DHCP4_ALREADY_RUNNING
dynamic_reconfiguration_test
shutdown_test "dhcpv4.sigterm_test" 15
shutdown_test "dhcpv4.sigint_test" 2

View File

@ -25,6 +25,7 @@ main(int argc, char* argv[]) {
// src/lib/log/README for info on how to tweak logging
isc::log::initLogger();
setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
int result = RUN_ALL_TESTS();
return (result);

View File

@ -19,6 +19,17 @@ This message is printed when DHCPv6 server enabled an interface to be used
to receive DHCPv6 traffic. IPv6 socket on this interface will be opened once
Interface Manager starts up procedure of opening sockets.
% DHCP6_ALREADY_RUNNING %1 already running? %2
This is an error message that occurs when the DHCPv6 server encounters
a pre-existing PID file which contains the PID of a running process.
This most likely indicates an attempt to start a second instance of
the server using the same configuration file. It is possible, though
unlikely that the PID file is a remnant left behind by a server crash or
power failure and the PID it contains refers to a process other than
the server. In such an event, it would be necessary to manually remove
the PID file. The first argument is the DHCPv6 process name, the second
contains the PID and PID file.
% DHCP6_ADD_GLOBAL_STATUS_CODE %1: adding Status Code to DHCPv6 packet: %2
This message is logged when the server is adding the top-level
Status Code option. The first argument includes the client and the

View File

@ -174,9 +174,6 @@ namespace dhcp {
void
ControlledDhcpv6Srv::init(const std::string& file_name) {
// Call parent class's init to initialize file name.
Daemon::init(file_name);
// Configure the server using JSON file.
configure(file_name);

View File

@ -148,6 +148,11 @@ main(int argc, char* argv[]) {
// Remember verbose-mode
server.setVerbose(verbose_mode);
// Create our PID file
server.setProcName(DHCP6_NAME);
server.setConfigFile(config_file);
server.createPIDFile();
try {
// Initialize the server, e.g. establish control session
// Read a configuration file
@ -177,6 +182,17 @@ main(int argc, char* argv[]) {
LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
} catch (const isc::dhcp::DaemonPIDExists& ex) {
// First, we print the error on stderr (that should always work)
cerr << DHCP6_NAME << " already running? " << ex.what()
<< endl;
// Let's also try to log it using logging system, but we're not
// sure if it's usable (the exception may have been thrown from
// the logger subsystem)
LOG_FATAL(dhcp6_logger, DHCP6_ALREADY_RUNNING)
.arg(DHCP6_NAME).arg(ex.what());
ret = EXIT_FAILURE;
} catch (const std::exception& ex) {
// First, we print the error on stderr (that should always work)

View File

@ -12,6 +12,7 @@ check-local:
for shtest in $(SHTESTS) ; do \
echo Running test: $$shtest ; \
export KEA_LOCKFILE_DIR=$(abs_top_builddir); \
export KEA_PIDFILE_DIR=$(abs_top_builddir); \
${SHELL} $(abs_builddir)/$$shtest || exit ; \
done

View File

@ -273,6 +273,7 @@ returned %d."
test_finish 0
}
server_pid_file_test "${CONFIG}" DHCP6_ALREADY_RUNNING
dynamic_reconfiguration_test
shutdown_test "dhcpv6.sigterm_test" 15
shutdown_test "dhcpv6.sigint_test" 2

View File

@ -22,6 +22,7 @@ main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
isc::log::initLogger();
setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
int result = RUN_ALL_TESTS();
return result;

View File

@ -16,6 +16,7 @@ check-local:
chmod +x $(abs_builddir)/$$shtest ; \
export KEA_LOCKFILE_DIR=$(abs_top_builddir); \
export KEACTRL_BUILD_DIR=$(abs_top_builddir); \
export KEA_PIDFILE_DIR=$(abs_top_builddir); \
export KEACTRL_CONF=$(abs_top_builddir)/src/bin/keactrl/tests/keactrl_test.conf; \
${SHELL} $(abs_builddir)/$$shtest || exit ; \
done

View File

@ -13,14 +13,18 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <cc/data.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/daemon.h>
#include <exceptions/exceptions.h>
#include <cc/data.h>
#include <boost/bind.hpp>
#include <logging.h>
#include <log/logger_name.h>
#include <log/logger_support.h>
#include <logging.h>
#include <util/filename.h>
#include <boost/bind.hpp>
#include <sstream>
#include <errno.h>
/// @brief provides default implementation for basic daemon operations
@ -30,18 +34,22 @@
namespace isc {
namespace dhcp {
// This is an initial config file location.
std::string Daemon::config_file_ = "";
Daemon::Daemon()
: signal_set_(), signal_handler_() {
: signal_set_(), signal_handler_(), config_file_(""), proc_name_(""),
pid_file_dir_(DHCP_DATA_DIR), pid_file_(), am_file_author_(false) {
// The pid_file_dir can be overridden via environment variable
// This is primarily intended to simplify testing
const char* const env = getenv("KEA_PIDFILE_DIR");
if (env) {
pid_file_dir_ = env;
}
}
Daemon::~Daemon() {
}
void Daemon::init(const std::string& config_file) {
config_file_ = config_file;
if (pid_file_ && am_file_author_) {
pid_file_->deleteFile();
}
}
void Daemon::cleanup() {
@ -96,5 +104,114 @@ std::string Daemon::getVersion(bool /*extended*/) {
isc_throw(isc::NotImplemented, "Daemon::getVersion() called");
}
std::string
Daemon::getConfigFile() const {
return (config_file_);
}
void
Daemon::setConfigFile(const std::string& config_file) {
config_file_ = config_file;
}
std::string
Daemon::getProcName() const {
return (proc_name_);
};
void
Daemon::setProcName(const std::string& proc_name) {
proc_name_ = proc_name;
}
std::string
Daemon::getPIDFileDir() const {
return(pid_file_dir_);
}
void
Daemon::setPIDFileDir(const std::string& pid_file_dir) {
pid_file_dir_ = pid_file_dir;
}
std::string
Daemon::getPIDFileName() const {
if (pid_file_) {
return (pid_file_->getFilename());
}
return ("");
};
void
Daemon::setPIDFileName(const std::string& pid_file_name) {
if (pid_file_) {
isc_throw(isc::InvalidOperation, "Daemon::setConfigFile"
" file name already set to:" << pid_file_->getFilename());
}
if (pid_file_name.empty()) {
isc_throw(isc::BadValue, "Daemon::setPIDFileName"
" file name may not be empty");
}
pid_file_.reset(new util::PIDFile(pid_file_name));
};
std::string
Daemon::makePIDFileName() const {
if (config_file_.empty()) {
isc_throw(isc::InvalidOperation,
"Daemon::makePIDFileName config file name is not set");
}
if (proc_name_.empty()) {
isc_throw(isc::InvalidOperation,
"Daemon::makePIDFileName process name is not set");
}
// Create Filename instance from the config_file_ pathname, so we can
// extract the fname component.
isc::util::Filename file(config_file_);
if (file.name().empty()) {
isc_throw(isc::BadValue, "Daemon::makePIDFileName config file:"
<< config_file_ << " is missing file name");
}
// Make the pathname for the PID file from the runtime directory,
// configuration name and process name.
std::ostringstream stream;
stream << pid_file_dir_ << "/" << file.name()
<< "." << proc_name_ << ".pid";
return(stream.str());
};
void
Daemon::createPIDFile(int pid) {
// If pid_file_ hasn't been instantiated explicitly, then do so
// using the default name.
if (!pid_file_) {
setPIDFileName(makePIDFileName());
}
// If we find a pre-existing file containing a live PID we bail.
int chk_pid = pid_file_->check();
if (chk_pid > 0) {
isc_throw(DaemonPIDExists, "Daemon::createPIDFile: PID: " << chk_pid
<< " exists, PID file: " << getPIDFileName());
}
if (pid == 0) {
// Write the PID of the current process
pid_file_->write();
} else {
// Write the PID we were given
pid_file_->write(pid);
}
am_file_author_ = true;
}
};
};

View File

@ -17,6 +17,7 @@
#include <cc/data.h>
#include <dhcpsrv/srv_config.h>
#include <util/pid_file.h>
#include <util/signal_set.h>
#include <boost/noncopyable.hpp>
#include <string>
@ -24,6 +25,13 @@
namespace isc {
namespace dhcp {
/// @brief Exception thrown when a the PID file points to a live PID
class DaemonPIDExists : public Exception {
public:
DaemonPIDExists(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Base class for all services
///
/// This is the base class that all daemons (DHCPv4, DHCPv6, D2 and possibly
@ -38,16 +46,6 @@ namespace dhcp {
/// Dhcpv6Srv) in tests, without going through the hassles of implemeting stub
/// methods.
///
/// This class comprises a static object holding a location of the configuration
/// file. The object must be static because it is instantiated by the signal
/// handler functions, which are static by their nature. The signal handlers
/// are used to reconfigure a running server and they need access to the
/// configuration file location. They get this access by calling
/// @c Daemon::getConfigFile function.
///
/// By default, the configuration file location is empty and its actual value
/// is assigned to the static object in @c Daemon::init function.
///
/// Classes derived from @c Daemon may install custom signal handlers using
/// @c isc::util::SignalSet class. This base class provides a declaration
/// of the @c SignalSet object that should be initialized in the derived
@ -71,25 +69,6 @@ public:
/// virtual destructor as well.
virtual ~Daemon();
/// @brief Initializes the server.
///
/// Depending on the configuration backend, it establishes msgq session,
/// or reads the configuration file.
///
/// Note: This function may throw to report enountered problems. It may
/// also return false if the initialization was skipped. That may seem
/// redundant, but the idea here is that in some cases the configuration
/// was read, understood and the decision was made to not start. One
/// case where such capability could be needed is when we have a single
/// config file for Kea4 and D2, but the DNS Update is disabled. It is
/// likely that the D2 will be started, it will analyze its config file,
/// decide that it is not needed and will shut down.
///
/// @note this method may throw
///
/// @param config_file Config file name (may be empty if unused).
virtual void init(const std::string& config_file);
/// @brief Performs final deconfiguration.
///
/// Performs configuration backend specific final clean-up. This is called
@ -103,11 +82,6 @@ public:
/// @brief Initiates shutdown procedure for the whole DHCPv6 server.
virtual void shutdown();
/// @brief Returns config file name.
static std::string getConfigFile() {
return (config_file_);
}
/// @brief Initializes logger
///
/// This method initializes logging system. It also sets the default
@ -161,6 +135,60 @@ public:
/// @return text string
static std::string getVersion(bool extended);
/// @brief Returns config file name.
/// @return text string
std::string getConfigFile() const;
/// @brief Sets the configuration file name
///
/// @param config_file pathname of the configuration file
void setConfigFile(const std::string& config_file);
/// @brief returns the process name
/// This value is used as when forming the default PID file name
/// @return text string
std::string getProcName() const;
/// @brief Sets the process name
/// @param proc_name name the process by which the process is recognized
void setProcName(const std::string& proc_name);
/// @brief Returns the directory used when forming default PID file name
/// @return text string
std::string getPIDFileDir() const;
/// @brief Sets the PID file directory
/// @param pid_file_dir path into which the PID file should be written
/// Note the value should not include a trailing slash, '/'
void setPIDFileDir(const std::string& pid_file_dir);
/// @brief Returns the current PID file name
/// @return text string
std::string getPIDFileName() const;
/// @brief Sets PID file name
///
/// If this method is called prior to calling createPIDFile,
/// the value passed in will be treated as the full file name
/// for the PID file. This provides a means to override the
/// default file name with an explicit value.
///
/// @param pid_file_name file name to be used as the PID file
void setPIDFileName(const std::string& pid_file_name);
/// @brief Creates the PID file
///
/// If the PID file name has not been previously set, the method
/// uses manufacturePIDFileName() to set it. If the PID file
/// name refers to an existing file whose contents are a PID whose
/// process is still alive, the method will throw a DaemonPIDExists
/// exception. Otherwise, the file created (or truncated) and
/// the given pid (if not zero) is written to the file.
///
/// @param pid PID to write to the file if not zero, otherwise the
/// PID of the current process is used.
void createPIDFile(int pid = 0);
protected:
/// @brief Invokes handler for the next received signal.
@ -189,11 +217,25 @@ protected:
/// it not initialized, the signals will not be handled.
isc::util::SignalHandler signal_handler_;
/// @brief Manufacture the pid file name
std::string makePIDFileName() const;
private:
/// @brief Config file name or empty if config file not used.
static std::string config_file_;
std::string config_file_;
/// @brief Name of this process, used when creating its pid file
std::string proc_name_;
/// @brief Pointer to the directory where PID file(s) are written
/// It defaults to --localstatedir
std::string pid_file_dir_;
/// @brief Pointer to the PID file for this process
isc::util::PIDFilePtr pid_file_;
/// @brief Flag indicating if this instance created the file
bool am_file_author_;
};
}; // end of isc::dhcp namespace

View File

@ -34,6 +34,8 @@ namespace dhcp {
class DaemonImpl : public Daemon {
public:
static std::string getVersion(bool extended);
using Daemon::makePIDFileName;
};
std::string DaemonImpl::getVersion(bool extended) {
@ -75,6 +77,172 @@ TEST_F(DaemonTest, constructor) {
// Check that the verbose mode is not set by default.
Daemon instance2;
EXPECT_FALSE(instance2.getVerbose());
EXPECT_TRUE(instance2.getConfigFile().empty());
EXPECT_TRUE(instance2.getProcName().empty());
EXPECT_EQ(CfgMgr::instance().getDataDir(),instance2.getPIDFileDir());
EXPECT_TRUE(instance2.getPIDFileName().empty());
}
// Verify config file accessors
TEST_F(DaemonTest, getSetConfigFile) {
Daemon instance;
EXPECT_NO_THROW(instance.setConfigFile("test.txt"));
EXPECT_EQ("test.txt", instance.getConfigFile());
}
// Verify process name accessors
TEST_F(DaemonTest, getSetProcName) {
Daemon instance;
EXPECT_NO_THROW(instance.setProcName("myproc"));
EXPECT_EQ("myproc", instance.getProcName());
}
// Verify PID file directory name accessors
TEST_F(DaemonTest, getSetPIDFileDir) {
Daemon instance;
EXPECT_NO_THROW(instance.setPIDFileDir("/tmp"));
EXPECT_EQ("/tmp", instance.getPIDFileDir());
}
// Verify PID file name accessors.
TEST_F(DaemonTest, setPIDFileName) {
Daemon instance;
// Verify that PID file name may not be set to empty
EXPECT_THROW(instance.setPIDFileName(""), BadValue);
EXPECT_NO_THROW(instance.setPIDFileName("myproc"));
EXPECT_EQ("myproc", instance.getPIDFileName());
// Verify that setPIDFileName cannot be called twice on the same instance.
EXPECT_THROW(instance.setPIDFileName("again"), InvalidOperation);
}
// Test the getVersion() redefinition
TEST_F(DaemonTest, getVersion) {
EXPECT_THROW(Daemon::getVersion(false), NotImplemented);
ASSERT_NO_THROW(DaemonImpl::getVersion(false));
EXPECT_EQ(DaemonImpl::getVersion(false), "BASIC");
ASSERT_NO_THROW(DaemonImpl::getVersion(true));
EXPECT_EQ(DaemonImpl::getVersion(true), "EXTENDED");
}
// Verify makePIDFileName method
TEST_F(DaemonTest, makePIDFileName) {
DaemonImpl instance;
// Verify that config file cannot be blank
instance.setProcName("notblank");
EXPECT_THROW(instance.makePIDFileName(), InvalidOperation);
// Verify that proc name cannot be blank
instance.setProcName("");
instance.setConfigFile("notblank");
EXPECT_THROW(instance.makePIDFileName(), InvalidOperation);
// Verify that config file must contain a file name
instance.setProcName("myproc");
instance.setConfigFile(".txt");
EXPECT_THROW(instance.makePIDFileName(), BadValue);
instance.setConfigFile("/tmp/");
EXPECT_THROW(instance.makePIDFileName(), BadValue);
// Given a valid config file name and proc name we should good to go
instance.setConfigFile("/tmp/test.conf");
std::string name;
EXPECT_NO_THROW(name = instance.makePIDFileName());
// Make sure the name is as we expect
std::ostringstream stream;
stream << CfgMgr::instance().getDataDir() << "/test.myproc.pid";
EXPECT_EQ(stream.str(), name);
// Verify that the default directory can be overridden
instance.setPIDFileDir("/tmp");
EXPECT_NO_THROW(name = instance.makePIDFileName());
EXPECT_EQ("/tmp/test.myproc.pid", name);
}
// Verifies the creation a PID file and that a pre-existing PID file
// which points to a live PID causes a throw.
TEST_F(DaemonTest, createPIDFile) {
DaemonImpl instance;
instance.setConfigFile("test.conf");
instance.setProcName("daemon_test");
instance.setPIDFileDir(TEST_DATA_BUILDDIR);
EXPECT_NO_THROW(instance.createPIDFile());
std::ostringstream stream;
stream << TEST_DATA_BUILDDIR << "/test.daemon_test.pid";
EXPECT_EQ(stream.str(), instance.getPIDFileName());
// If we try again, we should see our own PID file and fail
EXPECT_THROW(instance.createPIDFile(), DaemonPIDExists);
}
// Verifies that a pre-existing PID file which points to a dead PID
// is overwritten.
TEST_F(DaemonTest, createPIDFileOverwrite) {
DaemonImpl instance;
// We're going to use fork to generate a PID we KNOW is dead.
int pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0) {
// This is the child, die right away. Tragic, no?
exit (0);
}
// Back in the parent test, we need to wait for the child to die
int stat;
int ret = waitpid(pid, &stat, 0);
ASSERT_EQ(ret, pid);
// Ok, so we should now have a PID that we know to be dead.
// Let's use it to create a PID file.
instance.setConfigFile("test.conf");
instance.setProcName("daemon_test");
instance.setPIDFileDir(TEST_DATA_BUILDDIR);
EXPECT_NO_THROW(instance.createPIDFile(pid));
// If we try to create the PID file again, this should work.
EXPECT_NO_THROW(instance.createPIDFile());
}
// Verifies that Daemon destruction deletes the PID file
TEST_F(DaemonTest, PIDFileCleanup) {
boost::shared_ptr<DaemonImpl> instance;
instance.reset(new DaemonImpl);
instance->setConfigFile("test.conf");
instance->setProcName("daemon_test");
instance->setPIDFileDir(TEST_DATA_BUILDDIR);
EXPECT_NO_THROW(instance->createPIDFile());
// If we try again, we should see our own PID file
EXPECT_THROW(instance->createPIDFile(), DaemonPIDExists);
// Save the pid file name
std::string pid_file_name = instance->getPIDFileName();
// Now delete the Daemon instance. This should remove the
// PID file.
instance.reset();
struct stat stat_buf;
ASSERT_EQ(-1, stat(pid_file_name.c_str(), &stat_buf));
EXPECT_EQ(errno, ENOENT);
}
// Checks that configureLogger method is behaving properly.
@ -117,18 +285,6 @@ TEST_F(DaemonTest, parsingConsoleOutput) {
EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
}
// Test the getVersion() redefinition
TEST_F(DaemonTest, getVersion) {
EXPECT_THROW(Daemon::getVersion(false), NotImplemented);
ASSERT_NO_THROW(DaemonImpl::getVersion(false));
EXPECT_EQ(DaemonImpl::getVersion(false), "BASIC");
ASSERT_NO_THROW(DaemonImpl::getVersion(true));
EXPECT_EQ(DaemonImpl::getVersion(true), "EXTENDED");
}
// More tests will appear here as we develop Daemon class.

View File

@ -426,6 +426,60 @@ must be a number"
kill -${sig} ${_GET_PIDS}
}
# Verifies that a server is up running by its PID file
# The PID file is constructed from the given config file name and
# binary name. If it exists and the PID it contains refers to a
# live process it sets _SERVER_PID_FILE and _SERVER_PID to the
# corresponding values. Otherwise, it emits an error and exits.
verify_server_pid() {
local bin_name="${1}" # binary name of the server
local cfg_file="${2}" # config file name
# We will construct the PID file name based on the server config
# and binary name
if [ -z ${bin_name} ]; then
test_lib_error "verify_server_pid requires binary name"
clean_exit 1
fi
if [ -z ${cfg_file} ]; then
test_lib_error "verify_server_pid requires config file name"
clean_exit 1
fi
# Only the file name portion of the config file is used, try and
# extract it. NOTE if this "algorithm" changes this code will need
# to be updated.
fname=`basename ${cfg_file}`
fname=`echo $fname | cut -f1 -d'.'`
if [ -z ${fname} ]; then
test_lib_error "verify_server_pid could not extract config name"
clean_exit 1
fi
# Now we can build the name:
pid_file="$KEA_PIDFILE_DIR/$fname.$bin_name.pid"
if [ ! -e ${pid_file} ]; then
printf "ERROR: PID file:[%s] does not exist\n" ${pid_file}
clean_exit 1
fi
# File exists, does its PID point to a live process?
pid=`cat ${pid_file}`
kill -0 ${pid}
if [ $? -ne 0 ]; then
printf "ERROR: PID file:[%s] exists but PID:[%d] does not\n" \
${pid_file} ${pid}
clean_exit 1
fi
# Make the values accessible to the caller
_SERVER_PID="${pid}"
_SERVER_PID_FILE="${pid_file}"
}
# This test verifies that the binary is reporting its version properly.
version_test() {
test_name=${1} # Test name
@ -536,3 +590,49 @@ logger_vars_test() {
test_finish 0
}
# This test verifies server PID file management
# 1. It verifies that upon startup, the server creates a PID file
# 2. It verifies the an attempt to start a second instance fails
# due to pre-existing PID File/PID detection
server_pid_file_test() {
local server_cfg="${1}"
local log_id="${2}"
# Log the start of the test and print test name.
test_start "${bin}.server_pid_file_test"
# Remove dangling DHCP4 instances and remove log files.
cleanup
# Create new configuration file.
create_config "${CONFIG}"
# Instruct server to log to the specific file.
set_logger
# Start server
start_kea ${bin_path}/${bin}
# Wait up to 20s for server to start.
wait_for_kea 20
if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
printf "ERROR: timeout waiting for %s to start.\n" ${bin}
clean_exit 1
fi
# Verify server is still running
verify_server_pid ${bin} ${CFG_FILE}
printf "PID file is [%s], PID is [%d]" ${_SERVER_PID_FILE} ${_SERVER_PID}
# Now try to start a second one
start_kea ${bin_path}/${bin}
wait_for_message 10 "${log_id}" 1
if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
printf "ERROR: Second %s instance started? PID conflict not reported.\n" ${bin}
clean_exit 1
fi
# Verify server is still running
verify_server_pid ${bin} ${CFG_FILE}
# All ok. Shut down the server and exit.
test_finish 0
}

View File

@ -28,7 +28,7 @@ PIDFile::PIDFile(const std::string& filename)
PIDFile::~PIDFile() {
}
bool
int
PIDFile::check() const {
std::ifstream fs(filename_.c_str());
int pid;
@ -51,13 +51,13 @@ PIDFile::check() const {
<< filename_ << "'");
}
// If the process is still running return true
// If the process is still running return its pid.
if (kill(pid, 0) == 0) {
return (true);
return (pid);
}
// No process
return (false);
return (0);
}
void

View File

@ -16,6 +16,7 @@
#define PID_FILE_H
#include <exceptions/exceptions.h>
#include <boost/shared_ptr.hpp>
#include <fstream>
#include <ostream>
#include <string>
@ -62,11 +63,11 @@ public:
/// If the file exists but can't be read or it doesn't have
/// the proper format treat it as the process existing.
///
/// @return true if the PID is in use, false otherwise
/// @return returns the PID if it is in, 0 otherwise.
///
/// @throw throws PIDCantReadPID if it was able to open the file
/// but was unable to read the PID from it.
bool check() const;
int check() const;
/// @brief Write the PID to the file.
///
@ -95,6 +96,9 @@ private:
std::string filename_;
};
/// @brief Defines a shared pointer to a PIDFile
typedef boost::shared_ptr<PIDFile> PIDFilePtr;
} // namespace isc::util
} // namespace isc

View File

@ -127,7 +127,7 @@ TEST_F(PIDFileTest, pidInUse) {
pid_file.write();
// Check if we think the process is running
EXPECT_TRUE(pid_file.check());
EXPECT_EQ(getpid(), pid_file.check());
}
/// @brief Test checking a PID. Write a PID that isn't in use
@ -148,7 +148,7 @@ TEST_F(PIDFileTest, pidNotInUse) {
pid_file.write(pid);
// Check to see if we think the process is running
if (!pid_file.check()) {
if (pid_file.check() == 0) {
return;
}
@ -159,7 +159,7 @@ TEST_F(PIDFileTest, pidNotInUse) {
pid_file.write(pid);
// Check to see if we think the process is running
EXPECT_FALSE(pid_file.check());
EXPECT_EQ(0, pid_file.check());
}
/// @brief Test checking a PID. Write garbage to the PID file