2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-22 01:49:48 +00:00

Compare commits

...

24 Commits

Author SHA1 Message Date
Francis Dupont
ff637db448 [#4058] Improved ChangeLog 2025-08-20 20:50:47 +02:00
Francis Dupont
b5e0ca3cdb [#4058] Added missing line and ChangeLog entry 2025-08-20 20:50:47 +02:00
Francis Dupont
a15632a6b6 [#226] Fixed another doxygen error 2025-08-20 19:44:32 +02:00
Francis Dupont
f5ef1ab3ac [#226] Fixed some doxygen errors 2025-08-20 19:43:25 +02:00
Francis Dupont
6754a4b86a [#226] Typo 2025-08-20 18:43:22 +02:00
Francis Dupont
1b6681bb39 [#226] Addressed comments 2025-08-20 18:14:43 +02:00
Francis Dupont
b0b3b78d4a [#226] Typos in kea version 2025-08-20 17:39:16 +02:00
Francis Dupont
378de7eeaa [#226] Factored new server UTs 2025-08-20 17:39:16 +02:00
Francis Dupont
ce880380be [#226] Updated doc 2025-08-20 17:39:16 +02:00
Francis Dupont
3bfffe7205 [#226] Checkpoint: doc to do 2025-08-20 17:39:16 +02:00
Francis Dupont
3776e08a16 [#226] Finished v6 code 2025-08-20 17:39:16 +02:00
Francis Dupont
45799e22bf [#226] Checkpoint v6 code 2025-08-20 17:39:16 +02:00
Francis Dupont
069c6ec34e [#226] Finished v4 code 2025-08-20 17:39:16 +02:00
Francis Dupont
beec9ae4e6 [#226] Checkpoint v4 code 2025-08-20 17:39:15 +02:00
Francis Dupont
02ede08a7f [#226] Simplified getOccupancyRate 2025-08-20 17:39:15 +02:00
Francis Dupont
233e393735 [#226] Added getMin*Lft* 2025-08-20 17:39:15 +02:00
Francis Dupont
eea3d8af2e [#226] Added getRemaining 2025-08-20 17:39:15 +02:00
Francis Dupont
cbdf2b5728 [#226] Added getOccupancyRate 2025-08-20 17:39:15 +02:00
Francis Dupont
284c7f5855 [#226] Added UTs for the new parameter 2025-08-20 17:39:15 +02:00
Francis Dupont
f98c30e4a8 [#226] Added set/getAdaptiveLeaseTimeThreshold 2025-08-20 17:39:15 +02:00
Francis Dupont
b55feb15a8 [#226] Finished adaptive-lease-time-threshold global 2025-08-20 17:39:15 +02:00
Francis Dupont
7f34f80f9e [#226] Added adaptive-lease-time-threshold 2025-08-20 17:39:15 +02:00
Ben Scott
812ce8789f Add link to detail doc 2025-08-20 10:54:11 +03:00
Ben Scott
26255f80ee Remove text about obsolete docs
The wiki page is obsolete and apparently not worth updating.
No replacement is available.
Just delete the entire sentence.
2025-08-20 10:54:11 +03:00
59 changed files with 12012 additions and 9620 deletions

View File

@ -5,8 +5,6 @@ about: Create a new issue using this checklist for each release.
# Kea Release Checklist
This is thoroughly documented in [the Kea Release Process guide](https://wiki.isc.org/bin/view/QA/KeaReleaseProcess).
#### Legend
- `A.B.C`: the version being released
@ -212,7 +210,7 @@ Now it's time to publish the code.
## Support
1. [ ] Update tickets if any support customers are waiting for the release.
1. [ ] Update tickets if any support customers are waiting for the release. [:link:](https://wiki.isc.org/bin/view/Main/KeaReleaseSupportUpdateTix)
## QA

View File

@ -0,0 +1,7 @@
[func] fdupont
Added the new "adaptive-lease-time-threshold" parameter
for the FLQ (Free Lease Queue) allocator which reduces
the lifetime of leases when pools of a subnet have an
occupancy rate above a configured threshold (new feature
from ISC DHCP).
(Gitlab #226)

View File

@ -0,0 +1,4 @@
[bug] fdupont
When reusing an expired lease, kea-dhcp6 now correctly saves
the client hardware address in the lease.
(Gitlab #4058)

View File

@ -989,6 +989,9 @@
// lease is returned as it was "cached".
"cache-max-age": 1000,
// Adaptive lease time threshold (1.0 is disabled).
"adaptive-lease-time-threshold": 0.8,
// Specify whether the server should look up global reservations.
"reservations-global": false,
@ -1277,6 +1280,9 @@
// Subnet-level cache maximum.
"cache-max-age": 1000,
// Adaptive lease time threshold (1.0 is disabled).
"adaptive-lease-time-threshold": 0.8,
// List of static IPv4 reservations assigned to clients belonging
// to this subnet. For a detailed example, see reservations.json.
"reservations": [
@ -1419,6 +1425,9 @@
// Global cache maximum.
"cache-max-age": 1000,
// Adaptive lease time threshold (1.0 is disabled).
"adaptive-lease-time-threshold": 0.8,
// String of zero or more characters with which to replace each
// invalid character in the hostname or Client FQDN. The default
// value is an empty string, which will cause invalid characters

View File

@ -939,6 +939,9 @@
// lease is returned as it was "cached".
"cache-max-age": 1000,
// Adaptive lease time threshold (1.0 is disabled).
"adaptive-lease-time-threshold": 0.8,
// Specify whether the server should look up global reservations.
"reservations-global": false,
@ -1270,6 +1273,9 @@
// Subnet-level cache maximum.
"cache-max-age": 1000,
// Adaptive lease time threshold (1.0 is disabled).
"adaptive-lease-time-threshold": 0.8,
// List of static IPv6 reservations assigned to clients belonging
// to this subnet. For a detailed example, see reservations.json.
"reservations": [
@ -1408,6 +1414,9 @@
// Global cache maximum.
"cache-max-age": 1000,
// Adaptive lease time threshold (1.0 is disabled)
"adaptive-lease-time-threshold": 0.8,
// String of zero or more characters with which to replace each
// invalid character in the Client FQDN. The default
// value is an empty string, which will cause invalid characters

View File

@ -8985,3 +8985,20 @@ avoiding unnecessary impact on the server's startup time.
As a result, the servers will not be able to offer some of the available
leases to the clients. Only a server reclaiming a particular lease will
be able to offer it.
In Kea 3.1.1 a new parameter ``adaptive-lease-time-threshold`` was added.
It can be specified at global, shared network and subnet levels and
takes a floating point value between ``0.0`` (excluded) and ``1.0``.
It is disabled by default or when set to ``1.0``. It is only supported
by the FLQ allocator. When the occupancy rate of pools in a subnet is
above the specified value the server decreases the lease valid lifetime
to the applicable ``min-valid-lifetime`` for new clients. Clients
renewing an already existing lease get at least the remaining lifetime
from the current lease. Since the leases expire faster, the server may
either recover more quickly or avoid pool exhaustion entirely.
The occupancy rate is defined as being the number of already assigned
addresses (including the address being assigned) divided by the total
number of addresses in pools where the address can be allocated from
(i.e. if the query is not member of a ``client-classes`` guard of a pool
this pool is not taken into account).

View File

@ -8730,3 +8730,21 @@ avoiding unnecessary impact on the server's startup time.
As a result, the servers will not be able to offer some of the available
leases to the clients. Only a server reclaiming a particular lease will
be able to offer it.
In Kea 3.1.1 a new parameter ``adaptive-lease-time-threshold`` was added.
It can be specified at global, shared network and subnet levels and
takes a floating point value between ``0.0`` (excluded) and ``1.0``.
It is disabled by default or when set to ``1.0``. It is only supported
by the FLQ allocator. When the occupancy rate of pools in a subnet is
above the specified value the server decreases the lease valid lifetime
to the applicable ``min-valid-lifetime`` for new clients. Clients
renewing an already existing lease get at least the remaining lifetime
from the current lease. Since the leases expire faster, the server may
either recover more quickly or avoid pool exhaustion entirely.
The occupancy rate is defined as being the number of already assigned
delegated prefixes (including the address being assigned) divided by the total
number of prefixes in pools where the address can be allocated from
(i.e. if the query is not member of a ``client-classes`` guard of a pool,
or if the delegated prefix length is not compatible this pool is not taken
into account).

File diff suppressed because it is too large Load Diff

View File

@ -1316,6 +1316,17 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
}
}
\"adaptive-lease-time-threshold\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::DHCP4:
case isc::dhcp::Parser4Context::SUBNET4:
case isc::dhcp::Parser4Context::SHARED_NETWORK:
return isc::dhcp::Dhcp4Parser::make_ADAPTIVE_LEASE_TIME_THRESHOLD(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("adaptive-lease-time-threshold", driver.loc_);
}
}
\"loggers\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::DHCP4:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -130,6 +130,7 @@ using namespace std;
T2_PERCENT "t2-percent"
CACHE_THRESHOLD "cache-threshold"
CACHE_MAX_AGE "cache-max-age"
ADAPTIVE_LEASE_TIME_THRESHOLD "adaptive-lease-time-threshold"
DECLINE_PROBATION_PERIOD "decline-probation-period"
SERVER_TAG "server-tag"
STATISTIC_DEFAULT_SAMPLE_COUNT "statistic-default-sample-count"
@ -563,6 +564,7 @@ global_param: valid_lifetime
| t2_percent
| cache_threshold
| cache_max_age
| adaptive_lease_time_threshold
| loggers
| hostname_char_set
| hostname_char_replacement
@ -654,6 +656,12 @@ cache_max_age: CACHE_MAX_AGE COLON INTEGER {
ctx.stack_.back()->set("cache-max-age", cm);
};
adaptive_lease_time_threshold: ADAPTIVE_LEASE_TIME_THRESHOLD COLON FLOAT {
ctx.unique("adaptive-lease-time-threshold", ctx.loc2pos(@1));
ElementPtr altt(new DoubleElement($3, ctx.loc2pos(@3)));
ctx.stack_.back()->set("adaptive-lease-time-threshold", altt);
};
decline_probation_period: DECLINE_PROBATION_PERIOD COLON INTEGER {
ctx.unique("decline-probation-period", ctx.loc2pos(@1));
ElementPtr dpp(new IntElement($3, ctx.loc2pos(@3)));
@ -1683,6 +1691,7 @@ subnet4_param: valid_lifetime
| t2_percent
| cache_threshold
| cache_max_age
| adaptive_lease_time_threshold
| ddns_send_updates
| ddns_override_no_update
| ddns_override_client_update
@ -1887,6 +1896,7 @@ shared_network_param: name
| t2_percent
| cache_threshold
| cache_max_age
| adaptive_lease_time_threshold
| ddns_send_updates
| ddns_override_no_update
| ddns_override_client_update

View File

@ -662,6 +662,7 @@ processDhcp4Config(isc::data::ConstElementPtr config_set) {
(config_pair.first == "t2-percent") ||
(config_pair.first == "cache-threshold") ||
(config_pair.first == "cache-max-age") ||
(config_pair.first == "adaptive-lease-time-threshold") ||
(config_pair.first == "hostname-char-set") ||
(config_pair.first == "hostname-char-replacement") ||
(config_pair.first == "ddns-send-updates") ||

View File

@ -0,0 +1,313 @@
// Copyright (C) 2025 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/dhcp4.h>
#include <dhcp/option_int.h>
#include <dhcp/testutils/iface_mgr_test_config.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp4/tests/dhcp4_client.h>
#include <gtest/gtest.h>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
namespace {
/// @brief JSON configuration
std::string FLQ_CONFIG =
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"min-valid-lifetime\": 100,"
"\"valid-lifetime\": 200,"
"\"max-valid-lifetime\": 300,"
"\"adaptive-lease-time-threshold\": .5,"
"\"cache-threshold\": 0.,"
"\"subnet4\": [ { "
" \"subnet\": \"10.0.0.0/24\", "
" \"id\": 1,"
" \"pools\": [ { \"pool\": \"10.0.0.11-10.0.0.14\" } ],"
" \"allocator\": \"flq\""
" } ]"
"}";
/// @brief Test fixture class for testing FLQ / adaptive-lease-time-threshold.
class FLQTest : public Dhcpv4SrvTest {
public:
/// @brief Constructor.
///
/// Sets up fake interfaces.
FLQTest() : Dhcpv4SrvTest(), iface_mgr_test_config_(true) {
}
/// @brief Creates a DHCPv4 lease for an address and MAC address.
///
/// @param address Lease address.
/// @param hw_address_seed a seed from which the hardware address is generated.
/// @return Created lease pointer.
Lease4Ptr
createLease4(const IOAddress& address, uint64_t hw_address_seed) const {
vector<uint8_t> hw_address_vec(sizeof(hw_address_seed));
for (unsigned i = 0; i < sizeof(hw_address_seed); ++i) {
hw_address_vec[i] = (hw_address_seed >> i) & 0xFF;
}
HWAddrPtr hw_address(new HWAddr(hw_address_vec, HTYPE_ETHER));
Lease4Ptr lease(new Lease4(address, hw_address, ClientIdPtr(),
600, time(0), 1));
return (lease);
}
/// @brief Check the response.
///
/// @param resp the response.
/// @param expected the expected lifetime.
/// @param near use near comparision (when true) or equality (when false).
void checkResponse(Pkt4Ptr resp, uint32_t expected, bool near = false) {
ASSERT_TRUE(resp);
// Make sure that the server has responded with DHCPACK.
ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
// Make sure that the client has got the requested address.
EXPECT_EQ("10.0.0.14", resp->getYiaddr().toText());
// Verify the valid liftime.
OptionUint32Ptr opt = boost::dynamic_pointer_cast<
OptionUint32>(resp->getOption(DHO_DHCP_LEASE_TIME));
ASSERT_TRUE(opt);
if (near) {
EXPECT_NEAR(expected, opt->getValue(), 2);
} else {
EXPECT_EQ(expected, opt->getValue());
}
}
/// @brief Allocate all pool leases leaving the last one free.
void fill() {
auto& lease_mgr = LeaseMgrFactory::instance();
auto lease1 = createLease4(IOAddress("10.0.0.11"), 1);
EXPECT_TRUE(lease_mgr.addLease(lease1));
auto lease2 = createLease4(IOAddress("10.0.0.12"), 2);
EXPECT_TRUE(lease_mgr.addLease(lease2));
auto lease3 = createLease4(IOAddress("10.0.0.13"), 3);
EXPECT_TRUE(lease_mgr.addLease(lease3));
}
/// @brief Age and commit a lease.
///
/// @param lease the lease.
/// @param delay the amount of time backward.
/// @param update when false add the lease, when true update the lease.
/// @param reclaim when true change the state.
void ageLease(Lease4Ptr lease, uint32_t delay, bool update,
bool reclaim = false) {
ASSERT_TRUE(lease);
lease->cltt_ -= delay;
lease->current_cltt_ -= delay;
if (reclaim) {
lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
}
auto& lease_mgr = LeaseMgrFactory::instance();
if (update) {
EXPECT_NO_THROW(lease_mgr.updateLease4(lease));
} else {
EXPECT_TRUE(lease_mgr.addLease(lease));
}
}
/// @brief Interface Manager's fake configuration control.
IfaceMgrTestConfig iface_mgr_test_config_;
};
// Test allocation with empty pool.
TEST_F(FLQTest, empty) {
Dhcp4Client client(srv_, Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Perform 4-way exchange with the server requesting the last address.
boost::shared_ptr<IOAddress> hint(new IOAddress("10.0.0.14"));
ASSERT_NO_THROW(client.doDORA(hint));
// Valid lifetime should be the valid-lifetime parameter value (200).
Pkt4Ptr resp = client.getContext().response_;
checkResponse(resp, 200);
}
// Test allocation with almost full pool.
TEST_F(FLQTest, last) {
Dhcp4Client client(srv_, Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Create leases for the first addresses.
fill();
// Perform 4-way exchange with the server.
ASSERT_NO_THROW(client.doDORA());
// Valid lifetime should be the min-valid-lifetime parameter value (100).
Pkt4Ptr resp = client.getContext().response_;
checkResponse(resp, 100);
}
// Test allocation with an expired lease.
TEST_F(FLQTest, expired) {
Dhcp4Client client(srv_, Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Create leases for the first addresses.
fill();
// Create and expire last lease.
auto lease = createLease4(IOAddress("10.0.0.14"), 4);
ageLease(lease, 1000, false);
ASSERT_TRUE(lease->expired());
// Perform 4-way exchange with the server.
ASSERT_NO_THROW(client.doDORA());
// Valid lifetime should be the min-valid-lifetime parameter value (100).
Pkt4Ptr resp = client.getContext().response_;
checkResponse(resp, 100);
}
// Test allocation with a reclaimed lease.
TEST_F(FLQTest, reclaimed) {
Dhcp4Client client(srv_, Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Create leases for the first addresses.
fill();
// Create and reclaim last lease.
auto lease = createLease4(IOAddress("10.0.0.14"), 4);
ageLease(lease, 1000, false, true);
// Perform 4-way exchange with the server.
ASSERT_NO_THROW(client.doDORA());
// Valid lifetime should be the min-valid-lifetime parameter value (100).
Pkt4Ptr resp = client.getContext().response_;
checkResponse(resp, 100);
}
// Test renewal with almost empty pool.
TEST_F(FLQTest, renew) {
Dhcp4Client client(srv_, Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Obtain a lease from the server using the 4-way exchange.
boost::shared_ptr<IOAddress> hint(new IOAddress("10.0.0.14"));
ASSERT_NO_THROW(client.doDORA(hint));
// Valid lifetime should be the valid-lifetime parameter value (200).
Pkt4Ptr resp = client.getContext().response_;
checkResponse(resp, 200);
// Age the lease.
auto& lease_mgr = LeaseMgrFactory::instance();
Lease4Ptr lease = lease_mgr.getLease4(*hint);
ageLease(lease, 150, true);
ASSERT_FALSE(lease->expired());
// Let's transition the client to Renewing state.
client.setState(Dhcp4Client::RENEWING);
// Set the unicast destination address to indicate that it is a renewal.
client.setDestAddress(IOAddress("10.0.0.1"));
ASSERT_NO_THROW(client.doRequest());
// Valid lifetime should be the valid-lifetime parameter value (200).
resp = client.getContext().response_;
checkResponse(resp, 200);
}
// Test renewal with full pool.
TEST_F(FLQTest, renewFull) {
Dhcp4Client client(srv_, Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Obtain a lease from the server using the 4-way exchange.
boost::shared_ptr<IOAddress> hint(new IOAddress("10.0.0.14"));
ASSERT_NO_THROW(client.doDORA(hint));
// Valid lifetime should be the valid-lifetime parameter value (200).
Pkt4Ptr resp = client.getContext().response_;
checkResponse(resp, 200);
// Create leases for the first addresses.
fill();
// Age the lease.
auto& lease_mgr = LeaseMgrFactory::instance();
Lease4Ptr lease = lease_mgr.getLease4(*hint);
ageLease(lease, 150, true);
ASSERT_FALSE(lease->expired());
// Let's transition the client to Renewing state.
client.setState(Dhcp4Client::RENEWING);
// Set the unicast destination address to indicate that it is a renewal.
client.setDestAddress(IOAddress("10.0.0.1"));
ASSERT_NO_THROW(client.doRequest());
// Valid lifetime should be the min-valid-lifetime parameter value (100).
resp = client.getContext().response_;
checkResponse(resp, 100);
}
// Test renewal with full pool but remaining lifetime greater than minimal.
TEST_F(FLQTest, renewRemaining) {
Dhcp4Client client(srv_, Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Obtain a lease from the server using the 4-way exchange.
boost::shared_ptr<IOAddress> hint(new IOAddress("10.0.0.14"));
ASSERT_NO_THROW(client.doDORA(hint));
// Valid lifetime should be the valid-lifetime parameter value (200).
Pkt4Ptr resp = client.getContext().response_;
checkResponse(resp, 200);
// Create leases for the first addresses.
fill();
// Age the lease but only by 50 seconds.
auto& lease_mgr = LeaseMgrFactory::instance();
Lease4Ptr lease = lease_mgr.getLease4(*hint);
ageLease(lease, 50, true);
ASSERT_FALSE(lease->expired());
// Let's transition the client to Renewing state.
client.setState(Dhcp4Client::RENEWING);
// Set the unicast destination address to indicate that it is a renewal.
client.setDestAddress(IOAddress("10.0.0.1"));
ASSERT_NO_THROW(client.doRequest());
// Valid lifetime should be the remaining lifetime so ~150 seconds.
resp = client.getContext().response_;
checkResponse(resp, 150, true);
}
}

View File

@ -126,6 +126,7 @@ kea_dhcp4_tests = executable(
'dhcp4to6_ipc_unittest.cc',
'direct_client_unittest.cc',
'dora_unittest.cc',
'flq_unittest.cc',
'fqdn_unittest.cc',
'get_config_unittest.cc',
'hooks_unittest.cc',

View File

@ -210,6 +210,6 @@ TEST_F(SimpleParser4Test, optionDefDefaults4) {
checkBoolValue(def, "array", false);
}
};
};
};
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1169,7 +1169,7 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
return isc::dhcp::Dhcp6Parser::make_STRING("ddns-ttl-percent", driver.loc_);
}
}
\"ddns-ttl\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::DHCP6:
@ -1613,6 +1613,17 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
}
}
\"adaptive-lease-time-threshold\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::DHCP6:
case isc::dhcp::Parser6Context::SUBNET6:
case isc::dhcp::Parser6Context::SHARED_NETWORK:
return isc::dhcp::Dhcp6Parser::make_ADAPTIVE_LEASE_TIME_THRESHOLD(driver.loc_);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("adaptive-lease-time-threshold", driver.loc_);
}
}
\"loggers\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::DHCP6:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -115,6 +115,7 @@ using namespace std;
T2_PERCENT "t2-percent"
CACHE_THRESHOLD "cache-threshold"
CACHE_MAX_AGE "cache-max-age"
ADAPTIVE_LEASE_TIME_THRESHOLD "adaptive-lease-time-threshold"
DECLINE_PROBATION_PERIOD "decline-probation-period"
SERVER_TAG "server-tag"
STATISTIC_DEFAULT_SAMPLE_COUNT "statistic-default-sample-count"
@ -568,6 +569,7 @@ global_param: data_directory
| t2_percent
| cache_threshold
| cache_max_age
| adaptive_lease_time_threshold
| loggers
| hostname_char_set
| hostname_char_replacement
@ -686,6 +688,12 @@ cache_max_age: CACHE_MAX_AGE COLON INTEGER {
ctx.stack_.back()->set("cache-max-age", cm);
};
adaptive_lease_time_threshold: ADAPTIVE_LEASE_TIME_THRESHOLD COLON FLOAT {
ctx.unique("adaptive-lease-time-threshold", ctx.loc2pos(@1));
ElementPtr altt(new DoubleElement($3, ctx.loc2pos(@3)));
ctx.stack_.back()->set("adaptive-lease-time-threshold", altt);
};
decline_probation_period: DECLINE_PROBATION_PERIOD COLON INTEGER {
ctx.unique("decline-probation-period", ctx.loc2pos(@1));
ElementPtr dpp(new IntElement($3, ctx.loc2pos(@3)));
@ -1692,6 +1700,7 @@ subnet6_param: preferred_lifetime
| t2_percent
| cache_threshold
| cache_max_age
| adaptive_lease_time_threshold
| hostname_char_set
| hostname_char_replacement
| ddns_send_updates
@ -1884,6 +1893,7 @@ shared_network_param: name
| t2_percent
| cache_threshold
| cache_max_age
| adaptive_lease_time_threshold
| hostname_char_set
| hostname_char_replacement
| ddns_send_updates

View File

@ -780,6 +780,7 @@ processDhcp6Config(isc::data::ConstElementPtr config_set) {
(config_pair.first == "t2-percent") ||
(config_pair.first == "cache-threshold") ||
(config_pair.first == "cache-max-age") ||
(config_pair.first == "adaptive-lease-time-threshold") ||
(config_pair.first == "hostname-char-set") ||
(config_pair.first == "hostname-char-replacement") ||
(config_pair.first == "ddns-send-updates") ||

View File

@ -0,0 +1,409 @@
// Copyright (C) 2025 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/testutils/iface_mgr_test_config.h>
#include <dhcp6/tests/dhcp6_test_utils.h>
#include <dhcp6/tests/dhcp6_client.h>
#include <gtest/gtest.h>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
namespace {
/// @brief JSON configuration
std::string FLQ_CONFIG =
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"min-preferred-lifetime\": 100,"
"\"preferred-lifetime\": 200,"
"\"max-preferred-lifetime\": 300,"
"\"min-valid-lifetime\": 400,"
"\"valid-lifetime\": 600,"
"\"max-valid-lifetime\": 700,"
"\"rebind-timer\": 250,"
"\"renew-timer\": 250,"
"\"adaptive-lease-time-threshold\": .5,"
"\"cache-threshold\": 0.,"
"\"subnet6\": [ { "
" \"subnet\": \"3000::/32\", "
" \"id\": 1,"
" \"interface\": \"eth0\","
" \"pd-pools\": [ {"
" \"prefix\": \"2001:db8:1::\","
" \"prefix-len\": 48,"
" \"delegated-len\": 51"
" } ],"
" \"pd-allocator\": \"flq\""
" } ]"
"}";
/// @brief Test fixture class for testing FLQ / adaptive-lease-time-threshold.
class FLQTest : public Dhcpv6SrvTest {
public:
/// @brief Constructor.
///
/// Sets up fake interfaces.
FLQTest() : Dhcpv6SrvTest(), iface_mgr_test_config_(true) {
}
/// @brief Creates a DHCPv6 lease for an address and MAC address.
///
/// @param address Lease address.
/// @param duid_seed a seed from which the DUID is generated.
/// @return Created lease pointer.
Lease6Ptr
createLease6(const IOAddress& address, uint64_t duid_seed) const {
vector<uint8_t> duid_vec(sizeof(duid_seed));
for (unsigned i = 0; i < sizeof(duid_seed); ++i) {
duid_vec[i] = (duid_seed >> i) & 0xFF;
}
DuidPtr duid(new DUID(duid_vec));
Lease6Ptr lease(new Lease6(Lease::TYPE_PD, address, duid, 1,
800, 1200, 1, HWAddrPtr(), 51));
return (lease);
}
/// @brief Check the returned lease.
///
/// @param lease the lease.
/// @param exp_preferred the expected preferred lifetime.
/// @param exp_valid the expected valid lifetime
/// @param near_preferred use near comparision (when true) or equality
/// when false) for the preferred lifetime.
/// @param near_valid use near comparision (when true) or equality
/// when false) for the valid lifetime.
void checkResponse(Lease6Ptr lease, uint32_t exp_preferred,
uint32_t exp_valid, bool near_preferred = false,
bool near_valid = false) {
ASSERT_TRUE(lease);
// Make sure that the client has got the requested prefix.
EXPECT_EQ("2001:db8:1:e000::", lease->addr_.toText());
EXPECT_EQ(51, lease->prefixlen_);
if (near_preferred) {
EXPECT_NEAR(exp_preferred, lease->preferred_lft_, 2);
} else {
EXPECT_EQ(exp_preferred, lease->preferred_lft_);
}
if (near_valid) {
EXPECT_NEAR(exp_valid, lease->valid_lft_, 2);
} else {
EXPECT_EQ(exp_valid, lease->valid_lft_);
}
}
/// @brief Allocate all pool leases leaving the last one free.
void fill() {
auto& lease_mgr = LeaseMgrFactory::instance();
auto lease0 = createLease6(IOAddress("2001:db8:1::"), 1);
EXPECT_TRUE(lease_mgr.addLease(lease0));
auto lease1 = createLease6(IOAddress("2001:db8:1:2000::"), 2);
EXPECT_TRUE(lease_mgr.addLease(lease1));
auto lease2 = createLease6(IOAddress("2001:db8:1:4000::"), 3);
EXPECT_TRUE(lease_mgr.addLease(lease2));
auto lease3 = createLease6(IOAddress("2001:db8:1:6000::"), 4);
EXPECT_TRUE(lease_mgr.addLease(lease3));
auto lease4 = createLease6(IOAddress("2001:db8:1:8000::"), 5);
EXPECT_TRUE(lease_mgr.addLease(lease4));
auto lease5 = createLease6(IOAddress("2001:db8:1:a000::"), 6);
EXPECT_TRUE(lease_mgr.addLease(lease5));
auto lease6 = createLease6(IOAddress("2001:db8:1:c000::"), 7);
EXPECT_TRUE(lease_mgr.addLease(lease6));
}
/// @brief Age and commit a lease.
///
/// @param lease the lease.
/// @param delay the amount of time backward.
/// @param update when false add the lease, when true update the lease.
/// @param reclaim when true change the state.
void ageLease(Lease6Ptr lease, uint32_t delay, bool update,
bool reclaim = false) {
ASSERT_TRUE(lease);
lease->cltt_ -= delay;
lease->current_cltt_ -= delay;
if (reclaim) {
lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
}
auto& lease_mgr = LeaseMgrFactory::instance();
if (update) {
EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
} else {
EXPECT_TRUE(lease_mgr.addLease(lease));
}
}
/// @brief Interface Manager's fake configuration control.
IfaceMgrTestConfig iface_mgr_test_config_;
};
// Test allocation with empty pool.
TEST_F(FLQTest, empty) {
Dhcp6Client client(srv_);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Perform 4-way exchange with the server requesting the last prefix.
client.requestPrefix(1, 51, IOAddress("2001:db8:1:e000::"));
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr lease = checkLease(client.getLease(0));
// Preferred lifetime should be the config preferred-lifetime (200).
// Valid lifetime should be the config valid-lifetime (600).
checkResponse(lease, 200, 600);
}
// Test allocation with almost full pool.
TEST_F(FLQTest, last) {
Dhcp6Client client(srv_);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Create leases for the first prefixes.
fill();
// Perform 4-way exchange with the server.
client.requestPrefix();
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr lease = checkLease(client.getLease(0));
// Preferred lifetime should be the config min-preferred-lifetime (100).
// Valid lifetime should be the config min-valid-lifetime (400).
checkResponse(lease, 100, 400);
}
// Test allocation with an expired lease.
TEST_F(FLQTest, expired) {
Dhcp6Client client(srv_);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Create leases for the first prefixes.
fill();
// Create and expire last lease.
auto lease7 = createLease6(IOAddress("2001:db8:1:e000::"), 8);
ageLease(lease7, 10000, false);
ASSERT_TRUE(lease7->expired());
// Perform 4-way exchange with the server.
client.requestPrefix();
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr lease = checkLease(client.getLease(0));
// Preferred lifetime should be the config min-preferred-lifetime (100).
// Valid lifetime should be the config min-valid-lifetime (400).
checkResponse(lease, 100, 400);
}
// Test allocation with a reclaimed lease.
TEST_F(FLQTest, reclaimed) {
Dhcp6Client client(srv_);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Create leases for the first prefixes.
fill();
// Create and reclaim last lease.
auto lease7 = createLease6(IOAddress("2001:db8:1:e000::"), 8);
ageLease(lease7, 10000, false, true);
ASSERT_TRUE(lease7->expired());
// Perform 4-way exchange with the server.
client.requestPrefix();
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr lease = checkLease(client.getLease(0));
// Preferred lifetime should be the config min-preferred-lifetime (100).
// Valid lifetime should be the config min-valid-lifetime (400).
checkResponse(lease, 100, 400);
}
// Test renewal with almost empty pool.
TEST_F(FLQTest, renew) {
Dhcp6Client client(srv_);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Perform 4-way exchange with the server requesting the last prefix.
client.requestPrefix(1, 51, IOAddress("2001:db8:1:e000::"));
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr lease = checkLease(client.getLease(0));
// Preferred lifetime should be the config preferred-lifetime (200).
// Valid lifetime should be the config valid-lifetime (600).
checkResponse(lease, 200, 600);
// Age the lease.
ageLease(lease, 1000, true);
// Send the renew message to the server.
ASSERT_NO_THROW(client.doRenew());
// Server should have renewed the prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr renewed = checkLease(client.getLease(0));
// Preferred lifetime should be the config preferred-lifetime (200).
// Valid lifetime should be the config valid-lifetime (600).
checkResponse(renewed, 200, 600);
// Check the lease was updated.
EXPECT_NEAR(lease->cltt_ + 1000, renewed->cltt_, 2);
}
// Test renewal with full pool.
TEST_F(FLQTest, renewFull) {
Dhcp6Client client(srv_);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Perform 4-way exchange with the server requesting the last prefix.
client.requestPrefix(1, 51, IOAddress("2001:db8:1:e000::"));
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr lease = checkLease(client.getLease(0));
// Preferred lifetime should be the config preferred-lifetime (200).
// Valid lifetime should be the config valid-lifetime (600).
checkResponse(lease, 200, 600);
// Create leases for the first prefixes.
fill();
// Age the lease.
ageLease(lease, 1000, true);
// Send the renew message to the server.
ASSERT_NO_THROW(client.doRenew());
// Server should have renewed the prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr renewed = checkLease(client.getLease(0));
ASSERT_TRUE(renewed);
// Preferred lifetime should be the config min-preferred-lifetime (100).
// Valid lifetime should be the config min-valid-lifetime (400).
checkResponse(renewed, 100, 400);
// Check the lease was updated.
EXPECT_NEAR(lease->cltt_ + 1000, renewed->cltt_, 2);
}
// Test renewal with full pool but remaining lifetimes greater than minimal.
TEST_F(FLQTest, renewRemaining) {
Dhcp6Client client(srv_);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Perform 4-way exchange with the server requesting the last prefix.
client.requestPrefix(1, 51, IOAddress("2001:db8:1:e000::"));
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr lease = checkLease(client.getLease(0));
// Preferred lifetime should be the config preferred-lifetime (200).
// Valid lifetime should be the config valid-lifetime (600).
checkResponse(lease, 200, 600);
// Create leases for the first prefixes.
fill();
// Age the lease but only by 50 seconds.
ageLease(lease, 50, true);
// Send the renew message to the server.
ASSERT_NO_THROW(client.doRenew());
// Server should have renewed the prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr renewed = checkLease(client.getLease(0));
// Preferred lifetime should be the remaining lifetime so ~150.
// Valid lifetime should be the remaining lifetime so ~550
checkResponse(renewed, 150, 550, true, true);
// Check the lease was updated.
EXPECT_NEAR(lease->cltt_ + 50, renewed->cltt_, 2);
}
// Test renewal with full pool but remaining valid lifetime only greater
// than minimal.
TEST_F(FLQTest, renewRemainingValid) {
Dhcp6Client client(srv_);
// Configure DHCP server.
configure(FLQ_CONFIG, *client.getServer());
// Perform 4-way exchange with the server requesting the last prefix.
client.requestPrefix(1, 51, IOAddress("2001:db8:1:e000::"));
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr lease = checkLease(client.getLease(0));
// Preferred lifetime should be the config preferred-lifetime (200).
// Valid lifetime should be the config valid-lifetime (600).
checkResponse(lease, 200, 600);
// Create leases for the first prefixes.
fill();
// Age the lease but only by 150 seconds.
ageLease(lease, 150, true);
// Send the renew message to the server.
ASSERT_NO_THROW(client.doRenew());
// Server should have renewed the prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6Ptr renewed = checkLease(client.getLease(0));
// Preferred lifetime should be the config min-preferred-lifetime (100).
// Valid lifetime should be the remaining lifetime so ~450
checkResponse(renewed, 100, 450, false, true);
// Check the lease was updated.
EXPECT_NEAR(lease->cltt_ + 150, renewed->cltt_, 2);
}
}

View File

@ -127,6 +127,7 @@ kea_dhcp6_tests = executable(
'dhcp6_test_utils.cc',
'dhcp6_unittests.cc',
'dhcp6to4_ipc_unittest.cc',
'flq_unittest.cc',
'fqdn_unittest.cc',
'get_config_unittest.cc',
'hooks_unittest.cc',

View File

@ -69,7 +69,7 @@ public:
/// false this indicates that the IOService was stopped.
///
/// @param wait_time_usecs wait time in microseconds.
/// @param[out] time_out set to true if the wait time expired
/// @param[out] timed_out set to true if the wait time expired
/// without any handlers executing.
/// timed_out parameter will be set true if the wait time elapsed.
///

View File

@ -70,7 +70,7 @@ public:
/// false this indicates that the IOService was stopped.
///
/// @param wait_time_usecs wait time in microseconds.
/// @param[out] time_out set to true if the wait time expired
/// @param[out] timed_out set to true if the wait time expired
/// without any handlers executing.
/// timed_out parameter will be set true if the wait time elapsed.
///

View File

@ -272,7 +272,7 @@ private:
/// their hashes are equal, if two class lists are not equal their hashes
/// are almost surely not equal.
///
/// @param address A @c ClientClasses to hash.
/// @param client_classes A @c ClientClasses to hash.
/// @return The hash of the ClientClasses.
size_t hash_value(const ClientClasses& client_classes);

View File

@ -1737,6 +1737,22 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
existing_leases.end(), Lease6Ptr()), existing_leases.end());
}
namespace {
bool
useMinLifetimes6(AllocEngine::ClientContext6& ctx, const IOAddress& addr,
uint8_t prefix_len) {
auto const& threshold = ctx.subnet_->getAdaptiveLeaseTimeThreshold();
if (!threshold.unspecified() && (threshold < 1.0)) {
auto const& occupancy = ctx.subnet_->getAllocator(Lease::TYPE_PD)->
getOccupancyRate(addr, prefix_len, ctx.query_->getClasses());
if (occupancy >= threshold) {
return (true);
}
}
return (false);
}
} // end of anonymous namespace.
Lease6Ptr
AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
uint8_t prefix_len,
@ -1764,12 +1780,20 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
// address, lease type and prefixlen (0) stay the same
expired->iaid_ = ctx.currentIA().iaid_;
expired->duid_ = ctx.duid_;
expired->hwaddr_ = ctx.hwaddr_;
// Calculate life times.
getLifetimes6(ctx, expired->preferred_lft_, expired->valid_lft_);
expired->preferred_lft_ = 0;
expired->valid_lft_ = 0;
if ((expired->type_ == Lease::TYPE_PD) &&
useMinLifetimes6(ctx, expired->addr_, prefix_len)) {
getMinLifetimes6(ctx, expired->preferred_lft_, expired->valid_lft_);
} else {
getLifetimes6(ctx, expired->preferred_lft_, expired->valid_lft_);
}
expired->reuseable_valid_lft_ = 0;
expired->cltt_ = time(NULL);
expired->cltt_ = time(0);
expired->subnet_id_ = ctx.subnet_->getID();
expired->hostname_ = ctx.hostname_;
expired->fqdn_fwd_ = ctx.fwd_dns_update_;
@ -1891,6 +1915,20 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
return (expired);
}
namespace {
void sanitizeLifetimes6(AllocEngine::ClientContext6& ctx,
uint32_t& preferred, uint32_t& valid) {
// If preferred isn't set or insane, calculate it as valid_lft * 0.625.
if (!preferred || preferred > valid) {
preferred = ((valid * 5)/8);
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME)
.arg(ctx.query_->getLabel())
.arg(preferred);
}
}
} // end of anonymous namespace.
void
AllocEngine::getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& valid) {
// If the triplets are specified in one of our classes use it.
@ -1950,14 +1988,70 @@ AllocEngine::getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& v
}
}
// If preferred isn't set or insane, calculate it as valid_lft * 0.625.
if (!preferred || preferred > valid) {
preferred = ((valid * 5)/8);
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME)
.arg(ctx.query_->getLabel())
.arg(preferred);
sanitizeLifetimes6(ctx, preferred, valid);
}
void
AllocEngine::getMinLifetimes6(ClientContext6& ctx, uint32_t& preferred,
uint32_t& valid) {
// If the triplets are specified in one of our classes use it.
// We use the first one we find for each lifetime.
Triplet<uint32_t> candidate_preferred;
Triplet<uint32_t> candidate_valid;
const ClientClasses classes = ctx.query_->getClasses();
if (!classes.empty()) {
// Let's get class definitions
const ClientClassDictionaryPtr& dict =
CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
// Iterate over the assigned class definitions.
int have_both = 0;
for (auto const& name : classes) {
ClientClassDefPtr cl = dict->findClass(name);
if (candidate_preferred.unspecified() &&
(cl && (!cl->getPreferred().unspecified()))) {
candidate_preferred = cl->getPreferred();
++have_both;
}
if (candidate_valid.unspecified() &&
(cl && (!cl->getValid().unspecified()))) {
candidate_valid = cl->getValid();
++have_both;
}
if (have_both == 2) {
break;
}
}
}
// If no classes specified preferred lifetime, get it from the subnet.
if (!candidate_preferred) {
candidate_preferred = ctx.subnet_->getPreferred();
}
// If no classes specified valid lifetime, get it from the subnet.
if (!candidate_valid) {
candidate_valid = ctx.subnet_->getValid();
}
// Save remaining values.
uint32_t remain_preferred(preferred);
uint32_t remain_valid(valid);
// Set the outbound parameters to the minimal values.
preferred = candidate_preferred.getMin();
valid = candidate_valid.getMin();
// Return at least the remaining values.
if (remain_preferred > preferred) {
preferred = remain_preferred;
}
if (remain_valid > valid) {
valid = remain_valid;
}
sanitizeLifetimes6(ctx, preferred, valid);
}
Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
@ -1971,7 +2065,12 @@ Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
uint32_t preferred = 0;
uint32_t valid = 0;
getLifetimes6(ctx, preferred, valid);
if ((ctx.currentIA().type_ == Lease::TYPE_PD) &&
useMinLifetimes6(ctx, addr, prefix_len)) {
getMinLifetimes6(ctx, preferred, valid);
} else {
getLifetimes6(ctx, preferred, valid);
}
Lease6Ptr lease(new Lease6(ctx.currentIA().type_, addr, ctx.duid_,
ctx.currentIA().iaid_, preferred,
@ -2098,6 +2197,31 @@ Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
}
}
void
AllocEngine::getRemaining(const Lease6Ptr& lease, uint32_t& preferred,
uint32_t& valid) {
valid = 0;
preferred = 0;
if (!lease || (lease->state_ != Lease::STATE_DEFAULT)) {
return;
}
time_t now = time(0);
// Refuse time not going forward.
if (lease->cltt_ > now) {
return;
}
uint32_t age = now - lease->cltt_;
// Already expired.
if (age >= lease->valid_lft_) {
return;
}
valid = lease->valid_lft_ - age;
if (age >= lease->preferred_lft_) {
return;
}
preferred = lease->preferred_lft_ - age;
}
Lease6Collection
AllocEngine::renewLeases6(ClientContext6& ctx) {
try {
@ -2282,7 +2406,17 @@ AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
// Calculate life times.
uint32_t current_preferred_lft = lease->preferred_lft_;
getLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_);
if ((lease->type_ == Lease::TYPE_PD) &&
useMinLifetimes6(ctx, lease->addr_, lease->prefixlen_)) {
uint32_t remain_preferred_lft(0);
uint32_t remain_valid_lft(0);
getRemaining(lease, remain_preferred_lft, remain_valid_lft);
lease->preferred_lft_ = remain_preferred_lft;
lease->valid_lft_ = remain_valid_lft;
getMinLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_);
} else {
getLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_);
}
// If either has changed set the changed flag.
if ((lease->preferred_lft_ != current_preferred_lft) ||
@ -2468,7 +2602,13 @@ AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases
uint32_t current_preferred_lft = lease->preferred_lft_;
if (lease->valid_lft_ == 0) {
// The lease was expired by a release: reset zero lifetimes.
getLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_);
lease->preferred_lft_ = 0;
if ((lease->type_ == Lease::TYPE_PD) &&
useMinLifetimes6(ctx, lease->addr_, lease->prefixlen_)) {
getMinLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_);
} else {
getLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_);
}
}
if (!ctx.fake_allocation_) {
bool update_stats = false;
@ -4313,6 +4453,65 @@ AllocEngine::getValidLft(const ClientContext4& ctx) {
return (candidate_lft.get());
}
void
AllocEngine::getMinValidLft(const ClientContext4& ctx, uint32_t& valid) {
// If it's BOOTP, use infinite valid lifetime.
if (ctx.query_->inClass("BOOTP")) {
valid = Lease::INFINITY_LFT;
return;
}
// If the triplet is specified in one of our classes use it.
// We use the first one we find.
Triplet<uint32_t> candidate_lft;
const ClientClasses classes = ctx.query_->getClasses();
if (!classes.empty()) {
// Let's get class definitions
const ClientClassDictionaryPtr& dict =
CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
// Iterate over the assigned class definitions.
for (auto const& name : classes) {
ClientClassDefPtr cl = dict->findClass(name);
if (cl && (!cl->getValid().unspecified())) {
candidate_lft = cl->getValid();
break;
}
}
}
// If no classes specified it, get it from the subnet.
if (!candidate_lft) {
candidate_lft = ctx.subnet_->getValid();
}
// Save remaining value.
uint32_t remain(valid);
// Set to the minimal value.
valid = candidate_lft.getMin();
// Return at least the remaining value.
if (remain > valid) {
valid = remain;
}
}
namespace {
bool
useMinValidLft(const AllocEngine::ClientContext4& ctx, const IOAddress&addr) {
auto const& threshold = ctx.subnet_->getAdaptiveLeaseTimeThreshold();
if (!threshold.unspecified() && (threshold < 1.0)) {
auto const& occupancy = ctx.subnet_->getAllocator(Lease::TYPE_V4)->
getOccupancyRate(addr, ctx.query_->getClasses());
if (occupancy >= threshold) {
return (true);
}
}
return (false);
}
} // end of anonymous namespace.
Lease4Ptr
AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr,
CalloutHandle::CalloutNextStep& callout_status) {
@ -4324,9 +4523,16 @@ AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr,
}
// Get the context appropriate lifetime.
uint32_t valid_lft = (ctx.offer_lft_ ? ctx.offer_lft_ : getValidLft(ctx));
uint32_t valid_lft = ctx.offer_lft_;
if (!valid_lft) {
if (useMinValidLft(ctx, addr)) {
getMinValidLft(ctx, valid_lft);
} else {
valid_lft = getValidLft(ctx);
}
}
time_t now = time(NULL);
time_t now = time(0);
ClientIdPtr client_id;
if (ctx.subnet_->getMatchClientId()) {
@ -4457,6 +4663,30 @@ AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr,
}
}
void
AllocEngine::getRemaining(const Lease4Ptr& lease, uint32_t& valid) {
valid = 0;
if (!lease || (lease->state_ != Lease::STATE_DEFAULT)) {
return;
}
// Always remain infinite lifetime leases.
if (lease->valid_lft_ == Lease::INFINITY_LFT) {
valid = Lease::INFINITY_LFT;
return;
}
time_t now = time(0);
// Refuse time not going forward.
if (lease->cltt_ > now) {
return;
}
uint32_t age = now - lease->cltt_;
// Already expired.
if (age >= lease->valid_lft_) {
return;
}
valid = lease->valid_lft_ - age;
}
Lease4Ptr
AllocEngine::renewLease4(const Lease4Ptr& lease,
AllocEngine::ClientContext4& ctx) {
@ -4998,10 +5228,20 @@ AllocEngine::updateLease4Information(const Lease4Ptr& lease,
changed = true;
lease->client_id_ = ClientIdPtr();
}
lease->cltt_ = time(NULL);
uint32_t remain_lft(0);
getRemaining(lease, remain_lft);
lease->cltt_ = time(0);
// Get the context appropriate valid lifetime.
lease->valid_lft_ = (ctx.offer_lft_ ? ctx.offer_lft_ : getValidLft(ctx));
lease->valid_lft_ = ctx.offer_lft_;
if (!lease->valid_lft_) {
if (useMinValidLft(ctx, lease->addr_)) {
lease->valid_lft_ = remain_lft;
getMinValidLft(ctx, lease->valid_lft_);
} else {
lease->valid_lft_ = getValidLft(ctx);
}
}
// Valid lifetime has changed.
if (lease->valid_lft_ != lease->current_valid_lft_) {

View File

@ -816,10 +816,26 @@ public:
///
/// @param ctx client context that passes all necessary information. See
/// @ref ClientContext6 for details.
/// @param [out] preferred set to the preferred lifetime that should be used.
/// @param [out] valid set to the valid lifetime that should be used.
/// @param[out] preferred set to the preferred lifetime that should be used.
/// @param[out] valid set to the valid lifetime that should be used.
static void getLifetimes6(ClientContext6& ctx, uint32_t& preferred,
uint32_t& valid);
/// @brief Determines the preferred and valid v6 lease lifetimes when
/// the pool occupancy is over the adaptive lease time threshold.
///
/// As for the common case find the candidate triplet and return
/// minimal values. Requested lifetimes are ignored but remaining
/// lifetimes are returned when greater than minimal.
///
/// @param ctx client context that passes all necessary information. See
/// @ref ClientContext6 for details.
/// @param[in,out] preferred set to the preferred lifetime that should
/// be used. Caller must set it to 0 or remaining value.
/// @param[in,out] valid set to the valid lifetime that should be used.
/// Caller must set it to 0 or remaining value.
static void getMinLifetimes6(ClientContext6& ctx, uint32_t& preferred,
uint32_t& valid);
private:
/// @brief Creates a lease and inserts it in LeaseMgr if necessary
@ -834,7 +850,7 @@ private:
/// available
/// @param prefix_len length of the prefix (for PD only)
/// should be 128 for other lease types
/// @param [out] callout_status callout returned by the lease6_select
/// @param[out] callout_status callout returned by the lease6_select
///
/// The following fields of the ctx structure are used:
/// @ref ClientContext6::subnet_ Subnet the lease is allocated from
@ -1537,6 +1553,21 @@ public:
/// @return unsigned integer value of the valid lifetime to use.
static uint32_t getValidLft(const ClientContext4& ctx);
/// @brief Returns the valid lifetime based on the v4 context when
/// the pool occupancy is over the adaptive lease time threshold.
///
/// If the client query is a BOOTP query, the value returned will
/// be Lease::INFINITY_LFT.
///
/// Otherwise, as for the common case find the canndidate triplet
/// and return the minimal value. Requested lifetime is ignored but
/// remaining lifetime is returned when greater than minimal.
///
/// @param ctx Client context holding various information about the client.
/// @param[in,out] valid set to the valid lifetime that should be used.
/// Caller must set it to 0 or remaining value.
static void getMinValidLft(const ClientContext4& ctx, uint32_t& valid);
/// @brief Returns the offer lifetime based on the v4 context
///
/// If the client query is a BOOTP query or something other than
@ -1555,6 +1586,20 @@ public:
/// @return unsigned integer value of the offer lifetime to use.
static uint32_t getOfferLft(const ClientContext4& ctx,
bool only_on_discover = true);
/// @brief Set remaining valid life time.
///
/// @param lease A pointer to the lease.
/// @param [out] valid The remaining valid life time or 0.
static void getRemaining(const Lease4Ptr& lease, uint32_t& valid);
/// @brief Set remaining valid and preferred life times.
///
/// @param lease A pointer to the lease.
/// @param [out] preferred The remaining preferred life time or 0.
/// @param [out] valid The remaining valid life time or 0.
static void getRemaining(const Lease6Ptr& lease, uint32_t& preferred,
uint32_t& valid);
private:
/// @brief Offers the lease.

View File

@ -78,5 +78,18 @@ Allocator::initAfterConfigure() {
inited_ = true;
}
double
Allocator::getOccupancyRate(const asiolink::IOAddress&,
const ClientClasses&) const {
return (0.);
}
double
Allocator::getOccupancyRate(const asiolink::IOAddress&,
const uint8_t,
const ClientClasses&) const {
return (0.);
}
}
}

View File

@ -140,6 +140,36 @@ 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.
virtual double
getOccupancyRate(const asiolink::IOAddress& addr,
const ClientClasses& client_classes) 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.
virtual double
getOccupancyRate(const asiolink::IOAddress& pref,
const uint8_t plen,
const ClientClasses& client_classes) const;
/// @brief Check if the pool matches the selection criteria relative to the
/// provided hint prefix length.
///

View File

@ -61,6 +61,7 @@ CfgGlobals::nameToIndex = {
{ "ddns-ttl-min", DDNS_TTL_MIN },
{ "ddns-ttl-max", DDNS_TTL_MAX },
{ "host-reservation-identifiers", HOST_RESERVATION_IDENTIFIERS },
{ "adaptive-lease-time-threshold", ADAPTIVE_LEASE_TIME_THRESHOLD },
// DHCPv4 specific parameters.
{ "echo-client-id", ECHO_CLIENT_ID },

View File

@ -84,6 +84,7 @@ public:
DDNS_TTL_MIN,
DDNS_TTL_MAX,
HOST_RESERVATION_IDENTIFIERS,
ADAPTIVE_LEASE_TIME_THRESHOLD,
// DHCPv4 specific parameters.
ECHO_CLIENT_ID,

View File

@ -840,7 +840,7 @@ private:
/// @return Collection of const @c Host objects.
ConstHostCollection
getAllInternal4(const SubnetID& subnet_id,
const asiolink::IOAddress& address) const;
const asiolink::IOAddress& address) const;
/// @brief Returns @c Host objects for the specified (Subnet-id,IPv6 address) tuple.
///

View File

@ -904,7 +904,7 @@ public:
/// @return A pointer to the unparsed configuration.
isc::data::ElementPtr
toElementWithMetadata(const bool include_metadata,
CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr()) const;
CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr()) const;
private:

View File

@ -155,7 +155,7 @@
example: 0
- <b>fqdn_rev</b> - FQDN reverse DNS RR update flag \n
type: bool (0 or 1) \n
type: bool (0 or 1) \n
example: 1
- <b>hostname</b> - hostname \n

View File

@ -53,6 +53,17 @@ PoolFreeLeaseQueueAllocationState::deleteFreeLease(const asiolink::IOAddress& ad
}
}
bool
PoolFreeLeaseQueueAllocationState::isFreeLease(const asiolink::IOAddress& address) const {
if (free_lease4_queue_) {
auto const& idx = free_lease4_queue_->get<1>();
return (idx.count(address.toUint32()) > 0);
} else {
auto const& idx = free_lease6_queue_->get<1>();
return (idx.count(address) > 0);
}
}
IOAddress
PoolFreeLeaseQueueAllocationState::offerFreeLease() {
if (free_lease4_queue_) {
@ -84,4 +95,3 @@ PoolFreeLeaseQueueAllocationState::getFreeLeaseCount() const {
} // end of namespace isc::dhcp
} // end of namespace isc

View File

@ -55,6 +55,11 @@ public:
/// @param address lease address.
void deleteFreeLease(const asiolink::IOAddress& address);
/// @brief Check if a lease is in the queue.
///
/// @param address lease address.
bool isFreeLease(const asiolink::IOAddress& address) const;
/// @brief Returns next available lease.
///
/// @return next free lease address or IPv4/IPv6 zero address when

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,105 @@ FreeLeaseQueueAllocator::pickPrefixInternal(const ClientClasses& client_classes,
return (IOAddress::IPV6_ZERO_ADDRESS());
}
double
FreeLeaseQueueAllocator::getOccupancyRate(const IOAddress& addr,
const ClientClasses& client_classes) 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 ((free_cnt > 0) && pool_state->isFreeLease(addr)) {
--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 {
// 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 ((free_cnt > 0) && pool_state->isFreeLease(pref)) {
--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,36 @@ 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.
virtual double
getOccupancyRate(const asiolink::IOAddress& addr,
const ClientClasses& client_classes) 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.
virtual double
getOccupancyRate(const asiolink::IOAddress& pref,
const uint8_t plen,
const ClientClasses& client_classes) const;
private:
/// @brief Performs allocator initialization after server's reconfiguration.

View File

@ -276,6 +276,11 @@ Network::toElement() const {
map->set("allocator", Element::create(allocator_type_));
}
if (!adaptive_lease_time_threshold_.unspecified()) {
map->set("adaptive-lease-time-threshold",
Element::create(adaptive_lease_time_threshold_));
}
return (map);
}

View File

@ -221,7 +221,8 @@ public:
cache_threshold_(), cache_max_age_(), ddns_update_on_renew_(),
ddns_conflict_resolution_mode_(), ddns_ttl_percent_(),
ddns_ttl_(), ddns_ttl_min_(), ddns_ttl_max_(),
allocator_type_(), default_allocator_type_() {
allocator_type_(), default_allocator_type_(),
adaptive_lease_time_threshold_() {
}
/// @brief Virtual destructor.
@ -894,6 +895,25 @@ public:
default_allocator_type_ = allocator_type;
}
/// @brief Returns percentage of the adaptive lease time threshold,
///
/// @param inheritance inheritance mode to be used.
util::Optional<double>
getAdaptiveLeaseTimeThreshold(const Inheritance& inheritance = Inheritance::ALL) const {
return (getProperty<Network>(&Network::getAdaptiveLeaseTimeThreshold,
adaptive_lease_time_threshold_,
inheritance,
CfgGlobals::ADAPTIVE_LEASE_TIME_THRESHOLD));
}
/// @brief Sets new percentage of the adaptive lease time threshold.
///
/// @param adaptive_lease_time_threshold New percentage to use.
void setAdaptiveLeaseTimeThreshold(const util::Optional<double>&
adaptive_lease_time_threshold) {
adaptive_lease_time_threshold_ = adaptive_lease_time_threshold;
}
/// @brief Unparses network object.
///
/// @return A pointer to unparsed network configuration.
@ -1308,6 +1328,11 @@ protected:
/// backend internally.
util::Optional<std::string> default_allocator_type_;
/// @brief Percentage of the adaptive lease time threshold.
/// (a lease assignment over the threshold will get minimal or remaining
/// life times).
util::Optional<double> adaptive_lease_time_threshold_;
/// @brief Pointer to another network that this network belongs to.
///
/// The most common case is that this instance is a subnet which belongs

View File

@ -17,7 +17,6 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
void
BaseNetworkParser::parseCommon(const ConstElementPtr& network_data,
NetworkPtr& network) {
@ -171,6 +170,24 @@ BaseNetworkParser::parsePdAllocatorParams(const data::ConstElementPtr& network_d
}
}
void
BaseNetworkParser::parseAdaptiveLeaseTimeParam(const ConstElementPtr& network_data,
NetworkPtr& network) {
if (network_data->contains("adaptive-lease-time-threshold")) {
double adaptive_lease_time_threshold =
getDouble(network_data, "adaptive-lease-time-threshold");
if ((adaptive_lease_time_threshold <= 0.0) ||
(adaptive_lease_time_threshold > 1.0)) {
isc_throw(DhcpConfigError,
"adaptive-lease-time-threshold: "
<< adaptive_lease_time_threshold
<< " is invalid, it must be greater than 0.0 "
<< "and less than or equal to 1.0");
}
network->setAdaptiveLeaseTimeThreshold(adaptive_lease_time_threshold);
}
}
void
BaseNetworkParser::parseOfferLft(const data::ConstElementPtr& network_data,
Network4Ptr& network) {
@ -251,6 +268,5 @@ BaseNetworkParser::getClientClassesElem(ConstElementPtr params,
}
}
} // end of namespace isc::dhcp
} // end of namespace isc

View File

@ -117,6 +117,21 @@ protected:
void parsePdAllocatorParams(const data::ConstElementPtr& network_data,
Network6Ptr& network);
/// @brief Parses parameter related to adaptive lease time.
///
/// The parsed parameter is:
/// - adaptive-lease-time-threshold.
///
/// @param network_data Data element holding network configuration
/// to be parsed.
/// @param [out] network Pointer to a network in which parsed data is
/// to be stored.
///
/// @throw DhcpConfigError if configuration of this parameter is
/// invalid.
void parseAdaptiveLeaseTimeParam(const data::ConstElementPtr& network_data,
NetworkPtr& network);
/// @brief Parses offer-lifetime parameter (v4 only)
///
/// @param network_data Data element holding shared network

View File

@ -972,6 +972,9 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
// Parse lease cache parameters
parseCacheParams(params, network);
// Parse adaptive lease time parameter.
parseAdaptiveLeaseTimeParam(params, network);
// Set the offer_lft value for the subnet.
if (params->contains("offer-lifetime")) {
uint32_t offer_lft = getInteger(params, "offer-lifetime");
@ -1441,6 +1444,9 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params,
// Parse lease cache parameters
parseCacheParams(params, network);
// Parse adaptive lease time parameter.
parseAdaptiveLeaseTimeParam(params, network);
}
void

View File

@ -189,6 +189,9 @@ SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data,
// Parse allocator params.
parseAllocatorParams(shared_network_data, network);
// Parse adaptive lease time parameter.
parseAdaptiveLeaseTimeParam(shared_network_data, network);
// Parse offer-lifetime parameter.
Network4Ptr network4 = boost::dynamic_pointer_cast<Network4>(shared_network);
parseOfferLft(shared_network_data, network4);
@ -368,6 +371,9 @@ SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data,
auto network6 = boost::dynamic_pointer_cast<Network6>(shared_network);
parsePdAllocatorParams(shared_network_data, network6);
// Parse adaptive lease time parameter.
parseAdaptiveLeaseTimeParam(shared_network_data, network);
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError, ex.what() << " ("
<< shared_network_data->getPosition() << ")");

View File

@ -103,6 +103,7 @@ const SimpleKeywords SimpleParser4::GLOBAL4_PARAMETERS = {
{ "ddns-ttl", Element::integer },
{ "ddns-ttl-min", Element::integer },
{ "ddns-ttl-max", Element::integer },
{ "adaptive-lease-time-threshold", Element::real },
};
/// @brief This table defines default global values for DHCPv4
@ -281,6 +282,7 @@ const SimpleKeywords SimpleParser4::SUBNET4_PARAMETERS = {
{ "ddns-ttl", Element::integer },
{ "ddns-ttl-min", Element::integer },
{ "ddns-ttl-max", Element::integer },
{ "adaptive-lease-time-threshold", Element::real },
};
/// @brief This table defines default values for each IPv4 subnet.
@ -338,6 +340,7 @@ const ParamsList SimpleParser4::INHERIT_TO_SUBNET4 = {
"cache-max-age",
"allocator",
"offer-lifetime",
"adaptive-lease-time-threshold",
};
/// @brief This table defines all pool parameters.
@ -425,6 +428,7 @@ const SimpleKeywords SimpleParser4::SHARED_NETWORK4_PARAMETERS = {
{ "ddns-ttl", Element::integer },
{ "ddns-ttl-min", Element::integer },
{ "ddns-ttl-max", Element::integer },
{ "adaptive-lease-time-threshold", Element::real },
};
/// @brief This table defines default values for interfaces for DHCPv4.

View File

@ -103,6 +103,7 @@ const SimpleKeywords SimpleParser6::GLOBAL6_PARAMETERS = {
{ "ddns-ttl", Element::integer },
{ "ddns-ttl-min", Element::integer },
{ "ddns-ttl-max", Element::integer },
{ "adaptive-lease-time-threshold", Element::real },
};
/// @brief This table defines default global values for DHCPv6
@ -274,6 +275,7 @@ const SimpleKeywords SimpleParser6::SUBNET6_PARAMETERS = {
{ "ddns-ttl", Element::integer },
{ "ddns-ttl-min", Element::integer },
{ "ddns-ttl-max", Element::integer },
{ "adaptive-lease-time-threshold", Element::real },
};
/// @brief This table defines default values for each IPv6 subnet.
@ -329,7 +331,8 @@ const ParamsList SimpleParser6::INHERIT_TO_SUBNET6 = {
"cache-threshold",
"cache-max-age",
"allocator",
"pd-allocator"
"pd-allocator",
"adaptive-lease-time-threshold",
};
/// @brief This table defines all pool parameters.
@ -439,6 +442,7 @@ const SimpleKeywords SimpleParser6::SHARED_NETWORK6_PARAMETERS = {
{ "ddns-ttl", Element::integer },
{ "ddns-ttl-min", Element::integer },
{ "ddns-ttl-max", Element::integer },
{ "adaptive-lease-time-threshold", Element::real },
};
/// @brief This table defines default values for interfaces for DHCPv6.

View File

@ -5002,6 +5002,209 @@ TEST_F(AllocEngine4Test, getTemplateClassValidLft4) {
}
}
// Verifies that AllocEngine::getMinValidLft(ctx4, valid) sets the appropriate
// lifetime value based on the context content.
TEST_F(AllocEngine4Test, getMinValidLft4) {
AllocEngine engine(0);
// Let's make three classes, two with valid-lifetime and one without,
// and add them to the dictionary.
ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
ExpressionPtr match_expr;
ExpressionParser parser;
ElementPtr test_cfg = Element::create("'valid_one_value'");
parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr));
Triplet<uint32_t> valid_one(50, 100, 150);
class_def->setValid(valid_one);
dictionary->addClass(class_def);
test_cfg = Element::create("'valid_two_value'");
parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
class_def.reset(new TemplateClientClassDef("valid_two", match_expr));
Triplet<uint32_t>valid_two(200, 250, 300);
class_def->setValid(valid_two);
dictionary->addClass(class_def);
test_cfg = Element::create("'valid_unspec_value'");
parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr));
dictionary->addClass(class_def);
// Commit our class changes.
CfgMgr::instance().commit();
// Update the subnet's triplet to something more useful.
subnet_->setValid(Triplet<uint32_t>(500, 1000, 1500));
// Describes a test scenario.
struct Scenario {
std::string desc_; // descriptive text for logging
std::vector<std::string> classes_; // class list of assigned classes
uint32_t requested_lft_; // use as option 51 is > 0
uint32_t remaining_lft_; // remaining lifime or 0
uint32_t exp_valid_; // expected lifetime
};
// Scenarios to test.
std::vector<Scenario> scenarios = {
{
"BOOTP",
{ "BOOTP" },
0,
0,
Lease::INFINITY_LFT
},
{
"no classes, no option, remain 0",
{},
0,
0,
subnet_->getValid().getMin()
},
{
"no classes, no option, remain too small",
{},
0,
100,
subnet_->getValid().getMin()
},
{
"no classes, no option, remain",
{},
0,
800,
800
},
{
"no classes, option, remain 0",
{},
1000,
0,
subnet_->getValid().getMin()
},
{
"class unspecified, no option, remain 0",
{ "valid_unspec" },
0,
0,
subnet_->getValid().getMin()
},
{
"from last class, no option, remain 0",
{ "valid_unspec", "valid_one" },
0,
0,
valid_one.getMin()
},
{
"from first class, no option, remain 0",
{ "valid_two", "valid_one" },
0,
0,
valid_two.getMin()
},
{
"class plus remain too small",
{ "valid_one" },
0,
10,
valid_one.getMin(),
},
{
"class plus remain",
{ "valid_one" },
0,
100,
100
}
};
// Iterate over the scenarios and verify the correct outcome.
for (auto const& scenario : scenarios) {
SCOPED_TRACE(scenario.desc_); {
// Create a context;
AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
IOAddress("0.0.0.0"), false, false,
"", false);
ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
// Add client classes (if any)
for (auto const& class_name : scenario.classes_) {
if (class_name == "BOOTP") {
ctx.query_->addClass(class_name);
} else {
string subclass(TemplateClientClassDef::SPAWN_CLASS_PREFIX);
subclass += class_name;
subclass += "_value";
ctx.query_->addSubClass(class_name, subclass);
}
}
// Add client option (if one)
if (scenario.requested_lft_) {
OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME,
scenario.requested_lft_));
ctx.query_->addOption(opt);
}
uint32_t valid = scenario.remaining_lft_;
engine.getMinValidLft(ctx, valid);
EXPECT_EQ(valid, scenario.exp_valid_);
}
}
}
// Verifies that AllocEngine::getRemaining retuns the remaining lifetime value.
TEST_F(AllocEngine4Test, getRemaining) {
// No Lease.
uint32_t valid(1);
Lease4Ptr lease;
AllocEngine::getRemaining(lease, valid);
EXPECT_EQ(0, valid);
// Unexpected state.
valid = 1;
uint8_t hwaddr_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data), HTYPE_ETHER));
uint8_t clientid[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
time_t now = time(0);
lease.reset(new Lease4(IOAddress("192.0.2.100"), hwaddr, clientid,
sizeof(clientid), 100, now, 1));
lease->state_ = Lease::STATE_DECLINED;
AllocEngine::getRemaining(lease, valid);
EXPECT_EQ(0, valid);
// Infinite lifetime.
lease->state_ = Lease::STATE_DEFAULT;
uint32_t infinity_lft = Lease::INFINITY_LFT;
lease->valid_lft_ = lease->current_valid_lft_ = infinity_lft;
AllocEngine::getRemaining(lease, valid);
EXPECT_EQ(infinity_lft, valid);
// Time going backward.
lease->cltt_ = lease->current_cltt_ = now + 100;
lease->valid_lft_ = lease->current_valid_lft_ = 50;
AllocEngine::getRemaining(lease, valid);
EXPECT_EQ(0, valid);
// Already expired.
valid = 1;
lease->cltt_ = lease->current_cltt_ = now - 100;
AllocEngine::getRemaining(lease, valid);
EXPECT_EQ(0, valid);
// Valid case.
now = time(0);
lease->cltt_ = lease->current_cltt_ = now - 10;
AllocEngine::getRemaining(lease, valid);
EXPECT_NEAR(40, valid, 1);
}
// This test checks that deleteRelease handles BOOTP leases.
TEST_F(AllocEngine4Test, bootpDelete) {
boost::scoped_ptr<AllocEngine> engine;

View File

@ -6324,6 +6324,358 @@ TEST_F(AllocEngine6Test, getTemplateClassPreferredLifetime) {
}
}
// Verifies that AllocEngine::getMinLifetimes6() returns the appropriate
// valid lifetime value based on the context content.
TEST_F(AllocEngine6Test, getMinValidLifetime) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
ASSERT_TRUE(engine);
// Let's make three classes, two with valid-lifetime and one without,
// and add them to the dictionary.
ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
ExpressionPtr match_expr;
ExpressionParser parser;
ElementPtr test_cfg = Element::create("'valid_one_value'");
parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr));
Triplet<uint32_t> valid_one(50, 100, 150);
class_def->setValid(valid_one);
dictionary->addClass(class_def);
test_cfg = Element::create("'valid_two_value'");
parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
class_def.reset(new TemplateClientClassDef("valid_two", match_expr));
Triplet<uint32_t>valid_two(200, 250, 300);
class_def->setValid(valid_two);
dictionary->addClass(class_def);
test_cfg = Element::create("'valid_unspec_value'");
parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr));
dictionary->addClass(class_def);
// Commit our class changes.
CfgMgr::instance().commit();
// Update the subnet's triplet to something more useful.
subnet_->setValid(Triplet<uint32_t>(500, 1000, 1500));
// Describes a test scenario.
struct Scenario {
std::string desc_; // descriptive text for logging
std::vector<std::string> classes_; // class list of assigned classes
uint32_t requested_lft_; // use as option 51 is > 0
uint32_t remaining_lft_; // remaining valid lifetime
uint32_t exp_valid_; // expected lifetime
};
// Scenarios to test.
std::vector<Scenario> scenarios = {
{
"no classes, no hint, remain 0",
{},
0,
0,
subnet_->getValid().getMin()
},
{
"no classes, no hint, remain too small",
{},
0,
100,
subnet_->getValid().getMin()
},
{
"no classes, no hint, remain",
{},
0,
800,
800
},
{
"no classes, hint, remain 0",
{},
800,
0,
subnet_->getValid().getMin()
},
{
"class unspecified, no hint, remain 0",
{ "valid_unspec" },
0,
0,
subnet_->getValid().getMin()
},
{
"from last class, no hint, remain 0",
{ "valid_unspec", "valid_one" },
0,
0,
valid_one.getMin()
},
{
"from first class, no hint, remain 0",
{ "valid_two", "valid_one" },
0,
0,
valid_two.getMin()
},
{
"class plus remain too small",
{ "valid_one" },
0,
10,
valid_one.getMin()
},
{
"class plus remain",
{ "valid_one" },
0,
100,
100
}
};
// Iterate over the scenarios and verify the correct outcome.
for (auto const& scenario : scenarios) {
SCOPED_TRACE(scenario.desc_); {
// Create a context;
AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
// Add client classes (if any)
for (auto const& class_name : scenario.classes_) {
ctx.query_->addClass(class_name);
}
// Add hint
ctx.currentIA().iaid_ = iaid_;
// prefix, prefixlen, preferred, valid
ctx.currentIA().addHint(IOAddress("::"), 128, 0, scenario.requested_lft_);
uint32_t valid = scenario.remaining_lft_;
uint32_t preferred = 0;
engine->getMinLifetimes6(ctx, preferred, valid);
EXPECT_EQ(valid, scenario.exp_valid_);
}
}
}
// Verifies that AllocEngine::getMinLifetimes6() returns the appropriate
// preferred lifetime value based on the context content.
TEST_F(AllocEngine6Test, getMinPreferredLifetime) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
ASSERT_TRUE(engine);
// Let's make three classes, two with preferred-lifetime and one without,
// and add them to the dictionary.
ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
ExpressionPtr match_expr;
ExpressionParser parser;
ElementPtr test_cfg = Element::create("'preferred_one_value'");
parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
ClientClassDefPtr class_def(new TemplateClientClassDef("preferred_one", match_expr));
Triplet<uint32_t> preferred_one(50, 100, 150);
class_def->setPreferred(preferred_one);
dictionary->addClass(class_def);
test_cfg = Element::create("'preferred_two_value'");
parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
class_def.reset(new TemplateClientClassDef("preferred_two", match_expr));
Triplet<uint32_t>preferred_two(200, 250, 300);
class_def->setPreferred(preferred_two);
dictionary->addClass(class_def);
test_cfg = Element::create("'preferred_unspec_value'");
parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
class_def.reset(new TemplateClientClassDef("preferred_unspec", match_expr));
dictionary->addClass(class_def);
// Commit our class changes.
CfgMgr::instance().commit();
// Update the subnet's triplet to something more useful. Note that
// valid is 400 for the subnet.
subnet_->setPreferred(Triplet<uint32_t>(300, 350, 450));
// Describes a test scenario.
struct Scenario {
std::string desc_; // descriptive text for logging
std::vector<std::string> classes_; // class list of assigned classes
uint32_t requested_lft_; // use as option 51 is > 0
uint32_t remaining_lft_; // remaining preferred lifetime
uint32_t exp_preferred_; // expected lifetime
};
// Scenarios to test.
std::vector<Scenario> scenarios = {
{
"no classes, no hint, remain 0",
{},
0,
0,
subnet_->getPreferred().getMin()
},
{
"no classes, no hint, remain too small",
{},
0,
100,
subnet_->getPreferred().getMin()
},
{
"no classes, no hint, remain",
{},
0,
350,
350
},
{
"no classes, no hint, remain too big",
{},
0,
500,
subnet_->getValid().getMin() * 5 / 8
},
{
"no classes, hint, remain 0",
{},
800,
0,
subnet_->getPreferred().getMin()
},
{
"class unspecified, no hint, remain 0",
{ "preferred_unspec" },
0,
0,
subnet_->getPreferred().getMin()
},
{
"from last class, no hint, remain 0",
{ "preferred_unspec", "preferred_one" },
0,
0,
preferred_one.getMin()
},
{
"from first class, no hint, remain 0",
{ "preferred_two", "preferred_one" },
0,
0,
preferred_two.getMin()
},
{
"class plus remain too small",
{ "preferred_one" },
0,
10,
preferred_one.getMin()
},
{
"class plus remain",
{ "preferred_one" },
0,
100,
100
}
};
// Iterate over the scenarios and verify the correct outcome.
for (auto const& scenario : scenarios) {
SCOPED_TRACE(scenario.desc_); {
// Create a context;
AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
// Add client classes (if any)
for (auto const& class_name : scenario.classes_) {
string subclass(TemplateClientClassDef::SPAWN_CLASS_PREFIX);
subclass += class_name;
subclass += "_value";
ctx.query_->addSubClass(class_name, subclass);
}
// Add hint
ctx.currentIA().iaid_ = iaid_;
// prefix, prefixlen, preferred, valid
ctx.currentIA().addHint(IOAddress("::"), 128, scenario.requested_lft_, 0);
uint32_t valid = 0;
uint32_t preferred = scenario.remaining_lft_;
engine->getMinLifetimes6(ctx, preferred, valid);
EXPECT_EQ(preferred, scenario.exp_preferred_);
}
}
}
// Verifies that AllocEngine::getRemaining retuns the remaining lifetime values.
TEST_F(AllocEngine6Test, getRemaining) {
// No Lease.
uint32_t valid(1);
uint32_t preferred(1);
Lease6Ptr lease;
AllocEngine::getRemaining(lease, preferred, valid);
EXPECT_EQ(0, valid);
EXPECT_EQ(0, preferred);
// Unexpected state.
valid = 1;
preferred = 1;
DuidPtr duid(new DUID(vector<uint8_t>(12, 0xff)));
const uint32_t iaid = 3568;
time_t now = time(0);
lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), duid,
iaid, 30, 50, 1));
lease->state_ = Lease::STATE_DECLINED;
AllocEngine::getRemaining(lease, preferred, valid);
EXPECT_EQ(0, valid);
EXPECT_EQ(0, preferred);
// Time going backward.
valid = 1;
preferred = 1;
lease->state_ = Lease::STATE_DEFAULT;
lease->cltt_ = lease->current_cltt_ = now + 100;
lease->valid_lft_ = lease->current_valid_lft_ = 50;
AllocEngine::getRemaining(lease, preferred, valid);
EXPECT_EQ(0, valid);
EXPECT_EQ(0, preferred);
// Already expired.
valid = 1;
preferred = 1;
lease->cltt_ = lease->current_cltt_ = now - 100;
AllocEngine::getRemaining(lease, preferred, valid);
EXPECT_EQ(0, valid);
EXPECT_EQ(0, preferred);
// Valid case.
now = time(0);
lease->cltt_ = lease->current_cltt_ = now - 10;
AllocEngine::getRemaining(lease, preferred, valid);
EXPECT_NEAR(40, valid, 1);
EXPECT_NEAR(20, preferred, 1);
// No longer preferred.
now = time(0);
lease->cltt_ = lease->current_cltt_ = now - 40;
AllocEngine::getRemaining(lease, preferred, valid);
EXPECT_NEAR(10, valid, 1);
EXPECT_EQ(0, preferred);
}
} // namespace test
} // namespace dhcp
} // namespace isc

