Commit e897f24a authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[3769] Added support for creating PIDFiles to dhcpsrv::Daemon

src/lib/dhcpsrv/daemon.h/cc
    New methods:
    static void setConfigFile(const std::string& config_file);
    std::string getProcName() const;
    setProcName(const std::string& proc_name);
    std::string getPIDFileDir() const;
    void setPIDFileDir(const std::string& pid_file_dir);
    std::string getPIDFileName() const;
    void setPIDFileName(const std::string& pid_file_name);
    void createPIDFile(int pid = 0);
    std::string makePIDFileName() const;

    New members:
    std::string proc_name_;
    std::string pid_file_dir_;
    isc::util::PIDFilePtr pid_file_;

src/lib/dhcpsrv/tests/daemon_unittest.cc
    New tests:
    TEST_F(DaemonTest, getSetConfigFile)
    TEST_F(DaemonTest, getSetProcName)
    TEST_F(DaemonTest, getSetPIDFileDir)
    TEST_F(DaemonTest, setPIDFileName)
    TEST_F(DaemonTest, makePIDFileName)
    TEST_F(DaemonTest, createPIDFile)
    TEST_F(DaemonTest, createPIDFileOverwrite)
    TEST_F(DaemonTest, PIDFileCleanup)

src/lib/util/pid_file.h
    Added typedef boost::shared_ptr<PIDFile> PIDFilePtr;
parent 2baf637d
......@@ -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
......@@ -34,10 +38,14 @@ namespace dhcp {
std::string Daemon::config_file_ = "";
Daemon::Daemon()
: signal_set_(), signal_handler_() {
: signal_set_(), signal_handler_(), proc_name_(""),
pid_file_dir_(DHCP_DATA_DIR), pid_file_() {
}
Daemon::~Daemon() {
if (pid_file_) {
pid_file_->deleteFile();
}
}
void Daemon::init(const std::string& config_file) {
......@@ -96,5 +104,106 @@ std::string Daemon::getVersion(bool /*extended*/) {
isc_throw(isc::NotImplemented, "Daemon::getVersion() called");
}
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.
if (pid_file_->check()) {
isc_throw(DaemonPIDExists, "Daemon::createPIDFile " << proc_name_
<< " already running?, 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);
}
}
};
};
......@@ -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
......@@ -161,6 +169,56 @@ public:
/// @return text string
static std::string getVersion(bool extended);
/// @brief Sets the configuration file name
///
/// @param config_file pathname of the configuration file
static 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 +247,22 @@ protected:
/// it not initialized, the signals will not be handled.
isc::util::SignalHandler signal_handler_;
private:
/// @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_;
/// @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_;
};
}; // end of isc::dhcp namespace
......
......@@ -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) {
......@@ -63,6 +65,8 @@ public:
/// the default after each test completes.
~DaemonTest() {
isc::log::setDefaultLoggingOutput();
// Since it's static we need to clear it between tests
Daemon::setConfigFile("");
}
};
......@@ -75,6 +79,172 @@ TEST_F(DaemonTest, constructor) {
// Check that the verbose mode is not set by default.
Daemon instance2;
EXPECT_FALSE(instance2.getVerbose());
EXPECT_EQ("",instance2.getConfigFile());
EXPECT_EQ("",instance2.getProcName());
EXPECT_EQ(CfgMgr::instance().getDataDir(),instance2.getPIDFileDir());
EXPECT_EQ("",instance2.getPIDFileName());
}
// 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 +287,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.
......
......@@ -16,6 +16,7 @@
#define PID_FILE_H
#include <exceptions/exceptions.h>
#include <boost/shared_ptr.hpp>
#include <fstream>
#include <ostream>
#include <string>
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment