Browse Source

[master] Merge in log support for LFC

Add log support for LFC

Add statistics support in lease files used
by LFC to report stats for leases.

Update memfile tests to check that LFC
did the right thing.

Merge branch 'trac3667'

Conflicts:
	ChangeLog
	src/lib/util/csv_file.h
Shawn Routhier 10 years ago
parent
commit
7f36e034fe

+ 7 - 0
ChangeLog

@@ -1,3 +1,10 @@
+XXX.	[func]		sar
+	A class, LeaseFileStats, has been added to provide simple
+	statistics for use with lease files.  Also added logging
+	to the kea-lfc process per the design.
+	See http://kea.isc.org/wiki/LFCDesign for the design.
+	(Trac #3667, git )
+
 891.	[func]		tomek
 891.	[func]		tomek
 	libdhcpsrv: Allocation Engine now uses statically assigned
 	libdhcpsrv: Allocation Engine now uses statically assigned
 	addresses when processing DHCPv6 renewals.
 	addresses when processing DHCPv6 renewals.

+ 2 - 2
src/bin/lfc/Makefile.am

@@ -47,8 +47,8 @@ BUILT_SOURCES = lfc_messages.h lfc_messages.cc
 noinst_LTLIBRARIES = liblfc.la
 noinst_LTLIBRARIES = liblfc.la
 
 
 liblfc_la_SOURCES  =
 liblfc_la_SOURCES  =
-liblfc_la_SOURCES += lfc_controller.h
-liblfc_la_SOURCES += lfc_controller.cc
+liblfc_la_SOURCES += lfc_controller.h lfc_controller.cc
+liblfc_la_SOURCES += lfc_log.h lfc_log.cc
 
 
 nodist_liblfc_la_SOURCES = lfc_messages.h lfc_messages.cc
 nodist_liblfc_la_SOURCES = lfc_messages.h lfc_messages.cc
 EXTRA_DIST += lfc_messages.mes
 EXTRA_DIST += lfc_messages.mes

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

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <lfc/lfc_controller.h>
 #include <lfc/lfc_controller.h>
+#include <lfc/lfc_log.h>
 #include <util/pid_file.h>
 #include <util/pid_file.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <dhcpsrv/csv_lease_file4.h>
 #include <dhcpsrv/csv_lease_file4.h>
@@ -20,6 +21,8 @@
 #include <dhcpsrv/memfile_lease_storage.h>
 #include <dhcpsrv/memfile_lease_storage.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_file_loader.h>
 #include <dhcpsrv/lease_file_loader.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
 #include <config.h>
 #include <config.h>
 
 
 #include <iostream>
 #include <iostream>
@@ -31,6 +34,7 @@
 using namespace std;
 using namespace std;
 using namespace isc::util;
 using namespace isc::util;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::log;
 
 
 namespace {
 namespace {
 /// @brief Maximum number of errors to allow when reading leases from the file.
 /// @brief Maximum number of errors to allow when reading leases from the file.
@@ -56,9 +60,17 @@ LFCController::~LFCController() {
 }
 }
 
 
 void
 void
-LFCController::launch(int argc, char* argv[]) {
+LFCController::launch(int argc, char* argv[], const bool test_mode) {
     bool do_rotate = true;
     bool do_rotate = true;
 
 
+    // It would be nice to set up the logger as the first step
+    // in the process, but we don't know where to send logging
+    // info until after we have parsed our arguments.  As we
+    // don't currently log anything when trying to parse the
+    // arguments we do the parse before the logging setup.  If
+    // we do decide to log something then the code will need
+    // to move around a bit.
+
     try {
     try {
         parseArgs(argc, argv);
         parseArgs(argc, argv);
     } catch (const InvalidUsage& ex) {
     } catch (const InvalidUsage& ex) {
@@ -66,9 +78,10 @@ LFCController::launch(int argc, char* argv[]) {
         throw;  // rethrow it
         throw;  // rethrow it
     }
     }
 
 
-    if (verbose_) {
-        std::cerr << "Starting lease file cleanup" << std::endl;
-    }
+    // Start up the logging system.
+    startLogger(test_mode);
+
+    LOG_INFO(lfc_logger, LFC_START);
 
 
     // verify we are the only instance
     // verify we are the only instance
     PIDFile pid_file(pid_file_);
     PIDFile pid_file(pid_file_);
@@ -76,14 +89,14 @@ LFCController::launch(int argc, char* argv[]) {
     try {
     try {
         if (pid_file.check()) {
         if (pid_file.check()) {
             // Already running instance, bail out
             // Already running instance, bail out
-            std::cerr << "LFC instance already running" <<  std::endl;
+            LOG_FATAL(lfc_logger, LFC_RUNNING);
             return;
             return;
         }
         }
 
 
         // create the pid file for this instance
         // create the pid file for this instance
         pid_file.write();
         pid_file.write();
     } catch (const PIDFileError& pid_ex) {
     } catch (const PIDFileError& pid_ex) {
-        std::cerr << pid_ex.what() << std::endl;
+        LOG_FATAL(lfc_logger, LFC_FAIL_PID_CREATE).arg(pid_ex.what());
         return;
         return;
     }
     }
 
 
@@ -92,9 +105,9 @@ LFCController::launch(int argc, char* argv[]) {
     // all we care about is if it exists so that's okay
     // all we care about is if it exists so that's okay
     CSVFile lf_finish(getFinishFile());
     CSVFile lf_finish(getFinishFile());
     if (!lf_finish.exists()) {
     if (!lf_finish.exists()) {
-        if (verbose_) {
-            std::cerr << "LFC Processing files" << std::endl;
-        }
+        LOG_INFO(lfc_logger, LFC_PROCESSING)
+          .arg(previous_file_)
+          .arg(copy_file_);
 
 
         try {
         try {
             if (getProtocolVersion() == 4) {
             if (getProtocolVersion() == 4) {
@@ -105,7 +118,7 @@ LFCController::launch(int argc, char* argv[]) {
         } catch (const isc::Exception& proc_ex) {
         } catch (const isc::Exception& proc_ex) {
             // We don't want to do the cleanup but do want to get rid of the pid
             // We don't want to do the cleanup but do want to get rid of the pid
             do_rotate = false;
             do_rotate = false;
-            std::cerr << "Processing failed: " << proc_ex.what() << std::endl;
+            LOG_FATAL(lfc_logger, LFC_FAIL_PROCESS).arg(proc_ex.what());
         }
         }
     }
     }
 
 
@@ -114,14 +127,12 @@ LFCController::launch(int argc, char* argv[]) {
     // we don't want to return after the catch as we
     // we don't want to return after the catch as we
     // still need to cleanup the pid file
     // still need to cleanup the pid file
     if (do_rotate) {
     if (do_rotate) {
-        if (verbose_) {
-            std::cerr << "LFC cleaning files" << std::endl;
-        }
+        LOG_INFO(lfc_logger, LFC_ROTATING);
 
 
         try {
         try {
             fileRotate();
             fileRotate();
         } catch (const RunTimeFail& run_ex) {
         } catch (const RunTimeFail& run_ex) {
-            std::cerr << run_ex.what() << std::endl;
+          LOG_FATAL(lfc_logger, LFC_FAIL_ROTATE).arg(run_ex.what());
         }
         }
     }
     }
 
 
@@ -129,12 +140,10 @@ LFCController::launch(int argc, char* argv[]) {
     try {
     try {
         pid_file.deleteFile();
         pid_file.deleteFile();
     } catch (const PIDFileError& pid_ex) {
     } catch (const PIDFileError& pid_ex) {
-        std::cerr << pid_ex.what() << std::endl;
+          LOG_FATAL(lfc_logger, LFC_FAIL_PID_DEL).arg(pid_ex.what());
     }
     }
 
 
-    if (verbose_) {
-        std::cerr << "LFC complete" << std::endl;
-    }
+    LOG_INFO(lfc_logger, LFC_TERMINATE);
 }
 }
 
 
 void
 void
@@ -272,7 +281,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
 
 
     // If verbose is set echo the input information
     // If verbose is set echo the input information
     if (verbose_) {
     if (verbose_) {
-        std::cerr << "Protocol version:    DHCPv" << protocol_version_ << std::endl
+        std::cout << "Protocol version:    DHCPv" << protocol_version_ << std::endl
                   << "Previous or ex lease file: " << previous_file_ << std::endl
                   << "Previous or ex lease file: " << previous_file_ << std::endl
                   << "Copy lease file:           " << copy_file_ << std::endl
                   << "Copy lease file:           " << copy_file_ << std::endl
                   << "Output lease file:         " << output_file_ << std::endl
                   << "Output lease file:         " << output_file_ << std::endl
@@ -340,6 +349,17 @@ LFCController::processLeases() const {
     LeaseFileType lf_output(getOutputFile());
     LeaseFileType lf_output(getOutputFile());
     LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
     LeaseFileLoader::write<LeaseObjectType>(lf_output, storage);
 
 
+    // If desired log the stats
+    LOG_INFO(lfc_logger, LFC_READ_STATS)
+      .arg(lf_prev.getReadLeases() + lf_copy.getReadLeases())
+      .arg(lf_prev.getReads() + lf_copy.getReads())
+      .arg(lf_prev.getReadErrs() + lf_copy.getReadErrs());
+
+    LOG_INFO(lfc_logger, LFC_WRITE_STATS)
+      .arg(lf_output.getWriteLeases())
+      .arg(lf_output.getWrites())
+      .arg(lf_output.getWriteErrs());
+
     // Once we've finished the output file move it to the complete file
     // Once we've finished the output file move it to the complete file
     if (rename(getOutputFile().c_str(), getFinishFile().c_str()) != 0) {
     if (rename(getOutputFile().c_str(), getFinishFile().c_str()) != 0) {
         isc_throw(RunTimeFail, "Unable to move output (" << output_file_
         isc_throw(RunTimeFail, "Unable to move output (" << output_file_
@@ -371,5 +391,40 @@ LFCController::fileRotate() const {
                   << ") error: " << strerror(errno));
                   << ") error: " << strerror(errno));
     }
     }
 }
 }
+
+void
+LFCController::startLogger(const bool test_mode) const {
+    // If we are running in test mode use the environment variables
+    // else use our defaults
+    if (test_mode) {
+        initLogger();
+    }
+    else {
+        OutputOption option;
+        LoggerManager manager;
+
+        initLogger(lfc_app_name_, INFO, 0, NULL, false);
+
+        // Prepare the objects to define the logging specification
+        LoggerSpecification spec(getRootLoggerName(),
+                                 keaLoggerSeverity(INFO),
+                                 keaLoggerDbglevel(0));
+
+        // If we are running in verbose (debugging) mode
+        // we send the output to the console, otherwise
+        // by default we send it to the SYSLOG
+        if (verbose_) {
+            option.destination = OutputOption::DEST_CONSOLE;
+        } else {
+            option.destination = OutputOption::DEST_SYSLOG;
+        }
+
+        // ... and set the destination
+        spec.addOutputOption(option);
+
+        manager.process(spec);
+    }
+}
+
 }; // namespace isc::lfc
 }; // namespace isc::lfc
 }; // namespace isc
 }; // namespace isc

+ 11 - 1
src/bin/lfc/lfc_controller.h

@@ -77,9 +77,13 @@ public:
     ///
     ///
     /// @param argc Number of strings in the @c argv array.
     /// @param argc Number of strings in the @c argv array.
     /// @param argv Array of arguments passed in via the program's main function.
     /// @param argv Array of arguments passed in via the program's main function.
+    /// @param test_mode is a bool value which indicates if @c launch
+    /// should be run in the test mode (if true).  This parameter doesn't
+    /// have a default value to force test implementers to enable test
+    /// mode explicitly.
     ///
     ///
     /// @throw InvalidUsage if the command line parameters are invalid.
     /// @throw InvalidUsage if the command line parameters are invalid.
-    void launch(int argc, char* argv[]);
+    void launch(int argc, char* argv[], const bool test_mode);
 
 
     /// @brief Process the command line arguments.
     /// @brief Process the command line arguments.
     ///
     ///
@@ -192,6 +196,12 @@ private:
     /// @throw RunTimeFail if we can't move the file.
     /// @throw RunTimeFail if we can't move the file.
     template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
     template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
     void processLeases() const;
     void processLeases() const;
+
+    ///@brief Start up the logging system
+    ///
+    /// @param test_mode indicates if we have have been started from the test
+    /// system (true) or are running normally (false)
+    void startLogger(const bool test_mode) const;
 };
 };
 
 
 }; // namespace isc::lfc
 }; // namespace isc::lfc

+ 26 - 0
src/bin/lfc/lfc_log.cc

@@ -0,0 +1,26 @@
+// 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.
+
+/// Defines the logger used by the top-level component of kea-lfc.
+
+#include <lfc/lfc_log.h>
+
+namespace isc {
+namespace lfc {
+
+/// @brief Defines the logger used within LFC.
+isc::log::Logger lfc_logger("DhcpLFC");
+
+} // namespace lfc
+} // namespace isc

+ 32 - 0
src/bin/lfc/lfc_log.h

@@ -0,0 +1,32 @@
+// 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 LFC_LOG_H
+#define LFC_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <lfc/lfc_messages.h>
+
+namespace isc {
+namespace lfc {
+
+/// Define the logger for the "lfc" logging.
+extern isc::log::Logger lfc_logger;
+
+
+} // namespace lfc
+} // namespace isc
+
+#endif // LFC_LOG_H

+ 45 - 2
src/bin/lfc/lfc_messages.mes

@@ -13,5 +13,48 @@
 # PERFORMANCE OF THIS SOFTWARE.
 # PERFORMANCE OF THIS SOFTWARE.
 
 
 $NAMESPACE isc::lfc
 $NAMESPACE isc::lfc
-% LFC_TEST_MESSAGE test messages
-This is a test and placeholder debug message
+% LFC_START Starting lease file cleanup
+This message is issued as the LFC process starts.
+
+% LFC_TERMINATE LFC finished processing
+This message is issued when the LFC process completes.  It does not
+indicate that the process was successful only that it has finished.
+
+% LFC_RUNNING LFC instance already running
+This message is issued if LFC detects that a previous copy of LFC
+may still be running via the PID check.
+
+% LFC_PROCESSING Previous file: %1, copy file: %2
+This message is issued just before LFC starts processing the 
+lease files.
+
+% LFC_ROTATING LFC rotating files
+This message is issued just before LFC starts rotating the
+lease files - removing the old and replacing them with the new.
+
+% LFC_FAIL_PID_CREATE : %1
+This message is issued if LFC detected a failure when trying
+to create the PID file.  It includes a more specifc error string.
+
+% LFC_FAIL_PROCESS : %1
+This message is issued if LFC detected a failure when trying
+to process the files.  It includes a more specifc error string.
+
+% LFC_FAIL_ROTATE : %1
+This message is issued if LFC detected a failure when trying
+to rotate the files.  It includes a more specifc error string.
+
+% LFC_FAIL_PID_DEL : %1
+This message is issued if LFC detected a failure when trying
+to delete the PID file.  It includes a more specifc error string.
+
+% LFC_READ_STATS Leases: %1, attempts: %2, errors: %3.
+This message prints out the number of leases that were read, the
+number of attempts to read leases and the number of errors
+encountered while reading.
+
+% LFC_WRITE_STATS Leases: %1, attempts: %2, errors: %3.
+This message prints out the number of leases that were written, the
+number of attempts to write leases and the number of errors
+encountered while writing.
+

+ 2 - 2
src/bin/lfc/main.cc

@@ -19,8 +19,8 @@
 #include <config.h>
 #include <config.h>
 #include <iostream>
 #include <iostream>
 
 
-using namespace isc::lfc;
 using namespace std;
 using namespace std;
+using namespace isc::lfc;
 
 
 /// This file contains the entry point (main() function) for the
 /// This file contains the entry point (main() function) for the
 /// standard LFC process, kea-lfc, component of the Kea software suite.
 /// standard LFC process, kea-lfc, component of the Kea software suite.
@@ -36,7 +36,7 @@ int main(int argc, char* argv[]) {
     // Exit program with the controller's return code.
     // Exit program with the controller's return code.
     try  {
     try  {
         // 'false' value disables test mode.
         // 'false' value disables test mode.
-        lfc_controller.launch(argc, argv);
+        lfc_controller.launch(argc, argv, false);
     } catch (const isc::Exception& ex) {
     } catch (const isc::Exception& ex) {
         std::cerr << "Service failed: " << ex.what() << std::endl;
         std::cerr << "Service failed: " << ex.what() << std::endl;
         ret = EXIT_FAILURE;
         ret = EXIT_FAILURE;

+ 23 - 16
src/bin/lfc/tests/lfc_controller_unittests.cc

@@ -75,7 +75,7 @@ public:
     }
     }
 
 
 protected:
 protected:
-    /// @brief Sets up the file names and header string and removes 
+    /// @brief Sets up the file names and header string and removes
     /// any old test files before the test
     /// any old test files before the test
     virtual void SetUp() {
     virtual void SetUp() {
         // set up the test files we need
         // set up the test files we need
@@ -105,6 +105,13 @@ protected:
         removeTestFile();
         removeTestFile();
     }
     }
 
 
+    /// @Wrapper to invoke the controller's launch method  Please refer to
+    /// lfcController::launch for details.  This is wrapped to provide
+    /// a single place to update the test_mode throughout the file.
+    void launch(LFCController lfc_controller, int argc, char* argv[]) {
+        lfc_controller.launch(argc, argv, true);
+    }
+
 private:
 private:
 };
 };
 
 
@@ -348,7 +355,7 @@ TEST_F(LFCControllerTest, fileRotate) {
     writeFile(istr_, "11");
     writeFile(istr_, "11");
     writeFile(fstr_, "12");
     writeFile(fstr_, "12");
 
 
-    lfc_controller_launch.launch(argc, argv);
+    launch(lfc_controller_launch, argc, argv);
 
 
     // verify finish is now previous and no temp files or pid remain.
     // verify finish is now previous and no temp files or pid remain.
     EXPECT_EQ(readFile(xstr_), "12");
     EXPECT_EQ(readFile(xstr_), "12");
@@ -426,7 +433,7 @@ TEST_F(LFCControllerTest, launch4) {
     writeFile(istr_, test_str);
     writeFile(istr_, test_str);
 
 
     // Run the cleanup
     // Run the cleanup
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // Compare the results, we expect the last lease for each ip
     // Compare the results, we expect the last lease for each ip
     // except for C which was invalid and D which has expired.
     // except for C which was invalid and D which has expired.
@@ -445,7 +452,7 @@ TEST_F(LFCControllerTest, launch4) {
     // No copy file
     // No copy file
 
 
     // Run the cleanup
     // Run the cleanup
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // Compare the results, we expect the last lease for each ip
     // Compare the results, we expect the last lease for each ip
     // except for C which was invalid and D which has expired.
     // except for C which was invalid and D which has expired.
@@ -464,7 +471,7 @@ TEST_F(LFCControllerTest, launch4) {
     writeFile(istr_, test_str);
     writeFile(istr_, test_str);
 
 
     // Run the cleanup
     // Run the cleanup
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // Compare the results, we expect the last lease for each ip
     // Compare the results, we expect the last lease for each ip
     // except for C which was invalid and D which has expired.
     // except for C which was invalid and D which has expired.
@@ -481,7 +488,7 @@ TEST_F(LFCControllerTest, launch4) {
     // No copy file
     // No copy file
 
 
     // Run the cleanup
     // Run the cleanup
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, 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.
     // We also verify none of the temp or pid files remain.
@@ -502,7 +509,7 @@ TEST_F(LFCControllerTest, launch4) {
 
 
     // Run the cleanup, the file should fail but we should
     // Run the cleanup, the file should fail but we should
     // catch the error and properly cleanup.
     // catch the error and properly cleanup.
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // And we shouldn't have deleted the previous file.
     // And we shouldn't have deleted the previous file.
     // We also verify none of the temp or pid files remain.
     // We also verify none of the temp or pid files remain.
@@ -563,10 +570,10 @@ TEST_F(LFCControllerTest, launch6) {
     string b_3 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
     string b_3 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
                  "300,1000,6,150,0,8,0,0,0,,\n";
                  "300,1000,6,150,0,8,0,0,0,,\n";
 
 
-    string c_1 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,100,200,8,0,2,"
-                 "16,64,0,0,,\n";
-    string c_2 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,100,400,8,0,2,"
-                 "16,64,0,0,,\n";
+    string c_1 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+                 "100,200,8,0,2,16,64,0,0,,\n";
+    string c_2 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+                 "100,400,8,0,2,16,64,0,0,,\n";
 
 
     string d_1 = "2001:db8:1::3,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
     string d_1 = "2001:db8:1::3,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
                  "200,600,8,100,0,7,0,1,1,host.example.com,\n";
                  "200,600,8,100,0,7,0,1,1,host.example.com,\n";
@@ -581,7 +588,7 @@ TEST_F(LFCControllerTest, launch6) {
     writeFile(istr_, test_str);
     writeFile(istr_, test_str);
 
 
     // Run the cleanup
     // Run the cleanup
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // Compare the results, we expect the last lease for each ip
     // Compare the results, we expect the last lease for each ip
     // except for A which has expired.
     // except for A which has expired.
@@ -600,7 +607,7 @@ TEST_F(LFCControllerTest, launch6) {
     // No copy file
     // No copy file
 
 
     // Run the cleanup
     // Run the cleanup
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // Compare the results, we expect the last lease for each ip.
     // Compare the results, we expect the last lease for each ip.
     // We also verify none of the temp or pid files remain.
     // We also verify none of the temp or pid files remain.
@@ -618,7 +625,7 @@ TEST_F(LFCControllerTest, launch6) {
     writeFile(istr_, test_str);
     writeFile(istr_, test_str);
 
 
     // Run the cleanup
     // Run the cleanup
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // Compare the results, we expect the last lease for each ip.
     // Compare the results, we expect the last lease for each ip.
     // We also verify none of the temp or pid files remain.
     // We also verify none of the temp or pid files remain.
@@ -634,7 +641,7 @@ TEST_F(LFCControllerTest, launch6) {
     // No copy file
     // No copy file
 
 
     // Run the cleanup
     // Run the cleanup
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // Compare the results, we expect a header and no leases.
     // Compare the results, we expect a header and no leases.
     // We also verify none of the temp or pid files remain.
     // We also verify none of the temp or pid files remain.
@@ -655,7 +662,7 @@ TEST_F(LFCControllerTest, launch6) {
 
 
     // Run the cleanup, the file should fail but we should
     // Run the cleanup, the file should fail but we should
     // catch the error and properly cleanup.
     // catch the error and properly cleanup.
-    lfc_controller.launch(argc, argv);
+    launch(lfc_controller, argc, argv);
 
 
     // And we shouldn't have deleted the previous file.
     // And we shouldn't have deleted the previous file.
     // We also verify none of the temp or pid files remain.
     // We also verify none of the temp or pid files remain.

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

@@ -86,6 +86,7 @@ libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_file_loader.h
 libkea_dhcpsrv_la_SOURCES += lease_file_loader.h
+libkea_dhcpsrv_la_SOURCES += lease_file_stats.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
 libkea_dhcpsrv_la_SOURCES += logging.cc logging.h
 libkea_dhcpsrv_la_SOURCES += logging.cc logging.h

+ 38 - 3
src/lib/dhcpsrv/csv_lease_file4.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,10 +26,25 @@ CSVLeaseFile4::CSVLeaseFile4(const std::string& filename)
 }
 }
 
 
 void
 void
-CSVLeaseFile4::append(const Lease4& lease) const {
+CSVLeaseFile4::open(const bool seek_to_end) {
+    // Call the base class to open the file
+    CSVFile::open(seek_to_end);
+
+    // and clear any statistics we may have
+    clearStatistics();
+}
+
+void
+CSVLeaseFile4::append(const Lease4& lease) {
+    // Bump the number of write attempts
+    ++writes_;
+
     CSVRow row(getColumnCount());
     CSVRow row(getColumnCount());
     row.writeAt(getColumnIndex("address"), lease.addr_.toText());
     row.writeAt(getColumnIndex("address"), lease.addr_.toText());
     if (!lease.hwaddr_) {
     if (!lease.hwaddr_) {
+        // Bump the error counter
+        ++write_errs_;
+
         isc_throw(BadValue, "Lease4 must have hardware address specified.");
         isc_throw(BadValue, "Lease4 must have hardware address specified.");
     }
     }
     row.writeAt(getColumnIndex("hwaddr"), lease.hwaddr_->toText(false));
     row.writeAt(getColumnIndex("hwaddr"), lease.hwaddr_->toText(false));
@@ -43,11 +58,24 @@ CSVLeaseFile4::append(const Lease4& lease) const {
     row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
     row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
     row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
     row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
     row.writeAt(getColumnIndex("hostname"), lease.hostname_);
     row.writeAt(getColumnIndex("hostname"), lease.hostname_);
-    CSVFile::append(row);
+
+    try {
+        CSVFile::append(row);
+    } catch (const std::exception& ex) {
+        // Catch any errors so we can bump the error counter than rethrow it
+        ++write_errs_;
+        throw;
+    }
+
+    // Bump the number of leases written
+    ++write_leases_;
 }
 }
 
 
 bool
 bool
 CSVLeaseFile4::next(Lease4Ptr& lease) {
 CSVLeaseFile4::next(Lease4Ptr& lease) {
+    // Bump the number of read attempts
+    ++reads_;
+
     // Read the CSV row and try to create a lease from the values read.
     // Read the CSV row and try to create a lease from the values read.
     // This may easily result in exception. We don't want this function
     // This may easily result in exception. We don't want this function
     // to throw exceptions, so we catch them all and rather return the
     // to throw exceptions, so we catch them all and rather return the
@@ -88,12 +116,19 @@ CSVLeaseFile4::next(Lease4Ptr& lease) {
                                readHostname(row)));
                                readHostname(row)));
 
 
     } catch (std::exception& ex) {
     } catch (std::exception& ex) {
+        // bump the read error count
+        ++read_errs_;
+
         // The lease might have been created, so let's set it back to NULL to
         // The lease might have been created, so let's set it back to NULL to
         // signal that lease hasn't been parsed.
         // signal that lease hasn't been parsed.
         lease.reset();
         lease.reset();
         setReadMsg(ex.what());
         setReadMsg(ex.what());
         return (false);
         return (false);
     }
     }
+
+    // bump the number of leases read
+    ++read_leases_;
+
     return (true);
     return (true);
 }
 }
 
 

+ 13 - 3
src/lib/dhcpsrv/csv_lease_file4.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/lease_file_stats.h>
 #include <util/csv_file.h>
 #include <util/csv_file.h>
 #include <stdint.h>
 #include <stdint.h>
 #include <string>
 #include <string>
@@ -38,7 +39,7 @@ namespace dhcp {
 /// validation (see http://kea.isc.org/ticket/2405). However, when #2405
 /// validation (see http://kea.isc.org/ticket/2405). However, when #2405
 /// is implemented, the @c next function may need to be updated to use the
 /// is implemented, the @c next function may need to be updated to use the
 /// validation capablity of @c Lease4.
 /// validation capablity of @c Lease4.
-class CSVLeaseFile4 : public isc::util::CSVFile {
+class CSVLeaseFile4 : public isc::util::CSVFile, public LeaseFileStats {
 public:
 public:
 
 
     /// @brief Constructor.
     /// @brief Constructor.
@@ -48,6 +49,15 @@ public:
     /// @param filename Name of the lease file.
     /// @param filename Name of the lease file.
     CSVLeaseFile4(const std::string& filename);
     CSVLeaseFile4(const std::string& filename);
 
 
+    /// @brief Opens a lease file.
+    ///
+    /// This function calls the base class open to do the
+    /// work of opening a file.  It is used to clear any
+    /// statistics associated with any previous use of the file
+    /// While it doesn't throw any exceptions of its own
+    /// the base class may do so.
+    virtual void open(const bool seek_to_end = false);
+
     /// @brief Appends the lease record to the CSV file.
     /// @brief Appends the lease record to the CSV file.
     ///
     ///
     /// This function doesn't throw exceptions itself. In theory, exceptions
     /// This function doesn't throw exceptions itself. In theory, exceptions
@@ -56,7 +66,7 @@ public:
     /// error.
     /// error.
     ///
     ///
     /// @param lease Structure representing a DHCPv4 lease.
     /// @param lease Structure representing a DHCPv4 lease.
-    void append(const Lease4& lease) const;
+    void append(const Lease4& lease);
 
 
     /// @brief Reads next lease from the CSV file.
     /// @brief Reads next lease from the CSV file.
     ///
     ///

+ 34 - 3
src/lib/dhcpsrv/csv_lease_file6.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -27,7 +27,19 @@ CSVLeaseFile6::CSVLeaseFile6(const std::string& filename)
 }
 }
 
 
 void
 void
-CSVLeaseFile6::append(const Lease6& lease) const {
+CSVLeaseFile6::open(const bool seek_to_end) {
+    // Call the base class to open the file
+    CSVFile::open(seek_to_end);
+
+    // and clear any statistics we may have
+    clearStatistics();
+}
+
+void
+CSVLeaseFile6::append(const Lease6& lease) {
+    // Bump the number of write attempts
+    ++writes_;
+
     CSVRow row(getColumnCount());
     CSVRow row(getColumnCount());
     row.writeAt(getColumnIndex("address"), lease.addr_.toText());
     row.writeAt(getColumnIndex("address"), lease.addr_.toText());
     row.writeAt(getColumnIndex("duid"), lease.duid_->toText());
     row.writeAt(getColumnIndex("duid"), lease.duid_->toText());
@@ -46,11 +58,23 @@ CSVLeaseFile6::append(const Lease6& lease) const {
         // We may not have hardware information
         // We may not have hardware information
         row.writeAt(getColumnIndex("hwaddr"), lease.hwaddr_->toText(false));
         row.writeAt(getColumnIndex("hwaddr"), lease.hwaddr_->toText(false));
     }
     }
-    CSVFile::append(row);
+    try {
+        CSVFile::append(row);
+    } catch (const std::exception& ex) {
+        // Catch any errors so we can bump the error counter than rethrow it
+        ++write_errs_;
+        throw;
+    }
+
+    // Bump the number of leases written
+    ++write_leases_;
 }
 }
 
 
 bool
 bool
 CSVLeaseFile6::next(Lease6Ptr& lease) {
 CSVLeaseFile6::next(Lease6Ptr& lease) {
+    // Bump the number of read attempts
+    ++reads_;
+
     // Read the CSV row and try to create a lease from the values read.
     // Read the CSV row and try to create a lease from the values read.
     // This may easily result in exception. We don't want this function
     // This may easily result in exception. We don't want this function
     // to throw exceptions, so we catch them all and rather return the
     // to throw exceptions, so we catch them all and rather return the
@@ -77,12 +101,19 @@ CSVLeaseFile6::next(Lease6Ptr& lease) {
         lease->hostname_ = readHostname(row);
         lease->hostname_ = readHostname(row);
 
 
     } catch (std::exception& ex) {
     } catch (std::exception& ex) {
+        // bump the read error count
+        ++read_errs_;
+
         // The lease might have been created, so let's set it back to NULL to
         // The lease might have been created, so let's set it back to NULL to
         // signal that lease hasn't been parsed.
         // signal that lease hasn't been parsed.
         lease.reset();
         lease.reset();
         setReadMsg(ex.what());
         setReadMsg(ex.what());
         return (false);
         return (false);
     }
     }
+
+    // bump the number of leases read
+    ++read_leases_;
+
     return (true);
     return (true);
 }
 }
 
 

+ 13 - 3
src/lib/dhcpsrv/csv_lease_file6.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/lease_file_stats.h>
 #include <util/csv_file.h>
 #include <util/csv_file.h>
 #include <stdint.h>
 #include <stdint.h>
 #include <string>
 #include <string>
@@ -37,7 +38,7 @@ namespace dhcp {
 /// validation (see http://kea.isc.org/ticket/2405). However, when #2405
 /// validation (see http://kea.isc.org/ticket/2405). However, when #2405
 /// is implemented, the @c next function may need to be updated to use the
 /// is implemented, the @c next function may need to be updated to use the
 /// validation capablity of @c Lease6.
 /// validation capablity of @c Lease6.
-class CSVLeaseFile6 : public isc::util::CSVFile {
+class CSVLeaseFile6 : public isc::util::CSVFile, public LeaseFileStats {
 public:
 public:
 
 
     /// @brief Constructor.
     /// @brief Constructor.
@@ -47,6 +48,15 @@ public:
     /// @param filename Name of the lease file.
     /// @param filename Name of the lease file.
     CSVLeaseFile6(const std::string& filename);
     CSVLeaseFile6(const std::string& filename);
 
 
+    /// @brief Opens a lease file.
+    ///
+    /// This function calls the base class open to do the
+    /// work of opening a file.  It is used to clear any
+    /// statistics associated with any previous use of the file
+    /// While it doesn't throw any exceptions of its own
+    /// the base class may do so.
+    virtual void open(const bool seek_to_end = false);
+
     /// @brief Appends the lease record to the CSV file.
     /// @brief Appends the lease record to the CSV file.
     ///
     ///
     /// This function doesn't throw exceptions itself. In theory, exceptions
     /// This function doesn't throw exceptions itself. In theory, exceptions
@@ -55,7 +65,7 @@ public:
     /// error.
     /// error.
     ///
     ///
     /// @param lease Structure representing a DHCPv6 lease.
     /// @param lease Structure representing a DHCPv6 lease.
-    void append(const Lease6& lease) const;
+    void append(const Lease6& lease);
 
 
     /// @brief Reads next lease from the CSV file.
     /// @brief Reads next lease from the CSV file.
     ///
     ///

+ 1 - 3
src/lib/dhcpsrv/lease_file_loader.h

@@ -43,8 +43,6 @@ namespace dhcp {
 /// with the @c Lease4Storage and @c Lease6Storage to process the DHCPv4
 /// with the @c Lease4Storage and @c Lease6Storage to process the DHCPv4
 /// and DHCPv6 leases respectively.
 /// and DHCPv6 leases respectively.
 ///
 ///
-/// @todo Add a method which dumps all leases from the storage to a
-/// specified lease file.
 class LeaseFileLoader {
 class LeaseFileLoader {
 public:
 public:
 
 
@@ -184,7 +182,7 @@ public:
     /// @param storage A reference to the container from which leases
     /// @param storage A reference to the container from which leases
     /// should be written.
     /// should be written.
     ///
     ///
-    /// @tparam LeasePtrType A @c Lease4 or @c Lease6.
+    /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
     /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
     /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
     /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
     /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
     template<typename LeaseObjectType, typename LeaseFileType,
     template<typename LeaseObjectType, typename LeaseFileType,

+ 102 - 0
src/lib/dhcpsrv/lease_file_stats.h

@@ -0,0 +1,102 @@
+// 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 LEASE_FILE_STATS_H
+#define LEASE_FILE_STATS_H
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Provides statistics for leases.
+///
+/// This class provides a common space for statistics that we wish
+/// to keep about leases.  Currently this is for use with lease files
+/// but it may be expanded in the future.
+class LeaseFileStats {
+public:
+    /// @brief Constructor
+    ///
+    /// Initializes the stats variables to zeros
+    LeaseFileStats() {
+        clearStatistics();
+    }
+
+    /// @brief Destructor
+    ~LeaseFileStats() {
+    }
+
+    /// @brief Gets the number of attempts to read a lease
+    uint32_t getReads() const {
+        return (reads_);
+    }
+
+    /// @brief Gets the number of leases read
+    uint32_t getReadLeases() const {
+        return (read_leases_);
+    }
+
+    /// @brief Gets the number of errors when reading leases
+    uint32_t getReadErrs() const {
+        return (read_errs_);
+    }
+
+    /// @brief Gets the number of attempts to write a lease
+    uint32_t getWrites() const {
+        return (writes_);
+    }
+
+    /// @brief Gets the number of leases written
+    uint32_t getWriteLeases() const {
+        return (write_leases_);
+    }
+
+    /// @brief Gets the number of errors when writting leases
+    uint32_t getWriteErrs() const {
+        return (write_errs_);
+    }
+
+    /// @brief Clears the statistics
+    void clearStatistics() {
+        reads_        = 0;
+        read_leases_  = 0;
+        read_errs_    = 0;
+        writes_       = 0;
+        write_leases_ = 0;
+        write_errs_   = 0;
+    }
+
+protected:
+    /// @brief Number of attempts to read a lease
+    uint32_t reads_;
+
+    /// @brief Number of leases read
+    uint32_t read_leases_;
+
+    /// @brief Number of errors when reading
+    uint32_t read_errs_;
+
+    /// @brief Number of attempts to write a lease
+    uint32_t writes_;
+
+    /// @brief Number of lease written
+    uint32_t write_leases_;
+
+    /// @brief Number of errors when writing
+    uint32_t write_errs_;
+};
+
+} // namespace isc::dhcp
+} // namesapce isc
+
+#endif // LEASE_FILE_STATS_H

+ 66 - 2
src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -57,6 +57,30 @@ public:
     /// @brief Creates the lease file to be parsed by unit tests.
     /// @brief Creates the lease file to be parsed by unit tests.
     void writeSampleFile() const;
     void writeSampleFile() const;
 
 
+    /// @brief Checks the stats for the file
+    ///
+    /// This method is passed a leasefile and the values for the statistics it
+    /// should have for comparison.
+    ///
+    /// @param lease_file A reference to the file we are using
+    /// @param reads the number of attempted reads
+    /// @param read_leases the number of valid leases read
+    /// @param read_errs the number of errors while reading leases
+    /// @param writes the number of attempted writes
+    /// @param write_leases the number of leases successfully written
+    /// @param write_errs the number of errors while writing
+    void checkStats(CSVLeaseFile4& lease_file,
+                    uint32_t reads, uint32_t read_leases,
+                    uint32_t read_errs, uint32_t writes,
+                    uint32_t write_leases, uint32_t write_errs) const {
+        EXPECT_EQ(reads, lease_file.getReads());
+        EXPECT_EQ(read_leases, lease_file.getReadLeases());
+        EXPECT_EQ(read_errs, lease_file.getReadErrs());
+        EXPECT_EQ(writes, lease_file.getWrites());
+        EXPECT_EQ(write_leases, lease_file.getWriteLeases());
+        EXPECT_EQ(write_errs, lease_file.getWriteErrs());
+    }
+
     /// @brief Name of the test lease file.
     /// @brief Name of the test lease file.
     std::string filename_;
     std::string filename_;
 
 
@@ -104,10 +128,19 @@ TEST_F(CSVLeaseFile4Test, parse) {
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
 
 
+    // Verify the counters are cleared
+    {
+    SCOPED_TRACE("Check stats are empty");
+    checkStats(*lf, 0, 0, 0, 0, 0, 0);
+    }
+
     Lease4Ptr lease;
     Lease4Ptr lease;
     // Reading first read should be successful.
     // Reading first read should be successful.
+    {
+    SCOPED_TRACE("First lease valid");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
+    checkStats(*lf, 1, 1, 0, 0, 0, 0);
 
 
     // Verify that the lease attributes are correct.
     // Verify that the lease attributes are correct.
     EXPECT_EQ("192.0.2.1", lease->addr_.toText());
     EXPECT_EQ("192.0.2.1", lease->addr_.toText());
@@ -120,14 +153,23 @@ TEST_F(CSVLeaseFile4Test, parse) {
     EXPECT_TRUE(lease->fqdn_fwd_);
     EXPECT_TRUE(lease->fqdn_fwd_);
     EXPECT_TRUE(lease->fqdn_rev_);
     EXPECT_TRUE(lease->fqdn_rev_);
     EXPECT_EQ("host.example.com", lease->hostname_);
     EXPECT_EQ("host.example.com", lease->hostname_);
+    }
 
 
     // Second lease is malformed - HW address is empty.
     // Second lease is malformed - HW address is empty.
+    {
+    SCOPED_TRACE("Second lease malformed");
     EXPECT_FALSE(lf->next(lease));
     EXPECT_FALSE(lf->next(lease));
+    checkStats(*lf, 2, 1, 1, 0, 0, 0);
+    }
 
 
     // Even though parsing previous lease failed, reading the next lease should be
     // Even though parsing previous lease failed, reading the next lease should be
     // successful.
     // successful.
+    {
+    SCOPED_TRACE("Third lease valid");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
+    checkStats(*lf, 3, 2, 1, 0, 0, 0);
+
     // Verify that the third lease is correct.
     // Verify that the third lease is correct.
     EXPECT_EQ("192.0.3.15", lease->addr_.toText());
     EXPECT_EQ("192.0.3.15", lease->addr_.toText());
     HWAddr hwaddr3(*lease->hwaddr_);
     HWAddr hwaddr3(*lease->hwaddr_);
@@ -140,16 +182,24 @@ TEST_F(CSVLeaseFile4Test, parse) {
     EXPECT_FALSE(lease->fqdn_fwd_);
     EXPECT_FALSE(lease->fqdn_fwd_);
     EXPECT_FALSE(lease->fqdn_rev_);
     EXPECT_FALSE(lease->fqdn_rev_);
     EXPECT_TRUE(lease->hostname_.empty());
     EXPECT_TRUE(lease->hostname_.empty());
+    }
 
 
     // There are no more leases. Reading should cause no error, but the returned
     // There are no more leases. Reading should cause no error, but the returned
     // lease pointer should be NULL.
     // lease pointer should be NULL.
+    {
+    SCOPED_TRACE("Fifth read empty");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     EXPECT_FALSE(lease);
     EXPECT_FALSE(lease);
+    checkStats(*lf, 4, 2, 1, 0, 0, 0);
+    }
 
 
     // We should be able to do it again.
     // We should be able to do it again.
+    {
+    SCOPED_TRACE("Sixth read empty");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     EXPECT_FALSE(lease);
     EXPECT_FALSE(lease);
-
+    checkStats(*lf, 5, 2, 1, 0, 0, 0);
+    }
 }
 }
 
 
 // This test checks creation of the lease file and writing leases.
 // This test checks creation of the lease file and writing leases.
@@ -157,19 +207,33 @@ TEST_F(CSVLeaseFile4Test, recreate) {
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     ASSERT_NO_THROW(lf->recreate());
     ASSERT_NO_THROW(lf->recreate());
     ASSERT_TRUE(io_.exists());
     ASSERT_TRUE(io_.exists());
+
+    // Verify the counters are cleared
+    checkStats(*lf, 0, 0, 0, 0, 0, 0);
+
     // Create first lease, with NULL client id.
     // Create first lease, with NULL client id.
     Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
     Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
                                hwaddr0_,
                                hwaddr0_,
                                NULL, 0,
                                NULL, 0,
                                200, 50, 80, 0, 8, true, true,
                                200, 50, 80, 0, 8, true, true,
                                "host.example.com"));
                                "host.example.com"));
+    {
+    SCOPED_TRACE("First write");
     ASSERT_NO_THROW(lf->append(*lease));
     ASSERT_NO_THROW(lf->append(*lease));
+    checkStats(*lf, 0, 0, 0, 1, 1, 0);
+    }
+
     // Create second lease, with non-NULL client id.
     // Create second lease, with non-NULL client id.
     lease.reset(new Lease4(IOAddress("192.0.3.10"),
     lease.reset(new Lease4(IOAddress("192.0.3.10"),
                            hwaddr1_,
                            hwaddr1_,
                            CLIENTID0, sizeof(CLIENTID0),
                            CLIENTID0, sizeof(CLIENTID0),
                            100, 60, 90, 0, 7));
                            100, 60, 90, 0, 7));
+    {
+    SCOPED_TRACE("Second write");
     ASSERT_NO_THROW(lf->append(*lease));
     ASSERT_NO_THROW(lf->append(*lease));
+    checkStats(*lf, 0, 0, 0, 2, 2, 0);
+    }
+
     // Close the lease file.
     // Close the lease file.
     lf->close();
     lf->close();
     // Check that the contents of the csv file are correct.
     // Check that the contents of the csv file are correct.

+ 75 - 2
src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -63,6 +63,30 @@ public:
     /// @brief Create lease file that can be parsed by unit tests.
     /// @brief Create lease file that can be parsed by unit tests.
     void writeSampleFile() const;
     void writeSampleFile() const;
 
 
+    /// @brief Checks the stats for the file
+    ///
+    /// This method is passed a leasefile and the values for the statistics it
+    /// should have for comparison.
+    ///
+    /// @param lease_file A reference to the file we are using
+    /// @param reads the number of attempted reads
+    /// @param read_leases the number of valid leases read
+    /// @param read_errs the number of errors while reading leases
+    /// @param writes the number of attempted writes
+    /// @param write_leases the number of leases successfully written
+    /// @param write_errs the number of errors while writing
+    void checkStats(CSVLeaseFile6& lease_file,
+                    uint32_t reads, uint32_t read_leases,
+                    uint32_t read_errs, uint32_t writes,
+                    uint32_t write_leases, uint32_t write_errs) const {
+        EXPECT_EQ(reads, lease_file.getReads());
+        EXPECT_EQ(read_leases, lease_file.getReadLeases());
+        EXPECT_EQ(read_errs, lease_file.getReadErrs());
+        EXPECT_EQ(writes, lease_file.getWrites());
+        EXPECT_EQ(write_leases, lease_file.getWriteLeases());
+        EXPECT_EQ(write_errs, lease_file.getWriteErrs());
+    }
+
     /// @brief Name of the test lease file.
     /// @brief Name of the test lease file.
     std::string filename_;
     std::string filename_;
 
 
@@ -105,10 +129,19 @@ TEST_F(CSVLeaseFile6Test, parse) {
     boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
     boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
 
 
+    // Verify the counters are cleared
+    {
+    SCOPED_TRACE("Check stats are empty");
+    checkStats(*lf, 0, 0, 0, 0, 0, 0);
+    }
+
     Lease6Ptr lease;
     Lease6Ptr lease;
     // Reading first read should be successful.
     // Reading first read should be successful.
+    {
+    SCOPED_TRACE("First lease valid");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
+    checkStats(*lf, 1, 1, 0, 0, 0, 0);
 
 
     // Verify that the lease attributes are correct.
     // Verify that the lease attributes are correct.
     EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
     EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
@@ -124,14 +157,23 @@ TEST_F(CSVLeaseFile6Test, parse) {
     EXPECT_TRUE(lease->fqdn_fwd_);
     EXPECT_TRUE(lease->fqdn_fwd_);
     EXPECT_TRUE(lease->fqdn_rev_);
     EXPECT_TRUE(lease->fqdn_rev_);
     EXPECT_EQ("host.example.com", lease->hostname_);
     EXPECT_EQ("host.example.com", lease->hostname_);
+    }
 
 
     // Second lease is malformed - DUID is empty.
     // Second lease is malformed - DUID is empty.
+    {
+    SCOPED_TRACE("Second lease malformed");
     EXPECT_FALSE(lf->next(lease));
     EXPECT_FALSE(lf->next(lease));
+    checkStats(*lf, 2, 1, 1, 0, 0, 0);
+    }
 
 
     // Even, parsing previous lease failed, reading the next lease should be
     // Even, parsing previous lease failed, reading the next lease should be
     // successful.
     // successful.
+    {
+    SCOPED_TRACE("Third lease valid");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
+    checkStats(*lf, 3, 2, 1, 0, 0, 0);
+
     // Verify that the third lease is correct.
     // Verify that the third lease is correct.
     EXPECT_EQ("2001:db8:2::10", lease->addr_.toText());
     EXPECT_EQ("2001:db8:2::10", lease->addr_.toText());
     ASSERT_TRUE(lease->duid_);
     ASSERT_TRUE(lease->duid_);
@@ -146,10 +188,15 @@ TEST_F(CSVLeaseFile6Test, parse) {
     EXPECT_FALSE(lease->fqdn_fwd_);
     EXPECT_FALSE(lease->fqdn_fwd_);
     EXPECT_FALSE(lease->fqdn_rev_);
     EXPECT_FALSE(lease->fqdn_rev_);
     EXPECT_TRUE(lease->hostname_.empty());
     EXPECT_TRUE(lease->hostname_.empty());
+    }
 
 
     // Reading the fourth lease should be successful.
     // Reading the fourth lease should be successful.
+    {
+    SCOPED_TRACE("Fourth lease valid");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
+    checkStats(*lf, 4, 3, 1, 0, 0, 0);
+
     // Verify that the lease is correct.
     // Verify that the lease is correct.
     EXPECT_EQ("3000:1::", lease->addr_.toText());
     EXPECT_EQ("3000:1::", lease->addr_.toText());
     ASSERT_TRUE(lease->duid_);
     ASSERT_TRUE(lease->duid_);
@@ -164,16 +211,24 @@ TEST_F(CSVLeaseFile6Test, parse) {
     EXPECT_FALSE(lease->fqdn_fwd_);
     EXPECT_FALSE(lease->fqdn_fwd_);
     EXPECT_FALSE(lease->fqdn_rev_);
     EXPECT_FALSE(lease->fqdn_rev_);
     EXPECT_TRUE(lease->hostname_.empty());
     EXPECT_TRUE(lease->hostname_.empty());
+    }
 
 
     // There are no more leases. Reading should cause no error, but the returned
     // There are no more leases. Reading should cause no error, but the returned
     // lease pointer should be NULL.
     // lease pointer should be NULL.
+    {
+    SCOPED_TRACE("Fifth read empty");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     EXPECT_FALSE(lease);
     EXPECT_FALSE(lease);
+    checkStats(*lf, 5, 3, 1, 0, 0, 0);
+    }
 
 
     // We should be able to do it again.
     // We should be able to do it again.
+    {
+    SCOPED_TRACE("Sixth read empty");
     EXPECT_TRUE(lf->next(lease));
     EXPECT_TRUE(lf->next(lease));
     EXPECT_FALSE(lease);
     EXPECT_FALSE(lease);
-
+    checkStats(*lf, 6, 3, 1, 0, 0, 0);
+    }
 }
 }
 
 
 // This test checks creation of the lease file and writing leases.
 // This test checks creation of the lease file and writing leases.
@@ -182,26 +237,44 @@ TEST_F(CSVLeaseFile6Test, recreate) {
     ASSERT_NO_THROW(lf->recreate());
     ASSERT_NO_THROW(lf->recreate());
     ASSERT_TRUE(io_.exists());
     ASSERT_TRUE(io_.exists());
 
 
+    // Verify the counters are cleared
+    {
+    SCOPED_TRACE("Check stats are empty");
+    checkStats(*lf, 0, 0, 0, 0, 0, 0);
+    }
+
     Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
     Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
                                makeDUID(DUID0, sizeof(DUID0)),
                                makeDUID(DUID0, sizeof(DUID0)),
                                7, 100, 200, 50, 80, 8, true, true,
                                7, 100, 200, 50, 80, 8, true, true,
                                "host.example.com"));
                                "host.example.com"));
     lease->cltt_ = 0;
     lease->cltt_ = 0;
+    {
+    SCOPED_TRACE("First write");
     ASSERT_NO_THROW(lf->append(*lease));
     ASSERT_NO_THROW(lf->append(*lease));
+    checkStats(*lf, 0, 0, 0, 1, 1, 0);
+    }
 
 
     lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:2::10"),
     lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:2::10"),
                            makeDUID(DUID1, sizeof(DUID1)),
                            makeDUID(DUID1, sizeof(DUID1)),
                            8, 150, 300, 40, 70, 6, false, false,
                            8, 150, 300, 40, 70, 6, false, false,
                            "", HWAddrPtr(), 128));
                            "", HWAddrPtr(), 128));
     lease->cltt_ = 0;
     lease->cltt_ = 0;
+    {
+    SCOPED_TRACE("Second write");
     ASSERT_NO_THROW(lf->append(*lease));
     ASSERT_NO_THROW(lf->append(*lease));
+    checkStats(*lf, 0, 0, 0, 2, 2, 0);
+    }
 
 
     lease.reset(new Lease6(Lease::TYPE_PD, IOAddress("3000:1:1::"),
     lease.reset(new Lease6(Lease::TYPE_PD, IOAddress("3000:1:1::"),
                            makeDUID(DUID0, sizeof(DUID0)),
                            makeDUID(DUID0, sizeof(DUID0)),
                            7, 150, 300, 40, 70, 10, false, false,
                            7, 150, 300, 40, 70, 10, false, false,
                            "", HWAddrPtr(), 64));
                            "", HWAddrPtr(), 64));
     lease->cltt_ = 0;
     lease->cltt_ = 0;
+    {
+    SCOPED_TRACE("Third write");
     ASSERT_NO_THROW(lf->append(*lease));
     ASSERT_NO_THROW(lf->append(*lease));
+    checkStats(*lf, 0, 0, 0, 3, 3, 0);
+    }
 
 
     EXPECT_EQ("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
     EXPECT_EQ("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
               "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
               "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"

+ 217 - 103
src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc

@@ -86,11 +86,13 @@ public:
     /// the write was correct.  The order of the leases in the output will depend
     /// the write was correct.  The order of the leases in the output will depend
     /// on the order in which the container provides the leases.
     /// on the order in which the container provides the leases.
     ///
     ///
+    /// @param lease_file A reference to the file to write to
     /// @param storage A reference to the container to be written to the file
     /// @param storage A reference to the container to be written to the file
     /// @param compare The string to compare to what was read from the file
     /// @param compare The string to compare to what was read from the file
     ///
     ///
-    /// @tparam LeaseStorage Type of the container: @c Lease4Container
-    /// @c Lease6Container.
+    /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
+    /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
+    /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
     ///
     ///
     template<typename LeaseObjectType, typename LeaseFileType,
     template<typename LeaseObjectType, typename LeaseFileType,
              typename StorageType>
              typename StorageType>
@@ -109,12 +111,52 @@ public:
         EXPECT_EQ(compare, io_.readFile());
         EXPECT_EQ(compare, io_.readFile());
     }
     }
 
 
+    /// @brief Checks the stats for the file
+    ///
+    /// This method is passed a leasefile and the values for the statistics it
+    /// should have for comparison.
+    ///
+    /// @param lease_file A reference to the file we are using
+    /// @param reads the number of attempted reads
+    /// @param read_leases the number of valid leases read
+    /// @param read_errs the number of errors while reading leases
+    /// @param writes the number of attempted writes
+    /// @param write_leases the number of leases successfully written
+    /// @param write_errs the number of errors while writing
+    ///
+    /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
+    template<typename LeaseFileType>
+    void checkStats(LeaseFileType& lease_file,
+                    uint32_t reads, uint32_t read_leases,
+                    uint32_t read_errs, uint32_t writes,
+                    uint32_t write_leases, uint32_t write_errs) const {
+        EXPECT_EQ(reads, lease_file.getReads());
+        EXPECT_EQ(read_leases, lease_file.getReadLeases());
+        EXPECT_EQ(read_errs, lease_file.getReadErrs());
+        EXPECT_EQ(writes, lease_file.getWrites());
+        EXPECT_EQ(write_leases, lease_file.getWriteLeases());
+        EXPECT_EQ(write_errs, lease_file.getWriteErrs());
+    }
 
 
     /// @brief Name of the test lease file.
     /// @brief Name of the test lease file.
     std::string filename_;
     std::string filename_;
 
 
     /// @brief Object providing access to lease file IO.
     /// @brief Object providing access to lease file IO.
     LeaseFileIO io_;
     LeaseFileIO io_;
+
+    std::string v4_hdr_; ///< String for the header of the v4 csv test file
+    std::string v6_hdr_; ///< String for the header of the v6 csv test file
+
+protected:
+    /// @brief Sets up the header strings
+    virtual void SetUp() {
+        v4_hdr_ = "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+                  "fqdn_fwd,fqdn_rev,hostname\n";
+
+        v6_hdr_ = "address,duid,valid_lifetime,expire,subnet_id,"
+                  "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+                  "fqdn_rev,hostname,hwaddr\n";
+    }
 };
 };
 
 
 LeaseFileLoaderTest::LeaseFileLoaderTest()
 LeaseFileLoaderTest::LeaseFileLoaderTest()
@@ -135,20 +177,25 @@ LeaseFileLoaderTest::absolutePath(const std::string& filename) {
 // It also tests the write function by writing the storage to a file
 // It also tests the write function by writing the storage to a file
 // and comparing that with the expected value.
 // and comparing that with the expected value.
 TEST_F(LeaseFileLoaderTest, loadWrite4) {
 TEST_F(LeaseFileLoaderTest, loadWrite4) {
+    std::string test_str;
+    std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
+                      "200,200,8,1,1,host.example.com\n";
+    std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
+                      "200,500,8,1,1,host.example.com\n";
+
+    std::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";
+    std::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";
+
+    std::string c_1 = "192.0.2.3,,a:11:01:04,"
+                      "200,200,8,1,1,host.example.com\n";
+
     // Create lease file with leases for 192.0.2.1, 192.0.3.15. The lease
     // Create lease file with leases for 192.0.2.1, 192.0.3.15. The lease
     // entry for the 192.0.2.3 is invalid (lacks HW address) and should
     // entry for the 192.0.2.3 is invalid (lacks HW address) and should
     // be discarded.
     // be discarded.
-    io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,"
-                  "host.example.com\n"
-                  "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,100,7,"
-                  "0,0,\n"
-                  "192.0.2.3,,a:11:01:04,200,200,8,1,1,host.example.com\n"
-                  "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,135,7,"
-                  "0,0,\n"
-                  "192.0.2.1,06:07:08:09:0a:bc,,200,500,8,1,1,"
-                  "host.example.com\n");
+    test_str = v4_hdr_ + a_1 + b_1 + c_1 + b_2 + a_2;
+    io_.writeFile(test_str);
 
 
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
@@ -157,6 +204,12 @@ TEST_F(LeaseFileLoaderTest, loadWrite4) {
     Lease4Storage storage;
     Lease4Storage storage;
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10));
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10));
 
 
+    // We should have made 6 attempts to read, with 4 leases read and 1 error
+    {
+    SCOPED_TRACE("Read leases");
+    checkStats(*lf, 6, 4, 1, 0, 0, 0);
+    }
+
     // There are two unique leases.
     // There are two unique leases.
     ASSERT_EQ(2, storage.size());
     ASSERT_EQ(2, storage.size());
 
 
@@ -178,14 +231,14 @@ TEST_F(LeaseFileLoaderTest, loadWrite4) {
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
     EXPECT_EQ(35, lease->cltt_);
     EXPECT_EQ(35, lease->cltt_);
 
 
-    writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>
-        (*lf, storage,
-         "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-         "fqdn_fwd,fqdn_rev,hostname\n"
-         "192.0.2.1,06:07:08:09:0a:bc,,200,500,8,1,1,"
-         "host.example.com\n"
-         "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,135,7,"
-         "0,0,\n");
+    test_str = v4_hdr_ + a_2 + b_2;
+    writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str);
+
+    // We should have made 2 attempts to write, with 2 leases written and 0 errors
+    {
+    SCOPED_TRACE("Write leases");
+    checkStats(*lf, 0, 0, 0, 2, 2, 0);
+    }
 }
 }
 
 
 // This test verifies that the lease with a valid lifetime of 0
 // This test verifies that the lease with a valid lifetime of 0
@@ -195,19 +248,23 @@ TEST_F(LeaseFileLoaderTest, loadWrite4) {
 // It also tests the write function by writing the storage to a file
 // It also tests the write function by writing the storage to a file
 // and comparing that with the expected value.
 // and comparing that with the expected value.
 TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) {
 TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) {
+    std::string test_str;
+    std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
+                      "200,200,8,1,1,host.example.com\n";
+    std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
+                      "0,500,8,1,1,host.example.com\n";
+
+    std::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";
+    std::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";
+
+
     // Create lease file in which one of the entries for 192.0.2.1
     // Create lease file in which one of the entries for 192.0.2.1
     // has a valid_lifetime of 0 and results in the deletion of the
     // has a valid_lifetime of 0 and results in the deletion of the
     // lease.
     // lease.
-    io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,"
-                  "host.example.com\n"
-                  "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,100,7,"
-                  "0,0,\n"
-                  "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,135,7,"
-                  "0,0,\n"
-                  "192.0.2.1,06:07:08:09:0a:bc,,0,500,8,1,1,"
-                  "host.example.com\n");
+    test_str = v4_hdr_ + a_1 + b_1 + b_2 + a_2;
+    io_.writeFile(test_str);
 
 
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
@@ -215,6 +272,12 @@ TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) {
     Lease4Storage storage;
     Lease4Storage storage;
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10));
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10));
 
 
+    // We should have made 5 attempts to read, with 4 leases read and 0 error
+    {
+    SCOPED_TRACE("Read leases");
+    checkStats(*lf, 5, 4, 0, 0, 0, 0);
+    }
+
     // There should only be one lease. The one with the valid_lifetime
     // There should only be one lease. The one with the valid_lifetime
     // of 0 should be removed.
     // of 0 should be removed.
     ASSERT_EQ(1, storage.size());
     ASSERT_EQ(1, storage.size());
@@ -223,12 +286,14 @@ TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) {
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
     EXPECT_EQ(35, lease->cltt_);
     EXPECT_EQ(35, lease->cltt_);
 
 
-    writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>
-        (*lf, storage,
-         "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-         "fqdn_fwd,fqdn_rev,hostname\n"
-         "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,135,7,"
-         "0,0,\n");
+    test_str = v4_hdr_ + b_2;
+    writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str);
+
+    // We should have made 1 attempts to write, with 1 leases written and 0 errors
+    {
+    SCOPED_TRACE("Write leases");
+    checkStats(*lf, 0, 0, 0, 1, 1, 0);
+    }
 }
 }
 
 
 // This test verifies that the DHCPv6 leases can be loaded from the lease
 // This test verifies that the DHCPv6 leases can be loaded from the lease
@@ -238,22 +303,28 @@ TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) {
 // It also tests the write function by writing the storage to a file
 // It also tests the write function by writing the storage to a file
 // and comparing that with the expected value.
 // and comparing that with the expected value.
 TEST_F(LeaseFileLoaderTest, loadWrite6) {
 TEST_F(LeaseFileLoaderTest, loadWrite6) {
+    std::string test_str;
+    std::string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+                      "200,200,8,100,0,7,0,1,1,host.example.com,\n";
+    std::string a_2 = "2001:db8:1::1,,"
+                      "200,200,8,100,0,7,0,1,1,host.example.com,\n";
+    std::string a_3 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+                      "200,400,8,100,0,7,0,1,1,host.example.com,\n";
+
+    std::string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+                      "300,300,6,150,0,8,0,0,0,,\n";
+    std::string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+                      "300,800,6,150,0,8,0,0,0,,\n";
+
+    std::string c_1 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+                      "100,200,8,0,2,16,64,0,0,,\n";
+
+
+
     // Create a lease file with three valid leases: 2001:db8:1::1,
     // Create a lease file with three valid leases: 2001:db8:1::1,
     // 3000:1:: and 2001:db8:2::10.
     // 3000:1:: and 2001:db8:2::10.
-    io_.writeFile("address,duid,valid_lifetime,expire,subnet_id,"
-                  "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-                  "fqdn_rev,hostname,hwaddr\n"
-                  "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                  "200,200,8,100,0,7,0,1,1,host.example.com,\n"
-                  "2001:db8:1::1,,200,200,8,100,0,7,0,1,1,host.example.com,\n"
-                  "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,300,6,150,"
-                  "0,8,0,0,0,,\n"
-                  "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,100,200,8,0,2,"
-                  "16,64,0,0,,\n"
-                  "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,800,6,150,"
-                  "0,8,0,0,0,,\n"
-                  "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                  "200,400,8,100,0,7,0,1,1,host.example.com,\n");
+    test_str = v6_hdr_ + a_1 + a_2 + b_1 + c_1 + b_2 + a_3;
+    io_.writeFile(test_str);
 
 
     boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
     boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
@@ -262,6 +333,12 @@ TEST_F(LeaseFileLoaderTest, loadWrite6) {
     Lease6Storage storage;
     Lease6Storage storage;
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10));
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10));
 
 
+    // We should have made 7 attempts to read, with 5 leases read and 1 error
+    {
+    SCOPED_TRACE("Read leases");
+    checkStats(*lf, 7, 5, 1, 0, 0, 0);
+    }
+
     // There should be 3 unique leases.
     // There should be 3 unique leases.
     ASSERT_EQ(3, storage.size());
     ASSERT_EQ(3, storage.size());
 
 
@@ -283,17 +360,14 @@ TEST_F(LeaseFileLoaderTest, loadWrite6) {
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
     EXPECT_EQ(500, lease->cltt_);
     EXPECT_EQ(500, lease->cltt_);
 
 
-    writeLeases<Lease6, CSVLeaseFile6, Lease6Storage>
-        (*lf, storage,
-         "address,duid,valid_lifetime,expire,subnet_id,"
-         "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-         "fqdn_rev,hostname,hwaddr\n"
-         "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-         "200,400,8,100,0,7,0,1,1,host.example.com,\n"
-         "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,800,6,150,"
-         "0,8,0,0,0,,\n"
-         "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,100,200,8,0,2,"
-         "16,64,0,0,,\n");
+    test_str = v6_hdr_ + a_3 + b_2 + c_1;
+    writeLeases<Lease6, CSVLeaseFile6, Lease6Storage>(*lf, storage, test_str);
+
+    // We should have made 3 attempts to write, with 3 leases written and 0 errors
+    {
+    SCOPED_TRACE("Write leases");
+    checkStats(*lf, 0, 0, 0, 3, 3, 0);
+    }
 }
 }
 
 
 // This test verifies that the lease with a valid lifetime of 0
 // This test verifies that the lease with a valid lifetime of 0
@@ -303,20 +377,22 @@ TEST_F(LeaseFileLoaderTest, loadWrite6) {
 // It also tests the write function by writing the storage to a file
 // It also tests the write function by writing the storage to a file
 // and comparing that with the expected value.
 // and comparing that with the expected value.
 TEST_F(LeaseFileLoaderTest, loadWrite6LeaseRemove) {
 TEST_F(LeaseFileLoaderTest, loadWrite6LeaseRemove) {
+    std::string test_str;
+    std::string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+                      "200,200,8,100,0,7,0,1,1,host.example.com,\n";
+    std::string a_2 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+                      "0,400,8,100,0,7,0,1,1,host.example.com,\n";
+
+    std::string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+                      "300,300,6,150,0,8,0,0,0,,\n";
+    std::string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+                      "300,800,6,150,0,8,0,0,0,,\n";
+
     // Create lease file in which one of the entries for the 2001:db8:1::1
     // Create lease file in which one of the entries for the 2001:db8:1::1
     // has valid lifetime set to 0, in which case the lease should be
     // has valid lifetime set to 0, in which case the lease should be
     // deleted.
     // deleted.
-    io_.writeFile("address,duid,valid_lifetime,expire,subnet_id,"
-                  "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-                  "fqdn_rev,hostname,hwaddr\n"
-                  "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                  "200,200,8,100,0,7,0,1,1,host.example.com,\n"
-                  "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,300,6,150,"
-                  "0,8,0,0,0,,\n"
-                  "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,800,6,150,"
-                  "0,8,0,0,0,,\n"
-                  "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                  "0,400,8,100,0,7,0,1,1,host.example.com,\n");
+    test_str = v6_hdr_ + a_1 + b_1 + b_2 + a_2;
+    io_.writeFile(test_str);
 
 
     boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
     boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
@@ -325,6 +401,12 @@ TEST_F(LeaseFileLoaderTest, loadWrite6LeaseRemove) {
     Lease6Storage storage;
     Lease6Storage storage;
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10));
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10));
 
 
+    // We should have made 5 attempts to read, with 4 leases read and 0 error
+    {
+    SCOPED_TRACE("Read leases");
+    checkStats(*lf, 5, 4, 0, 0, 0, 0);
+    }
+
     // There should be only one lease for 2001:db8:2::10. The other one
     // There should be only one lease for 2001:db8:2::10. The other one
     // should have been deleted (or rather not loaded).
     // should have been deleted (or rather not loaded).
     ASSERT_EQ(1, storage.size());
     ASSERT_EQ(1, storage.size());
@@ -333,32 +415,34 @@ TEST_F(LeaseFileLoaderTest, loadWrite6LeaseRemove) {
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
     EXPECT_EQ(500, lease->cltt_);
     EXPECT_EQ(500, lease->cltt_);
 
 
-    writeLeases<Lease6, CSVLeaseFile6, Lease6Storage>
-        (*lf, storage,
-         "address,duid,valid_lifetime,expire,subnet_id,"
-         "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-         "fqdn_rev,hostname,hwaddr\n"
-         "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,800,6,150,"
-         "0,8,0,0,0,,\n");
+    test_str = v6_hdr_ + b_2;
+    writeLeases<Lease6, CSVLeaseFile6, Lease6Storage>(*lf, storage, test_str);
+
+    // We should have made 1 attempts to write, with 1 leases written and 0 errors
+    {
+    SCOPED_TRACE("Write leases");
+    checkStats(*lf, 0, 0, 0, 1, 1, 0);
+    }
 }
 }
 
 
 // This test verifies that the exception is thrown when the specific
 // This test verifies that the exception is thrown when the specific
 // number of errors in the test data occur during reading of the lease
 // number of errors in the test data occur during reading of the lease
 // file.
 // file.
 TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
 TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
+    std::string test_str;
+    std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
+                      "200,200,8,1,1,host.example.com\n";
+    std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
+                      "200,500,8,1,1,host.example.com\n";
+
+    std::string b_1 = "192.0.2.3,,a:11:01:04,200,200,8,1,1,host.example.com\n";
+
+    std::string c_1 = "192.0.2.10,01:02:03:04:05:06,,200,300,8,1,1,\n";
+
     // Create a lease file for which there is a number of invalid
     // Create a lease file for which there is a number of invalid
-    // entries.
-    io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,"
-                  "host.example.com\n"
-                  "192.0.2.3,,a:11:01:04,200,200,8,1,1,host.example.com\n"
-                  "192.0.2.3,,a:11:01:04,200,200,8,1,1,host.example.com\n"
-                  "192.0.2.10,01:02:03:04:05:06,,200,300,8,1,1,,\n"
-                  "192.0.2.3,,a:11:01:04,200,200,8,1,1,host.example.com\n"
-                  "192.0.2.3,,a:11:01:04,200,200,8,1,1,host.example.com\n"
-                  "192.0.2.1,06:07:08:09:0a:bc,,200,500,8,1,1,"
-                  "host.example.com\n");
+    // entries.  b_1 is invalid and gets used multiple times.
+    test_str = v4_hdr_ + a_1 + b_1 + b_1 + c_1 + b_1 + b_1 + a_2;
+    io_.writeFile(test_str);
 
 
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
@@ -369,6 +453,12 @@ TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
     ASSERT_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 3),
     ASSERT_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 3),
                  util::CSVFileError);
                  util::CSVFileError);
 
 
