diff --git a/src/lib/dhcpsrv/allocator.h b/src/lib/dhcpsrv/allocator.h index ac94ab992a..94e89a4fe3 100644 --- a/src/lib/dhcpsrv/allocator.h +++ b/src/lib/dhcpsrv/allocator.h @@ -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. /// diff --git a/src/lib/dhcpsrv/flq_allocator.cc b/src/lib/dhcpsrv/flq_allocator.cc index 8acf832dc2..d6c0ba4f56 100644 --- a/src/lib/dhcpsrv/flq_allocator.cc +++ b/src/lib/dhcpsrv/flq_allocator.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include 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::max()) { + return (0.); + } + auto pool_state = boost::dynamic_pointer_cast(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(busy) / static_cast(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(pool); + if (!pool6 || (pool6->getLength() > plen)) { + continue; + } + uint128_t capacity = pool->getCapacity(); + total += capacity; + if (total >= std::numeric_limits::max()) { + return (0.); + } + auto pool_state = boost::dynamic_pointer_cast(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(busy) / static_cast(total)); +} + void FreeLeaseQueueAllocator::initAfterConfigureInternal() { auto subnet = subnet_.lock(); diff --git a/src/lib/dhcpsrv/flq_allocator.h b/src/lib/dhcpsrv/flq_allocator.h index 026ccc3fa9..dbe8d6d48c 100644 --- a/src/lib/dhcpsrv/flq_allocator.h +++ b/src/lib/dhcpsrv/flq_allocator.h @@ -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. diff --git a/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc b/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc index c7b52333a1..36f5f68564 100644 --- a/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc +++ b/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc @@ -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 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 addresses_set; std::vector addresses_vector; std::vector 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(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 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 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