Parcourir la source

[master] Merge branch 'trac3965'

Marcin Siodelski il y a 9 ans
Parent
commit
dd5b954535

+ 21 - 21
src/bin/lfc/tests/lfc_controller_unittests.cc

@@ -90,11 +90,11 @@ protected:
         cstr_ = base_dir + "/" + "config_file";     // config
 
         v4_hdr_ = "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n";
+                  "fqdn_fwd,fqdn_rev,hostname,state\n";
 
         v6_hdr_ = "address,duid,valid_lifetime,expire,subnet_id,"
                   "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-                  "fqdn_rev,hostname,hwaddr\n";
+                  "fqdn_rev,hostname,hwaddr,state\n";
 
         // and remove any outstanding test files
         removeTestFile();
@@ -402,26 +402,26 @@ TEST_F(LFCControllerTest, launch4) {
     // We have several entries for different leases, the naming is:
     // <lease letter>_<version#>
     string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                 "200,200,8,1,1,host.example.com\n";
+                 "200,200,8,1,1,host.example.com,1\n";
     string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                 "200,500,8,1,1,host.example.com\n";
+                 "200,500,8,1,1,host.example.com,1\n";
     string a_3 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                 "200,800,8,1,1,host.example.com\n";
+                 "200,800,8,1,1,host.example.com,1\n";
 
     string b_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
-                 "100,100,7,0,0,\n";
+                 "100,100,7,0,0,,1\n";
     string b_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
-                 "100,135,7,0,0,\n";
+                 "100,135,7,0,0,,1\n";
     string b_3 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
-                 "100,150,7,0,0,\n";
+                 "100,150,7,0,0,,1\n";
 
     string c_1 = "192.0.2.3,,a:11:01:04,"
-                 "200,200,8,1,1,host.example.com\n";
+                 "200,200,8,1,1,host.example.com,1\n";
 
     string d_1 = "192.0.2.5,16:17:18:19:1a:bc,,"
-                 "200,200,8,1,1,host.example.com\n";
+                 "200,200,8,1,1,host.example.com,1\n";
     string d_2 = "192.0.2.5,16:17:18:19:1a:bc,,"
-                 "0,200,8,1,1,host.example.com\n";
+                 "0,200,8,1,1,host.example.com,1\n";
 
     // Subtest 1: both previous and copy available.
     // Create the test previous file
@@ -556,27 +556,27 @@ TEST_F(LFCControllerTest, launch6) {
     // We have several entries for different leases, the naming is:
     // <lease letter>_<version#>.
     string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                 "200,200,8,100,0,7,0,1,1,host.example.com,\n";
-    string a_2 = "2001:db8:1::1,,200,200,8,100,0,7,0,1,1,host.example.com,\n";
+                 "200,200,8,100,0,7,0,1,1,host.example.com,,1\n";
+    string a_2 = "2001:db8:1::1,,200,200,8,100,0,7,0,1,1,host.example.com,,1\n";
     string a_3 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                 "200,400,8,100,0,7,0,1,1,host.example.com,\n";
+                 "200,400,8,100,0,7,0,1,1,host.example.com,,1\n";
     string a_4 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                 "0,200,8,100,0,7,0,1,1,host.example.com,\n";
+                 "0,200,8,100,0,7,0,1,1,host.example.com,,1\n";
 
     string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
-                 "300,300,6,150,0,8,0,0,0,,\n";
+                 "300,300,6,150,0,8,0,0,0,,,1\n";
     string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
-                 "300,800,6,150,0,8,0,0,0,,\n";
+                 "300,800,6,150,0,8,0,0,0,,,1\n";
     string b_3 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
-                 "300,1000,6,150,0,8,0,0,0,,\n";
+                 "300,1000,6,150,0,8,0,0,0,,,1\n";
 
     string c_1 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                 "100,200,8,0,2,16,64,0,0,,\n";
+                 "100,200,8,0,2,16,64,0,0,,,1\n";
     string c_2 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                 "100,400,8,0,2,16,64,0,0,,\n";
+                 "100,400,8,0,2,16,64,0,0,,,1\n";
 
     string d_1 = "2001:db8:1::3,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                 "200,600,8,100,0,7,0,1,1,host.example.com,\n";
+                 "200,600,8,100,0,7,0,1,1,host.example.com,,1\n";
 
     // Subtest 1: bot previous and copy available
     // Create the test previous file

+ 0 - 2
src/lib/dhcpsrv/alloc_engine.cc

@@ -931,7 +931,6 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
     expired->t2_ = ctx.subnet_->getT2();
     expired->cltt_ = time(NULL);
     expired->subnet_id_ = ctx.subnet_->getID();
-    expired->fixed_ = false;
     expired->hostname_ = ctx.hostname_;
     expired->fqdn_fwd_ = ctx.fwd_dns_update_;
     expired->fqdn_rev_ = ctx.rev_dns_update_;
@@ -1912,7 +1911,6 @@ AllocEngine::reuseExpiredLease4(Lease4Ptr& expired,
     }
 
     updateLease4Information(expired, ctx);
-    expired->fixed_ = false;
 
     LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA,
               ALLOC_ENGINE_V4_REUSE_EXPIRED_LEASE_DATA)

+ 9 - 0
src/lib/dhcpsrv/csv_lease_file4.cc

@@ -59,6 +59,7 @@ CSVLeaseFile4::append(const Lease4& lease) {
     row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
     row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
     row.writeAt(getColumnIndex("hostname"), lease.hostname_);
+    row.writeAt(getColumnIndex("state"), lease.state_);
 
     try {
         CSVFile::append(row);
@@ -115,6 +116,7 @@ CSVLeaseFile4::next(Lease4Ptr& lease) {
                                readFqdnFwd(row),
                                readFqdnRev(row),
                                readHostname(row)));
+        lease->state_ = readState(row);
 
     } catch (std::exception& ex) {
         // bump the read error count
@@ -144,6 +146,7 @@ CSVLeaseFile4::initColumns() {
     addColumn("fqdn_fwd");
     addColumn("fqdn_rev");
     addColumn("hostname");
+    addColumn("state");
 }
 
 IOAddress
@@ -212,5 +215,11 @@ CSVLeaseFile4::readHostname(const CSVRow& row) {
     return (hostname);
 }
 
+uint32_t
+CSVLeaseFile4::readState(const util::CSVRow& row) {
+    uint32_t state = row.readAndConvertAt<uint32_t>(getColumnIndex("state"));
+    return (state);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 15 - 9
src/lib/dhcpsrv/csv_lease_file4.h

@@ -102,6 +102,7 @@ private:
     /// - fqdn_fwd
     /// - fqdn_rev
     /// - hostname
+    /// - state
     void initColumns();
 
     ///
@@ -111,48 +112,53 @@ private:
     ///
     /// @brief Reads lease address from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     asiolink::IOAddress readAddress(const util::CSVRow& row);
 
     /// @brief Reads HW address from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     HWAddr readHWAddr(const util::CSVRow& row);
 
     /// @brief Reads client identifier from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     ClientIdPtr readClientId(const util::CSVRow& row);
 
     /// @brief Reads valid lifetime from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     uint32_t readValid(const util::CSVRow& row);
 
     /// @brief Reads cltt value from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     time_t readCltt(const util::CSVRow& row);
 
     /// @brief Reads subnet id from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     SubnetID readSubnetID(const util::CSVRow& row);
 
     /// @brief Reads the FQDN forward flag from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     bool readFqdnFwd(const util::CSVRow& row);
 
     /// @brief Reads the FQDN reverse flag from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     bool readFqdnRev(const util::CSVRow& row);
 
     /// @brief Reads hostname from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     std::string readHostname(const util::CSVRow& row);
+
+    /// @brief Reads lease state from the CSV file row.
+    ///
+    /// @param row CSV file row holding lease information.
+    uint32_t readState(const util::CSVRow& row);
     //@}
 
 };

+ 8 - 19
src/lib/dhcpsrv/csv_lease_file6.cc

@@ -59,6 +59,7 @@ CSVLeaseFile6::append(const Lease6& lease) {
         // We may not have hardware information
         row.writeAt(getColumnIndex("hwaddr"), lease.hwaddr_->toText(false));
     }
+    row.writeAt(getColumnIndex("state"), lease.state_);
     try {
         CSVFile::append(row);
     } catch (const std::exception&) {
@@ -100,6 +101,7 @@ CSVLeaseFile6::next(Lease6Ptr& lease) {
         lease->fqdn_fwd_ = readFqdnFwd(row);
         lease->fqdn_rev_ = readFqdnRev(row);
         lease->hostname_ = readHostname(row);
+        lease->state_ = readState(row);
 
     } catch (std::exception& ex) {
         // bump the read error count
@@ -133,6 +135,7 @@ CSVLeaseFile6::initColumns() {
     addColumn("fqdn_rev");
     addColumn("hostname");
     addColumn("hwaddr");
+    addColumn("state");
 }
 
 Lease::Type
@@ -226,10 +229,7 @@ CSVLeaseFile6::readHWAddr(const CSVRow& row) {
         // Let's return a pointer to new freshly created copy.
         return (HWAddrPtr(new HWAddr(hwaddr)));
 
-    } catch (const CSVFileError&) {
-        // That's ok, we may be reading old CSV file that didn't store hwaddr
-        return (HWAddrPtr());
-    } catch (const BadValue& ex) {
+    } catch (const std::exception& ex) {
         // That's worse. There was something in the file, but its conversion
         // to HWAddr failed. Let's log it on warning and carry on.
         LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_READ_HWADDR_FAIL)
@@ -239,21 +239,10 @@ CSVLeaseFile6::readHWAddr(const CSVRow& row) {
     }
 }
 
-bool
-CSVLeaseFile6::validateHeader(const isc::util::CSVRow& header) {
-
-    if (!CSVFile::validateHeader(header)) {
-
-        // One possible validation failure is that we're reading Kea 0.9
-        // lease file that didn't have hwaddr column. Let's add it and
-        // try to revalidate.
-        isc::util::CSVRow copy = header;
-        copy.append("hwaddr");
-        return CSVFile::validateHeader(copy);
-    } else {
-        return (true);
-    }
-
+uint32_t
+CSVLeaseFile6::readState(const util::CSVRow& row) {
+    uint32_t state = row.readAndConvertAt<uint32_t>(getColumnIndex("state"));
+    return (state);
 }
 
 } // end of namespace isc::dhcp

+ 19 - 25
src/lib/dhcpsrv/csv_lease_file6.h

@@ -87,18 +87,6 @@ public:
     /// ticket http://kea.isc.org/ticket/2405 is implemented.
     bool next(Lease6Ptr& lease);
 
-protected:
-    /// @brief This function validates the header of the Lease6 CSV file.
-    ///
-    /// It works similar to @c CSVFile::validateHeader, but if the validation
-    /// fails, it attempts to add hwaddr column and retry validation.
-    /// That's useful when attmepting to read CSV file generated in 0.9
-    /// (did not have hwaddr field) in 0.9.1 or later code.
-    ///
-    /// @param header A row holding a header.
-    /// @return true if header matches the columns; false otherwise.
-    virtual bool validateHeader(const isc::util::CSVRow& header);
-
 private:
 
     /// @brief Initializes columns of the CSV file holding leases.
@@ -117,6 +105,7 @@ private:
     /// - fqdn_rev
     /// - hostname
     /// - hwaddr
+    /// - state
     void initColumns();
 
     ///
@@ -126,69 +115,74 @@ private:
     ///
     /// @brief Reads lease type from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     Lease::Type readType(const util::CSVRow& row);
 
     /// @brief Reads lease address from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     asiolink::IOAddress readAddress(const util::CSVRow& row);
 
     /// @brief Reads DUID from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     DuidPtr readDUID(const util::CSVRow& row);
 
     /// @brief Reads IAID from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     uint32_t readIAID(const util::CSVRow& row);
 
     /// @brief Reads preferred lifetime from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     uint32_t readPreferred(const util::CSVRow& row);
 
     /// @brief Reads valid lifetime from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     uint32_t readValid(const util::CSVRow& row);
 
     /// @brief Reads cltt value from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     uint32_t readCltt(const util::CSVRow& row);
 
     /// @brief Reads subnet id from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     SubnetID readSubnetID(const util::CSVRow& row);
 
     /// @brief Reads prefix length from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     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.
+    /// @param row CSV file row holding lease information.
     bool readFqdnFwd(const util::CSVRow& row);
 
     /// @brief Reads the FQDN reverse flag from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     bool readFqdnRev(const util::CSVRow& row);
 
     /// @brief Reads hostname from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     std::string readHostname(const util::CSVRow& row);
 
     /// @brief Reads HW address from the CSV file row.
     ///
-    /// @param row CSV file holding lease values.
+    /// @param row CSV file row holding lease information.
     /// @return pointer to the HWAddr structure that was read
     HWAddrPtr readHWAddr(const util::CSVRow& row);
+
+    /// @brief Reads lease state from the CSV file row.
+    ///
+    /// @param row CSV file row holding lease information.
+    uint32_t readState(const util::CSVRow& row);
     //@}
 
 };

+ 17 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -231,6 +231,23 @@ A debug message issued when the server is attempting to delete a lease
 for the specified address from the memory file database for the specified
 address.
 
+% DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4 deleting reclaimed leases expired more than %1 s ago
+A debug message issued when the server is removing reclaimed DHCPv4
+leases which have expired longer than a specified period of time.
+The argument specified the number of seconds since leases' expiration
+before they can be removed.
+
+% DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6 deleting reclaimed leases expired more than %1 s ago
+A debug message issued when the server is removing reclaimed DHCPv6
+leases which have expired longer than a specified period of time.
+The argument specified the number of seconds since leases' expiration
+before they can be removed.
+
+% DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START starting deletion of %1 expired-reclaimed leases
+A debug message issued wheb the server has found expired-reclaimed
+leases to be removed. The number of leases to be removed is logged
+in the message.
+
 % DHCPSRV_MEMFILE_GET_ADDR4 obtaining IPv4 lease for address %1
 A debug message issued when the server is attempting to obtain an IPv4
 lease from the memory file database for the specified address.

+ 57 - 22
src/lib/dhcpsrv/lease.cc

@@ -15,6 +15,7 @@
 #include <dhcpsrv/lease.h>
 #include <util/pointer_util.h>
 #include <sstream>
+#include <iostream>
 
 using namespace isc::util;
 using namespace std;
@@ -22,13 +23,17 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
+const uint32_t Lease::STATE_DEFAULT = 0x0;
+const uint32_t Lease::STATE_DECLINED = 0x1;
+const uint32_t Lease::STATE_EXPIRED_RECLAIMED = 0x2;
+
 Lease::Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
              uint32_t valid_lft, SubnetID subnet_id, time_t cltt,
              const bool fqdn_fwd, const bool fqdn_rev,
              const std::string& hostname, const HWAddrPtr& hwaddr)
     :addr_(addr), t1_(t1), t2_(t2), valid_lft_(valid_lft), cltt_(cltt),
-     subnet_id_(subnet_id), fixed_(false), hostname_(hostname),
-     fqdn_fwd_(fqdn_fwd), fqdn_rev_(fqdn_rev), hwaddr_(hwaddr) {
+     subnet_id_(subnet_id), hostname_(hostname), fqdn_fwd_(fqdn_fwd),
+    fqdn_rev_(fqdn_rev), hwaddr_(hwaddr), state_(STATE_DEFAULT) {
 }
 
 
@@ -52,11 +57,37 @@ Lease::typeToText(Lease::Type type) {
    }
 }
 
-bool Lease::expired() const {
+std::string
+Lease::basicStatesToText(const uint32_t state) {
+    switch (state) {
+    case STATE_DEFAULT:
+        return ("default");
+    case STATE_DECLINED:
+        return ("declined");
+    case STATE_EXPIRED_RECLAIMED:
+        return ("expired-reclaimed");
+    default:
+        // The default case will be handled further on
+        ;
+    }
+    std::ostringstream s;
+    s << "unknown (" << state << ")";
+    return s.str();
+}
+
+bool
+Lease::expired() const {
+    return (getExpirationTime() < time(NULL));
+}
+
+bool
+Lease::stateExpiredReclaimed() const {
+    return (state_ == STATE_EXPIRED_RECLAIMED);
+}
 
-    // Let's use int64 to avoid problems with negative/large uint32 values
-    int64_t expire_time = cltt_ + valid_lft_;
-    return (expire_time < time(NULL));
+int64_t
+Lease::getExpirationTime() const {
+    return (static_cast<int64_t>(cltt_) + valid_lft_);
 }
 
 bool
@@ -69,11 +100,10 @@ Lease::hasIdenticalFqdn(const Lease& other) const {
 Lease4::Lease4(const Lease4& other)
     : Lease(other.addr_, other.t1_, other.t2_, other.valid_lft_,
             other.subnet_id_, other.cltt_, other.fqdn_fwd_,
-            other.fqdn_rev_, other.hostname_, other.hwaddr_),
-            ext_(other.ext_) {
+            other.fqdn_rev_, other.hostname_, other.hwaddr_) {
 
-    fixed_ = other.fixed_;
-    comments_ = other.comments_;
+    // Copy over fields derived from Lease.
+    state_ = other.state_;
 
     // Copy the hardware address if it is defined.
     if (other.hwaddr_) {
@@ -105,10 +135,13 @@ Lease4::Lease4(const isc::asiolink::IOAddress& address,
 
     : Lease(address, t1, t2, valid_lifetime, subnet_id, cltt, fqdn_fwd,
             fqdn_rev, hostname, hw_address),
-      ext_(0), client_id_(client_id) {
+      client_id_(client_id) {
 }
 
-
+std::string
+Lease4::statesToText(const uint32_t state) {
+    return (Lease::basicStatesToText(state));
+}
 
 const std::vector<uint8_t>&
 Lease4::getClientIdVector() const {
@@ -155,12 +188,10 @@ Lease4::operator=(const Lease4& other) {
         valid_lft_ = other.valid_lft_;
         cltt_ = other.cltt_;
         subnet_id_ = other.subnet_id_;
-        fixed_ = other.fixed_;
         hostname_ = other.hostname_;
         fqdn_fwd_ = other.fqdn_fwd_;
         fqdn_rev_ = other.fqdn_rev_;
-        comments_ = other.comments_;
-        ext_ = other.ext_;
+        state_ = other.state_;
 
         // Copy the hardware address if it is defined.
         if (other.hwaddr_) {
@@ -215,6 +246,11 @@ Lease6::Lease6()
             duid_(DuidPtr()), preferred_lft_(0) {
 }
 
+std::string
+Lease6::statesToText(const uint32_t state) {
+    return (Lease::basicStatesToText(state));
+}
+
 const std::vector<uint8_t>&
 Lease6::getDuidVector() const {
     if (!duid_) {
@@ -239,7 +275,8 @@ Lease6::toText() const {
            << "Valid life:    " << valid_lft_ << "\n"
            << "Cltt:          " << cltt_ << "\n"
            << "Hardware addr: " << (hwaddr_?hwaddr_->toText(false):"(none)") << "\n"
-           << "Subnet ID:     " << subnet_id_ << "\n";
+           << "Subnet ID:     " << subnet_id_ << "\n"
+           << "State:         " << statesToText(state_) << "\n";
 
     return (stream.str());
 }
@@ -255,7 +292,8 @@ Lease4::toText() const {
            << "Cltt:          " << cltt_ << "\n"
            << "Hardware addr: " << (hwaddr_ ? hwaddr_->toText(false) : "(none)") << "\n"
            << "Client id:     " << (client_id_ ? client_id_->toText() : "(none)") << "\n"
-           << "Subnet ID:     " << subnet_id_ << "\n";
+           << "Subnet ID:     " << subnet_id_ << "\n"
+           << "State:         " << statesToText(state_) << "\n";
 
     return (stream.str());
 }
@@ -266,17 +304,15 @@ Lease4::operator==(const Lease4& other) const {
     return (nullOrEqualValues(hwaddr_, other.hwaddr_) &&
             nullOrEqualValues(client_id_, other.client_id_) &&
             addr_ == other.addr_ &&
-            ext_ == other.ext_ &&
             subnet_id_ == other.subnet_id_ &&
             t1_ == other.t1_ &&
             t2_ == other.t2_ &&
             valid_lft_ == other.valid_lft_ &&
             cltt_ == other.cltt_ &&
-            fixed_ == other.fixed_ &&
             hostname_ == other.hostname_ &&
             fqdn_fwd_ == other.fqdn_fwd_ &&
             fqdn_rev_ == other.fqdn_rev_ &&
-            comments_ == other.comments_);
+            state_ == other.state_);
 }
 
 bool
@@ -293,11 +329,10 @@ Lease6::operator==(const Lease6& other) const {
             t2_ == other.t2_ &&
             cltt_ == other.cltt_ &&
             subnet_id_ == other.subnet_id_ &&
-            fixed_ == other.fixed_ &&
             hostname_ == other.hostname_ &&
             fqdn_fwd_ == other.fqdn_fwd_ &&
             fqdn_rev_ == other.fqdn_rev_ &&
-            comments_ == other.comments_);
+            state_ == other.state_);
 }
 
 std::ostream&

+ 66 - 21
src/lib/dhcpsrv/lease.h

@@ -48,6 +48,29 @@ struct Lease {
     /// @return text decription
     static std::string typeToText(Type type);
 
+    /// @name Common lease states constants.
+    //@{
+    ///
+    /// @brief A lease in the default state.
+    static const uint32_t STATE_DEFAULT;
+
+    /// @brief Declined lease.
+    static const uint32_t STATE_DECLINED;
+
+    /// @brief Expired and reclaimed lease.
+    static const uint32_t STATE_EXPIRED_RECLAIMED;
+
+    //@}
+
+    /// @brief Returns name(s) of the basic lease state(s).
+    ///
+    /// @param state A numeric value holding a state information.
+    /// Some states may be composite, i.e. the single state value
+    /// maps to multiple logical states of the lease.
+    ///
+    /// @return Comma separated list of state names.
+    static std::string basicStatesToText(const uint32_t state);
+
     /// @brief Constructor
     ///
     /// @param addr IP address
@@ -108,11 +131,6 @@ struct Lease {
     /// Specifies the identification of the subnet to which the lease belongs.
     SubnetID subnet_id_;
 
-    /// @brief Fixed lease?
-    ///
-    /// Fixed leases are kept after they are released/expired.
-    bool fixed_;
-
     /// @brief Client hostname
     ///
     /// This field may be empty
@@ -133,11 +151,16 @@ struct Lease {
     /// This information may not be available in certain cases.
     HWAddrPtr hwaddr_;
 
-    /// @brief Lease comments
+    /// @brief Holds the lease state(s).
+    ///
+    /// This is the field that holds the lease state(s). Typically, a
+    /// lease remains in a single states. However, it is posible to
+    /// define a value for state which indicates that the lease remains
+    /// in multiple logical states.
     ///
-    /// Currently not used. It may be used for keeping comments made by the
-    /// system administrator.
-    std::string comments_;
+    /// The defined states are represented by the "STATE_*" constants
+    /// belonging to this class.
+    uint32_t state_;
 
     /// @brief Convert Lease to Printable Form
     ///
@@ -148,6 +171,12 @@ struct Lease {
     /// @return true if the lease is expired
     bool expired() const;
 
+    /// @brief Indicates if the lease is in the "expired-reclaimed" state.
+    ///
+    /// @return true if the lease is in the "expired-reclaimed" state, false
+    /// otherwise.
+    bool stateExpiredReclaimed() const;
+
     /// @brief Returns true if the other lease has equal FQDN data.
     ///
     /// @param other Lease which FQDN data is to be compared with our lease.
@@ -165,6 +194,12 @@ struct Lease {
     ///
     /// @return const reference to the hardware address
     const std::vector<uint8_t>& getHWAddrVector() const;
+
+    /// @brief Returns lease expiration time.
+    ///
+    /// The lease expiration time is a sum of a client last transmission time
+    /// and valid lifetime.
+    int64_t getExpirationTime() const;
 };
 
 /// @brief Structure that holds a lease for IPv4 address
@@ -175,16 +210,6 @@ struct Lease {
 /// extensively, direct access is warranted.
 struct Lease4 : public Lease {
 
-    /// @brief Address extension
-    ///
-    /// It is envisaged that in some cases IPv4 address will be accompanied
-    /// with some additional data. One example of such use are Address + Port
-    /// solutions (or Port-restricted Addresses), where several clients may get
-    /// the same address, but different port ranges. This feature is not
-    /// expected to be widely used.  Under normal circumstances, the value
-    /// should be 0.
-    uint32_t ext_;
-
     /// @brief Client identifier
     ///
     /// @todo Should this be a pointer to a client ID or the ID itself?
@@ -211,7 +236,7 @@ struct Lease4 : public Lease {
            const bool fqdn_fwd = false, const bool fqdn_rev = false,
            const std::string& hostname = "")
         : Lease(addr, t1, t2, valid_lft, subnet_id, cltt, fqdn_fwd, fqdn_rev,
-                hostname, hwaddr), ext_(0) {
+                hostname, hwaddr) {
         if (clientid_len) {
             client_id_.reset(new ClientId(clientid, clientid_len));
         }
@@ -246,7 +271,7 @@ struct Lease4 : public Lease {
     /// @brief Default constructor
     ///
     /// Initialize fields that don't have a default constructor.
-    Lease4() : Lease(0, 0, 0, 0, 0, 0, false, false, "", HWAddrPtr()), ext_(0)
+    Lease4() : Lease(0, 0, 0, 0, 0, 0, false, false, "", HWAddrPtr())
     {
     }
 
@@ -255,6 +280,16 @@ struct Lease4 : public Lease {
     /// @param other the @c Lease4 object to be copied.
     Lease4(const Lease4& other);
 
+    /// @brief Returns name of the lease states specific to DHCPv4.
+    ///
+    /// @todo Currently it simply returns common states for DHCPv4 and DHCPv6.
+    /// This method will have to be extended to handle DHCPv4 specific states
+    /// when they are defined.
+    ///
+    /// @param state Numeric value holding lease states.
+    /// @return Comma separated list of lease state names.
+    static std::string statesToText(const uint32_t state);
+
     /// @brief Returns a client identifier.
     ///
     /// @warning Since the function returns the reference to a vector (not a
@@ -440,6 +475,16 @@ struct Lease6 : public Lease {
     /// Initialize fields that don't have a default constructor.
     Lease6();
 
+    /// @brief Returns name of the lease states specific to DHCPv6.
+    ///
+    /// @todo Currently it simply returns common states for DHCPv4 and DHCPv6.
+    /// This method will have to be extended to handle DHCPv6 specific states
+    /// when they are defined.
+    ///
+    /// @param state Numeric value holding lease states.
+    /// @return Comma separated list of lease state names.
+    static std::string statesToText(const uint32_t state);
+
     /// @brief Returns a reference to a vector representing a DUID.
     ///
     /// @warning Since the function returns the reference to a vector (not a

+ 45 - 1
src/lib/dhcpsrv/lease_mgr.h

@@ -291,7 +291,6 @@ public:
     virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
                                         uint32_t iaid, SubnetID subnet_id) const = 0;
 
-
     /// @brief returns zero or one IPv6 lease for a given duid+iaid+subnet_id
     ///
     /// This function is mostly intended to be used in unit-tests during the
@@ -318,6 +317,33 @@ public:
     Lease6Ptr getLease6(Lease::Type type, const DUID& duid,
                         uint32_t iaid, SubnetID subnet_id) const;
 
+    /// @brief Returns a collection of expired DHCPv6 leases.
+    ///
+    /// This method returns at most @c max_leases expired leases. The leases
+    /// returned haven't been reclaimed, i.e. the database query must exclude
+    /// reclaimed leases from the results returned.
+    ///
+    /// @param [out] expired_leases A container to which expired leases returned
+    /// by the database backend are added.
+    /// @param max_leases A maximum number of leases to be returned. If this
+    /// value is set to 0, all expired (but not reclaimed) leases are returned.
+    virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+                                   const size_t max_leases) const = 0;
+
+
+    /// @brief Returns a collection of expired DHCPv4 leases.
+    ///
+    /// This method returns at most @c max_leases expired leases. The leases
+    /// returned haven't been reclaimed, i.e. the database query must exclude
+    /// reclaimed leases from the results returned.
+    ///
+    /// @param [out] expired_leases A container to which expired leases returned
+    /// by the database backend are added.
+    /// @param max_leases A maximum number of leases to be returned. If this
+    /// value is set to 0, all expired (but not reclaimed) leases are returned.
+    virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+                                   const size_t max_leases) const = 0;
+
     /// @brief Updates IPv4 lease.
     ///
     /// @param lease4 The lease to be updated.
@@ -338,6 +364,24 @@ public:
     /// @return true if deletion was successful, false if no such lease exists
     virtual bool deleteLease(const isc::asiolink::IOAddress& addr) = 0;
 
+    /// @brief Deletes all expired and reclaimed DHCPv4 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    ///
+    /// @return Number of leases deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs) = 0;
+
+    /// @brief Deletes all expired and reclaimed DHCPv6 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    ///
+    /// @return Number of leases deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs) = 0;
+
     /// @brief Return backend type
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

+ 174 - 49
src/lib/dhcpsrv/memfile_lease_mgr.cc

@@ -25,6 +25,7 @@
 #include <cstring>
 #include <errno.h>
 #include <iostream>
+#include <limits>
 #include <sstream>
 
 namespace {
@@ -320,10 +321,9 @@ Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
 
-    typedef Lease4Storage::nth_index<0>::type SearchIndex;
-    const SearchIndex& idx = storage4_.get<0>();
-    Lease4Storage::iterator l = idx.find(addr);
-    if (l == storage4_.end()) {
+    const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
+    Lease4StorageAddressIndex::iterator l = idx.find(addr);
+    if (l == idx.end()) {
         return (Lease4Ptr());
     } else {
         return (Lease4Ptr(new Lease4(**l)));
@@ -334,10 +334,9 @@ Lease4Collection
 Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_HWADDR).arg(hwaddr.toText());
-    typedef Lease4Storage::nth_index<0>::type SearchIndex;
     Lease4Collection collection;
-    const SearchIndex& idx = storage4_.get<0>();
-    for(SearchIndex::const_iterator lease = idx.begin();
+    const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
+    for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
         lease != idx.end(); ++lease) {
 
         // Every Lease4 has a hardware address, so we can compare it
@@ -355,14 +354,11 @@ Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
               DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
         .arg(hwaddr.toText());
 
-    // We are going to use index #1 of the multi index container.
-    // We define SearchIndex locally in this function because
-    // currently only this function uses this index.
-    typedef Lease4Storage::nth_index<1>::type SearchIndex;
-    // Get the index.
-    const SearchIndex& idx = storage4_.get<1>();
+    // Get the index by HW Address and Subnet Identifier.
+    const Lease4StorageHWAddressSubnetIdIndex& idx =
+        storage4_.get<HWAddressSubnetIdIndexTag>();
     // Try to find the lease using HWAddr and subnet id.
-    SearchIndex::const_iterator lease =
+    Lease4StorageHWAddressSubnetIdIndex::const_iterator lease =
         idx.find(boost::make_tuple(hwaddr.hwaddr_, subnet_id));
     // Lease was not found. Return empty pointer to the caller.
     if (lease == idx.end()) {
@@ -377,10 +373,9 @@ Lease4Collection
 Memfile_LeaseMgr::getLease4(const ClientId& client_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_CLIENTID).arg(client_id.toText());
-    typedef Lease4Storage::nth_index<0>::type SearchIndex;
     Lease4Collection collection;
-    const SearchIndex& idx = storage4_.get<0>();
-    for(SearchIndex::const_iterator lease = idx.begin();
+    const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
+    for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
         lease != idx.end(); ++ lease) {
 
         // client-id is not mandatory in DHCPv4. There can be a lease that does
@@ -402,14 +397,11 @@ Memfile_LeaseMgr::getLease4(const ClientId& client_id,
                                                         .arg(hwaddr.toText())
                                                         .arg(subnet_id);
 
-    // We are going to use index #3 of the multi index container.
-    // We define SearchIndex locally in this function because
-    // currently only this function uses this index.
-    typedef Lease4Storage::nth_index<3>::type SearchIndex;
-    // Get the index.
-    const SearchIndex& idx = storage4_.get<3>();
+    // Get the index by client id, HW address and subnet id.
+    const Lease4StorageClientIdHWAddressSubnetIdIndex& idx =
+        storage4_.get<ClientIdHWAddressSubnetIdIndexTag>();
     // Try to get the lease using client id, hardware address and subnet id.
-    SearchIndex::const_iterator lease =
+    Lease4StorageClientIdHWAddressSubnetIdIndex::const_iterator lease =
         idx.find(boost::make_tuple(client_id.getClientId(), hwaddr.hwaddr_,
                                    subnet_id));
 
@@ -429,14 +421,11 @@ Memfile_LeaseMgr::getLease4(const ClientId& client_id,
               DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
               .arg(client_id.toText());
 
-    // We are going to use index #2 of the multi index container.
-    // We define SearchIndex locally in this function because
-    // currently only this function uses this index.
-    typedef Lease4Storage::nth_index<2>::type SearchIndex;
-    // Get the index.
-    const SearchIndex& idx = storage4_.get<2>();
+    // Get the index by client and subnet id.
+    const Lease4StorageClientIdSubnetIdIndex& idx =
+        storage4_.get<ClientIdSubnetIdIndexTag>();
     // Try to get the lease using client id and subnet id.
-    SearchIndex::const_iterator lease =
+    Lease4StorageClientIdSubnetIdIndex::const_iterator lease =
         idx.find(boost::make_tuple(client_id.getClientId(), subnet_id));
     // Lease was not found. Return empty pointer to the caller.
     if (lease == idx.end()) {
@@ -470,15 +459,15 @@ Memfile_LeaseMgr::getLeases6(Lease::Type type,
         .arg(duid.toText())
         .arg(Lease::typeToText(type));
 
-    // We are going to use index #1 of the multi index container.
-    typedef Lease6Storage::nth_index<1>::type SearchIndex;
-    // Get the index.
-    const SearchIndex& idx = storage6_.get<1>();
+    // Get the index by DUID, IAID, lease type.
+    const Lease6StorageDuidIaidTypeIndex& idx = storage6_.get<DuidIaidTypeIndexTag>();
     // Try to get the lease using the DUID, IAID and lease type.
-    std::pair<SearchIndex::iterator, SearchIndex::iterator> l =
+    std::pair<Lease6StorageDuidIaidTypeIndex::const_iterator,
+              Lease6StorageDuidIaidTypeIndex::const_iterator> l =
         idx.equal_range(boost::make_tuple(duid.getDuid(), iaid, type));
     Lease6Collection collection;
-    for(SearchIndex::iterator lease = l.first; lease != l.second; ++lease) {
+    for(Lease6StorageDuidIaidTypeIndex::const_iterator lease =
+            l.first; lease != l.second; ++lease) {
         collection.push_back(Lease6Ptr(new Lease6(**lease)));
     }
 
@@ -496,15 +485,15 @@ Memfile_LeaseMgr::getLeases6(Lease::Type type,
         .arg(duid.toText())
         .arg(Lease::typeToText(type));
 
-    // We are going to use index #1 of the multi index container.
-    typedef Lease6Storage::nth_index<1>::type SearchIndex;
-    // Get the index.
-    const SearchIndex& idx = storage6_.get<1>();
+    // Get the index by DUID, IAID, lease type.
+    const Lease6StorageDuidIaidTypeIndex& idx = storage6_.get<DuidIaidTypeIndexTag>();
     // Try to get the lease using the DUID, IAID and lease type.
-    std::pair<SearchIndex::iterator, SearchIndex::iterator> l =
+    std::pair<Lease6StorageDuidIaidTypeIndex::const_iterator,
+              Lease6StorageDuidIaidTypeIndex::const_iterator> l =
         idx.equal_range(boost::make_tuple(duid.getDuid(), iaid, type));
     Lease6Collection collection;
-    for(SearchIndex::iterator lease = l.first; lease != l.second; ++lease) {
+    for(Lease6StorageDuidIaidTypeIndex::const_iterator lease =
+            l.first; lease != l.second; ++lease) {
         // Filter out the leases which subnet id doesn't match.
         if((*lease)->subnet_id_ == subnet_id) {
             collection.push_back(Lease6Ptr(new Lease6(**lease)));
@@ -515,11 +504,59 @@ Memfile_LeaseMgr::getLeases6(Lease::Type type,
 }
 
 void
+Memfile_LeaseMgr::getExpiredLeases6(Lease6Collection& expired_leases,
+                                    const size_t max_leases) const {
+    // Obtain the index which segragates leases by state and time.
+    const Lease6StorageExpirationIndex& index = storage6_.get<ExpirationIndexTag>();
+
+    // Retrieve leases which are not reclaimed and which haven't expired. The
+    // 'less-than' operator will be used for both components of the index. So,
+    // for the 'state' 'false' is less than 'true'. Also the leases with
+    // expiration time lower than current time will be returned.
+    Lease6StorageExpirationIndex::const_iterator ub =
+        index.upper_bound(boost::make_tuple(false, time(NULL)));
+
+    // Copy only the number of leases indicated by the max_leases parameter.
+    for (Lease6StorageExpirationIndex::const_iterator lease = index.begin();
+         (lease != ub) && ((max_leases == 0) || (std::distance(index.begin(), lease) <
+                                                 max_leases));
+         ++lease) {
+        expired_leases.push_back(Lease6Ptr(new Lease6(**lease)));
+    }
+}
+
+void
+Memfile_LeaseMgr::getExpiredLeases4(Lease4Collection& expired_leases,
+                                    const size_t max_leases) const {
+    // Obtain the index which segragates leases by state and time.
+    const Lease4StorageExpirationIndex& index = storage4_.get<ExpirationIndexTag>();
+
+    // Retrieve leases which are not reclaimed and which haven't expired. The
+    // 'less-than' operator will be used for both components of the index. So,
+    // for the 'state' 'false' is less than 'true'. Also the leases with
+    // expiration time lower than current time will be returned.
+    Lease4StorageExpirationIndex::const_iterator ub =
+        index.upper_bound(boost::make_tuple(false, time(NULL)));
+
+    // Copy only the number of leases indicated by the max_leases parameter.
+    for (Lease4StorageExpirationIndex::const_iterator lease = index.begin();
+         (lease != ub) && ((max_leases == 0) || (std::distance(index.begin(), lease) <
+                                                 max_leases));
+         ++lease) {
+        expired_leases.push_back(Lease4Ptr(new Lease4(**lease)));
+    }
+}
+
+void
 Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
 
-    Lease4Storage::iterator lease_it = storage4_.find(lease->addr_);
+    // Obtain 'by address' index.
+    Lease4StorageAddressIndex& index = storage4_.get<AddressIndexTag>();
+
+    // Lease must exist if it is to be updated.
+    Lease4StorageAddressIndex::const_iterator lease_it = index.find(lease->addr_);
     if (lease_it == storage4_.end()) {
         isc_throw(NoSuchLease, "failed to update the lease with address "
                   << lease->addr_ << " - no such lease");
@@ -532,7 +569,8 @@ Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
         lease_file4_->append(*lease);
     }
 
-    **lease_it = *lease;
+    // Use replace() to re-index leases.
+    index.replace(lease_it, Lease4Ptr(new Lease4(*lease)));
 }
 
 void
@@ -540,8 +578,12 @@ Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
 
-    Lease6Storage::iterator lease_it = storage6_.find(lease->addr_);
-    if (lease_it == storage6_.end()) {
+    // Obtain 'by address' index.
+    Lease6StorageAddressIndex& index = storage6_.get<AddressIndexTag>();
+
+    // Lease must exist if it is to be updated.
+    Lease6StorageAddressIndex::const_iterator lease_it = index.find(lease->addr_);
+    if (lease_it == index.end()) {
         isc_throw(NoSuchLease, "failed to update the lease with address "
                   << lease->addr_ << " - no such lease");
     }
@@ -553,7 +595,8 @@ Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
         lease_file6_->append(*lease);
     }
 
-    **lease_it = *lease;
+    // Use replace() to re-index leases.
+    index.replace(lease_it, Lease6Ptr(new Lease6(*lease)));
 }
 
 bool
@@ -603,6 +646,89 @@ Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
     }
 }
 
+uint64_t
+Memfile_LeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t secs) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4)
+        .arg(secs);
+    return (deleteExpiredReclaimedLeases<
+            Lease4StorageExpirationIndex, Lease4
+            >(secs, V4, storage4_, lease_file4_));
+}
+
+uint64_t
+Memfile_LeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6)
+        .arg(secs);
+    return (deleteExpiredReclaimedLeases<
+            Lease6StorageExpirationIndex, Lease6
+            >(secs, V6, storage6_, lease_file6_));
+}
+
+template<typename IndexType, typename LeaseType, typename StorageType,
+         typename LeaseFileType>
+uint64_t
+Memfile_LeaseMgr::deleteExpiredReclaimedLeases(const uint32_t secs,
+                                               const Universe& universe,
+                                               StorageType& storage,
+                                               LeaseFileType& lease_file) const {
+    // Obtain the index which segragates leases by state and time.
+    IndexType& index = storage.template get<ExpirationIndexTag>();
+
+    // This returns the first element which is greater than the specified
+    // tuple (true, time(NULL) - secs). However, the range between the
+    // beginnng of the index and returned element also includes all the
+    // elements for which the first value is false (lease state is NOT
+    // reclaimed), because false < true. All elements between the
+    // beginning of the index and the element returned, for which the
+    // first value is true, represent the reclaimed leases which should
+    // be deleted, because their expiration time + secs has occured earlier
+    // than current time.
+    typename IndexType::const_iterator upper_limit =
+        index.upper_bound(boost::make_tuple(true, time(NULL) - secs));
+
+    // Now, we have to exclude all elements of the index which represent
+    // leases in the state other than reclaimed - with the first value
+    // in the index equal to false. Note that elements in the index are
+    // ordered from the lower to the higher ones. So, all elements with
+    // the first value of false are placed before the elements with the
+    // value of true. Hence, we have to find the first element which
+    // contains value of true. The time value is the lowest possible.
+    typename IndexType::const_iterator lower_limit =
+        index.upper_bound(boost::make_tuple(true, std::numeric_limits<int64_t>::min()));
+
+    // If there are some elements in this range, delete them.
+    uint64_t num_leases = static_cast<uint64_t>(std::distance(lower_limit, upper_limit));
+    if (num_leases > 0) {
+
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+                  DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START)
+            .arg(num_leases);
+
+        // If lease persistence is enabled, we also have to mark leases
+        // as deleted in the lease file. We do this by setting the
+        // lifetime to 0.
+        if (persistLeases(universe)) {
+            for (typename IndexType::const_iterator lease = lower_limit;
+                 lease != upper_limit; ++lease) {
+                // Copy lease to not affect the lease in the container.
+                LeaseType lease_copy(**lease);
+                // Set the valid lifetime to 0 to indicate the removal
+                // of the lease.
+                lease_copy.valid_lft_ = 0;
+                lease_file->append(lease_copy);
+            }
+        }
+
+        // Erase leases from memory.
+        index.erase(lower_limit, upper_limit);
+    }
+    // Return number of leases deleted.
+    return (num_leases);
+}
+
+
 std::string
 Memfile_LeaseMgr::getDescription() const {
     return (std::string("This is a dummy memfile backend implementation.\n"
@@ -871,6 +997,5 @@ void Memfile_LeaseMgr::lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file)
     }
 }
 
-
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 82 - 0
src/lib/dhcpsrv/memfile_lease_mgr.h

@@ -259,6 +259,33 @@ public:
                                         uint32_t iaid,
                                         SubnetID subnet_id) const;
 
+    /// @brief Returns a collection of expired DHCPv6 leases.
+    ///
+    /// This method returns at most @c max_leases expired leases. The leases
+    /// returned haven't been reclaimed, i.e. the database query must exclude
+    /// reclaimed leases from the results returned.
+    ///
+    /// @param [out] expired_leases A container to which expired leases returned
+    /// by the database backend are added.
+    /// @param max_leases A maximum number of leases to be returned. If this
+    /// value is set to 0, all expired (but not reclaimed) leases are returned.
+    virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+                                   const size_t max_leases) const;
+
+
+    /// @brief Returns a collection of expired DHCPv4 leases.
+    ///
+    /// This method returns at most @c max_leases expired leases. The leases
+    /// returned haven't been reclaimed, i.e. the database query must exclude
+    /// reclaimed leases from the results returned.
+    ///
+    /// @param [out] expired_leases A container to which expired leases returned
+    /// by the database backend are added.
+    /// @param max_leases A maximum number of leases to be returned. If this
+    /// value is set to 0, all expired (but not reclaimed) leases are returned.
+    virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+                                   const size_t max_leases) const;
+
     /// @brief Updates IPv4 lease.
     ///
     /// @warning This function does not validate the pointer to the lease.
@@ -287,6 +314,61 @@ public:
     /// @return true if deletion was successful, false if no such lease exists
     virtual bool deleteLease(const isc::asiolink::IOAddress& addr);
 
+    /// @brief Deletes all expired-reclaimed DHCPv4 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    ///
+    /// @return Number of leases deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs);
+
+    /// @brief Deletes all expired-reclaimed DHCPv6 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    ///
+    /// @return Number of leases deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs);
+
+private:
+
+    /// @brief Deletes all expired-reclaimed leases.
+    ///
+    /// This private method is called by both of the public methods:
+    /// @c deleteExpiredReclaimedLeases4 and
+    /// @c deleteExpiredReclaimedLeases6 to remove all expired
+    /// reclaimed DHCPv4 or DHCPv6 leases respectively.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    /// @param universe V4 or V6.
+    /// @param storage Reference to the container where leases are held.
+    /// Some expired-reclaimed leases will be removed from this container.
+    /// @param lease_file Reference to a DHCPv4 or DHCPv6 lease file
+    /// instance where leases should be marked as deleted.
+    ///
+    /// @return Number of leases deleted.
+    ///
+    /// @tparam IndexType Index type to be used to search for the
+    /// expired-reclaimed leases, i.e.
+    /// @c Lease4StorageExpirationIndex or @c Lease6StorageExpirationIndex.
+    /// @tparam LeaseType Lease type, i.e. @c Lease4 or @c Lease6.
+    /// @tparam StorageType Type of storage where leases are held, i.e.
+    /// @c Lease4Storage or @c Lease6Storage.
+    /// @tparam LeaseFileType Type of the lease file, i.e. DHCPv4 or
+    /// DHCPv6 lease file type.
+    template<typename IndexType, typename LeaseType, typename StorageType,
+             typename LeaseFileType>
+    uint64_t deleteExpiredReclaimedLeases(const uint32_t secs,
+                                          const Universe& universe,
+                                          StorageType& storage,
+                                          LeaseFileType& lease_file) const;
+
+public:
+
     /// @brief Return backend type
     ///
     /// Returns the type of the backend.

+ 114 - 3
src/lib/dhcpsrv/memfile_lease_storage.h

@@ -12,8 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef INMEMORY_LEASE_STORAGE_H
-#define INMEMORY_LEASE_STORAGE_H
+#ifndef MEMFILE_LEASE_STORAGE_H
+#define MEMFILE_LEASE_STORAGE_H
 
 #include <asiolink/io_address.h>
 #include <dhcpsrv/lease.h>
@@ -31,11 +31,40 @@
 namespace isc {
 namespace dhcp {
 
+
+/// @brief Tag for indexes by address.
+struct AddressIndexTag { };
+
+/// @brief Tag for indexes by DUID, IAID, lease type tuple.
+struct DuidIaidTypeIndexTag { };
+
+/// @brief Tag for indexes by expiration time.
+struct ExpirationIndexTag { };
+
+/// @brief Tag for indexes by HW address, subnet identifier tuple.
+struct HWAddressSubnetIdIndexTag { };
+
+/// @brief Tag for indexes by client and subnet identifiers.
+struct ClientIdSubnetIdIndexTag { };
+
+/// @brief Tag for indexes by client id, HW address and subnet id.
+struct ClientIdHWAddressSubnetIdIndexTag { };
+
+/// @name Multi index containers holding DHCPv4 and DHCPv6 leases.
+///
+//@{
+
 /// @brief A multi index container holding DHCPv6 leases.
 ///
 /// The leases in the container may be accessed using different indexes:
 /// - using an IPv6 address,
 /// - using a composite index: DUID, IAID and lease type.
+/// - using a composite index: boolean flag indicating if the state is
+///   "expired-reclaimed" and expiration time.
+///
+/// Indexes can be accessed using the index number (from 0 to 2) or a
+/// name tag. It is recommended to use the tags to access indexes as
+/// they do not depend on the order of indexes in the container.
 typedef boost::multi_index_container<
     // It holds pointers to Lease6 objects.
     Lease6Ptr,
@@ -44,11 +73,13 @@ typedef boost::multi_index_container<
         // This index sorts leases by IPv6 addresses represented as
         // IOAddress objects.
         boost::multi_index::ordered_unique<
+            boost::multi_index::tag<AddressIndexTag>,
             boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
         >,
 
         // Specification of the second index starts here.
         boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<DuidIaidTypeIndexTag>,
             // This is a composite index that will be used to search for
             // the lease using three attributes: DUID, IAID and lease type.
             boost::multi_index::composite_key<
@@ -62,6 +93,24 @@ typedef boost::multi_index_container<
                 boost::multi_index::member<Lease6, uint32_t, &Lease6::iaid_>,
                 boost::multi_index::member<Lease6, Lease::Type, &Lease6::type_>
             >
+        >,
+
+        // Specification of the third index starts here.
+        boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<ExpirationIndexTag>,
+            // This is a composite index that will be used to search for
+            // the expired leases. Depending on the value of the first component
+            // of the search key, the reclaimed or not reclaimed leases will can
+            // be searched.
+            boost::multi_index::composite_key<
+                Lease6,
+                // The boolean value specifying if lease is reclaimed or not.
+                boost::multi_index::const_mem_fun<Lease, bool,
+                                                  &Lease::stateExpiredReclaimed>,
+                // Lease expiration time.
+                boost::multi_index::const_mem_fun<Lease, int64_t,
+                                                  &Lease::getExpirationTime>
+            >
         >
      >
 > Lease6Storage; // Specify the type name of this container.
@@ -73,6 +122,12 @@ typedef boost::multi_index_container<
 /// - composite index: HW address and subnet id,
 /// - composite index: client id and subnet id,
 /// - composite index: HW address, client id and subnet id
+/// - using a composite index: boolean flag indicating if the state is
+///   "expired-reclaimed" and expiration time.
+///
+/// Indexes can be accessed using the index number (from 0 to 4) or a
+/// name tag. It is recommended to use the tags to access indexes as
+/// they do not depend on the order of indexes in the container.
 typedef boost::multi_index_container<
     // It holds pointers to Lease4 objects.
     Lease4Ptr,
@@ -82,6 +137,7 @@ typedef boost::multi_index_container<
         // This index sorts leases by IPv4 addresses represented as
         // IOAddress objects.
         boost::multi_index::ordered_unique<
+            boost::multi_index::tag<AddressIndexTag>,
             // The IPv4 address are held in addr_ members that belong to
             // Lease class.
             boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
@@ -89,6 +145,7 @@ typedef boost::multi_index_container<
 
         // Specification of the second index starts here.
         boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<HWAddressSubnetIdIndexTag>,
             // This is a composite index that combines two attributes of the
             // Lease4 object: hardware address and subnet id.
             boost::multi_index::composite_key<
@@ -109,6 +166,7 @@ typedef boost::multi_index_container<
 
         // Specification of the third index starts here.
         boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<ClientIdSubnetIdIndexTag>,
             // This is a composite index that uses two values to search for a
             // lease: client id and subnet id.
             boost::multi_index::composite_key<
@@ -124,6 +182,7 @@ typedef boost::multi_index_container<
 
         // Specification of the fourth index starts here.
         boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<ClientIdHWAddressSubnetIdIndexTag>,
             // This is a composite index that uses three values to search for a
             // lease: client id, HW address and subnet id.
             boost::multi_index::composite_key<
@@ -141,11 +200,63 @@ typedef boost::multi_index_container<
                 // The subnet id is accessed through the subnet_id_ member.
                 boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
             >
+        >,
+
+        // Specification of the fifth index starts here.
+        boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<ExpirationIndexTag>,
+            // This is a composite index that will be used to search for
+            // the expired leases. Depending on the value of the first component
+            // of the search key, the reclaimed or not reclaimed leases will can
+            // be searched.
+            boost::multi_index::composite_key<
+                Lease4,
+                // The boolean value specifying if lease is reclaimed or not.
+                boost::multi_index::const_mem_fun<Lease, bool,
+                                                  &Lease::stateExpiredReclaimed>,
+                // Lease expiration time.
+                boost::multi_index::const_mem_fun<Lease, int64_t,
+                                                  &Lease::getExpirationTime>
+            >
         >
     >
 > Lease4Storage; // Specify the type name for this container.
 
+//@}
+
+/// @name Indexes used by the multi index containers
+///
+//@{
+
+/// @brief DHCPv6 lease storage index by address.
+typedef Lease6Storage::index<AddressIndexTag>::type Lease6StorageAddressIndex;
+
+/// @brief DHCPv6 lease storage index by DUID, IAID, lease type.
+typedef Lease6Storage::index<DuidIaidTypeIndexTag>::type Lease6StorageDuidIaidTypeIndex;
+
+/// @brief DHCPv6 lease storage index by expiration time.
+typedef Lease6Storage::index<ExpirationIndexTag>::type Lease6StorageExpirationIndex;
+
+/// @brief DHCPv4 lease storage index by address.
+typedef Lease4Storage::index<AddressIndexTag>::type Lease4StorageAddressIndex;
+
+/// @brief DHCPv4 lease storage index by exiration time.
+typedef Lease4Storage::index<ExpirationIndexTag>::type Lease4StorageExpirationIndex;
+
+/// @brief DHCPv4 lease storage index by HW address and subnet identifier.
+typedef Lease4Storage::index<HWAddressSubnetIdIndexTag>::type
+Lease4StorageHWAddressSubnetIdIndex;
+
+/// @brief DHCPv4 lease storage index by client and subnet identifier.
+typedef Lease4Storage::index<ClientIdSubnetIdIndexTag>::type
+Lease4StorageClientIdSubnetIdIndex;
+
+/// @brief DHCPv4 lease storage index by client id, HW address and subnet id.
+typedef Lease4Storage::index<ClientIdHWAddressSubnetIdIndexTag>::type
+Lease4StorageClientIdHWAddressSubnetIdIndex;
+
+//@}
 } // end of isc::dhcp namespace
 } // end of isc namespace
 
-#endif // INMEMORY_LEASE_STORAGE_H
+#endif // MEMFILE_LEASE_STORAGE_H

+ 25 - 0
src/lib/dhcpsrv/mysql_lease_mgr.cc

@@ -1932,6 +1932,18 @@ MySqlLeaseMgr::getLeases6(Lease::Type lease_type,
     return (result);
 }
 
+void
+MySqlLeaseMgr::getExpiredLeases6(Lease6Collection&, const size_t) const {
+    isc_throw(NotImplemented, "MySqlLeaseMgr::getExpiredLeases6 is currently"
+              " not implemented");
+}
+
+void
+MySqlLeaseMgr::getExpiredLeases4(Lease4Collection&, const size_t) const {
+    isc_throw(NotImplemented, "MySqlLeaseMgr::getExpiredLeases4 is currently"
+              " not implemented");
+}
+
 // Update lease methods.  These comprise common code that handles the actual
 // update, and type-specific methods that set up the parameters for the prepared
 // statement depending on the type of lease.
@@ -2074,6 +2086,19 @@ MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
     }
 }
 
+uint64_t
+MySqlLeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t) {
+    isc_throw(NotImplemented, "MySqlLeaseMgr::deleteExpiredReclaimedLeases4"
+              " is not implemented");
+}
+
+uint64_t
+MySqlLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t) {
+    isc_throw(NotImplemented, "MySqlLeaseMgr::deleteExpiredReclaimedLeases6"
+              " is not implemented");
+}
+
+
 // Miscellaneous database methods.
 
 std::string

+ 45 - 0
src/lib/dhcpsrv/mysql_lease_mgr.h

@@ -317,6 +317,33 @@ public:
     virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
                                         uint32_t iaid, SubnetID subnet_id) const;
 
+    /// @brief Returns a collection of expired DHCPv6 leases.
+    ///
+    /// This method returns at most @c max_leases expired leases. The leases
+    /// returned haven't been reclaimed, i.e. the database query must exclude
+    /// reclaimed leases from the results returned.
+    ///
+    /// @param [out] expired_leases A container to which expired leases returned
+    /// by the database backend are added.
+    /// @param max_leases A maximum number of leases to be returned. If this
+    /// value is set to 0, all expired (but not reclaimed) leases are returned.
+    virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+                                   const size_t max_leases) const;
+
+
+    /// @brief Returns a collection of expired DHCPv4 leases.
+    ///
+    /// This method returns at most @c max_leases expired leases. The leases
+    /// returned haven't been reclaimed, i.e. the database query must exclude
+    /// reclaimed leases from the results returned.
+    ///
+    /// @param [out] expired_leases A container to which expired leases returned
+    /// by the database backend are added.
+    /// @param max_leases A maximum number of leases to be returned. If this
+    /// value is set to 0, all expired (but not reclaimed) leases are returned.
+    virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+                                   const size_t max_leases) const;
+
     /// @brief Updates IPv4 lease.
     ///
     /// Updates the record of the lease in the database (as identified by the
@@ -354,6 +381,24 @@ public:
     ///        failed.
     virtual bool deleteLease(const isc::asiolink::IOAddress& addr);
 
+    /// @brief Deletes all expired-reclaimed DHCPv4 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    ///
+    /// @return Number of leases deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs);
+
+    /// @brief Deletes all expired-reclaimed DHCPv6 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    ///
+    /// @return Number of leases deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs);
+
     /// @brief Return backend type
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

+ 25 - 0
src/lib/dhcpsrv/pgsql_lease_mgr.cc

@@ -1382,6 +1382,19 @@ PgSqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid,
     return (result);
 }
 
+void
+PgSqlLeaseMgr::getExpiredLeases6(Lease6Collection&, const size_t) const {
+    isc_throw(NotImplemented, "PgSqlLeaseMgr::getExpiredLeases6 is currently"
+              " not implemented");
+}
+
+void
+PgSqlLeaseMgr::getExpiredLeases4(Lease4Collection&, const size_t) const {
+    isc_throw(NotImplemented, "PgSqlLeaseMgr::getExpiredLeases4 is currently"
+              " not implemented");
+}
+
+
 template <typename LeasePtr>
 void
 PgSqlLeaseMgr::updateLeaseCommon(StatementIndex stindex,
@@ -1494,6 +1507,18 @@ PgSqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
     return (deleteLeaseCommon(DELETE_LEASE6, bind_array));
 }
 
+uint64_t
+PgSqlLeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t) {
+    isc_throw(NotImplemented, "PgSqlLeaseMgr::deleteExpiredReclaimedLeases4"
+              " is not implemented");
+}
+
+uint64_t
+PgSqlLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t) {
+    isc_throw(NotImplemented, "PgSqlLeaseMgr::deleteExpiredReclaimedLeases6"
+              " is not implemented");
+}
+
 string
 PgSqlLeaseMgr::getName() const {
     string name = "";

+ 45 - 0
src/lib/dhcpsrv/pgsql_lease_mgr.h

@@ -322,6 +322,33 @@ public:
     virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
                                         uint32_t iaid, SubnetID subnet_id) const;
 
+    /// @brief Returns a collection of expired DHCPv6 leases.
+    ///
+    /// This method returns at most @c max_leases expired leases. The leases
+    /// returned haven't been reclaimed, i.e. the database query must exclude
+    /// reclaimed leases from the results returned.
+    ///
+    /// @param [out] expired_leases A container to which expired leases returned
+    /// by the database backend are added.
+    /// @param max_leases A maximum number of leases to be returned. If this
+    /// value is set to 0, all expired (but not reclaimed) leases are returned.
+    virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+                                   const size_t max_leases) const;
+
+
+    /// @brief Returns a collection of expired DHCPv4 leases.
+    ///
+    /// This method returns at most @c max_leases expired leases. The leases
+    /// returned haven't been reclaimed, i.e. the database query must exclude
+    /// reclaimed leases from the results returned.
+    ///
+    /// @param [out] expired_leases A container to which expired leases returned
+    /// by the database backend are added.
+    /// @param max_leases A maximum number of leases to be returned. If this
+    /// value is set to 0, all expired (but not reclaimed) leases are returned.
+    virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+                                   const size_t max_leases) const;
+
     /// @brief Updates IPv4 lease.
     ///
     /// Updates the record of the lease in the database (as identified by the
@@ -359,6 +386,24 @@ public:
     ///        failed.
     virtual bool deleteLease(const isc::asiolink::IOAddress& addr);
 
+    /// @brief Deletes all expired-reclaimed DHCPv4 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    ///
+    /// @return Number of leases deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs);
+
+    /// @brief Deletes all expired-reclaimed DHCPv6 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    ///
+    /// @return Number of leases deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs);
+
     /// @brief Return backend type
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

+ 10 - 7
src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc

@@ -111,12 +111,12 @@ CSVLeaseFile4Test::absolutePath(const std::string& filename) {
 void
 CSVLeaseFile4Test::writeSampleFile() const {
     io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
+                  "fqdn_fwd,fqdn_rev,hostname,state\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"
+                  "host.example.com,0\n"
+                  "192.0.2.1,,a:11:01:04,200,200,8,1,1,host.example.com,1\n"
                   "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,100,7,"
-                  "0,0,\n");
+                  "0,0,,1\n");
 }
 
 // This test checks the capability to read and parse leases from the file.
@@ -153,6 +153,7 @@ TEST_F(CSVLeaseFile4Test, parse) {
     EXPECT_TRUE(lease->fqdn_fwd_);
     EXPECT_TRUE(lease->fqdn_rev_);
     EXPECT_EQ("host.example.com", lease->hostname_);
+    EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
     }
 
     // Second lease is malformed - HW address is empty.
@@ -182,6 +183,7 @@ TEST_F(CSVLeaseFile4Test, parse) {
     EXPECT_FALSE(lease->fqdn_fwd_);
     EXPECT_FALSE(lease->fqdn_rev_);
     EXPECT_TRUE(lease->hostname_.empty());
+    EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
     }
 
     // There are no more leases. Reading should cause no error, but the returned
@@ -217,6 +219,7 @@ TEST_F(CSVLeaseFile4Test, recreate) {
                                NULL, 0,
                                200, 50, 80, 0, 8, true, true,
                                "host.example.com"));
+    lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
     {
     SCOPED_TRACE("First write");
     ASSERT_NO_THROW(lf->append(*lease));
@@ -238,10 +241,10 @@ TEST_F(CSVLeaseFile4Test, recreate) {
     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"
+              "fqdn_fwd,fqdn_rev,hostname,state\n"
+              "192.0.3.2,00:01:02:03:04:05,,200,200,8,1,1,host.example.com,2\n"
               "192.0.3.10,0d:0e:0a:0d:0b:0e:0e:0f,01:02:03:04,100,100,7,0,"
-              "0,\n",
+              "0,,0\n",
               io_.readFile());
 }
 

+ 10 - 9
src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc

@@ -110,14 +110,14 @@ 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,hwaddr\n"
+                  "fqdn_rev,hostname,hwaddr,state\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"
+                  "200,200,8,100,0,7,0,1,1,host.example.com,,1\n"
+                  "2001:db8:1::1,,200,200,8,100,0,7,0,1,1,host.example.com,,1\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"
+                  "0,8,0,0,0,,,1\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");
+                  "16,64,0,0,,,1\n");
 }
 
 // This test checks the capability to read and parse leases from the file.