+    // We should have made 6 attempts to read, with 2 leases read and 4 error
+    {
+    SCOPED_TRACE("Read leases 1");
+    checkStats(*lf, 6, 2, 4, 0, 0, 0);
+    }
+
     lf->close();
     lf->close();
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
 
 
@@ -377,6 +467,12 @@ TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
     storage.clear();
     storage.clear();
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 4));
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 4));
 
 
+    // We should have made 8 attempts to read, with 3 leases read and 4 error
+    {
+    SCOPED_TRACE("Read leases 2");
+    checkStats(*lf, 8, 3, 4, 0, 0, 0);
+    }
+
     ASSERT_EQ(2, storage.size());
     ASSERT_EQ(2, storage.size());
 
 
     Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
     Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
@@ -386,6 +482,15 @@ TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
     lease = getLease<Lease4Ptr>("192.0.2.10", storage);
     lease = getLease<Lease4Ptr>("192.0.2.10", storage);
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
     EXPECT_EQ(100, lease->cltt_);
     EXPECT_EQ(100, lease->cltt_);
+
+    test_str = v4_hdr_ + a_2 + c_1;
+    writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str);
+
+    // We should have made 1 attempts to write, with 1 leases written and 0 errors
+    {
+    SCOPED_TRACE("Write leases");
+    checkStats(*lf, 0, 0, 0, 2, 2, 0);
+    }
 }
 }
 
 
 // This test verifies that the lease with a valid lifetime set to 0 is
 // This test verifies that the lease with a valid lifetime set to 0 is
