BIND 10 master, updated. 09e6e71abf8bc693e389ebd262fd149b43c1f1d4 [master] Merge branch 'trac3360'

BIND 10 source code commits bind10-changes at lists.isc.org
Thu Apr 3 12:03:04 UTC 2014


The branch, master has been updated
       via  09e6e71abf8bc693e389ebd262fd149b43c1f1d4 (commit)
       via  d78c00959f21b45797d20bf3775688ae163528e9 (commit)
       via  a5e69119eb95f63bc132ebf7dcfa21116ac9a881 (commit)
       via  991fea36336a0303a1f61f1ef0bcfe82d3a682ce (commit)
       via  b96b77070dc8d204c34e69a42c371d59c5385c30 (commit)
       via  5d01646eef2fe130ddfa197bf116d16bd119acdf (commit)
       via  df6af6f3e337a9590f3aa26921e63025a74467f9 (commit)
       via  762f3c3996fa80214ea0f3650b00350800bf6c5b (commit)
       via  8b5ded2f871bd67584c67a380c9736baeb9c246d (commit)
       via  9ed199e32506a1370653a1f9d991cee6c120ab19 (commit)
       via  5785c87a155254aa1367edf6ecb9032f8c1763a5 (commit)
       via  26ddda6791c9d0026c16359ca6906865316c35ca (commit)
       via  56d75260f0a2a5c99117910cde00e18ea330183b (commit)
       via  f056c2a7f9133a37902ae38cc9637da5149ece95 (commit)
       via  e90a1c9c3070fc332372395200651f0ed0aaf09b (commit)
       via  21a15e9e506bcb1879382ca6d532da6c9b4c6d0c (commit)
       via  587f21a5bc1243c06b52bb4b70d23b7dc4300645 (commit)
       via  fbae37a0d687905cc456031131ec020f8182c16d (commit)
       via  3150e9c0e6ccf84d2bce9f14b4d8916c45cb8767 (commit)
       via  6199f3a5ea37a65a2405e571a5b189dc127af138 (commit)
       via  c3f63b985fde7d458d0624960ff3ecb17269709b (commit)
       via  88960c71306e8d91190ae0ca783121edd5d35ffa (commit)
      from  67d67f42c9d2ef4dc10e84694eff1b44cb95dee1 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 09e6e71abf8bc693e389ebd262fd149b43c1f1d4
Merge: 67d67f4 d78c009
Author: Marcin Siodelski <marcin at isc.org>
Date:   Thu Apr 3 14:01:03 2014 +0200

    [master] Merge branch 'trac3360'
    
    Conflicts:
    	doc/guide/bind10-guide.xml

-----------------------------------------------------------------------

Summary of changes:
 doc/guide/bind10-guide.xml                         |   72 ++-
 src/bin/dhcp4/config_parser.cc                     |    2 +-
 src/bin/dhcp4/dhcp4.spec                           |    6 +
 src/bin/dhcp4/dhcp4_srv.h                          |    2 +-
 src/bin/dhcp4/tests/dhcp4_test_utils.h             |    3 +-
 src/bin/dhcp6/config_parser.cc                     |    2 +-
 src/bin/dhcp6/dhcp6.spec                           |    6 +
 src/bin/dhcp6/tests/dhcp6_test_utils.h             |    2 +-
 src/lib/dhcp/duid.cc                               |   54 ++-
 src/lib/dhcp/duid.h                                |   51 ++-
 src/lib/dhcp/hwaddr.cc                             |   48 +-
 src/lib/dhcp/hwaddr.h                              |   34 +-
 src/lib/dhcp/tests/duid_unittest.cc                |   66 ++-
 src/lib/dhcp/tests/hwaddr_unittest.cc              |   33 +-
 src/lib/dhcpsrv/Makefile.am                        |    4 +
 src/lib/dhcpsrv/csv_lease_file4.cc                 |  178 ++++++++
 src/lib/dhcpsrv/csv_lease_file4.h                  |  153 +++++++
 src/lib/dhcpsrv/csv_lease_file6.cc                 |  176 +++++++
 src/lib/dhcpsrv/csv_lease_file6.h                  |  170 +++++++
 src/lib/dhcpsrv/dbaccess_parser.cc                 |   37 +-
 src/lib/dhcpsrv/dbaccess_parser.h                  |   15 +-
 src/lib/dhcpsrv/dhcpsrv_messages.mes               |   32 +-
 src/lib/dhcpsrv/memfile_lease_mgr.cc               |  272 ++++++++++-
 src/lib/dhcpsrv/memfile_lease_mgr.h                |  164 ++++++-
 src/lib/dhcpsrv/tests/Makefile.am                  |    6 +-
 src/lib/dhcpsrv/tests/alloc_engine_unittest.cc     |    4 +-
 src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc  |  182 ++++++++
 src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc  |  223 +++++++++
 src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc  |   90 +++-
 .../dhcpsrv/tests/generic_lease_mgr_unittest.cc    |  152 ++++++-
 src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h |   24 +-
 src/lib/dhcpsrv/tests/lease_file_io.cc             |   69 +++
 src/lib/dhcpsrv/tests/lease_file_io.h              |   65 +++
 src/lib/dhcpsrv/tests/lease_mgr_unittest.cc        |    2 +-
 .../dhcpsrv/tests/memfile_lease_mgr_unittest.cc    |  204 ++++++++-
 src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc  |   23 +-
 src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc  |    5 +-
 src/lib/util/Makefile.am                           |    3 +-
 src/lib/util/csv_file.cc                           |  374 +++++++++++++++
 src/lib/util/csv_file.h                            |  480 ++++++++++++++++++++
 src/lib/util/tests/Makefile.am                     |    3 +
 src/lib/util/tests/csv_file_unittest.cc            |  439 ++++++++++++++++++
 42 files changed, 3826 insertions(+), 104 deletions(-)
 create mode 100644 src/lib/dhcpsrv/csv_lease_file4.cc
 create mode 100644 src/lib/dhcpsrv/csv_lease_file4.h
 create mode 100644 src/lib/dhcpsrv/csv_lease_file6.cc
 create mode 100644 src/lib/dhcpsrv/csv_lease_file6.h
 create mode 100644 src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc
 create mode 100644 src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc
 create mode 100644 src/lib/dhcpsrv/tests/lease_file_io.cc
 create mode 100644 src/lib/dhcpsrv/tests/lease_file_io.h
 create mode 100644 src/lib/util/csv_file.cc
 create mode 100644 src/lib/util/csv_file.h
 create mode 100644 src/lib/util/tests/csv_file_unittest.cc

-----------------------------------------------------------------------
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index d73dda3..c59ac29 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -3836,10 +3836,44 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       </para>
 
       <section>
+      <title>Default storage for leases</title>
+      <para>
+        The server is able to store lease data in different repositories. Larger deployments
+        may elect to store leases in a database.
+        <xref linkend="database-configuration4"/> describes one way to do it.
+        By default, the server will use a CSV file rather than a database to store
+        lease information. One of the advantages of using a file is that it eliminates
+        dependency on third party database software.
+      </para>
+      <para>
+        The configuration of the file backend (Memfile)
+        is controlled through the Dhcp4/lease-database parameters. When default
+        parameters are used, the Memfile backend will write leases to a disk in the
+        [bind10-install-dir]/var/bind10/kea-leases4.csv.
+      </para>
+      <para>
+        It is possible to alter the default location of the lease file. The following
+        configuration:
+<screen>
+> <userinput>config set Dhcp4/lease-database/type "memfile"</userinput>
+> <userinput>config set Dhcp4/lease-database/persist true</userinput>
+> <userinput>config set Dhcp4/lease-database/name "/tmp/kea-leases4.csv"</userinput>
+> <userinput>config commit</userinput>
+</screen>
+        will change the default location of the lease file to /tmp/kea-leases4.csv.
+      </para>
+      <para>
+        The "persist" parameter controls whether the leases are written to disk.
+        It is strongly recommended that this parameter is set to "true" at all times
+        during the normal operation of the server
+      </para>
+      </section>
+
+      <section id="database-configuration4">
       <title>Database Configuration</title>
       <para>
       All leases issued by the server are stored in the lease database.  Currently
-      there are 3 database backends available: MySQL, PostgreSQL and experimental memfile.
+      there are 3 database backends available: MySQL, PostgreSQL and memfile.
       <footnote>
       <para>
       The server comes with an in-memory database ("memfile") configured as the default
@@ -5313,10 +5347,44 @@ Dhcp6/dhcp-ddns/qualifying-suffix   "example.com"   string
       </note>
 
       <section>
+      <title>Default storage for leases</title>
+      <para>
+        The server is able to store lease data in different repositories. Larger deployments
+        may elect to store leases in a database.
+        <xref linkend="database-configuration6"/> describes one way to do it.
+        By default, the server will use a CSV file rather than a database to store
+        lease information. One of the advantages of using a file is that it eliminates
+        dependency on third party database software.
+      </para>
+      <para>
+        The configuration of the file backend (Memfile)
+        is controlled through the Dhcp6/lease-database parameters. When default
+        parameters are left, the Memfile backend will write leases to a disk in the
+        [bind10-install-dir]/var/bind10/kea-leases6.csv.
+      </para>
+      <para>
+        It is possible to alter the default location of the lease file. The following
+        configuration:
+<screen>
+> <userinput>config set Dhcp4/lease-database/type "memfile"</userinput>
+> <userinput>config set Dhcp4/lease-database/persist true</userinput>
+> <userinput>config set Dhcp4/lease-database/leasefile "/tmp/kea-leases6.csv"</userinput>
+> <userinput>config commit</userinput>
+</screen>
+        will change the default location of the lease file to /tmp/kea-leases6.csv.
+      </para>
+      <para>
+        The "persist" parameter controls whether the leases are written to disk.
+        It is strongly recommended that this parameter is set to "true" at all times
+        during the normal operation of the server.
+      </para>
+      </section>
+
+      <section id="database-configuration6">
       <title>Database Configuration</title>
       <para>
       All leases issued by the server are stored in the lease database. Currently
-      there are 3 database backends available: MySQL, PostgreSQL and experimental memfile.
+      there are 3 database backends available: MySQL, PostgreSQL and memfile.
       <footnote>
       <para>
       The server comes with an in-memory database ("memfile") configured as the default
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index d22fef2..5e9e9d6 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -412,7 +412,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
         parser  = new StringParser(config_id,
                                     globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
-        parser = new DbAccessParser(config_id);
+        parser = new DbAccessParser(config_id, *globalContext());
     } else if (config_id.compare("hooks-libraries") == 0) {
         parser = new HooksLibrariesParser(config_id);
     } else if (config_id.compare("echo-client-id") == 0) {
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 1eae833..b0f8314 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -191,6 +191,12 @@
                 "item_type": "string",
                 "item_optional": true,
                 "item_default": ""
+            },
+            {
+                "item_name": "persist",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": true
             }
         ]
       },
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 4bae907..d041258 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -88,7 +88,7 @@ public:
     /// @param direct_response_desired specifies if it is desired to
     /// use direct V4 traffic.
     Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
-              const char* dbconfig = "type=memfile",
+              const char* dbconfig = "type=memfile universe=4",
               const bool use_bcast = true,
               const bool direct_response_desired = true);
 
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h
index 48befdf..b978979 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.h
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h
@@ -117,7 +117,8 @@ public:
     /// @param port port number to listen on; the default value 0 indicates
     /// that sockets should not be opened.
     NakedDhcpv4Srv(uint16_t port = 0)
-        : Dhcpv4Srv(port, "type=memfile", false, false) {
+        : Dhcpv4Srv(port, "type=memfile universe=4 persist=false",
+                    false, false) {
         // Create fixed server id.
         server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
                                             asiolink::IOAddress("192.0.3.1")));
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 9897f63..272fe0a 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -643,7 +643,7 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
         parser  = new StringParser(config_id,
                                    globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
-        parser = new DbAccessParser(config_id);
+        parser = new DbAccessParser(config_id, *globalContext());
     } else if (config_id.compare("hooks-libraries") == 0) {
         parser = new HooksLibrariesParser(config_id);
     } else if (config_id.compare("dhcp-ddns") == 0) {
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 5efc740..496e282 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -185,6 +185,12 @@
                 "item_type": "string",
                 "item_optional": true,
                 "item_default": ""
+            },
+            {
+                "item_name": "persist",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": true
             }
         ]
       },
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h
index a006e18..90ed4bc 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.h
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h
@@ -45,7 +45,7 @@ class NakedDhcpv6Srv: public isc::dhcp::Dhcpv6Srv {
 public:
     NakedDhcpv6Srv(uint16_t port) : isc::dhcp::Dhcpv6Srv(port) {
         // Open the "memfile" database for leases
-        std::string memfile = "type=memfile";
+        std::string memfile = "type=memfile universe=6 persist=false";
         isc::dhcp::LeaseMgrFactory::create(memfile);
     }
 
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index 80fa724..e4b7592 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 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,8 +14,11 @@
 
 #include <dhcp/duid.h>
 #include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
 #include <util/io_utilities.h>
-
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
 #include <iomanip>
 #include <sstream>
 #include <vector>
@@ -46,6 +49,41 @@ DUID::DUID(const uint8_t* data, size_t len) {
     duid_ = std::vector<uint8_t>(data, data + len);
 }
 
+std::vector<uint8_t>
+DUID::decode(const std::string& text) {
+    /// @todo optimize stream operations here.
+    std::vector<std::string> split_text;
+    boost::split(split_text, text, boost::is_any_of(":"),
+                 boost::algorithm::token_compress_off);
+
+    std::ostringstream s;
+    for (size_t i = 0; i < split_text.size(); ++i) {
+        // If there are multiple tokens and the current one is empty, it
+        // means that two consecutive colons were specified. This is not
+        // allowed for client identifier.
+        if ((split_text.size() > 1) && split_text[i].empty()) {
+            isc_throw(isc::BadValue, "invalid identifier '" << text << "': "
+                      << " tokens must be separated with a single colon");
+
+        } else if (split_text[i].size() == 1) {
+            s << "0";
+
+        } else if (split_text[i].size() > 2) {
+            isc_throw(isc::BadValue, "invalid identifier '" << text << "'");
+        }
+        s << split_text[i];
+    }
+
+    std::vector<uint8_t> binary;
+    try {
+        util::encode::decodeHex(s.str(), binary);
+    } catch (const Exception& ex) {
+        isc_throw(isc::BadValue, "failed to create identifier from text '"
+                  << text << "': " << ex.what());
+    }
+    return (binary);
+}
+
 const std::vector<uint8_t>& DUID::getDuid() const {
     return (duid_);
 }
@@ -62,6 +100,12 @@ DUID::DUIDType DUID::getType() const {
     }
 }
 
