Browse Source

[trac3665] Dump leases from storage to file and use this in LFC

Add the write method to lease_file_loader to dump the leaes files
from a storage container into a file.

Update the LFC code to use the lease_file_loader functions to
load the leases from the appropriate files and then to write
them to the output file and finally to move the file around.
Shawn Routhier 10 years ago
parent
commit
3e0491cba9

+ 50 - 2
src/bin/lfc/lfc_controller.cc

@@ -15,7 +15,15 @@
 #include <lfc/lfc_controller.h>
 #include <util/pid_file.h>
 #include <exceptions/exceptions.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_file_loader.h>
 #include <config.h>
+
+#include <boost/shared_ptr.hpp>
+
 #include <iostream>
 #include <sstream>
 #include <unistd.h>
@@ -24,6 +32,7 @@
 
 using namespace std;
 using namespace isc::util;
+using namespace isc::dhcp;
 
 namespace isc {
 namespace lfc {
@@ -35,6 +44,9 @@ 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 read the leases from the lease 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_("") {
@@ -77,13 +89,22 @@ LFCController::launch(int argc, char* argv[]) {
     }
 
     // do other work (TBD)
-    std::cerr << "Add code to perform lease cleanup" << std::endl;
     // If we don't have a finish file do the processing
+    if (access(finish_file_.c_str(), F_OK) == -1) {
+        std::cerr << "LFC Processing files" << std::endl;
+
+	if (protocol_version_ == 4) {
+	    processLeases<Lease4, CSVLeaseFile4, Lease4Storage>();
+	} else {
+	    processLeases<Lease6, CSVLeaseFile6, Lease6Storage>();
+	}
+    }
 
     // We either already had a finish file or just created one, do the
     // file cleanup, we don't want to return after the catch as we
     // still need to cleanup the pid file
     try {
+        std::cerr << "LFC cleaning files" << std::endl;
         fileCleanup();
     } catch (const RunTimeFail& run_ex) {
         std::cerr << run_ex.what() << std::endl;
@@ -239,7 +260,7 @@ LFCController::parseArgs(int argc, char* argv[]) {
                 << "Previous or ex lease file: " << previous_file_ << std::endl
                 << "Copy lease file:           " << copy_file_ << std::endl
                 << "Output lease file:         " << output_file_ << std::endl
-                << "Finishn file:              " << finish_file_ << std::endl
+                << "Finish file:               " << finish_file_ << std::endl
                 << "Config file:               " << config_file_ << std::endl
                 << "PID file:                  " << pid_file_ << std::endl;
     }
@@ -279,8 +300,35 @@ LFCController::getVersion(const bool extended) const{
     return (version_stream.str());
 }
 
+template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
 void
 LFCController::processLeases() const {
+    LeaseFileType lfPrev(previous_file_.c_str());
+    LeaseFileType lfCopy(copy_file_.c_str());
+    LeaseFileType lfOutput(output_file_.c_str());
+    StorageType storage;
+    storage.clear();
+
+    // If a previous file exists read the entries into storage
+    if (lfPrev.exists()) {
+        LeaseFileLoader::load<LeaseObjectType>(lfPrev, storage,
+					       MAX_LEASE_ERRORS);
+    }
+
+    // Follow that with the copy of the current lease file 
+    if (lfCopy.exists()) {
+        LeaseFileLoader::load<LeaseObjectType>(lfCopy, storage,
+					       MAX_LEASE_ERRORS);
+    }
+
+    // Write the result out to the output file
+    LeaseFileLoader::write<LeaseObjectType>(lfOutput, 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)
+        isc_throw(RunTimeFail, "Unable to move output (" << output_file_
+                  << ") to complete (" << finish_file_
+                  << ") error: " << strerror(errno));
 }
 
 void

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

@@ -158,6 +158,7 @@ public:
     /// @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.
+    template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
     void processLeases() const;
 
     /// @brief Cleanup files.  After we have a finish file, either from

+ 224 - 56
src/bin/lfc/tests/lfc_controller_unittests.cc

@@ -22,24 +22,25 @@ using namespace std;
 
 namespace {
 
-// Filenames used for testing.
-const char* PREVIOUS = "lease_file.2";
-const char* COPY   = "lease_file.1";
-const char* FINISH = "lease_file.completed";
-const char* OUTPUT = "lease_file.output";
-const char* PID    = "lease_file.pid";
-
 class LFCControllerTest : public ::testing::Test {
 public:
-    string pstr_;
-    string cstr_;
-    string fstr_;
-    string ostr_;
-    string istr_;
+    
+    string pstr_; ///< String for name for pid file
+    string xstr_; ///< String for name for previous file
+    string istr_; ///< String for name for copy file
+    string ostr_; ///< String for name for output file
+    string fstr_; ///< String for name for finish file
+    string cstr_; ///< String for name for config file
 
     /// @brief Create a file and write the filename into it.
     void touchFile(const std::string& filename, int);
 
+    /// @brief Create a file and write the given string into it.
+    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;
+
     /// @brief check the file to see if i matches what was written to it.
     bool checkFile(const std::string& filename, int);
 
@@ -48,26 +49,13 @@ protected:
     /// files before the test
     virtual void SetUp() {
         // set up the test files we need
-        std::ostringstream prev_str;
-        std::ostringstream copy_str;
-        std::ostringstream fin_str;
-        std::ostringstream out_str;
-        std::ostringstream pid_str;
-
-        prev_str << TEST_DATA_BUILDDIR << "/" << PREVIOUS;
-        pstr_ = prev_str.str();
-
-        copy_str << TEST_DATA_BUILDDIR << "/" << COPY;
-        cstr_ = copy_str.str();
-
-        fin_str << TEST_DATA_BUILDDIR << "/" << FINISH;
-        fstr_ = fin_str.str();
-
-        out_str << TEST_DATA_BUILDDIR << "/" << OUTPUT;
-        ostr_ = out_str.str();
-
-        pid_str << TEST_DATA_BUILDDIR << "/" << PID;
-        istr_ = pid_str.str();
+        string baseDir = TEST_DATA_BUILDDIR;
+	pstr_ = baseDir + "/" + "lease_file." + "pid";        // pid
+	xstr_ = baseDir + "/" + "lease_file." + "2";          // previous
+	istr_ = baseDir + "/" + "lease_file." + "1";          // copy
+	ostr_ = baseDir + "/" + "lease_file." + "output";     // output
+	fstr_ = baseDir + "/" + "lease_file." + "completed";  // finish
+	cstr_ = baseDir + "/" + "config_file";                // config
 
         // and remove any outstanding test files
         removeTestFile();
@@ -83,10 +71,10 @@ private:
     /// @brief Removes any remaining test files
     void removeTestFile() const {
         remove(pstr_.c_str());
-        remove(cstr_.c_str());
-        remove(fstr_.c_str());
-        remove(ostr_.c_str());
+        remove(xstr_.c_str());
         remove(istr_.c_str());
+        remove(ostr_.c_str());
+        remove(fstr_.c_str());
     }
 
 };
@@ -98,6 +86,29 @@ LFCControllerTest::touchFile(const std::string& filename, int i) {
     fs.open(filename, std::ofstream::out);
     fs << i << std::endl;
     fs.close();
+    
+}
+
+std::string
+LFCControllerTest::readFile(const std::string& filename) const {
+    std::ifstream fs;
+
+    fs.open(filename, std::ifstream::in);
+    std::string contents((std::istreambuf_iterator<char>(fs)),
+			 std::istreambuf_iterator<char>());
+    fs.close();
+    return (contents);
+}
+
+void
+LFCControllerTest::writeFile(const std::string& filename,
+			     const std::string& contents) const {
+    std::ofstream fs(filename, std::ofstream::out);
+
+    if (fs.is_open()) {
+        fs << contents;
+	fs.close();
+    }
 }
 
 bool
@@ -261,24 +272,26 @@ TEST_F(LFCControllerTest, fileCleanup) {
     LFCController lfc_controller, lfc_controller_launch;
 
     // We can use the same arguments and controller for all of the tests
-    // as the files get redone for each subtest.
+    // as the files get redone for each subtest.  We leave "-d" in the arg
+    // list but don't pass it as we use 14 as the argument count.  This
+    // makes it easy to turn it on by simply increasing argc below to 15
     char* argv[] = { const_cast<char*>("progName"),
                      const_cast<char*>("-4"),
                      const_cast<char*>("-x"),
-                     const_cast<char*>(pstr_.c_str()),
+                     const_cast<char*>(xstr_.c_str()),
                      const_cast<char*>("-i"),
-                     const_cast<char*>(cstr_.c_str()),
+                     const_cast<char*>(istr_.c_str()),
                      const_cast<char*>("-o"),
                      const_cast<char*>(ostr_.c_str()),
                      const_cast<char*>("-c"),
-                     const_cast<char*>("config"),
+                     const_cast<char*>(cstr_.c_str()),
                      const_cast<char*>("-f"),
                      const_cast<char*>(fstr_.c_str()),
                      const_cast<char*>("-p"),
-                     const_cast<char*>(istr_.c_str()),
+                     const_cast<char*>(pstr_.c_str()),
                      const_cast<char*>("-d")
     };
-    int argc = 15;
+    int argc = 14;
     lfc_controller.parseArgs(argc, argv);
 
     // Test 1: Start with no files - we expect an execption as there
@@ -288,41 +301,41 @@ TEST_F(LFCControllerTest, fileCleanup) {
 
     // 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.
-    touchFile(pstr_.c_str(), 1);
-    touchFile(cstr_.c_str(), 2);
+    touchFile(xstr_.c_str(), 1);
+    touchFile(istr_.c_str(), 2);
     touchFile(fstr_.c_str(), 3);
 
     lfc_controller.fileCleanup();
 
     // verify finish is now previous and copy and finish are gone
-    EXPECT_TRUE(checkFile(pstr_.c_str(), 3));
-    EXPECT_TRUE((remove(cstr_.c_str()) != 0) && (errno == ENOENT));
+    EXPECT_TRUE(checkFile(xstr_.c_str(), 3));
+    EXPECT_TRUE((remove(istr_.c_str()) != 0) && (errno == ENOENT));
     EXPECT_TRUE((remove(fstr_.c_str()) != 0) && (errno == ENOENT));
     remove(pstr_.c_str());
 
 
     // Test 3: Create a file for previous and finish but not copy.
-    touchFile(pstr_.c_str(), 4);
+    touchFile(xstr_.c_str(), 4);
     touchFile(fstr_.c_str(), 6);
 
     lfc_controller.fileCleanup();
 
     // verify finish is now previous and copy and finish are gone
-    EXPECT_TRUE(checkFile(pstr_.c_str(), 6));
-    EXPECT_TRUE((remove(cstr_.c_str()) != 0) && (errno == ENOENT));
+    EXPECT_TRUE(checkFile(xstr_.c_str(), 6));
+    EXPECT_TRUE((remove(istr_.c_str()) != 0) && (errno == ENOENT));
     EXPECT_TRUE((remove(fstr_.c_str()) != 0) && (errno == ENOENT));
     remove(pstr_.c_str());
 
 
     // Test 4: Create a file for copy and finish but not previous.
-    touchFile(cstr_.c_str(), 8);
+    touchFile(istr_.c_str(), 8);
     touchFile(fstr_.c_str(), 9);
 
     lfc_controller.fileCleanup();
 
     // verify finish is now previous and copy and finish are gone
-    EXPECT_TRUE(checkFile(pstr_.c_str(), 9));
-    EXPECT_TRUE((remove(cstr_.c_str()) != 0) && (errno == ENOENT));
+    EXPECT_TRUE(checkFile(xstr_.c_str(), 9));
+    EXPECT_TRUE((remove(istr_.c_str()) != 0) && (errno == ENOENT));
     EXPECT_TRUE((remove(fstr_.c_str()) != 0) && (errno == ENOENT));
     remove(pstr_.c_str());
 
@@ -330,19 +343,174 @@ TEST_F(LFCControllerTest, fileCleanup) {
     // 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
-    touchFile(pstr_.c_str(), 10);
-    touchFile(cstr_.c_str(), 11);
+    touchFile(xstr_.c_str(), 10);
+    touchFile(istr_.c_str(), 11);
     touchFile(fstr_.c_str(), 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_TRUE(checkFile(pstr_.c_str(), 12));
-    EXPECT_TRUE((remove(cstr_.c_str()) != 0) && (errno == ENOENT));
-    EXPECT_TRUE((remove(fstr_.c_str()) != 0) && (errno == ENOENT));
+    EXPECT_TRUE(checkFile(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(pstr_.c_str());
 }
 
+/// @brief Verify that we properly combine and clean up files
+/// 
+/// This is mostly a retest as we already test that the loader and
+/// writer functions work in their own tests but we combine it all
+/// here.  This is the v4 version
+
+TEST_F(LFCControllerTest, programLaunch4) {
+    LFCController lfc_controller;
+
+    // We can use the same arguments and controller for all of the tests
+    // as the files get redone for each subtest.
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-4"),
+                     const_cast<char*>("-x"),
+                     const_cast<char*>(xstr_.c_str()),
+                     const_cast<char*>("-i"),
+                     const_cast<char*>(istr_.c_str()),
+                     const_cast<char*>("-o"),
+                     const_cast<char*>(ostr_.c_str()),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>(cstr_.c_str()),
+                     const_cast<char*>("-f"),
+                     const_cast<char*>(fstr_.c_str()),
+                     const_cast<char*>("-p"),
+                     const_cast<char*>(pstr_.c_str()),
+                     const_cast<char*>("-d")
+    };
+    int argc = 14;
+    lfc_controller.parseArgs(argc, argv);
+
+    // Create the test previous file
+    writeFile(xstr_.c_str(),
+	      "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"
+	      "192.0.2.5,06:07:08:09:0a:bc,,200,200,8,1,1,"
+	      "host.example.com\n");
+
+
+
+    // Create the test copy file
+    writeFile(istr_.c_str(),
+	      "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,800,8,1,1,"
+	      "host.example.com\n"
+	      "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,150,7,"
+	      "0,0,\n"
+	      "192.0.2.5,06:07:08:09:0a:bc,,200,0,8,1,1,"
+	      "host.example.com\n");
+
+    // Run the cleanup
+    lfc_controller.launch(argc, argv);
+
+    // Compare the results
+    EXPECT_EQ(readFile(xstr_.c_str()),
+	      "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,800,8,1,1,"
+	      "host.example.com\n"
+	      "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,150,7,"
+	      "0,0,\n");
+
+	      
+
+}
+
+/// @brief Verify that we properly combine and clean up files
+/// 
+/// This is mostly a retest as we already test that the loader and
+/// writer functions work in their own tests but we combine it all
+/// here.  This is the v6 version
+
+TEST_F(LFCControllerTest, programLaunch6) {
+    LFCController lfc_controller;
+
+    // We can use the same arguments and controller for all of the tests
+    // as the files get redone for each subtest.
+    char* argv[] = { const_cast<char*>("progName"),
+                     const_cast<char*>("-6"),
+                     const_cast<char*>("-x"),
+                     const_cast<char*>(xstr_.c_str()),
+                     const_cast<char*>("-i"),
+                     const_cast<char*>(istr_.c_str()),
+                     const_cast<char*>("-o"),
+                     const_cast<char*>(ostr_.c_str()),
+                     const_cast<char*>("-c"),
+                     const_cast<char*>(cstr_.c_str()),
+                     const_cast<char*>("-f"),
+                     const_cast<char*>(fstr_.c_str()),
+                     const_cast<char*>("-p"),
+                     const_cast<char*>(pstr_.c_str()),
+                     const_cast<char*>("-d")
+    };
+    int argc = 14;
+    lfc_controller.parseArgs(argc, argv);
+
+    // Create the test previous file
+    writeFile(xstr_.c_str(),
+	      "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"
+	      );
+
+    // Create the test copy file
+    writeFile(istr_.c_str(),
+	      "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,1000,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,200,8,100,0,7,0,1,1,host.example.com,\n"
+	      "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"
+	      "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"
+	      );
+
+    // Run the cleanup
+    lfc_controller.launch(argc, argv);
+
+    // Compare the results
+    EXPECT_EQ(readFile(xstr_.c_str()),
+	      "address,duid,valid_lifetime,expire,subnet_id,"
+	      "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+	      "fqdn_rev,hostname,hwaddr\n"
+	      "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"
+	      "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,1000,6,150,"
+	      "0,8,0,0,0,,\n"
+	      "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"
+	      );
+}
+
 } // end of anonymous namespace

+ 40 - 2
src/lib/dhcpsrv/lease_file_loader.h

@@ -160,9 +160,47 @@ public:
             lease_file.close();
         }
     }
+
+    /// @brief Write leaes from the storage into a lease file
+    ///
+    /// This method iterates over the @c Lease4 or @c Lease6 object in the
+    /// storage specified in the arguments and writes them to the file
+    /// specified in the arguments.
+    /// 
+    /// This method writes all entries in the storege to the file, it does
+    /// not perform any checks for expriation or duplication.
+    ///
+    /// @param lease_file A reference to the @c CSVLeaseFile4 or
+    /// @c CSVLeaseFile6 object representing the lease file. The file
+    /// doesn't need to be open because the method re-opens the file.
+    /// @param storage A reference to the container from which leases
+    /// should be written..
+    /// @tparam LeasePtrType 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,
+             typename StorageType>
+    static void write(LeaseFileType& lease_file, const StorageType& storage) {
+        // Reopen the file, as we don't know whether the file is open
+        // and we also don't know its current state.
+        lease_file.close();
+        lease_file.open();
+
+	// Iterate over the storage area writing out the leases
+        for (typename StorageType::const_iterator lease = storage.begin();
+             lease != storage.end();
+             ++lease) {
+            lease_file.append(**lease);
+        }
+
+	// Close the file 
+        lease_file.close();
+    }
 };
 
-}
-}
+} // namesapce dhcp
+} // namespace isc
 
 #endif // LEASE_FILE_LOADER_H

+ 75 - 0
src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc

@@ -79,6 +79,37 @@ public:
         return (LeasePtrType());
     }
 
+    /// @brief Tests the write function.
+    ///
+    /// This method writes the leases from the storage container to the lease file
+    /// then compares the output to the string provided in the aguments to verify
+    /// the write was correct.  The order of the leases in the output will dpend
+    /// on the order in which the container provides the leases.
+    ///
+    /// @param storage A reference to the container to be written to the file
+    /// @param compStr The string to compare to what was read from the file
+    /// 
+    /// @tparam LeaseStorage Type of the container: @c Lease4Container
+    /// @c Lease6Container.
+    ///
+    template<typename LeaseObjectType, typename LeaseFileType,
+	     typename StorageType>
+    void writeLeases(LeaseFileType lease_file,
+		     const StorageType& storage,
+		     const std::string& compare) {
+      // Prepare for a new file, close and remove the old
+      lease_file.close();
+      io_.removeFile();
+
+      // Write the current leases to the file
+      LeaseFileLoader::write<LeaseObjectType, LeaseFileType, StorageType>
+	(lease_file, storage);
+
+      // Compare to see if we got what we exepcted.
+      EXPECT_EQ(compare, io_.readFile());
+    }
+
+
     /// @brief Name of the test lease file.
     std::string filename_;
 
@@ -143,6 +174,15 @@ TEST_F(LeaseFileLoaderTest, load4) {
     lease = getLease<Lease4Ptr>("192.0.3.15", storage);
     ASSERT_TRUE(lease);
     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");
 }
 
 // This test verifies that the lease with a valid lifetime of 0
@@ -176,6 +216,13 @@ TEST_F(LeaseFileLoaderTest, load4LeaseRemove) {
     Lease4Ptr lease = getLease<Lease4Ptr>("192.0.3.15", storage);
     ASSERT_TRUE(lease);
     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");
 }
 
 // This test verifies that the DHCPv6 leases can be loaded from the lease
@@ -226,6 +273,19 @@ TEST_F(LeaseFileLoaderTest, load6) {
     lease = getLease<Lease6Ptr>("2001:db8:2::10", storage);
     ASSERT_TRUE(lease);
     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");
 }
 
 // This test verifies that the lease with a valid lifetime of 0
@@ -261,6 +321,14 @@ TEST_F(LeaseFileLoaderTest, load6LeaseRemove) {
     Lease6Ptr lease = getLease<Lease6Ptr>("2001:db8:2::10", storage);
     ASSERT_TRUE(lease);
     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");
 }
 
 // This test verifies that the exception is thrown when the specific
@@ -334,6 +402,13 @@ TEST_F(LeaseFileLoaderTest, loadLeaseWithZeroLifetime) {
 
     // The lease with a valid lifetime of 0 should not be loaded.
     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");
+
 }