Browse Source

Merge branch 'trac3687' Add PIDFile class

Conflicts:
	ChangeLog

Add PIDFile class to write, delete and check PID files.

Use the PIDFile class in the LFC process to ensure that
only one LFC is running at a time.
Shawn Routhier 10 years ago
parent
commit
1e92382aaa

+ 5 - 0
ChangeLog

@@ -1,3 +1,8 @@
+882.	[func]		sar
+	A utility class has been added which handles writing and
+	deleting pid files as well as checking if the process with
+	the given pid is running.
+
 881.	[func]		kalmus
 	Extracting hardware/MAC address from the DHCPv6 remote-id
 	option is now implemented.

+ 15 - 3
src/bin/lfc/kea-lfc.xml

@@ -46,7 +46,8 @@
       <command>kea-lfc</command>
       <arg><option>-4|-6</option></arg>
       <arg><option>-c <replaceable class="parameter">config-file</replaceable></option></arg>
-      <arg><option>-p <replaceable class="parameter">previous-file</replaceable></option></arg>
+      <arg><option>-p <replaceable class="parameter">pid-file</replaceable></option></arg>
+      <arg><option>-x <replaceable class="parameter">previous-file</replaceable></option></arg>
       <arg><option>-i <replaceable class="parameter">copy-file</replaceable></option></arg>
       <arg><option>-o <replaceable class="parameter">output-file</replaceable></option></arg>
       <arg><option>-f <replaceable class="parameter">finish-file</replaceable></option></arg>
@@ -117,7 +118,7 @@
           Configuration file including the configuration for
           <command>kea-lfc</command> process.  It may also
           contain configuration entries for other Kea services.
-          Currently <command>kea-lfc</command> gets all of its arguments from 
+          Currently <command>kea-lfc</command> gets all of its arguments from
           the comamnd line, in the future it will be extended to get some arguments
           from the config file.
         </para></listitem>
@@ -126,7 +127,18 @@
       <varlistentry>
         <term><option>-p</option></term>
         <listitem><para>
-          Previous lease file - When <command>kea-lfc</command> starts this
+          PID file - When the <command>kea-lfc</command> process starts
+          it attempts to determine if another instance of the process is
+          already running by examining the pid file.  If one is running
+          it aborts the new process.  If one isn't running it writes its
+          pid into the pid file.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-x</option></term>
+        <listitem><para>
+          Previous or ex lease file - When <command>kea-lfc</command> starts this
           is the result of any previous run of <command>kea-lfc</command>.
           When <command>kea-lfc</command> finishes it is the result of this run.
           If <command>kea-lfc</command> is interrupted before compelting

+ 71 - 19
src/bin/lfc/lfc_controller.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <lfc/lfc_controller.h>
+#include <util/pid_file.h>
 #include <exceptions/exceptions.h>
 #include <config.h>
 #include <iostream>
@@ -21,6 +22,7 @@
 #include <stdlib.h>
 
 using namespace std;