+DUID
+DUID::fromText(const std::string& text) {
+    std::vector<uint8_t> binary = decode(text);
+    return DUID(binary);
+}
+
 std::string DUID::toText() const {
     std::stringstream tmp;
     tmp << std::hex;
@@ -118,6 +162,12 @@ std::string ClientId::toText() const {
     return (DUID::toText());
 }
 
+ClientIdPtr
+ClientId::fromText(const std::string& text) {
+    std::vector<uint8_t> binary = decode(text);
+    return (ClientIdPtr(new ClientId(binary)));
+}
+
 // Compares two client-ids
 bool ClientId::operator==(const ClientId& other) const {
     return (this->duid_ == other.duid_);
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 1cd8388..4feba1b 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 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
@@ -69,6 +69,17 @@ class DUID {
     /// @brief Returns the DUID type
     DUIDType getType() const;
 
+    /// @brief Create DUID from the textual format.
+    ///
+    /// This static function parses a DUID specified in the textual format.
+    /// Internally it uses @c DUID::decode to parse the DUID.
+    ///
+    /// @param text DUID in the hexadecimal format with digits representing
+    /// individual bytes separated by colons.
+    ///
+    /// @throw isc::BadValue if parsing the DUID failed.
+    static DUID fromText(const std::string& text);
+
     /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
     std::string toText() const;
 
@@ -79,6 +90,23 @@ class DUID {
     bool operator!=(const DUID& other) const;
 
  protected:
+
+    /// @brief Decodes the textual format of the DUID.
+    ///
+    /// The format being parsed should match the DUID representation returned
+    /// by the @c DUID::toText method, i.e. the pairs of hexadecimal digits
+    /// representing bytes of DUID must be separated by colons. Usually the
+    /// single byte is represented by two hexadecimal digits. However, this
+    /// function allows one digit per byte. In this case, a zero is prepended
+    /// before the conversion. For example, a DUID 0:1:2::4:5 equals to
+    /// 00:01:02:00:04:05.
+    ///
+    /// @param text DUID in the hexadecimal format with digits representing
+    /// individual bytes separated by colons.
+    ///
+    /// @throw isc::BadValue if parsing the DUID failed.
+    static std::vector<uint8_t> decode(const std::string& text);
+
     /// The actual content of the DUID
     std::vector<uint8_t> duid_;
 };
@@ -86,7 +114,10 @@ class DUID {
 /// @brief Shared pointer to a DUID
 typedef boost::shared_ptr<DUID> DuidPtr;
 
-
+/// @brief Forward declaration to the @c ClientId class.
+class ClientId;
+/// @brief Shared pointer to a Client ID.
+typedef boost::shared_ptr<ClientId> ClientIdPtr;
 
 /// @brief Holds Client identifier or client IPv4 address
 ///
@@ -130,6 +161,19 @@ public:
     /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
     std::string toText() const;
 
+    /// @brief Create client identifier from the textual format.
+    ///
+    /// This static function creates the instance of the @c ClientId from the
+    /// textual format. Internally it calls @c DUID::fromText. The format of
+    /// the input must match the format of the DUID in @c DUID::fromText.
+    ///
+    /// @param text Client identifier in the textual format.
+    ///
+    /// @return Pointer to the instance of the @c ClientId.
+    /// @throw isc::BadValue if parsing the client identifier failed.
+    /// @throw isc::OutOfRange if the client identifier is truncated.
+    static ClientIdPtr fromText(const std::string& text);
+
     /// @brief Compares two client-ids for equality
     bool operator==(const ClientId& other) const;
 
@@ -137,9 +181,6 @@ public:
     bool operator!=(const ClientId& other) const;
 };
 
-/// @brief Shared pointer to a Client ID.
-typedef boost::shared_ptr<ClientId> ClientIdPtr;
-
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 
diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc
index eb23b44..5f13080 100644
--- a/src/lib/dhcp/hwaddr.cc
+++ b/src/lib/dhcp/hwaddr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 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
@@ -15,6 +15,10 @@
 #include <dhcp/hwaddr.h>
 #include <dhcp/dhcp4.h>
 #include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
 #include <iomanip>
 #include <sstream>
 #include <vector>
@@ -42,9 +46,11 @@ HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
     }
 }
 
-std::string HWAddr::toText() const {
+std::string HWAddr::toText(bool include_htype) const {
     std::stringstream tmp;
-    tmp << "hwtype=" << static_cast<int>(htype_) << " ";
+    if (include_htype) {
+        tmp << "hwtype=" << static_cast<int>(htype_) << " ";
+    }
     tmp << std::hex;
     bool delim = false;
     for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
@@ -58,6 +64,42 @@ std::string HWAddr::toText() const {
     return (tmp.str());
 }
 
+HWAddr
+HWAddr::fromText(const std::string& text, const uint8_t htype) {
+    /// @todo optimize stream operations here.
+    std::vector<std::string> split_text;
+    boost::split(split_text, text, boost::is_any_of(":"),
+                 boost::algorithm::token_compress_off);
+
+    std::ostringstream s;
+    for (size_t i = 0; i < split_text.size(); ++i) {
+        // If there are multiple tokens and the current one is empty, it
+        // means that two consecutive colons were specified. This is not
+        // allowed for hardware address.
+        if ((split_text.size() > 1) && split_text[i].empty()) {
+            isc_throw(isc::BadValue, "failed to create hardware address"
+                      " from text '" << text << "': tokens of the hardware"
+                      " address must be separated with a single colon");
+
+        } else  if (split_text[i].size() == 1) {
+            s << "0";
+
+        } else if (split_text[i].size() > 2) {
+            isc_throw(isc::BadValue, "invalid hwaddr '" << text << "'");
+        }
+        s << split_text[i];
+    }
+
+    std::vector<uint8_t> binary;
+    try {
+        util::encode::decodeHex(s.str(), binary);
+    } catch (const Exception& ex) {
+        isc_throw(isc::BadValue, "failed to create hwaddr from text '"
+                  << text << "': " << ex.what());
+    }
+    return (HWAddr(binary, htype));
+}
+
 bool HWAddr::operator==(const HWAddr& other) const {
     return ((this->htype_  == other.htype_) &&
             (this->hwaddr_ == other.hwaddr_));
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
index 848ad23..3b4d626 100644
--- a/src/lib/dhcp/hwaddr.h
+++ b/src/lib/dhcp/hwaddr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 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
@@ -54,8 +54,36 @@ public:
     // Hardware type
     uint8_t htype_;
 
-    /// @brief Returns textual representation of a client-id (e.g. 00:01:02:03)
-    std::string toText() const;
+    /// @brief Returns textual representation of a hardware address
+    /// (e.g. 00:01:02:03:04:05)
+    ///
+    /// @param include_htype Boolean value which controls whether the hardware
+    /// type is included in the returned string (true), or not (false).
+    ///
+    /// @return Hardware address in the textual format.
+    std::string toText(bool include_htype = true) const;
+
+    /// @brief Creates instance of the hardware address from textual format.
+    ///
+    /// This function parses HW address specified as text and creates the
+    /// corresponding @c HWAddr instance. The hexadecimal digits representing
+    /// individual bytes of the hardware address should be separated with
+    /// colons. Typically, two digits per byte are used. However, this function
+    /// allows for 1 digit per HW address byte. In this case, the digit is
+    /// prepended with '0' during conversion to binary value.
+    ///
+    /// This function can be used to perform a reverse operation to the
+    /// @c HWAddr::toText(false).
+    ///
+    /// The instance created by this function sets HTYPE_ETHER as a hardware
+    /// type.
+    ///
+    /// @param text HW address in the textual format.
+    /// @param htype Hardware type.
+    ///
+    /// @return Instance of the HW address created from text.
+    static HWAddr fromText(const std::string& text,
+                           const uint8_t htype = HTYPE_ETHER);
 
     /// @brief Compares two hardware addresses for equality
     bool operator==(const HWAddr& other) const;
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index 36499c2..4670440 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014  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
@@ -126,6 +126,37 @@ TEST(DuidTest, getType) {
     EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
 }
 
+// This test checks that the DUID instance can be created from the textual
+// format and that error is reported if the textual format is invalid.
+TEST(DuidTest, fromText) {
+    scoped_ptr<DUID> duid;
+    // DUID with only decimal digits.
+    ASSERT_NO_THROW(
+        duid.reset(new DUID(DUID::fromText("00:01:02:03:04:05:06")))
+    );
+    EXPECT_EQ("00:01:02:03:04:05:06", duid->toText());
+    // DUID with some hexadecimal digits (upper case and lower case).
+    ASSERT_NO_THROW(
+        duid.reset(new DUID(DUID::fromText("00:aa:bb:CD:ee:EF:ab")))
+    );
+    EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", duid->toText());
+    // DUID with one digit for a particular byte.
+    ASSERT_NO_THROW(
+        duid.reset(new DUID(DUID::fromText("00:a:bb:D:ee:EF:ab")))
+    );
+    EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", duid->toText());
+    // Repeated colon sign is not allowed.
+    EXPECT_THROW(
+        duid.reset(new DUID(DUID::fromText("00::bb:D:ee:EF:ab"))),
+        isc::BadValue
+    );
+    // DUID with excessive number of digits for one of the bytes.
+    EXPECT_THROW(
+       duid.reset(new DUID(DUID::fromText("00:01:021:03:04:05:06"))),
+       isc::BadValue
+    );
+}
+
 // Test checks if the toText() returns valid texual representation
 TEST(DuidTest, toText) {
     uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
@@ -249,4 +280,37 @@ TEST(ClientIdTest, toText) {
     EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
 }
 
+// This test checks that the ClientId instance can be created from the textual
+// format and that error is reported if the textual format is invalid.
+TEST(ClientIdTest, fromText) {
+    ClientIdPtr cid;
+    // ClientId with only decimal digits.
+    ASSERT_NO_THROW(
+        cid = ClientId::fromText("00:01:02:03:04:05:06")
+    );
+    EXPECT_EQ("00:01:02:03:04:05:06", cid->toText());
+    // ClientId with some hexadecimal digits (upper case and lower case).
+    ASSERT_NO_THROW(
+        cid = ClientId::fromText("00:aa:bb:CD:ee:EF:ab")
+    );
+    EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", cid->toText());
+    // ClientId with one digit for a particular byte.
+    ASSERT_NO_THROW(
+        cid = ClientId::fromText("00:a:bb:D:ee:EF:ab")
+    );
+    EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText());
+    // Repeated colon sign in the ClientId is not allowed.
+    EXPECT_THROW(
+        ClientId::fromText("00::bb:D:ee:EF:ab"),
+        isc::BadValue
+
+    );
+    // ClientId with excessive number of digits for one of the bytes.
+    EXPECT_THROW(
+        ClientId::fromText("00:01:021:03:04:05:06"),
+        isc::BadValue
+    );
+}
+
+
 } // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc
index bf2eb9a..7a904d3 100644
--- a/src/lib/dhcp/tests/hwaddr_unittest.cc
+++ b/src/lib/dhcp/tests/hwaddr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012, 2014 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
@@ -108,6 +108,9 @@ TEST(HWAddrTest, toText) {
 
     EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
 
+    // In some cases we don't want htype value to be included. Check that
+    // it can be forced.
+    EXPECT_EQ("00:01:02:03:04:05", hw->toText(false));
 }
 
 TEST(HWAddrTest, stringConversion) {
@@ -131,5 +134,33 @@ TEST(HWAddrTest, stringConversion) {
     EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
 }
 
+// Checks that the HW address can be created from the textual format.
+TEST(HWAddrTest, fromText) {
+    scoped_ptr<HWAddr> hwaddr;
+    // Create HWAddr from text.
+    ASSERT_NO_THROW(
+        hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:A:bc:d:67")));
+    );
+    EXPECT_EQ("00:01:0a:bc:0d:67", hwaddr->toText(false));
+
+    // HWAddr class should allow empty address.
+    ASSERT_NO_THROW(
+        hwaddr.reset(new HWAddr(HWAddr::fromText("")));
+    );
+    EXPECT_TRUE(hwaddr->toText(false).empty());
+
+    // HWAddr should not allow multiple consecutive colons.
+    EXPECT_THROW(
+       hwaddr.reset(new HWAddr(HWAddr::fromText("00::01:00:bc:0d:67"))),
+       isc::BadValue
+    );
+
+    // There should be no more than two digits per byte of the HW addr.
+    EXPECT_THROW(
+       hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:00A:bc:0d:67"))),
+       isc::BadValue
+    );
+
+}
 
 } // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index dffdcf4..cb0ee32 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -36,12 +36,16 @@ AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
 
 # Make sure the generated files are deleted in a "clean" operation
 CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc s-messages
+# Remove CSV files created by the CSVLeaseFile6 and CSVLeaseFile4 unit tests.
+CLEANFILES += *.csv
 
 lib_LTLIBRARIES = libb10-dhcpsrv.la
 libb10_dhcpsrv_la_SOURCES  =
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
+libb10_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
+libb10_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
 libb10_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
 libb10_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
diff --git a/src/lib/dhcpsrv/csv_lease_file4.cc b/src/lib/dhcpsrv/csv_lease_file4.cc
new file mode 100644
index 0000000..49760c7
--- /dev/null
+++ b/src/lib/dhcpsrv/csv_lease_file4.cc
@@ -0,0 +1,178 @@
+// Copyright (C) 2014 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 <dhcpsrv/csv_lease_file4.h>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+CSVLeaseFile4::CSVLeaseFile4(const std::string& filename)
+    : CSVFile(filename) {
+    initColumns();
+}
+
+void
+CSVLeaseFile4::append(const Lease4& lease) const {
+    CSVRow row(getColumnCount());
+    row.writeAt(getColumnIndex("address"), lease.addr_.toText());
+    HWAddr hwaddr(lease.hwaddr_, HTYPE_ETHER);
+    row.writeAt(getColumnIndex("hwaddr"), hwaddr.toText(false));
+    // Client id may be unset (NULL).
+    if (lease.client_id_) {
+        row.writeAt(getColumnIndex("client_id"), lease.client_id_->toText());
+    }
+    row.writeAt(getColumnIndex("valid_lifetime"), lease.valid_lft_);
+    row.writeAt(getColumnIndex("expire"), lease.cltt_ + lease.valid_lft_);
+    row.writeAt(getColumnIndex("subnet_id"), lease.subnet_id_);
+    row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
+    row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
+    row.writeAt(getColumnIndex("hostname"), lease.hostname_);
+    CSVFile::append(row);
+}
+
+bool
+CSVLeaseFile4::next(Lease4Ptr& lease) {
+    // Read the CSV row and try to create a lease from the values read.
+    // This may easily result in exception. We don't want this function
+    // to throw exceptions, so we catch them all and rather return the
+    // false value.
+    try {
+        // Get the row of CSV values.
+        CSVRow row;
+        CSVFile::next(row);
+        // The empty row signals EOF.
+        if (row == CSVFile::EMPTY_ROW()) {
+            lease.reset();
+            return (true);
+        }
+
+        // Get client id. It is possible that the client id is empty and the
+        // returned pointer is NULL. This is ok, but if the client id is NULL,
+        // we need to be careful to not use the NULL pointer.
+        ClientIdPtr client_id = readClientId(row);
+        std::vector<uint8_t> client_id_vec;
+        if (client_id) {
+            client_id_vec = client_id->getClientId();
+        }
+        size_t client_id_len = client_id_vec.size();
+
+        // Get the HW address. It should never be empty and the readHWAddr checks
+        // that.
+        HWAddr hwaddr = readHWAddr(row);
+        lease.reset(new Lease4(readAddress(row),
+                               &hwaddr.hwaddr_[0], hwaddr.hwaddr_.size(),
+                               client_id_vec.empty() ? NULL : &client_id_vec[0],
+                               client_id_len,
+                               readValid(row),
+                               0, 0, // t1, t2 = 0
+                               readCltt(row),
+                               readSubnetID(row),
+                               readFqdnFwd(row),
+                               readFqdnRev(row),
+                               readHostname(row)));
+
+    } catch (std::exception& ex) {
+        // The lease might have been created, so let's set it back to NULL to
+        // signal that lease hasn't been parsed.
+        lease.reset();
+        setReadMsg(ex.what());
+        return (false);
+    }
+    return (true);
+}
+
+void
+CSVLeaseFile4::initColumns() {
+    addColumn("address");
+    addColumn("hwaddr");
+    addColumn("client_id");
+    addColumn("valid_lifetime");
+    addColumn("expire");
+    addColumn("subnet_id");
+    addColumn("fqdn_fwd");
+    addColumn("fqdn_rev");
+    addColumn("hostname");
+}
+
+IOAddress
+CSVLeaseFile4::readAddress(const CSVRow& row) {
+    IOAddress address(row.readAt(getColumnIndex("address")));
+    return (address);
+}
+
+HWAddr
+CSVLeaseFile4::readHWAddr(const CSVRow& row) {
+    HWAddr hwaddr = HWAddr::fromText(row.readAt(getColumnIndex("hwaddr")));
+    if (hwaddr.hwaddr_.empty()) {
+        isc_throw(isc::BadValue, "hardware address in the lease file"
+                  " must not be empty");
+    }
+    return (hwaddr);
+}
+
+ClientIdPtr
+CSVLeaseFile4::readClientId(const CSVRow& row) {
+    std::string client_id = row.readAt(getColumnIndex("client_id"));
+    // NULL client ids are allowed in DHCPv4.
+    if (client_id.empty()) {
+        return (ClientIdPtr());
+    }
+    ClientIdPtr cid = ClientId::fromText(client_id);
+    return (cid);
+}
+
+uint32_t
+CSVLeaseFile4::readValid(const CSVRow& row) {
+    uint32_t valid =
+        row.readAndConvertAt<uint32_t>(getColumnIndex("valid_lifetime"));
+    return (valid);
+}
+
+time_t
+CSVLeaseFile4::readCltt(const CSVRow& row) {
+    uint32_t cltt = row.readAndConvertAt<uint32_t>(getColumnIndex("expire"))
+        - readValid(row);
+    return (cltt);
+}
+
+SubnetID
+CSVLeaseFile4::readSubnetID(const CSVRow& row) {
+    SubnetID subnet_id =
+        row.readAndConvertAt<SubnetID>(getColumnIndex("subnet_id"));
+    return (subnet_id);
+}
+
+bool
+CSVLeaseFile4::readFqdnFwd(const CSVRow& row) {
+    bool fqdn_fwd = row.readAndConvertAt<bool>(getColumnIndex("fqdn_fwd"));
+    return (fqdn_fwd);
+}
+
+bool
+CSVLeaseFile4::readFqdnRev(const CSVRow& row) {
+    bool fqdn_rev = row.readAndConvertAt<bool>(getColumnIndex("fqdn_rev"));
+    return (fqdn_rev);
+}
+
+std::string
+CSVLeaseFile4::readHostname(const CSVRow& row) {
+    std::string hostname = row.readAt(getColumnIndex("hostname"));
+    return (hostname);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/csv_lease_file4.h b/src/lib/dhcpsrv/csv_lease_file4.h
new file mode 100644
index 0000000..fc13ea2
--- /dev/null
+++ b/src/lib/dhcpsrv/csv_lease_file4.h
@@ -0,0 +1,153 @@
+// Copyright (C) 2014 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 CSV_LEASE_FILE4_H
+#define CSV_LEASE_FILE4_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet.h>
+#include <util/csv_file.h>
+#include <stdint.h>
+#include <string>
+#include <time.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Provides methods to access CSV file with DHCPv4 leases.
+///
+/// This class contains methods customized to read and write DHCPv4 leases from
+/// and to the CSV file. It expects that the CSV file being parsed contains a
+/// set of columns with well known names (initialized in the class constructor).
+///
+/// @todo This class doesn't validate the lease values read from the file.
+/// The @c Lease4 is a structure that should be itself responsible for this
+/// validation (see http://bind10.isc.org/ticket/2405). However, when #2405
+/// is implemented, the @c next function may need to be updated to use the
+/// validation capablity of @c Lease4.
+class CSVLeaseFile4 : public isc::util::CSVFile {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes columns of the lease file.
+    ///
+    /// @param filename Name of the lease file.
+    CSVLeaseFile4(const std::string& filename);
+
+    /// @brief Appends the lease record to the CSV file.
+    ///
+    /// This function doesn't throw exceptions itself. In theory, exceptions
+    /// are possible when the index of the indexes of the values being written
+    /// to the file are invalid. However, this would have been a programming
+    /// error.
+    ///
+    /// @param lease Structure representing a DHCPv4 lease.
+    void append(const Lease4& lease) const;
+
+    /// @brief Reads next lease from the CSV file.
+    ///
+    /// If this function hits an error during lease read, it sets the error
+    /// message using @c CSVFile::setReadMsg and returns false. The error
+    /// string may be read using @c CSVFile::getReadMsg.
+    ///
+    /// This function is exception safe.
+    ///
+    /// @param [out] lease Pointer to the lease read from CSV file or
+    /// NULL pointer if lease hasn't been read.
+    ///
+    /// @return Boolean value indicating that the new lease has been
+    /// read from the CSV file (if true), or that the error has occurred
+    /// (false).
+    ///
+    /// @todo Make sure that the values read from the file are correct.
+    /// The appropriate @c Lease4 validation mechanism should be used once
+    /// ticket http://bind10.isc.org/ticket/2405 is implemented.
+    bool next(Lease4Ptr& lease);
+
+private:
+
+    /// @brief Initializes columns of the CSV file holding leases.
+    ///
+    /// This function initializes the following columns:
+    /// - address
+    /// - hwaddr
+    /// - client_id
+    /// - valid_lifetime
+    /// - expire
+    /// - subnet_id
+    /// - fqdn_fwd
+    /// - fqdn_rev
+    /// - hostname
+    void initColumns();
+
+    ///
+    /// @name Methods which read specific lease fields from the CSV row.
+    ///
+    //@{
+    ///
+    /// @brief Reads lease address from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    asiolink::IOAddress readAddress(const util::CSVRow& row);
+
+    /// @brief Reads HW address from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    HWAddr readHWAddr(const util::CSVRow& row);
+
+    /// @brief Reads client identifier from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    ClientIdPtr readClientId(const util::CSVRow& row);
+
+    /// @brief Reads valid lifetime from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    uint32_t readValid(const util::CSVRow& row);
+
+    /// @brief Reads cltt value from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    time_t readCltt(const util::CSVRow& row);
+
+    /// @brief Reads subnet id from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    SubnetID readSubnetID(const util::CSVRow& row);
+
+    /// @brief Reads the FQDN forward flag from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    bool readFqdnFwd(const util::CSVRow& row);
+
+    /// @brief Reads the FQDN reverse flag from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    bool readFqdnRev(const util::CSVRow& row);
+
+    /// @brief Reads hostname from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    std::string readHostname(const util::CSVRow& row);
+    //@}
+
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CSV_LEASE_FILE4_H
diff --git a/src/lib/dhcpsrv/csv_lease_file6.cc b/src/lib/dhcpsrv/csv_lease_file6.cc
new file mode 100644
index 0000000..4965df5
--- /dev/null
+++ b/src/lib/dhcpsrv/csv_lease_file6.cc
@@ -0,0 +1,176 @@
+// Copyright (C) 2014 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 <dhcpsrv/csv_lease_file6.h>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+CSVLeaseFile6::CSVLeaseFile6(const std::string& filename)
+    : CSVFile(filename) {
+    initColumns();
+}
+
+void
+CSVLeaseFile6::append(const Lease6& lease) const {
+    CSVRow row(getColumnCount());
+    row.writeAt(getColumnIndex("address"), lease.addr_.toText());
+    row.writeAt(getColumnIndex("duid"), lease.duid_->toText());
+    row.writeAt(getColumnIndex("valid_lifetime"), lease.valid_lft_);
+    row.writeAt(getColumnIndex("expire"), lease.cltt_ + lease.valid_lft_);
+    row.writeAt(getColumnIndex("subnet_id"), lease.subnet_id_);
+    row.writeAt(getColumnIndex("pref_lifetime"), lease.preferred_lft_);
+    row.writeAt(getColumnIndex("lease_type"), lease.type_);
+    row.writeAt(getColumnIndex("iaid"), lease.iaid_);
+    row.writeAt(getColumnIndex("prefix_len"),
+                static_cast<int>(lease.prefixlen_));
+    row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
+    row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
+    row.writeAt(getColumnIndex("hostname"), lease.hostname_);
+    CSVFile::append(row);
+}
+
+bool
+CSVLeaseFile6::next(Lease6Ptr& lease) {
+    // Read the CSV row and try to create a lease from the values read.
+    // This may easily result in exception. We don't want this function
+    // to throw exceptions, so we catch them all and rather return the
+    // false value.
+    try {
+        // Get the row of CSV values.
+        CSVRow row;
+        CSVFile::next(row);
+        // The empty row signals EOF.
+        if (row == CSVFile::EMPTY_ROW()) {
+            lease.reset();
+            return (true);
+        }
+
+        lease.reset(new Lease6(readType(row), readAddress(row), readDUID(row),
+                               readIAID(row), readPreferred(row),
+                               readValid(row), 0, 0, // t1, t2 = 0
+                               readSubnetID(row),
+                               readPrefixLen(row)));
+        lease->cltt_ = readCltt(row);
+        lease->fqdn_fwd_ = readFqdnFwd(row);
+        lease->fqdn_rev_ = readFqdnRev(row);
+        lease->hostname_ = readHostname(row);
+
+    } catch (std::exception& ex) {
+        // The lease might have been created, so let's set it back to NULL to
+        // signal that lease hasn't been parsed.
+        lease.reset();
+        setReadMsg(ex.what());
+        return (false);
+    }
+    return (true);
+}
+
+void
+CSVLeaseFile6::initColumns() {
+    addColumn("address");
+    addColumn("duid");
+    addColumn("valid_lifetime");
+    addColumn("expire");
+    addColumn("subnet_id");
+    addColumn("pref_lifetime");
+    addColumn("lease_type");
+    addColumn("iaid");
+    addColumn("prefix_len");
+    addColumn("fqdn_fwd");
+    addColumn("fqdn_rev");
+    addColumn("hostname");
+}
+
+Lease::Type
+CSVLeaseFile6::readType(const CSVRow& row) {
+    return (static_cast<Lease::Type>
+            (row.readAndConvertAt<int>(getColumnIndex("lease_type"))));
+}
+
+IOAddress
+CSVLeaseFile6::readAddress(const CSVRow& row) {
+    IOAddress address(row.readAt(getColumnIndex("address")));
+    return (address);
+}
+
+DuidPtr
+CSVLeaseFile6::readDUID(const util::CSVRow& row) {
+    DuidPtr duid(new DUID(DUID::fromText(row.readAt(getColumnIndex("duid")))));
+    return (duid);
+}
+
+uint32_t
+CSVLeaseFile6::readIAID(const CSVRow& row) {
+    uint32_t iaid = row.readAndConvertAt<uint32_t>(getColumnIndex("iaid"));
+    return (iaid);
+}
+
+uint32_t
+CSVLeaseFile6::readPreferred(const CSVRow& row) {
+    uint32_t pref =
+        row.readAndConvertAt<uint32_t>(getColumnIndex("pref_lifetime"));
+    return (pref);
+}
+
+uint32_t
+CSVLeaseFile6::readValid(const CSVRow& row) {
+    uint32_t valid =
+        row.readAndConvertAt<uint32_t>(getColumnIndex("valid_lifetime"));
+    return (valid);
+}
+
+uint32_t
+CSVLeaseFile6::readCltt(const CSVRow& row) {
+    uint32_t cltt = row.readAndConvertAt<uint32_t>(getColumnIndex("expire"))
+        - readValid(row);
+    return (cltt);
+}
+
+SubnetID
+CSVLeaseFile6::readSubnetID(const CSVRow& row) {
+    SubnetID subnet_id =
+        row.readAndConvertAt<SubnetID>(getColumnIndex("subnet_id"));
+    return (subnet_id);
+}
+
+uint8_t
+CSVLeaseFile6::readPrefixLen(const CSVRow& row) {
+    int prefixlen = row.readAndConvertAt<int>(getColumnIndex("prefix_len"));
+    return (static_cast<uint8_t>(prefixlen));
+}
+
+bool
+CSVLeaseFile6::readFqdnFwd(const CSVRow& row) {
+    bool fqdn_fwd = row.readAndConvertAt<bool>(getColumnIndex("fqdn_fwd"));
+    return (fqdn_fwd);
+}
+
+bool
+CSVLeaseFile6::readFqdnRev(const CSVRow& row) {
+    bool fqdn_rev = row.readAndConvertAt<bool>(getColumnIndex("fqdn_rev"));
+    return (fqdn_rev);
+}
+
+std::string
+CSVLeaseFile6::readHostname(const CSVRow& row) {
+    std::string hostname = row.readAt(getColumnIndex("hostname"));
+    return (hostname);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/csv_lease_file6.h b/src/lib/dhcpsrv/csv_lease_file6.h
new file mode 100644
index 0000000..4e0c595
--- /dev/null
+++ b/src/lib/dhcpsrv/csv_lease_file6.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2014 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 CSV_LEASE_FILE6_H
+#define CSV_LEASE_FILE6_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet.h>
+#include <util/csv_file.h>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Provides methods to access CSV file with DHCPv6 leases.
+///
+/// This class contains methods customized to read and write DHCPv6 leases from
+/// and to the CSV file. It expects that the CSV file being parsed contains a
+/// set of columns with well known names (initialized in the class constructor).
+///
+/// @todo This class doesn't validate the lease values read from the file.
+/// The @c Lease6 is a structure that should be itself responsible for this
+/// validation (see http://bind10.isc.org/ticket/2405). However, when #2405
+/// is implemented, the @c next function may need to be updated to use the
+/// validation capablity of @c Lease6.
+class CSVLeaseFile6 : public isc::util::CSVFile {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes columns of the lease file.
+    ///
+    /// @param filename Name of the lease file.
+    CSVLeaseFile6(const std::string& filename);
+
+    /// @brief Appends the lease record to the CSV file.
+    ///
+    /// This function doesn't throw exceptions itself. In theory, exceptions
+    /// are possible when the index of the indexes of the values being written
+    /// to the file are invalid. However, this would have been a programming
+    /// error.
+    ///
+    /// @param lease Structure representing a DHCPv6 lease.
+    void append(const Lease6& lease) const;
+
+    /// @brief Reads next lease from the CSV file.
+    ///
+    /// If this function hits an error during lease read, it sets the error
+    /// message using @c CSVFile::setReadMsg and returns false. The error
+    /// string may be read using @c CSVFile::getReadMsg.
+    ///
+    /// This function is exception safe.
+    ///
+    /// @param [out] lease Pointer to the lease read from CSV file or
+    /// NULL pointer if lease hasn't been read.
+    ///
+    /// @return Boolean value indicating that the new lease has been
+    /// read from the CSV file (if true), or that the error has occurred
+    /// (false).
+    ///
+    /// @todo Make sure that the values read from the file are correct.
+    /// The appropriate @c Lease6 validation mechanism should be used once
+    /// ticket http://bind10.isc.org/ticket/2405 is implemented.
+    bool next(Lease6Ptr& lease);
+
+private:
+
+    /// @brief Initializes columns of the CSV file holding leases.
+    ///
+    /// This function initializes the following columns:
+    /// - address
+    /// - duid
+    /// - valid_lifetime
+    /// - expire
+    /// - subnet_id
+    /// - pref_lifetime
+    /// - lease_type
+    /// - iaid
+    /// - prefix_len
+    /// - fqdn_fwd
+    /// - fqdn_rev
+    /// - hostname
+    void initColumns();
+
+    ///
+    /// @name Methods which read specific lease fields from the CSV row.
+    ///
+    //@{
+    ///
+    /// @brief Reads lease type from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    Lease::Type readType(const util::CSVRow& row);
+
+    /// @brief Reads lease address from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    asiolink::IOAddress readAddress(const util::CSVRow& row);
+
+    /// @brief Reads DUID from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    DuidPtr readDUID(const util::CSVRow& row);
+
+    /// @brief Reads IAID from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    uint32_t readIAID(const util::CSVRow& row);
+
+    /// @brief Reads preferred lifetime from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    uint32_t readPreferred(const util::CSVRow& row);
+
+    /// @brief Reads valid lifetime from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    uint32_t readValid(const util::CSVRow& row);
+
+    /// @brief Reads cltt value from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    uint32_t readCltt(const util::CSVRow& row);
+
+    /// @brief Reads subnet id from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    SubnetID readSubnetID(const util::CSVRow& row);
+
+    /// @brief Reads prefix length from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    uint8_t readPrefixLen(const util::CSVRow& row);
+
+    /// @brief Reads the FQDN forward flag from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    bool readFqdnFwd(const util::CSVRow& row);
+
+    /// @brief Reads the FQDN reverse flag from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    bool readFqdnRev(const util::CSVRow& row);
+
+    /// @brief Reads hostname from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    std::string readHostname(const util::CSVRow& row);
+    //@}
+
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CSV_LEASE_FILE6_H
diff --git a/src/lib/dhcpsrv/dbaccess_parser.cc b/src/lib/dhcpsrv/dbaccess_parser.cc
index 08722df..4445c63 100644
--- a/src/lib/dhcpsrv/dbaccess_parser.cc
+++ b/src/lib/dhcpsrv/dbaccess_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 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
@@ -12,6 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <dhcp/option.h>
 #include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -30,11 +31,9 @@ namespace dhcp {
 
 
 // Factory function to build the parser
-DbAccessParser::DbAccessParser(const std::string& param_name) : values_()
+DbAccessParser::DbAccessParser(const std::string&, const ParserContext& ctx)
+    : values_(), ctx_(ctx)
 {
-    if (param_name != "lease-database") {
-        LOG_WARN(dhcpsrv_logger, DHCPSRV_UNEXPECTED_NAME).arg(param_name);
-    }
 }
 
 // Parse the configuration and check that the various keywords are consistent.
@@ -43,19 +42,33 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
 
     // To cope with incremental updates, the strategy is:
     // 1. Take a copy of the stored keyword/value pairs.
-    // 2. Update the copy with the passed keywords.
-    // 3. Perform validation checks on the updated keyword/value pairs.
-    // 4. If all is OK, update the stored keyword/value pairs.
+    // 2. Inject the universe parameter.
+    // 3. Update the copy with the passed keywords.
+    // 4. Perform validation checks on the updated keyword/value pairs.
+    // 5. If all is OK, update the stored keyword/value pairs.
 
     // 1. Take a copy of the stored keyword/value pairs.
     std::map<string, string> values_copy = values_;
 
-    // 2. Update the copy with the passed keywords.
+    // 2. Inject the parameter which defines whether we are configuring
+    // DHCPv4 or DHCPv6. Some database backends (e.g. Memfile make
+    // use of it).
+    values_copy["universe"] = ctx_.universe_ == Option::V4 ? "4" : "6";
+
+    // 3. Update the copy with the passed keywords.
     BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
-        values_copy[param.first] = param.second->stringValue();
+        // The persist parameter is the only boolean parameter at the
+        // moment. It needs special handling.
+        if (param.first != "persist") {
+            values_copy[param.first] = param.second->stringValue();
+
+        } else {
+            values_copy[param.first] = (param.second->boolValue() ?
+                                        "true" : "false");
+        }
     }
 
-    // 3. Perform validation checks on the updated set of keyword/values.
+    // 4. Perform validation checks on the updated set of keyword/values.
     //
     // a. Check if the "type" keyword exists and thrown an exception if not.
     StringPairMap::const_iterator type_ptr = values_copy.find("type");
@@ -71,7 +84,7 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
         isc_throw(BadValue, "unknown backend database type: " << dbtype);
     }
 
-    // 4. If all is OK, update the stored keyword/value pairs.  We do this by
+    // 5. If all is OK, update the stored keyword/value pairs.  We do this by
     // swapping contents - values_copy is destroyed immediately after the
     // operation (when the method exits), so we are not interested in its new
     // value.
diff --git a/src/lib/dhcpsrv/dbaccess_parser.h b/src/lib/dhcpsrv/dbaccess_parser.h
index 77c9845..824fd27 100644
--- a/src/lib/dhcpsrv/dbaccess_parser.h
+++ b/src/lib/dhcpsrv/dbaccess_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 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
@@ -17,6 +17,7 @@
 
 #include <cc/data.h>
 #include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
 
 #include <string>
@@ -54,7 +55,8 @@ public:
     ///
     /// @param param_name Name of the parameter under which the database
     ///        access details are held.
-    DbAccessParser(const std::string& param_name);
+    /// @param ctx Parser context.
+    DbAccessParser(const std::string& param_name, const ParserContext& ctx);
 
     /// The destructor.
     virtual ~DbAccessParser()
@@ -96,11 +98,13 @@ public:
     ///
     /// @param param_name Name of the parameter used to access the
     /// 	configuration.
+    /// @param ctx Parser context.
     ///
     /// @return Pointer to a DbAccessParser.  The caller is responsible for
     ///         destroying the parser after use.
-    static DhcpConfigParser* factory(const std::string& param_name) {
-        return (new DbAccessParser(param_name));
+    static DhcpConfigParser* factory(const std::string& param_name,
+                                     const ParserContext& ctx) {
+        return (new DbAccessParser(param_name, ctx));
     }
 
 protected:
@@ -124,7 +128,10 @@ protected:
     std::string getDbAccessString() const;
 
 private:
+
     std::map<std::string, std::string> values_; ///< Stored parameter values
+
+    ParserContext ctx_; ///< Parser context
 };
 
 };  // namespace dhcp
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 6223662..03cbc72 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -279,6 +279,31 @@ 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_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_LEASES_RELOAD4 reloading leases from %1
+An info message issued when server is about to start reading DHCPv4 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 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_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
+in the configuration. This mode is useful for some kinds of performance
+testing but should not be enabled in normal circumstances. Non-persistence
+mode is enabled when 'persist4=no persist6=no' parameters are specified
+in the database access string.
+
 % DHCPSRV_MEMFILE_ROLLBACK rolling back memory file database
 The code has issued a rollback call.  For the memory file database, this is
 a no-op.
@@ -291,13 +316,6 @@ lease from the memory file database for the specified address.
 A debug message issued when the server is attempting to update IPv6
 lease from the memory file database for the specified address.
 
-% DHCPSRV_MEMFILE_WARNING using early version of memfile lease database - leases will be lost after a restart
-This warning message is issued when the 'memfile' lease database is
-opened.  The current version of memfile does not store anything
-to disk, so lease information will be lost in the event of a restart.
-Using this version of memfile in a production environment is NOT
-recommended.
-
 % DHCPSRV_MYSQL_ADD_ADDR4 adding IPv4 lease with address %1
 A debug message issued when the server is about to add an IPv4 lease
 with the specified address to the MySQL backend database.
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index c6c9c9f..29a6fe6 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 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
@@ -12,6 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 #include <exceptions/exceptions.h>
@@ -22,10 +23,42 @@ using namespace isc::dhcp;
 
 Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
     : LeaseMgr(parameters) {
-    LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_WARNING);
+    // Check the universe and use v4 file or v6 file.
+    std::string universe = getParameter("universe");
+    if (universe == "4") {
+        std::string file4 = initLeaseFilePath(V4);
+        if (!file4.empty()) {
+            lease_file4_.reset(new CSVLeaseFile4(file4));
+            lease_file4_->open();
+            load4();
+        }
+    } else {
+        std::string file6 = initLeaseFilePath(V6);
+        if (!file6.empty()) {
+            lease_file6_.reset(new CSVLeaseFile6(file6));
+            lease_file6_->open();
+            load6();
+        }
+    }
+
+    // If lease persistence have been disabled for both v4 and v6,
+    // issue a warning. It is ok not to write leases to disk when
+    // doing testing, but it should not be done in normal server
+    // operation.
+    if (!persistLeases(V4) && !persistLeases(V6)) {
+        LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_NO_STORAGE);
+    }
 }
 
 Memfile_LeaseMgr::~Memfile_LeaseMgr() {
+    if (lease_file4_) {
+        lease_file4_->close();
+        lease_file4_.reset();
+    }
+    if (lease_file6_) {
+        lease_file6_->close();
+        lease_file6_.reset();
+    }
 }
 
 bool
@@ -37,6 +70,14 @@ Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
         // there is a lease with specified address already
         return (false);
     }
+
+    // Try to write a lease to disk first. If this fails, the lease will
+    // not be inserted to the memory and the disk and in-memory data will
+    // remain consistent.
+    if (persistLeases(V4)) {
+        lease_file4_->append(*lease);
+    }
+
     storage4_.insert(lease);
     return (true);
 }
@@ -50,6 +91,14 @@ Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
         // there is a lease with specified address already
         return (false);
     }
+
+    // Try to write a lease to disk first. If this fails, the lease will
+    // not be inserted to the memory and the disk and in-memory data will
+    // remain consistent.
+    if (persistLeases(V6)) {
+        lease_file6_->append(*lease);
+    }
+
     storage6_.insert(lease);
     return (true);
 }
@@ -249,6 +298,14 @@ Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
         isc_throw(NoSuchLease, "failed to update the lease with address "
                   << lease->addr_ << " - no such lease");
     }
+
+    // Try to write a lease to disk first. If this fails, the lease will
+    // not be inserted to the memory and the disk and in-memory data will
+    // remain consistent.
+    if (persistLeases(V4)) {
+        lease_file4_->append(*lease);
+    }
+
     **lease_it = *lease;
 }
 
@@ -262,6 +319,14 @@ Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
         isc_throw(NoSuchLease, "failed to update the lease with address "
                   << lease->addr_ << " - no such lease");
     }
+
+    // Try to write a lease to disk first. If this fails, the lease will
+    // not be inserted to the memory and the disk and in-memory data will
+    // remain consistent.
+    if (persistLeases(V6)) {
+        lease_file6_->append(*lease);
+    }
+
     **lease_it = *lease;
 }
 
@@ -276,6 +341,15 @@ Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
             // No such lease
             return (false);
         } else {
+            if (persistLeases(V4)) {
+                // Copy the lease. The valid lifetime needs to be modified and
+                // we don't modify the original lease.
+                Lease4 lease_copy = **l;
+                // Setting valid lifetime to 0 means that lease is being
+                // removed.
+                lease_copy.valid_lft_ = 0;
+                lease_file4_->append(lease_copy);
+            }
             storage4_.erase(l);
             return (true);
         }
@@ -287,6 +361,16 @@ Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
             // No such lease
             return (false);
         } else {
+            if (persistLeases(V6)) {
+                // Copy the lease. The lifetimes need to be modified and we
+                // don't modify the original lease.
+                Lease6 lease_copy = **l;
+                // Setting lifetimes to 0 means that lease is being removed.
+                lease_copy.valid_lft_ = 0;
+                lease_copy.preferred_lft_ = 0;
+                lease_file6_->append(lease_copy);
+            }
+
             storage6_.erase(l);
             return (true);
         }
@@ -310,3 +394,187 @@ Memfile_LeaseMgr::rollback() {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_ROLLBACK);
 }
+
+std::string
+Memfile_LeaseMgr::getDefaultLeaseFilePath(Universe u) const {
+    std::ostringstream s;
+    s << CfgMgr::instance().getDataDir() << "/kea-leases";
+    s << (u == V4 ? "4" : "6");
+    s << ".csv";
+    return (s.str());
+}
+
+std::string
+Memfile_LeaseMgr::getLeaseFilePath(Universe u) const {
+    if (u == V4) {
+        return (lease_file4_ ? lease_file4_->getFilename() : "");
+    }
+
+    return (lease_file6_ ? lease_file6_->getFilename() : "");
+}
+
+bool
+Memfile_LeaseMgr::persistLeases(Universe u) const {
+    // Currently, if the lease file IO is not created, it means that writes to
+    // disk have been explicitly disabled by the administrator. At some point,
+    // there may be a dedicated ON/OFF flag implemented to control this.
+    if (u == V4 && lease_file4_) {
+        return (true);
+    }
+
+    return (u == V6 && lease_file6_);
+}
+
+std::string
+Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
+    std::string persist_val;
+    try {
+        persist_val = getParameter("persist");
+    } catch (const Exception& ex) {
+        // If parameter persist hasn't been specified, we use a default value
+        // 'yes'.
+        persist_val = "true";
+    }
+    // If persist_val is 'false' we will not store leases to disk, so let's
+    // return empty file name.
+    if (persist_val == "false") {
+        return ("");
+
+    } else if (persist_val != "true") {
+        isc_throw(isc::BadValue, "invalid value 'persist="
+                  << persist_val << "'");
+    }
+
+    std::string lease_file;
+    try {
+        lease_file = getParameter("name");
+    } catch (const Exception& ex) {
+        lease_file = getDefaultLeaseFilePath(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;
+    }
+
+    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);
+}
+
+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;
+        }
+    }
+}
+
+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;
+        }
+    }
+
+}
+
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 66548f6..31e738b 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 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
@@ -16,6 +16,8 @@
 #define MEMFILE_LEASE_MGR_H
 
 #include <dhcp/hwaddr.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/csv_lease_file6.h>
 #include <dhcpsrv/lease_mgr.h>
 
 #include <boost/multi_index/indexed_by.hpp>
@@ -27,17 +29,59 @@
 namespace isc {
 namespace dhcp {
 
-// This is a concrete implementation of a Lease database.
-//
-// It is for testing purposes only. It is NOT a production code.
-//
-// It does not do anything useful now, and is used for abstract LeaseMgr
-// class testing. It may later evolve into more useful backend if the
-// need arises. We can reuse code from memfile benchmark. See code in
-// tests/tools/dhcp-ubench/memfile_bench.{cc|h}
+/// @brief Concrete implementation of a lease database backend using flat file.
+///
+/// This class implements a lease database backend using CSV files to store
+/// DHCPv4 and DHCPv6 leases on disk. The format of the files is determined
+/// by the @c CSVLeaseFile4 and @c CSVLeaseFile6 classes.
+///
+/// The backend stores leases incrementally, i.e. updates to leases are appended
+/// at the end of the lease file. To record the deletion of a lease, the lease
+/// record is appended to the lease file with the valid lifetime set to 0.
+///
+/// When the backend is starting up, it reads leases from the lease file (one
+/// by one) and adds them to the in-memory container as follows:
+/// - if the lease record being parsed identifies a lease which is not present
+/// in the container, and the lease has valid lifetime greater than 0,
+/// the lease is added to the container,
+/// - if the lease record being parsed identifies a lease which is present in
+/// the container, and the valid lifetime of the lease record being parsed is
+/// greater than 0, the lease in the container is updated
+/// - if the lease record being parsed has valid lifetime equal to 0, and the
+/// corresponding lease exists in the container, the lease is removed from
+/// the container.
+///
+/// After the container holding leases is initialized, each subsequent update,
+/// removal or addition of the lease is appended to the lease file
+/// synchronously.
+///
+/// Originally, the Memfile backend didn't write leases to disk. This was
+/// particularly useful for testing server performance in non-disk bound
+/// conditions. In order to preserve this capability, the new parameter
+/// "persist=true|false" has been introduced in the database access string.
+/// For example, database access string: "type=memfile persist=true"
+/// enables writes of leases to a disk.
+///
+/// The lease file locations can be specified with the "name=[path]"
+/// parameter in the database access string. The [path] is the
+/// absolute path to the file (including file name). If this parameter
+/// is not specified, the default location in the installation
+/// directory is used: var/bind10/kea-leases4.csv and
+/// var/bind10/kea-leases6.csv.
 class Memfile_LeaseMgr : public LeaseMgr {
 public:
 
+    /// @brief Specifies universe (V4, V6)
+    ///
+    /// This enumeration is used by various functions in Memfile Lease Manager,
+    /// to identify the lease type referred to. In particular, it is used by
+    /// functions operating on the lease files to distinguish between lease
+    /// files for DHCPv4 and DHCPv6.
+    enum Universe {
+        V4,
+        V6
+    };
+
     /// @brief The sole lease manager constructor
     ///
     /// dbconfig is a generic way of passing parameters. Parameters
@@ -245,8 +289,97 @@ public:
     /// support transactions, this is a no-op.
     virtual void rollback();
 
+    /// @brief Returns default path to the lease file.
+    ///
+    /// @param u Universe (V4 or V6).
+    std::string getDefaultLeaseFilePath(Universe u) const;
+
+    /// @brief Returns an absolute path to the lease file.
+    ///
+    /// @param u Universe (V4 or V6).
+    ///
+    /// @return Absolute path to the lease file or empty string if no lease
+    /// file is used.
+    std::string getLeaseFilePath(Universe u) const;
+
+    /// @brief Specifies whether or not leases are written to disk.
+    ///
+    /// It is possible that leases for DHCPv4 are written to disk whereas leases
+    /// for DHCPv6 are not; or vice versa. The argument of the method specifies
+    /// the type of lease in that respect.
+    ///
+    /// @param u Universe (V4 or V6).
+    ///
+    /// @return true if leases are written to lease file; if false is
+    /// returned, leases will be held in memory and will be lost upon
+    /// 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);
+
+    /// @brief Initialize the location of the lease file.
+    ///
+    /// This method uses the parameters passed as a map to the constructor to
+    /// initialize the location of the lease file. If the lease file is not
+    /// specified, the method will use the default location for the universe
+    /// (v4 or v6) selected. If the location is specified in the map as empty
+    /// or the "persist" parameter is set to "no" it will set the empty
+    /// location, which implies that leases belonging to the specified universe
+    /// will not be written to disk.
+    ///
+    /// @param u Universe (v4 or v6)
+    /// @param parameters Map holding parameters of the Lease Manager, passed to
+    /// the constructor.
+    ///
+    /// @return The location of the lease file that should be assigned to the
+    /// lease_file4_ or lease_file6_, depending on the universe specified as an
+    /// 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<
@@ -283,7 +416,7 @@ protected:
     // be accessed using different search indexes.
     typedef boost::multi_index_container<
         // It holds pointers to Lease4 objects.
-        Lease4Ptr, 
+        Lease4Ptr,
         // Specification of search indexes starts here.
         boost::multi_index::indexed_by<
             // Specification of the first index starts here.
@@ -314,7 +447,7 @@ protected:
             >,
 
             // Specification of the third index starts here.
-            boost::multi_index::ordered_unique<
+            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<
@@ -329,7 +462,7 @@ protected:
             >,
 
             // Specification of the fourth index starts here.
-            boost::multi_index::ordered_unique<
+            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<
@@ -354,6 +487,13 @@ protected:
 
     /// @brief stores IPv6 leases
     Lease6Storage storage6_;
+
+    /// @brief Holds the pointer to the DHCPv4 lease file IO.
+    boost::shared_ptr<CSVLeaseFile4> lease_file4_;
+
+    /// @brief Holds the pointer to the DHCPv6 lease file IO.
+    boost::shared_ptr<CSVLeaseFile6> lease_file6_;
+
 };
 
 }; // end of isc::dhcp namespace
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 736aebd..6215218 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -2,7 +2,8 @@ SUBDIRS = .
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp/tests\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\"
+AM_CPPFLAGS += -DDHCP_DATA_DIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -55,9 +56,12 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
+libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index 4330efd..47c4e47 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -101,7 +101,7 @@ public:
 
         initFqdn("", false, false);
 
-        factory_.create("type=memfile");
+        factory_.create("type=memfile universe=6 persist=false");
     }
 
     /// @brief Configures a subnet and adds one pool to it.
@@ -424,7 +424,7 @@ public:
         subnet_->addPool(pool_);
         cfg_mgr.addSubnet4(subnet_);
 
-        factory_.create("type=memfile");
+        factory_.create("type=memfile universe=4 persist=false");
     }
 
     /// @brief checks if Lease4 matches expected configuration
diff --git a/src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc b/src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc
new file mode 100644
index 0000000..11e0065
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc
@@ -0,0 +1,182 @@
+// Copyright (C) 2014 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 <dhcp/duid.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/tests/lease_file_io.h>
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+// HWADDR values used by unit tests.
+const uint8_t HWADDR0[] = { 0, 1, 2, 3, 4, 5 };
+const uint8_t HWADDR1[] = { 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf };
+
+const uint8_t CLIENTID0[] = { 1, 2, 3, 4 };
+const uint8_t CLIENTID1[] = { 0xa, 0xb, 0xc, 0xd };
+
+/// @brief Test fixture class for @c CSVLeaseFile4 validation.
+class CSVLeaseFile4Test : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes IO for lease file used by unit tests.
+    CSVLeaseFile4Test();
+
+    /// @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 Creates the lease file to be parsed by unit tests.
+    void writeSampleFile() const;
+
+    /// @brief Name of the test lease file.
+    std::string filename_;
+
+    /// @brief Object providing access to lease file IO.
+    LeaseFileIO io_;
+
+};
+
+CSVLeaseFile4Test::CSVLeaseFile4Test()
+    : filename_(absolutePath("leases4.csv")), io_(filename_) {
+}
+
+std::string
+CSVLeaseFile4Test::absolutePath(const std::string& filename) {
+    std::ostringstream s;
+    s << DHCP_DATA_DIR << "/" << filename;
+    return (s.str());
+}
+
+void
+CSVLeaseFile4Test::writeSampleFile() const {
+    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.1,,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,100,7,"
+                  "0,0,\n");
+}
+
+// This test checks the capability to read and parse leases from the file.
+TEST_F(CSVLeaseFile4Test, parse) {
+    // Create a file to be parsed.
+    writeSampleFile();
+
+    // Open the lease file.
+    boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+    ASSERT_NO_THROW(lf->open());
+
+    Lease4Ptr lease;
+    // Reading first read should be successful.
+    EXPECT_TRUE(lf->next(lease));
+    ASSERT_TRUE(lease);
+
+    // Verify that the lease attributes are correct.
+    EXPECT_EQ("192.0.2.1", lease->addr_.toText());
+    HWAddr hwaddr1(lease->hwaddr_, HTYPE_ETHER);
+    EXPECT_EQ("06:07:08:09:0a:bc", hwaddr1.toText(false));
+    EXPECT_FALSE(lease->client_id_);
+    EXPECT_EQ(200, lease->valid_lft_);
+    EXPECT_EQ(0, lease->cltt_);
+    EXPECT_EQ(8, lease->subnet_id_);
+    EXPECT_TRUE(lease->fqdn_fwd_);
+    EXPECT_TRUE(lease->fqdn_rev_);
+    EXPECT_EQ("host.example.com", lease->hostname_);
+
+    // Second lease is malformed - HW address is empty.
+    EXPECT_FALSE(lf->next(lease));
+
+    // Even though parsing previous lease failed, reading the next lease should be
+    // successful.
+    EXPECT_TRUE(lf->next(lease));
+    ASSERT_TRUE(lease);
+    // Verify that the third lease is correct.
+    EXPECT_EQ("192.0.3.15", lease->addr_.toText());
+    HWAddr hwaddr3(lease->hwaddr_, HTYPE_ETHER);
+    EXPECT_EQ("dd:de:ba:0d:1b:2e:3e:4f", hwaddr3.toText(false));
+    ASSERT_TRUE(lease->client_id_);
+    EXPECT_EQ("0a:00:01:04", lease->client_id_->toText());
+    EXPECT_EQ(100, lease->valid_lft_);
+    EXPECT_EQ(0, lease->cltt_);
+    EXPECT_EQ(7, lease->subnet_id_);
+    EXPECT_FALSE(lease->fqdn_fwd_);
+    EXPECT_FALSE(lease->fqdn_rev_);
+    EXPECT_TRUE(lease->hostname_.empty());
+
+    // There are no more leases. Reading should cause no error, but the returned
+    // lease pointer should be NULL.
+    EXPECT_TRUE(lf->next(lease));
+    EXPECT_FALSE(lease);
+
+    // We should be able to do it again.
+    EXPECT_TRUE(lf->next(lease));
+    EXPECT_FALSE(lease);
+
+}
+
+// This test checks creation of the lease file and writing leases.
+TEST_F(CSVLeaseFile4Test, recreate) {
+    boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+    ASSERT_NO_THROW(lf->recreate());
+    ASSERT_TRUE(io_.exists());
+    // Create first lease, with NULL client id.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
+                               HWADDR0, sizeof(HWADDR0),
+                               NULL, 0,
+                               200, 50, 80, 0, 8, true, true,
+                               "host.example.com"));
+    ASSERT_NO_THROW(lf->append(*lease));
+    // Create second lease, with non-NULL client id.
+    lease.reset(new Lease4(IOAddress("192.0.3.10"),
+                           HWADDR1, sizeof(HWADDR1),
+                           CLIENTID0, sizeof(CLIENTID0),
+                           100, 60, 90, 0, 7));
+    ASSERT_NO_THROW(lf->append(*lease));
+    // Close the lease file.
+    lf->close();
+    // Check that the contents of the csv file are correct.
+    EXPECT_EQ("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+              "fqdn_fwd,fqdn_rev,hostname\n"
+              "192.0.3.2,00:01:02:03:04:05,,200,200,8,1,1,host.example.com\n"
+              "192.0.3.10,0d:0e:0a:0d:0b:0e:0e:0f,01:02:03:04,100,100,7,0,"
+              "0,\n",
+              io_.readFile());
+}
+
+/// @todo Currently we don't check invalid lease attributes, such as invalid
+/// lease type, invalid preferred lifetime vs valid lifetime etc. The Lease6
+/// should be extended with the function that validates lease attributes. Once
+/// this is implemented we should provide more tests for malformed leases
+/// in the CSV file. See http://bind10.isc.org/ticket/2405.
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc b/src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc
new file mode 100644
index 0000000..40770e4
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc
@@ -0,0 +1,223 @@
+// Copyright (C) 2014 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 <dhcp/duid.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/tests/lease_file_io.h>
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+// DUID values used by unit tests.
+const uint8_t DUID0[] = { 0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
+const uint8_t DUID1[] = { 1, 1, 1, 1, 0xa, 1, 2, 3, 4, 5 };
+
+/// @brief Test fixture class for @c CSVLeaseFile6 validation.
+class CSVLeaseFile6Test : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes IO for lease file used by unit tests.
+    CSVLeaseFile6Test();
+
+    /// @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 Create DUID object from the binary.
+    ///
+    /// @param duid Binary value representing a DUID.
+    /// @param size Size of the DUID.
+    /// @return Pointer to the @c DUID object.
+    DuidPtr makeDUID(const uint8_t* duid, const unsigned int size) const {
+        return (DuidPtr(new DUID(duid, size)));
+    }
+
+    /// @brief Create lease file that can be parsed by unit tests.
+    void writeSampleFile() const;
+
+    /// @brief Name of the test lease file.
+    std::string filename_;
+
+    /// @brief Object providing access to lease file IO.
+    LeaseFileIO io_;
+
+};
+
+CSVLeaseFile6Test::CSVLeaseFile6Test()
+    : filename_(absolutePath("leases6.csv")), io_(filename_) {
+}
+
+std::string
+CSVLeaseFile6Test::absolutePath(const std::string& filename) {
+    std::ostringstream s;
+    s << DHCP_DATA_DIR << "/" << filename;
+    return (s.str());
+}
+
+void
+CSVLeaseFile6Test::writeSampleFile() const {
+    io_.writeFile("address,duid,valid_lifetime,expire,subnet_id,"
+                  "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+                  "fqdn_rev,hostname\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,0,200,8,0,2,"
+                  "16,64,0,0,\n");
+}
+
+// This test checks the capability to read and parse leases from the file.
+TEST_F(CSVLeaseFile6Test, parse) {
+    // Create a file to be parsed.
+    writeSampleFile();
+
+    // Open the lease file.
+    boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
+    ASSERT_NO_THROW(lf->open());
+
+    Lease6Ptr lease;
+    // Reading first read should be successful.
+    EXPECT_TRUE(lf->next(lease));
+    ASSERT_TRUE(lease);
+
+    // Verify that the lease attributes are correct.
+    EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
+    ASSERT_TRUE(lease->duid_);
+    EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f", lease->duid_->toText());
+    EXPECT_EQ(200, lease->valid_lft_);
+    EXPECT_EQ(0, lease->cltt_);
+    EXPECT_EQ(8, lease->subnet_id_);
+    EXPECT_EQ(100, lease->preferred_lft_);
+    EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+    EXPECT_EQ(7, lease->iaid_);
+    EXPECT_EQ(0, lease->prefixlen_);
+    EXPECT_TRUE(lease->fqdn_fwd_);
+    EXPECT_TRUE(lease->fqdn_rev_);
+    EXPECT_EQ("host.example.com", lease->hostname_);
+
+    // Second lease is malformed - DUID is empty.
+    EXPECT_FALSE(lf->next(lease));
+
+    // Even, parsing previous lease failed, reading the next lease should be
+    // successful.
+    EXPECT_TRUE(lf->next(lease));
+    ASSERT_TRUE(lease);
+    // Verify that the third lease is correct.
+    EXPECT_EQ("2001:db8:2::10", lease->addr_.toText());
+    ASSERT_TRUE(lease->duid_);
+    EXPECT_EQ("01:01:01:01:0a:01:02:03:04:05", lease->duid_->toText());
+    EXPECT_EQ(300, lease->valid_lft_);
+    EXPECT_EQ(0, lease->cltt_);
+    EXPECT_EQ(6, lease->subnet_id_);
+    EXPECT_EQ(150, lease->preferred_lft_);
+    EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+    EXPECT_EQ(8, lease->iaid_);
+    EXPECT_EQ(0, lease->prefixlen_);
+    EXPECT_FALSE(lease->fqdn_fwd_);
+    EXPECT_FALSE(lease->fqdn_rev_);
+    EXPECT_TRUE(lease->hostname_.empty());
+
+    // Reading the fourth lease should be successful.
+    EXPECT_TRUE(lf->next(lease));
+    ASSERT_TRUE(lease);
+    // Verify that the lease is correct.
+    EXPECT_EQ("3000:1::", lease->addr_.toText());
+    ASSERT_TRUE(lease->duid_);
+    EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f", lease->duid_->toText());
+    EXPECT_EQ(0, lease->valid_lft_);
+    EXPECT_EQ(200, lease->cltt_);
+    EXPECT_EQ(8, lease->subnet_id_);
+    EXPECT_EQ(0, lease->preferred_lft_);
+    EXPECT_EQ(Lease::TYPE_PD, lease->type_);
+    EXPECT_EQ(16, lease->iaid_);
+    EXPECT_EQ(64, lease->prefixlen_);
+    EXPECT_FALSE(lease->fqdn_fwd_);
+    EXPECT_FALSE(lease->fqdn_rev_);
+    EXPECT_TRUE(lease->hostname_.empty());
+
+    // There are no more leases. Reading should cause no error, but the returned
+    // lease pointer should be NULL.
+    EXPECT_TRUE(lf->next(lease));
+    EXPECT_FALSE(lease);
+
+    // We should be able to do it again.
+    EXPECT_TRUE(lf->next(lease));
+    EXPECT_FALSE(lease);
+
+}
+
+// This test checks creation of the lease file and writing leases.
+TEST_F(CSVLeaseFile6Test, recreate) {
+    boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
+    ASSERT_NO_THROW(lf->recreate());
+    ASSERT_TRUE(io_.exists());
+
+    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+                               makeDUID(DUID0, sizeof(DUID0)),
+                               7, 100, 200, 50, 80, 8, true, true,
+                               "host.example.com"));
+    lease->cltt_ = 0;
+    ASSERT_NO_THROW(lf->append(*lease));
+
+    lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:2::10"),
+                           makeDUID(DUID1, sizeof(DUID1)),
+                           8, 150, 300, 40, 70, 6, false, false,
+                           "", 128));
+    lease->cltt_ = 0;
+    ASSERT_NO_THROW(lf->append(*lease));
+
+    lease.reset(new Lease6(Lease::TYPE_PD, IOAddress("3000:1:1::"),
+                           makeDUID(DUID0, sizeof(DUID0)),
+                           7, 150, 300, 40, 70, 10, false, false,
+                           "", 64));
+    lease->cltt_ = 0;
+    ASSERT_NO_THROW(lf->append(*lease));
+
+    EXPECT_EQ("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+              "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname\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,128,0,0,\n"
+              "3000:1:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+              "300,300,10,150,2,7,64,0,0,\n",
+              io_.readFile());
+}
+
+/// @todo Currently we don't check invalid lease attributes, such as invalid
+/// lease type, invalid preferred lifetime vs valid lifetime etc. The Lease6
+/// should be extended with the function that validates lease attributes. Once
+/// this is implemented we should provide more tests for malformed leases
+/// in the CSV file. See http://bind10.isc.org/ticket/2405.
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
index 24cfb1a..86d51d8 100644
--- a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
@@ -15,6 +15,7 @@
 #include <config.h>
 
 #include <dhcpsrv/dbaccess_parser.h>
+#include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <config/ccsession.h>
 #include <gtest/gtest.h>
@@ -86,8 +87,14 @@ public:
             }
 
             // Add the keyword and value - make sure that they are quoted.
-            result += quote + keyval[i] + quote + colon + space +
-                      quote + keyval[i + 1] + quote;
+            // The only parameter which is not quoted is persist as it
+            // is a boolean value.
+            result += quote + keyval[i] + quote + colon + space;
+            if (std::string(keyval[i]) != "persist") {
+                result += quote + keyval[i + 1] + quote;
+            } else {
+                result += keyval[i + 1];
+            }
         }
 
         // Add the terminating brace
@@ -114,14 +121,18 @@ public:
     /// @param dbaccess set of database access parameters to check
     /// @param keyval Array of "const char*" strings in the order keyword,
     ///        value, keyword, value ...  A NULL entry terminates the list.
+    /// @param u Universe (V4 or V6).
     void checkAccessString(const char* trace_string,
                            const DbAccessParser::StringPairMap& parameters,
-                           const char* keyval[]) {
+                           const char* keyval[],
+                           Option::Universe u = Option::V4) {
         SCOPED_TRACE(trace_string);
 
         // Construct a map of keyword value pairs.
         std::map<string, string> expected;
-        size_t expected_count = 0;
+        expected["universe"] = (u == Option::V4 ? "4" : "6");
+        // The universe is always injected by the parser itself.
+        size_t expected_count = 1;
         for (size_t i = 0; keyval[i] != NULL; i += 2) {
             // Get the value.  This should not be NULL
             ASSERT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
@@ -134,7 +145,7 @@ public:
         }
 
         // Check no duplicates in the test set of reference keywords.
-        ASSERT_EQ(expected_count, expected.size()) << 
+        ASSERT_EQ(expected_count, expected.size()) <<
             "Supplied reference keyword/value list contains duplicate keywords";
 
         // The passed parameter map should have the same number of entries as
@@ -169,8 +180,8 @@ public:
     /// @brief Constructor
     ///
     /// @brief Keyword/value collection of ddatabase access parameters
-    TestDbAccessParser(const std::string& param_name)
-        : DbAccessParser(param_name)
+    TestDbAccessParser(const std::string& param_name, const ParserContext& ctx)
+        : DbAccessParser(param_name, ctx)
     {}
 
     /// @brief Destructor
@@ -211,7 +222,7 @@ TEST_F(DbAccessParserTest, validTypeMemfile) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
 
-    TestDbAccessParser parser("lease-database");
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
 }
@@ -227,11 +238,49 @@ TEST_F(DbAccessParserTest, emptyKeyword) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
 
-    TestDbAccessParser parser("lease-database");
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
 }
 
+// Check that the parser works with more complex configuration when
+// lease file path is specified for DHCPv4.
+TEST_F(DbAccessParserTest, persistV4Memfile) {
+    const char* config[] = {"type", "memfile",
+                            "persist", "true",
+                            "name", "/opt/bind10/var/kea-leases4.csv",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    EXPECT_NO_THROW(parser.build(json_elements));
+
+    checkAccessString("Valid memfile", parser.getDbAccessParameters(),
+                      config);
+}
+
+// Check that the parser works with more complex configuration when
+// lease file path is specified for DHCPv6.
+TEST_F(DbAccessParserTest, persistV6Memfile) {
+    const char* config[] = {"type", "memfile",
+                            "persist", "true",
+                            "name", "/opt/bind10/var/kea-leases6.csv",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V6));
+    EXPECT_NO_THROW(parser.build(json_elements));
+
+    checkAccessString("Valid memfile", parser.getDbAccessParameters(),
+                      config, Option::V6);
+}
+
 // Check that the parser works with a valid MySQL configuration
 TEST_F(DbAccessParserTest, validTypeMysql) {
     const char* config[] = {"type",     "mysql",
@@ -245,7 +294,7 @@ TEST_F(DbAccessParserTest, validTypeMysql) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
 
-    TestDbAccessParser parser("lease-database");
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     checkAccessString("Valid mysql", parser.getDbAccessParameters(), config);
 }
@@ -262,7 +311,7 @@ TEST_F(DbAccessParserTest, missingTypeKeyword) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
 
-    TestDbAccessParser parser("lease-database");
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
     EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
 }
 
@@ -271,7 +320,8 @@ TEST_F(DbAccessParserTest, factory) {
 
     // Check that the parser is built through the factory.
     boost::scoped_ptr<DhcpConfigParser> parser(
-        DbAccessParser::factory("lease-database"));
+        DbAccessParser::factory("lease-database", ParserContext(Option::V4))
+    );
     EXPECT_TRUE(parser);
     DbAccessParser* dbap = dynamic_cast<DbAccessParser*>(parser.get());
     EXPECT_NE(static_cast<DbAccessParser*>(NULL), dbap);
@@ -321,7 +371,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
                              "name",     "keatest",
                              NULL};
 
-    TestDbAccessParser parser("lease-database");
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
 
     // First configuration string should cause a representation of that string
     // to be held.
@@ -385,7 +435,7 @@ TEST_F(DbAccessParserTest, getDbAccessString) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
 
-    TestDbAccessParser parser("lease-database");
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
 
     // Get the database access string
@@ -394,8 +444,7 @@ TEST_F(DbAccessParserTest, getDbAccessString) {
     // String should be either "type=mysql name=keatest" or
     // "name=keatest type=mysql". The "host" entry is null, so should not be
     // output.
-    EXPECT_TRUE((dbaccess == "type=mysql name=keatest") ||
-                (dbaccess == "name=keatest type=mysql"));
+    EXPECT_EQ(dbaccess, "name=keatest type=mysql universe=4");
 }
 
 // Check that the "commit" function actually opens the database.  We will
@@ -410,17 +459,18 @@ TEST_F(DbAccessParserTest, commit) {
             }, isc::dhcp::NoLeaseManager);
 
     // Set up the parser to open the memfile database.
-    const char* config[] = {"type", "memfile",
-                            NULL};
+    const char* config[] = {"type", "memfile", "persist", "false", NULL};
     string json_config = toJson(config);
+
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
 
-    TestDbAccessParser parser("lease-database");
+    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
 
     // Ensure that the access string is as expected.
-    EXPECT_EQ(std::string("type=memfile"), parser.getDbAccessString());
+    EXPECT_EQ("persist=false type=memfile universe=4",
+              parser.getDbAccessString());
 
     // Committal of the parser changes should open the database.
     EXPECT_NO_THROW(parser.commit());
diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
index 1d24cf8..8dc2a78 100644
--- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
@@ -673,7 +673,13 @@ GenericLeaseMgrTest::testAddGetDelete6(bool check_t1_t2) {
 
     // after the lease is deleted, it should really be gone
     x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456"));
-    EXPECT_EQ(Lease6Ptr(), x);
+    EXPECT_FALSE(x);
+
+    // Reopen the lease storage to make sure that lease is gone from the
+    // persistent storage.
+    reopen(V6);
+    x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456"));
+    EXPECT_FALSE(x);
 }
 
 void
@@ -689,7 +695,7 @@ GenericLeaseMgrTest::testBasicLease4() {
     lmptr_->commit();
 
     // Reopen the database to ensure that they actually got stored.
-    reopen();
+    reopen(V4);
 
     Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
     ASSERT_TRUE(l_returned);
@@ -717,6 +723,39 @@ GenericLeaseMgrTest::testBasicLease4() {
     l_returned = lmptr_->getLease4(ioaddress4_[2]);
     ASSERT_TRUE(l_returned);
     detailCompareLease(leases[2], l_returned);
+
+    reopen(V4);
+
+    // The deleted lease should be still gone after we re-read leases from
+    // persistent storage.
+    l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    EXPECT_FALSE(l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[3]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[3], l_returned);
+
+    // Update some FQDN data, so as we can check that update in
+    // persistent storage works as expected.
+    leases[2]->hostname_ = "memfile.example.com.";
+    leases[2]->fqdn_rev_ = true;
+
+    ASSERT_NO_THROW(lmptr_->updateLease4(leases[2]));
+
+    reopen(V4);
+
+    // The lease should be now updated in the storage.
+    l_returned = lmptr_->getLease4(ioaddress4_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[3]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[3], l_returned);
 }
 
 
@@ -733,7 +772,7 @@ GenericLeaseMgrTest::testBasicLease6() {
     lmptr_->commit();
 
     // Reopen the database to ensure that they actually got stored.
-    reopen();
+    reopen(V6);
 
     Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
     ASSERT_TRUE(l_returned);
@@ -761,6 +800,32 @@ GenericLeaseMgrTest::testBasicLease6() {
     l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
     ASSERT_TRUE(l_returned);
     detailCompareLease(leases[2], l_returned);
+
+    reopen(V6);
+
+    // The deleted lease should be still gone after we re-read leases from
+    // persistent storage.
+    l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+    EXPECT_FALSE(l_returned);
+
+    // Check that the second address is still there.
+    l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+
+    // Update some FQDN data, so as we can check that update in
+    // persistent storage works as expected.
+    leases[2]->hostname_ = "memfile.example.com.";
+    leases[2]->fqdn_rev_ = true;
+
+    ASSERT_NO_THROW(lmptr_->updateLease6(leases[2]));
+
+    reopen(V6);
+
+    // The lease should be now updated in the storage.
+    l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
 }
 
 void
@@ -1361,6 +1426,87 @@ GenericLeaseMgrTest::testUpdateLease6() {
     EXPECT_THROW(lmptr_->updateLease6(leases[2]), isc::dhcp::NoSuchLease);
 }
 
+void
+GenericLeaseMgrTest::testRecreateLease4() {
+    // Create a lease.
+    std::vector<Lease4Ptr> leases = createLeases4();
+    // Copy the lease so as we can freely modify it.
+    Lease4Ptr lease(new Lease4(*leases[0]));
+
+    // Add a lease.
+    EXPECT_TRUE(lmptr_->addLease(lease));
+    lmptr_->commit();
+
+    // Check that the lease has been successfuly added.
+    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[0]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(lease, l_returned);
+
+    // Delete a lease, check that it's gone.
+    EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[0]));
+    EXPECT_FALSE(lmptr_->getLease4(ioaddress4_[0]));
+
+    // Modify the copy of the lease. Increasing values or negating them ensures
+    // that they are really modified, because we will never get the same values.
+    ++lease->subnet_id_;
+    ++lease->valid_lft_;
+    lease->fqdn_fwd_ = !lease->fqdn_fwd_;
+    // Make sure that the lease has been really modified.
+    ASSERT_NE(*lease, *leases[1]);
+    // Add the updated lease.
+    EXPECT_TRUE(lmptr_->addLease(lease));
+    lmptr_->commit();
+
+    // Reopen the lease database, so as the lease is re-read.
+    reopen(V4);
+
+    // The lease in the database should be modified.
+    l_returned = lmptr_->getLease4(ioaddress4_[0]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(lease, l_returned);
+}
+
+void
+GenericLeaseMgrTest::testRecreateLease6() {
+    // Create a lease.
+    std::vector<Lease6Ptr> leases = createLeases6();
+    // Copy the lease so as we can freely modify it.
+    Lease6Ptr lease(new Lease6(*leases[0]));
+
+    // Add a lease.
+    EXPECT_TRUE(lmptr_->addLease(lease));
+    lmptr_->commit();
+
+    // Check that the lease has been successfuly added.
+    Lease6Ptr l_returned = lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(lease, l_returned);
+
+    // Delete a lease, check that it's gone.
+    EXPECT_TRUE(lmptr_->deleteLease(ioaddress6_[0]));
+    EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]));
+
+    // Modify the copy of the lease. Increasing values or negating them ensures
+    // that they are really modified, because we will never get the same values.
+    ++lease->subnet_id_;
+    ++lease->valid_lft_;
+    lease->fqdn_fwd_ = !lease->fqdn_fwd_;
+    // Make sure that the lease has been really modified.
+    ASSERT_NE(*lease, *leases[1]);
+    // Add the updated lease.
+    EXPECT_TRUE(lmptr_->addLease(lease));
+    lmptr_->commit();
+
+    // Reopen the lease database, so as the lease is re-read.
+    reopen(V6);
+
+    // The lease in the database should be modified.
+    l_returned = lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]);
+    ASSERT_TRUE(l_returned);
+    detailCompareLease(lease, l_returned);
+}
+
+
 }; // namespace test
 }; // namespace dhcp
 }; // namespace isc
diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
index 03ccbe2..c153f08 100644
--- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
+++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
@@ -30,6 +30,12 @@ namespace test {
 class GenericLeaseMgrTest : public ::testing::Test {
 public:
 
+    /// @brief Universe (V4 or V6).
+    enum Universe {
+        V4,
+        V6
+    };
+
     /// @brief Default constructor.
     GenericLeaseMgrTest();
 
@@ -40,7 +46,9 @@ public:
     ///
     /// Closes the database and re-opens it. It must be implemented
     /// in derived classes.
-    virtual void reopen() = 0;
+    ///
+    /// @param u Universe (V4 or V6), required by some backends.
+    virtual void reopen(Universe u = V4) = 0;
 
     /// @brief Initialize Lease4 Fields
     ///
@@ -159,6 +167,13 @@ public:
     /// @todo: check if it does overlap with @ref testGetLease4NullClientId()
     void testLease4NullClientId();
 
+    /// @brief Check that the DHCPv4 lease can be added, removed and recreated.
+    ///
+    /// This test creates a lease, removes it and then recreates it with some
+    /// of the attributes changed. Next it verifies that the lease in the
+    /// persistent storage has been updated as expected.
+    void testRecreateLease4();
+
     /// @brief Basic Lease6 Checks
     ///
     /// Checks that the addLease, getLease6 (by address) and deleteLease (with an
@@ -223,6 +238,13 @@ public:
     /// Checks that the code is able to update an IPv6 lease in the database.
     void testUpdateLease6();
 
+    /// @brief Check that the DHCPv6 lease can be added, removed and recreated.
+    ///
+    /// This test creates a lease, removes it and then recreates it with some
+    /// of the attributes changed. Next it verifies that the lease in the
+    /// persistent storage has been updated as expected.
+    void testRecreateLease6();
+
     /// @brief String forms of IPv4 addresses
     std::vector<std::string>  straddress4_;
 
diff --git a/src/lib/dhcpsrv/tests/lease_file_io.cc b/src/lib/dhcpsrv/tests/lease_file_io.cc
new file mode 100644
index 0000000..8ba00d9
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/lease_file_io.cc
@@ -0,0 +1,69 @@
+// Copyright (C) 2014 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 <dhcpsrv/tests/lease_file_io.h>
+#include <fstream>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+LeaseFileIO::LeaseFileIO(const std::string& filename)
+    : testfile_(filename) {
+    removeFile();
+}
+
+LeaseFileIO::~LeaseFileIO() {
+    removeFile();
+}
+
+bool
+LeaseFileIO::exists() const {
+    std::ifstream fs(testfile_.c_str());
+    bool ok = fs.good();
+    fs.close();
+    return (ok);
+}
+
+std::string
+LeaseFileIO::readFile() const {
+    std::ifstream fs(testfile_.c_str());
+    if (!fs.is_open()) {
+        return ("");
+    }
+    std::string contents((std::istreambuf_iterator<char>(fs)),
+                         std::istreambuf_iterator<char>());
+    fs.close();
+    return (contents);
+}
+
+void
+LeaseFileIO::removeFile() const {
+    remove(testfile_.c_str());
+}
+
+void
+LeaseFileIO::writeFile(const std::string& contents) const {
+    std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+    if (fs.is_open()) {
+        fs << contents;
+        fs.close();
+    }
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
diff --git a/src/lib/dhcpsrv/tests/lease_file_io.h b/src/lib/dhcpsrv/tests/lease_file_io.h
new file mode 100644
index 0000000..8e8e128
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/lease_file_io.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2014 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_IO_H
+#define LEASE_FILE_IO_H
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief This class contains functions to perform IO operations on files.
+///
+/// This class is solely used by unit tests. Some tests often need files
+/// as an input. This class allows for easy creation of text files that can
+/// be later used by unit tests. It also provides method to read the contents
+/// of the existing file and remove existing file (cleanup after unit test).
+class LeaseFileIO {
+public:
+    /// @brief Constructor
+    ///
+    /// @param filename Abolsute path to the file.
+    LeaseFileIO(const std::string& filename);
+
+    /// @brief Destructor.
+    ~LeaseFileIO();
+
+    /// @brief Check if test file exists on disk.
+    bool exists() const;
+
+    /// @brief Reads whole lease file.
+    ///
+    /// @return Contents of the file.
+    std::string readFile() const;
+
+    /// @brief Removes existing file (if any).
+    void removeFile() const;
+
+    /// @brief Creates file with contents.
+    ///
+    /// @param contents Contents of the file.
+    void writeFile(const std::string& contents) const;
+
+    /// @brief Absolute path to the file used in the tests.
+    std::string testfile_;
+
+};
+
+}
+}
+}
+
+#endif // LEASE_FILE_IO_H
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 0037bc9..55b4114 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -253,7 +253,7 @@ public:
     ///
     /// No-op implementation. We need to provide concrete implementation,
     /// as this is a pure virtual method in GenericLeaseMgrTest.
