Browse Source

[master] Merge branch 'trac3671'

Marcin Siodelski 10 years ago
parent
commit
667de2ef90

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

@@ -82,11 +82,13 @@ libkea_dhcpsrv_la_SOURCES += host_container.h
 libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
+libkea_dhcpsrv_la_SOURCES += lease_file_loader.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 += logging.cc logging.h
 libkea_dhcpsrv_la_SOURCES += logging_info.cc logging_info.h
 libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
+libkea_dhcpsrv_la_SOURCES += memfile_lease_storage.h
 
 if HAVE_MYSQL
 libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h

+ 5 - 15
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-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
@@ -284,23 +284,13 @@ subnet ID and hardware address.
 A debug message issued when the server is about to obtain schema version
 information from the memory file database.
 
-% DHCPSRV_MEMFILE_LEASES_RELOAD4 reloading leases from %1
-An info message issued when the server is about to start reading DHCPv4 leases
+% DHCPSRV_MEMFILE_LEASE_FILE_LOAD loading leases from file %1
+An info message issued when the server is about to start reading DHCP leases
 from the lease file. All leases currently held in the memory will be
 replaced by those read from the file.
 
-% DHCPSRV_MEMFILE_LEASES_RELOAD6 reloading leases from %1
-An info message issued when the server is about to start reading DHCPv6 leases
-from the lease file. All leases currently held in the memory will be
-replaced by those read from the file.
-
-% DHCPSRV_MEMFILE_LEASE_LOAD4 loading lease %1
-A debug message issued when DHCPv4 lease is being loaded from the file to
-memory.
-
-% DHCPSRV_MEMFILE_LEASE_LOAD6 loading lease %1
-A debug message issued when DHCPv6 lease is being loaded from the file to
-memory.
+% DHCPSRV_MEMFILE_LEASE_LOAD loading lease %1
+A debug message issued when DHCP lease is being loaded from the file to memory.
 
 % DHCPSRV_MEMFILE_NO_STORAGE running in non-persistent mode, leases will be lost after restart
 A warning message issued when writes of leases to disk have been disabled

+ 168 - 0
src/lib/dhcpsrv/lease_file_loader.h

@@ -0,0 +1,168 @@
+// 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_LOADER_H
+#define LEASE_FILE_LOADER_H
+
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <util/csv_file.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Utility class to manage bulk of leases in the lease files.
+///
+/// This class exposes methods which allow for bulk loading leases from
+/// the lease file and dumping the leases held in memory into the
+/// lease file. There are two major use cases for this class:
+/// - load leases by the DHCP server when the server starts up or
+///   reloads configuration,
+/// - an application performing a lease file cleanup rewrites the whole
+///   lease file to remove the redundant lease entries.
+///
+/// In the former case, this class is used by the @c MemFile_LeaseMgr.
+/// In the latter case, this class is used by the standalone application
+/// which reads the whole lease file into memory (storage) and then
+/// dumps the leases held in the storage to another file.
+///
+/// The methods in this class are templated so as they can be used both
+/// with the @c Lease4Storage and @c Lease6Storage to process the DHCPv4
+/// and DHCPv6 leases respectively.
+///
+/// @todo Add a method which dumps all leases from the storage to a
+/// specified lease file.
+class LeaseFileLoader {
+public:
+
+    /// @brief Load leases from the lease file into the specified storage.
+    ///
+    /// This method iterates over the entries in the lease file in the
+    /// CSV format, creates @c Lease4 or @c Lease6 objects and inserts
+    /// them into the storage to which reference is specified as an
+    /// argument. If there are multiple entries for the particular lease
+    /// in the lease file the entries further in the lease file override
+    /// the previous entries.
+    ///
+    /// If the method finds the entry with the valid lifetime of 0 it
+    /// means that the particular lease was released and the method
+    /// removes an existing lease from the container.
+    ///
+    /// @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 to which leases
+    /// should be inserted.
+    /// @param max_errors Maximum number of corrupted leases in the
+    /// lease file. The method will skip corrupted leases but after
+    /// exceeding the specified number of errors it will throw an
+    /// exception.
+    /// @param close_file_on_exit A boolean flag which indicates if
+    /// the file should be closed after it has been successfully parsed.
+    /// One case when the file is not opened is when the server starts
+    /// up, reads the leases in the file and then leaves the file open
+    /// for writing future lease updates.
+    /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
+    /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
+    /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
+    ///
+    /// @throw isc::util::CSVFileError when the maximum number of errors
+    /// has been exceeded.
+    template<typename LeaseObjectType, typename LeaseFileType,
+             typename StorageType>
+    static void load(LeaseFileType& lease_file, StorageType& storage,
+                     const uint32_t max_errors = 0xFFFFFFFF,
+                     const bool close_file_on_exit = true) {
+
+        LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASE_FILE_LOAD)
+            .arg(lease_file.getFilename());
+
+        // 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();
+
+        boost::shared_ptr<LeaseObjectType> lease;
+        // Track the number of corrupted leases.
+        uint32_t errcnt = 0;
+        while (true) {
+            // Unable to parse the lease.
+            if (!lease_file.next(lease)) {
+                // A value of 0xFFFFFFFF indicates that we don't return
+                // until the whole file is parsed, even if errors occur.
+                // Otherwise, check if we have exceeded the maximum number
+                // of errors and throw an exception if we have.
+                if (++errcnt > max_errors) {
+                    // If we break parsing the CSV file because of too many
+                    // errors, it doesn't make sense to keep the file open.
+                    // This is because the caller wouldn't know where we
+                    // stopped parsing and where the internal file pointer
+                    // is. So, there are probably no cases when the caller
+                    // would continue to use the open file.
+                    lease_file.close();
+                    isc_throw(util::CSVFileError, "exceeded maximum number of"
+                              " failures " << max_errors << " to read a lease"
+                              " from the lease file "
+                              << lease_file.getFilename());
+                }
+                // Skip the corrupted lease.
+                continue;
+            }
+
+            // Lease was found and we successfully parsed it.
+            if (lease) {
+                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL_DATA,
+                          DHCPSRV_MEMFILE_LEASE_LOAD)
+                    .arg(lease->toText());
+
+                // Check if this lease exists.
+                typename StorageType::iterator lease_it =
+                    storage.find(lease->addr_);
+                // The lease doesn't exist yet. Insert the lease if
+                // it has a positive valid lifetime.
+                if (lease_it == storage.end()) {
+                    if (lease->valid_lft_ > 0) {
+                        storage.insert(lease);
+                    }
+                } else {
+                    // The lease exists. If the new entry has a valid
+                    // lifetime of 0 it is an indication to remove the
+                    // existing entry. Otherwise, we update the lease.
+                    if (lease->valid_lft_ == 0) {
+                        storage.erase(lease_it);
+
+                    } else {
+                        **lease_it = *lease;
+                    }
+                }
+
+            } else {
+                // Being here means that we hit the end of file.
+                break;
+
+            }
+        }
+
+        if (close_file_on_exit) {
+            lease_file.close();
+        }
+    }
+};
+
+}
+}
+
+#endif // LEASE_FILE_LOADER_H

+ 45 - 124
src/lib/dhcpsrv/memfile_lease_mgr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-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
@@ -14,10 +14,18 @@
 
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_file_loader.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 #include <exceptions/exceptions.h>
-
 #include <iostream>
+#include <sstream>
+
+namespace {
+
+/// @brief Maximum number of errors to read the leases from the lease file.
+const uint32_t MAX_LEASE_ERRORS = 100;
+
+} // end of anonymous namespace
 
 using namespace isc::dhcp;
 
@@ -28,16 +36,14 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
     if (universe == "4") {
         std::string file4 = initLeaseFilePath(V4);
         if (!file4.empty()) {
-            lease_file4_.reset(new CSVLeaseFile4(file4));
-            lease_file4_->open();
-            load4();
+            loadLeasesFromFiles<Lease4, CSVLeaseFile4>(file4, lease_file4_,
+                                                       storage4_);
         }
     } else {
         std::string file6 = initLeaseFilePath(V6);
         if (!file6.empty()) {
-            lease_file6_.reset(new CSVLeaseFile6(file6));
-            lease_file6_->open();
-            load6();
+            loadLeasesFromFiles<Lease6, CSVLeaseFile6>(file6, lease_file6_,
+                                                       storage6_);
         }
     }
 
@@ -165,7 +171,7 @@ Lease4Collection
 Memfile_LeaseMgr::getLease4(const ClientId& client_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_CLIENTID).arg(client_id.toText());
-    typedef Memfile_LeaseMgr::Lease4Storage::nth_index<0>::type SearchIndex;
+    typedef Lease4Storage::nth_index<0>::type SearchIndex;
     Lease4Collection collection;
     const SearchIndex& idx = storage4_.get<0>();
     for(SearchIndex::const_iterator lease = idx.begin();
@@ -468,127 +474,42 @@ Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
     return (lease_file);
 }
 
-void
-Memfile_LeaseMgr::load4() {
-    // If lease file hasn't been opened, we are working in non-persistent mode.
-    // That's fine, just leave.
-    if (!persistLeases(V4)) {
-        return;
-    }
+template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
+void Memfile_LeaseMgr::
+loadLeasesFromFiles(const std::string& filename,
+                    boost::shared_ptr<LeaseFileType>& lease_file,
+                    StorageType& storage) {
+    storage.clear();
 
-    LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASES_RELOAD4)
-        .arg(lease_file4_->getFilename());
-
-    // Remove existing leases (if any). We will recreate them based on the
-    // data on disk.
-    storage4_.clear();
-
-    Lease4Ptr lease;
-    do {
-        /// @todo Currently we stop parsing on first failure. It is possible
-        /// that only one (or a few) leases are bad, so in theory we could
-        /// continue parsing but that would require some error counters to
-        /// prevent endless loops. That is enhancement for later time.
-        if (!lease_file4_->next(lease)) {
-            isc_throw(DbOperationError, "Failed to parse the DHCPv6 lease in"
-                      " the lease file: " << lease_file4_->getReadMsg());
-        }
-        // If we got the lease, we update the internal container holding
-        // leases. Otherwise, we reached the end of file and we leave.
-        if (lease) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL_DATA,
-                      DHCPSRV_MEMFILE_LEASE_LOAD4)
-                .arg(lease->toText());
-            loadLease4(lease);
-        }
-    } while (lease);
-}
+    // Load the leasefile.completed, if exists.
+    lease_file.reset(new LeaseFileType(std::string(filename + ".completed")));
+    if (lease_file->exists()) {
+        LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
+                                               MAX_LEASE_ERRORS);
 
-void
-Memfile_LeaseMgr::loadLease4(Lease4Ptr& lease) {
-    // Check if the lease already exists.
-    Lease4Storage::iterator lease_it = storage4_.find(lease->addr_);
-    // Lease doesn't exist.
-    if (lease_it == storage4_.end()) {
-        // Add the lease only if valid lifetime is greater than 0.
-        // We use valid lifetime of 0 to indicate that lease should
-        // be removed.
-        if (lease->valid_lft_ > 0) {
-           storage4_.insert(lease);
-       }
     } else {
-        // We use valid lifetime of 0 to indicate that the lease is
-        // to be removed. In such case, erase the lease.
-        if (lease->valid_lft_ == 0) {
-            storage4_.erase(lease_it);
-
-        } else {
-            // Update existing lease.
-            **lease_it = *lease;
+        // If the leasefile.completed doesn't exist, let's load the leases
+        // from leasefile.2 and leasefile.1, if they exist.
+        lease_file.reset(new LeaseFileType(std::string(filename + ".2")));
+        if (lease_file->exists()) {
+            LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
+                                                   MAX_LEASE_ERRORS);
         }
-    }
-}
-
-void
-Memfile_LeaseMgr::load6() {
-    // If lease file hasn't been opened, we are working in non-persistent mode.
-    // That's fine, just leave.
-    if (!persistLeases(V6)) {
-        return;
-    }
 
-    LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASES_RELOAD6)
-        .arg(lease_file6_->getFilename());
-
-    // Remove existing leases (if any). We will recreate them based on the
-    // data on disk.
-    storage6_.clear();
-
-    Lease6Ptr lease;
-    do {
-        /// @todo Currently we stop parsing on first failure. It is possible
-        /// that only one (or a few) leases are bad, so in theory we could
-        /// continue parsing but that would require some error counters to
-        /// prevent endless loops. That is enhancement for later time.
-        if (!lease_file6_->next(lease)) {
-            isc_throw(DbOperationError, "Failed to parse the DHCPv6 lease in"
-                      " the lease file: " << lease_file6_->getReadMsg());
-        }
-        // If we got the lease, we update the internal container holding
-        // leases. Otherwise, we reached the end of file and we leave.
-        if (lease) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL_DATA,
-                      DHCPSRV_MEMFILE_LEASE_LOAD6)
-                .arg(lease->toText());
-
-            loadLease6(lease);
-        }
-    } while (lease);
-}
-
-void
-Memfile_LeaseMgr::loadLease6(Lease6Ptr& lease) {
-    // Check if the lease already exists.
-    Lease6Storage::iterator lease_it = storage6_.find(lease->addr_);
-    // Lease doesn't exist.
-    if (lease_it == storage6_.end()) {
-        // Add the lease only if valid lifetime is greater than 0.
-        // We use valid lifetime of 0 to indicate that lease should
-        // be removed.
-        if (lease->valid_lft_ > 0) {
-            storage6_.insert(lease);
-       }
-    } else {
-        // We use valid lifetime of 0 to indicate that the lease is
-        // to be removed. In such case, erase the lease.
-        if (lease->valid_lft_ == 0) {
-            storage6_.erase(lease_it);
-
-        } else {
-            // Update existing lease.
-            **lease_it = *lease;
+        lease_file.reset(new LeaseFileType(std::string(filename + ".1")));
+        if (lease_file->exists()) {
+            LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
+                                                   MAX_LEASE_ERRORS);
         }
     }
 
+    // Always load leases from the primary lease file. If the lease file
+    // doesn't exist it will be created by the LeaseFileLoader. Note
+    // that the false value passed as the last parameter to load
+    // function causes the function to leave the file open after
+    // it is parsed. This file will be used by the backend to record
+    // future lease updates.
+    lease_file.reset(new LeaseFileType(filename));
+    LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
+                                           MAX_LEASE_ERRORS, false);;
 }
-

+ 44 - 157
src/lib/dhcpsrv/memfile_lease_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-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
@@ -18,13 +18,10 @@
 #include <dhcp/hwaddr.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 <boost/multi_index/indexed_by.hpp>
-#include <boost/multi_index/member.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/composite_key.hpp>
+#include <boost/shared_ptr.hpp>
 
 namespace isc {
 namespace dhcp {
@@ -333,51 +330,7 @@ public:
     /// server shut down.
     bool persistLeases(Universe u) const;
 
-protected:
-
-    /// @brief Load all DHCPv4 leases from the file.
-    ///
-    /// This method loads all DHCPv4 leases from a file to memory. It removes
-    /// existing leases before reading a file.
-    ///
-    /// @throw isc::DbOperationError If failed to read a lease from the lease
-    /// file.
-    void load4();
-
-    /// @brief Loads a single DHCPv4 lease from the file.
-    ///
-    /// This method reads a single lease record from the lease file. If the
-    /// corresponding record doesn't exist in the in-memory container, the
-    /// lease is added to the container (except for a lease which valid lifetime
-    /// is 0). If the corresponding lease exists, the lease being read updates
-    /// the existing lease. If the lease being read from the lease file has
-    /// valid lifetime of 0 and the corresponding lease exists in the in-memory
-    /// database, the existing lease is removed.
-    ///
-    /// @param lease Pointer to the lease read from the lease file.
-    void loadLease4(Lease4Ptr& lease);
-
-    /// @brief Load all DHCPv6 leases from the file.
-    ///
-    /// This method loads all DHCPv6 leases from a file to memory. It removes
-    /// existing leases before reading a file.
-    ///
-    /// @throw isc::DbOperationError If failed to read a lease from the lease
-    /// file.
-    void load6();
-
-    /// @brief Loads a single DHCPv6 lease from the file.
-    ///
-    /// This method reads a single lease record from the lease file. If the
-    /// corresponding record doesn't exist in the in-memory container, the
-    /// lease is added to the container (except for a lease which valid lifetime
-    /// is 0). If the corresponding lease exists, the lease being read updates
-    /// the existing lease. If the lease being read from the lease file has
-    /// valid lifetime of 0 and the corresponding lease exists in the in-memory
-    /// database, the existing lease is removed.
-    ///
-    /// @param lease Pointer to the lease read from the lease file.
-    void loadLease6(Lease6Ptr& lease);
+private:
 
     /// @brief Initialize the location of the lease file.
     ///
@@ -396,111 +349,45 @@ protected:
     /// argument to this function.
     std::string initLeaseFilePath(Universe u);
 
-    // This is a multi-index container, which holds elements that can
-    // be accessed using different search indexes.
-    typedef boost::multi_index_container<
-        // It holds pointers to Lease6 objects.
-        Lease6Ptr,
-        boost::multi_index::indexed_by<
-            // Specification of the first index starts here.
-            // This index sorts leases by IPv6 addresses represented as
-            // IOAddress objects.
-            boost::multi_index::ordered_unique<
-                boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
-            >,
-
-            // Specification of the second index starts here.
-            boost::multi_index::ordered_non_unique<
-                // This is a composite index that will be used to search for
-                // the lease using three attributes: DUID, IAID and lease type.
-                boost::multi_index::composite_key<
-                    Lease6,
-                    // The DUID can be retrieved from the Lease6 object using
-                    // a getDuidVector const function.
-                    boost::multi_index::const_mem_fun<Lease6, const std::vector<uint8_t>&,
-                                                      &Lease6::getDuidVector>,
-                    // The two other ingredients of this index are IAID and
-                    // lease type.
-                    boost::multi_index::member<Lease6, uint32_t, &Lease6::iaid_>,
-                    boost::multi_index::member<Lease6, Lease::Type, &Lease6::type_>
-                >
-            >
-        >
-     > Lease6Storage; // Specify the type name of this container.
-
-    // This is a multi-index container, which holds elements that can
-    // be accessed using different search indexes.
-    typedef boost::multi_index_container<
-        // It holds pointers to Lease4 objects.
-        Lease4Ptr,
-        // Specification of search indexes starts here.
-        boost::multi_index::indexed_by<
-            // Specification of the first index starts here.
-            // This index sorts leases by IPv4 addresses represented as
-            // IOAddress objects.
-            boost::multi_index::ordered_unique<
-                // The IPv4 address are held in addr_ members that belong to
-                // Lease class.
-                boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
-            >,
-
-            // Specification of the second index starts here.
-            boost::multi_index::ordered_unique<
-                // This is a composite index that combines two attributes of the
-                // Lease4 object: hardware address and subnet id.
-                boost::multi_index::composite_key<
-                    Lease4,
-                    // The hardware address is held in the hwaddr_ member of the
-                    // Lease4 object, which is a HWAddr object. Boost does not
-                    // provide a key extractor for getting a member of a member,
-                    // so we need a simple method for that.
-                    boost::multi_index::const_mem_fun<Lease, const std::vector<uint8_t>&,
-                                               &Lease::getHWAddrVector>,
-                    // The subnet id is held in the subnet_id_ member of Lease4
-                    // class. Note that the subnet_id_ is defined in the base
-                    // class (Lease) so we have to point to this class rather
-                    // than derived class: Lease4.
-                    boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
-                >
-            >,
-
-            // Specification of the third index starts here.
-            boost::multi_index::ordered_non_unique<
-                // This is a composite index that uses two values to search for a
-                // lease: client id and subnet id.
-                boost::multi_index::composite_key<
-                    Lease4,
-                    // The client id can be retrieved from the Lease4 object by
-                    // calling getClientIdVector const function.
-                    boost::multi_index::const_mem_fun<Lease4, const std::vector<uint8_t>&,
-                                                      &Lease4::getClientIdVector>,
-                    // The subnet id is accessed through the subnet_id_ member.
-                    boost::multi_index::member<Lease, uint32_t, &Lease::subnet_id_>
-                >
-            >,
-
-            // Specification of the fourth index starts here.
-            boost::multi_index::ordered_non_unique<
-                // This is a composite index that uses two values to search for a
-                // lease: client id and subnet id.
-                boost::multi_index::composite_key<
-                    Lease4,
-                    // The client id can be retrieved from the Lease4 object by
-                    // calling getClientIdVector const function.
-                    boost::multi_index::const_mem_fun<Lease4, const std::vector<uint8_t>&,
-                                                      &Lease4::getClientIdVector>,
-                    // The hardware address is held in the hwaddr_ object. We can
-                    // access the raw data using lease->hwaddr_->hwaddr_, but Boost
-                    // doesn't seem to provide a way to use member of a member for this,
-                    // so we need a simple key extractor method (getRawHWAddr).
-                    boost::multi_index::const_mem_fun<Lease, const std::vector<uint8_t>&,
-                                            &Lease::getHWAddrVector>,
-                    // The subnet id is accessed through the subnet_id_ member.
-                    boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
-                >
-            >
-        >
-    > Lease4Storage; // Specify the type name for this container.
+    /// @brief Load leases from the persistent storage.
+    ///
+    /// This method loads DHCPv4 or DHCPv6 leases from lease files in the
+    /// following order:
+    /// - If the <filename>.completed doesn't exist:
+    ///   - leases from the <filename>.2
+    ///   - leases from the <filename>.1
+    ///   - leases from the <filename>
+    /// - else
+    ///   - leases from the <filename>.completed
+    ///   - leases from the <filename>
+    ///
+    /// If any of the files doesn't exist the method proceeds to reading
+    /// leases from the subsequent file. If the <filename> doesn't exist
+    /// it is created.
+    ///
+    /// When the method successfully reads leases from the files, it leaves
+    /// the file <filename> open and its internal pointer is set to the
+    /// end of file. The server will append lease entries to this file as
+    /// a result of processing new messages from the clients.
+    ///
+    /// The <filename>.2, <filename>.1 and <filename>.completed are the
+    /// products of the lease file cleanups (LFC).
+    /// See: http://kea.isc.org/wiki/LFCDesign for details.
+    ///
+    /// @param filename Name of the lease file.
+    /// @param lease_file An object representing a lease file to which
+    /// the server will store lease updates.
+    /// @param storage A storage for leases read from the lease file.
+    /// @tparam LeaseObjectType @c Lease4 or @c Lease6.
+    /// @tparam LeaseFileType @c CSVLeaseFile4 or @c CSVLeaseFile6.
+    /// @tparam StorageType @c Lease4Storage or @c Lease6Storage.
+    ///
+    /// @throw CSVFileError when parsing any of the lease files fails.
+    template<typename LeaseObjectType, typename LeaseFileType,
+             typename StorageType>
+    void loadLeasesFromFiles(const std::string& filename,
+                             boost::shared_ptr<LeaseFileType>& lease_file,
+                             StorageType& storage);
 
     /// @brief stores IPv4 leases
     Lease4Storage storage4_;
@@ -519,4 +406,4 @@ protected:
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 
-#endif // MEMFILE_LEASE_MGR
+#endif // MEMFILE_LEASE_MGR_H

+ 151 - 0
src/lib/dhcpsrv/memfile_lease_storage.h

@@ -0,0 +1,151 @@
+// 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 INMEMORY_LEASE_STORAGE_H
+#define INMEMORY_LEASE_STORAGE_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet_id.h>
+
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A multi index container holding DHCPv6 leases.
+///
+/// The leases in the container may be accessed using different indexes:
+/// - using an IPv6 address,
+/// - using a composite index: DUID, IAID and lease type.
+typedef boost::multi_index_container<
+    // It holds pointers to Lease6 objects.
+    Lease6Ptr,
+    boost::multi_index::indexed_by<
+        // Specification of the first index starts here.
+        // This index sorts leases by IPv6 addresses represented as
+        // IOAddress objects.
+        boost::multi_index::ordered_unique<
+            boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+        >,
+
+        // Specification of the second index starts here.
+        boost::multi_index::ordered_non_unique<
+            // This is a composite index that will be used to search for
+            // the lease using three attributes: DUID, IAID and lease type.
+            boost::multi_index::composite_key<
+                Lease6,
+                // The DUID can be retrieved from the Lease6 object using
+                // a getDuidVector const function.
+                boost::multi_index::const_mem_fun<Lease6, const std::vector<uint8_t>&,
+                                                  &Lease6::getDuidVector>,
+                // The two other ingredients of this index are IAID and
+                // lease type.
+                boost::multi_index::member<Lease6, uint32_t, &Lease6::iaid_>,
+                boost::multi_index::member<Lease6, Lease::Type, &Lease6::type_>
+            >
+        >
+     >
+> Lease6Storage; // Specify the type name of this container.
+
+/// @brief A multi index container holding DHCPv4 leases.
+///
+/// The leases in the container may be accessed using different indexes:
+/// - IPv6 address,
+/// - composite index: HW address and subnet id,
+/// - composite index: client id and subnet id,
+/// - composite index: HW address, client id and subnet id
+typedef boost::multi_index_container<
+    // It holds pointers to Lease4 objects.
+    Lease4Ptr,
+    // Specification of search indexes starts here.
+    boost::multi_index::indexed_by<
+        // Specification of the first index starts here.
+        // This index sorts leases by IPv4 addresses represented as
+        // IOAddress objects.
+        boost::multi_index::ordered_unique<
+            // The IPv4 address are held in addr_ members that belong to
+            // Lease class.
+            boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+        >,
+
+        // Specification of the second index starts here.
+        boost::multi_index::ordered_unique<
+            // This is a composite index that combines two attributes of the
+            // Lease4 object: hardware address and subnet id.
+            boost::multi_index::composite_key<
+                Lease4,
+                // The hardware address is held in the hwaddr_ member of the
+                // Lease4 object, which is a HWAddr object. Boost does not
+                // provide a key extractor for getting a member of a member,
+                // so we need a simple method for that.
+                boost::multi_index::const_mem_fun<Lease, const std::vector<uint8_t>&,
+                                                  &Lease::getHWAddrVector>,
+                // The subnet id is held in the subnet_id_ member of Lease4
+                // class. Note that the subnet_id_ is defined in the base
+                // class (Lease) so we have to point to this class rather
+                // than derived class: Lease4.
+                boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
+            >
+        >,
+
+        // Specification of the third index starts here.
+        boost::multi_index::ordered_non_unique<
+            // This is a composite index that uses two values to search for a
+            // lease: client id and subnet id.
+            boost::multi_index::composite_key<
+                Lease4,
+                // The client id can be retrieved from the Lease4 object by
+                // calling getClientIdVector const function.
+                boost::multi_index::const_mem_fun<Lease4, const std::vector<uint8_t>&,
+                                                  &Lease4::getClientIdVector>,
+                // The subnet id is accessed through the subnet_id_ member.
+                boost::multi_index::member<Lease, uint32_t, &Lease::subnet_id_>
+            >
+        >,
+
+        // Specification of the fourth index starts here.
+        boost::multi_index::ordered_non_unique<
+            // This is a composite index that uses three values to search for a
+            // lease: client id, HW address and subnet id.
+            boost::multi_index::composite_key<
+                Lease4,
+                // The client id can be retrieved from the Lease4 object by
+                // calling getClientIdVector const function.
+                boost::multi_index::const_mem_fun<Lease4, const std::vector<uint8_t>&,
+                                                  &Lease4::getClientIdVector>,
+                // The hardware address is held in the hwaddr_ object. We can
+                // access the raw data using lease->hwaddr_->hwaddr_, but Boost
+                // doesn't seem to provide a way to use member of a member for this,
+                // so we need a simple key extractor method (getHWAddrVector).
+                boost::multi_index::const_mem_fun<Lease, const std::vector<uint8_t>&,
+                                                  &Lease::getHWAddrVector>,
+                // The subnet id is accessed through the subnet_id_ member.
+                boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
+            >
+        >
+    >
+> Lease4Storage; // Specify the type name for this container.
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // INMEMORY_LEASE_STORAGE_H

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

@@ -74,6 +74,7 @@ libdhcpsrv_unittests_SOURCES += host_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_reservation_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_reservations_list_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
+libdhcpsrv_unittests_SOURCES += lease_file_loader_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc

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

@@ -0,0 +1,340 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <dhcpsrv/lease_file_loader.h>
+#include <dhcpsrv/tests/lease_file_io.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture class for @c LeaseFileLoader class.
+class LeaseFileLoaderTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes filename used for unit tests and creates an
+    /// IO object to be used to write to this file.
+    LeaseFileLoaderTest();
+
+    /// @brief Prepends the absolute path to the file specified
+    /// as an argument.
+    ///
+    /// @param filename Name of the file.
+    /// @return Absolute path to the test file.
+    static std::string absolutePath(const std::string& filename);
+
+    /// @brief Retrieves the lease from the storage using an IP address.
+    ///
+    /// This method returns the pointer to the @c Lease4 or @c Lease6
+    /// object representing the lease in the container. This is used to
+    /// check if the lease was parsed in the lease file and added to the
+    /// container by the @c LeaseFileLoader::load method.
+    ///
+    /// @param address A string representation of the leased address.
+    /// @param storage A reference to the container in which the lease
+    /// is to be searched.
+    /// @tparam LeasePtrType Type of the returned object: @c Lease4Ptr
+    /// @c Lease6Ptr.
+    /// @tparam LeaseStorage Type of the container: @c Lease4Container
+    /// @c Lease6Container.
+    ///
+    /// @return A pointer to the lease or NULL if no lease found.
+    template<typename LeasePtrType, typename LeaseStorage>
+    static LeasePtrType getLease(const std::string& address, const LeaseStorage& storage) {
+        typedef typename LeaseStorage::template nth_index<0>::type SearchIndex;
+        // Both Lease4Storage and Lease6Storage use index 0 to retrieve the
+        // lease using an IP address.
+        const SearchIndex& idx = storage.template get<0>();
+        typename SearchIndex::iterator lease = idx.find(IOAddress(address));
+        // Lease found. Return it.
+        if (lease != idx.end()) {
+            return (*lease);
+        }
+        // No lease found.
+        return (LeasePtrType());
+    }
+
+    /// @brief Name of the test lease file.
+    std::string filename_;
+
+    /// @brief Object providing access to lease file IO.
+    LeaseFileIO io_;
+};
+
+LeaseFileLoaderTest::LeaseFileLoaderTest()
+    : filename_("leases4.csv"), io_(absolutePath(filename_)) {
+}
+
+std::string
+LeaseFileLoaderTest::absolutePath(const std::string& filename) {
+    std::ostringstream s;
+    s << DHCP_DATA_DIR << "/" << filename;
+    return (s.str());
+}
+
+// This test verifies that the DHCPv4 leases can be loaded from the lease
+// file and that only the most recent entry for each lease is loaded and
+// the previous entries are discarded.
+TEST_F(LeaseFileLoaderTest, load4) {
+    // 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
+    // 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");
+
+    boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+    ASSERT_NO_THROW(lf->open());
+
+    // Load leases from the file.
+    Lease4Storage storage;
+    ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10));
+
+    // There are two unique leases.
+    ASSERT_EQ(2, storage.size());
+
+    // The lease for 192.0.2.1 should exist and the cltt should be
+    // set to the expire-valid_lifetime for the second entry for
+    // this lease, i.e. 500 - 200 = 300.
+    Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(300, lease->cltt_);
+
+    // The invalid entry should not be loaded.
+    lease = getLease<Lease4Ptr>("192.0.2.3", storage);
+    ASSERT_FALSE(lease);
+
+    // The other lease should be present and the cltt time should
+    // be set according to the values in the second entry for this
+    // lease.
+    lease = getLease<Lease4Ptr>("192.0.3.15", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(35, lease->cltt_);
+}
+
+// This test verifies that the lease with a valid lifetime of 0
+// is removed from the storage. The valid lifetime of 0 is set
+// for the released leases.
+TEST_F(LeaseFileLoaderTest, load4LeaseRemove) {
+    // 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
+    // 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");
+
+    boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+    ASSERT_NO_THROW(lf->open());
+
+    Lease4Storage storage;
+    ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10));
+
+    // There should only be one lease. The one with the valid_lifetime
+    // of 0 should be removed.
+    ASSERT_EQ(1, storage.size());
+
+    Lease4Ptr lease = getLease<Lease4Ptr>("192.0.3.15", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(35, lease->cltt_);
+}
+
+// This test verifies that the DHCPv6 leases can be loaded from the lease
+// file and that only the most recent entry for each lease is loaded and
+// the previous entries are discarded.
+TEST_F(LeaseFileLoaderTest, load6) {
+    // Create a lease file with three valid leases: 2001:db8:1::1,
+    // 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");
+
+    boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
+    ASSERT_NO_THROW(lf->open());
+
+    // Load leases from the lease file.
+    Lease6Storage storage;
+    ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10));
+
+    // There should be 3 unique leases.
+    ASSERT_EQ(3, storage.size());
+
+    // The 2001:db8:1::1 should be present and its cltt should be
+    // calculated according to the expiration time and the valid
+    // lifetime from the last entry for this lease: 400 - 200 = 200.
+    Lease6Ptr lease = getLease<Lease6Ptr>("2001:db8:1::1", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(200, lease->cltt_);
+
+    // The 3000:1:: lease should be present.
+    lease = getLease<Lease6Ptr>("3000:1::", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(100, lease->cltt_);
+
+    // The 2001:db8:2::10 should be present and the cltt should be
+    // calculated according to the last entry in the lease file.
+    lease = getLease<Lease6Ptr>("2001:db8:2::10", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(500, lease->cltt_);
+}
+
+// This test verifies that the lease with a valid lifetime of 0
+// is removed from the storage. The valid lifetime of 0 set set
+// for the released leases.
+TEST_F(LeaseFileLoaderTest, load6LeaseRemove) {
+    // 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
+    // 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");
+
+    boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
+    ASSERT_NO_THROW(lf->open());
+
+    // Loaded leases.
+    Lease6Storage storage;
+    ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10));
+
+    // There should be only one lease for 2001:db8:2::10. The other one
+    // should have been deleted (or rather not loaded).
+    ASSERT_EQ(1, storage.size());
+
+    Lease6Ptr lease = getLease<Lease6Ptr>("2001:db8:2::10", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(500, lease->cltt_);
+}
+
+// This test verifies that the exception is thrown when the specific
+// number of errors in the test data occur during reading of the lease
+// file.
+TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
+    // 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");
+
+    boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+    ASSERT_NO_THROW(lf->open());
+
+    // Load leases and set the maximum number of errors to 3. This
+    // should result in an exception because there are 4 invalid entries.
+    Lease4Storage storage;
+    ASSERT_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 3),
+                 util::CSVFileError);
+
+    lf->close();
+    ASSERT_NO_THROW(lf->open());
+
+    // Repeat the test, but this time allow for 4 invalid entries. It
+    // should load just fine.
+    storage.clear();
+    ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 4));
+
+    ASSERT_EQ(2, storage.size());
+
+    Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(300, lease->cltt_);
+
+    lease = getLease<Lease4Ptr>("192.0.2.10", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(100, lease->cltt_);
+}
+
+// This test verifies that the lease with a valid lifetime set to 0 is
+// not loaded if there are no previous entries for this lease in the
+// lease file.
+TEST_F(LeaseFileLoaderTest, loadLeaseWithZeroLifetime) {
+    // 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");
+
+    boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+    ASSERT_NO_THROW(lf->open()); 
+
+    // Set the error count to 0 to make sure that lease with a zero
+    // lifetime doesn't cause an error.
+    Lease4Storage storage;
+    ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 0));
+
+    // The first lease should be present.
+    Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // The lease with a valid lifetime of 0 should not be loaded.
+    EXPECT_FALSE(getLease<Lease4Ptr>("192.0.2.3", storage));
+}   
+
+
+} // end of anonymous namespace