View File

@ -210,6 +210,7 @@ TEST(CfgSharedNetworks4Test, unparse) {
network1->setHostnameCharReplacement("x");
network1->setCacheThreshold(.20);
network1->setOfferLft(77);
network1->setAdaptiveLeaseTimeThreshold(.90);
network2->setIface("eth1");
network2->setT1(Triplet<uint32_t>(100));
@ -255,6 +256,7 @@ TEST(CfgSharedNetworks4Test, unparse) {
" \"cache-max-age\": 50\n"
" },\n"
" {\n"
" \"adaptive-lease-time-threshold\": .90,\n"
" \"calculate-tee-times\": true,\n"
" \"ddns-generated-prefix\": \"prefix\",\n"
" \"ddns-override-no-update\": true,\n"

View File

@ -1087,6 +1087,7 @@ TEST(CfgSubnets4Test, unparseSubnet) {
subnet1->setT1Percent(0.45);
subnet1->setT2Percent(0.70);
subnet1->setCacheThreshold(0.20);
subnet1->setAdaptiveLeaseTimeThreshold(.90);
subnet2->setIface("lo");
subnet2->addRelayAddress(IOAddress("10.0.0.1"));
@ -1146,6 +1147,7 @@ TEST(CfgSubnets4Test, unparseSubnet) {
" \"4o6-interface\": \"\",\n"
" \"4o6-interface-id\": \"\",\n"
" \"4o6-subnet\": \"\",\n"
" \"adaptive-lease-time-threshold\": .90,\n"
" \"option-data\": [ ],\n"
" \"pools\": [ ],\n"
" \"user-context\": { \"comment\": \"foo\" }\n"
@ -1821,6 +1823,10 @@ TEST(CfgSubnets4Test, cacheParamValidation) {
{"too big", 1.05,
"subnet configuration failed: cache-threshold:"
" 1.05 is invalid, it must be greater than or equal to 0.0 and less than 1.0"
},
{"excluded", 1.0,
"subnet configuration failed: cache-threshold:"
" 1 is invalid, it must be greater than or equal to 0.0 and less than 1.0"
}
};
@ -1849,7 +1855,6 @@ TEST(CfgSubnets4Test, cacheParamValidation) {
" \"4o6-subnet\": \"\" \n"
" }";
data::ElementPtr elems;
ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
<< "invalid JSON:" << json << "\n test is broken";
@ -1887,6 +1892,99 @@ TEST(CfgSubnets4Test, cacheParamValidation) {
}
}
// This test verifies the Subnet4 parser's validation logic for
// adaptive lease time parameter.
TEST(CfgSubnets4Test, adaptiveLeaseTimeParamValidation) {
// Describes a single test scenario.
struct Scenario {
std::string label; // label used for logging test failures
double threshold; // value of adaptive-lease-time-threshold
std::string error_message; // expected error message is parsing should fail
};
// Test Scenarios.
std::vector<Scenario> tests = {
{"valid", .25, ""},
{"valid", 1.0, ""},
{"negative", -.25,
"subnet configuration failed: adaptive-lease-time-threshold:"
" -0.25 is invalid, it must be greater than 0.0 and less than or equal to 1.0"
},
{"excluded", 0.,
"subnet configuration failed: adaptive-lease-time-threshold:"
" 0 is invalid, it must be greater than 0.0 and less than or equal to 1.0"
},
{"too big", 1.05,
"subnet configuration failed: adaptive-lease-time-threshold:"
" 1.05 is invalid, it must be greater than 0.0 and less than or equal to 1.0"
}
};
// First we create a set of elements that provides all
// required for a Subnet4.
std::string json =
" {"
" \"id\": 1,\n"
" \"subnet\": \"10.1.2.0/24\", \n"
" \"interface\": \"\", \n"
" \"renew-timer\": 100, \n"
" \"rebind-timer\": 200, \n"
" \"valid-lifetime\": 300, \n"
" \"match-client-id\": false, \n"
" \"authoritative\": false, \n"
" \"next-server\": \"\", \n"
" \"server-hostname\": \"\", \n"
" \"boot-file-name\": \"\", \n"
" \"client-classes\": [], \n"
" \"evaluate-additional-classes\": [], \n"
" \"reservations-global\": false, \n"
" \"reservations-in-subnet\": true, \n"
" \"reservations-out-of-pool\": false, \n"
" \"4o6-interface\": \"\", \n"
" \"4o6-interface-id\": \"\", \n"
" \"4o6-subnet\": \"\" \n"
" }";
data::ElementPtr elems;
ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
<< "invalid JSON:" << json << "\n test is broken";
// Iterate over the test scenarios, verifying each prescribed
// outcome.
for (auto const& test : tests) {
{
SCOPED_TRACE("test: " + test.label);
// Set this scenario's configuration parameters
elems->set("adaptive-lease-time-threshold",
data::Element::create(test.threshold));
Subnet4Ptr subnet;
try {
// Attempt to parse the configuration.
Subnet4ConfigParser parser;
subnet = parser.parse(elems);
} catch (const std::exception& ex) {
if (!test.error_message.empty()) {
// We expected a failure, did we fail the correct way?
EXPECT_EQ(test.error_message, ex.what());
} else {
// Should not have failed.
ADD_FAILURE() << "Scenario should not have failed: " << ex.what();
}
// Either way we're done with this scenario.
continue;
}
// We parsed correctly, make sure the values are right.
EXPECT_TRUE(util::areDoublesEquivalent(test.threshold,
subnet->getAdaptiveLeaseTimeThreshold()));
}
}
}
// This test verifies that the optional interface check works as expected.
TEST(CfgSubnets4Test, iface) {
// Create a configuration.

View File

@ -684,6 +684,7 @@ TEST(CfgSubnets6Test, unparseSubnet) {
subnet1->setT1Percent(0.45);
subnet1->setT2Percent(0.70);
subnet1->setCacheThreshold(0.20);
subnet1->setAdaptiveLeaseTimeThreshold(.90);
subnet2->setIface("lo");
subnet2->addRelayAddress(IOAddress("2001:db8:ff::2"));
@ -740,6 +741,7 @@ TEST(CfgSubnets6Test, unparseSubnet) {
" \"valid-lifetime\": 4,\n"
" \"min-valid-lifetime\": 4,\n"
" \"max-valid-lifetime\": 4,\n"
" \"adaptive-lease-time-threshold\": .90,\n"
" \"client-classes\": [ \"foo\", \"bar\" ],\n"
" \"pools\": [ ],\n"
" \"pd-pools\": [ ],\n"
@ -1610,6 +1612,10 @@ TEST(CfgSubnets6Test, cacheParamValidation) {
{"too big", 1.05,
"subnet configuration failed: cache-threshold:"
" 1.05 is invalid, it must be greater than or equal to 0.0 and less than 1.0"
},
{"excluded", 1.0,
"subnet configuration failed: cache-threshold:"
" 1 is invalid, it must be greater than or equal to 0.0 and less than 1.0"
}
};
@ -1667,6 +1673,91 @@ TEST(CfgSubnets6Test, cacheParamValidation) {
}
}
// This test verifies the Subnet6 parser's validation logic for
// adaptive lease time parameter.
TEST(CfgSubnets6Test, adaptiveLeaseTimeParamValidation) {
// Describes a single test scenario.
struct Scenario {
std::string label; // label used for logging test failures
double threshold; // value of adaptive-lease-time-threshold
std::string error_message; // expected error message is parsing should fail
};
// Test Scenarios.
std::vector<Scenario> tests = {
{"valid", .25, ""},
{"valid", 1.0, ""},
{"negative", -.25,
"subnet configuration failed: adaptive-lease-time-threshold:"
" -0.25 is invalid, it must be greater than 0.0 and less than or equal to 1.0"
},
{"excluded", 0.,
"subnet configuration failed: adaptive-lease-time-threshold:"
" 0 is invalid, it must be greater than 0.0 and less than or equal to 1.0"
},
{"too big", 1.05,
"subnet configuration failed: adaptive-lease-time-threshold:"
" 1.05 is invalid, it must be greater than 0.0 and less than or equal to 1.0"
}
};
// First we create a set of elements that provides all
// required for a Subnet6.
std::string json =
" {"
" \"id\": 1,\n"
" \"subnet\": \"2001:db8:1::/64\", \n"
" \"interface\": \"\", \n"
" \"renew-timer\": 100, \n"
" \"rebind-timer\": 200, \n"
" \"valid-lifetime\": 300, \n"
" \"client-classes\": [], \n"
" \"evaluate-additional-classes\": [], \n"
" \"reservations-global\": false, \n"
" \"reservations-in-subnet\": true, \n"
" \"reservations-out-of-pool\": false \n"
" }";
data::ElementPtr elems;
ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
<< "invalid JSON:" << json << "\n test is broken";
// Iterate over the test scenarios, verifying each prescribed
// outcome.
for (auto const& test : tests) {
{
SCOPED_TRACE("test: " + test.label);
// Set this scenario's configuration parameters
elems->set("adaptive-lease-time-threshold",
data::Element::create(test.threshold));
Subnet6Ptr subnet;
try {
// Attempt to parse the configuration.
Subnet6ConfigParser parser;
subnet = parser.parse(elems);
} catch (const std::exception& ex) {
if (!test.error_message.empty()) {
// We expected a failure, did we fail the correct way?
EXPECT_EQ(test.error_message, ex.what());
} else {
// Should not have failed.
ADD_FAILURE() << "Scenario should not have failed: " << ex.what();
}
// Either way we're done with this scenario.
continue;
}
// We parsed correctly, make sure the values are right.
EXPECT_TRUE(util::areDoublesEquivalent(test.threshold,
subnet->getAdaptiveLeaseTimeThreshold()));
}
}
}
// This test verifies that the optional interface check works as expected.
TEST(CfgSubnets6Test, iface) {
// Create a configuration.

View File

@ -78,157 +78,157 @@ public:
// Verifies valid permutations of ddns-ttl-percent, ddns-ttl,
// ddns-ttl-min, and ddns-ttl-max values for SubnetX.
template<typename ParserType, typename NetworkPtrType>
void validDdnsTtlParmatersSubnet(int family) {
struct Scenario {
size_t line_no_;
std::string json_;
double ddns_ttl_percent_;
uint32_t ddns_ttl_;
uint32_t ddns_ttl_min_;
uint32_t ddns_ttl_max_;
};
void validDdnsTtlParmatersSubnet(int family) {
struct Scenario {
size_t line_no_;
std::string json_;
double ddns_ttl_percent_;
uint32_t ddns_ttl_;
uint32_t ddns_ttl_min_;
uint32_t ddns_ttl_max_;
};
std::list<Scenario> scenarios = {
{
__LINE__,
R"^({
"id": 1,
"ddns-ttl": 100
})^",
0.0, 100, 0, 0
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-percent": 5.0
})^",
5.0, 0, 0, 0
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-min": 25
})^",
0.0, 0, 25, 0
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-max": 150
})^",
0.0, 0, 0, 150
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-min": 25,
"ddns-ttl-max": 150
})^",
0.0, 0, 25, 150
},{
__LINE__,
R"^({
"id": 1, "subnet": "192.0.2.0/24",
"ddns-ttl-percent": 5.0,
"ddns-ttl-min": 25,
"ddns-ttl-max": 150
})^",
5.0, 0, 25, 150
}};
std::list<Scenario> scenarios = {
{
__LINE__,
R"^({
"id": 1,
"ddns-ttl": 100
})^",
0.0, 100, 0, 0
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-percent": 5.0
})^",
5.0, 0, 0, 0
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-min": 25
})^",
0.0, 0, 25, 0
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-max": 150
})^",
0.0, 0, 0, 150
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-min": 25,
"ddns-ttl-max": 150
})^",
0.0, 0, 25, 150
},{
__LINE__,
R"^({
"id": 1, "subnet": "192.0.2.0/24",
"ddns-ttl-percent": 5.0,
"ddns-ttl-min": 25,
"ddns-ttl-max": 150
})^",
5.0, 0, 25, 150
}};
ElementPtr subnet_elem = Element::create(family == AF_INET ?
"192.0.2.0/24" : "2001:db8::/64");
for (const auto& scenario : scenarios) {
std::stringstream oss;
oss << "scenario at " << scenario.line_no_;
SCOPED_TRACE(oss.str());
for (const auto& scenario : scenarios) {
std::stringstream oss;
oss << "scenario at " << scenario.line_no_;
SCOPED_TRACE(oss.str());
// Parse configuration specified above.
ElementPtr config_element;
ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_));
// Parse configuration specified above.
ElementPtr config_element;
ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_));
config_element->set("subnet", subnet_elem);
ParserType parser(family);
ParserType parser(family);
NetworkPtrType subnet;
NetworkPtrType subnet;
ASSERT_NO_THROW_LOG(subnet = parser.parse(config_element));
ASSERT_TRUE(subnet);
ASSERT_NO_THROW_LOG(subnet = parser.parse(config_element));
ASSERT_TRUE(subnet);
EXPECT_EQ(subnet->getDdnsTtlPercent().unspecified(), (scenario.ddns_ttl_percent_ == 0.0));
EXPECT_EQ(subnet->getDdnsTtlPercent(), scenario.ddns_ttl_percent_);
EXPECT_EQ(subnet->getDdnsTtlPercent().unspecified(), (scenario.ddns_ttl_percent_ == 0.0));
EXPECT_EQ(subnet->getDdnsTtlPercent(), scenario.ddns_ttl_percent_);
EXPECT_EQ(subnet->getDdnsTtl().unspecified(), (scenario.ddns_ttl_ == 0));
EXPECT_EQ(subnet->getDdnsTtl(), scenario.ddns_ttl_);
EXPECT_EQ(subnet->getDdnsTtl().unspecified(), (scenario.ddns_ttl_ == 0));
EXPECT_EQ(subnet->getDdnsTtl(), scenario.ddns_ttl_);
EXPECT_EQ(subnet->getDdnsTtlMin().unspecified(), (scenario.ddns_ttl_min_ == 0));
EXPECT_EQ(subnet->getDdnsTtlMin(), scenario.ddns_ttl_min_);
EXPECT_EQ(subnet->getDdnsTtlMin().unspecified(), (scenario.ddns_ttl_min_ == 0));
EXPECT_EQ(subnet->getDdnsTtlMin(), scenario.ddns_ttl_min_);
EXPECT_EQ(subnet->getDdnsTtlMax().unspecified(), (scenario.ddns_ttl_max_ == 0));
EXPECT_EQ(subnet->getDdnsTtlMax(), scenario.ddns_ttl_max_);
}
}
EXPECT_EQ(subnet->getDdnsTtlMax().unspecified(), (scenario.ddns_ttl_max_ == 0));
EXPECT_EQ(subnet->getDdnsTtlMax(), scenario.ddns_ttl_max_);
}
}
// Verifies invalid permutations of ddns-ttl-percent, ddns-ttl,
// ddns-ttl-min, and ddns-ttl-max values for SubnetX.
// Verifies invalid permutations of ddns-ttl-percent, ddns-ttl,
// ddns-ttl-min, and ddns-ttl-max values for SubnetX.
template<typename ParserType>
void invalidDdnsTtlParmatersSubnet(int family) {
struct Scenario {
size_t line_no_;
std::string json_;
std::string exp_message_;
};
void invalidDdnsTtlParmatersSubnet(int family) {
struct Scenario {
size_t line_no_;
std::string json_;
std::string exp_message_;
};
std::list<Scenario> scenarios = {
{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-percent": 5.0,
"ddns-ttl": 100
})^",
"subnet configuration failed: cannot specify both ddns-ttl-percent and ddns-ttl"
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl": 100,
"ddns-ttl-min": 25
})^",
"subnet configuration failed: cannot specify both ddns-ttl-min and ddns-ttl"
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl": 100,
"ddns-ttl-max": 150
})^",
"subnet configuration failed: cannot specify both ddns-ttl-max and ddns-ttl"
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-min": 150,
"ddns-ttl-max": 25
})^",
"subnet configuration failed: ddns-ttl-max: 25 must be greater than ddns-ttl-min: 150"
}};
std::list<Scenario> scenarios = {
{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-percent": 5.0,
"ddns-ttl": 100
})^",
"subnet configuration failed: cannot specify both ddns-ttl-percent and ddns-ttl"
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl": 100,
"ddns-ttl-min": 25
})^",
"subnet configuration failed: cannot specify both ddns-ttl-min and ddns-ttl"
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl": 100,
"ddns-ttl-max": 150
})^",
"subnet configuration failed: cannot specify both ddns-ttl-max and ddns-ttl"
},{
__LINE__,
R"^({
"id": 1,
"ddns-ttl-min": 150,
"ddns-ttl-max": 25
})^",
"subnet configuration failed: ddns-ttl-max: 25 must be greater than ddns-ttl-min: 150"
}};
ElementPtr subnet_elem = Element::create(family == AF_INET ?
"192.0.2.0/24" : "2001:db8::/64");
for (const auto& scenario : scenarios) {
std::stringstream oss;
oss << "scenario at " << scenario.line_no_;
SCOPED_TRACE(oss.str());
for (const auto& scenario : scenarios) {
std::stringstream oss;
oss << "scenario at " << scenario.line_no_;
SCOPED_TRACE(oss.str());
// Parse configuration specified above.
ElementPtr config_element;
ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_));
// Parse configuration specified above.
ElementPtr config_element;
ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_));
config_element->set("subnet", subnet_elem);
ParserType parser(family);
ASSERT_THROW_MSG(parser.parse(config_element), DhcpConfigError, scenario.exp_message_);
}
}
ParserType parser(family);
ASSERT_THROW_MSG(parser.parse(config_element), DhcpConfigError, scenario.exp_message_);
}
}
/// @brief Tests valid DDNS parameters in v4 or v6 pools.
///
@ -241,7 +241,7 @@ public:
/// @param pool1 string pool specification for the first pool
/// @param pool2 string pool specification for the first pool
template<typename PoolListParserType>
void validPoolDdnsParameters(const std::string& pool1, const std::string& pool2) {
void validPoolDdnsParameters(const std::string& pool1, const std::string& pool2) {
std::stringstream ss;
ss <<
@ -456,8 +456,8 @@ public:
/// @brief Sets the Hooks path from which hooks can be loaded.
/// @param custom_path path to use as the hooks path.
void setHooksTestPath(const std::string explicit_path = "") {
HooksLibrariesParser::getHooksPath(true,
(!explicit_path.empty() ?
HooksLibrariesParser::getHooksPath(true,
(!explicit_path.empty() ?
explicit_path : DHCPSRV_HOOKS_TEST_PATH));
}
@ -4472,12 +4472,12 @@ TEST_F(DhcpParserTest, invalidDdnsTtlParmatersSubnet6) {
// Verifies valid DDNS parameters in v4 pools.
TEST_F(DhcpParserTest, validDdnsParmatersPool4) {
validPoolDdnsParameters<Pools4ListParser>("192.0.1.0/24", "192.0.2.0/24");
validPoolDdnsParameters<Pools4ListParser>("192.0.1.0/24", "192.0.2.0/24");
}
// Verifies valid DDNS parameters in v6 pools.
TEST_F(DhcpParserTest, validDdnsParmatersPool6) {
validPoolDdnsParameters<Pools6ListParser>("2001:db8:1::/64", "2001:db8:2::/64");
validPoolDdnsParameters<Pools6ListParser>("2001:db8:1::/64", "2001:db8:2::/64");
}
} // Anonymous namespace

View File

@ -39,12 +39,15 @@ TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeaseV4) {
// Add the first free lease. The pool should now have one free lease
// that is always offered.
EXPECT_FALSE(state->isFreeLease(IOAddress("192.0.2.1")));
state->addFreeLease(IOAddress("192.0.2.1"));
EXPECT_FALSE(state->exhausted());
EXPECT_EQ(1, state->getFreeLeaseCount());
EXPECT_TRUE(state->isFreeLease(IOAddress("192.0.2.1")));
// The same lease is always offered.
EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
EXPECT_TRUE(state->isFreeLease(IOAddress("192.0.2.1")));
// Add another free lease. We should now have two free leases.
state->addFreeLease(IOAddress("192.0.2.3"));
@ -55,9 +58,12 @@ TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeaseV4) {
EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
// Now, the second lease should be offered.
EXPECT_EQ("192.0.2.3", state->offerFreeLease().toText());
EXPECT_TRUE(state->isFreeLease(IOAddress("192.0.2.1")));
EXPECT_TRUE(state->isFreeLease(IOAddress("192.0.2.3")));
// Try to delete a non-existing lease. It should not affect the
// existing leases.
EXPECT_FALSE(state->isFreeLease(IOAddress("192.0.2.2")));
state->deleteFreeLease(IOAddress("192.0.2.2"));
EXPECT_FALSE(state->exhausted());
EXPECT_EQ(2, state->getFreeLeaseCount());
@ -126,12 +132,15 @@ TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeaseNA) {
// Add the first free lease. The pool should now have one free lease
// that is always offered.
EXPECT_FALSE(state->isFreeLease(IOAddress("2001:db8:1::1")));
state->addFreeLease(IOAddress("2001:db8:1::1"));
EXPECT_FALSE(state->exhausted());
EXPECT_EQ(1, state->getFreeLeaseCount());
EXPECT_TRUE(state->isFreeLease(IOAddress("2001:db8:1::1")));
// The same lease is always offered.
EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
EXPECT_TRUE(state->isFreeLease(IOAddress("2001:db8:1::1")));
// Add another free lease. We should now have two free leases.
state->addFreeLease(IOAddress("2001:db8:1::3"));
@ -142,9 +151,12 @@ TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeaseNA) {
EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
// Now, the second lease should be offered.
EXPECT_EQ("2001:db8:1::3", state->offerFreeLease().toText());
EXPECT_TRUE(state->isFreeLease(IOAddress("2001:db8:1::1")));
EXPECT_TRUE(state->isFreeLease(IOAddress("2001:db8:1::3")));
// Try to delete a non-existing lease. It should not affect the
// existing leases.
EXPECT_FALSE(state->isFreeLease(IOAddress("2001:db8:1::2")));
state->deleteFreeLease(IOAddress("2001:db8:1::2"));
EXPECT_FALSE(state->exhausted());
EXPECT_EQ(2, state->getFreeLeaseCount());
@ -211,11 +223,15 @@ TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeasePD) {
// Add the first free lease. The pool should now have one free lease
// that is always offered.
EXPECT_FALSE(state->isFreeLease(IOAddress("3000::5600")));
state->addFreeLease(IOAddress("3000::5600"));
EXPECT_FALSE(state->exhausted());
EXPECT_EQ(1, state->getFreeLeaseCount());
EXPECT_TRUE(state->isFreeLease(IOAddress("3000::5600")));
// The same lease is always offered.
EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
EXPECT_TRUE(state->isFreeLease(IOAddress("3000::5600")));
// Add another free lease. We should now have two free leases.
state->addFreeLease(IOAddress("3000::7800"));
@ -226,9 +242,12 @@ TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeasePD) {
EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
// Now, the second lease should be offered.
EXPECT_EQ("3000::7800", state->offerFreeLease().toText());
EXPECT_TRUE(state->isFreeLease(IOAddress("3000::5600")));
EXPECT_TRUE(state->isFreeLease(IOAddress("3000::7800")));
// Try to delete a non-existing lease. It should not affect the
// existing leases.
EXPECT_FALSE(state->isFreeLease(IOAddress("3000::6400")));
state->deleteFreeLease(IOAddress("3000::6400"));
EXPECT_FALSE(state->exhausted());
EXPECT_EQ(2, state->getFreeLeaseCount());
@ -274,5 +293,4 @@ TEST(PoolFreeLeaseAllocationState, addFreeLeasPDSeveralTimes) {
EXPECT_TRUE(state->exhausted());
}
} // end of anonymous namespace

View File

@ -65,6 +65,11 @@ TEST_F(FreeLeaseQueueAllocatorTest4, populateFreeAddressLeases) {
ASSERT_TRUE(pool_state);
EXPECT_FALSE(pool_state->exhausted());
double r = alloc.getOccupancyRate(IOAddress("192.0.2.101"), cc_);
EXPECT_EQ(.6, r);
r = alloc.getOccupancyRate(IOAddress("192.0.2.1"), cc_);
EXPECT_EQ(0., r);
std::set<IOAddress> addresses;
for (auto i = 0; i < 5; ++i) {
auto lease = pool_state->offerFreeLease();
@ -124,6 +129,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_);
EXPECT_EQ(1., r);
auto i = 0;
for (auto const& address_lease : leases) {
if (i % 2) {
@ -132,6 +140,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithAllocations) {
++i;
}
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_);
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 +153,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_);
EXPECT_EQ(1., r);
}
// Test allocating IPv4 addresses and re-allocating these that are
@ -170,6 +184,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_);
EXPECT_EQ(1., r);
auto i = 0;
for (auto const& address_lease : leases) {
if (i % 2) {
@ -179,6 +196,9 @@ TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithReclamations) {
}
++i;
}
r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_);
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 +207,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_);
EXPECT_EQ(1., r);
}
// Test allocating DHCPv4 leases for many pools in a subnet.
@ -213,6 +235,10 @@ TEST_F(FreeLeaseQueueAllocatorTest4, manyPools) {
auto& lease_mgr = LeaseMgrFactory::instance();
double r = alloc.getOccupancyRate(IOAddress("192.0.2.100"), cc_);
// 1/100
EXPECT_EQ(.01, r);
std::set<IOAddress> addresses_set;
std::vector<IOAddress> addresses_vector;
std::vector<PoolPtr> pools_vector;
@ -232,6 +258,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_);
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 +292,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_);
EXPECT_EQ(0., r);
}
// Test that the allocator respects client class guards.
@ -304,6 +337,9 @@ 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_);
// 1/20
EXPECT_EQ(.05, 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,17 @@ TEST_F(FreeLeaseQueueAllocatorTest4, clientClasses) {
}
EXPECT_EQ(20, addresses_set.size());
r = alloc.getOccupancyRate(IOAddress("192.0.2.120"), cc_);
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_);
// 21/40
EXPECT_EQ(.525, 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 +369,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_);
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_);
EXPECT_EQ(0., r);
}
/// @brief Test fixture class for the DHCPv6 Free Lease Queue allocator.
@ -382,6 +428,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_);
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 +511,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 +559,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 +736,9 @@ TEST_F(FreeLeaseQueueAllocatorTest6, populateFreePrefixDelegationLeases) {
ASSERT_TRUE(pool_state);
EXPECT_FALSE(pool_state->exhausted());
double r = alloc.getOccupancyRate(IOAddress("2001:db8:2::"), 128, cc_);
EXPECT_EQ(5. / 256., r);
std::set<IOAddress> addresses;
for (auto i = 0; i < 256; ++i) {
auto lease = pool_state->offerFreeLease();
@ -721,9 +774,12 @@ 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_);
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 +813,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_);
EXPECT_EQ(1., r);
auto i = 0;
for (auto const& address_lease : leases) {
@ -765,6 +823,8 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithAllocations) {
}
++i;
}
r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_);
EXPECT_EQ(.5, r);
for (auto j = 0; j < 128; ++j) {
candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
@ -776,9 +836,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_);
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 +875,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_);
EXPECT_EQ(1., r);
auto i = 0;
for (auto const& address_lease : leases) {
@ -822,6 +887,8 @@ TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithReclamations) {
}
++i;
}
r = alloc.getOccupancyRate(IOAddress("3000::"), 128, cc_);
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 +901,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_);
EXPECT_EQ(1., r);
}
// Test allocating delegated prefixes from multiple pools.
TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPools) {
@ -869,6 +938,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_);
EXPECT_EQ(1., r);
}
// Test allocating delegated prefixes from multiple pools.
@ -890,7 +962,6 @@ TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferLower) {
ASSERT_NO_THROW(alloc.initAfterConfigure());
auto& lease_mgr = LeaseMgrFactory::instance();
Pool6Ptr pool;
std::set<IOAddress> prefixes;
@ -904,6 +975,13 @@ 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_);
EXPECT_EQ(1., r);
r = alloc.getOccupancyRate(IOAddress("2001:db8:1:2::"), 128, cc_);
EXPECT_EQ(65536. / 68096., r);
r = alloc.getOccupancyRate(IOAddress("2001:db8:1:2::"), 64, cc_);
EXPECT_EQ(0., r);
}
// Test allocating delegated prefixes from multiple pools.
@ -938,6 +1016,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_);
EXPECT_EQ(2560. / 68096., r);
}
// Test allocating delegated prefixes from multiple pools.
@ -972,6 +1052,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_);
EXPECT_EQ(2560. / 68096., r);
}
// Test that the allocator respects client class guards.
@ -1008,8 +1090,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_);
EXPECT_EQ(1., r);
cc_.insert("foo");
r = alloc.getOccupancyRate(IOAddress("3000:1::"), 128, cc_);
EXPECT_EQ(256. / 65792., r);
cc_.clear();
r = alloc.getOccupancyRate(IOAddress("3000:1::"), 128, cc_);
EXPECT_EQ(0., r);
}
} // end of isc::dhcp::test namespace
} // end of isc::dhcp namespace