-    virtual void reopen() {
+    virtual void reopen(Universe) {
     }
 
 };
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
index 1b2001e..30a8884 100644
--- a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
@@ -16,8 +16,11 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
+#include <dhcpsrv/tests/lease_file_io.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 #include <gtest/gtest.h>
@@ -32,6 +35,7 @@ using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 
 namespace {
+
 // empty class for now, but may be extended once Addr6 becomes bigger
 class MemfileLeaseMgrTest : public GenericLeaseMgrTest {
 public:
@@ -39,43 +43,170 @@ public:
     /// @brief memfile lease mgr test constructor
     ///
     /// Creates memfile and stores it in lmptr_ pointer
-    MemfileLeaseMgrTest() {
-        const LeaseMgr::ParameterMap pmap;
-        lmptr_ = new Memfile_LeaseMgr(pmap);
+    MemfileLeaseMgrTest() :
+        io4_(getLeaseFilePath("leasefile4_0.csv")),
+        io6_(getLeaseFilePath("leasefile6_0.csv")) {
+
+        // Make sure there are no dangling files after previous tests.
+        io4_.removeFile();
+        io6_.removeFile();
     }
 
-    virtual void reopen() {
-        /// @todo: write lease to disk, flush, read file from disk
+    /// @brief Reopens the connection to the backend.
+    ///
+    /// This function is called by unit tests to force reconnection of the
+    /// backend to check that the leases are stored and can be retrieved
+    /// from the storage.
+    ///
+    /// @param u Universe (V4 or V6)
+    virtual void reopen(Universe u) {
+        LeaseMgrFactory::destroy();
+        startBackend(u);
     }
 
     /// @brief destructor
     ///
     /// destroys lease manager backend.
     virtual ~MemfileLeaseMgrTest() {
-        delete lmptr_;
-        lmptr_ = 0;
+        LeaseMgrFactory::destroy();
+    }
+
+    /// @brief Return path to the lease file used by unit tests.
+    ///
+    /// @param filename Name of the lease file appended to the path to the
+    /// directory where test data is held.
+    ///
+    /// @return Full path to the lease file.
+    static std::string getLeaseFilePath(const std::string& filename) {
+        std::ostringstream s;
+        s << TEST_DATA_BUILDDIR << "/" << filename;
+        return (s.str());
     }
 
+    /// @brief Returns the configuration string for the backend.
+    ///
+    /// This string configures the @c LeaseMgrFactory to create the memfile
+    /// backend and use leasefile4_0.csv and leasefile6_0.csv files as
+    /// storage for leases.
+    ///
+    /// @param no_universe Indicates whether universe parameter should be
+    /// included (false), or not (true).
+    ///
+    /// @return Configuration string for @c LeaseMgrFactory.
+    static std::string getConfigString(Universe u) {
+        std::ostringstream s;
+        s << "type=memfile " << (u == V4 ? "universe=4 " : "universe=6 ")
+          << "name="
+          << getLeaseFilePath(u == V4 ? "leasefile4_0.csv" : "leasefile6_0.csv");
+        return (s.str());
+    }
+
+    /// @brief Creates instance of the backend.
+    ///
+    /// @param u Universe (v4 or V6).
+    void startBackend(Universe u) {
+        try {
+            LeaseMgrFactory::create(getConfigString(u));
+        } catch (...) {
+            std::cerr << "*** ERROR: unable to create instance of the Memfile\n"
+                " lease database backend.\n";
+            throw;
+        }
+        lmptr_ = &(LeaseMgrFactory::instance());
+    }
+
+    /// @brief Object providing access to v4 lease IO.
+    LeaseFileIO io4_;
+
+    /// @brief Object providing access to v6 lease IO.
+    LeaseFileIO io6_;
+
 };
 
 // This test checks if the LeaseMgr can be instantiated and that it
 // parses parameters string properly.
 TEST_F(MemfileLeaseMgrTest, constructor) {
-
-    const LeaseMgr::ParameterMap pmap;  // Empty parameter map
+    LeaseMgr::ParameterMap pmap;
+    pmap["universe"] = "4";
+    pmap["persist"] = "false";
     boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr;
 
-    ASSERT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
+    EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
+
+    pmap["persist"] = "true";
+    pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
+    EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
+
+    // Expecting that persist parameter is yes or no. Everything other than
+    // that is wrong.
+    pmap["persist"] = "bogus";
+    pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
+    EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
 }
 
 // Checks if the getType() and getName() methods both return "memfile".
 TEST_F(MemfileLeaseMgrTest, getTypeAndName) {
+    startBackend(V4);
     EXPECT_EQ(std::string("memfile"), lmptr_->getType());
     EXPECT_EQ(std::string("memory"),  lmptr_->getName());
 }
 
+// Checks if the path to the lease files is initialized correctly.
+TEST_F(MemfileLeaseMgrTest, getLeaseFilePath) {
+    // Initialize IO objects, so as the test csv files get removed after the
+    // test (when destructors are called).
+    LeaseFileIO io4(getLeaseFilePath("leasefile4_1.csv"));
+    LeaseFileIO io6(getLeaseFilePath("leasefile6_1.csv"));
+
+    LeaseMgr::ParameterMap pmap;
+    pmap["universe"] = "4";
+    pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
+    boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
+
+    EXPECT_EQ(pmap["name"],
+              lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V4));
+
+    pmap["persist"] = "false";
+    lease_mgr.reset(new Memfile_LeaseMgr(pmap));
+    EXPECT_TRUE(lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V4).empty());
+    EXPECT_TRUE(lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V6).empty());
+}
+
+// Check if the persitLeases correctly checks that leases should not be written
+// to disk when disabled through configuration.
+TEST_F(MemfileLeaseMgrTest, persistLeases) {
+    // Initialize IO objects, so as the test csv files get removed after the
+    // test (when destructors are called).
+    LeaseFileIO io4(getLeaseFilePath("leasefile4_1.csv"));
+    LeaseFileIO io6(getLeaseFilePath("leasefile6_1.csv"));
+
+    LeaseMgr::ParameterMap pmap;
+    pmap["universe"] = "4";
+    // Specify the names of the lease files. Leases will be written.
+    pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
+    boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
+
+    lease_mgr.reset(new Memfile_LeaseMgr(pmap));
+    EXPECT_TRUE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4));
+    EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
+
+    pmap["universe"] = "6";
+    pmap["name"] = getLeaseFilePath("leasefile6_1.csv");
+    lease_mgr.reset(new Memfile_LeaseMgr(pmap));
+    EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4));
+    EXPECT_TRUE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
+
+    // This should disable writes of leases to disk.
+    pmap["persist"] = "false";
+    lease_mgr.reset(new Memfile_LeaseMgr(pmap));
+    EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4));
+    EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
+}
+
+
 // Checks that adding/getting/deleting a Lease6 object works.
 TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
+    startBackend(V6);
     testAddGetDelete6(true); // true - check T1,T2 values
     // memfile is able to preserve those values, but some other
     // backends can't do that.
@@ -86,6 +217,7 @@ TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
 /// Checks that the addLease, getLease4 (by address) and deleteLease (with an
 /// IPv4 address) works.
 TEST_F(MemfileLeaseMgrTest, basicLease4) {
+    startBackend(V4);
     testBasicLease4();
 }
 
@@ -93,16 +225,19 @@ TEST_F(MemfileLeaseMgrTest, basicLease4) {
 
 // Simple test about lease4 retrieval through client id method
 TEST_F(MemfileLeaseMgrTest, getLease4ClientId) {
+    startBackend(V4);
     testGetLease4ClientId();
 }
 
 // Checks that lease4 retrieval client id is null is working
 TEST_F(MemfileLeaseMgrTest, getLease4NullClientId) {
+    startBackend(V4);
     testGetLease4NullClientId();
 }
 
 // Checks lease4 retrieval through HWAddr
 TEST_F(MemfileLeaseMgrTest, getLease4HWAddr1) {
+    startBackend(V4);
     testGetLease4HWAddr1();
 }
 
@@ -111,11 +246,13 @@ TEST_F(MemfileLeaseMgrTest, getLease4HWAddr1) {
 /// Adds leases to the database and checks that they can be accessed via
 /// a combination of DUID and IAID.
 TEST_F(MemfileLeaseMgrTest, getLease4HWAddr2) {
+    startBackend(V4);
     testGetLease4HWAddr2();
 }
 
 // Checks lease4 retrieval with clientId, HWAddr and subnet_id
 TEST_F(MemfileLeaseMgrTest, getLease4ClientIdHWAddrSubnetId) {
+    startBackend(V4);
     testGetLease4ClientIdHWAddrSubnetId();
 }
 
@@ -124,10 +261,8 @@ TEST_F(MemfileLeaseMgrTest, getLease4ClientIdHWAddrSubnetId) {
 /// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
 /// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id.
 /// (client-id is optional and may not be present)
-TEST_F(MemfileLeaseMgrTest, DISABLED_lease4NullClientId) {
-
-    /// @todo Test is disabled, because memfile does not support disk storage, so
-    /// all leases are lost after reopen()
+TEST_F(MemfileLeaseMgrTest, lease4NullClientId) {
+    startBackend(V4);
     testLease4NullClientId();
 }
 
@@ -138,6 +273,7 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_lease4NullClientId) {
 TEST_F(MemfileLeaseMgrTest, DISABLED_getLease4HwaddrSubnetId) {
 
     /// @todo: fails on memfile. It's probably a memfile bug.
+    startBackend(V4);
     testGetLease4HWAddrSubnetId();
 }
 
@@ -146,6 +282,7 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_getLease4HwaddrSubnetId) {
 /// Adds leases to the database and checks that they can be accessed via
 /// the Client ID.
 TEST_F(MemfileLeaseMgrTest, getLease4ClientId2) {
+    startBackend(V4);
     testGetLease4ClientId2();
 }
 
@@ -153,6 +290,7 @@ TEST_F(MemfileLeaseMgrTest, getLease4ClientId2) {
 //
 // Check that the system can cope with a client ID of any size.
 TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSize) {
+    startBackend(V4);
     testGetLease4ClientIdSize();
 }
 
@@ -161,9 +299,20 @@ TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSize) {
 /// Adds leases to the database and checks that they can be accessed via
 /// a combination of client and subnet IDs.
 TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSubnetId) {
+    startBackend(V4);
     testGetLease4ClientIdSubnetId();
 }
 
+/// @brief Basic Lease6 Checks
+///
+/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
+/// IPv6 address) works.
+TEST_F(MemfileLeaseMgrTest, basicLease6) {
+    startBackend(V6);
+    testBasicLease6();
+}
+
+
 /// @brief Check GetLease6 methods - access by DUID/IAID
 ///
 /// Adds leases to the database and checks that they can be accessed via
@@ -171,6 +320,7 @@ TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSubnetId) {
 /// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type,
 /// const DUID& duid, uint32_t iaid) const is not implemented yet.
 TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidIaid) {
+    startBackend(V6);
     testGetLeases6DuidIaid();
 }
 
@@ -179,6 +329,7 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidIaid) {
 /// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type,
 /// const DUID& duid, uint32_t iaid) const is not implemented yet.
 TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidSize) {
+    startBackend(V6);
     testGetLeases6DuidSize();
 }
 
@@ -191,6 +342,7 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidSize) {
 /// @todo: Disabled, because type parameter in Memfile_LeaseMgr::getLease6
 /// (Lease::Type, const isc::asiolink::IOAddress& addr) const is not used.
 TEST_F(MemfileLeaseMgrTest, DISABLED_lease6LeaseTypeCheck) {
+    startBackend(V6);
     testLease6LeaseTypeCheck();
 }
 
@@ -199,12 +351,14 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_lease6LeaseTypeCheck) {
 /// Adds leases to the database and checks that they can be accessed via
 /// a combination of DIUID and IAID.
 TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetId) {
+    startBackend(V6);
     testGetLease6DuidIaidSubnetId();
 }
 
 /// Checks that getLease6(type, duid, iaid, subnet-id) works with different
 /// DUID sizes
 TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
+    startBackend(V6);
     testGetLease6DuidIaidSubnetIdSize();
 }
 
@@ -215,6 +369,7 @@ TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
 /// We should reconsider if lease{4,6} structures should have a limit
 /// implemented in them.
 TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease4) {
+    startBackend(V4);
     testUpdateLease4();
 }
 
@@ -225,9 +380,30 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease4) {
 /// We should reconsider if lease{4,6} structures should have a limit
 /// implemented in them.
 TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease6) {
+    startBackend(V6);
     testUpdateLease6();
 }
 
+/// @brief DHCPv4 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(MemfileLeaseMgrTest, testRecreateLease4) {
+    startBackend(V4);
+    testRecreateLease4();
+}
+
+/// @brief DHCPv6 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(MemfileLeaseMgrTest, testRecreateLease6) {
+    startBackend(V6);
+    testRecreateLease6();
+}
+
 // The following tests are not applicable for memfile. When adding
 // new tests to the list here, make sure to provide brief explanation
 // why they are not applicable:
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index 3f61e8c..b985451 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -183,7 +183,10 @@ public:
     ///
     /// Closes the database and re-open it.  Anything committed should be
     /// visible.
-    void reopen() {
+    ///
+    /// Parameter is ignored for MySQL backend as the v4 and v6 leases share
+    /// the same database.
+    void reopen(Universe) {
         LeaseMgrFactory::destroy();
         LeaseMgrFactory::create(validConnectionString());
         lmptr_ = &(LeaseMgrFactory::instance());
@@ -485,4 +488,22 @@ TEST_F(MySqlLeaseMgrTest, updateLease6) {
     testUpdateLease6();
 }
 
+/// @brief DHCPv4 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(MySqlLeaseMgrTest, testRecreateLease4) {
+    testRecreateLease4();
+}
+
+/// @brief DHCPv6 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(MySqlLeaseMgrTest, testRecreateLease6) {
+    testRecreateLease6();
+}
+
 }; // Of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
index 653b843..1744bd2 100644
--- a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
@@ -190,7 +190,10 @@ public:
     ///
     /// Closes the database and re-open it.  Anything committed should be
     /// visible.
