-// Copyright (C) 2017-2024 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-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
return;
}
+ int attempted = 0;
+ int failed = 0;
for (auto lease : *leases) {
try {
/// @todo - Users might want to only update NA or PD leases.
/// both NA and PD leases.
// Only update a lease if its active.
if (lease->valid_lft_) {
+ ++attempted;
if (mgr->evaluateVariables(query, response, lease)) {
LeaseMgrFactory::instance().updateLease6(lease);
}
}
- /// @todo - for now if any leases fail we stop. This could lead
- /// to inconsistencies in user-context content for leases belonging
- /// the the same response. The lease6BulkApplyHandler() accumlates
- /// failures but iterates over all the leases..
} catch (const NoSuchLease&) {
- isc_throw(LeaseCmdsConflict, "failed to update"
- " the lease with address " << lease->addr_ <<
- " either because the lease has been"
- " deleted or it has changed in the database");
+ ++failed;
+ LOG_ERROR(lease_cmds_logger, LEASE_CMDS_LEASES6_COMMITTED_CONFLICT)
+ .arg("WTF") //lease->addr_.toText())
+ .arg(query->getLabel());
} catch (const std::exception& ex) {
- isc_throw(Unexpected, "evaluating binding variables failed for: "
- << query->getLabel() << ", :" << ex.what());
+ ++failed;
+ LOG_ERROR(lease_cmds_logger, LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR)
+ .arg(query->getLabel())
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
}
}
+
+ if (failed) {
+ isc_throw(Unexpected, failed << " out of " << attempted
+ << " leases failed to update for "
+ << query->getLabel());
+ }
}
int
-// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-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
extern const isc::log::MessageID LEASE_CMDS_INIT_OK = "LEASE_CMDS_INIT_OK";
extern const isc::log::MessageID LEASE_CMDS_LEASE4_OFFER_FAILED = "LEASE_CMDS_LEASE4_OFFER_FAILED";
extern const isc::log::MessageID LEASE_CMDS_LEASES4_COMMITTED_FAILED = "LEASE_CMDS_LEASES4_COMMITTED_FAILED";
+extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_CONFLICT = "LEASE_CMDS_LEASES6_COMMITTED_CONFLICT";
extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_FAILED = "LEASE_CMDS_LEASES6_COMMITTED_FAILED";
+extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR = "LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR";
extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4 = "LEASE_CMDS_RESEND_DDNS4";
extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4_FAILED = "LEASE_CMDS_RESEND_DDNS4_FAILED";
extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS6 = "LEASE_CMDS_RESEND_DDNS6";
"LEASE_CMDS_GET4_FAILED", "lease4-get command failed (parameters: %1, reason: %2)",
"LEASE_CMDS_GET6_FAILED", "lease6-get command failed (parameters: %1, reason: %2)",
"LEASE_CMDS_INIT_OK", "loading Lease Commands hooks library successful",
- "LEASE_CMDS_LEASE4_OFFER_FAILED", "processing error occurred evaluating binding variables:%1",
+ "LEASE_CMDS_LEASE4_OFFER_FAILED", "processing error occurred evaluating binding variables: %1",
"LEASE_CMDS_LEASES4_COMMITTED_FAILED", "processing error occurred evaluating binding variables: %1",
- "LEASE_CMDS_LEASES6_COMMITTED_FAILED", "processing error occurred evaluating binding variables: %1",
+ "LEASE_CMDS_LEASES6_COMMITTED_CONFLICT", "could not updating lease: %1 for: %2",
+ "LEASE_CMDS_LEASES6_COMMITTED_FAILED", "reason: %1",
+ "LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR", "evaluating binding-variables for lease: %1 for: %2, reason: %3",
"LEASE_CMDS_RESEND_DDNS4", "lease4-resend-ddns command successful: %1",
"LEASE_CMDS_RESEND_DDNS4_FAILED", "lease4-resend-ddns command failed: %1",
"LEASE_CMDS_RESEND_DDNS6", "lease6-resend-ddns command successful: %1",
extern const isc::log::MessageID LEASE_CMDS_INIT_OK;
extern const isc::log::MessageID LEASE_CMDS_LEASE4_OFFER_FAILED;
extern const isc::log::MessageID LEASE_CMDS_LEASES4_COMMITTED_FAILED;
+extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_CONFLICT;
extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_FAILED;
+extern const isc::log::MessageID LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR;
extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4;
extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS4_FAILED;
extern const isc::log::MessageID LEASE_CMDS_RESEND_DDNS6;
This info message indicates that the Lease Commands hooks library has been
loaded successfully. Enjoy!
+% LEASE_CMDS_LEASE4_OFFER_FAILED processing error occurred evaluating binding variables: %1
+This error log is emitted when an error occurs in the lease4_offer
+handler is invoked. The argument provides an explanation.
+
+% LEASE_CMDS_LEASES4_COMMITTED_FAILED processing error occurred evaluating binding variables: %1
+This error log is emitted when an error occurs in the leases4_committed
+handler is invoked. The argument provides an explanation.
+
+% LEASE_CMDS_LEASES6_COMMITTED_CONFLICT could not updating lease: %1 for: %2
+This error log is emitted by the leases6_committed callback when attempting
+to update a lease with new binding-variable values but a conflicting change
+has occurred rendering the update invalid. The arguments provide the lease
+address and the query details.
+
+% LEASE_CMDS_LEASES6_COMMITTED_FAILED reason: %1
+This error log is emitted when one or more leases associated with a client
+query failed to be updated with binding-variable values. The argument
+provides details. Individual errors for each lease should precede this log.
+
+% LEASE_CMDS_LEASES6_COMMITTED_LEASE_ERROR evaluating binding-variables for lease: %1 for: %2, reason: %3
+This error log is emitted by the leases6_committed callback when an
+unexpected error occurs evaluating the binding-variables for a given
+lease. The arguments provide the lease address, the query details, and
+an error explanation.
+
% LEASE_CMDS_RESEND_DDNS4 lease4-resend-ddns command successful: %1
A request to update DNS for the requested IPv4 lease has been
successfully queued for transmission to kea-dhcp-ddns.
% LEASE_CMDS_WIPE6_FAILED lease6-wipe command failed (parameters: %1, reason: %2)
The lease6-wipe command has failed. Both the reason as well as the
parameters passed are logged.
-
-% LEASE_CMDS_LEASE4_OFFER_FAILED processing error occurred evaluating binding variables:%1
-This debug log is emitted when an error occurs in the lease4_offer
-handler is invoked. The argument provides an explanation.
-
-% LEASE_CMDS_LEASES4_COMMITTED_FAILED processing error occurred evaluating binding variables: %1
-This debug log is emitted when an error occurs in the leases4_committed
-handler is invoked. The argument provides an explanation.
-
-% LEASE_CMDS_LEASES6_COMMITTED_FAILED processing error occurred evaluating binding variables: %1
-This debug log is emitted when an error occurs in the leases6_committed
-handler is invoked. The argument provides an explanation.
hook_load_unittests_SOURCES =
hook_load_unittests_SOURCES += load_unload_unittests.cc
+hook_load_unittests_SOURCES += lease_cmds_unittest.cc lease_cmds_unittest.h
+hook_load_unittests_SOURCES += lease_cmds4_unittest.cc
+hook_load_unittests_SOURCES += lease_cmds6_unittest.cc
hook_load_unittests_SOURCES += run_unittests.cc
hook_load_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
hook_load_unittests_CXXFLAGS = $(AM_CXXFLAGS)
/// @brief Check that lease4-write works as expected.
void testLease4Write();
-
- /// @brief Check that lease4_offer handler works as expected with
- /// valid inputs.
- void testValidLease4Offer();
-
- /// @brief Check that leases4_committed handler works as expected with
- /// valid inputs.
- void testValidLeases4Committed();
-
- /// @brief Check that leases4_committed handler does not throw or alter
- /// the lease under NOP conditions:
- /// 1. There is no repsonse packet
- /// 2. Response packet is a DHCPACK
- /// 3. There is no active lease
- void testNopLeases4Committed();
};
void Lease4CmdsTest::testLease4AddMissingParams() {
testCommand(txt, CONTROL_RESULT_ERROR, exp_rsp);
}
-void
-Lease4CmdsTest::testValidLease4Offer() {
- // Initialize lease manager (false = v4, true = add leases)
- initLeaseMgr(false, true);
-
- struct Scenario {
- uint32_t line_;
- std::string config_;
- std::string orig_context_;
- std::string exp_context_;
- uint32_t offer_lifetime_;
- };
-
- std::list<Scenario> scenarios = {
- {
- // No variables configured, nothing in lease context.
- __LINE__,
- R"({})",
- R"({})",
- R"({})",
- 500
- },
- {
- // lease context has no binding-variables, two configured
- __LINE__,
- R"^({"binding-variables":[
- {
- "name": "hwaddr",
- "expression": "hexstring(pkt4.mac,':')",
- "source": "query"
- },
- {
- "name": "yiaddr",
- "expression": "addrtotext(pkt4.yiaddr)",
- "source": "response"
- }]})^",
- R"({})",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": "01:02:03:04:05:06",
- "yiaddr": "192.0.2.1"
- }
- }})",
- 500
- },
- {
- // lease context has no binding-variables, two configured
- // offer lifetime is 0.
- __LINE__,
- R"^({"binding-variables":[
- {
- "name": "hwaddr",
- "expression": "hexstring(pkt4.mac,':')",
- "source": "query"
- },
- {
- "name": "yiaddr",
- "expression": "addrtotext(pkt4.yiaddr)",
- "source": "response"
- }]})^",
- R"({})",
- R"({})",
- 0
- },
- {
- // lease context has binding-variables, none configured
- // Current logic leaves lease untouched.
- __LINE__,
- R"({})",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": "01:02:03:04:05:06",
- "yiaddr": "192.0.2.1"
- }
- }})",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": "01:02:03:04:05:06",
- "yiaddr": "192.0.2.1"
- }
- }})",
- 500
- },
- {
- // Evaluated variable value is an empty string.
- __LINE__,
- R"^({"binding-variables":[
- {
- "name": "hwaddr",
- "expression": "''",
- "source": "query"
- }]})^",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": "01:02:03:04:05:06"
- }
- }})",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": ""
- }
- }})",
- 500
- }};
-
- // Create packet pair and lease.
- Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
- query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
-
- Pkt4Ptr response(new Pkt4(DHCPOFFER, 1234));
- IOAddress yiaddr("192.0.2.1");
- response->setYiaddr(yiaddr);
-
- // Iterater over scenarios.
- for (auto const& scenario : scenarios) {
- SCOPED_LINE(scenario.line_);
-
- // Create and configure the manager.
- BindingVariableMgrPtr mgr;
- ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
- ConstElementPtr config;
- ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
- ASSERT_NO_THROW_LOG(mgr->configure(config));
-
- // Fetch the lease and set its user-context to the original content.
- Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
- ASSERT_TRUE(orig_lease);
- ConstElementPtr orig_context;
- ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
- orig_lease->setContext(orig_context);
- ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
-
- Lease4CollectionPtr leases(new Lease4Collection());
- leases->push_back(orig_lease);
-
- // Create a callout handle and add the expected arguments.
- CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
- callout_handle->setArgument("query4", query);
- callout_handle->setArgument("response4", response);
- callout_handle->setArgument("leases4", leases);
- callout_handle->setArgument("offer_lifetime", scenario.offer_lifetime_);
-
- // Invoke the leases4Committed handler.
- LeaseCmds cmds;
- ASSERT_NO_THROW_LOG(cmds.lease4Offer(*callout_handle, mgr));
-
- // Fetch the lease.
- Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
- ASSERT_TRUE(after_lease);
-
- // Context contents should match the expected context content.
- ConstElementPtr exp_context;
- ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
- ASSERT_EQ(*(after_lease->getContext()), *exp_context);
- }
-}
-
-void
-Lease4CmdsTest::testValidLeases4Committed() {
- // Initialize lease manager (false = v4, true = add leases)
- initLeaseMgr(false, true);
-
- struct Scenario {
- uint32_t line_;
- std::string config_;
- std::string orig_context_;
- std::string exp_context_;
- };
-
- std::list<Scenario> scenarios = {
- {
- // No variables configured, nothing in lease context.
- __LINE__,
- R"({})",
- R"({})",
- R"({})"
- },
- {
- // lease context has no binding-variables, two configured
- __LINE__,
- R"^({"binding-variables":[
- {
- "name": "hwaddr",
- "expression": "hexstring(pkt4.mac,':')",
- "source": "query"
- },
- {
- "name": "yiaddr",
- "expression": "addrtotext(pkt4.yiaddr)",
- "source": "response"
- }]})^",
- R"({})",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": "01:02:03:04:05:06",
- "yiaddr": "192.0.2.1"
- }
- }})",
- },
- {
- // lease context has binding-variables, none configured
- // Current logic leaves lease untouched.
- __LINE__,
- R"({})",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": "01:02:03:04:05:06",
- "yiaddr": "192.0.2.1"
- }
- }})",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": "01:02:03:04:05:06",
- "yiaddr": "192.0.2.1"
- }
- }})",
- },
- {
- // Evaluated variable value is an empty string.
- __LINE__,
- R"^({"binding-variables":[
- {
- "name": "hwaddr",
- "expression": "''",
- "source": "query"
- }]})^",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": "01:02:03:04:05:06"
- }
- }})",
- R"({"ISC":{
- "binding-variables":{
- "hwaddr": ""
- }
- }})",
- }};
-
- // Create packet pair and lease.
- Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
- query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
-
- Pkt4Ptr response(new Pkt4(DHCPACK, 1234));
- IOAddress yiaddr("192.0.2.1");
- response->setYiaddr(yiaddr);
-
- // Iterater over scenarios.
- for (auto const& scenario : scenarios) {
- SCOPED_LINE(scenario.line_);
-
- // Create and configure the manager.
- BindingVariableMgrPtr mgr;
- ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
- ConstElementPtr config;
- ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
- ASSERT_NO_THROW_LOG(mgr->configure(config));
-
- // Fetch the lease and set its user-context to the original content.
- Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
- ASSERT_TRUE(orig_lease);
- ConstElementPtr orig_context;
- ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
- orig_lease->setContext(orig_context);
- ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
-
- Lease4CollectionPtr leases(new Lease4Collection());
- leases->push_back(orig_lease);
-
- // Create a callout handle and add the expected arguments.
- CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
- callout_handle->setArgument("query4", query);
- callout_handle->setArgument("response4", response);
- callout_handle->setArgument("leases4", leases);
-
- // Invoke the leases4Committed handler.
- LeaseCmds cmds;
- ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
-
- // Fetch the lease.
- Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
- ASSERT_TRUE(after_lease);
-
- // Context contents should match the expected context content.
- ConstElementPtr exp_context;
- ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
- ASSERT_EQ(*(after_lease->getContext()), *exp_context);
- }
-}
-
-void
-Lease4CmdsTest::testNopLeases4Committed() {
- // Initialize lease manager (false = v4, true = add leases)
- initLeaseMgr(false, true);
-
- struct Scenario {
- uint32_t line_;
- DHCPMessageType response_type_;
- bool send_lease_;
- };
-
- // Configure a single variable.
- std::string config =
- R"^({"binding-variables":[
- {
- "name": "hwaddr",
- "expression": "hexstring(pkt4.mac,':')",
- "source": "query"
- }]})^";
-
- // Create and configure the manager.
- BindingVariableMgrPtr mgr;
- ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
- ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
-
- // Scenarios should all result in no change to the lease.
- std::list<Scenario> scenarios = {
- {
- // Response is not a DHCPACK.
- __LINE__,
- DHCPNAK,
- true
- },
- {
- // No active lease.
- __LINE__,
- DHCPACK,
- false
- },
- {
- // No response.
- __LINE__,
- DHCP_NOTYPE,
- false
- }};
-
- // Create packet pair and lease.
- Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
- query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
- IOAddress yiaddr("192.0.2.1");
- for (auto const& scenario : scenarios) {
- SCOPED_LINE(scenario.line_);
-
- Pkt4Ptr response;
- if (scenario.response_type_ != DHCP_NOTYPE) {
- response.reset(new Pkt4(scenario.response_type_, 1234));
- }
-
- // Fetch the lease and set its user-context to the original content.
- Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
- ASSERT_TRUE(orig_lease);
- ASSERT_FALSE(orig_lease->getContext());
-
- Lease4CollectionPtr leases(new Lease4Collection());
- if (scenario.send_lease_) {
- leases->push_back(orig_lease);
- }
-
- // Create a callout handle and add the expected arguments.
- CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
- callout_handle->setArgument("query4", query);
- callout_handle->setArgument("response4", response);
- callout_handle->setArgument("leases4", leases);
-
- // Invoke the leases4Committed handler.
- LeaseCmds cmds;
- ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
-
- // Fetch the lease. Context should still be empty.
- Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
- ASSERT_TRUE(after_lease);
- ASSERT_FALSE(after_lease->getContext());
- }
-}
-
TEST_F(Lease4CmdsTest, lease4AddMissingParams) {
testLease4AddMissingParams();
}
testLease4Write();
}
-TEST_F(Lease4CmdsTest, validLease4Offer) {
- testValidLease4Offer();
-}
-
-TEST_F(Lease4CmdsTest, validLease4OfferMultiThreading) {
- MultiThreadingTest mt(true);
- testValidLease4Offer();
-}
-
-TEST_F(Lease4CmdsTest, validLeases4Committed) {
- testValidLeases4Committed();
-}
-
-TEST_F(Lease4CmdsTest, validLeases4CommittedMultiThreading) {
- MultiThreadingTest mt(true);
- testValidLeases4Committed();
-}
-
-TEST_F(Lease4CmdsTest, nopLeases4Committed) {
- testNopLeases4Committed();
-}
-
-TEST_F(Lease4CmdsTest, nopLeases4CommittedMultiThreading) {
- MultiThreadingTest mt(true);
- testNopLeases4Committed();
-}
} // end of anonymous namespace
/// @brief Check that lease6-write works as expected.
void testLease6Write();
+#if 0
/// @brief Check that leases6_committed handler works as expected with
/// valid inputs.
void testValidLeases6Committed();
/// 2. There are no leases
/// 3. There are leases but none active
void testNopLeases6Committed();
+
+ void testLeases6CommittedErrors();
+#endif
};
void Lease6CmdsTest::testLease6AddMissingParams() {
testCommand(txt, CONTROL_RESULT_ERROR, exp_rsp);
}
+#if 0
void
Lease6CmdsTest::testValidLeases6Committed() {
- // Initialize lease manager (false = v4, true = add leases)
+ // Initialize lease manager (true = v6, true = add leases)
initLeaseMgr(true, true);
struct Scenario {
R"({})"
},
{
- // lease context has no binding-variables, two configured
+ // Lease context has no binding-variables, two configured.
__LINE__,
R"^({"binding-variables":[
{
}})",
},
{
- // lease context has binding-variables, none configured
+ // Lease context has binding-variables, none configured.
// Current logic leaves lease untouched.
__LINE__,
R"({})",
// Create packet pair and lease.
Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
- // Add the client id.
OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
{ 0x01, 0x02, 0x03, 0x04 }));
query->addOption(client_id);
{ 0x05, 0x06, 0x07, 0x08 }));
response->addOption(subscriber_id);
- IOAddress na_addr("2001:db8:1::1");
+ // Create a list of the lease addresses.
+ std::list<IOAddress> lease_addrs;
+ lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+ lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+ lease_addrs.push_back(IOAddress("2001:db8:2::2"));
// Iterater over scenarios.
for (auto const& scenario : scenarios) {
ASSERT_NO_THROW_LOG(mgr->configure(config));
// Fetch the lease and set its user-context to the original content.
- Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
- ASSERT_TRUE(orig_lease);
- ASSERT_TRUE(orig_lease->valid_lft_ > 0);
-
- ConstElementPtr orig_context;
- ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
- orig_lease->setContext(orig_context);
- ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
-
- Lease6CollectionPtr leases(new Lease6Collection());
- leases->push_back(orig_lease);
+ Lease6CollectionPtr orig_leases(new Lease6Collection());
+ for (auto const& address : lease_addrs) {
+ Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+ ASSERT_TRUE(orig_lease);
+ ASSERT_TRUE(orig_lease->valid_lft_ > 0);
+
+ ConstElementPtr orig_context;
+ ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+ orig_lease->setContext(orig_context);
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
+ orig_leases->push_back(orig_lease);
+ }
// Create a callout handle and add the expected arguments.
CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
callout_handle->setArgument("query6", query);
callout_handle->setArgument("response6", response);
- callout_handle->setArgument("leases6", leases);
+ callout_handle->setArgument("leases6", orig_leases);
- // Invoke the leases4Committed handler.
+ // Invoke the leases6Committed handler.
LeaseCmds cmds;
ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
- // Fetch the lease.
- Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
- ASSERT_TRUE(after_lease);
+ // Iterate over the leases and make sure the user-contexts are as expected.
+ for (auto const& lease : *orig_leases) {
+ // Fetch the lease.
+ Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+ ASSERT_TRUE(after_lease);
- // Context contents should match the expected context content.
- ConstElementPtr exp_context;
- ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
- ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ // Context contents should match the expected context content.
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ }
}
}
void
Lease6CmdsTest::testNopLeases6Committed() {
- // Initialize lease manager (false = v4, true = add leases)
+ // Initialize lease manager (true = v6, true = add leases)
initLeaseMgr(true, true);
struct Scenario {
for (auto const& scenario : scenarios) {
SCOPED_LINE(scenario.line_);
+ // Create the response packet, if one.
Pkt6Ptr response;
if (scenario.response_type_ != DHCPV6_NOTYPE) {
response.reset(new Pkt6(scenario.response_type_, 1234));
callout_handle->setArgument("response6", response);
callout_handle->setArgument("leases6", leases);
- // Invoke the leases4Committed handler.
+ // Invoke the leases6Committed handler.
LeaseCmds cmds;
ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
}
}
+void
+Lease6CmdsTest::testLeases6CommittedErrors() {
+ // Initialize lease manager (false = v4, true = add leases)
+ initLeaseMgr(true, true);
+
+ // Create a config with two binding variables.
+ std::string config_str =
+ R"^({"binding-variables":[
+ {
+ "name": "duid",
+ "expression": "hexstring(option[1].hex,':')",
+ "source": "query"
+ },
+ {
+ "name": "sub-id",
+ "expression": "hexstring(option[38].hex, ':')",
+ "source": "response"
+ }]})^";
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(config_str));
+
+ // Create the expected context contents.
+ std::string exp_context_str =
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})";
+
+
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(exp_context_str));
+
+ // Create packet pair and lease.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+ { 0x01, 0x02, 0x03, 0x04 }));
+ query->addOption(client_id);
+
+ Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+ OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+ { 0x05, 0x06, 0x07, 0x08 }));
+ response->addOption(subscriber_id);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the leases.
+ std::list<IOAddress> lease_addrs;
+ lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+ lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+ lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+ Lease6CollectionPtr orig_leases(new Lease6Collection());
+ for (auto const& address : lease_addrs) {
+ Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+ ASSERT_TRUE(orig_lease);
+ orig_leases->push_back(orig_lease);
+ }
+
+ // Delete the middle lease from the back end. This should cause a conflict error.
+ ASSERT_NO_THROW_LOG(lmptr_->deleteLease((*orig_leases)[1]));
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query6", query);
+ callout_handle->setArgument("response6", response);
+ callout_handle->setArgument("leases6", orig_leases);
+
+ // Invoke the leases6Committed handler.
+ LeaseCmds cmds;
+ ASSERT_THROW_MSG(cmds.leases6Committed(*callout_handle, mgr),
+ Unexpected,
+ "1 out of 3 leases failed to update for "
+ "duid=[01:02:03:04], [no hwaddr info], tid=0x4d2");
+
+ for (auto const& lease : *orig_leases) {
+ // Fetch the lease.
+ Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+ if (after_lease) {
+ // Context contents should match the expected context content.
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ } else {
+ // Middle lease should not be found.
+ EXPECT_EQ(lease->addr_, (*orig_leases)[1]->addr_);
+ }
+ }
+}
+#endif
+
TEST_F(Lease6CmdsTest, lease6AddMissingParams) {
testLease6AddMissingParams();
}
testLease6Write();
}
+#if 0
TEST_F(Lease6CmdsTest, validLeases6Committed) {
testValidLeases6Committed();
}
testNopLeases6Committed();
}
+TEST_F(Lease6CmdsTest, leases6CommittedErrors) {
+ testLeases6CommittedErrors();
+}
+
+TEST_F(Lease6CmdsTest, leases6CommittedErrorsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeases6CommittedErrors();
+}
+#endif
+
} // end of anonymous namespace
/// Also ensured there is no lease manager leftovers from previous
/// test.
LeaseCmdsTest()
- : LibLoadTest(LEASE_CMDS_LIB_SO),
+ : LibLoadTest(LIBDHCP_LEASE_CMDS_SO),
d2_mgr_(isc::dhcp::CfgMgr::instance().getD2ClientMgr()) {
isc::dhcp::LeaseMgrFactory::destroy();
enableD2();
TESTS += lease_cmds_unittests
lease_cmds_unittests_SOURCES = run_unittests.cc
-lease_cmds_unittests_SOURCES += lease_cmds_unittest.h lease_cmds_unittest.cc
-lease_cmds_unittests_SOURCES += lease_cmds4_unittest.cc
-lease_cmds_unittests_SOURCES += lease_cmds6_unittest.cc
lease_cmds_unittests_SOURCES += binding_variables_unittest.cc
+lease_cmds_unittests_SOURCES += lease_cmds_func_unittest.h
+lease_cmds_unittests_SOURCES += lease_cmds_func4_unittest.cc
+lease_cmds_unittests_SOURCES += lease_cmds_func6_unittest.cc
lease_cmds_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
#include <config.h>
-#include <binding_variables.h>
+#include <lease_cmds.h>
+#include <hooks/hooks_manager.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
#include <exceptions/exceptions.h>
#include <cc/data.h>
#include <dhcp/dhcp6.h>
}
}
+#if 0
+class BindingVariableHandlerTest : public ::testing::Test {
+public:
+
+ /// @brief Pointer to the lease manager
+ isc::dhcp::LeaseMgr* lmptr_;
+
+ /// @brief Constructor
+ BindingVariableHandlerTest() {
+ isc::dhcp::LeaseMgrFactory::destroy();
+ lmptr_ = 0;
+ }
+
+ /// @brief Destructor
+ ///
+ virtual ~BindingVariableHandlerTest() {
+ isc::dhcp::LeaseMgrFactory::destroy();
+ lmptr_ = 0;
+ }
+
+ /// @brief Creates an IPv4 lease
+ ///
+ /// Lease parameters: valid lifetime = 0xFFFFFFFE, cltt = 1923222072, fqdn-fwd = false,
+ /// fqdn-rev = true, hostname = myhost.example.com
+ ///
+ /// @param ip_address IP address for the lease.
+ /// @param subnet_id subnet identifier
+ /// @param hw_address_pattern value to be used for generating HW address by repeating
+ /// it 6 times.
+ /// @param client_id_pattern value to be used for generating client identifier by
+ /// repeating it 8 times.
+ /// @param declined controls whether the lease should be in declined state.
+ ///
+ /// @return Returns the lease created
+ isc::dhcp::Lease4Ptr createLease4(const std::string& ip_address,
+ const isc::dhcp::SubnetID& subnet_id,
+ const uint8_t hw_address_pattern,
+ const uint8_t client_id_pattern,
+ uint32_t pool_id = 0) {
+ isc::dhcp::Lease4Ptr lease(new isc::dhcp::Lease4());
+
+ lease->addr_ = isc::asiolink::IOAddress(ip_address);
+
+ lease->hwaddr_.reset(new isc::dhcp::HWAddr(std::vector<uint8_t>(6, hw_address_pattern), isc::dhcp::HTYPE_ETHER));
+ lease->client_id_ = isc::dhcp::ClientIdPtr(new isc::dhcp::ClientId(std::vector<uint8_t>(8, client_id_pattern)));
+ lease->valid_lft_ = 2400;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = subnet_id;
+ lease->pool_id_ = pool_id;
+ return (lease);
+ }
+
+ /// @brief Creates an IPv6 lease
+ ///
+ /// @param ip_address IP address for the lease.
+ /// @param subnet_id subnet identifier
+ /// @param duid_address_pattern value to be used for generating DUID by
+ /// repeating it 8 times
+ /// @param declined controls whether the lease should be in declined state.
+ ///
+ /// @return Returns the lease created
+ isc::dhcp::Lease6Ptr createLease6(const std::string& ip_address,
+ const isc::dhcp::SubnetID& subnet_id,
+ const uint8_t duid_pattern,
+ uint32_t pool_id = 0) {
+ isc::dhcp::Lease6Ptr lease(new isc::dhcp::Lease6());
+
+ lease->addr_ = isc::asiolink::IOAddress(ip_address);
+ lease->type_ = isc::dhcp::Lease::TYPE_NA;
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 42;
+ lease->duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(std::vector<uint8_t>(8, duid_pattern)));
+ lease->preferred_lft_ = 1800;
+ lease->valid_lft_ = 2400;
+ lease->subnet_id_ = subnet_id;
+ lease->pool_id_ = pool_id;
+
+ return (lease);
+ }
+
+ /// @brief Initializes lease manager (and optionally populates it with a lease)
+ ///
+ /// Creates a lease manager (memfile, trimmed down to keep everything in memory
+ /// only) and optionally can create a lease, which is useful for leaseX-get and
+ /// leaseX-del type of tests. For lease details, see @ref createLease4 and
+ /// @ref createLease6.
+ ///
+ /// @param famiily protocol family AF_INET or AF_INET6
+ /// @param insert_lease governs whether a lease should be pre-inserted
+ /// @param declined governs whether a lease should be in declined state
+ void initLeaseMgr(int family) {
+ isc::dhcp::LeaseMgrFactory::destroy();
+ if (family == AF_INET6) {
+ isc::dhcp::LeaseMgrFactory::create("type=memfile persist=false "
+ "universe=6 extended-info-tables=true");
+ lmptr_ = &(isc::dhcp::LeaseMgrFactory::instance());
+ ASSERT_TRUE(lmptr_);
+ lmptr_->addLease(createLease6("2001:db8:1::1", 66, 0x42));
+ lmptr_->addLease(createLease6("2001:db8:1::2", 66, 0x56));
+ lmptr_->addLease(createLease6("2001:db8:2::1", 99, 0x42));
+ lmptr_->addLease(createLease6("2001:db8:2::2", 99, 0x56));
+ } else {
+ isc::dhcp::LeaseMgrFactory::create("type=memfile persist=false universe=4");
+ lmptr_ = &(isc::dhcp::LeaseMgrFactory::instance());
+ ASSERT_TRUE(lmptr_);
+ lmptr_->addLease(createLease4("192.0.2.1", 44, 0x08, 0x42));
+ lmptr_->addLease(createLease4("192.0.2.2", 44, 0x09, 0x56));
+ lmptr_->addLease(createLease4("192.0.3.1", 88, 0x08, 0x42));
+ lmptr_->addLease(createLease4("192.0.3.2", 88, 0x09, 0x56));
+ }
+ }
+
+#if 0
+ /// @brief Check that lease4_offer handler works as expected with
+ /// valid inputs.
+ void testValidLease4Offer();
+
+ /// @brief Check that leases4_committed handler works as expected with
+ /// valid inputs.
+ void testValidLeases4Committed();
+
+ /// @brief Check that leases4_committed handler does not throw or alter
+ /// the lease under NOP conditions:
+ /// 1. There is no repsonse packet
+ /// 2. Response packet is a DHCPACK
+ /// 3. There is no active lease
+ void testNopLeases4Committed();
+#endif
+
+ /// @brief Check that leases6_committed handler works as expected with
+ /// valid inputs.
+ void testValidLeases6Committed();
+
+ /// @brief Check that leases6_committed handler does not throw or alter
+ /// leases under NOP conditions:
+ /// 1. There is no repsonse packet
+ /// 2. There are no leases
+ /// 3. There are leases but none active
+ void testNopLeases6Committed();
+
+ void testLeases6CommittedErrors();
+};
+
+#if 0
+void
+BindingVariableHandlerTest::testValidLease4Offer() {
+ initLeaseMgr(AF_INET);
+
+ struct Scenario {
+ uint32_t line_;
+ std::string config_;
+ std::string orig_context_;
+ std::string exp_context_;
+ uint32_t offer_lifetime_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // No variables configured, nothing in lease context.
+ __LINE__,
+ R"({})",
+ R"({})",
+ R"({})",
+ 500
+ },
+ {
+ // lease context has no binding-variables, two configured
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "hexstring(pkt4.mac,':')",
+ "source": "query"
+ },
+ {
+ "name": "yiaddr",
+ "expression": "addrtotext(pkt4.yiaddr)",
+ "source": "response"
+ }]})^",
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ 500
+ },
+ {
+ // lease context has no binding-variables, two configured
+ // offer lifetime is 0.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "hexstring(pkt4.mac,':')",
+ "source": "query"
+ },
+ {
+ "name": "yiaddr",
+ "expression": "addrtotext(pkt4.yiaddr)",
+ "source": "response"
+ }]})^",
+ R"({})",
+ R"({})",
+ 0
+ },
+ {
+ // lease context has binding-variables, none configured
+ // Current logic leaves lease untouched.
+ __LINE__,
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ 500
+ },
+ {
+ // Evaluated variable value is an empty string.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "''",
+ "source": "query"
+ }]})^",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": ""
+ }
+ }})",
+ 500
+ }};
+
+ // Create packet pair and lease.
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+ Pkt4Ptr response(new Pkt4(DHCPOFFER, 1234));
+ IOAddress yiaddr("192.0.2.1");
+ response->setYiaddr(yiaddr);
+
+ // Iterater over scenarios.
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the lease and set its user-context to the original content.
+ Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(orig_lease);
+ ConstElementPtr orig_context;
+ ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+ orig_lease->setContext(orig_context);
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+ Lease4CollectionPtr leases(new Lease4Collection());
+ leases->push_back(orig_lease);
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("response4", response);
+ callout_handle->setArgument("leases4", leases);
+ callout_handle->setArgument("offer_lifetime", scenario.offer_lifetime_);
+
+ // Invoke the leases4Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.lease4Offer(*callout_handle, mgr));
+
+ // Fetch the lease.
+ Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(after_lease);
+
+ // Context contents should match the expected context content.
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ }
+}
+
+void
+BindingVariableHandlerTest::testValidLeases4Committed() {
+ initLeaseMgr(AF_INET);
+
+ struct Scenario {
+ uint32_t line_;
+ std::string config_;
+ std::string orig_context_;
+ std::string exp_context_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // No variables configured, nothing in lease context.
+ __LINE__,
+ R"({})",
+ R"({})",
+ R"({})"
+ },
+ {
+ // lease context has no binding-variables, two configured
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "hexstring(pkt4.mac,':')",
+ "source": "query"
+ },
+ {
+ "name": "yiaddr",
+ "expression": "addrtotext(pkt4.yiaddr)",
+ "source": "response"
+ }]})^",
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ },
+ {
+ // lease context has binding-variables, none configured
+ // Current logic leaves lease untouched.
+ __LINE__,
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ },
+ {
+ // Evaluated variable value is an empty string.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "''",
+ "source": "query"
+ }]})^",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": ""
+ }
+ }})",
+ }};
+
+ // Create packet pair and lease.
+ Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+ query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+ Pkt4Ptr response(new Pkt4(DHCPACK, 1234));
+ IOAddress yiaddr("192.0.2.1");
+ response->setYiaddr(yiaddr);
+
+ // Iterater over scenarios.
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the lease and set its user-context to the original content.
+ Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(orig_lease);
+ ConstElementPtr orig_context;
+ ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+ orig_lease->setContext(orig_context);
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+ Lease4CollectionPtr leases(new Lease4Collection());
+ leases->push_back(orig_lease);
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("response4", response);
+ callout_handle->setArgument("leases4", leases);
+
+ // Invoke the leases4Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+ // Fetch the lease.
+ Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(after_lease);
+
+ // Context contents should match the expected context content.
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ }
+}
+
+void
+BindingVariableHandlerTest::testNopLeases4Committed() {
+ initLeaseMgr(AF_INET);
+
+ struct Scenario {
+ uint32_t line_;
+ DHCPMessageType response_type_;
+ bool send_lease_;
+ };
+
+ // Configure a single variable.
+ std::string config =
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "hexstring(pkt4.mac,':')",
+ "source": "query"
+ }]})^";
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+ ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
+
+ // Scenarios should all result in no change to the lease.
+ std::list<Scenario> scenarios = {
+ {
+ // Response is not a DHCPACK.
+ __LINE__,
+ DHCPNAK,
+ true
+ },
+ {
+ // No active lease.
+ __LINE__,
+ DHCPACK,
+ false
+ },
+ {
+ // No response.
+ __LINE__,
+ DHCP_NOTYPE,
+ false
+ }};
+
+ // Create packet pair and lease.
+ Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+ query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+ IOAddress yiaddr("192.0.2.1");
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ Pkt4Ptr response;
+ if (scenario.response_type_ != DHCP_NOTYPE) {
+ response.reset(new Pkt4(scenario.response_type_, 1234));
+ }
+
+ // Fetch the lease and set its user-context to the original content.
+ Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(orig_lease);
+ ASSERT_FALSE(orig_lease->getContext());
+
+ Lease4CollectionPtr leases(new Lease4Collection());
+ if (scenario.send_lease_) {
+ leases->push_back(orig_lease);
+ }
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("response4", response);
+ callout_handle->setArgument("leases4", leases);
+
+ // Invoke the leases4Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+ // Fetch the lease. Context should still be empty.
+ Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(after_lease);
+ ASSERT_FALSE(after_lease->getContext());
+ }
+}
+#endif
+
+void
+BindingVariableHandlerTest::testValidLeases6Committed() {
+ initLeaseMgr(AF_INET6);
+
+ struct Scenario {
+ uint32_t line_;
+ std::string config_;
+ std::string orig_context_;
+ std::string exp_context_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // No variables configured, nothing in lease context.
+ __LINE__,
+ R"({})",
+ R"({})",
+ R"({})"
+ },
+ {
+ // Lease context has no binding-variables, two configured.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "duid",
+ "expression": "hexstring(option[1].hex,':')",
+ "source": "query"
+ },
+ {
+ "name": "sub-id",
+ "expression": "hexstring(option[38].hex, ':')",
+ "source": "response"
+ }]})^",
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})",
+ },
+ {
+ // Lease context has binding-variables, none configured.
+ // Current logic leaves lease untouched.
+ __LINE__,
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})",
+ },
+ {
+ // Evaluated variable value is an empty string.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "my-var",
+ "expression": "''",
+ "source": "query"
+ }]})^",
+ R"({"ISC":{
+ "binding-variables":{
+ "my-var": "pre-existing value"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "my-var": ""
+ }
+ }})",
+ }
+ };
+
+ // Create packet pair and lease.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+ { 0x01, 0x02, 0x03, 0x04 }));
+ query->addOption(client_id);
+
+ Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+ OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+ { 0x05, 0x06, 0x07, 0x08 }));
+ response->addOption(subscriber_id);
+
+ // Create a list of the lease addresses.
+ std::list<IOAddress> lease_addrs;
+ lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+ lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+ lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+ // Iterater over scenarios.
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the lease and set its user-context to the original content.
+ Lease6CollectionPtr orig_leases(new Lease6Collection());
+ for (auto const& address : lease_addrs) {
+ Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+ ASSERT_TRUE(orig_lease);
+ ASSERT_TRUE(orig_lease->valid_lft_ > 0);
+
+ ConstElementPtr orig_context;
+ ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+ orig_lease->setContext(orig_context);
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
+ orig_leases->push_back(orig_lease);
+ }
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query6", query);
+ callout_handle->setArgument("response6", response);
+ callout_handle->setArgument("leases6", orig_leases);
+
+ // Invoke the leases6Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
+
+ // Iterate over the leases and make sure the user-contexts are as expected.
+ for (auto const& lease : *orig_leases) {
+ // Fetch the lease.
+ Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+ ASSERT_TRUE(after_lease);
+
+ // Context contents should match the expected context content.
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ }
+ }
+}
+
+void
+BindingVariableHandlerTest::testNopLeases6Committed() {
+ initLeaseMgr(AF_INET6);
+
+ struct Scenario {
+ uint32_t line_;
+ DHCPv6MessageType response_type_;
+ bool send_lease_;
+ uint32_t valid_lft_;
+ };
+
+ // Configure a single variable.
+ std::string config =
+ R"^({"binding-variables":[
+ {
+ "name": "duid",
+ "expression": "hexstring(option[1].hex,':')",
+ "source": "query"
+ }]})^";
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+ ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
+
+ // Scenarios should all result in no change to the lease.
+ std::list<Scenario> scenarios = {
+ {
+ // No leases.
+ __LINE__,
+ DHCPV6_REPLY,
+ false,
+ 0
+ },
+ {
+ // No active leases.
+ __LINE__,
+ DHCPV6_REPLY,
+ true,
+ 0
+ },
+ {
+ // No response.
+ __LINE__,
+ DHCPV6_NOTYPE,
+ true,
+ 1000
+ }};
+
+ // Create query packet.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+ { 0x01, 0x02, 0x03, 0x04 }));
+ query->addOption(client_id);
+
+ IOAddress na_addr("2001:db8:1::1");
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ // Create the response packet, if one.
+ Pkt6Ptr response;
+ if (scenario.response_type_ != DHCPV6_NOTYPE) {
+ response.reset(new Pkt6(scenario.response_type_, 1234));
+ }
+
+
+ // Fetch the lease and verify there is no context content.
+ Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
+ ASSERT_TRUE(orig_lease);
+ ASSERT_FALSE(orig_lease->getContext());
+ orig_lease->valid_lft_ = scenario.valid_lft_;
+
+ Lease6CollectionPtr leases(new Lease6Collection());
+ if (scenario.send_lease_) {
+ leases->push_back(orig_lease);
+ }
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query6", query);
+ callout_handle->setArgument("response6", response);
+ callout_handle->setArgument("leases6", leases);
+
+ // Invoke the leases6Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
+
+ // Fetch the lease. Context should still be empty.
+ Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
+ ASSERT_TRUE(after_lease);
+ ASSERT_FALSE(after_lease->getContext());
+ }
+}
+
+void
+BindingVariableHandlerTest::testLeases6CommittedErrors() {
+ initLeaseMgr(AF_INET6);
+
+ // Create a config with two binding variables.
+ std::string config_str =
+ R"^({"binding-variables":[
+ {
+ "name": "duid",
+ "expression": "hexstring(option[1].hex,':')",
+ "source": "query"
+ },
+ {
+ "name": "sub-id",
+ "expression": "hexstring(option[38].hex, ':')",
+ "source": "response"
+ }]})^";
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(config_str));
+
+ // Create the expected context contents.
+ std::string exp_context_str =
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})";
+
+
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(exp_context_str));
+
+ // Create packet pair and lease.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+ { 0x01, 0x02, 0x03, 0x04 }));
+ query->addOption(client_id);
+
+ Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+ OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+ { 0x05, 0x06, 0x07, 0x08 }));
+ response->addOption(subscriber_id);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the leases.
+ std::list<IOAddress> lease_addrs;
+ lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+ lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+ lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+ Lease6CollectionPtr orig_leases(new Lease6Collection());
+ for (auto const& address : lease_addrs) {
+ Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+ ASSERT_TRUE(orig_lease);
+ orig_leases->push_back(orig_lease);
+ }
+
+ // Delete the middle lease from the back end. This should cause a conflict error.
+ ASSERT_NO_THROW_LOG(lmptr_->deleteLease((*orig_leases)[1]));
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query6", query);
+ callout_handle->setArgument("response6", response);
+ callout_handle->setArgument("leases6", orig_leases);
+
+ // Invoke the leases6Committed handler.
+ LeaseCmds cmds;
+ ASSERT_THROW_MSG(cmds.leases6Committed(*callout_handle, mgr),
+ Unexpected,
+ "1 out of 3 leases failed to update for "
+ "duid=[01:02:03:04], [no hwaddr info], tid=0x4d2");
+
+ for (auto const& lease : *orig_leases) {
+ // Fetch the lease.
+ Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+ if (after_lease) {
+ // Context contents should match the expected context content.
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ } else {
+ // Middle lease should not be found.
+ EXPECT_EQ(lease->addr_, (*orig_leases)[1]->addr_);
+ }
+ }
+}
+
+TEST_F(BindingVariableHandlerTest, validLeases6Committed) {
+ testValidLeases6Committed();
+}
+
+TEST_F(BindingVariableHandlerTest, validLeases6CommittedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testValidLeases6Committed();
+}
+
+TEST_F(BindingVariableHandlerTest, nopLeases6Committed) {
+ testNopLeases6Committed();
+}
+
+TEST_F(BindingVariableHandlerTest, nopLeases6CommittedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNopLeases6Committed();
+}
+
+TEST_F(BindingVariableHandlerTest, leases6CommittedErrors) {
+ testLeases6CommittedErrors();
+}
+
+TEST_F(BindingVariableHandlerTest, leases6CommittedErrorsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeases6CommittedErrors();
+}
+#endif
+
} // end of anonymous namespace
--- /dev/null
+// 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 <lease_cmds.h>
+#include <lease_cmds_func_unittest.h>
+#include <hooks/hooks_manager.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <dhcp/pkt4.h>
+
+#include <testutils/gtest_utils.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::test;
+using namespace isc::asiolink;
+using namespace isc::hooks;
+
+using namespace isc::lease_cmds;
+
+namespace {
+
+class LeaseCmdsFuncTest4 : public LeaseCmdsFuncTest {
+public:
+ /// @brief Constructor
+ LeaseCmdsFuncTest4() = default;
+
+ /// @brief Destructor
+ ///
+ virtual ~LeaseCmdsFuncTest4() = default;
+
+ /// @brief Creates an IPv4 lease
+ ///
+ /// Lease parameters: valid lifetime = 0xFFFFFFFE, cltt = 1923222072, fqdn-fwd = false,
+ /// fqdn-rev = true, hostname = myhost.example.com
+ ///
+ /// @param ip_address IP address for the lease.
+ /// @param subnet_id subnet identifier
+ /// @param hw_address_pattern value to be used for generating HW address by repeating
+ /// it 6 times.
+ /// @param client_id_pattern value to be used for generating client identifier by
+ /// repeating it 8 times.
+ ///
+ /// @return Returns the lease created
+ Lease4Ptr createLease(const std::string& ip_address,
+ const SubnetID& subnet_id,
+ const uint8_t hw_address_pattern,
+ const uint8_t client_id_pattern,
+ uint32_t pool_id = 0) {
+ Lease4Ptr lease(new Lease4());
+ lease->addr_ = IOAddress(ip_address);
+
+ lease->hwaddr_.reset(new HWAddr(std::vector<uint8_t>(6, hw_address_pattern), HTYPE_ETHER));
+ lease->client_id_ = ClientIdPtr(new ClientId(std::vector<uint8_t>(8, client_id_pattern)));
+ lease->valid_lft_ = 2400;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = subnet_id;
+ lease->pool_id_ = pool_id;
+ return (lease);
+ }
+
+ /// @brief Initializes lease manager (and optionally populates it with a lease)
+ ///
+ /// Creates a lease manager and initial leases.
+ /// only) and optionally can create a lease, which is useful for leaseX-get and
+ /// leaseX-del type of tests. For lease details, see @ref createLease and
+ /// @ref createLease6.
+ ///
+ virtual void initLeaseMgr() {
+ LeaseMgrFactory::destroy();
+ LeaseMgrFactory::create("type=memfile persist=false universe=4");
+ lmptr_ = &(LeaseMgrFactory::instance());
+ ASSERT_TRUE(lmptr_);
+
+ lmptr_->addLease(createLease("192.0.2.1", 44, 0x08, 0x42));
+ lmptr_->addLease(createLease("192.0.2.2", 44, 0x09, 0x56));
+ lmptr_->addLease(createLease("192.0.3.1", 88, 0x08, 0x42));
+ lmptr_->addLease(createLease("192.0.3.2", 88, 0x09, 0x56));
+ }
+
+ /// @brief Check that lease4_offer handler works as expected with
+ /// valid inputs.
+ void testValidLease4Offer();
+
+ /// @brief Check that leases4_committed handler works as expected with
+ /// valid inputs.
+ void testValidLeases4Committed();
+
+ /// @brief Check that leases4_committed handler does not throw or alter
+ /// the lease under NOP conditions:
+ /// 1. There is no repsonse packet
+ /// 2. Response packet is a DHCPACK
+ /// 3. There is no active lease
+ void testNopLeases4Committed();
+};
+
+void
+LeaseCmdsFuncTest4::testValidLease4Offer() {
+ struct Scenario {
+ uint32_t line_;
+ std::string config_;
+ std::string orig_context_;
+ std::string exp_context_;
+ uint32_t offer_lifetime_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // No variables configured, nothing in lease context.
+ __LINE__,
+ R"({})",
+ R"({})",
+ R"({})",
+ 500
+ },
+ {
+ // lease context has no binding-variables, two configured
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "hexstring(pkt4.mac,':')",
+ "source": "query"
+ },
+ {
+ "name": "yiaddr",
+ "expression": "addrtotext(pkt4.yiaddr)",
+ "source": "response"
+ }]})^",
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ 500
+ },
+ {
+ // lease context has no binding-variables, two configured
+ // offer lifetime is 0.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "hexstring(pkt4.mac,':')",
+ "source": "query"
+ },
+ {
+ "name": "yiaddr",
+ "expression": "addrtotext(pkt4.yiaddr)",
+ "source": "response"
+ }]})^",
+ R"({})",
+ R"({})",
+ 0
+ },
+ {
+ // lease context has binding-variables, none configured
+ // Current logic leaves lease untouched.
+ __LINE__,
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ 500
+ },
+ {
+ // Evaluated variable value is an empty string.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "''",
+ "source": "query"
+ }]})^",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": ""
+ }
+ }})",
+ 500
+ }};
+
+ // Create packet pair and lease.
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+ Pkt4Ptr response(new Pkt4(DHCPOFFER, 1234));
+ IOAddress yiaddr("192.0.2.1");
+ response->setYiaddr(yiaddr);
+
+ // Iterater over scenarios.
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the lease and set its user-context to the original content.
+ Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(orig_lease);
+ ConstElementPtr orig_context;
+ ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+ orig_lease->setContext(orig_context);
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+ Lease4CollectionPtr leases(new Lease4Collection());
+ leases->push_back(orig_lease);
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("response4", response);
+ callout_handle->setArgument("leases4", leases);
+ callout_handle->setArgument("offer_lifetime", scenario.offer_lifetime_);
+
+ // Invoke the leases4Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.lease4Offer(*callout_handle, mgr));
+
+ // Fetch the lease.
+ Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(after_lease);
+
+ // Context contents should match the expected context content.
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ }
+}
+
+void
+LeaseCmdsFuncTest4::testValidLeases4Committed() {
+ struct Scenario {
+ uint32_t line_;
+ std::string config_;
+ std::string orig_context_;
+ std::string exp_context_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // No variables configured, nothing in lease context.
+ __LINE__,
+ R"({})",
+ R"({})",
+ R"({})"
+ },
+ {
+ // lease context has no binding-variables, two configured
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "hexstring(pkt4.mac,':')",
+ "source": "query"
+ },
+ {
+ "name": "yiaddr",
+ "expression": "addrtotext(pkt4.yiaddr)",
+ "source": "response"
+ }]})^",
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ },
+ {
+ // lease context has binding-variables, none configured
+ // Current logic leaves lease untouched.
+ __LINE__,
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06",
+ "yiaddr": "192.0.2.1"
+ }
+ }})",
+ },
+ {
+ // Evaluated variable value is an empty string.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "''",
+ "source": "query"
+ }]})^",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": "01:02:03:04:05:06"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "hwaddr": ""
+ }
+ }})",
+ }};
+
+ // Create packet pair and lease.
+ Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+ query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+ Pkt4Ptr response(new Pkt4(DHCPACK, 1234));
+ IOAddress yiaddr("192.0.2.1");
+ response->setYiaddr(yiaddr);
+
+ // Iterater over scenarios.
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the lease and set its user-context to the original content.
+ Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(orig_lease);
+ ConstElementPtr orig_context;
+ ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+ orig_lease->setContext(orig_context);
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease4(orig_lease));
+
+ Lease4CollectionPtr leases(new Lease4Collection());
+ leases->push_back(orig_lease);
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("response4", response);
+ callout_handle->setArgument("leases4", leases);
+
+ // Invoke the leases4Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+ // Fetch the lease.
+ Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(after_lease);
+
+ // Context contents should match the expected context content.
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ }
+}
+
+void
+LeaseCmdsFuncTest4::testNopLeases4Committed() {
+ struct Scenario {
+ uint32_t line_;
+ DHCPMessageType response_type_;
+ bool send_lease_;
+ };
+
+ // Configure a single variable.
+ std::string config =
+ R"^({"binding-variables":[
+ {
+ "name": "hwaddr",
+ "expression": "hexstring(pkt4.mac,':')",
+ "source": "query"
+ }]})^";
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET)));
+ ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
+
+ // Scenarios should all result in no change to the lease.
+ std::list<Scenario> scenarios = {
+ {
+ // Response is not a DHCPACK.
+ __LINE__,
+ DHCPNAK,
+ true
+ },
+ {
+ // No active lease.
+ __LINE__,
+ DHCPACK,
+ false
+ },
+ {
+ // No response.
+ __LINE__,
+ DHCP_NOTYPE,
+ false
+ }};
+
+ // Create packet pair and lease.
+ Pkt4Ptr query(new Pkt4(DHCPREQUEST, 1234));
+ query->setHWAddr(1, 6, { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+ IOAddress yiaddr("192.0.2.1");
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ Pkt4Ptr response;
+ if (scenario.response_type_ != DHCP_NOTYPE) {
+ response.reset(new Pkt4(scenario.response_type_, 1234));
+ }
+
+ // Fetch the lease and set its user-context to the original content.
+ Lease4Ptr orig_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(orig_lease);
+ ASSERT_FALSE(orig_lease->getContext());
+
+ Lease4CollectionPtr leases(new Lease4Collection());
+ if (scenario.send_lease_) {
+ leases->push_back(orig_lease);
+ }
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("response4", response);
+ callout_handle->setArgument("leases4", leases);
+
+ // Invoke the leases4Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.leases4Committed(*callout_handle, mgr));
+
+ // Fetch the lease. Context should still be empty.
+ Lease4Ptr after_lease = lmptr_->getLease4(yiaddr);
+ ASSERT_TRUE(after_lease);
+ ASSERT_FALSE(after_lease->getContext());
+ }
+}
+
+TEST_F(LeaseCmdsFuncTest4, validLease4Offer) {
+ testValidLease4Offer();
+}
+
+TEST_F(LeaseCmdsFuncTest4, validLease4OfferMultiThreading) {
+ MultiThreadingTest mt(true);
+ testValidLease4Offer();
+}
+
+TEST_F(LeaseCmdsFuncTest4, validLeases4Committed) {
+ testValidLeases4Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest4, validLeases4CommittedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testValidLeases4Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest4, nopLeases4Committed) {
+ testNopLeases4Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest4, nopLeases4CommittedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNopLeases4Committed();
+}
+
+} // end of anonymous namespace
--- /dev/null
+// 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 <lease_cmds.h>
+#include <lease_cmds_func_unittest.h>
+#include <hooks/hooks_manager.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+
+#include <testutils/gtest_utils.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::test;
+using namespace isc::asiolink;
+using namespace isc::hooks;
+
+using namespace isc::lease_cmds;
+
+namespace {
+
+class LeaseCmdsFuncTest6 : public LeaseCmdsFuncTest {
+public:
+ /// @brief Constructor
+ LeaseCmdsFuncTest6() = default;
+
+ /// @brief Destructor
+ virtual ~LeaseCmdsFuncTest6() = default;
+
+ /// @brief Initializes lease manager (and optionally populates it with a lease)
+ void initLeaseMgr() {
+ LeaseMgrFactory::destroy();
+ LeaseMgrFactory::create("type=memfile persist=false "
+ "universe=6 extended-info-tables=true");
+ lmptr_ = &(isc::dhcp::LeaseMgrFactory::instance());
+ ASSERT_TRUE(lmptr_);
+ lmptr_->addLease(createLease6("2001:db8:1::1", 66, 0x42));
+ lmptr_->addLease(createLease6("2001:db8:1::2", 66, 0x56));
+ lmptr_->addLease(createLease6("2001:db8:2::1", 99, 0x42));
+ lmptr_->addLease(createLease6("2001:db8:2::2", 99, 0x56));
+ }
+
+ /// @brief Creates an IPv6 lease
+ ///
+ /// @param ip_address IP address for the lease.
+ /// @param subnet_id subnet identifier
+ /// @param duid_address_pattern value to be used for generating DUID by
+ /// repeating it 8 times
+ /// @return Returns the lease created
+ isc::dhcp::Lease6Ptr createLease6(const std::string& ip_address,
+ const isc::dhcp::SubnetID& subnet_id,
+ const uint8_t duid_pattern) {
+ isc::dhcp::Lease6Ptr lease(new isc::dhcp::Lease6());
+ lease->addr_ = isc::asiolink::IOAddress(ip_address);
+ lease->type_ = isc::dhcp::Lease::TYPE_NA;
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 42;
+ lease->duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(std::vector<uint8_t>(8, duid_pattern)));
+ lease->preferred_lft_ = 1800;
+ lease->valid_lft_ = 2400;
+ lease->subnet_id_ = subnet_id;
+ return (lease);
+ }
+
+ /// @brief Check that leases6_committed handler works as expected with
+ /// valid inputs.
+ void testValidLeases6Committed();
+
+ /// @brief Check that leases6_committed handler does not throw or alter
+ /// leases under NOP conditions:
+ /// 1. There is no repsonse packet
+ /// 2. There are no leases
+ /// 3. There are leases but none active
+ void testNopLeases6Committed();
+
+ /// @brief Checks that errors occurring in leases6Committed() are handled
+ /// properly.
+ void testLeases6CommittedErrors();
+};
+
+void
+LeaseCmdsFuncTest6::testValidLeases6Committed() {
+ struct Scenario {
+ uint32_t line_;
+ std::string config_;
+ std::string orig_context_;
+ std::string exp_context_;
+ };
+
+ std::list<Scenario> scenarios = {
+ {
+ // No variables configured, nothing in lease context.
+ __LINE__,
+ R"({})",
+ R"({})",
+ R"({})"
+ },
+ {
+ // Lease context has no binding-variables, two configured.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "duid",
+ "expression": "hexstring(option[1].hex,':')",
+ "source": "query"
+ },
+ {
+ "name": "sub-id",
+ "expression": "hexstring(option[38].hex, ':')",
+ "source": "response"
+ }]})^",
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})",
+ },
+ {
+ // Lease context has binding-variables, none configured.
+ // Current logic leaves lease untouched.
+ __LINE__,
+ R"({})",
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})",
+ },
+ {
+ // Evaluated variable value is an empty string.
+ __LINE__,
+ R"^({"binding-variables":[
+ {
+ "name": "my-var",
+ "expression": "''",
+ "source": "query"
+ }]})^",
+ R"({"ISC":{
+ "binding-variables":{
+ "my-var": "pre-existing value"
+ }
+ }})",
+ R"({"ISC":{
+ "binding-variables":{
+ "my-var": ""
+ }
+ }})",
+ }
+ };
+
+ // Create packet pair and lease.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+ { 0x01, 0x02, 0x03, 0x04 }));
+ query->addOption(client_id);
+
+ Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+ OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+ { 0x05, 0x06, 0x07, 0x08 }));
+ response->addOption(subscriber_id);
+
+ // Create a list of the lease addresses.
+ std::list<IOAddress> lease_addrs;
+ lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+ lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+ lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+ // Iterater over scenarios.
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the lease and set its user-context to the original content.
+ Lease6CollectionPtr orig_leases(new Lease6Collection());
+ for (auto const& address : lease_addrs) {
+ Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+ ASSERT_TRUE(orig_lease);
+ ASSERT_TRUE(orig_lease->valid_lft_ > 0);
+
+ ConstElementPtr orig_context;
+ ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
+ orig_lease->setContext(orig_context);
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
+ orig_leases->push_back(orig_lease);
+ }
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query6", query);
+ callout_handle->setArgument("response6", response);
+ callout_handle->setArgument("leases6", orig_leases);
+
+ // Invoke the leases6Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
+
+ // Iterate over the leases and make sure the user-contexts are as expected.
+ for (auto const& lease : *orig_leases) {
+ // Fetch the lease.
+ Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+ ASSERT_TRUE(after_lease);
+
+ // Context contents should match the expected context content.
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ }
+ }
+}
+
+void
+LeaseCmdsFuncTest6::testNopLeases6Committed() {
+ struct Scenario {
+ uint32_t line_;
+ DHCPv6MessageType response_type_;
+ bool send_lease_;
+ uint32_t valid_lft_;
+ };
+
+ // Configure a single variable.
+ std::string config =
+ R"^({"binding-variables":[
+ {
+ "name": "duid",
+ "expression": "hexstring(option[1].hex,':')",
+ "source": "query"
+ }]})^";
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+ ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));
+
+ // Scenarios should all result in no change to the lease.
+ std::list<Scenario> scenarios = {
+ {
+ // No leases.
+ __LINE__,
+ DHCPV6_REPLY,
+ false,
+ 0
+ },
+ {
+ // No active leases.
+ __LINE__,
+ DHCPV6_REPLY,
+ true,
+ 0
+ },
+ {
+ // No response.
+ __LINE__,
+ DHCPV6_NOTYPE,
+ true,
+ 1000
+ }};
+
+ // Create query packet.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+ { 0x01, 0x02, 0x03, 0x04 }));
+ query->addOption(client_id);
+
+ IOAddress na_addr("2001:db8:1::1");
+ for (auto const& scenario : scenarios) {
+ SCOPED_LINE(scenario.line_);
+
+ // Create the response packet, if one.
+ Pkt6Ptr response;
+ if (scenario.response_type_ != DHCPV6_NOTYPE) {
+ response.reset(new Pkt6(scenario.response_type_, 1234));
+ }
+
+
+ // Fetch the lease and verify there is no context content.
+ Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
+ ASSERT_TRUE(orig_lease);
+ ASSERT_FALSE(orig_lease->getContext());
+ orig_lease->valid_lft_ = scenario.valid_lft_;
+
+ Lease6CollectionPtr leases(new Lease6Collection());
+ if (scenario.send_lease_) {
+ leases->push_back(orig_lease);
+ }
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query6", query);
+ callout_handle->setArgument("response6", response);
+ callout_handle->setArgument("leases6", leases);
+
+ // Invoke the leases6Committed handler.
+ LeaseCmds cmds;
+ ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));
+
+ // Fetch the lease. Context should still be empty.
+ Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
+ ASSERT_TRUE(after_lease);
+ ASSERT_FALSE(after_lease->getContext());
+ }
+}
+
+void
+LeaseCmdsFuncTest6::testLeases6CommittedErrors() {
+ // Create a config with two binding variables.
+ std::string config_str =
+ R"^({"binding-variables":[
+ {
+ "name": "duid",
+ "expression": "hexstring(option[1].hex,':')",
+ "source": "query"
+ },
+ {
+ "name": "sub-id",
+ "expression": "hexstring(option[38].hex, ':')",
+ "source": "response"
+ }]})^";
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW_LOG(config = Element::fromJSON(config_str));
+
+ // Create the expected context contents.
+ std::string exp_context_str =
+ R"({"ISC":{
+ "binding-variables":{
+ "duid": "01:02:03:04",
+ "sub-id": "05:06:07:08"
+ }
+ }})";
+
+
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(exp_context_str));
+
+ // Create packet pair and lease.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
+ { 0x01, 0x02, 0x03, 0x04 }));
+ query->addOption(client_id);
+
+ Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
+ OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
+ { 0x05, 0x06, 0x07, 0x08 }));
+ response->addOption(subscriber_id);
+
+ // Create and configure the manager.
+ BindingVariableMgrPtr mgr;
+ ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
+ ASSERT_NO_THROW_LOG(mgr->configure(config));
+
+ // Fetch the leases.
+ std::list<IOAddress> lease_addrs;
+ lease_addrs.push_back(IOAddress("2001:db8:1::1"));
+ lease_addrs.push_back(IOAddress("2001:db8:1::2"));
+ lease_addrs.push_back(IOAddress("2001:db8:2::2"));
+
+ Lease6CollectionPtr orig_leases(new Lease6Collection());
+ for (auto const& address : lease_addrs) {
+ Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
+ ASSERT_TRUE(orig_lease);
+ orig_leases->push_back(orig_lease);
+ }
+
+ // Delete the middle lease from the back end. This should cause a conflict error.
+ ASSERT_NO_THROW_LOG(lmptr_->deleteLease((*orig_leases)[1]));
+
+ // Create a callout handle and add the expected arguments.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+ callout_handle->setArgument("query6", query);
+ callout_handle->setArgument("response6", response);
+ callout_handle->setArgument("leases6", orig_leases);
+
+ // Invoke the leases6Committed handler.
+ LeaseCmds cmds;
+ ASSERT_THROW_MSG(cmds.leases6Committed(*callout_handle, mgr),
+ Unexpected,
+ "1 out of 3 leases failed to update for "
+ "duid=[01:02:03:04], [no hwaddr info], tid=0x4d2");
+
+ for (auto const& lease : *orig_leases) {
+ // Fetch the lease.
+ Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
+ if (after_lease) {
+ // Context contents should match the expected context content.
+ ASSERT_EQ(*(after_lease->getContext()), *exp_context);
+ } else {
+ // Middle lease should not be found.
+ EXPECT_EQ(lease->addr_, (*orig_leases)[1]->addr_);
+ }
+ }
+}
+
+TEST_F(LeaseCmdsFuncTest6, validLeases6Committed) {
+ testValidLeases6Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest6, validLeases6CommittedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testValidLeases6Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest6, nopLeases6Committed) {
+ testNopLeases6Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest6, nopLeases6CommittedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNopLeases6Committed();
+}
+
+TEST_F(LeaseCmdsFuncTest6, leases6CommittedErrors) {
+ testLeases6CommittedErrors();
+}
+
+TEST_F(LeaseCmdsFuncTest6, leases6CommittedErrorsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeases6CommittedErrors();
+}
+
+} // end of anonymous namespace
--- /dev/null
+// 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 <dhcpsrv/lease_mgr_factory.h>
+
+#include <gtest/gtest.h>
+
+namespace {
+
+#define SCOPED_LINE(line) \
+ std::stringstream ss; \
+ ss << "Scenario at line: " << line; \
+ SCOPED_TRACE(ss.str());
+
+class LeaseCmdsFuncTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ LeaseCmdsFuncTest() {
+ isc::dhcp::LeaseMgrFactory::destroy();
+ lmptr_ = 0;
+ }
+
+ virtual void SetUp() {
+ initLeaseMgr();
+ }
+
+ /// @brief Destructor
+ ///
+ virtual ~LeaseCmdsFuncTest() {
+ isc::dhcp::LeaseMgrFactory::destroy();
+ lmptr_ = 0;
+ }
+
+ /// @brief Initializes lease manager creates the lease manager
+ /// and initial leases.
+ virtual void initLeaseMgr() = 0;
+
+ /// @brief Pointer to the lease manager
+ isc::dhcp::LeaseMgr* lmptr_;
+};
+
+} // end of anonymous namespace