+ 320 - 1
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-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
@@ -481,4 +481,323 @@ TEST_F(MemfileLeaseMgrTest, versionCheck) {
     LeaseMgrFactory::destroy();
 }
 
+// This test checks that the backend reads DHCPv4 lease data from multiple
+// files.
+TEST_F(MemfileLeaseMgrTest, load4MultipleLeaseFiles) {
+    LeaseFileIO io2("leasefile4_0.csv.2");
+    io2.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+                  "fqdn_fwd,fqdn_rev,hostname\n"
+                  "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,\n"
+                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,\n");
+
+    LeaseFileIO io1("leasefile4_0.csv.1");
+    io1.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+                  "fqdn_fwd,fqdn_rev,hostname\n"
+                  "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,\n"
+                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,\n"
+                  "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,\n");
+
+    LeaseFileIO io("leasefile4_0.csv");
+    io.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+                 "fqdn_fwd,fqdn_rev,hostname\n"
+                 "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,\n"
+                 "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,\n");
+
+    startBackend(V4);
+
+    // This lease only exists in the second file and the cltt should
+    // be 0.
+    Lease4Ptr lease = lmptr_->getLease4(IOAddress("192.0.2.1"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // This lease only exists in the first file and the cltt should
+    // be 0.
+    lease = lmptr_->getLease4(IOAddress("192.0.2.2"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // This lease only exists in the third file and the cltt should
+    // be 0.
+    lease = lmptr_->getLease4(IOAddress("192.0.2.10"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // This lease exists in the first and second file and the cltt
+    // should be calculated using the expiration time and the
+    // valid lifetime from the second file.
+    lease = lmptr_->getLease4(IOAddress("192.0.2.11"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(200, lease->cltt_);
+
+    // Thsi lease exists in the second and third file and the cltt
+    // should be calculated using the expiration time and the
+    // valid lifetime from the third file.
+    lease = lmptr_->getLease4(IOAddress("192.0.2.12"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(200, lease->cltt_);
+}
+
+// This test checks that the lease database backend loads the file with
+// the .completed postfix instead of files with postfixes .1 and .2 if
+// the file with .completed postfix exists.
+TEST_F(MemfileLeaseMgrTest, load4CompletedFile) {
+    LeaseFileIO io2("leasefile4_0.csv.2");
+    io2.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+                  "fqdn_fwd,fqdn_rev,hostname\n"
+                  "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,\n"
+                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,\n");
+
+    LeaseFileIO io1("leasefile4_0.csv.1");
+    io1.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+                  "fqdn_fwd,fqdn_rev,hostname\n"
+                  "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,\n"
+                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,\n"
+                  "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,\n");
+
+    LeaseFileIO io("leasefile4_0.csv");
+    io.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+                 "fqdn_fwd,fqdn_rev,hostname\n"
+                 "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,\n"
+                 "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,\n");
+
+    LeaseFileIO ioc("leasefile4_0.csv.completed");
+    ioc.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+                  "fqdn_fwd,fqdn_rev,hostname\n"
+                  "192.0.2.13,ff:ff:ff:ff:ff:ff,,200,200,8,1,1,,\n");
+
+    startBackend(V4);
+
+    // We expect that this file only holds leases that belong to the
+    // lease file or to the file with .completed postfix.
+    Lease4Ptr lease = lmptr_->getLease4(IOAddress("192.0.2.10"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    lease = lmptr_->getLease4(IOAddress("192.0.2.12"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(200, lease->cltt_);
+
+    // This lease is in the .completed file.
+    lease = lmptr_->getLease4(IOAddress("192.0.2.13"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // Leases from the .1 and .2 files should not be loaded.
+    EXPECT_FALSE(lmptr_->getLease4(IOAddress("192.0.2.11")));
+    EXPECT_FALSE(lmptr_->getLease4(IOAddress("192.0.2.1")));
+}
+
+// This test checks that the backend reads DHCPv6 lease data from multiple
+// files.
+TEST_F(MemfileLeaseMgrTest, load6MultipleLeaseFiles) {
+    LeaseFileIO io2("leasefile6_0.csv.2");
+    io2.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,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+                  "200,200,8,100,0,7,0,1,1,,\n");
+
+    LeaseFileIO io1("leasefile6_0.csv.1");
+    io1.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::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
+                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+                  "300,800,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+                  "200,200,8,100,0,7,0,1,1,,\n");
+
+    LeaseFileIO io("leasefile6_0.csv");
+    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::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+                 "400,1000,8,100,0,7,0,1,1,,\n"
+                 "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
+                 "200,200,8,100,0,7,0,1,1,,\n");
+
+    startBackend(V6);
+
+    // This lease only exists in the first file and the cltt should be 0.
+    Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA,
+                                        IOAddress("2001:db8:1::1"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // This lease exists in the first and second file and the cltt should
+    // be calculated using the expiration time and the valid lifetime
+    // from the second file.
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(500, lease->cltt_);
+
+    // This lease only exists in the second file and the cltt should be 0.
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // This lease exists in the second and third file and the cltt should
+    // be calculated using the expiration time and the valid lifetime
+    // from the third file.
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(600, lease->cltt_);
+
+    // This lease only exists in the third file and the cltt should be 0.
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+}
+
+// This test checks that the backend reads DHCPv6 lease data from the
+// leasefile without the postfix and the file with a .1 postfix when
+// the file with the .2 postfix is missing.
+TEST_F(MemfileLeaseMgrTest, load6MultipleNoSecondFile) {
+    LeaseFileIO io1("leasefile6_0.csv.1");
+    io1.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::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
+                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+                  "300,800,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+                  "200,200,8,100,0,7,0,1,1,,\n");
+
+    LeaseFileIO io("leasefile6_0.csv");
+    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::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+                 "400,1000,8,100,0,7,0,1,1,,\n"
+                 "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
+                 "200,200,8,100,0,7,0,1,1,,\n");
+
+    startBackend(V6);
+
+    // Check that leases from the leasefile6_0 and leasefile6_0.1 have
+    // been loaded.
+    Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(500, lease->cltt_);
+
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(600, lease->cltt_);
+
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // Make sure that a lease which is not in those files is not loaded.
+    EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1")));
+}
+
+// This test checks that the backend reads DHCPv6 lease data from the
+// leasefile without the postfix and the file with a .2 postfix when
+// the file with the .1 postfix is missing.
+TEST_F(MemfileLeaseMgrTest, load6MultipleNoFirstFile) {
+    LeaseFileIO io2("leasefile6_0.csv.2");
+    io2.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,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+                  "200,200,8,100,0,7,0,1,1,,\n");
+
+    LeaseFileIO io("leasefile6_0.csv");
+    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::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+                 "400,1000,8,100,0,7,0,1,1,,\n"
+                 "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
+                 "200,200,8,100,0,7,0,1,1,,\n");
+
+    startBackend(V6);
+
+    // Verify that leases which belong to the leasefile6_0.csv and
+    // leasefile6_0.2 are loaded.
+    Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA,
+                                        IOAddress("2001:db8:1::1"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(600, lease->cltt_);
+
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    // A lease which doesn't belong to these files should not be loaded.
+    EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3")));
+}
+
+
+// This test checks that the lease database backend loads the file with
+// the .completed postfix instead of files with postfixes .1 and .2 if
+// the file with .completed postfix exists.
+TEST_F(MemfileLeaseMgrTest, load6CompletedFile) {
+    LeaseFileIO io2("leasefile6_0.csv.2");
+    io2.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,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+                  "200,200,8,100,0,7,0,1,1,,\n");
+
+    LeaseFileIO io1("leasefile6_0.csv.1");
+    io1.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::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
+                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+                  "300,800,8,100,0,7,0,1,1,,\n"
+                  "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+                  "200,200,8,100,0,7,0,1,1,,\n");
+
+    LeaseFileIO io("leasefile6_0.csv");
+    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::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+                 "400,1000,8,100,0,7,0,1,1,,\n"
+                 "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
+                 "200,200,8,100,0,7,0,1,1,,\n");
+
+    LeaseFileIO ioc("leasefile6_0.csv.completed");
+    ioc.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::125,ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff,"
+                  "400,1000,8,100,0,7,0,1,1,,\n");
+
+    startBackend(V6);
+
+    // We expect that this file only holds leases that belong to the
+    // lease file or to the file with .completed postfix.
+    Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(600, lease->cltt_);
+
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(0, lease->cltt_);
+
+    lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::125"));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(600, lease->cltt_);
+
+    // Leases from the .1 and .2 files should not be loaded.
+    EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1")));
+    EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2")));
+    EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3")));
+}
+
 }; // end of anonymous namespace

+ 9 - 1
src/lib/util/csv_file.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
 // purpose with or without fee is hereby granted, provided that the above
@@ -97,6 +97,14 @@ CSVFile::close() {
     }
 }
 
+bool
+CSVFile::exists() const {
+    std::ifstream fs(filename_.c_str());
+    const bool file_exists = fs.good();
+    fs.close();
+    return (file_exists);
+}
+
 void
 CSVFile::flush() const {
     checkStreamStatusAndReset("flush");

+ 9 - 1
src/lib/util/csv_file.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
 // purpose with or without fee is hereby granted, provided that the above
@@ -320,6 +320,14 @@ public:
     /// @brief Closes the CSV file.
     void close();
 
+    /// @brief Checks if the CSV file exists and can be opened for reading.
+    ///
+    /// This method doesn't check if the existing file has a correct file
+    /// format.
+    ///
+    /// @return true if file exists, false otherwise.
+    bool exists() const;
+
     /// @brief Flushes a file.
     void flush() const;
 

+ 25 - 1
src/lib/util/tests/csv_file_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
 // purpose with or without fee is hereby granted, provided that the above
@@ -454,5 +454,29 @@ TEST_F(CSVFileTest, validateHeader) {
     EXPECT_THROW(csv->open(), CSVFileError);
 }
 
+// This test checks that the exists method of the CSVFile class properly
+// checks that the file exists.
+TEST_F(CSVFileTest, exists) {
+    // Create a new CSV file that contains a header and two data rows.
+    writeFile("animal,age,color\n"
+              "cat,10,white\n"
+              "lion,15,yellow\n");
+
+    boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+    // The CSVFile class should return true even if the file hasn't been
+    // opened.
+    EXPECT_TRUE(csv->exists());
+    // Now open the file and make sure it still returns true.
+    ASSERT_NO_THROW(csv->open());
+    EXPECT_TRUE(csv->exists());
+
+    // Close the file and remove it.
+    csv->close();
+    removeFile();
+
+    // The file should not exist.
+    EXPECT_FALSE(csv->exists());
+}
+
 
 } // end of anonymous namespace