% DHCP_DDNS_UPDATE_RESPONSE_RECEIVED Request ID %1: to server: %2 status: %3
This is a debug message issued when DHCP_DDNS receives sends a DNS update
response from a 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, the 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.
+
+% 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.
throw; // rethrow it
}
+ Daemon::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.
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).
isc_throw(InvalidUsage, "configuration file name missing");
}
- Daemon::init(optarg);
+ Daemon::setConfigFile(optarg);
break;
case '?': {
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 {
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
-# 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
test_finish 0
}
+# This test verifies if only one D2 per config can be started.
+dupcliate_server_start_test() {
+ # Log the start of the test and print test name.
+ test_start "dhcp_ddns.duplicate_server_start_test"
+ # Remove dangling D2 instances and remove log files.
+ cleanup
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct D2 to log to the specific file.
+ set_logger
+ # Start D2.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for D2 to start.
+ wait_for_kea 20
+ if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
+ printf "ERROR: timeout waiting for D2 to start.\n"
+ 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 "DHCP_DDNS_ALREADY_RUNNING" 1
+ if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+ printf "ERROR: Second D2 instance started? PID conflict not reported.\n"
+ clean_exit 1
+ fi
+
+ # Verify server is still running
+ verify_server_pid ${bin} ${CFG_FILE}
+
+ # All ok. Shut down D2 and exit.
+ test_finish 0
+}
+
+
dynamic_reconfiguration_test
shutdown_test "dhcp-ddns.sigterm_test" 15
shutdown_test "dhcp-ddns.sigint_test" 2
version_test "dhcp-ddns.version"
logger_vars_test "dhcp-ddns.variables"
+dupcliate_server_start_test
// 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);
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
Daemon::Daemon()
: signal_set_(), signal_handler_(), proc_name_(""),
- pid_file_dir_(DHCP_DATA_DIR), pid_file_() {
+ 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() {
- if (pid_file_) {
+ if (pid_file_ && am_file_author_) {
pid_file_->deleteFile();
}
}
}
// If we find a pre-existing file containing a live PID we bail.
- if (pid_file_->check()) {
- isc_throw(DaemonPIDExists, "Daemon::createPIDFile " << proc_name_
- << " already running?, PID file: " << getPIDFileName());
+ 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 we were given
pid_file_->write(pid);
}
+
+ am_file_author_ = true;
}
};
/// @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
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 portio 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
PIDFile::~PIDFile() {
}
-bool
+int
PIDFile::check() const {
std::ifstream fs(filename_.c_str());
int pid;
// If the process is still running return true
if (kill(pid, 0) == 0) {
- return (true);
+ return (pid);
}
// No process
- return (false);
+ return (0);
}
void
/// 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.
///
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
pid_file.write(pid);
// Check to see if we think the process is running
- if (!pid_file.check()) {
+ if (pid_file.check() == 0) {
return;
}
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