Commit 7b894faf authored by Shawn Routhier's avatar Shawn Routhier

[trac3665] Updates per review comments

One of the large items was to rearrange the
lfc_controller test code to:
- not use .c_str()
- check the non-existance of the temporary files after processing
- remove any files after each subtest
- Use a_1 instead of A_1 for variable names

There were also some other coding standards issues as well
as additional comments and such.
parent ca432aa5
xxx. [func] sar
A new process, lfc, has been added. When invoked this
process will load leases from a previous lease file and
a copy of the current lease file. It removes duplicate
and expired leases and writes the result to an output
file. If all goes well the old files are removed
and the new file is renamed to the name of the previous
file. See http://kea.isc.org/wiki/LFCDesign#no1 for
the design.
882. [func] sar
A utility class has been added which handles writing and
deleting pid files as well as checking if the process with
......
......@@ -32,6 +32,11 @@ using namespace std;
using namespace isc::util;
using namespace isc::dhcp;
namespace {
/// @brief Maximum number of errors to allow when reading leases from the file.
const uint32_t MAX_LEASE_ERRORS = 100;
}; // namespace anonymous
namespace isc {
namespace lfc {
......@@ -42,9 +47,6 @@ const char* LFCController::lfc_app_name_ = "DhcpLFC";
/// @brief Defines the executable name.
const char* LFCController::lfc_bin_name_ = "kea-lfc";
/// @brief Maximum number of errors to allow when reading leases from the file.
const uint32_t MAX_LEASE_ERRORS = 100;
LFCController::LFCController()
: protocol_version_(0), verbose_(false), config_file_(""), previous_file_(""),
copy_file_(""), output_file_(""), finish_file_(""), pid_file_("") {
......@@ -55,7 +57,7 @@ LFCController::~LFCController() {
void
LFCController::launch(int argc, char* argv[]) {
bool do_clean = true;
bool do_rotate = true;
try {
parseArgs(argc, argv);
......@@ -64,58 +66,60 @@ LFCController::launch(int argc, char* argv[]) {
throw; // rethrow it
}
if (verbose_ == true)
if (verbose_) {
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) {
if (pid_file.check()) {
// 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 {
// create the pid file for this instance
pid_file.write();
} catch (const PIDFileError& pid_ex) {
std::cerr << pid_ex.what() << std::endl;
return;
}
// If we don't have a finish file do the processing
if (access(finish_file_.c_str(), F_OK) == -1) {
if (verbose_ == true)
// If we don't have a finish file do the processing. We
// don't know the exact type of the finish file here but
// all we care about is if it exists so that's okay
CSVFile lf_finish(getFinishFile());
if (!lf_finish.exists()) {
if (verbose_) {
std::cerr << "LFC Processing files" << std::endl;
}
try {
if (protocol_version_ == 4) {
if (getProtocolVersion() == 4) {
processLeases<Lease4, CSVLeaseFile4, Lease4Storage>();
} else {
processLeases<Lease6, CSVLeaseFile6, Lease6Storage>();
}
} catch (const isc::Exception& proc_ex) {
// We don't want to do the cleanup but do want to get rid of the pid
do_clean = false;
do_rotate = false;
std::cerr << "Processing failed: " << proc_ex.what() << std::endl;
}
}
// If do_clean is true We either already had a finish file or
// If do_rotate is true We either already had a finish file or
// were able to create one. We now want to do the file cleanup,
// we don't want to return after the catch as we
// still need to cleanup the pid file
if (do_clean == true) {
if (verbose_ == true)
if (do_rotate) {
if (verbose_) {
std::cerr << "LFC cleaning files" << std::endl;
}
try {
fileCleanup();
fileRotate();
} catch (const RunTimeFail& run_ex) {
std::cerr << run_ex.what() << std::endl;
}
......@@ -128,8 +132,9 @@ LFCController::launch(int argc, char* argv[]) {
std::cerr << pid_ex.what() << std::endl;
}
if (verbose_ == true)
if (verbose_) {
std::cerr << "LFC complete" << std::endl;
}
}
void
......@@ -266,7 +271,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
}
// If verbose is set echo the input information
if (verbose_ == true) {
if (verbose_) {
std::cerr << "Protocol version: DHCPv" << protocol_version_ << std::endl
<< "Previous or ex lease file: " << previous_file_ << std::endl
<< "Copy lease file: " << copy_file_ << std::endl
......@@ -280,7 +285,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
void
LFCController::usage(const std::string& text) {
if (text != "") {
if (!text.empty()) {
std::cerr << "Usage error: " << text << std::endl;
}
......@@ -315,55 +320,56 @@ LFCController::getVersion(const bool extended) const{
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
void
LFCController::processLeases() const {
LeaseFileType lf_prev(previous_file_.c_str());
LeaseFileType lf_copy(copy_file_.c_str());
LeaseFileType lf_output(output_file_.c_str());
StorageType storage;
storage.clear();
// If a previous file exists read the entries into storage
LeaseFileType lf_prev(getPreviousFile());
if (lf_prev.exists()) {
LeaseFileLoader::load<LeaseObjectType>(lf_prev, storage,
MAX_LEASE_ERRORS);
}
// Follow that with the copy of the current lease file
LeaseFileType lf_copy(getCopyFile());
if (lf_copy.exists()) {
LeaseFileLoader::load<LeaseObjectType>(lf_copy, storage,
MAX_LEASE_ERRORS);
}
// Write the result out to the output file
LeaseFileType lf_output(getOutputFile());
LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
// Once we've finished the output file move it to the complete file
if (rename(output_file_.c_str(), finish_file_.c_str()) != 0)
if (rename(getOutputFile().c_str(), getFinishFile().c_str()) != 0) {
isc_throw(RunTimeFail, "Unable to move output (" << output_file_
<< ") to complete (" << finish_file_
<< ") error: " << strerror(errno));
}
}
void
LFCController::fileCleanup() const {
LFCController::fileRotate() const {
// Remove the old previous file
if ((remove(previous_file_.c_str()) != 0) &&
if ((remove(getPreviousFile().c_str()) != 0) &&
(errno != ENOENT)) {
isc_throw(RunTimeFail, "Unable to delete previous file '"
<< previous_file_ << "' error: " << strerror(errno));
}
// Remove the copy file
if ((remove(copy_file_.c_str()) != 0) &&
if ((remove(getCopyFile().c_str()) != 0) &&
(errno != ENOENT)) {
isc_throw(RunTimeFail, "Unable to delete copy file '"
<< copy_file_ << "' error: " << strerror(errno));
}
// Rename the finish file to be the previous file
if (rename(finish_file_.c_str(), previous_file_.c_str()) != 0)
if (rename(finish_file_.c_str(), previous_file_.c_str()) != 0) {
isc_throw(RunTimeFail, "Unable to move finish (" << finish_file_
<< ") to previous (" << previous_file_
<< ") error: " << strerror(errno));
}
}
}; // namespace isc::lfc
}; // namespace isc
......@@ -28,7 +28,8 @@ public:
isc::Exception(file, line, what) { };
};
/// @brief Exceptions thrown when the processing fails
/// @brief Exceptions thrown when a method is unable to manipulate
/// (remove or rename) a file.
class RunTimeFail : public isc::Exception {
public:
RunTimeFail(const char* file, size_t line, const char* what) :
......@@ -58,7 +59,10 @@ public:
~LFCController();
/// @brief Acts as the primary entry point to start execution
/// of the process. Provides the control logic:
/// of the process. Provides the control logic to combine
/// two lease files and weed out duplicate and expired leases.
/// A description of the design can be found at
/// http://kea.isc.org/wiki/LFCDesign#no1
///
/// -# parse command line arguments
/// -# verify that it is the only instance
......@@ -85,17 +89,13 @@ public:
/// @throw InvalidUsage if the command line parameters are invalid.
void parseArgs(int argc, char* argv[]);
/// @brief Prints the program usage text to std error.
///
/// @param text is a string message which will preceded the usage text.
/// This is intended to be used for specific usage violation messages.
void usage(const std::string& text);
/// @brief Gets the Kea version number for printing
/// @brief Rotate files. After we have a finish file, either from
/// doing the cleanup or because a previous instance was interrupted,
/// delete the work files (previous & copy) and move the finish file
/// to be the new previous file.
///
/// @param extended is a boolean indicating if the version string
/// should be short (false) or extended (true)
std::string getVersion(const bool extended) const;
/// @throw RunTimeFail if we can't manipulate the files.
void fileRotate() const;
/// @name Accessor methods mainly used for testing purposes
//@{
......@@ -150,22 +150,6 @@ public:
std::string getPidFile() const {
return (pid_file_);
}
/// @brief Process files. Read in the leases from any previous & copy
/// files we have and write the results out to the output file. Upon
/// completion of the write move the file to the finish file.
///
/// @throw RunTimeFail if we can't move the file.
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
void processLeases() const;
/// @brief Cleanup files. After we have a finish file, either from
/// doing the cleanup or because a previous instance was interrupted,
/// delete the work files (previous & copy) and move the finish file
/// to be the new previous file.
///
/// @throw RunTimeFail if we can't manipulate the files.
void fileCleanup() const;
//@}
private:
......@@ -179,6 +163,30 @@ private:
std::string output_file_; ///< The path to the output file
std::string finish_file_; ///< The path to the finished output file
std::string pid_file_; ///< The path to the pid file
/// @brief Prints the program usage text to std error.
///
/// @param text is a string message which will preceded the usage text.
/// This is intended to be used for specific usage violation messages.
void usage(const std::string& text);
/// @brief Gets the Kea version number for printing
///
/// @param extended is a boolean indicating if the version string
/// should be short (false) or extended (true)
std::string getVersion(const bool extended) const;
/// @brief Process files. Read in the leases from any previous & copy
/// files we have and write the results out to the output file. Upon
/// completion of the write move the file to the finish file.
///
/// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
/// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
/// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
///
/// @throw RunTimeFail if we can't move the file.
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
void processLeases() const;
};
}; // namespace isc::lfc
......
......@@ -39,7 +39,49 @@ public:
void writeFile(const std::string& filename, const std::string& contents) const;
/// @brief Read a string from a file
std::string readFile(const std::string& contents) const;
std::string readFile(const std::string& filename) const;
/// @brief Test if a file doesn't exist
///
/// @returns true if the file doesn't exist, false if it does
bool noExist(const std::string& filename) const {
if ((remove(filename.c_str()) != 0) && (errno == ENOENT)) {
return (true);
}
return (false);
}
/// @brief Test if any of the temporary (copy, output or finish)
/// files exist
///
/// @returns true if no files exist, and false if any do.
bool noExistIOF() const {
if (noExist(istr_) && noExist(ostr_) && noExist(fstr_)) {
return (true);
}
return (false);
}
/// @brief Test if any of the temporary (copy, output or finish)
/// files and the pid file exist
///
/// @returns true if no files exist, and false if any do.
bool noExistIOFP() const {
if (noExist(istr_) && noExist(ostr_) &&
noExist(fstr_) && noExist(pstr_)) {
return (true);
}
return (false);
}
/// @brief Remove any files we may have created
void removeTestFile() const {
remove(pstr_.c_str());
remove(xstr_.c_str());
remove(istr_.c_str());
remove(ostr_.c_str());
remove(fstr_.c_str());
}
protected:
/// @brief Sets up the file names and header string and removes
......@@ -73,15 +115,6 @@ protected:
}
private:
/// @brief Removes any remaining test files
void removeTestFile() const {
remove(pstr_.c_str());
remove(xstr_.c_str());
remove(istr_.c_str());
remove(ostr_.c_str());
remove(fstr_.c_str());
}
};
std::string
......@@ -246,9 +279,9 @@ TEST_F(LFCControllerTest, someBadData) {
EXPECT_THROW(lfc_controller.parseArgs(argc, argv), InvalidUsage);
}
/// @brief Verify that we do file_cleanup correctly. We create different
/// @brief Verify that we do file rotation correctly. We create different
/// files and see if we properly delete and move them.
TEST_F(LFCControllerTest, fileCleanup) {
TEST_F(LFCControllerTest, fileRotate) {
LFCController lfc_controller, lfc_controller_launch;
// We can use the same arguments and controller for all of the tests
......@@ -276,66 +309,60 @@ TEST_F(LFCControllerTest, fileCleanup) {
// Test 1: Start with no files - we expect an execption as there
// is no file to copy.
EXPECT_THROW(lfc_controller.fileCleanup(), RunTimeFail);
EXPECT_THROW(lfc_controller.fileRotate(), RunTimeFail);
removeTestFile();
// Test 2: Create a file for each of previous, copy and finish. We should
// delete the previous and copy files then move finish to previous.
writeFile(xstr_.c_str(), "1");
writeFile(istr_.c_str(), "2");
writeFile(fstr_.c_str(), "3");
writeFile(xstr_, "1");
writeFile(istr_, "2");
writeFile(fstr_, "3");
lfc_controller.fileCleanup();
lfc_controller.fileRotate();
// verify finish is now previous and copy and finish are gone
EXPECT_EQ(readFile(xstr_.c_str()), "3");
EXPECT_TRUE((remove(istr_.c_str()) != 0) && (errno == ENOENT));
EXPECT_TRUE((remove(fstr_.c_str()) != 0) && (errno == ENOENT));
remove(xstr_.c_str());
// verify finish is now previous and no temp files remain.
EXPECT_EQ(readFile(xstr_), "3");
EXPECT_TRUE(noExistIOF());
removeTestFile();
// Test 3: Create a file for previous and finish but not copy.
writeFile(xstr_.c_str(), "4");
writeFile(fstr_.c_str(), "6");
writeFile(xstr_, "4");
writeFile(fstr_, "6");
lfc_controller.fileCleanup();
lfc_controller.fileRotate();
// verify finish is now previous and copy and finish are gone
EXPECT_EQ(readFile(xstr_.c_str()), "6");
EXPECT_TRUE((remove(istr_.c_str()) != 0) && (errno == ENOENT));
EXPECT_TRUE((remove(fstr_.c_str()) != 0) && (errno == ENOENT));
remove(xstr_.c_str());
// verify finish is now previous and no temp files remain.
EXPECT_EQ(readFile(xstr_), "6");
EXPECT_TRUE(noExistIOF());
removeTestFile();
// Test 4: Create a file for copy and finish but not previous.
writeFile(istr_.c_str(), "8");
writeFile(fstr_.c_str(), "9");
writeFile(istr_, "8");
writeFile(fstr_, "9");
lfc_controller.fileCleanup();
lfc_controller.fileRotate();
// verify finish is now previous and copy and finish are gone
EXPECT_EQ(readFile(xstr_.c_str()), "9");
EXPECT_TRUE((remove(istr_.c_str()) != 0) && (errno == ENOENT));
EXPECT_TRUE((remove(fstr_.c_str()) != 0) && (errno == ENOENT));
remove(xstr_.c_str());
// verify finish is now previous and no temp files remain.
EXPECT_EQ(readFile(xstr_), "9");
EXPECT_TRUE(noExistIOF());
removeTestFile();
// Test 5: rerun test 2 but using launch instead of cleanup
// as we already have a finish file we shouldn't do any extra
// processing
writeFile(xstr_.c_str(), "10");
writeFile(istr_.c_str(), "11");
writeFile(fstr_.c_str(), "12");
writeFile(xstr_, "10");
writeFile(istr_, "11");
writeFile(fstr_, "12");
lfc_controller_launch.launch(argc, argv);
// verify finish is now previous and copy and finish are gone
// as we ran launch we also check to see if the pid is gone.
EXPECT_EQ(readFile(xstr_.c_str()), "12");
EXPECT_TRUE((remove(istr_.c_str()) != 0) && (errno == ENOENT));
EXPECT_TRUE((remove(fstr_.c_str()) != 0) && (errno == ENOENT));
EXPECT_TRUE((remove(pstr_.c_str()) != 0) && (errno == ENOENT));
remove(xstr_.c_str());
// verify finish is now previous and no temp files or pid remain.
EXPECT_EQ(readFile(xstr_), "12");
EXPECT_TRUE(noExistIOFP());
removeTestFile();
}
/// @brief Verify that we properly combine and clean up files
......@@ -376,51 +403,53 @@ TEST_F(LFCControllerTest, launch4) {
// Create the various strings we want to use, the header is predefined.
// We have several entries for different leases, the naming is:
// <lease letter>_<version#>
string A_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
"200,200,8,1,1,host.example.com\n";
string A_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
"200,500,8,1,1,host.example.com\n";
string A_3 = "192.0.2.1,06:07:08:09:0a:bc,,"
string a_3 = "192.0.2.1,06:07:08:09:0a:bc,,"
"200,800,8,1,1,host.example.com\n";
string B_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
string b_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
"100,100,7,0,0,\n";
string B_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
string b_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
"100,135,7,0,0,\n";
string B_3 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
string b_3 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
"100,150,7,0,0,\n";
string C_1 = "192.0.2.3,,a:11:01:04,"
string c_1 = "192.0.2.3,,a:11:01:04,"
"200,200,8,1,1,host.example.com\n";
string D_1 = "192.0.2.5,16:17:18:19:1a:bc,,"
string d_1 = "192.0.2.5,16:17:18:19:1a:bc,,"
"200,200,8,1,1,host.example.com\n";
string D_2 = "192.0.2.5,16:17:18:19:1a:bc,,"
string d_2 = "192.0.2.5,16:17:18:19:1a:bc,,"
"0,200,8,1,1,host.example.com\n";
// Subtest 1: both previous and copy available.
// Create the test previous file
test_str = v4_hdr_ + A_1 + B_1 + C_1 + B_2 + A_2 + D_1;
writeFile(xstr_.c_str(), test_str);
test_str = v4_hdr_ + a_1 + b_1 + c_1 + b_2 + a_2 + d_1;
writeFile(xstr_, test_str);
// Create the test copy file
test_str = v4_hdr_ + A_3 + B_3 + D_2;
writeFile(istr_.c_str(), test_str);
test_str = v4_hdr_ + a_3 + b_3 + d_2;
writeFile(istr_, test_str);
// Run the cleanup
lfc_controller.launch(argc, argv);
// Compare the results, we expect the last lease for each ip
// except for C which was invalid and D which has expired
test_str = v4_hdr_ + A_3 + B_3;
EXPECT_EQ(readFile(xstr_.c_str()), test_str);
remove(xstr_.c_str());
// except for C which was invalid and D which has expired.
// We also verify none of the temp or pid files remain.
test_str = v4_hdr_ + a_3 + b_3;
EXPECT_EQ(readFile(xstr_), test_str);
EXPECT_TRUE(noExistIOFP());
removeTestFile();
// Subtest 2: only previous available
// Create the test previous file
test_str = v4_hdr_ + A_1 + B_1 + C_1 + B_2 + A_2 + D_1;
writeFile(xstr_.c_str(), test_str);
test_str = v4_hdr_ + a_1 + b_1 + c_1 + b_2 + a_2 + d_1;
writeFile(xstr_, test_str);
// No copy file
......@@ -428,27 +457,31 @@ TEST_F(LFCControllerTest, launch4) {
lfc_controller.launch(argc, argv);
// Compare the results, we expect the last lease for each ip
// except for C which was invalid and D which has expired
test_str = v4_hdr_ + A_2 + D_1 + B_2;
EXPECT_EQ(readFile(xstr_.c_str()), test_str);
remove(xstr_.c_str());
// except for C which was invalid and D which has expired.
// We also verify none of the temp or pid files remain.
test_str = v4_hdr_ + a_2 + d_1 + b_2;
EXPECT_EQ(readFile(xstr_), test_str);
EXPECT_TRUE(noExistIOFP());
removeTestFile();
// Subtest 3: only copy available
// No previous file
// Create the test copy file
test_str = v4_hdr_ + D_1 + A_1 + B_1 + B_3 + D_2 + A_3;
writeFile(istr_.c_str(), test_str);
test_str = v4_hdr_ + d_1 + a_1 + b_1 + b_3 + d_2 + a_3;
writeFile(istr_, test_str);
// Run the cleanup
lfc_controller.launch(argc, argv);
// Compare the results, we expect the last lease for each ip
// except for C which was invalid and D which has expired
test_str = v4_hdr_ + A_3 + B_3;
EXPECT_EQ(readFile(xstr_.c_str()), test_str);
remove(xstr_.c_str());
// except for C which was invalid and D which has expired.
// We also verify none of the temp or pid files remain.
test_str = v4_hdr_ + a_3 + b_3;
EXPECT_EQ(readFile(xstr_), test_str);
EXPECT_TRUE(noExistIOFP());
removeTestFile();
// Subtest 4: neither available
......@@ -459,10 +492,12 @@ TEST_F(LFCControllerTest, launch4) {
// Run the cleanup
lfc_controller.launch(argc, argv);
// Compare the results, we expect a header and no leaes
// Compare the results, we expect a header and no leaes.
// We also verify none of the temp or pid files remain.
test_str = v4_hdr_;
EXPECT_EQ(readFile(xstr_.c_str()), test_str);
remove(xstr_.c_str());
EXPECT_EQ(readFile(xstr_), test_str);
EXPECT_TRUE(noExistIOFP());
removeTestFile();
// Subtest 5: a file with a lot of errors
......@@ -470,7 +505,7 @@ TEST_F(LFCControllerTest, launch4) {
astr = "1,\n2,\n3,\n4,\n5,\n6,\n7,\n7,\n8,\n9,\n10,\n";
test_str = v4_hdr_ + astr + astr + astr + astr + astr +
astr + astr + astr + astr + astr + astr;
writeFile(xstr_.c_str(), test_str);
writeFile(xstr_, test_str);
// No copy file
......@@ -478,10 +513,10 @@ TEST_F(LFCControllerTest, launch4) {
// catch the error and properly cleanup.
lfc_controller.launch(argc, argv);
// And we shouldn't have deleted the previous file, but should
// have deleted the pid file
EXPECT_EQ(readFile(xstr_.c_str()), test_str);
EXPECT_TRUE((remove(pstr_.c_str()) != 0) && (errno == ENOENT));
// And we shouldn't have deleted the previous file.