From: Marcin Siodelski Date: Thu, 2 Mar 2023 10:19:37 +0000 (+0100) Subject: [#2780] Implemented FLQ allocator X-Git-Tag: Kea-2.3.7~70 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7b93da8dfaeb5aee77a6599d293ffbcddbc625dc;p=thirdparty%2Fkea.git [#2780] Implemented FLQ allocator --- diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index db54107759..d739707c74 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -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())); diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 4b39cb0cda..3a259f458f 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -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 diff --git a/src/lib/dhcpsrv/allocator.h b/src/lib/dhcpsrv/allocator.h index dd82ea8a43..f8dcdf253e 100644 --- a/src/lib/dhcpsrv/allocator.h +++ b/src/lib/dhcpsrv/allocator.h @@ -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 index 0000000000..7f9fa58583 --- /dev/null +++ b/src/lib/dhcpsrv/flq_allocation_state.cc @@ -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 +#include +#include + +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() + : SubnetAllocationState() { +} + +PoolFreeLeaseQueueAllocationStatePtr +PoolFreeLeaseQueueAllocationState::create(const PoolPtr& pool) { + return (boost::make_shared(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>(); + } else { + free_lease6_queue_ = boost::make_shared>(); + } +} + +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 index 0000000000..22713b6a72 --- /dev/null +++ b/src/lib/dhcpsrv/flq_allocation_state.h @@ -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 +#include +#include +#include +#include + +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 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 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 + 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 + > + > + >; + + /// @brief A multi-index container holding free IPv4 leases. + typedef boost::shared_ptr> FreeLease4QueuePtr; + + /// @brief A multi-index container holding free IPv6 leases. + typedef boost::shared_ptr> 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 index 0000000000..633ac31aee --- /dev/null +++ b/src/lib/dhcpsrv/flq_allocator.cc @@ -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 + +#include +#include +#include +#include +#include +#include + +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 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 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(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 +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 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 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(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(subnet->getAllocationState(pool_type_))); +} + +PoolFreeLeaseQueueAllocationStatePtr +FreeLeaseQueueAllocator::getPoolState(const PoolPtr& pool) const { + if (!pool->getAllocationState()) { + pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool)); + } + return (boost::dynamic_pointer_cast(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 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 index 0000000000..794af2854b --- /dev/null +++ b/src/lib/dhcpsrv/flq_allocator.h @@ -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 +#include +#include +#include + +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 + 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 diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index f7228441ba..36217d8c6b 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -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 index 0000000000..273e071ade --- /dev/null +++ b/src/lib/dhcpsrv/tests/flq_allocation_state_unittest.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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(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(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(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(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(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(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(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(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 index 0000000000..86a17c1470 --- /dev/null +++ b/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc @@ -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 +#include +#include +#include +#include +#include +#include + +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 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(hw_address_vec, HTYPE_ETHER); + auto lease = boost::make_shared(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(pool_->getAllocationState()); + ASSERT_TRUE(pool_state); + EXPECT_FALSE(pool_state->exhausted()); + + std::set 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 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 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 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(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 addresses_set; + std::vector addresses_vector; + std::vector 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(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(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(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 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 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_vec); + auto lease = boost::make_shared(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(pool_->getAllocationState()); + ASSERT_TRUE(pool_state); + EXPECT_FALSE(pool_state->exhausted()); + + std::set 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 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 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 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(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 addresses_set; + std::vector addresses_vector; + std::vector 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(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(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(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 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(pool->getAllocationState()); + ASSERT_TRUE(pool_state); + EXPECT_FALSE(pool_state->exhausted()); + + std::set 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 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(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 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(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 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(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 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(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 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(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 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(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 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(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