Browse Source

[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;
Thomas Markwalder 10 years ago
parent
commit
e897f24ac6
4 changed files with 357 additions and 17 deletions
  1. 113 4
      src/lib/dhcpsrv/daemon.cc
  2. 70 1
      src/lib/dhcpsrv/daemon.h
  3. 170 12
      src/lib/dhcpsrv/tests/daemon_unittest.cc
  4. 4 0
      src/lib/util/pid_file.h

+ 113 - 4
src/lib/dhcpsrv/daemon.cc

@@ -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);
+    }
+}
+
 };
 };

+ 70 - 1
src/lib/dhcpsrv/daemon.h

@@ -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

+ 170 - 12
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<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.

+ 4 - 0
src/lib/util/pid_file.h

@@ -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