@@ -395,11 +500,13 @@ TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
 // It also tests the write function by writing the storage to a file
 // It also tests the write function by writing the storage to a file
 // and comparing that with the expected value.
 // and comparing that with the expected value.
 TEST_F(LeaseFileLoaderTest, loadWriteLeaseWithZeroLifetime) {
 TEST_F(LeaseFileLoaderTest, loadWriteLeaseWithZeroLifetime) {
+    std::string test_str;
+    std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,\n";
+    std::string b_2 = "192.0.2.3,06:07:08:09:0a:bd,,0,200,8,1,1,\n";
+
     // Create lease file. The second lease has a valid lifetime of 0.
     // Create lease file. The second lease has a valid lifetime of 0.
-    io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,,\n"
-                  "192.0.2.3,06:07:08:09:0a:bd,,0,200,8,1,1,,\n");
+    test_str = v4_hdr_ + a_1 + b_2;
+    io_.writeFile(test_str);
 
 
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
     ASSERT_NO_THROW(lf->open());
     ASSERT_NO_THROW(lf->open());
@@ -409,6 +516,12 @@ TEST_F(LeaseFileLoaderTest, loadWriteLeaseWithZeroLifetime) {
     Lease4Storage storage;
     Lease4Storage storage;
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 0));
     ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 0));
 
 
