2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 05:55:28 +00:00

[#226] Added getOccupancyRate

This commit is contained in:
Francis Dupont
2025-08-08 12:33:16 +02:00
parent 284c7f5855
commit cbdf2b5728
4 changed files with 280 additions and 12 deletions

View File

@@ -140,6 +140,44 @@ public:
hint_prefix_length));
}
/// @brief Returns the occupancy rate (v4 addresses).
///
/// The method counts the total number and the number of not free
/// addresses in the suitable pools of the subnet, and returns the
/// occupancy rate. If the total number of addresses is over UMAX64
/// or the address is not from one of these pools, or by default
/// the 0. rate is returned.
///
/// @param addr the address.
/// @param client_classes list of classes client belongs to.
/// @param count_me the address is still marked as free.
virtual double
getOccupancyRate(const asiolink::IOAddress& addr,
const ClientClasses& client_classes,
const bool count_me) const {
return (0.);
}
/// @brief Returns the occupancy rate (v6 prefixes).
///
/// The method counts the total number and the number of not free
/// prefixes in the suitable pools of the subnet, and returns the
/// occupancy rate. If the total number of prefixes is over UMAX64
/// or the prefix is not from one of these pools, or by default
/// the 0. rate is returned.
///
/// @param pref the prefix.
/// @param plen the prefix length.
/// @param client_classes list of classes client belongs to.
/// @param count_me the prefix is still marked as free.
virtual double
getOccupancyRate(const asiolink::IOAddress& pref,
const uint8_t plen,
const ClientClasses& client_classes,
const bool count_me) const {
return (0.);
}
/// @brief Check if the pool matches the selection criteria relative to the
/// provided hint prefix length.
///

View File

@@ -13,6 +13,7 @@
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
#include <util/stopwatch.h>
#include <limits>
#include <unordered_set>
using namespace isc::asiolink;
@@ -134,6 +135,107 @@ FreeLeaseQueueAllocator::pickPrefixInternal(const ClientClasses& client_classes,
return (IOAddress::IPV6_ZERO_ADDRESS());
}
double
FreeLeaseQueueAllocator::getOccupancyRate(const IOAddress& addr,
const ClientClasses& client_classes,
const bool count_me) const {
// Sanity.
if (!addr.isV4()) {
return (0.);
}
auto subnet = subnet_.lock();
uint128_t total(0);
uint128_t busy(0);
bool found(false);
for (auto const& pool : subnet->getPools(Lease::TYPE_V4)) {
if (!pool->clientSupported(client_classes)) {
continue;
}
uint128_t capacity = pool->getCapacity();
total += capacity;
if (total >= std::numeric_limits<uint64_t>::max()) {
return (0.);
}
auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState());
if (!pool_state) {
continue;
}
uint128_t free_cnt = pool_state->getFreeLeaseCount();
if (!found && pool->inRange(addr)) {
found = true;
if (count_me && (free_cnt > 0)) {
--free_cnt;
}
}
if (free_cnt > capacity) {
free_cnt = capacity;
}
busy += capacity - free_cnt;
}
if (!found) {
return (0.);
}
// Should not happen...
if (total == 0) {
return (0.);
}
return (static_cast<double>(busy) / static_cast<double>(total));
}
double
FreeLeaseQueueAllocator::getOccupancyRate(const IOAddress& pref,
const uint8_t plen,
const ClientClasses& client_classes,
const bool count_me) const {
// Sanity.
if (!pref.isV6()) {
return (0.);
}
auto subnet = subnet_.lock();
uint128_t total(0);
uint128_t busy(0);
bool found(false);
for (auto const& pool : subnet->getPools(Lease::TYPE_PD)) {
if (!pool->clientSupported(client_classes)) {
continue;
}
auto const& pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
if (!pool6 || (pool6->getLength() > plen)) {
continue;
}
uint128_t capacity = pool->getCapacity();
total += capacity;
if (total >= std::numeric_limits<uint64_t>::max()) {
return (0.);
}
auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState());
if (!pool_state) {
continue;
}
uint128_t free_cnt = pool_state->getFreeLeaseCount();
if (!found && pool->inRange(pref)) {
found = true;
if (count_me && (free_cnt > 0)) {
--free_cnt;
}
}
if (free_cnt > capacity) {
free_cnt = capacity;
}
busy += capacity - free_cnt;
}
if (!found) {
return (0.);
}
// Should not happen...
if (total == 0) {
return (0.);
}
return (static_cast<double>(busy) / static_cast<double>(total));
}
void
FreeLeaseQueueAllocator::initAfterConfigureInternal() {
auto subnet = subnet_.lock();

View File

@@ -51,6 +51,40 @@ public:
return ("flq");
}
/// @brief Returns the occupancy rate (v4 addresses).
///
/// The method counts the total number and the number of not free
/// addresses in the suitable pools of the subnet, and returns the
/// occupancy rate. If the total number of addresses is over UMAX64
/// or the address is not from one of these pools, or by default
/// the 0. rate is returned.
///
/// @param addr the address.
/// @param client_classes list of classes client belongs to.
/// @param count_me the address is still marked as free.
virtual double
getOccupancyRate(const asiolink::IOAddress& addr,
const ClientClasses& client_classes,
const bool count_me) const;
/// @brief Returns the occupancy rate (v6 prefixes).
///
/// The method counts the total number and the number of not free
/// prefixes in the suitable pools of the subnet, and returns the
/// occupancy rate. If the total number of prefixes is over UMAX64
/// or the prefix is not from one of these pools, or by default
/// the 0. rate is returned.
///
/// @param pref the prefix.
/// @param plen the prefix length.
/// @param client_classes list of classes client belongs to.
/// @param count_me the prefix is still marked as free.
virtual double
getOccupancyRate(const asiolink::IOAddress& pref,
const uint8_t plen,
const ClientClasses& client_classes,
const bool count_me) const;
private:
/// @brief Performs allocator initialization after server's reconfiguration.

