--- /dev/null
+// 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
--- /dev/null
+// 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