@@ -277,13 +277,14 @@ TEST_F(CSVLeaseFile6Test, recreate) {
     }
 
     EXPECT_EQ("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-              "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+              "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+              "state\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"
+              "200,200,8,100,0,7,0,1,1,host.example.com,,0\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"
+              ",300,300,6,150,0,8,128,0,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",
+              "300,300,10,150,2,7,64,0,0,,,0\n",
               io_.readFile());
 }
 

+ 433 - 7
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc

@@ -79,11 +79,8 @@ GenericLeaseMgrTest::initializeLease4(std::string address) {
     lease->addr_ = IOAddress(address);
 
     // Initialize unused fields.
-    lease->ext_ = 0;                            // Not saved
     lease->t1_ = 0;                             // Not saved
     lease->t2_ = 0;                             // Not saved
-    lease->fixed_ = false;                      // Unused
-    lease->comments_ = std::string("");         // Unused
 
     // Set other parameters.  For historical reasons, address 0 is not used.
     if (address == straddress4_[0]) {
@@ -199,8 +196,6 @@ GenericLeaseMgrTest::initializeLease6(std::string address) {
     // Initialize unused fields.
     lease->t1_ = 0;                             // Not saved
     lease->t2_ = 0;                             // Not saved
-    lease->fixed_ = false;                      // Unused
-    lease->comments_ = std::string("");         // Unused
 
     // Set other parameters.  For historical reasons, address 0 is not used.
     if (address == straddress6_[0]) {
@@ -1304,8 +1299,6 @@ GenericLeaseMgrTest::testLease6LeaseTypeCheck() {
     // Initialize unused fields.
     empty_lease->t1_ = 0;                             // Not saved
     empty_lease->t2_ = 0;                             // Not saved
-    empty_lease->fixed_ = false;                      // Unused
-    empty_lease->comments_ = std::string("");         // Unused
     empty_lease->iaid_ = 142;
     empty_lease->duid_ = DuidPtr(new DUID(*duid));
     empty_lease->subnet_id_ = 23;
@@ -1657,6 +1650,439 @@ GenericLeaseMgrTest::testVersion(int major, int minor) {
     EXPECT_EQ(minor, lmptr_->getVersion().second);
 }
 
+void
+GenericLeaseMgrTest::testGetExpiredLeases4() {
+    // Get the leases to be used for the test.
+    vector<Lease4Ptr> leases = createLeases4();
+    // Make sure we have at least 6 leases there.
+    ASSERT_GE(leases.size(), 6);
+
+    // Use the same current time for all leases.
+    time_t current_time = time(NULL);
+
+    // Add them to the database
+    for (size_t i = 0; i < leases.size(); ++i) {
+        // Mark every other lease as expired.
+        if (i % 2 == 0) {
+            // Set client last transmission time to the value older than the
+            // valid lifetime to make it expired. The expiration time also
+            // depends on the lease index, so as we can later check that the
+            // leases are ordered by the expiration time.
+            leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i;
+
+        } else {
+            // Set current time as cltt for remaining leases. These leases are
+            // not expired.
+            leases[i]->cltt_ = current_time;
+        }
+        ASSERT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Retrieve at most 1000 expired leases.
+    Lease4Collection expired_leases;
+    ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 1000));
+    // Leases with even indexes should be returned as expired.
+    ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+    // The expired leases should be returned from the most to least expired.
+    // This matches the reverse order to which they have been added.
+    for (Lease4Collection::reverse_iterator lease = expired_leases.rbegin();
+         lease != expired_leases.rend(); ++lease) {
+        int index = static_cast<int>(std::distance(expired_leases.rbegin(), lease));
+        // Multiple current index by two, because only leases with even indexes
+        // should have been returned.
+        EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+    }
+
+    // Update current time for the next test.
+    current_time = time(NULL);
+    // Also, remove expired leases collected during the previous test.
+    expired_leases.clear();
+
+    // This time let's reverse the expiration time and see if they will be returned
+    // in the correct order.
+    for (int i = 0; i < leases.size(); ++i) {
+        // Update the time of expired leases with even indexes.
+        if (i % 2 == 0) {
+            leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i;
+
+        } else {
+            // Make sure remaining leases remain unexpired.
+            leases[i]->cltt_ = current_time + 100;
+        }
+        ASSERT_NO_THROW(lmptr_->updateLease4(leases[i]));
+    }
+
+    // Retrieve expired leases again. The limit of 0 means return all expired
+    // leases.
+    ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 0));
+    // The same leases should be returned.
+    ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+    // This time leases should be returned in the non-reverse order.
+    for (Lease4Collection::iterator lease = expired_leases.begin();
+         lease != expired_leases.end(); ++lease) {
+        int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+        EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+    }
+
+    // Remember expired leases returned.
+    std::vector<Lease4Ptr> saved_expired_leases = expired_leases;
+
+    // Remove expired leases again. 
+    expired_leases.clear();
+
+    // Limit the number of leases to be returned to 2.
+    ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 2));
+
+    // Make sure we have exactly 2 leases returned.
+    ASSERT_EQ(2, expired_leases.size());
+
+    // Test that most expired leases have been returned.
+    for (Lease4Collection::iterator lease = expired_leases.begin();
+         lease != expired_leases.end(); ++lease) {
+        int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+        EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+    }
+
+    // Mark every other expired lease as reclaimed.
+    for (int i = 0; i < saved_expired_leases.size(); ++i) {
+        if (i % 2 != 0) {
+            saved_expired_leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+        }
+        ASSERT_NO_THROW(lmptr_->updateLease4(saved_expired_leases[i]));
+    }
+
+    expired_leases.clear();
+
+    // This the returned leases should exclude reclaimed ones. So the number
+    // of returned leases should be roughly half of the expired leases.
+    ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 0));
+    ASSERT_EQ(static_cast<size_t>(saved_expired_leases.size() / 2),
+              expired_leases.size());
+
+    // Make sure that returned leases are those that are not reclaimed, i.e.
+    // those that have even index.
+    for (Lease4Collection::iterator lease = expired_leases.begin();
+         lease != expired_leases.end(); ++lease) {
+        int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+        EXPECT_EQ(saved_expired_leases[2 * index]->addr_, (*lease)->addr_);
+    }
+}
+
+void
+GenericLeaseMgrTest::testGetExpiredLeases6() {
+    // Get the leases to be used for the test.
+    vector<Lease6Ptr> leases = createLeases6();
+    // Make sure we have at least 6 leases there.
+    ASSERT_GE(leases.size(), 6);
+
+    // Use the same current time for all leases.
+    time_t current_time = time(NULL);
+
+    // Add them to the database
+    for (size_t i = 0; i < leases.size(); ++i) {
+        // Mark every other lease as expired.
+        if (i % 2 == 0) {
+            // Set client last transmission time to the value older than the
+            // valid lifetime to make it expired. The expiration time also
+            // depends on the lease index, so as we can later check that the
+            // leases are ordered by the expiration time.
+            leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i;
+
+        } else {
+            // Set current time as cltt for remaining leases. These leases are
+            // not expired.
+            leases[i]->cltt_ = current_time;
+        }
+        ASSERT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Retrieve at most 1000 expired leases.
+    Lease6Collection expired_leases;
+    ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 1000));
+    // Leases with even indexes should be returned as expired.
+    ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+    // The expired leases should be returned from the most to least expired.
+    // This matches the reverse order to which they have been added.
+    for (Lease6Collection::reverse_iterator lease = expired_leases.rbegin();
+         lease != expired_leases.rend(); ++lease) {
+        int index = static_cast<int>(std::distance(expired_leases.rbegin(), lease));
+        // Multiple current index by two, because only leases with even indexes
+        // should have been returned.
+        EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+    }
+
+    // Update current time for the next test.
+    current_time = time(NULL);
+    // Also, remove expired leases collected during the previous test.
+    expired_leases.clear();
+
+    // This time let's reverse the expiration time and see if they will be returned
+    // in the correct order.
+    for (int i = 0; i < leases.size(); ++i) {
+        // Update the time of expired leases with even indexes.
+        if (i % 2 == 0) {
+            leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i;
+
+        } else {
+            // Make sure remaining leases remain unexpired.
+            leases[i]->cltt_ = current_time + 100;
+        }
+        ASSERT_NO_THROW(lmptr_->updateLease6(leases[i]));
+    }
+
+    // Retrieve expired leases again. The limit of 0 means return all expired
+    // leases.
+    ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0));
+    // The same leases should be returned.
+    ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+    // This time leases should be returned in the non-reverse order.
+    for (Lease6Collection::iterator lease = expired_leases.begin();
+         lease != expired_leases.end(); ++lease) {
+        int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+        EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+    }
+
+    // Remember expired leases returned.
+    std::vector<Lease6Ptr> saved_expired_leases = expired_leases;
+
+    // Remove expired leases again. 
+    expired_leases.clear();
+
+    // Limit the number of leases to be returned to 2.
+    ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 2));
+
+    // Make sure we have exactly 2 leases returned.
+    ASSERT_EQ(2, expired_leases.size());
+
+    // Test that most expired leases have been returned.
+    for (Lease6Collection::iterator lease = expired_leases.begin();
+         lease != expired_leases.end(); ++lease) {
+        int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+        EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+    }
+
+    // Mark every other expired lease as reclaimed.
+    for (int i = 0; i < saved_expired_leases.size(); ++i) {
+        if (i % 2 != 0) {
+            saved_expired_leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+        }
+        ASSERT_NO_THROW(lmptr_->updateLease6(saved_expired_leases[i]));
+    }
+
+    expired_leases.clear();
+
+    // This the returned leases should exclude reclaimed ones. So the number
+    // of returned leases should be roughly half of the expired leases.
+    ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0));
+    ASSERT_EQ(static_cast<size_t>(saved_expired_leases.size() / 2),
+              expired_leases.size());
+
+    // Make sure that returned leases are those that are not reclaimed, i.e.
+    // those that have even index.
+    for (Lease6Collection::iterator lease = expired_leases.begin();
+         lease != expired_leases.end(); ++lease) {
+        int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+        EXPECT_EQ(saved_expired_leases[2 * index]->addr_, (*lease)->addr_);
+    }
+}
+
+void
+GenericLeaseMgrTest::testDeleteExpiredReclaimedLeases4() {
+    // Get the leases to be used for the test.
+    vector<Lease4Ptr> leases = createLeases4();
+    // Make sure we have at least 6 leases there.
+    ASSERT_GE(leases.size(), 6);
+
+    time_t current_time = time(NULL);
+
+    // Add them to the database
+    for (size_t i = 0; i < leases.size(); ++i) {
+        // Mark every other lease as expired.
+        if (i % 2 == 0) {
+            // Set client last transmission time to the value older than the
+            // valid lifetime to make it expired. We also substract the value
+            // of 10, 20, 30, 40 etc, depending on the lease index. This
+            // simulates different expiration times for various leases.
+            leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - i * 10;
+            // Set reclaimed state.
+            leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+
+        } else {
+            // Other leases are left as not expired - client last transmission
+            // time set to current time.
+            leases[i]->cltt_ = current_time;
+        }
+        ASSERT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Keep reclaimed lease for 15 seconds after expiration.
+    const uint32_t lease_affinity_time = 15;
+
+    // Delete expired and reclaimed leases which have expired earlier than
+    // 15 seconds ago. This should affect leases with index 2, 3, 4 etc.
+    uint64_t deleted_num;
+    uint64_t should_delete_num = 0;
+    ASSERT_NO_THROW(
+        deleted_num = lmptr_->deleteExpiredReclaimedLeases4(lease_affinity_time)
+    );
+
+    for (size_t i = 0; i < leases.size(); ++i) {
+        // Obtain lease from the server.
+        Lease4Ptr lease = lmptr_->getLease4(leases[i]->addr_);
+
+        // If the lease is reclaimed and the expiration time passed more than
+        // 15 seconds ago, the lease should have been deleted.
+        if (leases[i]->stateExpiredReclaimed() &&
+            ((leases[i]->getExpirationTime() + lease_affinity_time) < current_time)) {
+            EXPECT_FALSE(lease) << "The following lease should have been"
+                " deleted: " << leases[i]->toText();
+            ++should_delete_num;
+
+        } else {
+            // If the lease is not reclaimed or it has expired less than
+            // 15 seconds ago, the lease should still be there.
+            EXPECT_TRUE(lease) << "The following lease shouldn't have been"
+                " deleted: " << leases[i]->toText();
+        }
+    }
+    // Check that the number of leases deleted is correct.
+    EXPECT_EQ(deleted_num, should_delete_num);
+
+    // Make sure we can make another attempt, when there are no more leases
+    // to be deleted.
+    ASSERT_NO_THROW(
+        deleted_num = lmptr_->deleteExpiredReclaimedLeases4(lease_affinity_time)
+    );
+    // No lease should have been deleted.
+    EXPECT_EQ(0, deleted_num);
+
+    // Reopen the database. This to ensure that the leases have been deleted
+    // from the persistent storage.
+    reopen(V4);
+
+    for (size_t i = 0; i < leases.size(); ++i) {
+        /// @todo Leases with empty HW address are dropped by the memfile
+        /// backend. We will have to reevaluate if this is right behavior
+        /// of the backend when client identifier is present.
+        if (leases[i]->hwaddr_ && leases[i]->hwaddr_->hwaddr_.empty()) {
+            continue;
+        }
+        // Obtain lease from the server.
+        Lease4Ptr lease = lmptr_->getLease4(leases[i]->addr_);
+
+        // If the lease is reclaimed and the expiration time passed more than
+        // 15 seconds ago, the lease should have been deleted.
+        if (leases[i]->stateExpiredReclaimed() &&
+            ((leases[i]->getExpirationTime() + lease_affinity_time) < current_time)) {
+            EXPECT_FALSE(lease) << "The following lease should have been"
+                " deleted: " << leases[i]->toText();
+
+        } else {
+            // If the lease is not reclaimed or it has expired less than
+            // 15 seconds ago, the lease should still be there.
+            EXPECT_TRUE(lease) << "The following lease shouldn't have been"
+                " deleted: " << leases[i]->toText();
+        }
+    }
+}
+
+void
+GenericLeaseMgrTest::testDeleteExpiredReclaimedLeases6() {
+    // Get the leases to be used for the test.
+    vector<Lease6Ptr> leases = createLeases6();
+    // Make sure we have at least 6 leases there.
+    ASSERT_GE(leases.size(), 6);
+
+    time_t current_time = time(NULL);
+
+    // Add them to the database
+    for (size_t i = 0; i < leases.size(); ++i) {
+        // Mark every other lease as expired.
+        if (i % 2 == 0) {
+            // Set client last transmission time to the value older than the
+            // valid lifetime to make it expired. We also substract the value
+            // of 10, 20, 30, 40 etc, depending on the lease index. This
+            // simulates different expiration times for various leases.
+            leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - i * 10;
+            // Set reclaimed state.
+            leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+
+        } else {
+            // Other leases are left as not expired - client last transmission
+            // time set to current time.
+            leases[i]->cltt_ = current_time;
+        }
+        ASSERT_TRUE(lmptr_->addLease(leases[i]));
+    }
+
+    // Keep reclaimed lease for 15 seconds after expiration.
+    const uint32_t lease_affinity_time = 15;
+
+    // Delete expired and reclaimed leases which have expired earlier than
+    // 15 seconds ago. This should affect leases with index 2, 3, 4 etc.
+    uint64_t deleted_num;
+    uint64_t should_delete_num = 0;
+    ASSERT_NO_THROW(
+        deleted_num = lmptr_->deleteExpiredReclaimedLeases6(lease_affinity_time)
+    );
+
+    for (size_t i = 0; i < leases.size(); ++i) {
+        // Obtain lease from the server.
+        Lease6Ptr lease = lmptr_->getLease6(leases[i]->type_, leases[i]->addr_);
+
+        // If the lease is reclaimed and the expiration time passed more than
+        // 15 seconds ago, the lease should have been deleted.
+        if (leases[i]->stateExpiredReclaimed() &&
+            ((leases[i]->getExpirationTime() + lease_affinity_time) < current_time)) {
+            EXPECT_FALSE(lease) << "The following lease should have been"
+                " deleted: " << leases[i]->toText();
+            ++should_delete_num;
+
+        } else {
+            // If the lease is not reclaimed or it has expired less than
+            // 15 seconds ago, the lease should still be there.
+            EXPECT_TRUE(lease) << "The following lease shouldn't have been"
+                " deleted: " << leases[i]->toText();
+        }
+    }
+    // Check that the number of deleted leases is correct.
+    EXPECT_EQ(should_delete_num, deleted_num);
+
+    // Make sure we can make another attempt, when there are no more leases
+    // to be deleted.
+    ASSERT_NO_THROW(
+        deleted_num = lmptr_->deleteExpiredReclaimedLeases6(lease_affinity_time)
+    );
+    // No lease should have been deleted.
+    EXPECT_EQ(0, deleted_num);
+
+    // Reopen the database. This to ensure that the leases have been deleted
+    // from the persistent storage.
+    reopen(V6);
+
+    for (size_t i = 0; i < leases.size(); ++i) {
+        // Obtain lease from the server.
+        Lease6Ptr lease = lmptr_->getLease6(leases[i]->type_, leases[i]->addr_);
+
+        // If the lease is reclaimed and the expiration time passed more than
+        // 15 seconds ago, the lease should have been deleted.
+        if (leases[i]->stateExpiredReclaimed() &&
+            ((leases[i]->getExpirationTime() + lease_affinity_time) < current_time)) {
+            EXPECT_FALSE(lease) << "The following lease should have been"
+                " deleted: " << leases[i]->toText();
+
+        } else {
+            // If the lease is not reclaimed or it has expired less than
+            // 15 seconds ago, the lease should still be there.
+            EXPECT_TRUE(lease) << "The following lease shouldn't have been"
+                " deleted: " << leases[i]->toText();
+        }
+    }
+}
 
 }; // namespace test
 }; // namespace dhcp

