]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2780] Implemented FLQ allocator
authorMarcin Siodelski <marcin@isc.org>
Thu, 2 Mar 2023 10:19:37 +0000 (11:19 +0100)
committerMarcin Siodelski <msiodelski@gmail.com>
Wed, 29 Mar 2023 12:43:39 +0000 (14:43 +0200)
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/allocator.h
src/lib/dhcpsrv/flq_allocation_state.cc [new file with mode: 0644]
src/lib/dhcpsrv/flq_allocation_state.h [new file with mode: 0644]
src/lib/dhcpsrv/flq_allocator.cc [new file with mode: 0644]
src/lib/dhcpsrv/flq_allocator.h [new file with mode: 0644]
src/lib/dhcpsrv/tests/Makefile.am
src/lib/dhcpsrv/tests/flq_allocation_state_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/tests/flq_allocator_unittest.cc [new file with mode: 0644]

index db54107759a4cca6776949fe496286693feb278b..d739707c746b0890dee204a9011da8095d3fe046 100644 (file)
@@ -930,6 +930,7 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
         cfg_db->createManagers();
         // Reset counters related to connections as all managers have been recreated.
         srv->getNetworkState()->reset(NetworkState::Origin::DB_CONNECTION);
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->initAllocatorsAfterConfigure();
     } catch (const std::exception& ex) {
         err << "Unable to open database: " << ex.what();
         return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
index 4b39cb0cda70e108dece3f6039ece0b5d2dc57dd..3a259f458f135820c5cd6a62ab4e649372adc7e7 100644 (file)
@@ -107,6 +107,8 @@ libkea_dhcpsrv_la_SOURCES += dhcp4o6_ipc.cc dhcp4o6_ipc.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_exceptions.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_messages.h dhcpsrv_messages.cc
+libkea_dhcpsrv_la_SOURCES += flq_allocation_state.cc flq_allocation_state.h
+libkea_dhcpsrv_la_SOURCES += flq_allocator.cc flq_allocator.h
 libkea_dhcpsrv_la_SOURCES += free_lease_queue.h free_lease_queue.cc
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += host_container.h
index dd82ea8a43ed9e043c68a2114888a09b64f71f5a..f8dcdf253e02ca26dcd862e19e8d063e1d02e5ee 100644 (file)
@@ -212,8 +212,6 @@ protected:
     /// @brief Weak pointer to the subnet owning the allocator.
     WeakSubnetPtr subnet_;
 
-private:
-
     /// @brief The mutex to protect the allocated lease.
     std::mutex mutex_;
 };
