Commit 1e92382a authored by Shawn Routhier's avatar Shawn Routhier
Browse files

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.
parents d4c29082 d93715fd
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.
......
......@@ -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
......
......@@ -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
......
......@@ -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 {
......
......@@ -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);
......
......@@ -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
......
// 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
// 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
......@@ -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
......
// 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