View File

@ -186,6 +186,7 @@ TEST_F(NetworkTest, inheritanceSupport4) {
globals_->set("ddns-ttl", Element::create(400));
globals_->set("ddns-ttl-min", Element::create(200));
globals_->set("ddns-ttl-max", Element::create(600));
globals_->set("adaptive-lease-time-threshold", Element::create(.90));
// For each parameter for which inheritance is supported run
// the test that checks if the values are inherited properly.
@ -393,6 +394,12 @@ TEST_F(NetworkTest, inheritanceSupport4) {
&Network::setDdnsTtlMax,
500, 600);
}
{
SCOPED_TRACE("adaptive-lease-time-threshold");
testNetworkInheritance<TestNetwork4>(&Network::getAdaptiveLeaseTimeThreshold,
&Network::setAdaptiveLeaseTimeThreshold,
.80, .90);
}
}
// This test verifies that the inheritance is supported for DHCPv6
@ -420,6 +427,7 @@ TEST_F(NetworkTest, inheritanceSupport6) {
globals_->set("ddns-ttl-max", Element::create(600));
globals_->set("cache-threshold", Element::create(.35));
globals_->set("cache-max-age", Element::create(20));
globals_->set("adaptive-lease-time-threshold", Element::create(.90));
// For each parameter for which inheritance is supported run
// the test that checks if the values are inherited properly.
@ -558,6 +566,12 @@ TEST_F(NetworkTest, inheritanceSupport6) {
&Network::setCacheMaxAge,
10, 20);
}
{
SCOPED_TRACE("adaptive-lease-time-threshold");
testNetworkInheritance<TestNetwork4>(&Network::getAdaptiveLeaseTimeThreshold,
&Network::setAdaptiveLeaseTimeThreshold,
.80, .90);
}
// Interface-id requires special type of test.
boost::shared_ptr<TestNetwork6> net_child(new TestNetwork6());