+ 37 - 1
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -265,6 +265,42 @@ public:
     /// @param minor Expected minor version to be reported.
     void testVersion(int major, int minor);
 
+    /// @brief Checks that the expired DHCPv4 leases can be retrieved.
+    ///
+    /// This test checks the following:
+    /// - all expired and not reclaimed leases are retured
+    /// - number of leases returned can be limited
+    /// - leases are returned in the order from the most expired to the
+    ///   least expired
+    /// - reclaimed leases are not returned.
+    void testGetExpiredLeases4();
+
+    /// @brief Checks that the expired DHCPv6 leases can be retrieved.
+    ///
+    /// This test checks the following:
+    /// - all expired and not reclaimed leases are retured
+    /// - number of leases returned can be limited
+    /// - leases are returned in the order from the most expired to the
+    ///   least expired
+    /// - reclaimed leases are not returned.
+    void testGetExpiredLeases6();
+
+    /// @brief Checks that selected expired-reclaimed DHCPv6 leases
+    /// are removed.
+    ///
+    /// This creates a number of DHCPv6 leases and marks some of them
+    /// as expired-reclaimed. It later verifies that the expired-reclaimed
+    /// leases can be removed.
+    void testDeleteExpiredReclaimedLeases6();
+
+    /// @brief Checks that selected expired-reclaimed DHCPv4 leases
+    /// are removed.
+    ///
+    /// This creates a number of DHCPv4 leases and marks some of them
+    /// as expired-reclaimed. It later verifies that the expired-reclaimed
+    /// leases can be removed.
+    void testDeleteExpiredReclaimedLeases4();
+
     /// @brief String forms of IPv4 addresses
     std::vector<std::string>  straddress4_;
 