View File

@@ -65,6 +65,13 @@ TEST_F(FreeLeaseQueueAllocatorTest4, populateFreeAddressLeases) {
ASSERT_TRUE(pool_state);
EXPECT_FALSE(pool_state->exhausted());
double r = alloc.getOccupancyRate(IOAddress("192.0.2.101"), cc_, false);
EXPECT_EQ(.5, r);
r = alloc.getOccupancyRate(IOAddress("192.0.2.101"), cc_, true);
EXPECT_EQ(.6, r);
r = alloc.getOccupancyRate(IOAddress("192.0.2.1"), cc_, false);
EXPECT_EQ(0., r);
std::set<IOAddress> addresses;
for (auto i = 0; i < 5; ++i) {
auto lease = pool_state->offerFreeLease();
@@ -124,6 +131,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithAllocations) {
IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(candidate.isV4Zero());
double r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(1., r);
auto i = 0;
for (auto const& address_lease : leases) {
if (i % 2) {
@@ -132,6 +142,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithAllocations) {
++i;
}
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(.5, r);
for (auto j = 0; j < 5; ++j) {
candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
@@ -142,6 +155,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithAllocations) {
candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(candidate.isV4Zero());
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(1., r);
}
// Test allocating IPv4 addresses and re-allocating these that are
@@ -170,6 +186,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithReclamations) {
IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(candidate.isV4Zero());
double r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(1., r);
auto i = 0;
for (auto const& address_lease : leases) {
if (i % 2) {
@@ -179,6 +198,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithReclamations) {
}
++i;
}
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(.5, r);
for (auto j = 0; j < 5; ++j) {
candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
@@ -187,9 +209,11 @@ TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithReclamations) {
lease->state_ = Lease::STATE_DEFAULT;
EXPECT_NO_THROW(lease_mgr.updateLease4(lease));
}
candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(candidate.isV4Zero());
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(1., r);
}
// Test allocating DHCPv4 leases for many pools in a subnet.
@@ -213,6 +237,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, manyPools) {
auto& lease_mgr = LeaseMgrFactory::instance();
double r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(0., r);
std::set<IOAddress> addresses_set;
std::vector<IOAddress> addresses_vector;
std::vector<PoolPtr> pools_vector;
@@ -232,6 +259,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, manyPools) {
// Make sure that unique addresses have been returned.
EXPECT_EQ(total, addresses_set.size());
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(1., r);
// 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.
@@ -263,12 +293,16 @@ TEST_F(FreeLeaseQueueAllocatorTest4, manyPools) {
// 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_);
FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
subnet_->delPools(Lease::TYPE_V4);
subnet_->delPools(Lease::TYPE_V4);
IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(candidate.isV4Zero());
IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(candidate.isV4Zero());
// rate is 0. because of the address can't be found, not from 0./0....
double r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(0., r);
}
// Test that the allocator respects client class guards.
@@ -304,6 +338,8 @@ TEST_F(FreeLeaseQueueAllocatorTest4, clientClasses) {
// Simulate client's request belonging to the class bar.
cc_.insert("bar");
double r = alloc.getOccupancyRate(IOAddress("192.0.2.120"), cc_, false);
EXPECT_EQ(0., r);
for (auto i = 0; i < 20; ++i) {
// Allocate random addresses and make sure they belong to the
// pools associated with the class bar.
@@ -315,11 +351,16 @@ TEST_F(FreeLeaseQueueAllocatorTest4, clientClasses) {
}
EXPECT_EQ(20, addresses_set.size());
r = alloc.getOccupancyRate(IOAddress("192.0.2.120"), cc_, false);
EXPECT_EQ(1., r);
addresses_set.clear();
// Simulate the case that the client also belongs to the class foo.
// All pools should now be available.
cc_.insert("foo");
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(.5, r);
for (auto i = 0; i < 20; ++i) {
IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
addresses_set.insert(candidate);
@@ -327,12 +368,16 @@ TEST_F(FreeLeaseQueueAllocatorTest4, clientClasses) {
EXPECT_TRUE(subnet_->inRange(candidate));
}
EXPECT_EQ(20, addresses_set.size());
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(1., r);
// 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());
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_, false);
EXPECT_EQ(0., r);
}
/// @brief Test fixture class for the DHCPv6 Free Lease Queue allocator.
@@ -382,6 +427,10 @@ TEST_F(FreeLeaseQueueAllocatorTest6, populateFreeAddressLeases) {
EXPECT_NO_THROW(alloc.initAfterConfigure());
// Address getOccupancyRate is for IPv4 only.
double r = alloc.getOccupancyRate(IOAddress("2001:db8:1::10"), cc_, false);
EXPECT_EQ(0., r);
auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool_->getAllocationState());
ASSERT_TRUE(pool_state);
EXPECT_FALSE(pool_state->exhausted());
@@ -461,7 +510,7 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePoolWithAllocations) {
}
for (auto j = 0; j < 8; ++j) {
candidate = alloc.pickAddress(cc_, duid_, 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);
@@ -509,7 +558,7 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePoolWithReclamations) {
}
for (auto j = 0; j < 8; ++j) {
candidate = alloc.pickAddress(cc_, duid_, 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 = lease_mgr.getLease6(Lease::TYPE_NA, candidate);
@@ -686,6 +735,10 @@ TEST_F(FreeLeaseQueueAllocatorTest6, populateFreePrefixDelegationLeases) {
ASSERT_TRUE(pool_state);
EXPECT_FALSE(pool_state->exhausted());
double r = alloc.getOccupancyRate(IOAddress("2001:db8:2::"),
128, cc_, false);
EXPECT_EQ(5. / 256., r);
std::set<IOAddress> addresses;
for (auto i = 0; i < 256; ++i) {
auto lease = pool_state->offerFreeLease();
@@ -721,9 +774,13 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPool) {
}
// The pool comprises 65536 prefixes. All should be returned.
EXPECT_EQ(65536, prefixes.size());
double r = alloc.getOccupancyRate(IOAddress("2001:db8:1:2::"),
128, cc_, false);
EXPECT_EQ(1., r);
}
// Test allocating IPv6 addresses and re-allocating these that are
// Test allocating delegated prefixes 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.
@@ -757,6 +814,8 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithAllocations) {
IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
EXPECT_TRUE(candidate.isV6Zero());
double r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_, false);
EXPECT_EQ(1., r);
auto i = 0;
for (auto const& address_lease : leases) {
@@ -765,6 +824,10 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithAllocations) {
}
++i;
}
r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_, false);
EXPECT_EQ(.5, r);
r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_, true);
EXPECT_EQ(129. / 256., r);
for (auto j = 0; j < 128; ++j) {
candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
@@ -776,9 +839,12 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithAllocations) {
candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
EXPECT_TRUE(candidate.isV6Zero());
r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_, false);
EXPECT_EQ(1., r);
}
// Test allocating IPv6 addresses and re-allocating these that are
// Test allocating delegated prefixes and re-allocating these that are
// reclaimed.
TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithReclamations) {
// Remove the default pool because it is too large for this test case.
@@ -812,6 +878,8 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithReclamations) {
IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
EXPECT_TRUE(candidate.isV6Zero());
double r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_, false);
EXPECT_EQ(1., r);
auto i = 0;
for (auto const& address_lease : leases) {
@@ -822,6 +890,8 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithReclamations) {
}
++i;
}
r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_, false);
EXPECT_EQ(.5, r);
for (auto j = 0; j < 128; ++j) {
candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
@@ -834,8 +904,10 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithReclamations) {
candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
EXPECT_TRUE(candidate.isV6Zero());
}
r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_, false);
EXPECT_EQ(1., r);
}
// Test allocating delegated prefixes from multiple pools.
TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPools) {
@@ -869,6 +941,9 @@ TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPools) {
}
// Make sure that unique prefixes have been returned.
EXPECT_EQ(total, prefixes.size());
double r = alloc.getOccupancyRate(IOAddress("3001::"), 128, cc_, false);
EXPECT_EQ(1., r);
}
// Test allocating delegated prefixes from multiple pools.
@@ -890,7 +965,6 @@ TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferLower) {
ASSERT_NO_THROW(alloc.initAfterConfigure());
auto& lease_mgr = LeaseMgrFactory::instance();
Pool6Ptr pool;
std::set<IOAddress> prefixes;
@@ -904,6 +978,14 @@ TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferLower) {
}
// Make sure that unique prefixes have been returned.
EXPECT_EQ(total, prefixes.size());
double r = alloc.getOccupancyRate(IOAddress("2001:db8:1:2::"),
120, cc_, false);
EXPECT_EQ(1., r);
r = alloc.getOccupancyRate(IOAddress("2001:db8:1:2::"), 128, cc_, false);
EXPECT_EQ(65536. / 68096., r);
r = alloc.getOccupancyRate(IOAddress("2001:db8:1:2::"), 64, cc_, false);
EXPECT_EQ(0., r);
}
// Test allocating delegated prefixes from multiple pools.
@@ -938,6 +1020,8 @@ TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferEqual) {
}
// Make sure that unique prefixes have been returned.
EXPECT_EQ(total, prefixes.size());
double r = alloc.getOccupancyRate(IOAddress("3001::"), 128, cc_, false);
EXPECT_EQ(2560. / 68096., r);
}
// Test allocating delegated prefixes from multiple pools.
@@ -972,6 +1056,8 @@ TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferHigher) {
}
// Make sure that unique prefixes have been returned.
EXPECT_EQ(total, prefixes.size());
double r = alloc.getOccupancyRate(IOAddress("3001::"), 128, cc_, false);
EXPECT_EQ(2560. / 68096., r);
}
// Test that the allocator respects client class guards.
@@ -1008,8 +1094,16 @@ TEST_F(FreeLeaseQueueAllocatorTest6, pdPoolsClientClasses) {
candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
EXPECT_TRUE(candidate.isV6Zero());
}
double r = alloc.getOccupancyRate(IOAddress("3000:1::"), 128, cc_, false);
EXPECT_EQ(1., r);
cc_.insert("foo");
r = alloc.getOccupancyRate(IOAddress("3000:1::"), 128, cc_, false);
EXPECT_EQ(256. / 65792., r);
cc_.clear();
r = alloc.getOccupancyRate(IOAddress("3000:1::"), 128, cc_, false);
EXPECT_EQ(0., r);
}
} // end of isc::dhcp::test namespace
} // end of isc::dhcp namespace