View File

@ -299,6 +299,7 @@ public:
" \"store-extended-info\": true,"
" \"cache-threshold\": 0.123,"
" \"cache-max-age\": 123,"
" \"adaptive-lease-time-threshold\": .90,"
" \"offer-lifetime\": 777,"
" \"ddns-update-on-renew\": true,"
" \"option-data\": ["
@ -336,7 +337,8 @@ public:
" \"hostname-char-set\": \"\","
" \"cache-threshold\": .20,"
" \"cache-max-age\": 50,"
" \"allocator\": \"random\""
" \"allocator\": \"random\","
" \"adaptive-lease-time-threshold\": .80"
" },"
" {"
" \"id\": 2,"
@ -362,7 +364,8 @@ public:
" \"t1-percent\": .40,"
" \"t2-percent\": .80,"
" \"cache-threshold\": 0.0,"
" \"cache-max-age\": 0"
" \"cache-max-age\": 0,"
" \"adaptive-lease-time-threshold\": .70"
" }"
" ]"
"}";
@ -432,6 +435,7 @@ TEST_F(SharedNetwork4ParserTest, parse) {
EXPECT_EQ(777, network->getOfferLft().get());
EXPECT_TRUE(network->getDdnsUpdateOnRenew().get());
EXPECT_EQ("iterative", network->getAllocatorType().get());
EXPECT_EQ(.90, network->getAdaptiveLeaseTimeThreshold().get());
// Relay information.
auto relay_info = network->getRelayInfo();
@ -730,6 +734,7 @@ public:
" \"ddns-update-on-renew\": true,"
" \"allocator\": \"random\","
" \"pd-allocator\": \"iterative\","
" \"adaptive-lease-time-threshold\": .90,"
" \"option-data\": ["
" {"
" \"name\": \"dns-servers\","
@ -845,6 +850,7 @@ TEST_F(SharedNetwork6ParserTest, parse) {
EXPECT_TRUE(network->getDdnsUpdateOnRenew().get());
EXPECT_EQ("random", network->getAllocatorType().get());
EXPECT_EQ("iterative", network->getPdAllocatorType().get());
EXPECT_EQ(.90, network->getAdaptiveLeaseTimeThreshold().get());
// Relay information.
auto relay_info = network->getRelayInfo();

View File

@ -246,7 +246,7 @@ public:
/// @return validated path as a string (supported path + input file name)
///
/// @throw BadValue if the input path does not include a file name.
/// @trhow SecurityError if the parent path does not path the supported path and
/// @throw SecurityError if the parent path does not path the supported path and
/// security is being enforced, SecurityWarn if it is not being enforced.
std::string validatePath(const std::string input_path_str,
bool enforce_path = shouldEnforceSecurity()) const;