+ 27 - 27
src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc

@@ -151,11 +151,11 @@ protected:
     /// @brief Sets up the header strings
     virtual void SetUp() {
         v4_hdr_ = "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n";
+                  "fqdn_fwd,fqdn_rev,hostname,state\n";
 
         v6_hdr_ = "address,duid,valid_lifetime,expire,subnet_id,"
                   "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-                  "fqdn_rev,hostname,hwaddr\n";
+                  "fqdn_rev,hostname,hwaddr,state\n";
     }
 };
 
@@ -179,17 +179,17 @@ LeaseFileLoaderTest::absolutePath(const std::string& filename) {
 TEST_F(LeaseFileLoaderTest, loadWrite4) {
     std::string test_str;
     std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                      "200,200,8,1,1,host.example.com\n";
+                      "200,200,8,1,1,host.example.com,1\n";
     std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                      "200,500,8,1,1,host.example.com\n";
+                      "200,500,8,1,1,host.example.com,1\n";
 
     std::string b_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
-                      "100,100,7,0,0,\n";
+                      "100,100,7,0,0,,1\n";
     std::string b_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
-                      "100,135,7,0,0,\n";
+                      "100,135,7,0,0,,1\n";
 
     std::string c_1 = "192.0.2.3,,a:11:01:04,"
-                      "200,200,8,1,1,host.example.com\n";
+                      "200,200,8,1,1,host.example.com,1\n";
 
     // Create lease file with leases for 192.0.2.1, 192.0.3.15. The lease
     // entry for the 192.0.2.3 is invalid (lacks HW address) and should
@@ -250,14 +250,14 @@ TEST_F(LeaseFileLoaderTest, loadWrite4) {
 TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) {
     std::string test_str;
     std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                      "200,200,8,1,1,host.example.com\n";
+                      "200,200,8,1,1,host.example.com,1\n";
     std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                      "0,500,8,1,1,host.example.com\n";
+                      "0,500,8,1,1,host.example.com,1\n";
 
     std::string b_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
-                      "100,100,7,0,0,\n";
+                      "100,100,7,0,0,,1\n";
     std::string b_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
-                      "100,135,7,0,0,\n";
+                      "100,135,7,0,0,,1\n";
 
 
     // Create lease file in which one of the entries for 192.0.2.1
@@ -305,19 +305,19 @@ TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) {
 TEST_F(LeaseFileLoaderTest, loadWrite6) {
     std::string test_str;
     std::string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                      "200,200,8,100,0,7,0,1,1,host.example.com,\n";
+                      "200,200,8,100,0,7,0,1,1,host.example.com,,1\n";
     std::string a_2 = "2001:db8:1::1,,"
-                      "200,200,8,100,0,7,0,1,1,host.example.com,\n";
+                      "200,200,8,100,0,7,0,1,1,host.example.com,,1\n";
     std::string a_3 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                      "200,400,8,100,0,7,0,1,1,host.example.com,\n";
+                      "200,400,8,100,0,7,0,1,1,host.example.com,,1\n";
 
     std::string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
-                      "300,300,6,150,0,8,0,0,0,,\n";
+                      "300,300,6,150,0,8,0,0,0,,,1\n";
     std::string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
-                      "300,800,6,150,0,8,0,0,0,,\n";
+                      "300,800,6,150,0,8,0,0,0,,,1\n";
 
     std::string c_1 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                      "100,200,8,0,2,16,64,0,0,,\n";
+                      "100,200,8,0,2,16,64,0,0,,,1\n";
 
 
 
@@ -379,14 +379,14 @@ TEST_F(LeaseFileLoaderTest, loadWrite6) {
 TEST_F(LeaseFileLoaderTest, loadWrite6LeaseRemove) {
     std::string test_str;
     std::string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                      "200,200,8,100,0,7,0,1,1,host.example.com,\n";
+                      "200,200,8,100,0,7,0,1,1,host.example.com,,1\n";
     std::string a_2 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
-                      "0,400,8,100,0,7,0,1,1,host.example.com,\n";
+                      "0,400,8,100,0,7,0,1,1,host.example.com,,1\n";
 
     std::string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
-                      "300,300,6,150,0,8,0,0,0,,\n";
+                      "300,300,6,150,0,8,0,0,0,,,1\n";
     std::string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
-                      "300,800,6,150,0,8,0,0,0,,\n";
+                      "300,800,6,150,0,8,0,0,0,,,1\n";
 
     // Create lease file in which one of the entries for the 2001:db8:1::1
     // has valid lifetime set to 0, in which case the lease should be
@@ -431,13 +431,13 @@ TEST_F(LeaseFileLoaderTest, loadWrite6LeaseRemove) {
 TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
     std::string test_str;
     std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                      "200,200,8,1,1,host.example.com\n";
+                      "200,200,8,1,1,host.example.com,1\n";
     std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
-                      "200,500,8,1,1,host.example.com\n";
+                      "200,500,8,1,1,host.example.com,1\n";
 
-    std::string b_1 = "192.0.2.3,,a:11:01:04,200,200,8,1,1,host.example.com\n";
+    std::string b_1 = "192.0.2.3,,a:11:01:04,200,200,8,1,1,host.example.com,1\n";
 
-    std::string c_1 = "192.0.2.10,01:02:03:04:05:06,,200,300,8,1,1,\n";
+    std::string c_1 = "192.0.2.10,01:02:03:04:05:06,,200,300,8,1,1,,1\n";
 
     // Create a lease file for which there is a number of invalid
     // entries.  b_1 is invalid and gets used multiple times.
@@ -501,8 +501,8 @@ TEST_F(LeaseFileLoaderTest, loadMaxErrors) {
 // and comparing that with the expected value.
 TEST_F(LeaseFileLoaderTest, loadWriteLeaseWithZeroLifetime) {
     std::string test_str;
-    std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,\n";
-    std::string b_2 = "192.0.2.3,06:07:08:09:0a:bd,,0,200,8,1,1,\n";
+    std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,,1\n";
+    std::string b_2 = "192.0.2.3,06:07:08:09:0a:bd,,0,200,8,1,1,,1\n";
 
     // Create lease file. The second lease has a valid lifetime of 0.
     test_str = v4_hdr_ + a_1 + b_2;

+ 38 - 1
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -173,6 +173,23 @@ public:
         return (leases6_);
     }
 
+
+    /// @brief Returns expired DHCPv6 leases.
+    ///
+    /// This method is not implemented.
+    virtual void getExpiredLeases6(Lease6Collection&, const size_t) const {
+        isc_throw(NotImplemented, "ConcreteLeaseMgr::getExpiredLeases6 is not"
+                  " implemented");
+    }
+
+    /// @brief Returns expired DHCPv4 leases.
+    ///
+    /// This method is not implemented.
+    virtual void getExpiredLeases4(Lease4Collection&, const size_t) const {
+        isc_throw(NotImplemented, "ConcreteLeaseMgr::getExpiredLeases4 is not"
+                  " implemented");
+    }
+
     /// @brief Updates IPv4 lease.
     ///
     /// @param lease4 The lease to be updated.
@@ -197,6 +214,26 @@ public:
         return (false);
     }
 
+    /// @brief Deletes all expired and reclaimed DHCPv4 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t) {
+        isc_throw(NotImplemented, "ConcreteLeaseMgr::deleteExpiredReclaimedLeases4"
+                  " is not implemented");
+    }
+
+    /// @brief Deletes all expired and reclaimed DHCPv6 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t) {
+        isc_throw(NotImplemented, "ConcreteLeaseMgr::deleteExpiredReclaimedLeases6"
+                  " is not implemented");
+    }
+
     /// @brief Returns backend type.
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

+ 48 - 41
src/lib/dhcpsrv/tests/lease_unittest.cc

@@ -96,7 +96,6 @@ TEST_F(Lease4Test, constructor) {
                      "hostname.example.com.");
 
         EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
-        EXPECT_EQ(0, lease.ext_);
         EXPECT_TRUE(util::equalValues(hwaddr_, lease.hwaddr_));
         EXPECT_TRUE(util::equalValues(clientid_, lease.client_id_));
         EXPECT_EQ(0, lease.t1_);
@@ -104,11 +103,10 @@ TEST_F(Lease4Test, constructor) {
         EXPECT_EQ(VALID_LIFETIME, lease.valid_lft_);
         EXPECT_EQ(current_time, lease.cltt_);
         EXPECT_EQ(SUBNET_ID, lease.subnet_id_);
-        EXPECT_FALSE(lease.fixed_);
         EXPECT_EQ("hostname.example.com.", lease.hostname_);
         EXPECT_TRUE(lease.fqdn_fwd_);
         EXPECT_TRUE(lease.fqdn_rev_);
-        EXPECT_TRUE(lease.comments_.empty());
+        EXPECT_EQ(Lease::STATE_DEFAULT, lease.state_);
     }
 }
 
@@ -126,6 +124,10 @@ TEST_F(Lease4Test, copyConstructor) {
     Lease4 lease(0xffffffff, hwaddr_, clientid_, VALID_LIFETIME, 0, 0, current_time,
                  SUBNET_ID);
 
+    // Declined is a non-default state. We'll see if the state will be copied
+    // or the default state will be set for the copied lease.
+    lease.state_ = Lease::STATE_DECLINED;
+
     // Use copy constructor to copy the lease.
     Lease4 copied_lease(lease);
 
@@ -163,8 +165,14 @@ TEST_F(Lease4Test, operatorAssign) {
     Lease4 lease(0xffffffff, hwaddr_, clientid_, VALID_LIFETIME, 0, 0, current_time,
                  SUBNET_ID);
 
-    // Use assignment operator to assign the lease.
-    Lease4 copied_lease = lease;
+    // Declined is a non-default state. We'll see if the state will be copied
+    // or the default state will be set for the copied lease.
+    lease.state_ = Lease::STATE_DECLINED;
+
+    // Create a default lease.
+    Lease4 copied_lease;
+    // Use assignment operator to assign new lease.
+    copied_lease = lease;
 
     // Both leases should be now equal. When doing this check we assume that
     // the equality operator works correctly.
@@ -284,13 +292,6 @@ TEST_F(Lease4Test, operatorEquals) {
     EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
     EXPECT_FALSE(lease1 != lease2); // ... leases equal
 
-    ++lease1.ext_;
-    EXPECT_FALSE(lease1 == lease2);
-    EXPECT_TRUE(lease1 != lease2);
-    lease1.ext_ = lease2.ext_;
-    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
-    EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
     ++lease1.hwaddr_->hwaddr_[0];
     EXPECT_FALSE(lease1 == lease2);
     EXPECT_TRUE(lease1 != lease2);
@@ -343,13 +344,6 @@ TEST_F(Lease4Test, operatorEquals) {
     EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
     EXPECT_FALSE(lease1 != lease2); // ... leases equal
 
-    lease1.fixed_ = !lease1.fixed_;
-    EXPECT_FALSE(lease1 == lease2);
-    EXPECT_TRUE(lease1 != lease2);
-    lease1.fixed_ = lease2.fixed_;
-    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
-    EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
     lease1.hostname_ += std::string("Something random");
     EXPECT_FALSE(lease1 == lease2);
     EXPECT_TRUE(lease1 != lease2);
@@ -371,10 +365,10 @@ TEST_F(Lease4Test, operatorEquals) {
     EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
     EXPECT_FALSE(lease1 != lease2); // ... leases equal
 
-    lease1.comments_ += std::string("Something random");
+    lease1.state_  += 1;
     EXPECT_FALSE(lease1 == lease2);
     EXPECT_TRUE(lease1 != lease2);
-    lease1.comments_ = lease2.comments_;
+    lease2.state_ += 1;
     EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
     EXPECT_FALSE(lease1 != lease2); // ... leases equal
 }
@@ -430,7 +424,8 @@ TEST_F(Lease4Test, toText) {
              << "Cltt:          12345678\n"
              << "Hardware addr: " << hwaddr_->toText(false) << "\n"
              << "Client id:     " << clientid_->toText() << "\n"
-             << "Subnet ID:     789\n";
+             << "Subnet ID:     789\n"
+             << "State:         default\n";
 
     EXPECT_EQ(expected.str(), lease.toText());
 
@@ -445,10 +440,18 @@ TEST_F(Lease4Test, toText) {
              << "Cltt:          12345678\n"
              << "Hardware addr: (none)\n"
              << "Client id:     (none)\n"
-             << "Subnet ID:     789\n";
+             << "Subnet ID:     789\n"
+             << "State:         default\n";
     EXPECT_EQ(expected.str(), lease.toText());
 }
 
+// Verify that the lease states are correctly returned in the textual format.
+TEST_F(Lease4Test, stateToText) {
+    EXPECT_EQ("default", Lease4::statesToText(Lease::STATE_DEFAULT));
+    EXPECT_EQ("declined", Lease4::statesToText(Lease::STATE_DECLINED));
+    EXPECT_EQ("expired-reclaimed", Lease4::statesToText(Lease::STATE_EXPIRED_RECLAIMED));
+}
+
 /// @brief Creates an instance of the lease with certain FQDN data.
 ///
 /// @param hostname Hostname.
@@ -467,7 +470,7 @@ Lease6 createLease6(const std::string& hostname, const bool fqdn_fwd,
 
 // Lease6 is also defined in lease_mgr.h, so is tested in this file as well.
 // This test checks if the Lease6 structure can be instantiated correctly
-TEST(Lease6, Lease6ConstructorDefault) {
+TEST(Lease6Test, Lease6ConstructorDefault) {
 
     // check a variety of addresses with different bits set.
     const char* ADDRESS[] = {
@@ -514,7 +517,7 @@ TEST(Lease6, Lease6ConstructorDefault) {
 
 // This test verifies that the Lease6 constructor which accepts FQDN data,
 // sets the data correctly for the lease.
-TEST(Lease6, Lease6ConstructorWithFQDN) {
+TEST(Lease6Test, Lease6ConstructorWithFQDN) {
 
     // check a variety of addresses with different bits set.
     const char* ADDRESS[] = {
@@ -563,7 +566,7 @@ TEST(Lease6, Lease6ConstructorWithFQDN) {
 /// Checks that the operator==() correctly compares two leases for equality.
 /// As operator!=() is also defined for this class, every check on operator==()
 /// is followed by the reverse check on operator!=().
-TEST(Lease6, OperatorEquals) {
+TEST(Lease6Test, operatorEquals) {
 
     // check a variety of addresses with different bits set.
     const IOAddress addr("2001:db8:1::456");
@@ -665,13 +668,6 @@ TEST(Lease6, OperatorEquals) {
     EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
     EXPECT_FALSE(lease1 != lease2); // ... leases equal
 
-    lease1.fixed_ = !lease1.fixed_;
-    EXPECT_FALSE(lease1 == lease2);
-    EXPECT_TRUE(lease1 != lease2);
-    lease1.fixed_ = lease2.fixed_;
-    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
-    EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
     lease1.hostname_ += std::string("Something random");
     EXPECT_FALSE(lease1 == lease2);
     EXPECT_TRUE(lease1 != lease2);
@@ -693,16 +689,16 @@ TEST(Lease6, OperatorEquals) {
     EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
     EXPECT_FALSE(lease1 != lease2); // ... leases equal
 
-    lease1.comments_ += std::string("Something random");
+    lease1.state_  += 1;
     EXPECT_FALSE(lease1 == lease2);
     EXPECT_TRUE(lease1 != lease2);
-    lease1.comments_ = lease2.comments_;
+    lease2.state_ += 1;
     EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
     EXPECT_FALSE(lease1 != lease2); // ... leases equal
 }
 
 // Checks if lease expiration is calculated properly
-TEST(Lease6, Lease6Expired) {
+TEST(Lease6Test, Lease6Expired) {
     const IOAddress addr("2001:db8:1::456");
     const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
     const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
@@ -727,7 +723,7 @@ TEST(Lease6, Lease6Expired) {
 
 // Verify that the DUID can be returned as a vector object and if DUID is NULL
 // the empty vector is returned.
-TEST(Lease6, getDuidVector) {
+TEST(Lease6Test, getDuidVector) {
     // Create a lease.
     Lease6 lease;
     // By default, the lease should have client id set to NULL. If it doesn't,
@@ -746,7 +742,7 @@ TEST(Lease6, getDuidVector) {
 }
 
 // Verify the behavior of the function which checks FQDN data for equality.
-TEST(Lease6, hasIdenticalFqdn) {
+TEST(Lease6Test, hasIdenticalFqdn) {
     Lease6 lease = createLease6("myhost.example.com.", true, true);
     EXPECT_TRUE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
                                                     true, true)));
@@ -763,7 +759,7 @@ TEST(Lease6, hasIdenticalFqdn) {
 }
 
 // Verify that toText() method reports Lease4 structure properly.
-TEST(Lease6, toText) {
+TEST(Lease6Test, toText) {
 
     HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
 
@@ -773,6 +769,7 @@ TEST(Lease6, toText) {
     Lease6 lease(Lease::TYPE_NA, IOAddress("2001:db8::1"), duid, 123456,
                  400, 800, 100, 200, 5678, hwaddr, 128);
     lease.cltt_ = 12345678;
+    lease.state_ = Lease::STATE_DECLINED;
     
     std::stringstream expected;
     expected << "Type:          IA_NA(" << static_cast<int>(Lease::TYPE_NA) << ")\n"
@@ -783,7 +780,8 @@ TEST(Lease6, toText) {
              << "Valid life:    800\n"
              << "Cltt:          12345678\n"
              << "Hardware addr: " << hwaddr->toText(false) << "\n"
-             << "Subnet ID:     5678\n";
+             << "Subnet ID:     5678\n"
+             << "State:         declined\n";
 
     EXPECT_EQ(expected.str(), lease.toText());
 
@@ -798,8 +796,17 @@ TEST(Lease6, toText) {
              << "Valid life:    800\n"
              << "Cltt:          12345678\n"
              << "Hardware addr: (none)\n"
-             << "Subnet ID:     5678\n";
+             << "Subnet ID:     5678\n"
+             << "State:         declined\n";
     EXPECT_EQ(expected.str(), lease.toText());
 }
 
+// Verify that the lease states are correctly returned in the textual format.
+TEST(Lease6Test, stateToText) {
+    EXPECT_EQ("default", Lease6::statesToText(Lease::STATE_DEFAULT));
+    EXPECT_EQ("declined", Lease6::statesToText(Lease::STATE_DECLINED));
+    EXPECT_EQ("expired-reclaimed", Lease6::statesToText(Lease::STATE_EXPIRED_RECLAIMED));
+}
+
+
 }; // end of anonymous namespace

+ 131 - 124
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -404,20 +404,20 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup4) {
     // stored.
     std::string new_file_contents =
         "address,hwaddr,client_id,valid_lifetime,expire,"
-        "subnet_id,fqdn_fwd,fqdn_rev,hostname\n";
+        "subnet_id,fqdn_fwd,fqdn_rev,hostname,state\n";
 
     // This string contains the contents of the lease file with exactly
     // one lease, but two entries. One of the entries should be removed
     // as a result of lease file cleanup.
     std::string current_file_contents = new_file_contents +
-        "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,\n"
-        "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,\n";
+        "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1\n"
+        "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,1\n";
     LeaseFileIO current_file(getLeaseFilePath("leasefile4_0.csv"));
     current_file.writeFile(current_file_contents);
 
     std::string previous_file_contents = new_file_contents +
-        "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,,\n"
-        "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,\n";
+        "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,,1\n"
+        "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,1\n";
     LeaseFileIO previous_file(getLeaseFilePath("leasefile4_0.csv.2"));
     previous_file.writeFile(previous_file_contents);
 
@@ -453,15 +453,15 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup4) {
     ASSERT_NO_THROW(lease_mgr->addLease(new_lease));
 
     std::string updated_file_contents = new_file_contents +
-        "192.0.2.45,00:00:00:00:00:00,,100,100,1,0,0,\n";
+        "192.0.2.45,00:00:00:00:00:00,,100,100,1,0,0,,0\n";
     EXPECT_EQ(updated_file_contents, current_file.readFile());
 
     // This string contains the contents of the lease file we
     // expect after the LFC run.  It has two leases with one
     // entry each.
     std::string result_file_contents = new_file_contents +
-        "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,\n"
-        "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,\n";
+        "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,1\n"
+        "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,1\n";
 
     // The LFC should have created a file with the two leases and moved it
     // to leasefile4_0.csv.2
@@ -480,24 +480,24 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup6) {
     std::string new_file_contents =
         "address,duid,valid_lifetime,expire,subnet_id,"
         "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-        "fqdn_rev,hostname,hwaddr\n";
+        "fqdn_rev,hostname,hwaddr,state\n";
 
     // This string contains the contents of the lease file with exactly
     // one lease, but two entries. One of the entries should be removed
     // as a result of lease file cleanup.
     std::string current_file_contents = new_file_contents +
         "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,,\n"
+        "8,100,0,7,0,1,1,,,1\n"
         "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
-        "8,100,0,7,0,1,1,,\n";
+        "8,100,0,7,0,1,1,,,1\n";
     LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv"));
     current_file.writeFile(current_file_contents);
 
     std::string previous_file_contents = new_file_contents +
         "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,200,"
-        "8,100,0,7,0,1,1,,\n"
+        "8,100,0,7,0,1,1,,,1\n"
         "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
-        "8,100,0,7,0,1,1,,\n";
+        "8,100,0,7,0,1,1,,,1\n";
     LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2"));
     previous_file.writeFile(previous_file_contents);
 
@@ -536,7 +536,7 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup6) {
 
     std::string update_file_contents = new_file_contents +
         "3000::1,00:00:00:00:00:00:00:00:00:00:00:00:00,400,"
-        "400,2,300,0,123,128,0,0,,\n";
+        "400,2,300,0,123,128,0,0,,,0\n";
     EXPECT_EQ(update_file_contents, current_file.readFile());
 
     // This string contains the contents of the lease file we
@@ -544,9 +544,9 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanup6) {
     // entry each.
     std::string result_file_contents = new_file_contents +
         "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
-        "8,100,0,7,0,1,1,,\n"
+        "8,100,0,7,0,1,1,,,1\n"
         "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
-        "8,100,0,7,0,1,1,,\n";
+        "8,100,0,7,0,1,1,,,1\n";
 
     // The LFC should have created a file with the two leases and moved it
     // to leasefile6_0.csv.2
@@ -564,11 +564,11 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCleanupStartFail) {
     // stored.
     std::string new_file_contents =
         "address,hwaddr,client_id,valid_lifetime,expire,"
-        "subnet_id,fqdn_fwd,fqdn_rev,hostname\n";
+        "subnet_id,fqdn_fwd,fqdn_rev,hostname,state\n";
 
     // Create the lease file to be used by the backend.
     std::string current_file_contents = new_file_contents +
-        "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,\n";
+        "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1\n";
     LeaseFileIO current_file(getLeaseFilePath("leasefile4_0.csv"));
     current_file.writeFile(current_file_contents);
 
@@ -605,15 +605,15 @@ TEST_F(MemfileLeaseMgrTest, leaseFileFinish) {
     std::string new_file_contents =
         "address,duid,valid_lifetime,expire,subnet_id,"
         "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-        "fqdn_rev,hostname,hwaddr\n";
+        "fqdn_rev,hostname,hwaddr,state\n";
 
     // This string contains the contents of the current lease file.
     // It should not be moved.
     std::string current_file_contents = new_file_contents +
         "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,,\n"
+        "8,100,0,7,0,1,1,,,1\n"
         "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
-        "8,100,0,7,0,1,1,,\n";
+        "8,100,0,7,0,1,1,,,1\n";
     LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv"));
     current_file.writeFile(current_file_contents);
 
@@ -621,7 +621,7 @@ TEST_F(MemfileLeaseMgrTest, leaseFileFinish) {
     // be moved to the previous file.
     std::string finish_file_contents = new_file_contents +
         "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
-        "8,100,0,7,0,1,1,,\n";
+        "8,100,0,7,0,1,1,,,1\n";
     LeaseFileIO finish_file(getLeaseFilePath("leasefile6_0.csv.completed"));
     finish_file.writeFile(finish_file_contents);
 
@@ -668,15 +668,15 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCopy) {
     std::string new_file_contents =
         "address,duid,valid_lifetime,expire,subnet_id,"
         "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
-        "fqdn_rev,hostname,hwaddr\n";
+        "fqdn_rev,hostname,hwaddr,state\n";
 
     // This string contains the contents of the current lease file.
     // It should not be moved.
     std::string current_file_contents = new_file_contents +
         "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,,\n"
+        "8,100,0,7,0,1,1,,,1\n"
         "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
-        "8,100,0,7,0,1,1,,\n";
+        "8,100,0,7,0,1,1,,,1\n";
     LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv"));
     current_file.writeFile(current_file_contents);
 
@@ -686,7 +686,7 @@ TEST_F(MemfileLeaseMgrTest, leaseFileCopy) {
     // the same.
     std::string input_file_contents = new_file_contents +
         "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
-        "8,100,0,7,0,1,1,,\n";
+        "8,100,0,7,0,1,1,,,1\n";
     LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.1"));
     input_file.writeFile(input_file_contents);
 
@@ -872,15 +872,48 @@ TEST_F(MemfileLeaseMgrTest, getLeases6DuidIaid) {
     testGetLeases6DuidIaid();
 }
 
-// Check that the system can cope with a DUID of allowed size.
-
-/// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type,
-/// const DUID& duid, uint32_t iaid) const is not implemented yet.
+/// @brief Check that the system can cope with a DUID of allowed size.
 TEST_F(MemfileLeaseMgrTest, getLeases6DuidSize) {
     startBackend(V6);
     testGetLeases6DuidSize();
 }
 
+/// @brief Check that the expired DHCPv4 leases can be retrieved.
+///
+/// This test adds a number of leases to the lease database and marks
+/// some of them as expired. Then it queries for expired leases and checks
+/// whether only expired leases are returned, and that they are returned in
+/// the order from most to least expired. It also checks that the lease
+/// which is marked as 'reclaimed' is not returned.
+TEST_F(MemfileLeaseMgrTest, getExpiredLeases4) {
+    startBackend(V4);
+    testGetExpiredLeases4();
+}
+
+/// @brief Check that the expired DHCPv6 leases can be retrieved.
+///
+/// This test adds a number of leases to the lease database and marks
+/// some of them as expired. Then it queries for expired leases and checks
+/// whether only expired leases are returned, and that they are returned in
+/// the order from most to least expired. It also checks that the lease
+/// which is marked as 'reclaimed' is not returned.
+TEST_F(MemfileLeaseMgrTest, getExpiredLeases6) {
+    startBackend(V6);
+    testGetExpiredLeases6();
+}
+
+/// @brief Check that expired reclaimed DHCPv6 leases are removed.
+TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases6) {
+    startBackend(V6);
+    testDeleteExpiredReclaimedLeases6();
+}
+
+/// @brief Check that expired reclaimed DHCPv4 leases are removed.
+TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases4) {
+    startBackend(V4);
+    testDeleteExpiredReclaimedLeases4();
+}
+
 /// @brief Check that getLease6 methods discriminate by lease type.
 ///
 /// Adds six leases, two per lease type all with the same duid and iad but
@@ -975,43 +1008,6 @@ TEST_F(MemfileLeaseMgrTest, testLease6Mac) {
     testLease6MAC();
 }
 
-/// @brief Tests whether memfile is able to work with old CSV file (without mac)
-///
-/// Ticket #3555 introduced MAC address support in Lease6. Instead of developing
-/// an upgrade script, the code is written in a way that allows reading old CSV
-/// (i.e. format that was used in Kea 0.9), hence no upgrade is necessary.
-TEST_F(MemfileLeaseMgrTest, testUpgrade0_9_0_to_0_9_1) {
-
-    // Let's write a CSV file without hwaddr column. Sorry about the long
-    // lines, but nobody was around to whine about 80 columns limit when CSV
-    // format was invented :).
-    string csv_nohwaddr =
-        "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname\n"
-        "2001:db8::1,42:42:42:42:42:42:42:42,3677,127133,73,3600,1,42,0,0,1,myhost.example.com.\n"
-        "2001:db8::2,3a:3a:3a:3a:3a:3a:3a:3a,5412,239979,73,1800,2,89,7,0,0,myhost.example.com.\n"
-        "2001:db8::3,1f:20:21:22:23:24:25:26,7000,241567,37,7200,0,4294967294,28,1,0,myhost.example.com.\n";
-
-    ofstream csv(getLeaseFilePath("leasefile6_0.csv").c_str(), ios::out | ios::trunc);
-    ASSERT_TRUE(csv.is_open());
-    csv << csv_nohwaddr;
-    csv.close();
-
-    startBackend(V6);
-
-    // None of the leases should have any hardware addresses assigned.
-    Lease6Ptr stored1 = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
-    ASSERT_TRUE(stored1);
-    EXPECT_FALSE(stored1->hwaddr_);
-
-    Lease6Ptr stored2 = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
-    ASSERT_TRUE(stored2);
-    EXPECT_FALSE(stored2->hwaddr_);
-
-    Lease6Ptr stored3 = lmptr_->getLease6(leasetype6_[3], ioaddress6_[3]);
-    ASSERT_TRUE(stored3);
-    EXPECT_FALSE(stored3->hwaddr_);
-}
-
 // Check that memfile reports version correctly.
 TEST_F(MemfileLeaseMgrTest, versionCheck) {
 
@@ -1033,22 +1029,22 @@ TEST_F(MemfileLeaseMgrTest, versionCheck) {
 TEST_F(MemfileLeaseMgrTest, load4MultipleLeaseFiles) {
     LeaseFileIO io2(getLeaseFilePath("leasefile4_0.csv.2"));
     io2.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,\n"
-                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,\n");
+                  "fqdn_fwd,fqdn_rev,hostname,state\n"
+                  "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1\n"
+                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,1\n");
 
     LeaseFileIO io1(getLeaseFilePath("leasefile4_0.csv.1"));
     io1.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,\n"
-                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,\n"
-                  "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,\n");
+                  "fqdn_fwd,fqdn_rev,hostname,state\n"
+                  "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,1\n"
+                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,1\n"
+                  "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,1\n");
 
     LeaseFileIO io(getLeaseFilePath("leasefile4_0.csv"));
     io.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                 "fqdn_fwd,fqdn_rev,hostname\n"
-                 "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,\n"
-                 "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,\n");
+                 "fqdn_fwd,fqdn_rev,hostname,state\n"
+                 "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,1\n"
+                 "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,1\n");
 
     startBackend(V4);
 
@@ -1091,27 +1087,27 @@ TEST_F(MemfileLeaseMgrTest, load4MultipleLeaseFiles) {
 TEST_F(MemfileLeaseMgrTest, load4CompletedFile) {
     LeaseFileIO io2(getLeaseFilePath("leasefile4_0.csv.2"));
     io2.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,\n"
-                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,\n");
+                  "fqdn_fwd,fqdn_rev,hostname,state\n"
+                  "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1\n"
+                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,1\n");
 
     LeaseFileIO io1(getLeaseFilePath("leasefile4_0.csv.1"));
     io1.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,\n"
-                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,\n"
-                  "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,\n");
+                  "fqdn_fwd,fqdn_rev,hostname,state\n"
+                  "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,1\n"
+                  "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,1\n"
+                  "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,1\n");
 
     LeaseFileIO io(getLeaseFilePath("leasefile4_0.csv"));
     io.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                 "fqdn_fwd,fqdn_rev,hostname\n"
-                 "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,\n"
-                 "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,\n");
+                 "fqdn_fwd,fqdn_rev,hostname,state\n"
+                 "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,1\n"
+                 "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,1\n");
 
     LeaseFileIO ioc(getLeaseFilePath("leasefile4_0.csv.completed"));
     ioc.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
-                  "fqdn_fwd,fqdn_rev,hostname\n"
-                  "192.0.2.13,ff:ff:ff:ff:ff:ff,,200,200,8,1,1,,\n");
+                  "fqdn_fwd,fqdn_rev,hostname,state\n"
+                  "192.0.2.13,ff:ff:ff:ff:ff:ff,,200,200,8,1,1,,1\n");
 
     startBackend(V4);
 
@@ -1167,29 +1163,32 @@ TEST_F(MemfileLeaseMgrTest, load4LFCInProgress) {
 TEST_F(MemfileLeaseMgrTest, load6MultipleLeaseFiles) {
     LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2"));
     io2.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                  "state\n"
                   "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
-                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "200,200,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
-                  "200,200,8,100,0,7,0,1,1,,\n");
+                  "200,200,8,100,0,7,0,1,1,,,1\n");
 
     LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1"));
     io1.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                  "state\n"
                   "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
-                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "200,200,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
-                  "300,800,8,100,0,7,0,1,1,,\n"
+                  "300,800,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
-                  "200,200,8,100,0,7,0,1,1,,\n");
+                  "200,200,8,100,0,7,0,1,1,,,1\n");
 
     LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
     io.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                 "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                 "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                 "state\n"
                  "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
-                 "400,1000,8,100,0,7,0,1,1,,\n"
+                 "400,1000,8,100,0,7,0,1,1,,,1\n"
                  "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
-                 "200,200,8,100,0,7,0,1,1,,\n");
+                 "200,200,8,100,0,7,0,1,1,,,1\n");
 
     startBackend(V6);
 
@@ -1230,21 +1229,23 @@ TEST_F(MemfileLeaseMgrTest, load6MultipleLeaseFiles) {
 TEST_F(MemfileLeaseMgrTest, load6MultipleNoSecondFile) {
     LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1"));
     io1.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                  "state\n"
                   "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
-                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "200,200,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
-                  "300,800,8,100,0,7,0,1,1,,\n"
+                  "300,800,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
-                  "200,200,8,100,0,7,0,1,1,,\n");
+                  "200,200,8,100,0,7,0,1,1,,,1\n");
 
     LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
     io.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                 "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                 "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                 "state\n"
                  "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
-                 "400,1000,8,100,0,7,0,1,1,,\n"
+                 "400,1000,8,100,0,7,0,1,1,,,1\n"
                  "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
-                 "200,200,8,100,0,7,0,1,1,,\n");
+                 "200,200,8,100,0,7,0,1,1,,,1\n");
 
     startBackend(V6);
 
@@ -1276,19 +1277,21 @@ TEST_F(MemfileLeaseMgrTest, load6MultipleNoSecondFile) {
 TEST_F(MemfileLeaseMgrTest, load6MultipleNoFirstFile) {
     LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2"));
     io2.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                  "state\n"
                   "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
-                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "200,200,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
-                  "200,200,8,100,0,7,0,1,1,,\n");
+                  "200,200,8,100,0,7,0,1,1,,,1\n");
 
     LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
     io.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                 "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                 "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                 "state\n"
                  "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
-                 "400,1000,8,100,0,7,0,1,1,,\n"
+                 "400,1000,8,100,0,7,0,1,1,,,1\n"
                  "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
-                 "200,200,8,100,0,7,0,1,1,,\n");
+                 "200,200,8,100,0,7,0,1,1,,,1\n");
 
     startBackend(V6);
 
@@ -1322,35 +1325,39 @@ TEST_F(MemfileLeaseMgrTest, load6MultipleNoFirstFile) {
 TEST_F(MemfileLeaseMgrTest, load6CompletedFile) {
     LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2"));
     io2.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                  "state\n"
                   "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
-                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "200,200,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
-                  "200,200,8,100,0,7,0,1,1,,\n");
+                  "200,200,8,100,0,7,0,1,1,,,1\n");
 
     LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1"));
     io1.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                  "state\n"
                   "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
-                  "200,200,8,100,0,7,0,1,1,,\n"
+                  "200,200,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
-                  "300,800,8,100,0,7,0,1,1,,\n"
+                  "300,800,8,100,0,7,0,1,1,,,1\n"
                   "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
-                  "200,200,8,100,0,7,0,1,1,,\n");
+                  "200,200,8,100,0,7,0,1,1,,,1\n");
 
     LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
     io.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                 "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                 "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                 "state\n"
                  "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
-                 "400,1000,8,100,0,7,0,1,1,,\n"
+                 "400,1000,8,100,0,7,0,1,1,,,1\n"
                  "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
-                 "200,200,8,100,0,7,0,1,1,,\n");
+                 "200,200,8,100,0,7,0,1,1,,,1\n");
 
     LeaseFileIO ioc(getLeaseFilePath("leasefile6_0.csv.completed"));
     ioc.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
-                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr\n"
+                  "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+                  "state\n"
                   "2001:db8:1::125,ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff,"
-                  "400,1000,8,100,0,7,0,1,1,,\n");
+                  "400,1000,8,100,0,7,0,1,1,,,1\n");
 
     startBackend(V6);