Parcourir la source

[3973] Implemented first version of the lease reclamation routine.

Marcin Siodelski il y a 9 ans
Parent
commit
c8837f49d0

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

@@ -14,8 +14,12 @@
 
 #include <config.h>
 
+#include <dhcp/option_data_types.h>
+#include <dhcp_ddns/ncr_msg.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/alloc_engine_log.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/d2_client_mgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/host.h>
@@ -34,6 +38,7 @@
 
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
 using namespace isc::hooks;
 using namespace isc::stats;
 
@@ -1272,6 +1277,66 @@ AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases
     return (updated_leases);
 }
 
+void
+AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
+                                   const bool remove_lease) {
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+
+    Lease6Collection leases;
+    lease_mgr.getExpiredLeases6(leases, max_leases);
+
+    for (Lease6Collection::const_iterator lease_it = leases.begin();
+         lease_it != leases.end(); ++lease_it) {
+
+        /// @todo execute a lease6_expire hook here
+
+        /// @todo perform DNS update here
+        queueNameChangeRequest(*lease_it, *(*lease_it)->duid_);
+
+        // Reclaim the lease - depending on the configuration, set the
+        // expired-reclaimed state or simply remove it.
+        if (remove_lease) {
+            LeaseMgrFactory::instance().deleteLease((*lease_it)->addr_);
+
+        } else {
+            (*lease_it)->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+            LeaseMgrFactory::instance().updateLease6(*lease_it);
+        }
+    }
+}
+
+template<typename LeasePtrType, typename IdentifierType>
+void
+AllocEngine::queueNameChangeRequest(const LeasePtrType& lease,
+                                    const IdentifierType& identifier) const {
+
+    if (lease->hostname_.empty() || !lease->fqdn_fwd_ || !lease->fqdn_rev_) {
+        return;
+    }
+
+    if (!CfgMgr::instance().getD2ClientMgr().ddnsEnabled()) {
+        return;
+    }
+
+    std::vector<uint8_t> hostname_wire;
+    try {
+        OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true);
+
+    } catch (const std::exception& ex) {
+
+    }
+
+    isc::dhcp_ddns::D2Dhcid dhcid(identifier, hostname_wire);
+    NameChangeRequestPtr ncr;
+    ncr.reset(new NameChangeRequest(isc::dhcp_ddns::CHG_REMOVE,
+                                    lease->fqdn_fwd_, lease->fqdn_rev_,
+                                    lease->hostname_,
+                                    lease->addr_.toText(),
+                                    dhcid, 0, lease->valid_lft_));
+
+    CfgMgr::instance().getD2ClientMgr().sendRequest(ncr);
+}
+
 } // end of isc::dhcp namespace
 } // end of isc namespace
 

+ 19 - 0
src/lib/dhcpsrv/alloc_engine.h

@@ -490,6 +490,21 @@ public:
     /// @return Returns renewed lease.
     Lease6Collection renewLeases6(ClientContext6& ctx);
 
+    /// @brief Reclaims expired leases.
+    ///
+    /// This method retrieves a collection of expired leases and reclaims them.
+    /// See http://kea.isc.org/wiki/LeaseExpirationDesign#LeasesReclamationRoutine
+    /// for the details.
+    ///
+    /// @param max_leases Maximum number of leases to be reclaimed.
+    /// @param timeout Maximum amount of time that the reclaimation routine
+    /// may be processing expired leases, expressed in seconds.
+    /// @param remove_lease A boolean value indicating if the lease should
+    /// be removed when it is reclaimed (if true) or it should be left in the
+    /// database in the "expired-reclaimed" state (if false).
+    void reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
+                               const bool remove_lease);
+
     /// @brief Attempts to find appropriate host reservation.
     ///
     /// Attempts to find appropriate host reservation in HostMgr. If found, it
@@ -663,6 +678,10 @@ private:
     /// @param lease IPv6 lease to be extended.
     void extendLease6(ClientContext6& ctx, Lease6Ptr lease);
 
