From: Thomas Markwalder Date: Wed, 1 Jul 2015 20:20:40 +0000 (-0400) Subject: [3769] Added support for creating PIDFiles to dhcpsrv::Daemon X-Git-Tag: trac3785_base~2^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e897f24ac63aa47888161c2336aea03d930fad6c;p=thirdparty%2Fkea.git [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 PIDFilePtr; --- diff --git a/src/lib/dhcpsrv/daemon.cc b/src/lib/dhcpsrv/daemon.cc index 2733b926cd..9d89bbc098 100644 --- a/src/lib/dhcpsrv/daemon.cc +++ b/src/lib/dhcpsrv/daemon.cc @@ -13,14 +13,18 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include #include #include #include -#include -#include -#include #include #include +#include +#include + +#include + +#include #include /// @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); + } +} + }; }; diff --git a/src/lib/dhcpsrv/daemon.h b/src/lib/dhcpsrv/daemon.h index 7766472053..bd20e14a05 100644 --- a/src/lib/dhcpsrv/daemon.h +++ b/src/lib/dhcpsrv/daemon.h @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -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 diff --git a/src/lib/dhcpsrv/tests/daemon_unittest.cc b/src/lib/dhcpsrv/tests/daemon_unittest.cc index 89fb9354cb..f28819a371 100644 --- a/src/lib/dhcpsrv/tests/daemon_unittest.cc +++ b/src/lib/dhcpsrv/tests/daemon_unittest.cc @@ -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 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. diff --git a/src/lib/util/pid_file.h b/src/lib/util/pid_file.h index 8910c4355e..58bc93dd1b 100644 --- a/src/lib/util/pid_file.h +++ b/src/lib/util/pid_file.h @@ -16,6 +16,7 @@ #define PID_FILE_H #include +#include #include #include #include @@ -95,6 +96,9 @@ private: std::string filename_; }; +/// @brief Defines a shared pointer to a PIDFile +typedef boost::shared_ptr PIDFilePtr; + } // namespace isc::util } // namespace isc