+    // We should have made 3 attempts to read, with 2 leases read and 0 error
+    {
+    SCOPED_TRACE("Read leases");
+    checkStats(*lf, 3, 2, 0, 0, 0, 0);
+    }
+
     // The first lease should be present.
     // The first lease should be present.
     Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
     Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
@@ -417,12 +530,13 @@ TEST_F(LeaseFileLoaderTest, loadWriteLeaseWithZeroLifetime) {
     // The lease with a valid lifetime of 0 should not be loaded.
     // The lease with a valid lifetime of 0 should not be loaded.
     EXPECT_FALSE(getLease<Lease4Ptr>("192.0.2.3", storage));
     EXPECT_FALSE(getLease<Lease4Ptr>("192.0.2.3", storage));
 
 
-    writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>
-        (*lf, storage,
-         "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-         "fqdn_fwd,fqdn_rev,hostname\n"
-         "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,\n");
-}
-
+    test_str = v4_hdr_ + a_1;
+    writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str);
 
 
+    // We should have made 1 attempts to write, with 1 leases written and 0 errors
+    {
+    SCOPED_TRACE("Write leases");
+    checkStats(*lf, 0, 0, 0, 1, 1, 0);
+    }
+}
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 25 - 19
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -456,17 +456,19 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup4) {
         "192.0.2.45,00:00:00:00:00:00,,100,100,1,0,0,\n";
         "192.0.2.45,00:00:00:00:00:00,,100,100,1,0,0,\n";
     EXPECT_EQ(updated_file_contents, current_file.readFile());
     EXPECT_EQ(updated_file_contents, current_file.readFile());
 
 
-    /// @todo Replace the following with the checks that the LFC has
-    /// completed successfully, i.e. the leasefile4_0.csv.2 exists
-    /// and it holds the cleaned up lease information.
-
-    // Until the kea-lfc is implemented and performs the cleanup, we can
-    // only check that the backend has moved the lease file to a lease
-    // file with suffix ".1".
-    LeaseFileIO input_file(getLeaseFilePath("leasefile4_0.csv.1"), false);
+    // This string contains the contents of the lease file we
+    // expect after the LFC run.  It has two leases with one
+    // entry each.
+    std::string result_file_contents = new_file_contents +
+        "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,\n"
+        "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,\n";
+
+    // The LFC should have created a file with the two leases and moved it
+    // to leasefile4_0.csv.2
+    LeaseFileIO input_file(getLeaseFilePath("leasefile4_0.csv.2"), false);
     ASSERT_TRUE(input_file.exists());
     ASSERT_TRUE(input_file.exists());
-    // And this file should contain the contents of the original file.
-    EXPECT_EQ(current_file_contents, input_file.readFile());
+    // And this file should contain the contents of the result file.
+    EXPECT_EQ(result_file_contents, input_file.readFile());
 }
 }
 
 
 // This test that the callback function executing the cleanup of the
 // This test that the callback function executing the cleanup of the
@@ -537,17 +539,21 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup6) {
         "400,2,300,0,123,128,0,0,,\n";
         "400,2,300,0,123,128,0,0,,\n";
     EXPECT_EQ(update_file_contents, current_file.readFile());
     EXPECT_EQ(update_file_contents, current_file.readFile());
 
 
-    /// @todo Replace the following with the checks that the LFC has
-    /// completed successfully, i.e. the leasefile6_0.csv.2 exists
-    /// and it holds the cleaned up lease information.
+    // This string contains the contents of the lease file we
+    // expect after the LFC run.  It has two leases with one
+    // entry each.
+    std::string result_file_contents = new_file_contents +
+        "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
+        "8,100,0,7,0,1,1,,\n"
+        "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
+        "8,100,0,7,0,1,1,,\n";
 
 
-    // Until the kea-lfc is implemented and performs the cleanup, we can
-    // only check that the backend has moved the lease file to a lease
-    // file with suffix ".1".
-    LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.1"), false);
+    // The LFC should have created a file with the two leases and moved it
+    // to leasefile6_0.csv.2
+    LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.2"), false);
     ASSERT_TRUE(input_file.exists());
     ASSERT_TRUE(input_file.exists());
-    // And this file should contain the contents of the original file.
-    EXPECT_EQ(current_file_contents, input_file.readFile());
+    // And this file should contain the contents of the result file.
+    EXPECT_EQ(result_file_contents, input_file.readFile());
 }
 }
 
 
 // This test verifies that EXIT_FAILURE status code is returned when
 // This test verifies that EXIT_FAILURE status code is returned when

+ 2 - 1
src/lib/util/csv_file.h

@@ -395,7 +395,8 @@ public:
     /// output file pointer should be set at the end of file.
     /// output file pointer should be set at the end of file.
     ///
     ///
     /// @throw CSVFileError when IO operation fails.
     /// @throw CSVFileError when IO operation fails.
-    void open(const bool seek_to_end = false);
+
+    virtual void open(const bool seek_to_end = false);
 
 
     /// @brief Creates a new CSV file.
     /// @brief Creates a new CSV file.
     ///
     ///