+    template<typename LeasePtrType, typename IdentifierType>
+    void queueNameChangeRequest(const LeasePtrType& lease,
+                                const IdentifierType& identifier) const;
+
 public:
 
     /// @brief Context information for the DHCPv4 lease allocation.

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

@@ -56,6 +56,7 @@ TESTS += libdhcpsrv_unittests
 libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_utils.cc alloc_engine_utils.h
+libdhcpsrv_unittests_SOURCES += alloc_engine_expiration_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_hooks_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine4_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine6_unittest.cc

+ 641 - 0
src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc

@@ -0,0 +1,641 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <dhcpsrv/tests/test_utils.h>
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/static_assert.hpp>
+#include <iomanip>
+#include <sstream>
+#include <time.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Number of leases to be initialized for each test.
+///
+/// This value is expected by some of the tests to be multiple
+/// of 10.
+const unsigned int TEST_LEASES_NUM = 100;
+
+/// @brief Structure wrapping a lower limit within the collection
+/// of leases.
+///
+/// We're using this structure rather than a size_t value directly
+/// to make API of the test fixture class more readable, i.e. the
+/// struct name indicates the purpose of the value being held.
+struct LowerBound {
+    /// @brief Constructor.
+    ///
+    /// @param lower_bound Interger value wrapped by the structure.
+    explicit LowerBound(const size_t lower_bound)
+        : lower_bound_(lower_bound) { };
+
+    /// @brief Operator returning the size_t value wrapped.
+    operator size_t() const {
+        return (lower_bound_);
+    }
+
+    /// @brief Value wrapped in the structure.
+    size_t lower_bound_;
+};
+
+/// @brief Structure wrapping an upper limit within the collection
+/// of leases.
+///
+/// We're using this structure rather than a size_t value directly
+/// to make API of the test fixture class more readable, i.e. the
+/// struct name indicates the purpose of the value being held.
+struct UpperBound {
+    /// @brief Constructor.
+    ///
+    /// @param lower_bound Interger value wrapped by the structure.
+    explicit UpperBound(const size_t upper_bound)
+        : upper_bound_(upper_bound) { };
+
+    /// @brief Operator returning the size_t value wrapped.
+    operator size_t() const {
+        return (upper_bound_);
+    }
+
+    /// @brief Value wrapped in the structure.
+    size_t upper_bound_;
+};
+
+/// @brief Test fixture class for the lease reclamation routines in the
+/// @c AllocEngine.
+///
+/// This class implements infrastructure for testing leases reclamation
+/// routines. The lease reclamation routine has the following
+/// characteristic:
+/// - it processes multiple leases,
+/// - leases are processed in certain order,
+/// - number of processed leases may be limited by the parameters,
+/// - maxium duration of the lease reclamation routine may be limited,
+/// - reclaimed leases may be marked as reclaimed or deleted,
+/// - DNS records for some of the leases must be removed when the lease
+///   is reclaimed and DNS updates are enabled,
+/// - hooks must be invoked (if installed) for each reclaimed lease
+///
+/// The typical test requires many leases to be initialized and stored
+/// in the lease database for the test. The test fixture class creates
+/// these leases upon construction. It is possible to modify these
+/// leases to test various properties of the lease reclamation routine
+/// as listed above. For example: some of the leases may be marked
+/// as expired or hostname may be cleared for some of the leases to
+/// check that DNS updates are not generated for them.
+///
+/// The tests are built around the
+/// @c ExpirationAllocEngine6Test::testLeases methods. These methods
+/// verify that the certain operations have been performed by the
+/// lease reclamation routine on selected leases. The leases for which
+/// certain conditions should be met are selected using the "index
+/// algorithms". Various index algorithms are implemented in the
+/// test fixture class as static functions and the algorithm is
+/// selected by passing function pointer to the @c testLeases method.
+///
+/// Examples of index algorithms are:
+/// - evenLeaseIndex(index) - picks even index numbers,
+/// - oddLeaseIndex(index) - picks odd index numbers,
+/// - allLeasesIndexes(index) - picks all index number.
+///
+/// For example, the test may use the @c evenLeaseIndex algorithm
+/// to mark leases with even indexes as expired and then test whether
+/// leases with even indexes have been successfully reclaimed.
+///
+/// The "lease algorithm" verifies if the given lease fulfils the
+/// specific conditions after reclamation. These are the examples of
+/// the lease algorithms:
+/// - leaseExists - lease still exists in the database,
+/// - leaseDoesntExist - lease removed from the database,
+/// - leaseReclaimed - lease exists but has reclaimed status,
+/// - leaseNotReclaimed - lease exists and is not in the reclaimed status,
+/// - dnsUpdateGeneratedForLease - DNS updates generated for lease,
+/// - dnsUpdateNotGeneratedForLease - DNS updates not generated for lease
+///
+/// The combination of index algorithm and lease algorithm allows for
+/// verifying that the whole sets of leases in the lease database fulfil
+/// certain conditions. For example, it is possible to verify that
+/// after lease reclamation leases with even indexes have state set to
+/// "expired-reclaimed".
+///
+/// See @c ExpirationAllocEngine6Test::testLeases for further details.
+class ExpirationAllocEngine6Test : public AllocEngine6Test {
+public:
+
+    /// @brief Type definition for the lease algorithm.
+    typedef boost::function<bool (const Lease6Ptr)> LeaseAlgorithmFun;
+    /// @brief type definition for the lease index algorithm.
+    typedef boost::function<bool (const size_t)> IndexAlgorithmFun;
+
+    /// @brief Class constructor.
+    ///
+    /// This constructor initializes @c TEST_LEASES_NUM leases and
+    /// stores them in the lease manager.
+    ExpirationAllocEngine6Test();
+
+    /// @brief Class destructor.
+    ///
+    /// It stops the D2 client, if it is running.
+    virtual ~ExpirationAllocEngine6Test();
+
+    /// @brief Creates collection of leases for a test.
+    ///
+    /// It is called internally at the construction time.
+    void createLeases();
+
+    /// @brief Starts D2 client.
+    void enableDDNS() const;
+
+    /// @brief No-op error handler for the D2 client.
+    static void d2ErrorHandler(const dhcp_ddns::NameChangeSender::Result,
+                               dhcp_ddns::NameChangeRequestPtr&) {
+    }
+
+    /// @brief Marks a lease as expired.
+    ///
+    /// @param lease_index Lease index. Must be between 0 and
+    /// @c TEST_LEASES_NUM.
+    /// @param secs Offset of the expiration time since now. For example
+    /// a value of 2 would set the lease expiration time to 2 seconds ago.
+    void expire(const unsigned int lease_index, const time_t secs);
+
+    /// @brief Test selected leases using the specified algorithms.
+    ///
+    /// This function picks leases from the range of 0 thru
+    /// @c TEST_LEASES_NUM and selects the ones to be verified using the
+    /// specified index algorithm. Selected leases are tested using
+    /// the specified lease algorithm.
+    ///
+    /// @param lease_algorithm Pointer to the lease algorithm function.
+    /// @param index_algorithm Pointer to the index algorithm function.
+    bool testLeases(const LeaseAlgorithmFun& lease_algorithm,
+                    const IndexAlgorithmFun& index_algorithm) const;
+
+
+    /// @brief Test selected leases using the specified algorithms.
+    ///
+    /// This function picks leases from the range of @c lower_bound
+    /// thru @c upper_bound and selects the ones to be verified using the
+    /// specified index algorithm. Selected leases are tested using the
+    /// specified lease algorithm.
+    ///
+    /// @param lease_algorithm Pointer to the lease algorithm function.
+    /// @param index_algorithm Pointer to the index algorithm function.
+    /// @param lower_bound First index in the range.
+    /// @param upper_bound Last + 1 index in the range.
+    bool testLeases(const LeaseAlgorithmFun& lease_algorithm,
+                    const IndexAlgorithmFun& index_algorithm,
+                    const LowerBound& lower_bound,
+                    const UpperBound& upper_bound) const;
+
+    /// @brief Index algorithm selecting even indexes.
+    ///
+    /// @param index Lease index.
+    /// @return true if index is an even number.
+    static bool evenLeaseIndex(const size_t index) {
+        return (index % 2);
+    }
+
+    /// @brief Index algorithm selecting odd indexes.
+    ///
+    /// @param index Lease index.
+    /// @return true if index is an odd number.
+    static bool oddLeaseIndex(const size_t index) {
+        return (!evenLeaseIndex(index));
+    }
+
+    /// @brief Index algorithm selecting all indexes.
+    ///
+    /// @param index Lease index.
+    /// @return true if the index is in the range of [0 .. TEST_LEASES_NUM).
+    static bool allLeaseIndexes(const size_t index) {
+        return (index < TEST_LEASES_NUM);
+    }
+
+    /// @brief Lease algorithm checking if lease exists.
+    ///
+    /// @param lease Pointer to lease.
+    /// @return true if lease pointer is non-null.
+    static bool leaseExists(const Lease6Ptr& lease) {
+        return (static_cast<bool>(lease));
+    }
+
+    /// @brief Lease algorithm checking if lease doesn't exist.
+    ///
+    /// @param lease Pointer to lease.
+    /// @return true if lease pointer is null.
+    static bool leaseDoesntExist(const Lease6Ptr& lease) {
+        return (static_cast<bool>(!lease));
+    }
+
+    /// @brief Lease algorithm checking if lease state is expired-reclaimed.
+    ///
+    /// @param lease Pointer to lease.
+    /// @return true if lease state is "expired-reclaimed".
+    static bool leaseReclaimed(const Lease6Ptr& lease) {
+        return (lease && lease->stateExpiredReclaimed());
+    }
+
+    /// @brief Lease algorithm checking if lease state is not
+    /// expired-reclaimed.
+    ///
+    /// @param lease Pointer to lease.
+    /// @return true if lease state is not "expired-reclaimed".
+    static bool leaseNotReclaimed(const Lease6Ptr& lease) {
+        return (lease && !lease->stateExpiredReclaimed());
+    }
+
+    /// @brief Lease algorithm checking if removal name change request
+    /// has been generated for lease.
+    ///
+    /// @param lease Pointer to lease.
+    /// @return true if NCR has been generated for the lease.
+    static bool dnsUpdateGeneratedForLease(const Lease6Ptr& lease);
+
+    /// @brief Lease algorithm checking if removal name change request
+    /// hasn't been generated for lease.
+    ///
+    /// @param lease Pointer to lease.
+    /// @return true if NCR has not been generated for the lease.
+    static bool dnsUpdateNotGeneratedForLease(const Lease6Ptr& lease);
+
+    /// @brief Collection of leases created at construction time.
+    Lease6Collection leases_;
+
+};
+
+ExpirationAllocEngine6Test::ExpirationAllocEngine6Test()
+    : AllocEngine6Test() {
+    createLeases();
+}
+
+ExpirationAllocEngine6Test::~ExpirationAllocEngine6Test() {
+    // Stop D2 client if running and remove all queued name change
+    // requests.
+    D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    if (mgr.amSending()) {
+        mgr.stopSender();
+        mgr.clearQueue();
+    }
+}
+
+void
+ExpirationAllocEngine6Test::createLeases() {
+    // Create TEST_LEASES_NUM leases.
+    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        // DUID
+        std::ostringstream duid_s;
+        duid_s << "01020304050607" << std::setw(4) << std::setfill('0') << i;
+        DuidPtr duid(new DUID(DUID::fromText(duid_s.str()).getDuid()));
+
+        // Address.
+        std::ostringstream address_s;
+        address_s << "2001:db8:1::" << std::setw(4) << std::setfill('0') << i;
+        IOAddress address(address_s.str());
+
+        // Hostname.
+        std::ostringstream hostname_s;
+        hostname_s << "host" << std::setw(4) << std::setfill('0') << i
+            << ".example.org";
+
+        // Create lease.
+        Lease6Ptr lease(new Lease6(Lease::TYPE_NA, address, duid, 1, 50, 60, 10, 20,
+                                   SubnetID(1), true, true, hostname_s.str()));
+        leases_.push_back(lease);
+        LeaseMgrFactory::instance().addLease(lease);
+    }
+}
+
+void
+ExpirationAllocEngine6Test::enableDDNS() const {
+    // Start DDNS and assign no-op error handler.
+    D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    D2ClientConfigPtr cfg(new D2ClientConfig());
+    cfg->enableUpdates(true);
+    mgr.setD2ClientConfig(cfg);
+    mgr.startSender(boost::bind(&ExpirationAllocEngine6Test::d2ErrorHandler, _1, _2));
+}
+
+void
+ExpirationAllocEngine6Test::expire(const unsigned int lease_index,
+                                   const time_t secs) {
+    ASSERT_GT(leases_.size(), lease_index);
+    // We set the expiration time indirectly by modifying the cltt value.
+    leases_[lease_index]->cltt_ = time(NULL) - secs - leases_[lease_index]->valid_lft_;
+    ASSERT_NO_THROW(LeaseMgrFactory::instance().updateLease6(leases_[lease_index]));
+}
+
+bool
+ExpirationAllocEngine6Test::testLeases(const LeaseAlgorithmFun& lease_algorithm,
+                                       const IndexAlgorithmFun& index_algorithm) const {
+    // No limits are specified, so test all leases in the range of
+    // 0 .. TEST_LEASES_NUM.
+    return (testLeases(lease_algorithm, index_algorithm, LowerBound(0),
+                       UpperBound(TEST_LEASES_NUM)));
+}
+
+bool
+ExpirationAllocEngine6Test::testLeases(const LeaseAlgorithmFun& lease_algorithm,
+                                       const IndexAlgorithmFun& index_algorithm,
+                                       const LowerBound& lower_bound,
+                                       const UpperBound& upper_bound) const {
+    // Select leases between the lower_bound and upper_bound.
+    for (size_t i = lower_bound; i < upper_bound; ++i) {
+        // Get the lease from the lease database.
+        Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(leases_[i]->type_,
+                                                                leases_[i]->addr_);
+        // index_algorithm(i) checks if the lease should be checked.
+        // If so, check if the lease_algorithm indicates that the
+        // lease fulfils a given condition, e.g. is present in the
+        // database. If not, return false.
+        if (index_algorithm(i) && !lease_algorithm(lease)) {
+            return (false);
+        }
+    }
+    // All leases checked, so return true.
+    return (true);
+}
+
+bool
+ExpirationAllocEngine6Test::dnsUpdateGeneratedForLease(const Lease6Ptr& lease) {
+    try {
+        // Iterate over the generated name change requests and try
+        // to find the match with our lease (using IP address). If
+        D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+        for (size_t i = 0; i < mgr.getQueueSize(); ++i) {
+            const NameChangeRequestPtr& ncr = mgr.peekAt(i);
+            // If match found, return true.
+            if (ncr->getIpAddress() == lease->addr_.toText()) {
+                return (true);
+            }
+        }
+    } catch (...) {
+        // If error occurred, treat it as no match.
+        return (false);
+    }
+
+    // All leases checked - no match.
+    return (false);
+}
+
+bool
+ExpirationAllocEngine6Test::dnsUpdateNotGeneratedForLease(const Lease6Ptr& lease) {
+    try {
+        // Iterate over the generated name change requests and try
+        // to find the match with our lease (using IP address). If
+        D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+        for (size_t i = 0; i < mgr.getQueueSize(); ++i) {
+            const NameChangeRequestPtr& ncr = mgr.peekAt(i);
+            // If match found, we treat it as if the test fails
+            // because we expected no NCR.
+            if (ncr->getIpAddress() == lease->addr_.toText()) {
+                return (false);
+            }
+        }
+    } catch (...) {
+        return (false);
+    }
+
+    // No match found, so we're good.
+    return (true);
+}
+
+// This test verifies that the leases can be reclaimed without being removed
+// from the database. In such case, the leases' state is set to
+// "expired-reclaimed".
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeases6UpdateState) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+                                                 100, false)));
+    ASSERT_TRUE(engine);
+
+    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        // Mark leases with even indexes as expired.
+        if (evenLeaseIndex(i)) {
+            // The higher the index, the more expired the lease.
+            expire(i, 10 + i);
+        }
+    }
+
+    // Run leases reclamation routine on all leases. This should result
+    // in setting "expired-reclaimed" state for all leases with even
+    // indexes.
+    ASSERT_NO_THROW(engine->reclaimExpiredLeases6(0, 0, false));
+
+    // Leases with even indexes should be marked as reclaimed.
+    EXPECT_TRUE(testLeases(&leaseReclaimed, &evenLeaseIndex));
+    // Leases with odd indexes shouldn't be marked as reclaimed.
+    EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex));
+
+}
+
+// This test verifies that the reclaimed leases are deleted when requested.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeases6Delete) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+                                                 100, false)));
+    ASSERT_TRUE(engine);
+
+    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        // Mark leases with even indexes as expired.
+        if (evenLeaseIndex(i)) {
+            // The higher the index, the more expired the lease.
+            expire(i, 10 + i);
+        }
+    }
+
+    // Run leases reclamation routine on all leases. This should result
+    // in removal of all leases with even indexes.
+    ASSERT_NO_THROW(engine->reclaimExpiredLeases6(0, 0, true));
+
+    // Leases with odd indexes should be retained and their state
+    // shouldn't be "expired-reclaimed".
+    EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex));
+    // Leases with even indexes should have been removed.
+    EXPECT_TRUE(testLeases(&leaseDoesntExist, &evenLeaseIndex));
+}
+
+// This test verifies that it is possible to specify the limit for the
+// number of reclaimed leases.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeases6Limit) {
+   boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+                                                 100, false)));
+    ASSERT_TRUE(engine);
+
+    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        // Mark all leaes as expired. The higher the index the less
+        // expired the lease.
+        expire(i, 1000 - i);
+    }
+
+    // We will be performing lease reclamation on lease groups of 10.
+    // Hence, it is convenient if the number of test leases is a
+    // multiple of 10.
+    const size_t reclamation_group_size = 10;
+    BOOST_STATIC_ASSERT(TEST_LEASES_NUM % reclamation_group_size == 0);
+
+    // Leases will be reclaimed in groups of 10.
+    for (int i = reclamation_group_size; i < TEST_LEASES_NUM;
+         i += reclamation_group_size) {
+
+        // Reclaim 10 most expired leases out of TEST_LEASES_NUM. Since
+        // leases are ordered from the most expired to the least expired
+        // this should reclaim leases between 0 and 9, then 10 and 19 etc.
+        ASSERT_NO_THROW(engine->reclaimExpiredLeases6(reclamation_group_size, 0,
+                                                      false));
+
+        // Check that leases having all indexes between 0 and 9, 19, 29 etc.
+        // have been reclaimed.
+        EXPECT_TRUE(testLeases(&leaseReclaimed, &allLeaseIndexes,
+                               LowerBound(0), UpperBound(i)))
+            << "check failed for i = " << i;
+
+        // Check that all remaining leases haven't been reclaimed.
+        EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes,
+                               LowerBound(i), UpperBound(TEST_LEASES_NUM)))
+            << "check failed for i = " << i;
+    }
+}
+
+// This test verifies that DNS updates are generated for the leases
+// for which the DNS records exist.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeases6WithDDNS) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+                                                 100, false)));
+    ASSERT_TRUE(engine);
+
+    // DNS must be started for the D2 client to accept NCRs.
+    ASSERT_NO_THROW(enableDDNS());
+
+    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        // Expire all leases with even indexes.
+        if (evenLeaseIndex(i)) {
+            // The higher the index, the more expired the lease.
+            expire(i, 10 + i);
+        }
+    }
+
+    // Reclaim all expired leases.
+    ASSERT_NO_THROW(engine->reclaimExpiredLeases6(0, 0, false));
+
+    // Leases with odd indexes shouldn't be reclaimed.
+    EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex));
+    // Leases with even indexes should be reclaimed.
+    EXPECT_TRUE(testLeases(&leaseReclaimed, &evenLeaseIndex));
+    // DNS updates (removal NCRs) should be generated for leases with even
+    // indexes.
+    EXPECT_TRUE(testLeases(&dnsUpdateGeneratedForLease, &evenLeaseIndex));
+    // DNS updates (removal NCRs) shouldn't be generated for leases with
+    // odd indexes.
+    EXPECT_TRUE(testLeases(&dnsUpdateNotGeneratedForLease, &oddLeaseIndex));
+}
+
+// This test verifies that it is DNS updates are generated only for the
+// reclaimed expired leases. In this case we limit the number of leases
+// reclaimed during a single call to reclamation routine.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeases6WithDDNSAndLimit) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+                                                 100, false)));
+    ASSERT_TRUE(engine);
+
+    // DNS must be started for the D2 client to accept NCRs.
+    ASSERT_NO_THROW(enableDDNS());
+
+    for (int i = 0; i < TEST_LEASES_NUM; ++i) {
+        // Expire only leases with even indexes.
+        if (evenLeaseIndex(i)) {
+            // The higher the index, the more expired the lease.
+            expire(i, 10 + i);
+        }
+    }
+
+    const size_t reclamation_group_size = 10;
+    BOOST_STATIC_ASSERT(TEST_LEASES_NUM % reclamation_group_size == 0);
+
+    // Leases will be reclaimed in groups of 10
+    for (int i = 10; i < TEST_LEASES_NUM;  i += reclamation_group_size) {
+        // Reclaim 10 most expired leases. Note that the leases with the higher
+        // index are more expired. For example, if the TEST_LEASES_NUM is equal
+        // to 100, the most expired lease will be 98, then 96, 94 etc.
+        ASSERT_NO_THROW(engine->reclaimExpiredLeases6(reclamation_group_size, 0,
+                                                      false));
+
+        // After the first iteration the lower bound is 80, because there will
+        // be 10 the most expired leases in this group: 80, 82, 84, 86, 88, 90,
+        // 92, 94, 96, 98. For subsequent iterations accordingly.
+        int reclaimed_lower_bound = TEST_LEASES_NUM - 2 * i;
+        // At some point the lower bound will hit the negative value, which
+        // must be corrected to 0.
+        if (reclaimed_lower_bound < 0) {
+            reclaimed_lower_bound = 0;
+        }
+
+        // Leases between the lower bound calculated above and the upper bound
+        // of all leases, and having even indexes should have been reclaimed.
+        EXPECT_TRUE(testLeases(&leaseReclaimed, &evenLeaseIndex,
+                               LowerBound(reclaimed_lower_bound),
+                               UpperBound(TEST_LEASES_NUM)))
+            << "check failed for i = " << i;
+
+        // For the same leases we should have generated DNS updates (removal NCRs).
+        EXPECT_TRUE(testLeases(&dnsUpdateGeneratedForLease, &evenLeaseIndex,
+                               LowerBound(reclaimed_lower_bound),
+                               UpperBound(TEST_LEASES_NUM)))
+            << "check failed for i = " << i;
+
+        // Leases with odd indexes (falling between the reclaimed ones) shouldn't
+        // have been reclaimed, because they are not expired.
+        EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex,
+                               LowerBound(reclaimed_lower_bound),
+                               UpperBound(TEST_LEASES_NUM)))
+            << "check failed for i = " << i;
+
+        EXPECT_TRUE(testLeases(&dnsUpdateNotGeneratedForLease, &oddLeaseIndex,
+                               LowerBound(reclaimed_lower_bound),
+                               UpperBound(TEST_LEASES_NUM)))
+            << "check failed for i = " << i;
+
+
+        // At early stages of iterations, there should be conitnuous group of leases
+        // (expired and not expired) which haven't been reclaimed.
+        if (reclaimed_lower_bound > 0) {
+            EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes,
+                                   LowerBound(0),
+                                   UpperBound(reclaimed_lower_bound)))
+                << "check failed for i = " << i;
+
+            EXPECT_TRUE(testLeases(&dnsUpdateNotGeneratedForLease, &oddLeaseIndex,
+                                   LowerBound(0), UpperBound(reclaimed_lower_bound)));
+
+        }
+    }
+}
+
+
+
+}; // end of anonymous namespace