-    void reopen() {
+    ///
+    /// Parameter is ignored for Postgres abckend as the v4 and v6 leases share
+    /// the same database.
+    void reopen(Universe) {
         LeaseMgrFactory::destroy();
         LeaseMgrFactory::create(validConnectionString());
         lmptr_ = &(LeaseMgrFactory::instance());
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
index 7d3781e..e3ba39f 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -19,7 +19,8 @@ AM_CXXFLAGS += $(BOOST_MAPPED_FILE_CXXFLAG)
 endif
 
 lib_LTLIBRARIES = libb10-util.la
-libb10_util_la_SOURCES  = filename.h filename.cc
+libb10_util_la_SOURCES  = csv_file.h csv_file.cc
+libb10_util_la_SOURCES += filename.h filename.cc
 libb10_util_la_SOURCES += locks.h lru_list.h
 libb10_util_la_SOURCES += strutil.h strutil.cc
 libb10_util_la_SOURCES += buffer.h io_utilities.h
diff --git a/src/lib/util/csv_file.cc b/src/lib/util/csv_file.cc
new file mode 100644
index 0000000..968ee92
--- /dev/null
+++ b/src/lib/util/csv_file.cc
@@ -0,0 +1,374 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/csv_file.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <fstream>
+#include <sstream>
+
+namespace isc {
+namespace util {
+
+CSVRow::CSVRow(const size_t cols, const char separator)
+    : separator_(1, separator), values_(cols) {
+}
+
+CSVRow::CSVRow(const std::string& text, const char separator)
+    : separator_(1, separator) {
+    // Parsing is exception safe, so this will not throw.
+    parse(text.c_str());
+}
+
+void
+CSVRow::parse(const std::string& line) {
+    // Tokenize the string using a specified separator. Disable compression,
+    // so as the two consecutive separators mark an empty value.
+    boost::split(values_, line, boost::is_any_of(separator_),
+                 boost::algorithm::token_compress_off);
+}
+
+std::string
+CSVRow::readAt(const size_t at) const {
+    checkIndex(at);
+    return (values_[at]);
+}
+
+std::string
+CSVRow::render() const {
+    std::ostringstream s;
+    for (int i = 0; i < values_.size(); ++i) {
+        // Do not put separator before the first value.
+        if (i > 0) {
+            s << separator_;
+        }
+        s << values_[i];
+    }
+    return (s.str());
+}
+
+void
+CSVRow::writeAt(const size_t at, const char* value) {
+    checkIndex(at);
+    values_[at] = value;
+}
+
+std::ostream& operator<<(std::ostream& os, const CSVRow& row) {
+    os << row.render();
+    return (os);
+}
+
+void
+CSVRow::checkIndex(const size_t at) const {
+    if (at >= values_.size()) {
+        isc_throw(CSVFileError, "value index '" << at << "' of the CSV row"
+                  " is out of bounds; maximal index is '"
+                  << (values_.size() - 1) << "'");
+    }
+}
+
+CSVFile::CSVFile(const std::string& filename)
+    : primary_separator_(','), filename_(filename), fs_(), cols_(0),
+      read_msg_() {
+}
+
+CSVFile::~CSVFile() {
+    close();
+}
+
+void
+CSVFile::close() {
+    // It is allowed to close multiple times. If file has been already closed,
+    // this is no-op.
+    if (fs_) {
+        fs_->close();
+        fs_.reset();
+    }
+}
+
+void
+CSVFile::flush() const {
+    checkStreamStatusAndReset("flush");
+    fs_->flush();
+}
+
+void
+CSVFile::addColumn(const std::string& col_name) {
+    // It is not allowed to add a new column when file is open.
+    if (fs_) {
+        isc_throw(CSVFileError, "attempt to add a column '" << col_name
+                  << "' while the file '" << getFilename()
+                  << "' is open");
+    }
+    addColumnInternal(col_name);
+}
+
+void
+CSVFile::addColumnInternal(const std::string& col_name) {
+    if (getColumnIndex(col_name) >= 0) {
+        isc_throw(CSVFileError, "attempt to add duplicate column '"
+                  << col_name << "'");
+    }
+    cols_.push_back(col_name);
+}
+
+void
+CSVFile::append(const CSVRow& row) const {
+    checkStreamStatusAndReset("append");
+
+    if (row.getValuesCount() != getColumnCount()) {
+        isc_throw(CSVFileError, "number of values in the CSV row '"
+                  << row.getValuesCount() << "' doesn't match the number of"
+                  " columns in the CSV file '" << getColumnCount() << "'");
+    }
+
+    /// @todo Apparently, seekp and seekg are interchangable. A call to seekp
+    /// results in moving the input pointer too. This is ok for now. It means
+    /// that when the append() is called, the read pointer is moved to the EOF.
+    /// For the current use cases we only read a file and then append a new
+    /// content. If we come up with the scenarios when read and write is
+    /// needed at the same time, we may revisit this: perhaps remember the
+    /// old pointer. Also, for safety, we call both functions so as we are
+    /// sure that both pointers are moved.
+    fs_->seekp(0, std::ios_base::end);
+    fs_->seekg(0, std::ios_base::end);
+    fs_->clear();
+
+    std::string text = row.render();
+    *fs_ << text << std::endl;
+    if (!fs_->good()) {
+        fs_->clear();
+        isc_throw(CSVFileError, "failed to write CSV row '"
+                  << text << "' to the file '" << filename_ << "'");
+    }
+}
+
+void
+CSVFile::checkStreamStatusAndReset(const std::string& operation) const {
+    if (!fs_) {
+        isc_throw(CSVFileError, "NULL stream pointer when performing '"
+                  << operation << "' on file '" << filename_ << "'");
+
+    } else if (!fs_->is_open()) {
+        fs_->clear();
+        isc_throw(CSVFileError, "closed stream when performing '"
+                  << operation << "' on file '" << filename_ << "'");
+
+    } else {
+        fs_->clear();
+    }
+}
+
+std::ifstream::pos_type
+CSVFile::size() const {
+    std::ifstream fs(filename_.c_str());
+    bool ok = fs.good();
+    // If something goes wrong, including that the file doesn't exist,
+    // return 0.
+    if (!ok) {
+        fs.close();
+        return (0);
+    }
+    std::ifstream::pos_type pos;
+    try {
+        // Seek to the end of file and see where we are. This is a size of
+        // the file.
+        fs.seekg(0, std::ifstream::end);
+        pos = fs.tellg();
+        fs.close();
+    } catch (const std::exception& ex) {
+        return (0);
+    }
+    return (pos);
+}
+
+int
+CSVFile::getColumnIndex(const std::string& col_name) const {
+    for (int i = 0; i < cols_.size(); ++i) {
+        if (cols_[i] == col_name) {
+            return (i);
+        }
+    }
+    return (-1);
+}
+
+std::string
+CSVFile::getColumnName(const size_t col_index) const {
+    if (col_index >= cols_.size()) {
+        isc_throw(isc::OutOfRange, "column index " << col_index << " in the "
+                  " CSV file '" << filename_ << "' is out of range; the CSV"
+                  " file has only  " << cols_.size() << " columns ");
+    }
+    return (cols_[col_index]);
+}
+
+bool
+CSVFile::next(CSVRow& row, const bool skip_validation) {
+    // Set somethings as row validation error. Although, we haven't started
+    // actual row validation we should get rid of any previously recorded
+    // errors so as the caller doesn't interpret them as the current one.
+    setReadMsg("validation not started");
+
+    try {
+        // Check that stream is "ready" for any IO operations.
+        checkStreamStatusAndReset("get next row");
+
+    } catch (isc::Exception& ex) {
+        setReadMsg(ex.what());
+        return (false);
+    }
+
+    // Get exactly one line of the file.
+    std::string line;
+    std::getline(*fs_, line);
+    // If we got empty line because we reached the end of file
+    // return an empty row.
+    if (line.empty() && fs_->eof()) {
+        row = EMPTY_ROW();
+        return (true);
+
+    } else if (!fs_->good()) {
+        // If we hit an IO error, communicate it to the caller but do NOT close
+        // the stream. Caller may try again.
+        setReadMsg("error reading a row from CSV file '"
+                   + std::string(filename_) + "'");
+        return (false);
+    }
+    // If we read anything, parse it.
+    row.parse(line);
+
+    // And check if it is correct.
+    return (skip_validation ? true : validate(row));
+}
+
+void
+CSVFile::open() {
+    // If file doesn't exist or is empty, we have to create our own file.
+    if (size() == 0) {
+        recreate();
+
+    } else {
+        // Try to open existing file, holding some data.
+        fs_.reset(new std::fstream(filename_.c_str()));
+
+        // Catch exceptions so as we can close the file if error occurs.
+        try {
+            // The file may fail to open. For example, because of insufficient
+            // persmissions. Although the file is not open we should call close
+            // to reset our internal pointer.
+            if (!fs_->is_open()) {
+                isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
+            }
+            // Make sure we are on the beginning of the file, so as we can parse
+            // the header.
+            fs_->seekg(0);
+            if (!fs_->good()) {
+                isc_throw(CSVFileError, "unable to set read pointer in the file '"
+                          << filename_ << "'");
+            }
+
+            // Read the header.
+            CSVRow header;
+            if (!next(header, true)) {
+                isc_throw(CSVFileError, "failed to read and parse header of the"
+                          " CSV file '" << filename_ << "': "
+                          << getReadMsg());
+            }
+
+            // Check the header against the columns specified for the CSV file.
+            if (!validateHeader(header)) {
+                isc_throw(CSVFileError, "invalid header '" << header
+                          << "' in CSV file '" << filename_ << "'");
+            }
+
+            // Everything is good, so if we haven't added any columns yet,
+            // add them.
+            if (getColumnCount() == 0) {
+                for (size_t i = 0; i < header.getValuesCount(); ++i) {
+                    addColumnInternal(header.readAt(i));
+                }
+            }
+        } catch (const std::exception& ex) {
+            close();
+            throw;
+        }
+    }
+}
+
+void
+CSVFile::recreate() {
+    // There is no sense creating a file if we don't specify columns for it.
+    if (getColumnCount() == 0) {
+        close();
+        isc_throw(CSVFileError, "no columns defined for the newly"
+                  " created CSV file '" << filename_ << "'");
+    }
+
+    // Close any dangling files.
+    close();
+    fs_.reset(new std::fstream(filename_.c_str(), std::fstream::out));
+    if (!fs_->is_open()) {
+        close();
+        isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
+    }
+    // Opened successfuly. Write a header to it.
+    try {
+        CSVRow header(getColumnCount());
+        for (int i = 0; i < getColumnCount(); ++i) {
+            header.writeAt(i, getColumnName(i));
+        }
+        *fs_ << header << std::endl;
+
+    } catch (const std::exception& ex) {
+        close();
+        isc_throw(CSVFileError, ex.what());
+    }
+
+}
+
+bool
+CSVFile::validate(const CSVRow& row) {
+    setReadMsg("success");
+    bool ok = (row.getValuesCount() == getColumnCount());
+    if (!ok) {
+        std::ostringstream s;
+        s << "the size of the row '" << row << "' doesn't match the number of"
+            " columns '" << getColumnCount() << "' of the CSV file '"
+          << filename_ << "'";
+        setReadMsg(s.str());
+    }
+    return (ok);
+}
+
+bool
+CSVFile::validateHeader(const CSVRow& header) {
+    if (getColumnCount() == 0) {
+        return (true);
+    }
+
+    if (getColumnCount() != header.getValuesCount()) {
+        return (false);
+    }
+
+    for (int i = 0; i < getColumnCount(); ++i) {
+        if (getColumnName(i) != header.readAt(i)) {
+            return (false);
+        }
+    }
+    return (true);
+}
+
+} // end of isc::util namespace
+} // end of isc namespace
diff --git a/src/lib/util/csv_file.h b/src/lib/util/csv_file.h
new file mode 100644
index 0000000..12938db
--- /dev/null
+++ b/src/lib/util/csv_file.h
@@ -0,0 +1,480 @@
+// Copyright (C) 2014 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 CSV_FILE_H
+#define CSV_FILE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <fstream>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during CSV file processing.
+class CSVFileError : public Exception {
+public:
+    CSVFileError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a single row of the CSV file.
+///
+/// The object of this type can create the string holding a collection of the
+/// comma separated values, representing a row of the CSV file. It allows the
+/// selection of any character as a separator for the values. The default
+/// separator is the comma symbol.
+///
+/// The @c CSVRow object can be constructed in two different ways. The first
+/// option is that the caller creates an object holding empty values
+/// and then adds values one by one. Note that it is possible to either add
+/// a string or a number. The number is converted to the appropriate text
+/// representation. When all the values are added, the text representation of
+/// the row can be obtained by calling @c CSVRow::render function or output
+/// stream operator.
+///
+/// The @c CSVRow object can be also constructed by parsing a row of a CSV
+/// file. In this case, the separator has to be known in advance and passed to
+/// the class constructor. The constructor will call the @c CSVRow::parse
+/// function internally to tokenize the CSV row and create the collection of
+/// values. The class accessors can be then used to retrieve individual values.
+///
+/// This class is meant to be used by the @c CSVFile class to manipulate
+/// individual rows of the CSV file.
+class CSVRow {
+public:
+
+    /// @brief Constructor, creates the raw to be used for output.
+    ///
+    /// Creates CSV row with empty values. The values should be
+    /// later set using the @c CSVRow::writeAt functions. When the
+    /// @c CSVRow::render is called, the text representation of the
+    /// row will be created using a separator character specified
+    /// as an argument of this constructor.
+    ///
+    /// This constructor is exception-free.
+    ///
+    /// @param cols Number of values in the row.
+    /// @param separator Character used as a separator between values in the
+    /// text representation of the row.
+    CSVRow(const size_t cols = 0, const char separator = ',');
+
+    /// @brief Constructor, parses a single row of the CSV file.
+    ///
+    /// This constructor should be used to parse a single row of the CSV
+    /// file. The separator being used for the particular row needs to
+    /// be known in advance and specified as an argument of the constructor
+    /// if other than the default separator is used in the row being parsed.
+    /// An example string to be parsed by this function looks as follows:
+    /// "foo,bar,foo-bar".
+    ///
+    /// This constructor is exception-free.
+    ///
+    /// @param text Text representation of the CSV row.
+    /// @param separator Character being used as a separator in a parsed file.
+    CSVRow(const std::string& text, const char separator = ',');
+
+    /// @brief Returns number of values in a CSV row.
+    size_t getValuesCount() const {
+        return (values_.size());
+    }
+
+    /// @brief Parse the CSV file row.
+    ///
+    /// This function parses a string containing CSV values and assigns them
+    /// to the @c values_ private container. These values can be retrieved
+    /// from the container by calling @c CSVRow::readAt function.
+    ///
+    /// This function is exception-free.
+    ///
+    /// @param line String holding a row of comma separated values.
+    void parse(const std::string& line);
+
+    /// @brief Retrieves a value from the internal container.
+    ///
+    /// @param at Index of the value in the container. The values are indexed
+    /// from 0, where 0 corresponds to the left-most value in the CSV file row.
+    ///
+    /// @return Value at specified index in the text form.
+    ///
+    /// @throw CSVFileError if the index is out of range. The number of elements
+    /// being held by the container can be obtained using
+    /// @c CSVRow::getValuesCount.
+    std::string readAt(const size_t at) const;
+
+    /// @brief Retrieves a value from the internal container.
+    ///
+    /// This method is reads a value from the internal container and converts
+    /// this value to the type specified as a template parameter. Internally
+    /// it uses @c boost::lexical_cast.
+    ///
+    /// @param at Index of the value in the container. The values are indexed
+    /// from 0, where 0 corresponds to the left-most value in the CSV file row.
+    /// @tparam T type of the value to convert to.
+    ///
+    /// @return Converted value.
+    ///
+    /// @throw CSVFileError if the index is out of range or if the
+    /// @c boost::bad_lexical_cast is thrown by the @c boost::lexical_cast.
+    template<typename T>
+    T readAndConvertAt(const size_t at) const {
+        T cast_value;
+        try {
+            cast_value = boost::lexical_cast<T>(readAt(at).c_str());
+
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(CSVFileError, ex.what());
+        }
+        return (cast_value);
+    }
+
+    /// @brief Creates a text representation of the CSV file row.
+    ///
+    /// This function iterates over all values currently held in the internal
+    /// @c values_ container and appends them to a string. The values are
+    /// separated using the separator character specified in the constructor.
+    ///
+    /// This function is exception free.
+    ///
+    /// @return Text representation of the CSV file row.
+    std::string render() const;
+
+    /// @brief Replaces the value at specified index.
+    ///
+    /// This function is used to set values to be rendered using
+    /// @c CSVRow::render function.
+    ///
+    /// @param at Index of the value to be replaced.
+    /// @param value Value to be written given as string.
+    ///
+    /// @throw CSVFileError if index is out of range.
+    void writeAt(const size_t at, const char* value);
+
+    /// @brief Replaces the value at specified index.
+    ///
+    /// This function is used to set values to be rendered using
+    /// @c CSVRow::render function.
+    ///
+    /// @param at Index of the value to be replaced.
+    /// @param value Value to be written given as string.
+    ///
+    /// @throw CSVFileError if index is out of range.
+    void writeAt(const size_t at, const std::string& value) {
+        writeAt(at, value.c_str());
+    }
+
+    /// @brief Replaces the value at specified index.
+    ///
+    /// This function is used to set values to be rendered using
+    /// @c CSVRow::render function.
+    ///
+    /// @param at Index of the value to be replaced.
+    /// @param value Value to be written - typically a number.
+    /// @tparam T Type of the value being written.
+    ///
+    /// @throw CSVFileError if index is out of range.
+    template<typename T>
+    void writeAt(const size_t at, const T value) {
+        checkIndex(at);
+        try {
+            values_[at] = boost::lexical_cast<std::string>(value);
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(CSVFileError, "unable to stringify the value to be"
+                      " written in the CSV file row at position '"
+                      << at << "'");
+        }
+    }
+
+    /// @brief Equality operator.
+    ///
+    /// Two CSV rows are equal when their string representation is equal. This
+    /// includes the order of fields, separator etc.
+    ///
+    /// @param other Object to compare to.
+    bool operator==(const CSVRow& other) const {
+        return (render() == other.render());
+    }
+
+    /// @brief Unequality operator.
+    ///
+    /// Two CSV rows are unequal when their string representation is unequal.
+    /// This includes the order of fields, separator etc.
+    ///
+    /// @param other Object to compare to.
+    bool operator!=(const CSVRow& other) const {
+        return (render() != other.render());
+    }
+
+private:
+
+    /// @brief Check if the specified index of the value is in range.
+    ///
+    /// This function is used interally by other functions.
+    ///
+    /// @param at Value index.
+    /// @throw CSVFileError if specified index is not in range.
+    void checkIndex(const size_t at) const;
+
+    /// @brief Separator character specifed in the constructor.
+    ///
+    /// @note Separator is held as a string object (one character long),
+    /// because the boost::is_any_of algorithm requires a string, not a
+    /// char value. If we held the separator as a char, we would need to
+    /// convert it to string on every call to @c CSVRow::parse.
+    std::string separator_;
+
+    /// @brief Internal container holding values that belong to the row.
+    std::vector<std::string> values_;
+};
+
+/// @brief Overrides standard output stream operator for @c CSVRow object.
+///
+/// The resulting string of characters is the same as the one returned by
+/// @c CSVRow::render function.
+///
+/// @param os Output stream.
+/// @param row Object representing a CSV file row.
+std::ostream& operator<<(std::ostream& os, const CSVRow& row);
+
+/// @brief Provides input/output access to CSV files.
+///
+/// This class provides basic methods to access (parse) and create CSV files.
+/// The file is identified by its name qualified with the absolute path.
+/// The name of the file is passed to the constructor. Constructor doesn't
+/// open/create a file, but simply records a file name specified by a caller.
+///
+/// There are two functions that can be used to open a file:
+/// - @c open - opens an existing file; if the file doesn't exist it creates it,
+/// - @c recreate - removes existing file and creates a new one.
+///
+/// When the file is opened its header file is parsed and column names are
+/// idenetified. At this point it is already possible to get the list of the
+/// column names using appropriate accessors. The data rows are not parsed
+/// at this time. The row parsing is triggered by calling @c next function.
+/// The result of parsing a row is stored in the @c CSVRow object passed as
+/// a parameter.
+///
+/// When the new file is created (when @c recreate is called), the CSV header is
+/// immediately written into it. The header consists of the column names
+/// specified with the @c addColumn function. The subsequent rows are written
+/// into this file by calling @c append.
+class CSVFile {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param filename CSV file name.
+    CSVFile(const std::string& filename);
+
+    /// @brief Destructor
+    virtual ~CSVFile();
+
+    /// @brief Adds new column name.
+    ///
+    /// This column adds a new column but doesn't write it to the file yet.
+    /// The name of the column will be placed in the CSV header when new file
+    /// is created by calling @c recreate or @c open function.
+    ///
+    /// @param col_name Name of the column.
+    ///
+    /// @throw CSVFileError if a column with the specified name exists.
+    void addColumn(const std::string& col_name);
+
+    /// @brief Writes the CSV row into the file.
+    ///
+    /// @param Object representing a CSV file row.
+    ///
+    /// @throw CSVFileError When error occured during IO operation or if the
+    /// size of the row doesn't match the number of columns.
+    void append(const CSVRow& row) const;
+
+    /// @brief Closes the CSV file.
+    void close();
+
+    /// @brief Flushes a file.
+    void flush() const;
+
+    /// @brief Returns the number of columns in the file.
+    size_t getColumnCount() const {
+        return (cols_.size());
+    }
+
+    /// @brief Returns the path to the CSV file.
+    std::string getFilename() const {
+        return (filename_);
+    }
+
+    /// @brief Returns the description of the last error returned by the
+    /// @c CSVFile::next function.
+    ///
+    /// @return Description of the last error during row validation.
+    std::string getReadMsg() const {
+        return (read_msg_);
+    }
+
+    /// @brief Returns the index of the column having specified name.
+    ///
+    /// This function is exception safe.
+    ///
+    /// @param col_name Name of the column.
+    /// @return Index of the column or negative value if the column doesn't
+    /// exist.
+    int getColumnIndex(const std::string& col_name) const;
+
+    /// @brief Returns the name of the column.
+    ///
+    /// @param col_index Index of the column.
+    ///
+    /// @return Name of the column.
+    /// @throw CSVFileError if the specified index is out of range.
+    std::string getColumnName(const size_t col_index) const;
+
+    /// @brief Reads next row from CSV file.
+    ///
+    /// This function will return the @c CSVRow object representing a
+    /// parsed row if parsing is successful. If the end of file has been
+    /// reached, the empty row is returned (a row containing no values).
+    ///
+    /// @param [out] row Object receiving the parsed CSV file.
+    /// @param skip_validation Do not perform validation.
+    ///
+    /// @return true if row has been read and validated; false if validation
+    /// failed.
+    bool next(CSVRow& row, const bool skip_validation = false);
+
+    /// @brief Opens existing file or creates a new one.
+    ///
+    /// This function will try to open existing file if this file has size
+    /// greater than 0. If the file doesn't exist or has size of 0, the
+    /// file is recreated. If the existing file has been opened, the header
+    /// is parsed and column names are initialized in the @c CSVFile object.
+    /// The data pointer in the file is set to the beginning of the first
+    /// row. In order to retrieve the row contents the @c next function should
+    /// be called.
+    ///
+    /// @throw CSVFileError when IO operation fails.
+    void open();
+
+    /// @brief Creates a new CSV file.
+    ///
+    /// The file creation will fail if there are no columns specified.
+    /// Otherwise, this function will write the header to the file.
+    /// In order to write rows to opened file, the @c append function
+    /// should be called.
+    void recreate();
+
+    /// @brief Sets error message after row validation.
+    ///
+    /// The @c CSVFile::validate function is responsible for setting the
+    /// error message after validation of the row read from the CSV file.
+    /// It will use this function to set this message. Note, that the
+    /// @c validate function can set a message after successful validation
+    /// too. Such message could say "success", or something similar.
+    ///
+    /// @param val_msg Error message to be set.
+    void setReadMsg(const std::string& read_msg) {
+        read_msg_ = read_msg;
+    }
+
+    /// @brief Represents empty row.
+    static CSVRow EMPTY_ROW() {
+        static CSVRow row(0);
+        return (row);
+    }
+
+protected:
+
+    /// @brief Adds a column regardless if the file is open or not.
+    ///
+    /// This function adds as new column to the collection. It is meant to be
+    /// called internally by the methods of the base class and derived classes.
+    /// It must not be used in the public scope. The @c CSVFile::addColumn
+    /// must be used in the public scope instead, because it prevents addition
+    /// of the new column when the file is open.
+    ///
+    /// @param col_name Name of the column.
+    ///
+    /// @throw CSVFileError if a column with the specified name exists.
+    void addColumnInternal(const std::string& col_name);
+
+    /// @brief Validate the row read from a file.
+    ///
+    /// This function implements a basic validation for the row read from the
+    /// CSV file. It is virtual so as it may be customized in derived classes.
+    ///
+    /// This default implementation checks that the number of values in the
+    /// row corresponds to the number of columns specified for this file.
+    ///
+    /// If row validation fails, the error message is noted and can be retrieved
+    /// using @c CSVFile::getReadMsg. The function which overrides this
+    /// base implementation is responsible for setting the error message using
+    /// @c CSVFile::setReadMsg.
+    ///
+    /// @param row A row to be validated.
+    ///
+    /// @return true if the column is valid; false otherwise.
+    virtual bool validate(const CSVRow& row);
+
+private:
+
+    /// @brief This function validates the header of the CSV file.
+    ///
+    /// If there are any columns added to the @c CSVFile object, it will
+    /// compare that they exactly match (including order) the header read
+    /// from the file.
+    ///
+    /// This function is called internally by @CSVFile::open.
+    ///
+    /// @param header A row holding a header.
+    /// @return true if header matches the columns; false otherwise.
+    bool validateHeader(const CSVRow& header);
+
+    /// @brief Sanity check if stream is open.
+    ///
+    /// Checks if the file stream is open so as IO operations can be performed
+    /// on it. This is internally called by the public class members to prevent
+    /// them from performing IO operations on invalid stream and using NULL
+    /// pointer to a stream. The @c clear() method is called on the stream
+    /// after the status has been checked.
+    ///
+    /// @throw CSVFileError if stream is closed or pointer to it is NULL.
+    void checkStreamStatusAndReset(const std::string& operation) const;
+
+    /// @brief Returns size of the CSV file.
+    std::ifstream::pos_type size() const;
+
+    /// @brief Separator used by CSV file.
+    char primary_separator_;
+
+    /// @brief CSV file name.
+    std::string filename_;
+
+    /// @brief Holds a pointer to the file stream.
+    boost::shared_ptr<std::fstream> fs_;
+
+    /// @brief Holds CSV file columns.
+    std::vector<std::string> cols_;
+
+    /// @brief Holds last error during row reading or validation.
+    std::string read_msg_;
+};
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // CSV_FILE_H
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index d8f3d30..9e18510 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -14,6 +14,8 @@ AM_LDFLAGS = -static
 endif
 
 CLEANFILES = *.gcno *.gcda
+# CSV files are created by unit tests for CSVFile class.
+CLEANFILES += *.csv
 
 TESTS_ENVIRONMENT = \
         $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
@@ -25,6 +27,7 @@ run_unittests_SOURCES  = run_unittests.cc
 run_unittests_SOURCES += base32hex_unittest.cc
 run_unittests_SOURCES += base64_unittest.cc
 run_unittests_SOURCES += buffer_unittest.cc
+run_unittests_SOURCES += csv_file_unittest.cc
 run_unittests_SOURCES += fd_share_tests.cc
 run_unittests_SOURCES += fd_tests.cc
 run_unittests_SOURCES += filename_unittest.cc
diff --git a/src/lib/util/tests/csv_file_unittest.cc b/src/lib/util/tests/csv_file_unittest.cc
new file mode 100644
index 0000000..29c3f13
--- /dev/null
+++ b/src/lib/util/tests/csv_file_unittest.cc
@@ -0,0 +1,439 @@
+// Copyright (C) 2014 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 <util/csv_file.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+namespace {
+
+using namespace isc::util;
+
+// This test checks that the single data row is parsed.
+TEST(CSVRow, parse) {
+    CSVRow row0("foo,bar,foo-bar");
+    ASSERT_EQ(3, row0.getValuesCount());
+    EXPECT_EQ("foo", row0.readAt(0));
+    EXPECT_EQ("bar", row0.readAt(1));
+    EXPECT_EQ("foo-bar", row0.readAt(2));
+
+    row0.parse("bar,,foo-bar");
+    ASSERT_EQ(3, row0.getValuesCount());
+    EXPECT_EQ("bar", row0.readAt(0));
+    EXPECT_TRUE(row0.readAt(1).empty());
+    EXPECT_EQ("foo-bar", row0.readAt(2));
+
+    CSVRow row1("foo-bar|foo|bar|", '|');
+    ASSERT_EQ(4, row1.getValuesCount());
+    EXPECT_EQ("foo-bar", row1.readAt(0));
+    EXPECT_EQ("foo", row1.readAt(1));
+    EXPECT_EQ("bar", row1.readAt(2));
+    EXPECT_TRUE(row1.readAt(3).empty());
+
+    row1.parse("");
+    ASSERT_EQ(1, row1.getValuesCount());
+    EXPECT_TRUE(row1.readAt(0).empty());
+}
+
+// This test checks that the text representation of the CSV row
+// is created correctly.
+TEST(CSVRow, render) {
+    CSVRow row0(3);
+    row0.writeAt(0, "foo");
+    row0.writeAt(1, "foo-bar");
+    row0.writeAt(2, "bar");
+
+    std::string text;
+    ASSERT_NO_THROW(text = row0.render());
+    EXPECT_EQ(text, "foo,foo-bar,bar");
+
+    CSVRow row1(4, ';');
+    row1.writeAt(0, "foo");
+    row1.writeAt(2, "bar");
+    row1.writeAt(3, 10);
+
+    ASSERT_NO_THROW(text = row1.render());
+    EXPECT_EQ(text, "foo;;bar;10");
+
+    CSVRow row2(0);
+    ASSERT_NO_THROW(text = row2.render());
+    EXPECT_TRUE(text.empty());
+}
+
+// This test checks that the data values can be set for the CSV row.
+TEST(CSVRow, writeAt) {
+    CSVRow row(3);
+    row.writeAt(0, 10);
+    row.writeAt(1, "foo");
+    row.writeAt(2, "bar");
+
+    EXPECT_EQ("10", row.readAt(0));
+    EXPECT_EQ("foo", row.readAt(1));
+    EXPECT_EQ("bar", row.readAt(2));
+
+    EXPECT_THROW(row.writeAt(3, 20), CSVFileError);
+    EXPECT_THROW(row.writeAt(3, "foo"), CSVFileError);
+}
+
+/// @brief Test fixture class for testing operations on CSV file.
+///
+/// It implements basic operations on files, such as reading writing
+/// file removal and checking presence of the file. This is used by
+/// unit tests to verify correctness of the file created by the
+/// CSVFile class.
+class CSVFileTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets the path to the CSV file used throughout the tests.
+    /// The name of the file is test.csv and it is located in the
+    /// current build folder.
+    ///
+    /// It also deletes any dangling files after previous tests.
+    CSVFileTest();
+
+    /// @brief Destructor.
+    ///
+    /// Deletes the test CSV file if any.
+    virtual ~CSVFileTest();
+
+    /// @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 Check if test file exists on disk.
+    bool exists() const;
+
+    /// @brief Reads whole CSV file.
+    ///
+    /// @return Contents of the file.
+    std::string readFile() const;
+
+    /// @brief Removes existing file (if any).
+    void removeFile() const;
+
+    /// @brief Creates file with contents.
+    ///
+    /// @param contents Contents of the file.
+    void writeFile(const std::string& contents) const;
+
+    /// @brief Absolute path to the file used in the tests.
+    std::string testfile_;
+
+};
+
+CSVFileTest::CSVFileTest()
+    : testfile_(absolutePath("test.csv")) {
+    removeFile();
+}
+
+CSVFileTest::~CSVFileTest() {
+    removeFile();
+}
+
+std::string
+CSVFileTest::absolutePath(const std::string& filename) {
+    std::ostringstream s;
+    s << TEST_DATA_BUILDDIR << "/" << filename;
+    return (s.str());
+}
+
+bool
+CSVFileTest::exists() const {
+    std::ifstream fs(testfile_.c_str());
+    bool ok = fs.good();
+    fs.close();
+    return (ok);
+}
+
+std::string
+CSVFileTest::readFile() const {
+    std::ifstream fs(testfile_.c_str());
+    if (!fs.is_open()) {
+        return ("");
+    }
+    std::string contents((std::istreambuf_iterator<char>(fs)),
+                         std::istreambuf_iterator<char>());
+    fs.close();
+    return (contents);
+}
+
+void
+CSVFileTest::removeFile() const {
+    remove(testfile_.c_str());
+}
+
+void
+CSVFileTest::writeFile(const std::string& contents) const {
+    std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+    if (fs.is_open()) {
+        fs << contents;
+        fs.close();
+    }
+}
+
+// This test checks that the function which is used to add columns of the
+// CSV file works as expected.
+TEST_F(CSVFileTest, addColumn) {
+    boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+    // Add two columns.
+    ASSERT_NO_THROW(csv->addColumn("animal"));
+    ASSERT_NO_THROW(csv->addColumn("color"));
+    // Make sure we can't add duplicates.
+    EXPECT_THROW(csv->addColumn("animal"), CSVFileError);
+    EXPECT_THROW(csv->addColumn("color"), CSVFileError);
+    // But we should still be able to add unique columns.
+    EXPECT_NO_THROW(csv->addColumn("age"));
+    EXPECT_NO_THROW(csv->addColumn("comments"));
+    // Assert that the file is opened, because the rest of the test relies
+    // on this.
+    ASSERT_NO_THROW(csv->recreate());
+    ASSERT_TRUE(exists());
+
+    // Make sure we can't add columns (even unique) when the file is open.
+    ASSERT_THROW(csv->addColumn("zoo"), CSVFileError);
+    // Close the file.
+    ASSERT_NO_THROW(csv->close());
+    // And check that now it is possible to add the column.
+    EXPECT_NO_THROW(csv->addColumn("zoo"));
+}
+
+// This test checks that the appropriate file name is initialized.
+TEST_F(CSVFileTest, getFilename) {
+    CSVFile csv(testfile_);
+    EXPECT_EQ(testfile_, csv.getFilename());
+}
+
+// This test checks that the file can be opened,  its whole content is
+// parsed correctly and data may be appended. It also checks that empty
+// row is returned when EOF is reached.
+TEST_F(CSVFileTest, openReadAllWrite) {
+    // 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");
+
+    // Open this file and check that the header is parsed.
+    boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+    ASSERT_NO_THROW(csv->open());
+    ASSERT_EQ(3, csv->getColumnCount());
+    EXPECT_EQ("animal", csv->getColumnName(0));
+    EXPECT_EQ("age", csv->getColumnName(1));
+    EXPECT_EQ("color", csv->getColumnName(2));
+
+    // Read first row.
+    CSVRow row;
+    ASSERT_TRUE(csv->next(row));
+    ASSERT_EQ(3, row.getValuesCount());
+    EXPECT_EQ("cat", row.readAt(0));
+    EXPECT_EQ("10", row.readAt(1));
+    EXPECT_EQ("white", row.readAt(2));
+
+    // Read second row.
+    ASSERT_TRUE(csv->next(row));
+    ASSERT_EQ(3, row.getValuesCount());
+    EXPECT_EQ("lion", row.readAt(0));
+    EXPECT_EQ("15", row.readAt(1));
+    EXPECT_EQ("yellow", row.readAt(2));
+
+    // There is no 3rd row, so the empty one should be returned.
+    ASSERT_TRUE(csv->next(row));
+    EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+    // It should be fine to read again, but again empty row should be returned.
+    ASSERT_TRUE(csv->next(row));
+    EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+    // Now, let's try to append something to this file.
+    CSVRow row_write(3);
+    row_write.writeAt(0, "dog");
+    row_write.writeAt(1, 2);
+    row_write.writeAt(2, "blue");
+    ASSERT_NO_THROW(csv->append(row_write));
+
+    // Close the file.
+    ASSERT_NO_THROW(csv->flush());
+    csv->close();
+
+    // Check the the file contents are correct.
+    EXPECT_EQ("animal,age,color\n"
+              "cat,10,white\n"
+              "lion,15,yellow\n"
+              "dog,2,blue\n",
+              readFile());
+
+    // Any attempt to read from the file or write to it should now fail.
+    EXPECT_FALSE(csv->next(row));
+    EXPECT_THROW(csv->append(row_write), CSVFileError);
+}
+
+// This test checks that contents may be appended to a file which hasn't
+// been fully parsed/read.
+TEST_F(CSVFileTest, openReadPartialWrite) {
+    // Create a CSV file with two rows in it.
+    writeFile("animal,age,color\n"
+              "cat,10,white\n"
+              "lion,15,yellow\n");
+
+    // Open this file.
+    boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+    ASSERT_NO_THROW(csv->open());
+
+    // Read the first row.
+    CSVRow row0(0);
+    ASSERT_NO_THROW(csv->next(row0));
+    ASSERT_EQ(3, row0.getValuesCount());
+    EXPECT_EQ("cat", row0.readAt(0));
+    EXPECT_EQ("10", row0.readAt(1));
+    EXPECT_EQ("white", row0.readAt(2));
+
+    // There is still second row to be read. But, it should be possible to
+    // skip reading it and append new row to the end of file.
+    CSVRow row_write(3);
+    row_write.writeAt(0, "dog");
+    row_write.writeAt(1, 2);
+    row_write.writeAt(2, "blue");
+    ASSERT_NO_THROW(csv->append(row_write));
+
+    // At this point, the file pointer is at the end of file, so reading
+    // should return empty row.
+    CSVRow row1(0);
+    ASSERT_NO_THROW(csv->next(row1));
+    EXPECT_EQ(CSVFile::EMPTY_ROW(), row1);
+
+    // Close the file.
+    ASSERT_NO_THROW(csv->flush());
+    csv->close();
+
+    // Check that there are two initial lines and one new there.
+    EXPECT_EQ("animal,age,color\n"
+              "cat,10,white\n"
+              "lion,15,yellow\n"
+              "dog,2,blue\n",
+              readFile());
+
+}
+
+// This test checks that the new CSV file is created and header
+// is written to it. It also checks that data rows can be
+// appended to it.
+TEST_F(CSVFileTest, recreate) {
+    boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+    csv->addColumn("animal");
+    csv->addColumn("color");
+    csv->addColumn("age");
+    csv->addColumn("comments");
+    ASSERT_NO_THROW(csv->recreate());
+    ASSERT_TRUE(exists());
+
+    CSVRow row0(4);
+    row0.writeAt(0, "dog");
+    row0.writeAt(1, "grey");
+    row0.writeAt(2, 3);
+    row0.writeAt(3, "nice one");
+    ASSERT_NO_THROW(csv->append(row0));
+
+    CSVRow row1(4);
+    row1.writeAt(0, "cat");
+    row1.writeAt(1, "black");
+    row1.writeAt(2, 2);
+    ASSERT_NO_THROW(csv->append(row1));
+
+    ASSERT_NO_THROW(csv->flush());
+    csv->close();
+
+    EXPECT_EQ("animal,color,age,comments\n"
+              "dog,grey,3,nice one\n"
+              "cat,black,2,\n",
+              readFile());
+}
+
+// This test checks that the error is reported when the size of the row being
+// read doesn't match the number of columns of the CSV file.
+TEST_F(CSVFileTest, validate) {
+    // Create CSV file with 2 invalid rows in it: one too long, one too short.
+    // Apart from that, there are two valid columns that should be read
+    // successfuly.
+    writeFile("animal,age,color\n"
+              "cat,10,white\n"
+              "lion,15,yellow,black\n"
+              "dog,3,green\n"
+              "elephant,11\n");
+
+    boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+    ASSERT_NO_THROW(csv->open());
+    // First row is correct.
+    CSVRow row0;
+    ASSERT_TRUE(csv->next(row0));
+    EXPECT_EQ("cat", row0.readAt(0));
+    EXPECT_EQ("10", row0.readAt(1));
+    EXPECT_EQ("white", row0.readAt(2));
+    EXPECT_EQ("success", csv->getReadMsg());
+    // This row is too long.
+    CSVRow row1;
+    EXPECT_FALSE(csv->next(row1));
+    EXPECT_NE("success", csv->getReadMsg());
+    // This row is correct.
+    CSVRow row2;
+    ASSERT_TRUE(csv->next(row2));
+    EXPECT_EQ("dog", row2.readAt(0));
+    EXPECT_EQ("3", row2.readAt(1));
+    EXPECT_EQ("green", row2.readAt(2));
+    EXPECT_EQ("success", csv->getReadMsg());
+    // This row is too short.
+    CSVRow row3;
+    EXPECT_FALSE(csv->next(row3));
+    EXPECT_NE("success", csv->getReadMsg());
+}
+
+// Test test checks that exception is thrown when the header of the CSV file
+// parsed, doesn't match the columns specified.
+TEST_F(CSVFileTest, validateHeader) {
+    // Create CSV file with 3 columns.
+    writeFile("animal,age,color\n"
+              "cat,10,white\n"
+              "lion,15,yellow,black\n");
+
+    // Invalid order of columns.
+    boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+    csv->addColumn("color");
+    csv->addColumn("animal");
+    csv->addColumn("age");
+    EXPECT_THROW(csv->open(), CSVFileError);
+
+    // Too many columns.
+    csv.reset(new CSVFile(testfile_));
+    csv->addColumn("animal");
+    csv->addColumn("age");
+    csv->addColumn("color");
+    csv->addColumn("notes");
+    EXPECT_THROW(csv->open(), CSVFileError);
+
+    // Too few columns.
+    csv.reset(new CSVFile(testfile_));
+    csv->addColumn("animal");
+    csv->addColumn("age");
+    EXPECT_THROW(csv->open(), CSVFileError);
+}
+
+
+} // end of anonymous namespace



More information about the bind10-changes mailing list