+using namespace isc::util;
 
 namespace isc {
 namespace lfc {
@@ -42,12 +44,49 @@ LFCController::~LFCController() {
 
 void
 LFCController::launch(int argc, char* argv[]) {
-  try {
-    parseArgs(argc, argv);
-  } catch (const InvalidUsage& ex) {
-    usage(ex.what());
-    throw;  // rethrow it
-  }
+    try {
+        parseArgs(argc, argv);
+    } catch (const InvalidUsage& ex) {
+        usage(ex.what());
+        throw;  // rethrow it
+    }
+
+    std::cerr << "Starting lease file cleanup" << std::endl;
+
+    // verify we are the only instance
+    PIDFile pid_file(pid_file_);
+
+    try {
+        if (pid_file.check() == true) {
+            // Already running instance, bail out
+            std::cerr << "LFC instance already running" <<  std::endl;
+            return;
+        }
+    } catch (const PIDFileError& pid_ex) {
+        std::cerr << pid_ex.what() << std::endl;
+        return;
+    }
+
+    // create the pid file for this instance
+    try {
+        pid_file.write();
+    } catch (const PIDFileError& pid_ex) {
+        std::cerr << pid_ex.what() << std::endl;
+        return;
+    }
+
+    // do other work (TBD)
+    std::cerr << "Add code to perform lease cleanup" << std::endl;
+
+    // delete the pid file for this instance
+    try {
+        pid_file.deleteFile();
+    } catch (const PIDFileError& pid_ex) {
+        std::cerr << pid_ex.what() << std::endl;
+        return;
+    }
+
+    std::cerr << "LFC complete" << std::endl;
 }
 
 void
@@ -56,7 +95,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
 
     opterr = 0;
     optind = 1;
-    while ((ch = getopt(argc, argv, ":46dvVp:i:o:c:f:")) != -1) {
+    while ((ch = getopt(argc, argv, ":46dvVp:x:i:o:c:f:")) != -1) {
         switch (ch) {
         case '4':
             // Process DHCPv4 lease files.
@@ -84,9 +123,17 @@ LFCController::parseArgs(int argc, char* argv[]) {
             break;
 
         case 'p':
-            // Previous file name.
+            // PID file name.
             if (optarg == NULL) {
-                isc_throw(InvalidUsage, "Previous file name missing");
+                isc_throw(InvalidUsage, "PID file name missing");
+            }
+            pid_file_ = optarg;
+            break;
+
+        case 'x':
+            // Previous (or ex) file name.
+            if (optarg == NULL) {
+                isc_throw(InvalidUsage, "Previous (ex) file name missing");
             }
             previous_file_ = optarg;
             break;
@@ -108,7 +155,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
             break;
 
         case 'f':
-            // Output file name.
+            // Finish file name.
             if (optarg == NULL) {
                 isc_throw(InvalidUsage, "Finish file name missing");
             }
@@ -116,7 +163,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
             break;
 
         case 'c':
-            // Previous file name.
+            // Configuration file name
             if (optarg == NULL) {
                 isc_throw(InvalidUsage, "Configuration file name missing");
             }
@@ -151,6 +198,10 @@ LFCController::parseArgs(int argc, char* argv[]) {
         isc_throw(InvalidUsage, "DHCP version required");
     }
 
+    if (pid_file_.empty()) {
+        isc_throw(InvalidUsage, "PID file not specified");
+    }
+
     if (previous_file_.empty()) {
         isc_throw(InvalidUsage, "Previous file not specified");
     }
@@ -174,12 +225,12 @@ LFCController::parseArgs(int argc, char* argv[]) {
     // If verbose is set echo the input information
     if (verbose_ == true) {
       std::cerr << "Protocol version:    DHCPv" << protocol_version_ << std::endl
-                << "Previous lease file: " << previous_file_ << std::endl
-                << "Copy lease file:     " << copy_file_ << std::endl
-                << "Output lease file:   " << output_file_ << std::endl
-                << "Finishn file:        " << finish_file_ << std::endl
-                << "Config file:         " << config_file_ << std::endl
-                << "PID file:            " << pid_file_ << std::endl;
+                << "Previous or ex lease file: " << previous_file_ << std::endl
+                << "Copy lease file:           " << copy_file_ << std::endl
+                << "Output lease file:         " << output_file_ << std::endl
+                << "Finishn file:              " << finish_file_ << std::endl
+                << "Config file:               " << config_file_ << std::endl
+                << "PID file:                  " << pid_file_ << std::endl;
     }
 }
 
@@ -190,9 +241,10 @@ LFCController::usage(const std::string& text) {
     }
 
     std::cerr << "Usage: " << lfc_bin_name_ << std::endl
-              << " [-4|-6] -p file -i file -o file -f file -c file" << std::endl
+              << " [-4|-6] -p file -x file -i file -o file -f file -c file" << std::endl
               << "   -4 or -6 clean a set of v4 or v6 lease files" << std::endl
-              << "   -p <file>: previous lease file" << std::endl
+              << "   -p <file>: PID file" << std::endl
+              << "   -x <file>: previous or ex lease file" << std::endl
               << "   -i <file>: copy of lease file" << std::endl
               << "   -o <file>: output lease file" << std::endl
               << "   -f <file>: finish file" << std::endl

+ 4 - 4
src/bin/lfc/lfc_controller.h

@@ -59,10 +59,10 @@ public:
     /// of the process.  Provides the control logic:
     ///
     /// -# parse command line arguments
-    /// -# verifies that it is the only instance
-    /// -# creates pid file (TBD)
+    /// -# verify that it is the only instance
+    /// -# create pid file
     /// -# .... TBD
-    /// -# remove pid file (TBD)
+    /// -# remove pid file
     /// -# exit to the caller
     ///
     /// @param argc Number of strings in the @c argv array.
@@ -111,7 +111,7 @@ public:
         return (config_file_);
     }
 
-    /// @brief Gets the prevous file name
+    /// @brief Gets the previous file name
     ///
     /// @return Returns the path to the previous file
     std::string getPreviousFile() const {

+ 15 - 8
src/bin/lfc/tests/lfc_controller_unittests.cc

@@ -45,7 +45,7 @@ TEST(LFCControllerTest, fullCommandLine) {
     // Verify that standard options can be parsed without error
     char* argv[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-4"),
-                     const_cast<char*>("-p"),
+                     const_cast<char*>("-x"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("copy"),
@@ -54,8 +54,10 @@ TEST(LFCControllerTest, fullCommandLine) {
                      const_cast<char*>("-c"),
                      const_cast<char*>("config"),
                      const_cast<char*>("-f"),
-                     const_cast<char*>("finish") };
-    int argc = 12;
+                     const_cast<char*>("finish"),
+                     const_cast<char*>("-p"),
+                     const_cast<char*>("pid") };
+    int argc = 14;
 
     ASSERT_NO_THROW(lfc_controller.parseArgs(argc, argv));
 
@@ -66,6 +68,7 @@ TEST(LFCControllerTest, fullCommandLine) {
     EXPECT_EQ(lfc_controller.getCopyFile(), "copy");
     EXPECT_EQ(lfc_controller.getOutputFile(), "output");
     EXPECT_EQ(lfc_controller.getFinishFile(), "finish");
+    EXPECT_EQ(lfc_controller.getPidFile(), "pid");
 }
 
 /// @brief Verify that parsing a correct but incomplete line fails.
@@ -80,7 +83,7 @@ TEST(LFCControllerTest, notEnoughData) {
     // to the parse routine via the argc variable.
     char* argv[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-4"),
-                     const_cast<char*>("-p"),
+                     const_cast<char*>("-x"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("copy"),
@@ -89,11 +92,13 @@ TEST(LFCControllerTest, notEnoughData) {
                      const_cast<char*>("-c"),
                      const_cast<char*>("config"),
                      const_cast<char*>("-f"),
-                     const_cast<char*>("finish") };
+                     const_cast<char*>("finish"),
+                     const_cast<char*>("-p"),
+                     const_cast<char*>("pid") };
 
     int argc = 1;
 
-    for (; argc < 12; ++argc) {
+    for (; argc < 14; ++argc) {
         EXPECT_THROW(lfc_controller.parseArgs(argc, argv), InvalidUsage)
             << "test failed for argc = " << argc;
     }
@@ -114,7 +119,7 @@ TEST(LFCControllerTest, tooMuchData) {
 
     char* argv[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-4"),
-                     const_cast<char*>("-p"),
+                     const_cast<char*>("-x"),
                      const_cast<char*>("previous"),
                      const_cast<char*>("-i"),
                      const_cast<char*>("copy"),
@@ -124,11 +129,13 @@ TEST(LFCControllerTest, tooMuchData) {
                      const_cast<char*>("config"),
                      const_cast<char*>("-f"),
                      const_cast<char*>("finish"),
+                     const_cast<char*>("-p"),
+                     const_cast<char*>("pid"),
                      const_cast<char*>("some"),
                      const_cast<char*>("other"),
                      const_cast<char*>("args"),
     };
-    int argc = 15;
+    int argc = 17;
 
     // We expect an error as we have arguments that aren't valid
     EXPECT_THROW(lfc_controller.parseArgs(argc, argv), InvalidUsage);

+ 1 - 0
src/lib/util/Makefile.am

@@ -18,6 +18,7 @@ libkea_util_la_SOURCES += time_utilities.h time_utilities.cc
 libkea_util_la_SOURCES += memory_segment.h
 libkea_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
 libkea_util_la_SOURCES += optional_value.h
+libkea_util_la_SOURCES += pid_file.h pid_file.cc
 libkea_util_la_SOURCES += range_utilities.h
 libkea_util_la_SOURCES += signal_set.cc signal_set.h
 libkea_util_la_SOURCES += encode/base16_from_binary.h

+ 97 - 0
src/lib/util/pid_file.cc

@@ -0,0 +1,97 @@
+// Copyright (C) 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/pid_file.h>
+#include <cstdio>
+#include <signal.h>
+#include <unistd.h>
+
+namespace isc {
+namespace util {
+
+PIDFile::PIDFile(const std::string& filename)
+    : filename_(filename) {
+}
+
+PIDFile::~PIDFile() {
+}
+
+bool
+PIDFile::check() const {
+    std::ifstream fs(filename_.c_str());
+    int pid;
+    bool good;
+
+    // If we weren't able to open the file treat
+    // it as if the process wasn't running
+    if (!fs.is_open()) {
+        return (false);
+    }
+
+    // Try to get the pid, get the status and get rid of the file
+    fs >> pid;
+    good = fs.good();
+    fs.close();
+
+    // If we weren't able to read a pid send back an execption
+    if (!good) {
+        isc_throw(PIDCantReadPID, "Unable to read PID from file '"
+                  << filename_ << "'");
+    }
+
+    // If the process is still running return true
+    if (kill(pid, 0) == 0) {
+        return (true);
+    }
+
+    // No process
+    return (false);
+}
+
+void
+PIDFile::write() const {
+    write(getpid());
+}
+
+void
+PIDFile::write(int pid) const {
+  std::ofstream fs(filename_.c_str(), std::ofstream::trunc);
+
+    if (!fs.is_open()) {
+        isc_throw(PIDFileError, "Unable to open PID file '"
+                  << filename_ << "' for write");
+    }
+
+    // File is open, write the pid.
+    fs << pid << std::endl;
+
+    bool good = fs.good();
+    fs.close();
+
+    if (!good) {
+        isc_throw(PIDFileError, "Unable to write to PID file '"
+                  << filename_ << "'");
+    }
+}
+
+void
+PIDFile::deleteFile() const {
+    if (remove(filename_.c_str()) != 0) {
+        isc_throw(PIDFileError, "Unable to delete PID file '"
+                  << filename_ << "'");
+    }
+}
+
+} // namespace isc::util
+} // namespace isc

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

@@ -0,0 +1,101 @@
+// Copyright (C) 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PID_FILE_H
+#define PID_FILE_H
+
+#include <exceptions/exceptions.h>
+#include <fstream>
+#include <ostream>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during PID file processing.
+class PIDFileError : public Exception {
+public:
+    PIDFileError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when an error occurs trying to read a PID
+/// from an opened file.
+class PIDCantReadPID : public Exception {
+public:
+    PIDCantReadPID(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Class to help with processing PID files
+///
+/// This is a utility class to manipulate PID file. It provides
+/// functions for writing and deleting a file containing a PID as
+/// well as for extracting a PID from a file and checking if the
+/// process is still running.
+class PIDFile {
+public:
+    /// @brief Constructor
+    ///
+    /// @param filename PID filename.
+    PIDFile(const std::string& filename);
+
+    /// @brief Destructor
+    ~PIDFile();
+
+    /// @brief Read the PID in from the file and check it.
+    ///
+    /// Read the PID in from the file then use it to see if
+    /// a process with that PID exists. If the file doesn't
+    /// exist treat it as the process not being there.
+    /// 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
+    ///
+    /// @throw throws PIDCantReadPID if it was able to open the file
+    /// but was unable to read the PID from it.
+    bool check() const;
+
+    /// @brief Write the PID to the file.
+    ///
+    /// @param pid the pid to write to the file.
+    ///
+    /// @throw throws PIDFileError if it can't open or write to the PID file.
+    void write(int) const;
+
+    /// @brief Get PID of the current process and write it to the file.
+    ///
+    /// @throw throws PIDFileError if it can't open or write to the PID file.
+    void write() const;
+
+    /// @brief Delete the PID file.
+    ///
+    /// @throw throws PIDFileError if it can't delete the PID file
+    void deleteFile() const;
+
+    /// @brief Returns the path to the PID file.
+    std::string getFilename() const {
+        return (filename_);
+    }
+
+private:
+    /// @brief PID filename
+    std::string filename_;
+};
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // PID_FILE_H

+ 1 - 0
src/lib/util/tests/Makefile.am

@@ -38,6 +38,7 @@ run_unittests_SOURCES += memory_segment_local_unittest.cc
 run_unittests_SOURCES += memory_segment_common_unittest.h
 run_unittests_SOURCES += memory_segment_common_unittest.cc
 run_unittests_SOURCES += optional_value_unittest.cc
+run_unittests_SOURCES += pid_file_unittest.cc
 run_unittests_SOURCES += qid_gen_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 run_unittests_SOURCES += socketsession_unittest.cc

+ 195 - 0
src/lib/util/tests/pid_file_unittest.cc

@@ -0,0 +1,195 @@
+// Copyright (C) 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/pid_file.h>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <signal.h>
+#include <stdint.h>
+
+namespace {
+using namespace isc::util;
+
+// Filenames used for testing.
+const char* TESTNAME = "pid_file.test";
+
+class PIDFileTest : public ::testing::Test {
+public:
+    /// @brief Prepends the absolute path to the file specified
+    /// as an argument.
+    ///
+    /// @param filename Name of the file.
+    /// @return Absolute path to the test file.
+    static std::string absolutePath(const std::string& filename);
+
+    /// @brief Generate a random number for use as a PID
+    ///
+    /// @param start - the start of the range we want the PID in
+    /// @param range - the size of the range for our PID
+    ///
+    /// @return returns a random value between start and start + range
+    int randomizePID(const uint32_t start, const uint32_t range) {
+        int pid;
+
+        for (pid = (random() % range) + start;
+             kill(pid, 0) == 0;
+             ++pid)
+            ;
+
+        return (pid);
+    }
+
+protected:
+    /// @brief Removes any old test files before the test
+    virtual void SetUp() {
+        removeTestFile();
+    }
+
+    /// @brief Removes any remaining test files after the test
+    virtual void TearDown() {
+        removeTestFile();
+    }
+
+private:
+    /// @brief Removes any remaining test files
+    void removeTestFile() const {
+        remove(TESTNAME);
+    }
+
+};
+
+std::string
+PIDFileTest::absolutePath(const std::string& filename) {
+    std::ostringstream s;
+    s << TEST_DATA_BUILDDIR << "/" << filename;
+
+    return (s.str());
+}
+
+/// @brief Test file writing and deletion. Start by removing
+/// any leftover file. Then write a known PID to the file and
+/// attempt to read the file and verify the PID. Next write
+/// a second and verify a second PID to verify that an existing
+/// file is properly overwritten.
+
+TEST_F(PIDFileTest, writeAndDelete) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+    std::ifstream fs;
+    int pid(0);
+
+    // Write a known process id
+    pid_file.write(10);
+
+    // Read the file and compare the pid
+    fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+    fs >> pid;
+    EXPECT_TRUE(fs.good());
+    EXPECT_EQ(pid, 10);
+    fs.close();
+
+    // Write a second known process id
+    pid_file.write(20);
+
+    // And comapre the second pid
+    fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+    fs >> pid;
+    EXPECT_TRUE(fs.good());
+    EXPECT_EQ(pid, 20);
+    fs.close();
+
+    // Delete the file
+    pid_file.deleteFile();
+
+    // And verify that it's gone
+    fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+    EXPECT_FALSE(fs.good());
+    fs.close();
+}
+
+/// @brief Test checking a PID. Write the PID of the current
+/// process to the PID file then verify that check indicates
+/// the process is running.
+TEST_F(PIDFileTest, pidInUse) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+
+    // Write the current PID
+    pid_file.write();
+
+    // Check if we think the process is running
+    EXPECT_TRUE(pid_file.check());
+}
+
+/// @brief Test checking a PID. Write a PID that isn't in use
+/// to the PID file and verify that check indicates the process
+/// isn't running. The PID may get used between when we select it
+/// and write the file and when we check it. To minimize false
+/// errors if the first call to check fails we try again with a
+/// different range of values and only if both attempts fail do
+/// we declare the test to have failed.
+TEST_F(PIDFileTest, pidNotInUse) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+    int pid;
+
+    // get a pid betwen 10000 and 20000
+    pid = randomizePID(10000, 10000);
+
+    // write it
+    pid_file.write(pid);
+
+    // Check to see if we think the process is running
+    if (!pid_file.check()) {
+        return;
+    }
+
+    // get a pid betwen 40000 and 50000
+    pid = randomizePID(10000, 40000);
+
+    // write it
+    pid_file.write(pid);
+
+    // Check to see if we think the process is running
+    EXPECT_FALSE(pid_file.check());
+}
+
+/// @brief Test checking a PID.  Write garbage to the PID file
+/// and verify that check throws an error. In this situation
+/// the caller should probably log an error and may decide to
+/// continue or not depending on the requirements.
+TEST_F(PIDFileTest, pidGarbage) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+    std::ofstream fs;
+
+    // Open the file and write garbage to it
+    fs.open(absolutePath(TESTNAME).c_str(), std::ofstream::out);
+    fs << "text" << std::endl;
+    fs.close();
+
+    // Run the check, we expect to get an execption
+    EXPECT_THROW(pid_file.check(), PIDCantReadPID);
+}
+
+/// @brief Test failing to write a file.
+TEST_F(PIDFileTest, pidWriteFail) {
+    PIDFile pid_file(absolutePath(TESTNAME));
+
+    // Create the test file and change it's permission bits
+    // so we can't write to it.
+    pid_file.write(10);
+    chmod(absolutePath(TESTNAME).c_str(), S_IRUSR);
+
+    // Now try a write to the file, expecting an exception
+    EXPECT_THROW(pid_file.write(10), PIDFileError);
+}
+
+} // end of anonymous namespace