diff --git a/src/lib/dhcpsrv/flq_allocation_state.cc b/src/lib/dhcpsrv/flq_allocation_state.cc
new file mode 100644 (file)
index 0000000..7f9fa58
--- /dev/null
@@ -0,0 +1,89 @@
+// Copyright (C) 2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcpsrv/flq_allocation_state.h>
+#include <boost/make_shared.hpp>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+SubnetFreeLeaseQueueAllocationStatePtr
+SubnetFreeLeaseQueueAllocationState::create(const SubnetPtr& subnet) {
+    auto subnet_prefix = subnet->get();
+    return (boost::make_shared<SubnetFreeLeaseQueueAllocationState>());
+}
+
+SubnetFreeLeaseQueueAllocationState::SubnetFreeLeaseQueueAllocationState()
+    : SubnetAllocationState() {
+}
+
+PoolFreeLeaseQueueAllocationStatePtr
+PoolFreeLeaseQueueAllocationState::create(const PoolPtr& pool) {
+    return (boost::make_shared<PoolFreeLeaseQueueAllocationState>(pool->getType()));
+}
+
+PoolFreeLeaseQueueAllocationState::PoolFreeLeaseQueueAllocationState(Lease::Type type)
+    : AllocationState(), free_lease4_queue_(), free_lease6_queue_() {
+    if (type == Lease::TYPE_V4) {
+        free_lease4_queue_ = boost::make_shared<FreeLeaseQueue<uint32_t>>();
+    } else {
+        free_lease6_queue_ = boost::make_shared<FreeLeaseQueue<IOAddress>>();
+    }
+}
+
+bool
+PoolFreeLeaseQueueAllocationState::exhausted() const {
+    return ((free_lease4_queue_ && free_lease4_queue_->empty()) ||
+            (free_lease6_queue_ && free_lease6_queue_->empty()));
+}
+
+void
+PoolFreeLeaseQueueAllocationState::addFreeLease(const asiolink::IOAddress& address) {
+    if (free_lease4_queue_) {
+        free_lease4_queue_->push_back(address.toUint32());
+    } else {
+        free_lease6_queue_->push_back(address);
+    }
+}
+
+void
+PoolFreeLeaseQueueAllocationState::deleteFreeLease(const asiolink::IOAddress& address) {
+    if (free_lease4_queue_) {
+        auto& idx = free_lease4_queue_->get<1>();
+        idx.erase(address.toUint32());
+    } else {
+        auto& idx = free_lease6_queue_->get<1>();
+        idx.erase(address);
+    }
+}
+
+IOAddress
+PoolFreeLeaseQueueAllocationState::offerFreeLease() {
+    if (free_lease4_queue_) {
+        if (free_lease4_queue_->empty()) {
+            return (IOAddress::IPV4_ZERO_ADDRESS());
+        }
+        uint32_t lease = free_lease4_queue_->front();
+        free_lease4_queue_->pop_front();
+        free_lease4_queue_->push_back(lease);
+        return (IOAddress(lease));
+    }
+
+    if (free_lease6_queue_->empty()) {
+        return (IOAddress::IPV6_ZERO_ADDRESS());
+    }
+    IOAddress lease = free_lease6_queue_->front();
+    free_lease6_queue_->pop_front();
+    free_lease6_queue_->push_back(lease);
+    return (lease);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
diff --git a/src/lib/dhcpsrv/flq_allocation_state.h b/src/lib/dhcpsrv/flq_allocation_state.h
new file mode 100644 (file)
index 0000000..22713b6
--- /dev/null
@@ -0,0 +1,124 @@
+// Copyright (C) 2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FLQ_ALLOCATION_STATE_H
+#define FLQ_ALLOCATION_STATE_H
+
+#include <dhcpsrv/allocation_state.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/subnet.h>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration of the @c SubnetFreeLeaseQueueAllocationState.
+class SubnetFreeLeaseQueueAllocationState;
+
+/// @brief Type of the pointer to the @c SubnetFreeLeaseQueueAllocationState.
+typedef boost::shared_ptr<SubnetFreeLeaseQueueAllocationState> SubnetFreeLeaseQueueAllocationStatePtr;
+
+/// @brief Subnet allocation state used by the FLQ allocator.
+class SubnetFreeLeaseQueueAllocationState : public SubnetAllocationState {
+public:
+
+    /// @brief Factory function creating the state instance from subnet.
+    ///
+    /// @param subnet instance of the subnet for which the allocation
+    /// state should be instantiated.
+    /// @return new allocation state instance.
+    static SubnetFreeLeaseQueueAllocationStatePtr create(const SubnetPtr& subnet);
+
+    /// @brief Constructor.
+    SubnetFreeLeaseQueueAllocationState();
+};
+
+/// @brief Forward declaration of the @c PoolFreeLeaseQueueAllocationState.
+class PoolFreeLeaseQueueAllocationState;
+
+/// @brief Type of the pointer to the @c PoolFreeLeaseQueueAllocationState.
+typedef boost::shared_ptr<PoolFreeLeaseQueueAllocationState> PoolFreeLeaseQueueAllocationStatePtr;
+
+/// @brief Pool allocation state used by the FLQ allocator.
+class PoolFreeLeaseQueueAllocationState : public AllocationState {
+public:
+
+    /// @brief Factory function creating the state instance from pool.
+    ///
+    /// @param pool instance of the pool for which the allocation state
+    /// should be instantiated.
+    /// @return new allocation state instance.
+    static PoolFreeLeaseQueueAllocationStatePtr create(const PoolPtr& pool);
+
+    /// @brief Constructor.
+    ///
+    /// Instantiates the allocator for the specified lease type.
+    ///
+    /// @param type lease type.
+    PoolFreeLeaseQueueAllocationState(Lease::Type type);
+
+    /// @brief Checks if the pool has any free leases.
+    ///
+    /// @return true if the pool has no free leases, false otherwise.
+    bool exhausted() const;
+
+    /// @brief Adds a free lease to the queue.
+    ///
+    /// @param address lease address.
+    void addFreeLease(const asiolink::IOAddress& address);
+
+    /// @brief Deletes free lease from the queue.
+    ///
+    /// @param address lease address.
+    void deleteFreeLease(const asiolink::IOAddress& address);
+
+    /// @brief Returns next available lease.
+    ///
+    /// @return next free lease address or IPv4/IPv6 zero address when
+    /// there are no free leases.
+    asiolink::IOAddress offerFreeLease();
+
+private:
+
+    /// @brief A multi-index container holding free leases.
+    ///
+    /// When it is used as a storage for IPv4 leases, the @c AddressType
+    /// should be @c uint32_t. For IPv6 leases, it should be @c IOAddress.
+    /// Note that using the @c uint32_t for the IPv4 case significantly reduces
+    /// the amount of memory occupied by the container by removing the overhead
+    /// of holding the entire IOAddress instance.
+    template<typename AddressType>
+    using FreeLeaseQueue = boost::multi_index_container<
+        AddressType,
+        boost::multi_index::indexed_by<
+            boost::multi_index::sequenced<>,
+            boost::multi_index::hashed_unique<
+                boost::multi_index::identity<AddressType>
+            >
+        >
+    >;
+
+    /// @brief A multi-index container holding free IPv4 leases.
+    typedef boost::shared_ptr<FreeLeaseQueue<uint32_t>> FreeLease4QueuePtr;
+
+    /// @brief A multi-index container holding free IPv6 leases.
+    typedef boost::shared_ptr<FreeLeaseQueue<asiolink::IOAddress>> FreeLease6QueuePtr;
+
+    /// @brief An instance of the multi-index container holding
+    /// free IPv4 leases.
+    FreeLease4QueuePtr free_lease4_queue_;
+
+    /// @brief An instance of the multi-index container holding
+    /// free IPv6 leases.
+    FreeLease6QueuePtr free_lease6_queue_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // FLQ_ALLOCATION_STATE_H
diff --git a/src/lib/dhcpsrv/flq_allocator.cc b/src/lib/dhcpsrv/flq_allocator.cc
new file mode 100644 (file)
index 0000000..633ac31
--- /dev/null
@@ -0,0 +1,349 @@
+// Copyright (C) 2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/addr_utilities.h>
+#include <dhcpsrv/flq_allocator.h>
+#include <dhcpsrv/ip_range_permutation.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <unordered_set>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+/// @brief An owner string used in the callbacks installed in
+/// the lease manager.
+const string FLQ_OWNER = "flq";
+}
+
+namespace isc {
+namespace dhcp {
+
+FreeLeaseQueueAllocator::FreeLeaseQueueAllocator(Lease::Type type, const WeakSubnetPtr& subnet)
+    : Allocator(type, subnet), generator_() {
+    random_device rd;
+    generator_.seed(rd());
+}
+
+IOAddress
+FreeLeaseQueueAllocator::pickAddressInternal(const ClientClasses& client_classes,
+                                             const DuidPtr&,
+                                             const IOAddress&) {
+    auto subnet = subnet_.lock();
+    auto pools = subnet->getPools(pool_type_);
+    if (pools.empty()) {
+        // No pools, no allocation.
+        return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+    }
+    // Let's first iterate over the pools and identify the ones that
+    // meet client class criteria and are not exhausted.
+    std::vector<uint64_t> available;
+    for (auto i = 0; i < pools.size(); ++i) {
+        // Check if the pool is allowed for the client's classes.
+        if (pools[i]->clientSupported(client_classes)) {
+            // Get or create the pool state.
+            auto pool_state = getPoolState(pools[i]);
+            if (!pool_state->exhausted()) {
+                // There are still available addresses in this pool.
+                available.push_back(i);
+            }
+        }
+    }
+    if (available.empty()) {
+        // No pool meets the client class criteria or all are exhausted.
+        return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+    }
+    // Get a random pool from the available ones.
+    auto pool = pools[available[getRandomNumber(available.size()-1)]];
+
+    // Get or create the pool state.
+    auto pool_state = getPoolState(pool);
+
+    // The pool should still offer some leases.
+    auto free_lease = pool_state->offerFreeLease();
+    // It shouldn't happen, but let's be safe.
+    if (!free_lease.isV4Zero() && !free_lease.isV6Zero()) {
+        return (free_lease);
+    }
+    // No address available.
+    return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+IOAddress
+FreeLeaseQueueAllocator::pickPrefixInternal(const ClientClasses& client_classes,
+                                            Pool6Ptr& pool6,
+                                            const DuidPtr&,
+                                            PrefixLenMatchType prefix_length_match,
+                                            const IOAddress&,
+                                            uint8_t hint_prefix_length) {
+    auto subnet = subnet_.lock();
+    auto pools = subnet->getPools(pool_type_);
+    if (pools.empty()) {
+        // No pool, no allocation.
+        return (IOAddress::IPV6_ZERO_ADDRESS());
+    }
+    // Let's first iterate over the pools and identify the ones that
+    // meet client class criteria and are not exhausted.
+    std::vector<uint64_t> available;
+    for (auto i = 0; i < pools.size(); ++i) {
+        // Check if the pool is allowed for the client's classes.
+        if (pools[i]->clientSupported(client_classes)) {
+            if (!Allocator::isValidPrefixPool(prefix_length_match, pools[i],
+                                              hint_prefix_length)) {
+                continue;
+            }
+            // Get or create the pool state.
+            auto pool_state = getPoolState(pools[i]);
+            if (!pool_state->exhausted()) {
+                // There are still available prefixes in this pool.
+                available.push_back(i);
+            }
+        }
+    }
+    if (available.empty()) {
+        // No pool meets the client class criteria or all are exhausted.
+        return (IOAddress::IPV6_ZERO_ADDRESS());
+    }
+    // Get a random pool from the available ones.
+    auto pool = pools[available[getRandomNumber(available.size()-1)]];
+    pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+    if (!pool6) {
+        // Something is gravely wrong here
+        isc_throw(Unexpected, "Wrong type of pool: "
+                  << (pool)->toText()
+                  << " is not Pool6");
+    }
+    // Get or create the pool state.
+    auto pool_state = getPoolState(pool);
+    // The pool should still offer some leases.
+    auto free_lease = pool_state->offerFreeLease();
+    // It shouldn't happen, but let's be safe.
+    if (!free_lease.isV6Zero()) {
+        return (free_lease);
+    }
+    // No prefix available.
+    return (IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+void
+FreeLeaseQueueAllocator::initAfterConfigure() {
+    auto subnet = subnet_.lock();
+    auto pools = subnet->getPools(pool_type_);
+    if (pools.empty()) {
+        // If there are no pools there is nothing to do.
+        return;
+    }
+    Lease4Collection leases4;
+    Lease6Collection leases6;
+    switch (pool_type_) {
+    case Lease::TYPE_V4:
+        leases4 = LeaseMgrFactory::instance().getLeases4(subnet->getID());
+        populateFreeAddressLeases(leases4, pools);
+        break;
+    case Lease::TYPE_NA:
+    case Lease::TYPE_TA:
+        leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
+        populateFreeAddressLeases(leases6, pools);
+        break;
+    case Lease::TYPE_PD:
+        leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
+        populateFreePrefixDelegationLeases(leases6, pools);
+        break;
+    default:
+        ;
+    }
+    // Install the callbacks for lease add, update and delete in the interface manager.
+    // These callbacks will ensure that we have up-to-date free lease queue.
+    auto& lease_mgr = LeaseMgrFactory::instance();
+    lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+                               std::bind(&FreeLeaseQueueAllocator::addLeaseCallback, this,
+                                         std::placeholders::_1,
+                                         std::placeholders::_2));
+    lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+                               std::bind(&FreeLeaseQueueAllocator::updateLeaseCallback, this,
+                                         std::placeholders::_1,
+                                         std::placeholders::_2));
+    lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+                               std::bind(&FreeLeaseQueueAllocator::deleteLeaseCallback, this,
+                                         std::placeholders::_1,
+                                         std::placeholders::_2));
+}
+
+template<typename LeaseCollectionType>
+void
+FreeLeaseQueueAllocator::populateFreeAddressLeases(const LeaseCollectionType& leases, const PoolCollection& pools) {
+    // Let's iterate over the lease queue and index them with the
+    // unordered_set. Also, elminate the expired leases and those
+    // in the expired-reclaimed state.
+    unordered_set<IOAddress, IOAddress::Hash> leased_addresses;
+    for (auto lease : leases) {
+        if ((lease->getType() == pool_type_) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
+            leased_addresses.insert(lease->addr_);
+        }
+    }
+    // For each pool, check if the address is in the leases list.
+    for (auto pool : pools) {
+        // Create the pool permutation so the resulting lease queue is no
+        // particular order.
+        IPRangePermutation perm(AddressRange(pool->getFirstAddress(), pool->getLastAddress()));
+        auto pool_state = getPoolState(pool);
+        auto done = false;
+        while (!done) {
+            auto address = perm.next(done);
+            if (address.isV4Zero() || address.isV6Zero()) {
+                continue;
+            }
+            if (leased_addresses.count(address) == 0) {
+                // No lease for this address, so add it to the free leases queue.
+                pool_state->addFreeLease(address);
+            }
+        }
+    }
+}
+
+void
+FreeLeaseQueueAllocator::populateFreePrefixDelegationLeases(const Lease6Collection& leases, const PoolCollection& pools) {
+    // Let's iterate over the lease queue and index them with the
+    // unordered_set. Also, elminate the expired leases and those
+    // in the expired-reclaimed state.
+    unordered_set<IOAddress, IOAddress::Hash> leased_prefixes;
+    for (auto lease : leases) {
+        if ((lease->getType() == Lease::TYPE_PD) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
+            leased_prefixes.insert(lease->addr_);
+        }
+    }
+    // For each pool, check if the prefix is in the leases list.
+    for (auto pool : pools) {
+        auto pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+        if (!pool6) {
+            continue;
+        }
+        // Create the pool permutation so the resulting lease queue is no
+        // particular order.
+        IPRangePermutation perm(PrefixRange(pool->getFirstAddress(), pool->getLastAddress(), pool6->getLength()));
+        auto pool_state = getPoolState(pool);
+        auto done = false;
+        while (!done) {
+            auto prefix = perm.next(done);
+            if (prefix.isV4Zero() || prefix.isV6Zero()) {
+                continue;
+            }
+            if (leased_prefixes.count(prefix) == 0) {
+                // No lease for this prefix, so add it to the free leases queue.
+                pool_state->addFreeLease(prefix);
+            }
+        }
+    }
+}
+
+SubnetFreeLeaseQueueAllocationStatePtr
+FreeLeaseQueueAllocator::getSubnetState() const {
+    auto subnet = subnet_.lock();
+    if (!subnet->getAllocationState(pool_type_)) {
+        subnet->setAllocationState(pool_type_, SubnetFreeLeaseQueueAllocationState::create(subnet));
+    }
+    return (boost::dynamic_pointer_cast<SubnetFreeLeaseQueueAllocationState>(subnet->getAllocationState(pool_type_)));
+}
+
+PoolFreeLeaseQueueAllocationStatePtr
+FreeLeaseQueueAllocator::getPoolState(const PoolPtr& pool) const {
+    if (!pool->getAllocationState()) {
+        pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool));
+    }
+    return (boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState()));
+}
+
+PoolPtr
+FreeLeaseQueueAllocator::getLeasePool(const LeasePtr& lease) const {
+    auto subnet = subnet_.lock();
+    if (!subnet) {
+        return (PoolPtr());
+    }
+    auto pool = subnet->getPool(pool_type_, lease->addr_, false);
+    return (pool);
+}
+
+void
+FreeLeaseQueueAllocator::addLeaseCallback(LeasePtr lease, bool mt_safe) {
+    if (!mt_safe) {
+        MultiThreadingLock lock(mutex_);
+        addLeaseCallbackInternal(lease);
+        return;
+    }
+    addLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::addLeaseCallbackInternal(LeasePtr lease) {
+    if (lease->expired()) {
+        return;
+    }
+    auto pool = getLeasePool(lease);
+    if (!pool) {
+        return;
+    }
+   getPoolState(pool)->deleteFreeLease(lease->addr_);
+}
+
+void
+FreeLeaseQueueAllocator::updateLeaseCallback(LeasePtr lease, bool mt_safe) {
+    if (!mt_safe) {
+        MultiThreadingLock lock(mutex_);
+        updateLeaseCallbackInternal(lease);
+        return;
+    }
+    updateLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::updateLeaseCallbackInternal(LeasePtr lease) {
+    auto pool = getLeasePool(lease);
+    if (!pool) {
+        return;
+    }
+    auto pool_state = getPoolState(pool);
+    if (lease->stateExpiredReclaimed() || (lease->expired())) {
+        pool_state->addFreeLease(lease->addr_);
+    } else {
+        pool_state->deleteFreeLease(lease->addr_);
+    }
+}
+
+void
+FreeLeaseQueueAllocator::deleteLeaseCallback(LeasePtr lease, bool mt_safe) {
+    if (!mt_safe) {
+        MultiThreadingLock lock(mutex_);
+        deleteLeaseCallbackInternal(lease);
+        return;
+    }
+    deleteLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::deleteLeaseCallbackInternal(LeasePtr lease) {
+    auto pool = getLeasePool(lease);
+    if (!pool) {
+        return;
+    }
+    getPoolState(pool)->addFreeLease(lease->addr_);
+}
+
+uint64_t
+FreeLeaseQueueAllocator::getRandomNumber(uint64_t limit) {
+    // Take the short path if there is only one number to randomize from.
+    if (limit == 0) {
+        return (0);
+    }
+    std::uniform_int_distribution<uint64_t> dist(0, limit);
+    return (dist(generator_));
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/flq_allocator.h b/src/lib/dhcpsrv/flq_allocator.h
new file mode 100644 (file)
index 0000000..794af28
--- /dev/null
@@ -0,0 +1,209 @@
+// Copyright (C) 2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FLQ_ALLOCATOR_H
+#define FLQ_ALLOCATOR_H
+
+#include <dhcpsrv/allocator.h>
+#include <dhcpsrv/flq_allocation_state.h>
+#include <dhcpsrv/lease.h>
+#include <cstdint>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief An allocator maintaining a queue of free leases.
+///
+/// This allocator populates the queue of free leases during the initialization.
+/// It also installs the callbacks in the @c LeaseMgr, to track the subsequent
+/// lease changes. It allows for maintaining the running queue of free leases.
+///
+/// The allocator offers the leases from the queue, minimizing the number of
+/// the allocation engine's attempts to check if some other client is using
+/// the offered lease. Ideally, only one check should suffice. However, when
+/// several servers share a lease database, the collisions may occur because
+/// the servers don't observe each others' allocations.
+///
+/// This allocator should only be used for reasonably small pools due to the
+/// overhead to populate the free leases. A reasonably small pool is an IPv4
+/// pool (including /8) and the prefix delegation pools with similar capacity.
+/// This allocator is not suitable for a typical IPv6 address pool (e.g., /64).
+/// An attempt to populate free leases for such a giant pool would freeze the
+/// server and likely exhaust its memory.
+///
+/// Free leases are populated in a random order.
+class FreeLeaseQueueAllocator : public Allocator {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param type specifies the type of allocated leases.
+    /// @param subnet weak pointer to the subnet owning the allocator.
+    FreeLeaseQueueAllocator(Lease::Type type, const WeakSubnetPtr& subnet);
+
+    /// @brief Performs allocator initialization after server's reconfiguration.
+    ///
+    /// The allocator installs the callbacks in the lease manager to keep track of
+    /// the lease allocations and maintain the free leases queue.
+    virtual void initAfterConfigure();
+
+private:
+
+    /// @brief Populates the queue of free addresses (IPv4 and IPv6).
+    ///
+    /// It adds each address in the subnet pools that does not exist in the
+    /// list of leases to the free leases queue. The addresses are added
+    /// in a random order.
+    ///
+    /// @param lease collection of leases in the database for a subnet.
+    /// @param pools collection of pools in the subnet.
+    /// @tparam LeaseCollectionType Type of the lease collection returned from the
+    /// database (i.e., @c Lease4Collection or @c Lease6Collection).
+    template<typename LeaseCollectionType>
+    void populateFreeAddressLeases(const LeaseCollectionType& leases, const PoolCollection& pools);
+
+    /// @brief Populates the queue of free delegated prefixes.
+    ///
+    /// It adds each delegated prefix in the subnet pools that does not exist in the
+    /// list of leases to the free leases queue. The delegated prefixes are added
+    /// in a random order.
+    ///
+    /// @param lease collection of delegated prefixes in the database for a subnet.
+    /// @param pools collection of prefix delegation pools in the subnet.
+    void populateFreePrefixDelegationLeases(const Lease6Collection& leases, const PoolCollection& pools);
+
+    /// @brief Returns next available address from the queue.
+    ///
+    /// Internal thread-unsafe implementation of the @c pickAddress.
+    ///
+    /// @param client_classes list of classes client belongs to.
+    /// @param duid client DUID (ignored).
+    /// @param hint client hint (ignored).
+    ///
+    /// @return next offered address.
+    virtual asiolink::IOAddress pickAddressInternal(const ClientClasses& client_classes,
+                                                    const DuidPtr& duid,
+                                                    const asiolink::IOAddress& hint);
+
+    /// @brief Returns next available delegated prefix from the queue.
+    ///
+    /// Internal thread-unsafe implementation of the @c pickPrefix.
+    ///
+    /// @param client_classes list of classes client belongs to.
+    /// @param pool the selected pool satisfying all required conditions.
+    /// @param duid Client's DUID.
+    /// @param prefix_length_match type which indicates the selection criteria
+    ///        for the pools relative to the provided hint prefix length
+    /// @param hint Client's hint.
+    /// @param hint_prefix_length the hint prefix length that the client
+    ///        provided. The 0 value means that there is no hint and that any
+    ///        pool will suffice.
+    ///
+    /// @return the next prefix.
+    virtual isc::asiolink::IOAddress
+    pickPrefixInternal(const ClientClasses& client_classes,
+                       Pool6Ptr& pool,
+                       const DuidPtr& duid,
+                       PrefixLenMatchType prefix_length_match,
+                       const isc::asiolink::IOAddress& hint,
+                       uint8_t hint_prefix_length);
+
+    /// @brief Convenience function returning subnet allocation state instance.
+    ///
+    /// It creates a new subnet state instance and assigns it to the subnet
+    /// if it hasn't been initialized.
+    ///
+    /// @return allocation state instance for the subnet.
+    SubnetFreeLeaseQueueAllocationStatePtr getSubnetState() const;
+
+    /// @brief Convenience function returning pool allocation state instance.
+    ///
+    /// It creates a new pool state instance and assigns it to the pool
+    /// if it hasn't been initialized.
+    ///
+    /// @param pool pool instance.
+    /// @return allocation state instance for the pool.
+    PoolFreeLeaseQueueAllocationStatePtr getPoolState(const PoolPtr& pool) const;
+
+    /// @brief Returns a pool in the subnet the lease belongs to.
+    ///
+    /// This function is used in the interface manager callbacks to find
+    /// a pool for a lease modified in the database.
+    ///
+    /// @param lease lease instance for which the pool should be returned.
+    /// @return A pool found for a lease or null pointer if such a pool does
+    /// not exist.
+    PoolPtr getLeasePool(const LeasePtr& lease) const;
+
+    /// @brief Thread safe callback for adding a lease.
+    ///
+    /// Removes the lease from the free lease queue.
+    ///
+    /// @param lease added lease.
+    /// @param mt_safe a boolean flag indicating if the callback
+    /// has been invoked in the MT-safe context.
+    void addLeaseCallback(LeasePtr lease, bool mt_safe);
+
+    /// @brief Thread unsafe callback for adding a lease.
+    ///
+    /// Removes the lease from the free lease queue.
+    ///
+    /// @param lease added lease.
+    void addLeaseCallbackInternal(LeasePtr lease);
+
+    /// @brief Thread safe callback for updating a lease.
+    ///
+    /// If the lease is reclaimed in this update it is added to the
+    /// free lease queue. If the lease is valid after the update,
+    /// the lease is removed from the free lease queue, if exists.
+    ///
+    /// @param lease updated lease.
+    /// @param mt_safe a boolean flag indicating if the callback
+    /// has been invoked in the MT-safe context.
+    void updateLeaseCallback(LeasePtr lease, bool mt_safe);
+
+    /// @brief Thread unsafe callback for updating a lease.
+    ///
+    /// If the lease is reclaimed in this update it is added to the
+    /// free lease queue. If the lease is valid after the update,
+    /// the lease is removed from the free lease queue, if exists.
+    ///
+    /// @param lease updated lease.
+    void updateLeaseCallbackInternal(LeasePtr lease);
+
+    /// @brief Thread safe callback for deleting a lease.
+    ///
+    /// Adds the lease to the free lease queue.
+    ///
+    /// @param lease deleted lease.
+    /// @param mt_safe a boolean flag indicating if the callback
+    /// has been invoked in the MT-safe context.
+    void deleteLeaseCallback(LeasePtr lease, bool mt_safe);
+
+    /// @brief Thread unsafe callback for updating a lease.
+    ///
+    /// Adds the lease to the free lease queue.
+    ///
+    /// @param lease deleted lease.
+    void deleteLeaseCallbackInternal(LeasePtr lease);
+
+    /// @brief Convenience function returning a random number.
+    ///
+    /// It is used internally by the @c pickAddressInternal and @c pickPrefixInternal
+    /// functions to select a random pool.
+    ///
+    /// @param limit upper bound of the range.
+    /// @returns random number between 0 and limit.
+    uint64_t getRandomNumber(uint64_t limit);
+
+    /// @brief Random generator used by this class.
+    std::mt19937 generator_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // FLQ_ALLOCATOR_H
index f7228441baee75a3fa967cd9b4acfc74560fcc86..36217d8c6b91fd06b020e21c17d6e0e190631cf1 100644 (file)
@@ -90,6 +90,8 @@ libdhcpsrv_unittests_SOURCES += dhcp_queue_control_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp4o6_ipc_unittest.cc
 libdhcpsrv_unittests_SOURCES += duid_config_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += flq_allocation_state_unittest.cc
+libdhcpsrv_unittests_SOURCES += flq_allocator_unittest.cc
 libdhcpsrv_unittests_SOURCES += free_lease_queue_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_cache_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_data_source_factory_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/flq_allocation_state_unittest.cc b/src/lib/dhcpsrv/tests/flq_allocation_state_unittest.cc
new file mode 100644 (file)
index 0000000..273e071
--- /dev/null
@@ -0,0 +1,171 @@
+// Copyright (C) 2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcpsrv/flq_allocation_state.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/pool.h>
+#include <testutils/multi_threading_utils.h>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+// Test creating a new free lease queue allocation state for an IPv4
+// address pool.
+TEST(PoolFreeLeaseAllocationState, createV4) {
+    auto pool = boost::make_shared<Pool4>(IOAddress("192.0.2.1"), IOAddress("192.0.2.10"));
+    auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+    ASSERT_TRUE(state);
+    EXPECT_TRUE(state->exhausted());
+}
+
+// Test adding and deleting free IPv4 leases.
+TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeaseV4) {
+    auto pool = boost::make_shared<Pool4>(IOAddress("192.0.2.1"), IOAddress("192.0.2.10"));
+    auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+    ASSERT_TRUE(state);
+
+    state->addFreeLease(IOAddress("192.0.2.1"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+
+    state->deleteFreeLease(IOAddress("192.0.2.2"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+
+    state->deleteFreeLease(IOAddress("192.0.2.1"));
+    EXPECT_TRUE(state->exhausted());
+    EXPECT_TRUE(state->offerFreeLease().isV4Zero());
+}
+
+// Test that duplicate leases are not added to the queue.
+TEST(PoolFreeLeaseAllocationState, addFreeLeaseV4SeveralTimes) {
+    auto pool = boost::make_shared<Pool4>(IOAddress("192.0.2.1"), IOAddress("192.0.2.10"));
+    auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+    ASSERT_TRUE(state);
+
+    // Add the free lease for the first time.
+    state->addFreeLease(IOAddress("192.0.2.1"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+
+    // Add the same lease the second time. The second lease instance should
+    // not be inserted.
+    state->addFreeLease(IOAddress("192.0.2.1"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+
+    // Delete the sole lease and ensure there are no more leases.
+    state->deleteFreeLease(IOAddress("192.0.2.1"));
+    EXPECT_TRUE(state->exhausted());
+}
+
+
+// Test creating a new free lease queue allocation state for an IPv6
+// address pool.
+TEST(PoolFreeLeaseAllocationState, createNA) {
+    auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1::"),
+                                          IOAddress("2001:db8:1::10"));
+    auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+    ASSERT_TRUE(state);
+    EXPECT_TRUE(state->exhausted());
+}
+
+// Test adding and deleting free IPv6 address leases.
+TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeaseNA) {
+    auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1::"),
+                                          IOAddress("2001:db8:1::10"));
+    auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+    ASSERT_TRUE(state);
+
+    state->addFreeLease(IOAddress("2001:db8:1::1"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
+
+    state->deleteFreeLease(IOAddress("2001:db8:1::2"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
+
+    state->deleteFreeLease(IOAddress("2001:db8:1::1"));
+    EXPECT_TRUE(state->exhausted());
+    EXPECT_TRUE(state->offerFreeLease().isV6Zero());
+}
+
+// Test that duplicate leases are not added to the queue.
+TEST(PoolFreeLeaseAllocationState, addFreeLeasNASeveralTimes) {
+    auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1::"),
+                                          IOAddress("2001:db8:1::10"));
+    auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+    ASSERT_TRUE(state);
+
+    // Add the free lease for the first time.
+    state->addFreeLease(IOAddress("2001:db8:1::5"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("2001:db8:1::5", state->offerFreeLease().toText());
+
+    // Add the same lease the second time. The second lease instance should
+    // not be inserted.
+    state->addFreeLease(IOAddress("2001:db8:1::5"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("2001:db8:1::5", state->offerFreeLease().toText());
+
+    // Delete the sole lease and ensure there are no more leases.
+    state->deleteFreeLease(IOAddress("2001:db8:1::5"));
+    EXPECT_TRUE(state->exhausted());
+}
+
+// Test creating a new free lease queue allocation state for a
+// delegated prefix pool.
+TEST(PoolFreeLeaseAllocationState, createPD) {
+    auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("3000::"), 112, 120);
+    auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+    ASSERT_TRUE(state);
+    EXPECT_TRUE(state->exhausted());
+
+    state->addFreeLease(IOAddress("3000::5600"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+
+    state->deleteFreeLease(IOAddress("3000::6400"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+
+    state->deleteFreeLease(IOAddress("3000::5600"));
+    EXPECT_TRUE(state->exhausted());
+    EXPECT_TRUE(state->offerFreeLease().isV6Zero());
+}
+
+// Test that duplicate leases are not added to the queue.
+TEST(PoolFreeLeaseAllocationState, addFreeLeasPDSeveralTimes) {
+    auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("3000::"), 112, 120);
+    auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+    ASSERT_TRUE(state);
+
+    // Add the free lease for the first time.
+    state->addFreeLease(IOAddress("3000::5600"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+
+    // Add the same lease the second time. The second lease instance should
+    // not be inserted.
+    state->addFreeLease(IOAddress("3000::5600"));
+    EXPECT_FALSE(state->exhausted());
+    EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+
+    // Delete the sole lease and ensure there are no more leases.
+    state->deleteFreeLease(IOAddress("3000::5600"));
+    EXPECT_TRUE(state->exhausted());
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc b/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc
new file mode 100644 (file)
index 0000000..86a17c1
--- /dev/null
@@ -0,0 +1,998 @@
+// Copyright (C) 2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/flq_allocator.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test fixture class for the DHCPv4 Free Lease Queue allocator.
+class FreeLeaseQueueAllocatorTest4 : public AllocEngine4Test {
+public:
+
+    /// @brief Creates a DHCPv4 lease for an address and MAC address.
+    ///
+    /// @param address Lease address.
+    /// @param hw_address_seed a seed from which the MAC address is generated.
+    /// @return Created lease pointer.
+    Lease4Ptr
+    createLease4(const IOAddress& address, uint64_t hw_address_seed) const {
+        vector<uint8_t> hw_address_vec(6);
+        for (auto i = 0; i < sizeof(hw_address_seed); ++i) {
+            hw_address_vec[i] = hw_address_seed >> sizeof(hw_address_seed-1-i) & 0xFF;
+        }
+        auto hw_address = boost::make_shared<HWAddr>(hw_address_vec, HTYPE_ETHER);
+        auto lease = boost::make_shared<Lease4>(address, hw_address, ClientIdPtr(),
+                                                3600, time(0), subnet_->getID());
+        return (lease);
+    }
+};
+
+// Test populating free DHCPv4 leases to the queue.
+TEST_F(FreeLeaseQueueAllocatorTest4, populateFreeAddressLeases) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.100"), 0))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.102"), 1))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.104"), 2))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.106"), 3))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.108"), 4))));
+
+    EXPECT_NO_THROW(alloc.initAfterConfigure());
+
+    auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool_->getAllocationState());
+    ASSERT_TRUE(pool_state);
+    EXPECT_FALSE(pool_state->exhausted());
+
+    std::set<IOAddress> addresses;
+    for (auto i = 0; i < 5; ++i) {
+        auto lease = pool_state->offerFreeLease();
+        ASSERT_FALSE(lease.isV4Zero());
+        addresses.insert(lease);
+    }
+    ASSERT_EQ(5, addresses.size());
+    EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.101")));
+    EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.103")));
+    EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.105")));
+    EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.107")));
+    EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.109")));
+}
+
+// Test allocating IPv4 addresses when a subnet has a single pool.
+TEST_F(FreeLeaseQueueAllocatorTest4, singlePool) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    // Remember returned addresses, so we can verify that unique addresses
+    // are returned.
+    std::set<IOAddress> addresses;
+    for (auto i = 0; i < 1000; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+        addresses.insert(candidate);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+    }
+    // The pool comprises 10 addresses. All should be returned.
+    EXPECT_EQ(10, addresses.size());
+}
+
+// Test allocating IPv4 addresses and re-allocating these that are
+// deleted (released).
+TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithAllocations) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    // Remember returned addresses, so we can verify that unique addresses
+    // are returned.
+    std::map<IOAddress, Lease4Ptr> leases;
+    for (auto i = 0; i < 10; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+        auto lease = createLease4(candidate, i);
+        leases[candidate] = lease;
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+    // The pool comprises 10 addresses. All should be returned.
+    EXPECT_EQ(10, leases.size());
+
+    IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+    EXPECT_TRUE(candidate.isV4Zero());
+
+    auto i = 0;
+    for (auto address_lease : leases) {
+        if (i % 2) {
+            EXPECT_TRUE(lease_mgr.deleteLease(address_lease.second));
+        }
+        ++i;
+    }
+
+    for (auto i = 0; i < 5; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+        auto lease = createLease4(candidate, i);
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+
+    candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+    EXPECT_TRUE(candidate.isV4Zero());
+}
+
+// Test allocating IPv4 addresses and re-allocating these that are
+// reclaimed.
+TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithReclamations) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    // Remember returned addresses, so we can verify that unique addresses
+    // are returned.
+    std::map<IOAddress, Lease4Ptr> leases;
+    for (auto i = 0; i < 10; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+        auto lease = createLease4(candidate, i);
+        leases[candidate] = lease;
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+    // The pool comprises 10 addresses. All should be returned.
+    EXPECT_EQ(10, leases.size());
+
+    IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+    EXPECT_TRUE(candidate.isV4Zero());
+
+    auto i = 0;
+    for (auto address_lease : leases) {
+        if (i % 2) {
+            auto lease = address_lease.second;
+            lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+            EXPECT_NO_THROW(lease_mgr.updateLease4(lease));
+        }
+        ++i;
+    }
+    for (auto i = 0; i < 5; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+        auto lease = createLease4(candidate, i);
+        EXPECT_NO_THROW(lease_mgr.updateLease4(lease));
+    }
+
+    candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+    EXPECT_TRUE(candidate.isV4Zero());
+}
+
+// Test allocating DHCPv4 leases for many pools in a subnet.
+TEST_F(FreeLeaseQueueAllocatorTest4, manyPools) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+    // Add several more pools.
+    for (int i = 1; i < 10; ++i) {
+        stringstream min, max;
+        min << "192.0.2." << i * 10;
+        max << "192.0.2." << i * 10 + 9;
+        auto pool = boost::make_shared<Pool4>(IOAddress(min.str()),
+                                              IOAddress(max.str()));
+        subnet_->addPool(pool);
+    }
+
+    // There are ten pools with 10 addresses each.
+    int total = 100;
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    std::set<IOAddress> addresses_set;
+    std::vector<IOAddress> addresses_vector;
+    std::vector<PoolPtr> pools_vector;
+
+    // Pick random addresses the number of times equal to the
+    // subnet capacity to ensure that all addresses are returned.
+    for (auto i = 0; i < total; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+        addresses_set.insert(candidate);
+        addresses_vector.push_back(candidate);
+        auto lease = createLease4(candidate, i);
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+        pools_vector.push_back(subnet_->getPool(Lease::TYPE_V4, candidate));
+    }
+    // Make sure that unique addresses have been returned.
+    EXPECT_EQ(total, addresses_set.size());
+
+    // Verify that the addresses are returned in the random order.
+    // Count how many times we found consecutive addresses. It should
+    // be 0 or close to 0.
+    int consecutive_addresses = 0;
+    for (auto k = 0; k < addresses_vector.size()-1; ++k) {
+        if (addresses_vector[k].toUint32() == addresses_vector[k+1].toUint32()-1) {
+            ++consecutive_addresses;
+        }
+    }
+    // Ideally, the number of consecutive occurrences should be 0 but we
+    // allow some to make sure the test doesn't fall over sporadically.
+    EXPECT_LT(consecutive_addresses, addresses_vector.size()/4);
+
+    // Repeat similar check for pools. The pools should be picked in the
+    // random order too.
+    int consecutive_pools = 0;
+    for (auto k = 0; k < pools_vector.size()-1; ++k) {
+        // Check if the pools are adjacent (i.e., last address of the
+        // previous pool is a neighbor of the first address of the next
+        // pool).
+        if (pools_vector[k]->getLastAddress().toUint32()+1 ==
+            pools_vector[k+1]->getFirstAddress().toUint32()) {
+            ++consecutive_pools;
+        }
+    }
+    EXPECT_LT(consecutive_pools, pools_vector.size()/2);
+}
+
+// Test that the allocator returns a zero address when there are no pools
+// in a subnet.
+TEST_F(FreeLeaseQueueAllocatorTest4, noPools) {
+   FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+   subnet_->delPools(Lease::TYPE_V4);
+
+   IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+   EXPECT_TRUE(candidate.isV4Zero());
+}
+
+// Test that the allocator respects client class guards.
+TEST_F(FreeLeaseQueueAllocatorTest4, clientClasses) {
+   FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+   // First pool only allows the client class foo.
+   pool_->allowClientClass("foo");
+
+   // Second pool. It only allows client class bar.
+   auto pool1 = boost::make_shared<Pool4>(IOAddress("192.0.2.120"),
+                                         IOAddress("192.0.2.129"));
+   pool1->allowClientClass("bar");
+   subnet_->addPool(pool1);
+
+   // Third pool. It only allows client class foo.
+   auto pool2 = boost::make_shared<Pool4>(IOAddress("192.0.2.140"),
+                                          IOAddress("192.0.2.149"));
+   pool2->allowClientClass("foo");
+   subnet_->addPool(pool2);
+
+   // Forth pool. It only allows client class bar.
+   auto pool3 = boost::make_shared<Pool4>(IOAddress("192.0.2.160"),
+                                          IOAddress("192.0.2.169"));
+   pool3->allowClientClass("bar");
+   subnet_->addPool(pool3);
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+   // Remember offered addresses.
+   std::set<IOAddress> addresses_set;
+
+   // Simulate client's request belonging to the class bar.
+   cc_.insert("bar");
+   for (auto i = 0; i < 20; ++i) {
+       // Allocate random addresses and make sure they belong to the
+       // pools associated with the class bar.
+       IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+       EXPECT_FALSE(candidate.isV4Zero());
+       EXPECT_TRUE(lease_mgr.addLease(createLease4(candidate, i+50)));
+       addresses_set.insert(candidate);
+       EXPECT_TRUE(pool1->inRange(candidate) || pool3->inRange(candidate));
+   }
+   EXPECT_EQ(20, addresses_set.size());
+
+   addresses_set.clear();
+
+   // Simulate the case that the client also belongs to the class foo.
+   // All pools should now be available.
+   cc_.insert("foo");
+   for (auto i = 0; i < 20; ++i) {
+       IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+       addresses_set.insert(candidate);
+       EXPECT_TRUE(lease_mgr.addLease(createLease4(candidate, i+100)));
+       EXPECT_TRUE(subnet_->inRange(candidate));
+   }
+   EXPECT_EQ(20, addresses_set.size());
+
+   // When the client does not belong to any client class the allocator
+   // can't offer any address to the client.
+   cc_.clear();
+   IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+   EXPECT_TRUE(candidate.isV4Zero());
+}
+
+/// @brief Test fixture class for the DHCPv6 Free Lease Queue allocator.
+class FreeLeaseQueueAllocatorTest6 : public AllocEngine6Test {
+public:
+
+    /// @brief Creates a DHCPv6 lease for an address and DUID.
+    ///
+    /// @param type lease type.
+    /// @param address Lease address.
+    /// @param duid_seed a seed from which the DUID is generated.
+    /// @return Created lease pointer.
+    Lease6Ptr
+    createLease6(Lease::Type type, const IOAddress& address, uint64_t duid_seed) const {
+        vector<uint8_t> duid_vec(sizeof(duid_seed));
+        for (auto i = 0; i < sizeof(duid_seed); ++i) {
+            duid_vec[i] = duid_seed >> sizeof(duid_seed-1-i) & 0xFF;
+        }
+        auto duid = boost::make_shared<DUID>(duid_vec);
+        auto lease = boost::make_shared<Lease6>(type, address, duid, 1, 1800,
+                                                3600, subnet_->getID());
+        return (lease);
+    }
+
+};
+
+// Test populating free DHCPv6 address leases to the queue.
+TEST_F(FreeLeaseQueueAllocatorTest6, populateFreeAddressLeases) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::10"), 0))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::12"), 1))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::14"), 2))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::16"), 3))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::18"), 4))));
+
+    EXPECT_NO_THROW(alloc.initAfterConfigure());
+
+    auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool_->getAllocationState());
+    ASSERT_TRUE(pool_state);
+    EXPECT_FALSE(pool_state->exhausted());
+
+    std::set<IOAddress> addresses;
+    for (auto i = 0; i < 12; ++i) {
+        auto lease = pool_state->offerFreeLease();
+        ASSERT_FALSE(lease.isV6Zero());
+        addresses.insert(lease);
+    }
+    ASSERT_EQ(12, addresses.size());
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::11")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::13")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::15")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::17")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::19")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1a")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1b")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1c")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1d")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1e")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1f")));
+    EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::20")));
+}
+
+// Test allocating IPv6 addresses when a subnet has a single pool.
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePool) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    // Remember returned addresses, so we can verify that unique addresses
+    // are returned.
+    std::set<IOAddress> addresses;
+    for (auto i = 0; i < 1000; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+        EXPECT_FALSE(candidate.isV6Zero());
+        addresses.insert(candidate);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+    }
+    // The pool comprises 17 addresses. All should be returned.
+    EXPECT_EQ(17, addresses.size());
+}
+
+// Test allocating IPv6 addresses and re-allocating these that are
+// deleted (released).
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePoolWithAllocations) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    // Remember returned addresses, so we can verify that unique addresses
+    // are returned.
+    std::map<IOAddress, Lease6Ptr> leases;
+    for (auto i = 0; i < 17; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+        EXPECT_FALSE(candidate.isV6Zero());
+        auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+        leases[candidate] = lease;
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+    // The pool comprises 17 addresses. All should be returned.
+    EXPECT_EQ(17, leases.size());
+
+    IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+    EXPECT_TRUE(candidate.isV6Zero());
+
+    auto i = 0;
+    for (auto address_lease : leases) {
+        if (i % 2) {
+            EXPECT_TRUE(lease_mgr.deleteLease(address_lease.second));
+        }
+        ++i;
+    }
+
+    for (auto i = 0; i < 8; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+        auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+
+    candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+    EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test allocating IPv6 addresses and re-allocating these that are
+// reclaimed.
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePoolWithReclamations) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    // Remember returned addresses, so we can verify that unique addresses
+    // are returned.
+    std::map<IOAddress, Lease6Ptr> leases;
+    for (auto i = 0; i < 17; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+        EXPECT_FALSE(candidate.isV6Zero());
+        auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+        leases[candidate] = lease;
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+    // The pool comprises 17 addresses. All should be returned.
+    EXPECT_EQ(17, leases.size());
+
+    IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+    EXPECT_TRUE(candidate.isV6Zero());
+
+    auto i = 0;
+    for (auto address_lease : leases) {
+        if (i % 2) {
+            auto lease = address_lease.second;
+            lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+            EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
+        }
+        ++i;
+    }
+
+    for (auto i = 0; i < 8; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+        auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+        EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
+    }
+
+    candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+    EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test allocating DHCPv6 leases for many pools in a subnet.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPools) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+
+    // Add several more pools.
+    for (int i = 2; i < 10; ++i) {
+        stringstream min, max;
+        min << "2001:db8:1::" << hex << i * 16 + 1;
+        max << "2001:db8:1::" << hex << i * 16 + 16;
+        auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA,
+                                              IOAddress(min.str()),
+                                              IOAddress(max.str()));
+        subnet_->addPool(pool);
+    }
+
+    // First pool (::10 - ::20) has 17 addresses.
+    // There are 8 extra pools with 16 addresses in each.
+    int total = 17 + 8 * 16;
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    std::set<IOAddress> addresses_set;
+    std::vector<IOAddress> addresses_vector;
+    std::vector<PoolPtr> pools_vector;
+
+    // Pick random addresses the number of times equal to the
+    // subnet capacity to ensure that all addresses are returned.
+    for (auto i = 0; i < total; ++i) {
+        IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+        addresses_set.insert(candidate);
+        addresses_vector.push_back(candidate);
+        auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+        pools_vector.push_back(subnet_->getPool(Lease::TYPE_NA, candidate));
+    }
+    // Make sure that unique addresses have been returned.
+    EXPECT_EQ(total, addresses_set.size());
+
+    // Verify that the addresses are returned in the random order.
+    // Count how many times we found consecutive addresses. It should
+    // be 0 or close to 0.
+    int consecutive_addresses = 0;
+    for (auto k = 0; k < addresses_vector.size()-1; ++k) {
+        if (IOAddress::increase(addresses_vector[k]) == addresses_vector[k+1]) {
+            ++consecutive_addresses;
+        }
+    }
+    // Ideally, the number of consecutive occurrences should be 0 but we
+    // allow some to make sure the test doesn't fall over sporadically.
+    EXPECT_LT(consecutive_addresses, addresses_vector.size()/4);
+
+    // Repeat similar check for pools. The pools should be picked in the
+    // random order too.
+    int consecutive_pools = 0;
+    for (auto k = 0; k < pools_vector.size()-1; ++k) {
+        if (IOAddress::increase(pools_vector[k]->getLastAddress()) ==
+            pools_vector[k]->getFirstAddress()) {
+            ++consecutive_pools;
+        }
+    }
+    EXPECT_LT(consecutive_pools, pools_vector.size()/2);
+}
+
+// Test that the allocator returns a zero address when there are no pools
+// in a subnet.
+TEST_F(FreeLeaseQueueAllocatorTest6, noPools) {
+   FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+
+   subnet_->delPools(Lease::TYPE_NA);
+
+   IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+   EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test that the allocator respects client class guards.
+TEST_F(FreeLeaseQueueAllocatorTest6, clientClasses) {
+   FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+
+   // First pool only allows the client class foo.
+   pool_->allowClientClass("foo");
+
+   // Second pool. It only allows client class bar.
+   auto pool1 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+                                          IOAddress("2001:db8:1::120"),
+                                          IOAddress("2001:db8:1::129"));
+   pool1->allowClientClass("bar");
+   subnet_->addPool(pool1);
+
+   // Third pool. It only allows client class foo.
+   auto pool2 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+                                          IOAddress("2001:db8:1::140"),
+                                          IOAddress("2001:db8:1::149"));
+   pool2->allowClientClass("foo");
+   subnet_->addPool(pool2);
+
+   // Forth pool. It only allows client class bar.
+   auto pool3 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+                                          IOAddress("2001:db8:1::160"),
+                                          IOAddress("2001:db8:1::169"));
+   pool3->allowClientClass("bar");
+   subnet_->addPool(pool3);
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+   // When the client does not belong to any client class the allocator
+   // can't offer any address to the client.
+   IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+   EXPECT_TRUE(candidate.isV6Zero());
+
+   // Remember offered addresses.
+   std::set<IOAddress> addresses_set;
+
+   // Simulate client's request belonging to the class bar.
+   cc_.insert("bar");
+   for (auto i = 0; i < 20; ++i) {
+       // Allocate random addresses and make sure they belong to the
+       // pools associated with the class bar.
+       IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+       EXPECT_FALSE(candidate.isV6Zero());
+       EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_NA, candidate, i+50)));
+       addresses_set.insert(candidate);
+       EXPECT_TRUE(pool1->inRange(candidate) || pool3->inRange(candidate));
+   }
+   EXPECT_EQ(20, addresses_set.size());
+
+   // Simulate the case that the client also belongs to the class foo.
+   // All pools should now be available.
+   cc_.insert("foo");
+   for (auto i = 0; i < 27; ++i) {
+       IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+       addresses_set.insert(candidate);
+       EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_NA, candidate, i+100)));
+       EXPECT_TRUE(subnet_->inRange(candidate));
+   }
+   EXPECT_EQ(47, addresses_set.size());
+}
+
+// Test populating free DHCPv6 prefix leases to the queue.
+TEST_F(FreeLeaseQueueAllocatorTest6, populateFreePrefixDelegationLeases) {
+    subnet_->delPools(Lease::TYPE_PD);
+
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+    auto pool = Pool6::create(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 112, 120);
+    subnet_->addPool(pool);
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 0))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::1000"), 1))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::2000"), 2))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::3000"), 3))));
+    EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::4000"), 4))));
+
+    EXPECT_NO_THROW(alloc.initAfterConfigure());
+
+    auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState());
+    ASSERT_TRUE(pool_state);
+    EXPECT_FALSE(pool_state->exhausted());
+
+    std::set<IOAddress> addresses;
+    for (auto i = 0; i < 256; ++i) {
+        auto lease = pool_state->offerFreeLease();
+        ASSERT_FALSE(lease.isV6Zero());
+        addresses.insert(lease);
+    }
+    ASSERT_EQ(251, addresses.size());
+    EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::")));
+    EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::1000")));
+    EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::2000")));
+    EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::3000")));
+    EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::4000")));
+}
+
+// Test allocating delegated prefixes when a subnet has a single pool.
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPool) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    Pool6Ptr pool;
+
+    // Remember returned prefixes, so we can verify that unique addresses
+    // are returned.
+    std::set<IOAddress> prefixes;
+    for (auto i = 0; i < 65536; ++i) {
+        IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+        EXPECT_EQ(pd_pool_, pool);
+        EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+        prefixes.insert(candidate);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+    }
+    // The pool comprises 65536 prefixes. All should be returned.
+    EXPECT_EQ(65536, prefixes.size());
+}
+
+// Test allocating IPv6 addresses and re-allocating these that are
+// deleted (released).
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithAllocations) {
+    // Remove the default pool because it is too large for this test case.
+    subnet_->delPools(Lease::TYPE_PD);
+    // Add a smaller pool.
+    auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+                                          IOAddress("3000::"),
+                                          120,
+                                          128);
+    subnet_->addPool(pool);
+
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    // Remember returned addresses, so we can verify that unique addresses
+    // are returned.
+    std::map<IOAddress, Lease6Ptr> leases;
+    for (auto i = 0; i < 256; ++i) {
+        IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+        EXPECT_FALSE(candidate.isV6Zero());
+        auto lease = createLease6(Lease::TYPE_PD, candidate, i);
+        leases[candidate] = lease;
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+    // The pool comprises 256 delegated prefixes. All should be returned.
+    EXPECT_EQ(256, leases.size());
+
+    IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+    EXPECT_TRUE(candidate.isV6Zero());
+
+    auto i = 0;
+    for (auto address_lease : leases) {
+        if (i % 2) {
+            EXPECT_TRUE(lease_mgr.deleteLease(address_lease.second));
+        }
+        ++i;
+    }
+
+    for (auto i = 0; i < 128; ++i) {
+        candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+        auto lease = createLease6(Lease::TYPE_PD, candidate, i);
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+
+    candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+    EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test allocating IPv6 addresses and re-allocating these that are
+// reclaimed.
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithReclamations) {
+    // Remove the default pool because it is too large for this test case.
+    subnet_->delPools(Lease::TYPE_PD);
+    // Add a smaller pool.
+    auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+                                          IOAddress("3000::"),
+                                          120,
+                                          128);
+    subnet_->addPool(pool);
+
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    // Remember returned addresses, so we can verify that unique addresses
+    // are returned.
+    std::map<IOAddress, Lease6Ptr> leases;
+    for (auto i = 0; i < 256; ++i) {
+        IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+        EXPECT_FALSE(candidate.isV6Zero());
+        auto lease = createLease6(Lease::TYPE_PD, candidate, i);
+        leases[candidate] = lease;
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+        EXPECT_TRUE(lease_mgr.addLease(lease));
+    }
+    // The pool comprises 256 delegated prefixes. All should be returned.
+    EXPECT_EQ(256, leases.size());
+
+    IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+    EXPECT_TRUE(candidate.isV6Zero());
+
+    auto i = 0;
+    for (auto address_lease : leases) {
+        if (i % 2) {
+            auto lease = address_lease.second;
+            lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+            EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
+        }
+        ++i;
+    }
+
+    for (auto i = 0; i < 128; ++i) {
+        candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+        auto lease = createLease6(Lease::TYPE_PD, candidate, i);
+        EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
+    }
+
+    candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+    EXPECT_TRUE(candidate.isV6Zero());
+}
+
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPools) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+    for (auto i = 0; i < 10; ++i) {
+        ostringstream s;
+        s << "300" << hex << i + 1 << "::";
+        auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+                                              IOAddress(s.str()),
+                                              120,
+                                              128);
+        subnet_->addPool(pool);
+    }
+    size_t total = 65536 + 10 * 256;
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    Pool6Ptr pool;
+
+    std::set<IOAddress> prefixes;
+    for (auto i = 0; i < total; ++i) {
+        IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+        EXPECT_TRUE(pool);
+        EXPECT_FALSE(candidate.isV6Zero());
+        EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+        prefixes.insert(candidate);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+    }
+    // Make sure that unique prefixes have been returned.
+    EXPECT_EQ(total, prefixes.size());
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferLower) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+    for (auto i = 0; i < 10; ++i) {
+        ostringstream s;
+        s << "300" << hex << i + 1 << "::";
+        auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+                                              IOAddress(s.str()),
+                                              120,
+                                              128);
+        subnet_->addPool(pool);
+    }
+
+    size_t total = 65536;
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+
+    Pool6Ptr pool;
+
+    std::set<IOAddress> prefixes;
+    for (auto i = 0; i < total; ++i) {
+        IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 120);
+        EXPECT_FALSE(candidate.isV6Zero());
+        EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+        prefixes.insert(candidate);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+    }
+    // Make sure that unique prefixes have been returned.
+    EXPECT_EQ(total, prefixes.size());
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferEqual) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+    for (auto i = 0; i < 10; ++i) {
+        ostringstream s;
+        s << "300" << hex << i + 1 << "::";
+        auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+                                              IOAddress(s.str()),
+                                              120,
+                                              128);
+        subnet_->addPool(pool);
+    }
+
+    size_t total = 10 * 256;
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    Pool6Ptr pool;
+
+    std::set<IOAddress> prefixes;
+    for (auto i = 0; i < total; ++i) {
+        IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_EQUAL, IOAddress("::"), 128);
+        EXPECT_FALSE(candidate.isV6Zero());
+        EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+        prefixes.insert(candidate);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+    }
+    // Make sure that unique prefixes have been returned.
+    EXPECT_EQ(total, prefixes.size());
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferHigher) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+    for (auto i = 0; i < 10; ++i) {
+        ostringstream s;
+        s << "300" << hex << i + 1 << "::";
+        auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+                                              IOAddress(s.str()),
+                                              120,
+                                              128);
+        subnet_->addPool(pool);
+    }
+
+    size_t total = 10 * 256;
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    Pool6Ptr pool;
+
+    std::set<IOAddress> prefixes;
+    for (auto i = 0; i < total; ++i) {
+        IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+        EXPECT_FALSE(candidate.isV6Zero());
+        EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+        prefixes.insert(candidate);
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+    }
+    // Make sure that unique prefixes have been returned.
+    EXPECT_EQ(total, prefixes.size());
+}
+
+// Test that the allocator respects client class guards.
+TEST_F(FreeLeaseQueueAllocatorTest6, pdPoolsClientClasses) {
+    FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+    // First pool only allows the client class foo.
+    pd_pool_->allowClientClass("foo");
+
+    auto pool2 = boost::make_shared<Pool6>(Lease::TYPE_PD,
+                                           IOAddress("3000:1::"),
+                                           120,
+                                           128);
+    // Second pool only allows the client class bar.
+    pool2->allowClientClass("bar");
+    subnet_->addPool(pool2);
+
+    ASSERT_NO_THROW(alloc.initAfterConfigure());
+    auto& lease_mgr = LeaseMgrFactory::instance();
+
+    Pool6Ptr pool;
+
+    IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+    EXPECT_TRUE(candidate.isV6Zero());
+
+    cc_.insert("bar");
+    for (auto i = 0; i < 256; ++i) {
+        candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+        EXPECT_FALSE(candidate.isV6Zero());
+        EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+    }
+
+    candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+    EXPECT_TRUE(candidate.isV6Zero());
+}
+
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace