summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcin Siodelski <marcin@isc.org>2024-04-18 15:25:48 +0200
committerMarcin Siodelski <marcin@isc.org>2024-06-19 12:34:18 +0200
commita52bf68db907f9dd9f7bc829bb711637909912b0 (patch)
tree96c0a561b3fa3412d56723ef7ba20f470263bcb1
parent[#3436] fixed compilation (diff)
downloadkea-a52bf68db907f9dd9f7bc829bb711637909912b0.tar.xz
kea-a52bf68db907f9dd9f7bc829bb711637909912b0.zip
[#3246] Do not delete soft released leases
-rw-r--r--ChangeLog10
-rw-r--r--configure.ac4
-rw-r--r--src/bin/admin/tests/mysql_tests.sh.in14
-rw-r--r--src/bin/admin/tests/pgsql_tests.sh.in15
-rw-r--r--src/bin/dhcp4/dhcp4_srv.cc39
-rw-r--r--src/bin/dhcp4/tests/release_unittest.cc12
-rw-r--r--src/bin/dhcp6/dhcp6_srv.cc70
-rw-r--r--src/bin/dhcp6/tests/dhcp6_test_utils.cc7
-rw-r--r--src/hooks/dhcp/high_availability/command_creator.cc12
-rw-r--r--src/hooks/dhcp/high_availability/ha_service.cc30
-rw-r--r--src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc44
-rw-r--r--src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc309
-rw-r--r--src/hooks/dhcp/lease_cmds/lease_parser.cc10
-rw-r--r--src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc64
-rw-r--r--src/hooks/dhcp/lease_cmds/tests/lease_cmds6_unittest.cc65
-rw-r--r--src/lib/dhcpsrv/alloc_engine.cc48
-rw-r--r--src/lib/dhcpsrv/lease.cc9
-rw-r--r--src/lib/dhcpsrv/lease.h3
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc66
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc158
-rw-r--r--src/lib/dhcpsrv/tests/lease_unittest.cc2
-rw-r--r--src/lib/mysql/mysql_constants.h2
-rw-r--r--src/lib/pgsql/pgsql_connection.h2
-rw-r--r--src/share/database/scripts/mysql/.gitignore1
-rw-r--r--src/share/database/scripts/mysql/dhcpdb_create.mysql20
-rw-r--r--src/share/database/scripts/mysql/upgrade_022_to_023.sh.in68
-rw-r--r--src/share/database/scripts/pgsql/.gitignore1
-rw-r--r--src/share/database/scripts/pgsql/dhcpdb_create.pgsql13
-rw-r--r--src/share/database/scripts/pgsql/upgrade_022_to_023.sh.in55
29 files changed, 1045 insertions, 108 deletions
diff --git a/ChangeLog b/ChangeLog
index 2f6788a705..422427cf8d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2256. [func] marcin
+ High Availability hook now supports lease affinity for the
+ released leases. When a lease is released but left in the
+ database for a possible re-allocation this information is
+ propagated to the partner server. Previously, a released
+ lease was always deleted in the partner, causing a loss of
+ the association between the lease and the client in the
+ partner's lease database.
+ (Gitlab #3246)
+
2255. [bug] razvan
The environment is now inherited by kea-lfc when started by
the kea dhcp server.
diff --git a/configure.ac b/configure.ac
index 071c665963..b39fb75e17 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1766,6 +1766,8 @@ AC_CONFIG_FILES([src/share/database/scripts/mysql/upgrade_020_to_021.sh],
[chmod +x src/share/database/scripts/mysql/upgrade_020_to_021.sh])
AC_CONFIG_FILES([src/share/database/scripts/mysql/upgrade_021_to_022.sh],
[chmod +x src/share/database/scripts/mysql/upgrade_021_to_022.sh])
+AC_CONFIG_FILES([src/share/database/scripts/mysql/upgrade_022_to_023.sh],
+ [chmod +x src/share/database/scripts/mysql/upgrade_022_to_023.sh])
AC_CONFIG_FILES([src/share/database/scripts/mysql/wipe_data.sh],
[chmod +x src/share/database/scripts/mysql/wipe_data.sh])
AC_CONFIG_FILES([src/share/database/scripts/pgsql/Makefile])
@@ -1823,6 +1825,8 @@ AC_CONFIG_FILES([src/share/database/scripts/pgsql/upgrade_020_to_021.sh],
[chmod +x src/share/database/scripts/pgsql/upgrade_020_to_021.sh])
AC_CONFIG_FILES([src/share/database/scripts/pgsql/upgrade_021_to_022.sh],
[chmod +x src/share/database/scripts/pgsql/upgrade_021_to_022.sh])
+AC_CONFIG_FILES([src/share/database/scripts/pgsql/upgrade_022_to_023.sh],
+ [chmod +x src/share/database/scripts/pgsql/upgrade_022_to_023.sh])
AC_CONFIG_FILES([src/share/database/scripts/pgsql/wipe_data.sh],
[chmod +x src/share/database/scripts/pgsql/wipe_data.sh])
AC_CONFIG_FILES([src/share/yang/Makefile])
diff --git a/src/bin/admin/tests/mysql_tests.sh.in b/src/bin/admin/tests/mysql_tests.sh.in
index 3bf7b513de..b0b555b3a4 100644
--- a/src/bin/admin/tests/mysql_tests.sh.in
+++ b/src/bin/admin/tests/mysql_tests.sh.in
@@ -813,6 +813,14 @@ mysql_upgrade_18_to_19_test() {
run_statement "ipv6_reservations_insert" "$qry" "3001::99"
}
+mysql_upgrade_22_to_23_test() {
+ qry="SELECT name FROM lease_state WHERE state = 3"
+ run_command \
+ mysql_execute "${qry}"
+ assert_eq 0 "${EXIT_CODE}" "${qry}: expected %d, returned %d"
+ assert_str_eq 'released' "${OUTPUT}" "${qry}: expected output %s, returned %s"
+}
+
mysql_upgrade_test() {
test_start "mysql.upgrade"
@@ -834,7 +842,7 @@ mysql_upgrade_test() {
# Verify that the upgraded schema reports the latest version.
version=$("${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
- assert_str_eq "22.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+ assert_str_eq "23.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
# Let's check that the new tables are indeed there.
@@ -1497,6 +1505,10 @@ SET @disable_audit = 0"
# Check upgrade from 18.0 to 19.0.
mysql_upgrade_18_to_19_test
+
+ # Check upgrade from 22.0 to 23.0.
+ mysql_upgrade_22_to_23_test
+
# Let's wipe the whole database
mysql_wipe
diff --git a/src/bin/admin/tests/pgsql_tests.sh.in b/src/bin/admin/tests/pgsql_tests.sh.in
index 893fd13e90..7f8a3406b2 100644
--- a/src/bin/admin/tests/pgsql_tests.sh.in
+++ b/src/bin/admin/tests/pgsql_tests.sh.in
@@ -142,7 +142,7 @@ pgsql_db_version_test() {
run_command \
"${kea_admin}" db-version pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}"
version="${OUTPUT}"
- assert_str_eq "22.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+ assert_str_eq "23.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
# Let's wipe the whole database
pgsql_wipe
@@ -909,6 +909,14 @@ pgsql_upgrade_20_to_21_test() {
assert_str_eq '4' "${OUTPUT}" "${query}: expected output %s, returned %s"
}
+pgsql_upgrade_22_to_23_test() {
+ query="SELECT name FROM lease_state WHERE state = 3"
+ run_command \
+ pgsql_execute "${query}"
+ assert_eq 0 "${EXIT_CODE}" "${query}: expected %d, returned %d"
+ assert_str_eq 'released' "${OUTPUT}" "${query}: expected output %s, returned %s"
+}
+
pgsql_upgrade_test() {
test_start "pgsql.upgrade"
@@ -927,7 +935,7 @@ pgsql_upgrade_test() {
# Verify upgraded schema reports the latest version.
version=$("${kea_admin}" db-version pgsql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
- assert_str_eq "22.0" "${version}" 'Expected kea-admin to return %s, returned value was %s'
+ assert_str_eq "23.0" "${version}" 'Expected kea-admin to return %s, returned value was %s'
# Check 1.0 to 2.0 upgrade
pgsql_upgrade_1_0_to_2_0_test
@@ -986,6 +994,9 @@ pgsql_upgrade_test() {
# Check 20 to 21 upgrade
pgsql_upgrade_20_to_21_test
+ # Check 22 to 23 upgrade
+ pgsql_upgrade_22_to_23_test
+
# Let's wipe the whole database
pgsql_wipe
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 664a0ecdbc..3b3c9de81e 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -3920,6 +3920,12 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release, AllocEngine::ClientContext4Ptr& cont
lease->valid_lft_ != Lease::INFINITY_LFT) {
// Expire the lease.
lease->valid_lft_ = 0;
+ // Set the lease state to released to indicate that this lease
+ // must be preserved in the database. It is particularly useful
+ // in HA to differentiate between the leases that should be
+ // updated in the partner's database and deleted from the partner's
+ // database.
+ lease->state_ = Lease4::STATE_RELEASED;
LeaseMgrFactory::instance().updateLease4(lease);
expired = true;
success = true;
@@ -3945,25 +3951,26 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release, AllocEngine::ClientContext4Ptr& cont
.arg(release->getLabel())
.arg(lease->addr_.toText());
- // Need to decrease statistic for assigned addresses.
- StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-addresses"),
- static_cast<int64_t>(-1));
-
- auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
- if (subnet) {
- auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
- if (pool) {
- StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", subnet->getID(),
- StatsMgr::generateName("pool", pool->getID(), "assigned-addresses")),
- static_cast<int64_t>(-1));
- }
- }
-
// Remove existing DNS entries for the lease, if any.
queueNCR(CHG_REMOVE, lease);
}
+
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-addresses"),
+ static_cast<int64_t>(-1));
+
+ auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
+ if (subnet) {
+ auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool", pool->getID(), "assigned-addresses")),
+ static_cast<int64_t>(-1));
+ }
+ }
+
} else {
// Release failed
LOG_ERROR(lease4_logger, DHCP4_RELEASE_FAIL)
diff --git a/src/bin/dhcp4/tests/release_unittest.cc b/src/bin/dhcp4/tests/release_unittest.cc
index 62952161ea..9b784172e4 100644
--- a/src/bin/dhcp4/tests/release_unittest.cc
+++ b/src/bin/dhcp4/tests/release_unittest.cc
@@ -169,17 +169,18 @@ ReleaseTest::acquireAndRelease(const std::string& hw_address_1,
uint64_t after = assigned_cnt->getInteger().first;
// We check if the release process was successful by checking if the
- // lease is in the database. It is expected that it is not present,
- // i.e. has been deleted with the release.
+ // lease is in the database.
if (expected_result == SHOULD_PASS_EXPIRED) {
ASSERT_TRUE(lease);
// The update succeeded, so the assigned-address should be expired
EXPECT_EQ(lease->valid_lft_, 0);
+ EXPECT_EQ(Lease4::STATE_RELEASED, lease->state_);
+
+ // The removal succeeded, so the assigned-addresses statistic should
+ // be decreased by one
+ EXPECT_EQ(before, after + 1);
- // No lease has been removed, so the assigned-address should be the same
- // as before
- EXPECT_EQ(before, after);
} else if (expected_result == SHOULD_PASS_DELETED) {
EXPECT_FALSE(lease);
@@ -188,6 +189,7 @@ ReleaseTest::acquireAndRelease(const std::string& hw_address_1,
EXPECT_EQ(before, after + 1);
} else {
EXPECT_TRUE(lease);
+ EXPECT_EQ(Lease4::STATE_DEFAULT, lease->state_);
// The removal failed, so the assigned-address should be the same
// as before
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 6ac3b6aa4d..aca7ac1f16 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -3366,6 +3366,12 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
// Expire the lease.
lease->valid_lft_ = 0;
lease->preferred_lft_ = 0;
+ // Set the lease state to released to indicate that this lease
+ // must be preserved in the database. It is particularly useful
+ // in HA to differentiate between the leases that should be
+ // updated in the partner's database and deleted from the partner's
+ // database.
+ lease->state_ = Lease6::STATE_RELEASED;
LeaseMgrFactory::instance().updateLease6(lease);
expired = true;
success = true;
@@ -3410,28 +3416,28 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
.arg(lease->addr_.toText())
.arg(lease->iaid_);
- // Need to decrease statistic for assigned addresses.
- StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-nas"),
- static_cast<int64_t>(-1));
-
- auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
- if (subnet) {
- auto const& pool = subnet->getPool(Lease::TYPE_NA, lease->addr_, false);
- if (pool) {
- StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", subnet->getID(),
- StatsMgr::generateName("pool", pool->getID(), "assigned-nas")),
- static_cast<int64_t>(-1));
- }
- }
-
// Check if a lease has flags indicating that the FQDN update has
// been performed. If so, create NameChangeRequest which removes
// the entries.
queueNCR(CHG_REMOVE, lease);
}
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-nas"),
+ static_cast<int64_t>(-1));
+
+ auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
+ if (subnet) {
+ auto const& pool = subnet->getPool(Lease::TYPE_NA, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool", pool->getID(), "assigned-nas")),
+ static_cast<int64_t>(-1));
+ }
+ }
+
return (ia_rsp);
}
}
@@ -3571,6 +3577,12 @@ Dhcpv6Srv::releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query,
// Expire the lease.
lease->valid_lft_ = 0;
lease->preferred_lft_ = 0;
+ // Set the lease state to released to indicate that this lease
+ // must be preserved in the database. It is particularly useful
+ // in HA to differentiate between the leases that should be
+ // updated in the partner's database and deleted from the partner's
+ // database.
+ lease->state_ = Lease6::STATE_RELEASED;
LeaseMgrFactory::instance().updateLease6(lease);
expired = true;
success = true;
@@ -3617,21 +3629,21 @@ Dhcpv6Srv::releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query,
.arg(lease->addr_.toText())
.arg(static_cast<int>(lease->prefixlen_))
.arg(lease->iaid_);
+ }
- // Need to decrease statistic for assigned prefixes.
- StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-pds"),
- static_cast<int64_t>(-1));
+ // Need to decrease statistic for assigned prefixes.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-pds"),
+ static_cast<int64_t>(-1));
- auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
- if (subnet) {
- auto const& pool = subnet->getPool(Lease::TYPE_PD, lease->addr_, false);
- if (pool) {
- StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", subnet->getID(),
- StatsMgr::generateName("pd-pool", pool->getID(), "assigned-pds")),
- static_cast<int64_t>(-1));
- }
+ auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
+ if (subnet) {
+ auto const& pool = subnet->getPool(Lease::TYPE_PD, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pd-pool", pool->getID(), "assigned-pds")),
+ static_cast<int64_t>(-1));
}
}
}
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
index 8d3baded46..49d4d25680 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
@@ -653,14 +653,14 @@ Dhcpv6SrvTest::testReleaseBasic(Lease::Type type, const IOAddress& existing,
ASSERT_TRUE(stat);
EXPECT_EQ(0, stat->getInteger().first);
} else {
- // Check that the lease is really gone in the database
- // get lease by address
l = LeaseMgrFactory::instance().getLease6(type, release_addr);
ASSERT_TRUE(l);
EXPECT_EQ(l->valid_lft_, 0);
EXPECT_EQ(l->preferred_lft_, 0);
+ EXPECT_EQ(Lease6::STATE_RELEASED, l->state_);
+
// get lease by subnetid/duid/iaid combination
l = LeaseMgrFactory::instance().getLease6(type, *duid_, iaid,
subnet_->getID());
@@ -668,11 +668,12 @@ Dhcpv6SrvTest::testReleaseBasic(Lease::Type type, const IOAddress& existing,
EXPECT_EQ(l->valid_lft_, 0);
EXPECT_EQ(l->preferred_lft_, 0);
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
// We should have decremented the address counter
stat = StatsMgr::instance().getObservation(name);
ASSERT_TRUE(stat);
- EXPECT_EQ(before, stat->getInteger().first);
+ EXPECT_EQ(0, stat->getInteger().first);
}
}
diff --git a/src/hooks/dhcp/high_availability/command_creator.cc b/src/hooks/dhcp/high_availability/command_creator.cc
index b5d079897c..4971066028 100644
--- a/src/hooks/dhcp/high_availability/command_creator.cc
+++ b/src/hooks/dhcp/high_availability/command_creator.cc
@@ -144,14 +144,22 @@ CommandCreator::createLease4GetPage(const Lease4Ptr& last_lease4,
ConstElementPtr
CommandCreator::createLease6BulkApply(const Lease6CollectionPtr& leases,
const Lease6CollectionPtr& deleted_leases) {
+ ElementPtr leases_list = Element::createList();
ElementPtr deleted_leases_list = Element::createList();
for (auto const& lease : *deleted_leases) {
ElementPtr lease_as_json = lease->toElement();
insertLeaseExpireTime(lease_as_json);
- deleted_leases_list->add(lease_as_json);
+ // If the deleted/released lease has zero lifetimes it means that
+ // it should be preserved in the database. It is treated as released
+ // but can be re-allocated to the same client later (lease affinity).
+ // Such a lease should be updated by the partner rather than deleted.
+ if (lease->state_ == Lease6::STATE_RELEASED) {
+ leases_list->add(lease_as_json);
+ } else {
+ deleted_leases_list->add(lease_as_json);
+ }
}
- ElementPtr leases_list = Element::createList();
for (auto const& lease : *leases) {
ElementPtr lease_as_json = lease->toElement();
insertLeaseExpireTime(lease_as_json);
diff --git a/src/hooks/dhcp/high_availability/ha_service.cc b/src/hooks/dhcp/high_availability/ha_service.cc
index ebd3e531fe..7847ef718e 100644
--- a/src/hooks/dhcp/high_availability/ha_service.cc
+++ b/src/hooks/dhcp/high_availability/ha_service.cc
@@ -1218,7 +1218,14 @@ HAService::asyncSendLeaseUpdates(const dhcp::Pkt4Ptr& query,
if (shouldQueueLeaseUpdates(conf)) {
// Lease updates for deleted leases.
for (auto const& l : *deleted_leases) {
- lease_update_backlog_.push(LeaseUpdateBacklog::DELETE, l);
+ // If a released lease is preserved in the database send the lease
+ // update with the zero lifetime to the partner. Otherwise, delete
+ // the lease.
+ if (l->state_ == Lease4::STATE_RELEASED) {
+ lease_update_backlog_.push(LeaseUpdateBacklog::ADD, l);
+ } else {
+ lease_update_backlog_.push(LeaseUpdateBacklog::DELETE, l);
+ }
}
// Lease updates for new allocations and updated leases.
@@ -1245,8 +1252,16 @@ HAService::asyncSendLeaseUpdates(const dhcp::Pkt4Ptr& query,
// Lease updates for deleted leases.
for (auto const& l : *deleted_leases) {
- asyncSendLeaseUpdate(query, conf, CommandCreator::createLease4Delete(*l),
- parking_lot);
+ // If a released lease is preserved in the database send the lease
+ // update with the zero lifetime to the partner. Otherwise, delete
+ // the lease.
+ if (l->state_ == Lease4::STATE_RELEASED) {
+ asyncSendLeaseUpdate(query, conf, CommandCreator::createLease4Update(*l),
+ parking_lot);
+ } else {
+ asyncSendLeaseUpdate(query, conf, CommandCreator::createLease4Delete(*l),
+ parking_lot);
+ }
}
// Lease updates for new allocations and updated leases.
@@ -1297,7 +1312,14 @@ HAService::asyncSendLeaseUpdates(const dhcp::Pkt6Ptr& query,
// be sent when the communication is re-established.
if (shouldQueueLeaseUpdates(conf)) {
for (auto const& l : *deleted_leases) {
- lease_update_backlog_.push(LeaseUpdateBacklog::DELETE, l);
+ // If a released lease is preserved in the database send the lease
+ // update with the zero lifetime to the partner. Otherwise, delete
+ // the lease.
+ if (l->state_ == Lease4::STATE_RELEASED) {
+ lease_update_backlog_.push(LeaseUpdateBacklog::ADD, l);
+ } else {
+ lease_update_backlog_.push(LeaseUpdateBacklog::DELETE, l);
+ }
}
// Lease updates for new allocations and updated leases.
diff --git a/src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc b/src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc
index ee1fca1a0a..ac08966911 100644
--- a/src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc
+++ b/src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc
@@ -399,7 +399,7 @@ TEST(CommandCreatorTest, createLease6BulkApply) {
Lease6CollectionPtr deleted_leases(new Lease6Collection());
leases->push_back(lease);
- deleted_leases->push_back(lease);
+ deleted_leases->push_back(deleted_lease);
ConstElementPtr command = CommandCreator::createLease6BulkApply(leases, deleted_leases);
ConstElementPtr arguments;
@@ -416,7 +416,7 @@ TEST(CommandCreatorTest, createLease6BulkApply) {
ASSERT_EQ(Element::list, deleted_leases_json->getType());
ASSERT_EQ(1, deleted_leases_json->size());
auto lease_as_json = deleted_leases_json->get(0);
- EXPECT_EQ(leaseAsJson(createLease6())->str(), lease_as_json->str());
+ EXPECT_EQ(leaseAsJson(deleted_lease)->str(), lease_as_json->str());
// Verify leases.
auto leases_json = arguments->get("leases");
@@ -424,7 +424,45 @@ TEST(CommandCreatorTest, createLease6BulkApply) {
ASSERT_EQ(Element::list, leases_json->getType());
ASSERT_EQ(1, leases_json->size());
lease_as_json = leases_json->get(0);
- EXPECT_EQ(leaseAsJson(createLease6())->str(), lease_as_json->str());
+ EXPECT_EQ(leaseAsJson(lease)->str(), lease_as_json->str());
+}
+
+// This test verifies that the lease6-bulk-apply command is correct when
+// deleted leases have lifetimes of 0.
+TEST(CommandCreatorTest, createLease6BulkApplySoftDelete) {
+ Lease6Ptr deleted_lease = createLease6();
+ deleted_lease->valid_lft_ = 0;
+ deleted_lease->preferred_lft_ = 0;
+ deleted_lease->state_ = Lease6::STATE_RELEASED;
+
+ Lease6CollectionPtr leases(new Lease6Collection());
+ Lease6CollectionPtr deleted_leases(new Lease6Collection());
+
+ deleted_leases->push_back(deleted_lease);
+
+ ConstElementPtr command = CommandCreator::createLease6BulkApply(leases, deleted_leases);
+ ConstElementPtr arguments;
+ ASSERT_NO_FATAL_FAILURE(testCommandBasics(command, "lease6-bulk-apply",
+ "dhcp6", arguments));
+
+ ConstElementPtr origin = arguments->get("origin");
+ ASSERT_TRUE(origin);
+ ASSERT_EQ("ha-partner", origin->stringValue());
+
+ // There should be no deleted leases.
+ auto deleted_leases_json = arguments->get("deleted-leases");
+ ASSERT_TRUE(deleted_leases_json);
+ ASSERT_EQ(Element::list, deleted_leases_json->getType());
+ EXPECT_EQ(0, deleted_leases_json->size());
+
+ // The lease with the valid lifetime of 0 should be in the updated
+ // leases list.
+ auto leases_json = arguments->get("leases");
+ ASSERT_TRUE(leases_json);
+ ASSERT_EQ(Element::list, leases_json->getType());
+ ASSERT_EQ(1, leases_json->size());
+ auto lease_as_json = leases_json->get(0);
+ EXPECT_EQ(leaseAsJson(deleted_lease)->str(), lease_as_json->str());
}
// This test verifies that the lease6-bulk-apply command can be created
diff --git a/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc b/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc
index 5688a95848..610efb7ed0 100644
--- a/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc
+++ b/src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc
@@ -815,12 +815,16 @@ public:
/// from the backup servers.
/// @param create_service a boolean flag indicating whether the test should
/// re-create HA service and communication state.
+ /// @param soft_delete_leases a boolean flag indicating that the test should simulate
+ /// soft release of leases, i.e., lease lifetime is set to 0 and the lease is not
+ /// deleted.
void testSendLeaseUpdates(std::function<void()> unpark_handler,
const bool should_fail,
const size_t num_updates,
const MyState& my_state = MyState(HA_LOAD_BALANCING_ST),
const bool wait_backup_ack = false,
- const bool create_service = true) {
+ const bool create_service = true,
+ const bool soft_delete_leases = false) {
// Create parking lot where query is going to be parked and unparked.
ParkingLotPtr parking_lot(new ParkingLot());
ParkingLotHandlePtr parking_lot_handle(new ParkingLotHandle(parking_lot));
@@ -841,6 +845,10 @@ public:
Lease4Ptr deleted_lease4(new Lease4(IOAddress("192.2.3.4"), hwaddr,
static_cast<const uint8_t*>(0), 0,
60, 0, 1));
+ if (soft_delete_leases) {
+ deleted_lease4->valid_lft_ = 0;
+ deleted_lease4->state_ = Lease4::STATE_RELEASED;
+ }
deleted_leases4->push_back(deleted_lease4);
if (create_service) {
@@ -927,12 +935,16 @@ public:
/// from the backup servers.
/// @param create_service a boolean flag indicating whether the test should
/// re-create HA service and communication state.
+ /// @param soft_delete_leases a boolean flag indicating that the test should simulate
+ /// soft release of leases, i.e., lease lifetime is set to 0 and the lease is not
+ /// deleted.
void testSendLeaseUpdates6(std::function<void()> unpark_handler,
const bool should_fail,
const size_t num_updates,
const MyState& my_state = MyState(HA_LOAD_BALANCING_ST),
const bool wait_backup_ack = false,
- const bool create_service = true) {
+ const bool create_service = true,
+ const bool soft_delete_leases = false) {
// Create parking lot where query is going to be parked and unparked.
ParkingLotPtr parking_lot(new ParkingLot());
@@ -958,6 +970,11 @@ public:
Lease6CollectionPtr deleted_leases6(new Lease6Collection());
Lease6Ptr deleted_lease6(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::efac"),
duid, 1234, 50, 60, 1));
+ if (soft_delete_leases) {
+ deleted_lease6->valid_lft_ = 0;
+ deleted_lease6->preferred_lft_ = 0;
+ deleted_lease6->state_ = Lease6::STATE_RELEASED;
+ }
deleted_leases6->push_back(deleted_lease6);
if (create_service) {
@@ -1143,6 +1160,63 @@ public:
EXPECT_TRUE(delete_request3);
}
+ /// @brief Tests scenarios when released leases have zero valid
+ /// lifetime and should be preserved in the partner's lease
+ /// database.
+ void testSendSuccessfulUpdatesSoftDelete() {
+ // Start HTTP servers.
+ ASSERT_NO_THROW({
+ listener_->start();
+ listener2_->start();
+ listener3_->start();
+ });
+
+ // This flag will be set to true if unpark is called.
+ bool unpark_called = false;
+ testSendLeaseUpdates([&unpark_called] { unpark_called = true; },
+ false, 1, MyState(HA_LOAD_BALANCING_ST), false,
+ true, true);
+
+ // Expecting that the packet was unparked because lease
+ // updates are expected to be successful.
+ EXPECT_TRUE(unpark_called);
+
+ // Updates have been sent so this counter should remain 0.
+ EXPECT_EQ(0, service_->communication_state_->getUnsentUpdateCount());
+
+ // The server 2 should have received two commands.
+ EXPECT_EQ(2, factory2_->getResponseCreator()->getReceivedRequests().size());
+
+ // Check that the server 2 has received lease4-update command.
+ auto update_request2 =
+ factory2_->getResponseCreator()->findRequest("lease4-update",
+ "192.1.2.3");
+ EXPECT_TRUE(update_request2);
+
+ // Check that the server 2 has received lease4-update command
+ // for released lease.
+ auto soft_delete_request2 =
+ factory2_->getResponseCreator()->findRequest("lease4-update",
+ "192.2.3.4");
+ EXPECT_TRUE(soft_delete_request2);
+
+ // Lease updates should be successfully sent to server3.
+ EXPECT_EQ(2, factory3_->getResponseCreator()->getReceivedRequests().size());
+
+ // Check that the server 3 has received lease4-update command.
+ auto update_request3 =
+ factory3_->getResponseCreator()->findRequest("lease4-update",
+ "192.1.2.3");
+ EXPECT_TRUE(update_request3);
+
+ // Check that the server 3 has received lease4-update command
+ // for released lease.
+ auto soft_delete_request3 =
+ factory3_->getResponseCreator()->findRequest("lease4-update",
+ "192.2.3.4");
+ EXPECT_TRUE(soft_delete_request3);
+ }
+
/// @brief Tests that DHCPv4 lease updates are queued when the server is in the
/// communication-recovery state and later sent before transitioning back to
/// the load-balancing state.
@@ -1188,6 +1262,51 @@ public:
EXPECT_EQ(0, service_->lease_update_backlog_.size());
}
+ /// @brief Tests sending outstanding lease updates in the communication-recovery
+ /// state when released leases are preserved in the database.
+ void testSendUpdatesCommunicationRecoverySoftDelete() {
+ // Start HTTP servers.
+ ASSERT_NO_THROW({
+ listener_->start();
+ listener2_->start();
+ listener3_->start();
+ });
+
+ // This flag will be set to true if unpark is called.
+ bool unpark_called = false;
+ testSendLeaseUpdates([&unpark_called] { unpark_called = true; },
+ false, 0, MyState(HA_COMMUNICATION_RECOVERY_ST),
+ false, true, true);
+
+ // Packet shouldn't be unparked because no updates went out. We merely
+ // queued the updates.
+ EXPECT_FALSE(unpark_called);
+
+ // Let's make sure they have been queued.
+ EXPECT_EQ(2, service_->lease_update_backlog_.size());
+
+ // Make partner available.
+ service_->communication_state_->poke();
+ service_->communication_state_->setPartnerState("load-balancing");
+
+ // This should cause the server to send outstanding lease updates and
+ // because they are all successful the server should transition to the
+ // load-balancing state and continue normal operation.
+ testSynchronousCommands([this]() {
+ service_->runModel(HAService::NOP_EVT);
+ EXPECT_EQ(HA_LOAD_BALANCING_ST, service_->getCurrState());
+ });
+
+ // Lease updates should have been sent.
+ EXPECT_TRUE(factory2_->getResponseCreator()->findRequest("lease4-update",
+ "192.1.2.3"));
+ EXPECT_TRUE(factory2_->getResponseCreator()->findRequest("lease4-update",
+ "192.2.3.4"));
+
+ // Backlog should be empty.
+ EXPECT_EQ(0, service_->lease_update_backlog_.size());
+ }
+
/// @brief Test the cases when the trying to recover from the communication
/// interruption and sending lease updates or/and ha-reset fails.
///
@@ -1507,6 +1626,7 @@ public:
// Change the partner's response to success.
factory2_->getResponseCreator()->setControlResult(CONTROL_RESULT_SUCCESS);
+ factory3_->getResponseCreator()->setControlResult(CONTROL_RESULT_SUCCESS);
io_service_->stopAndPoll();
@@ -1562,6 +1682,67 @@ public:
EXPECT_TRUE(update_request3);
}
+ /// @brief Tests scenarios when released leases have zero valid
+ /// lifetime and should be preserved in the partner's lease
+ /// database.
+ void testSendSuccessfulUpdates6SoftDelete() {
+ // Start HTTP servers.
+ ASSERT_NO_THROW({
+ listener_->start();
+ listener2_->start();
+ listener3_->start();
+ });
+
+ // This flag will be set to true if unpark is called.
+ bool unpark_called = false;
+ testSendLeaseUpdates6([&unpark_called] { unpark_called = true; },
+ false, 1, MyState(HA_LOAD_BALANCING_ST),
+ false, true, true);
+
+ // Expecting that the packet was unparked because lease
+ // updates are expected to be successful.
+ EXPECT_TRUE(unpark_called);
+
+ // Updates have been sent so this counter should remain 0.
+ EXPECT_EQ(0, service_->communication_state_->getUnsentUpdateCount());
+
+ // The server 2 should have received one command.
+ EXPECT_EQ(1, factory2_->getResponseCreator()->getReceivedRequests().size());
+
+ // Check that the server 2 has received lease6-bulk-apply command.
+ auto update_request2 =
+ factory2_->getResponseCreator()->findRequest("lease6-bulk-apply",
+ "2001:db8:1::cafe",
+ "2001:db8:1::efac");
+ ASSERT_TRUE(update_request2);
+
+ // Make sure that both leases are to be updated in the partner's
+ // database.
+ auto arguments = update_request2->getBodyAsJson()->get("arguments");
+ ASSERT_TRUE(arguments);
+ EXPECT_EQ(Element::map, arguments->getType());
+ auto leases = arguments->get("leases");
+ ASSERT_TRUE(leases);
+ EXPECT_EQ(Element::list, leases->getType());
+ EXPECT_EQ(2, leases->size());
+
+ // Lease updates should be successfully sent to server3.
+ EXPECT_EQ(1, factory3_->getResponseCreator()->getReceivedRequests().size());
+
+ // Check that the server 3 has received lease6-bulk-apply command.
+ auto update_request3 =
+ factory3_->getResponseCreator()->findRequest("lease6-bulk-apply",
+ "2001:db8:1::cafe",
+ "2001:db8:1::efac");
+ EXPECT_TRUE(update_request3);
+
+ arguments = update_request3->getBodyAsJson()->get("arguments");
+ EXPECT_EQ(Element::map, arguments->getType());
+ leases = arguments->get("leases");
+ EXPECT_EQ(Element::list, leases->getType());
+ EXPECT_EQ(2, leases->size());
+ }
+
/// @brief Tests that DHCPv6 lease updates are queued when the server is in the
/// communication-recovery state and later sent before transitioning back to
/// the load-balancing state.
@@ -1598,9 +1779,75 @@ public:
});
// Bulk lease update should have been sent.
- EXPECT_TRUE(factory2_->getResponseCreator()->findRequest("lease6-bulk-apply",
- "2001:db8:1::cafe",
- "2001:db8:1::efac"));
+ auto update_request = factory2_->getResponseCreator()->findRequest("lease6-bulk-apply",
+ "2001:db8:1::cafe",
+ "2001:db8:1::efac");
+ ASSERT_TRUE(update_request);
+
+ // Verify that there is one lease to be updated and one to be deleted.
+ auto arguments = update_request->getBodyAsJson()->get("arguments");
+ EXPECT_EQ(Element::map, arguments->getType());
+ auto leases = arguments->get("leases");
+ ASSERT_TRUE(leases);
+ EXPECT_EQ(Element::list, leases->getType());
+ EXPECT_EQ(1, leases->size());
+ auto deleted_leases = arguments->get("deleted-leases");
+ ASSERT_TRUE(deleted_leases);
+ EXPECT_EQ(Element::list, deleted_leases->getType());
+ EXPECT_EQ(1, deleted_leases->size());
+
+ // Backlog should be empty.
+ EXPECT_EQ(0, service_->lease_update_backlog_.size());
+ }
+
+ /// @brief Tests sending outstanding lease updates in the communication-recovery
+ /// state when released leases are preserved in the database.
+ void testSendUpdatesCommunicationRecovery6SoftDelete() {
+ // Start HTTP servers.
+ ASSERT_NO_THROW({
+ listener_->start();
+ listener2_->start();
+ listener3_->start();
+ });
+
+ // This flag will be set to true if unpark is called.
+ bool unpark_called = false;
+ testSendLeaseUpdates6([&unpark_called] { unpark_called = true; },
+ false, 0, MyState(HA_COMMUNICATION_RECOVERY_ST),
+ false, true, true);
+
+ // Packet shouldn't be unparked because no updates went out. We merely
+ // queued the updates.
+ EXPECT_FALSE(unpark_called);
+
+ // Let's make sure they have been queued.
+ EXPECT_EQ(2, service_->lease_update_backlog_.size());
+
+ // Make partner available.
+ service_->communication_state_->poke();
+ service_->communication_state_->setPartnerState("load-balancing");
+
+ // This should cause the server to send outstanding lease updates and
+ // because they are all successful the server should transition to the
+ // load-balancing state and continue normal operation.
+ testSynchronousCommands([this]() {
+ service_->runModel(HAService::NOP_EVT);
+ EXPECT_EQ(HA_LOAD_BALANCING_ST, service_->getCurrState());
+ });
+
+ // Bulk lease update should have been sent.
+ auto update_request = factory2_->getResponseCreator()->findRequest("lease6-bulk-apply",
+ "2001:db8:1::cafe",
+ "2001:db8:1::efac");
+ EXPECT_TRUE(update_request);
+
+ // Make sure that both leases are to be updated in the partner's
+ // database.
+ auto arguments = update_request->getBodyAsJson()->get("arguments");
+ EXPECT_EQ(Element::map, arguments->getType());
+ auto leases = arguments->get("leases");
+ EXPECT_EQ(Element::list, leases->getType());
+ EXPECT_EQ(2, leases->size());
// Backlog should be empty.
EXPECT_EQ(0, service_->lease_update_backlog_.size());
@@ -2489,6 +2736,19 @@ TEST_F(HAServiceTest, sendSuccessfulUpdatesMultiThreading) {
testSendSuccessfulUpdates();
}
+// Tests scenarios when released leases have zero valid lifetime
+// and should be preserved in the partner's lease database.
+TEST_F(HAServiceTest, sendSuccessfulUpdatesSoftDelete) {
+ testSendSuccessfulUpdatesSoftDelete();
+}
+
+// Tests scenarios when released leases are have zero valid lifetime
+// and should be preserved in the partner's lease database.
+TEST_F(HAServiceTest, sendSuccessfulUpdatesSoftDeleteMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testSendSuccessfulUpdatesSoftDelete();
+}
+
// Test scenario when lease updates are queued in the communication-recovery
// state for later send.
TEST_F(HAServiceTest, sendUpdatesCommunicationRecovery) {
@@ -2502,6 +2762,19 @@ TEST_F(HAServiceTest, sendUpdatesCommunicationRecoveryMultiThreading) {
testSendUpdatesCommunicationRecovery();
}
+// Tests sending outstanding lease updates in the communication-recovery
+// state when released leases are preserved in the database.
+TEST_F(HAServiceTest, sendUpdatesCommunicationRecoverySoftDelete) {
+ testSendUpdatesCommunicationRecoverySoftDelete();
+}
+
+// Tests sending outstanding lease updates in the communication-recovery
+// state when released leases are preserved in the database.
+TEST_F(HAServiceTest, senUpdatesCommunicationRecoverySoftDeleteMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testSendUpdatesCommunicationRecoverySoftDelete();
+}
+
// Test scenario when lease updates are queued in the communication-recovery
// state and sending them later is unsuccessful. Partner is in load-balancing
// state when the communication is re-established, so the test expects that the
@@ -2671,6 +2944,19 @@ TEST_F(HAServiceTest, sendSuccessfulUpdates6MultiThreading) {
testSendSuccessfulUpdates6();
}
+// Tests scenarios when released leases have zero valid lifetime
+// and should be preserved in the partner's lease database.
+TEST_F(HAServiceTest, sendSuccessfulUpdates6SoftDelete) {
+ testSendSuccessfulUpdates6SoftDelete();
+}
+
+// Tests scenarios when released leases are have zero valid lifetime
+// and should be preserved in the partner's lease database.
+TEST_F(HAServiceTest, sendSuccessfulUpdates6SoftDeleteMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testSendSuccessfulUpdates6SoftDelete();
+}
+
// Test scenario when lease updates are queued in the communication-recovery
// state for later send. Then, the partner refuses lease updates causing the
// server to transition to the waiting state.
@@ -2686,6 +2972,19 @@ TEST_F(HAServiceTest, sendUpdatesCommunicationRecovery6MultiThreading) {
testSendUpdatesCommunicationRecovery6();
}
+// Tests sending outstanding lease updates in the communication-recovery
+// state when released leases are preserved in the database.
+TEST_F(HAServiceTest, sendUpdatesCommunicationRecovery6SoftDelete) {
+ testSendUpdatesCommunicationRecovery6SoftDelete();
+}
+
+// Tests sending outstanding lease updates in the communication-recovery
+// state when released leases are preserved in the database.
+TEST_F(HAServiceTest, sendUpdatesCommunicationRecovery6SoftDeleteMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testSendUpdatesCommunicationRecovery6SoftDelete();
+}
+
// Test scenario when lease updates are queued in the communication-recovery
// state and sending them later is unsuccessful. Partner is in load-balancing
// state when the communication is re-established, so the test expects that the
diff --git a/src/hooks/dhcp/lease_cmds/lease_parser.cc b/src/hooks/dhcp/lease_cmds/lease_parser.cc
index 585c079edb..a531be1adb 100644
--- a/src/hooks/dhcp/lease_cmds/lease_parser.cc
+++ b/src/hooks/dhcp/lease_cmds/lease_parser.cc
@@ -144,9 +144,10 @@ Lease4Parser::parse(ConstSrvConfigPtr& cfg,
}
// Check if the state value is sane.
- if (state > Lease::STATE_EXPIRED_RECLAIMED) {
+ if (state > Lease::STATE_RELEASED) {
isc_throw(BadValue, "Invalid state value: " << state << ", supported "
- "values are: 0 (default), 1 (declined) and 2 (expired-reclaimed)");
+ "values are: 0 (default), 1 (declined), 2 (expired-reclaimed)"
+ " and 3 (released)");
}
// Handle user context.
@@ -351,9 +352,10 @@ Lease6Parser::parse(ConstSrvConfigPtr& cfg,
}
// Check if the state value is sane.
- if (state > Lease::STATE_EXPIRED_RECLAIMED) {
+ if (state > Lease::STATE_RELEASED) {
isc_throw(BadValue, "Invalid state value: " << state << ", supported "
- "values are: 0 (default), 1 (declined) and 2 (expired-reclaimed)");
+ "values are: 0 (default), 1 (declined), 2 (expired-reclaimed)"
+ " and 3 (released)");
}
if ((state == Lease::STATE_DECLINED) && (type == Lease::TYPE_PD)) {
diff --git a/src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc b/src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc
index 9416e34d45..783d5ce52e 100644
--- a/src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc
+++ b/src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc
@@ -150,6 +150,9 @@ public:
/// @brief Check that a simple, well formed lease4 can be added.
void testLease4AddDeclinedLeases();
+ /// @brief Check that a simple, well formed released lease4 can be added.
+ void testLease4AddReleasedLeases();
+
/// @brief Check that a lease4 is not added when it already exists.
void testLease4AddExisting();
@@ -546,11 +549,11 @@ void Lease4CmdsTest::testLease4AddBadParams() {
" \"subnet-id\": 44,\n"
" \"ip-address\": \"192.0.2.1\",\n"
" \"hw-address\": \"1a:1b:1c:1d:1e:1f\",\n"
- " \"state\": 123\n"
+ " \"state\": 4\n"
" }\n"
"}";
- exp_rsp = "Invalid state value: 123, supported values are: 0 (default), 1 "
- "(declined) and 2 (expired-reclaimed)";
+ exp_rsp = "Invalid state value: 4, supported values are: 0 (default), 1 "
+ "(declined), 2 (expired-reclaimed) and 3 (released)";
testCommand(txt, CONTROL_RESULT_ERROR, exp_rsp);
// Bad user context: not a map.
@@ -707,6 +710,52 @@ void Lease4CmdsTest::testLease4AddDeclinedLeases() {
EXPECT_EQ(1, l->state_);
}
+void Lease4CmdsTest::testLease4AddReleasedLeases() {
+ // Initialize lease manager (false = v4, false = don't add leases)
+ initLeaseMgr(false, false);
+
+ checkLease4Stats(44, 0, 0);
+
+ checkLease4Stats(88, 0, 0);
+
+ // Now send the command.
+ string txt =
+ "{\n"
+ " \"command\": \"lease4-add\",\n"
+ " \"arguments\": {"
+ " \"subnet-id\": 44,\n"
+ " \"ip-address\": \"192.0.2.202\",\n"
+ " \"state\": 3,\n"
+ " \"hw-address\": \"1a:1b:1c:1d:1e:1f\"\n"
+ " }\n"
+ "}";
+ string exp_rsp = "Lease for address 192.0.2.202, subnet-id 44 added.";
+ testCommand(txt, CONTROL_RESULT_SUCCESS, exp_rsp);
+
+ checkLease4Stats(44, 1, 0);
+
+ checkLease4Stats(88, 0, 0);
+
+ // Now check that the lease is really there.
+ Lease4Ptr l = lmptr_->getLease4(IOAddress("192.0.2.202"));
+ ASSERT_TRUE(l);
+
+ // Make sure the lease has proper value set.
+ ASSERT_TRUE(l->hwaddr_);
+ EXPECT_EQ("1a:1b:1c:1d:1e:1f", l->hwaddr_->toText(false));
+ EXPECT_EQ(3, l->valid_lft_); // taken from subnet configuration
+ EXPECT_FALSE(l->fqdn_fwd_);
+ EXPECT_FALSE(l->fqdn_rev_);
+ EXPECT_EQ("", l->hostname_);
+ EXPECT_FALSE(l->getContext());
+
+ // Test execution is fast. The cltt should be set to now. In some rare
+ // cases we could have the seconds counter to tick, so having a value off
+ // by one is ok.
+ EXPECT_LE(abs(l->cltt_ - time(NULL)), 1);
+ EXPECT_EQ(3, l->state_);
+}
+
void Lease4CmdsTest::testLease4AddExisting() {
// Initialize lease manager (false = v4, true = add leases)
initLeaseMgr(false, true);
@@ -3469,6 +3518,15 @@ TEST_F(Lease4CmdsTest, lease4AddDeclinedLeasesMultiThreading) {
testLease4AddDeclinedLeases();
}
+TEST_F(Lease4CmdsTest, lease4AddReleasedLeases) {
+ testLease4AddDeclinedLeases();
+}
+
+TEST_F(Lease4CmdsTest, lease4AddReleasedLeasesMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease4AddDeclinedLeases();
+}
+
TEST_F(Lease4CmdsTest, lease4AddExisting) {
testLease4AddExisting();
}
diff --git a/src/hooks/dhcp/lease_cmds/tests/lease_cmds6_unittest.cc b/src/hooks/dhcp/lease_cmds/tests/lease_cmds6_unittest.cc
index 65cdda2275..8bdda0b78b 100644
--- a/src/hooks/dhcp/lease_cmds/tests/lease_cmds6_unittest.cc
+++ b/src/hooks/dhcp/lease_cmds/tests/lease_cmds6_unittest.cc
@@ -159,6 +159,9 @@ public:
/// @brief Check that a simple, well formed lease6 can be added.
void testLease6AddDeclinedLeases();
+ /// @brief Check that a simple, well formed released lease6 can be added.
+ void testLease6AddReleasedLeases();
+
/// @brief Check that a lease6 is not added when it already exists.
void testLease6AddExisting();
@@ -609,11 +612,11 @@ void Lease6CmdsTest::testLease6AddBadParams() {
" \"ip-address\": \"2001:db8:1::1\",\n"
" \"duid\": \"1a:1b:1c:1d:1e:1f\",\n"
" \"iaid\": 1234\n,"
- " \"state\": 123\n"
+ " \"state\": 4\n"
" }\n"
"}";
- exp_rsp = "Invalid state value: 123, supported values are: 0 (default), 1 "
- "(declined) and 2 (expired-reclaimed)";
+ exp_rsp = "Invalid state value: 4, supported values are: 0 (default), 1 "
+ "(declined), 2 (expired-reclaimed) and 3 (released)";
testCommand(txt, CONTROL_RESULT_ERROR, exp_rsp);
// Bad user context: not a map.
@@ -809,6 +812,53 @@ void Lease6CmdsTest::testLease6AddDeclinedLeases() {
EXPECT_EQ(1, l->state_);
}
+void Lease6CmdsTest::testLease6AddReleasedLeases() {
+ // Initialize lease manager (true = v6, false = don't add leases)
+ initLeaseMgr(true, false);
+
+ checkLease6Stats(66, 0, 0, 0);
+
+ checkLease6Stats(99, 0, 0, 0);
+
+ // Now send the command.
+ string txt =
+ "{\n"
+ " \"command\": \"lease6-add\",\n"
+ " \"arguments\": {"
+ " \"subnet-id\": 66,\n"
+ " \"ip-address\": \"2001:db8:1::3\",\n"
+ " \"state\": 3,\n"
+ " \"duid\": \"1a:1b:1c:1d:1e:1f\",\n"
+ " \"iaid\": 1234\n"
+ " }\n"
+ "}";
+ string exp_rsp = "Lease for address 2001:db8:1::3, subnet-id 66 added.";
+ testCommand(txt, CONTROL_RESULT_SUCCESS, exp_rsp);
+
+ checkLease6Stats(66, 1, 0, 0);
+
+ checkLease6Stats(99, 0, 0, 0);
+
+ // Now check that the lease is really there.
+ Lease6Ptr l = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3"));
+ ASSERT_TRUE(l);
+
+ // Make sure the lease has proper value set.
+ ASSERT_TRUE(l->duid_);
+ EXPECT_EQ("1a:1b:1c:1d:1e:1f", l->duid_->toText());
+ EXPECT_EQ(4, l->valid_lft_); // taken from subnet configuration
+ EXPECT_FALSE(l->fqdn_fwd_);
+ EXPECT_FALSE(l->fqdn_rev_);
+ EXPECT_EQ("", l->hostname_);
+ EXPECT_FALSE(l->getContext());
+
+ // Test execution is fast. The cltt should be set to now. In some rare
+ // cases we could have the seconds counter to tick, so having a value off
+ // by one is ok.
+ EXPECT_LE(abs(l->cltt_ - time(NULL)), 1);
+ EXPECT_EQ(3, l->state_);
+}
+
void Lease6CmdsTest::testLease6AddExisting() {
// Initialize lease manager (true = v6, true = add leases)
initLeaseMgr(true, true);
@@ -4204,6 +4254,15 @@ TEST_F(Lease6CmdsTest, lease6AddDeclinedLeasesMultiThreading) {
testLease6AddDeclinedLeases();
}
+TEST_F(Lease6CmdsTest, lease6AddReleasedLeases) {
+ testLease6AddReleasedLeases();
+}
+
+TEST_F(Lease6CmdsTest, lease6AddReleasedLeasesMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6AddReleasedLeases();
+}
+
TEST_F(Lease6CmdsTest, lease6AddExisting) {
testLease6AddExisting();
}
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index 6019012b1f..5e84357079 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -2445,7 +2445,7 @@ AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases
if (!ctx.fake_allocation_) {
bool update_stats = false;
- if (lease->state_ == Lease::STATE_EXPIRED_RECLAIMED) {
+ if (lease->state_ == Lease::STATE_EXPIRED_RECLAIMED || lease->state_ == Lease::STATE_RELEASED) {
// Transition lease state to default (aka assigned)
lease->state_ = Lease::STATE_DEFAULT;
@@ -2908,6 +2908,20 @@ AllocEngine::reclaimExpiredLease(const Lease6Ptr& lease,
// Update statistics.
+ // Increase number of reclaimed leases for a subnet.
+ StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
+ lease->subnet_id_,
+ "reclaimed-leases"),
+ static_cast<int64_t>(1));
+
+ // Increase total number of reclaimed leases.
+ StatsMgr::instance().addValue("reclaimed-leases", static_cast<int64_t>(1));
+
+ // Statistics must have been updated during the release.
+ if (lease->state_ == Lease::STATE_RELEASED) {
+ return;
+ }
+
// Decrease number of assigned leases.
if (lease->type_ == Lease::TYPE_NA) {
// IA_NA
@@ -2959,15 +2973,6 @@ AllocEngine::reclaimExpiredLease(const Lease6Ptr& lease,
}
}
}
-
- // Increase number of reclaimed leases for a subnet.
- StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
- lease->subnet_id_,
- "reclaimed-leases"),
- static_cast<int64_t>(1));
-
- // Increase total number of reclaimed leases.
- StatsMgr::instance().addValue("reclaimed-leases", static_cast<int64_t>(1));
}
void
@@ -3043,11 +3048,8 @@ AllocEngine::reclaimExpiredLease(const Lease4Ptr& lease,
// Update statistics.
- // Decrease number of assigned addresses.
- StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
- lease->subnet_id_,
- "assigned-addresses"),
- static_cast<int64_t>(-1));
+ // Increase total number of reclaimed leases.
+ StatsMgr::instance().addValue("reclaimed-leases", static_cast<int64_t>(1));
// Increase number of reclaimed leases for a subnet.
StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
@@ -3055,6 +3057,17 @@ AllocEngine::reclaimExpiredLease(const Lease4Ptr& lease,
"reclaimed-leases"),
static_cast<int64_t>(1));
+ // Statistics must have been updated during the release.
+ if (lease->state_ == Lease4::STATE_RELEASED) {
+ return;
+ }
+
+ // Decrease number of assigned addresses.
+ StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
+ lease->subnet_id_,
+ "assigned-addresses"),
+ static_cast<int64_t>(-1));
+
auto const& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
if (subnet) {
auto const& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
@@ -3072,9 +3085,6 @@ AllocEngine::reclaimExpiredLease(const Lease4Ptr& lease,
static_cast<int64_t>(1));
}
}
-
- // Increase total number of reclaimed leases.
- StatsMgr::instance().addValue("reclaimed-leases", static_cast<int64_t>(1));
}
void
@@ -4134,7 +4144,7 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
.arg(ctx.query_->getLabel())
.arg(client_lease->addr_.toText());
- if (LeaseMgrFactory::instance().deleteLease(client_lease)) {
+ if (LeaseMgrFactory::instance().deleteLease(client_lease) && (client_lease->state_ != Lease4::STATE_RELEASED)) {
// Need to decrease statistic for assigned addresses.
StatsMgr::instance().addValue(
StatsMgr::generateName("subnet", client_lease->subnet_id_,
diff --git a/src/lib/dhcpsrv/lease.cc b/src/lib/dhcpsrv/lease.cc
index 2ea916794d..373bd03504 100644
--- a/src/lib/dhcpsrv/lease.cc
+++ b/src/lib/dhcpsrv/lease.cc
@@ -24,9 +24,10 @@ using namespace std;
namespace isc {
namespace dhcp {
-const uint32_t Lease::STATE_DEFAULT = 0x0;
-const uint32_t Lease::STATE_DECLINED = 0x1;
-const uint32_t Lease::STATE_EXPIRED_RECLAIMED = 0x2;
+const uint32_t Lease::STATE_DEFAULT = 0;
+const uint32_t Lease::STATE_DECLINED = 1;
+const uint32_t Lease::STATE_EXPIRED_RECLAIMED = 2;
+const uint32_t Lease::STATE_RELEASED = 3;
std::string
Lease::lifetimeToText(uint32_t lifetime) {
@@ -97,6 +98,8 @@ Lease::basicStatesToText(const uint32_t state) {
return ("declined");
case STATE_EXPIRED_RECLAIMED:
return ("expired-reclaimed");
+ case STATE_RELEASED:
+ return ("released");
default:
// The default case will be handled further on
;
diff --git a/src/lib/dhcpsrv/lease.h b/src/lib/dhcpsrv/lease.h
index f84302d276..8e4d297d76 100644
--- a/src/lib/dhcpsrv/lease.h
+++ b/src/lib/dhcpsrv/lease.h
@@ -74,6 +74,9 @@ struct Lease : public isc::data::UserContext, public isc::data::CfgToElement {
/// @brief Expired and reclaimed lease.
static const uint32_t STATE_EXPIRED_RECLAIMED;
+ /// @brief Released lease held in the database for lease affinity.
+ static const uint32_t STATE_RELEASED;
+
/// @brief Returns name(s) of the basic lease state(s).
///
/// @param state A numeric value holding a state information.
diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
index 8a4286ad59..906c3a9952 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
@@ -1950,6 +1950,72 @@ TEST_F(AllocEngine4Test, requestReuseDeclinedLease4Stats) {
EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1, subnet_->getID()));
}
+// This test checks if a released lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine4Test, requestReuseReleasedLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.105");
+
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2),
+ 495, now, subnet_->getID()));
+ lease->state_ = Lease4::STATE_RELEASED;
+ // Make a copy of the lease, so as we can compare that with the old lease
+ // instance returned by the allocation engine.
+ Lease4 original_lease(*lease);
+
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress(addr), false, false,
+ "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx);
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // The allocation engine should return a copy of the old lease. This
+ // lease should be equal to the original lease.
+ ASSERT_TRUE(ctx.old_lease_);
+ EXPECT_TRUE(*ctx.old_lease_ == original_lease);
+
+ EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1, subnet_->getID()));
+}
+
// This test checks that the Allocation Engine correctly identifies the
// existing client's lease in the lease database, using the client
// identifier and HW address.
diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
index 6f3e88cc2e..1e6469658e 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
@@ -776,6 +776,95 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
EXPECT_TRUE(testStatistics("reclaimed-leases", 1, other_subnetid));
}
+// This test checks if a released lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine6Test, requestReuseReleasedLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+ cfg_mgr.commit();
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's create an expired lease
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+
+ const SubnetID other_subnetid = 999;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, other_subnetid, HWAddrPtr()));
+ lease->state_ = Lease6::STATE_RELEASED;
+ int64_t other_cumulative =
+ getStatistics("cumulative-assigned-nas", other_subnetid);
+
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ lease->fqdn_fwd_ = true;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ // This reactivated lease should have updated FQDN data.
+ EXPECT_TRUE(lease->hostname_.empty());
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+ EXPECT_EQ(Lease6::STATE_DEFAULT, lease->state_);
+
+ // Check that the old lease has been returned.
+ Lease6Ptr old_lease = expectOneLease(ctx.currentIA().old_leases_);
+ ASSERT_TRUE(old_lease);
+
+ // It should at least have the same IPv6 address.
+ EXPECT_EQ(lease->addr_, old_lease->addr_);
+ // Check that it carries not updated FQDN data.
+ EXPECT_EQ("myhost.example.com.", old_lease->hostname_);
+ EXPECT_TRUE(old_lease->fqdn_fwd_);
+ EXPECT_TRUE(old_lease->fqdn_rev_);
+ EXPECT_EQ(Lease6::STATE_RELEASED, old_lease->state_);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr);
+ ASSERT_TRUE(from_mgr);
+
+ EXPECT_EQ(Lease6::STATE_DEFAULT, from_mgr->state_);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Verify the stats got adjusted correctly
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1, other_subnetid));
+}
+
// Checks if the lease lifetime is extended when the client sends the
// Request.
TEST_F(AllocEngine6Test, requestExtendLeaseLifetime) {
@@ -1046,6 +1135,70 @@ TEST_F(AllocEngine6Test, renewClassLeaseLifetime) {
EXPECT_EQ(renewed[0]->valid_lft_, 700);
}
+// Checks if a released lease is renewed and that its state is set to default.
+TEST_F(AllocEngine6Test, renewReleasedLease) {
+ // Create a released lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+ lease->state_ = Lease6::STATE_RELEASED;
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));
+
+ // Client should renew the lease.
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+
+ // The lease should have the default state.
+ EXPECT_EQ(Lease6::STATE_DEFAULT, renewed[0]->state_);
+}
+
+// Checks if a released lease is re-allocated and that its state set to default.
+TEST_F(AllocEngine6Test, reallocReleasedLease) {
+ // Create a released lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+ lease->state_ = Lease6::STATE_RELEASED;
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));
+
+ // Reallocate the released lease.
+ Lease6Ptr renewed = simpleAlloc6Test(pool_, IOAddress("::"), false);
+ ASSERT_TRUE(renewed);
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+
+ // The lease should have the default state.
+ EXPECT_EQ(Lease6::STATE_DEFAULT, renewed->state_);
+}
+
// Renew and the client has a reservation for the lease.
TEST_F(AllocEngine6Test, renewExtendLeaseLifetimeForReservation) {
// Create reservation for the client. This is in-pool reservation,
@@ -1097,10 +1250,7 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitNoHint) {
AllocEngine engine(100);
- Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true);
- ASSERT_TRUE(lease);
- EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
-}
+ }
// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
diff --git a/src/lib/dhcpsrv/tests/lease_unittest.cc b/src/lib/dhcpsrv/tests/lease_unittest.cc
index e50db582bb..8d3b1b6b56 100644
--- a/src/lib/dhcpsrv/tests/lease_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_unittest.cc
@@ -589,6 +589,7 @@ TEST_F(Lease4Test, stateToText) {
EXPECT_EQ("default", Lease4::statesToText(Lease::STATE_DEFAULT));
EXPECT_EQ("declined", Lease4::statesToText(Lease::STATE_DECLINED));
EXPECT_EQ("expired-reclaimed", Lease4::statesToText(Lease::STATE_EXPIRED_RECLAIMED));
+ EXPECT_EQ("released", Lease4::statesToText(Lease::STATE_RELEASED));
}
/// @brief Creates an instance of the lease with certain FQDN data.
@@ -1363,6 +1364,7 @@ TEST(Lease6Test, stateToText) {
EXPECT_EQ("default", Lease6::statesToText(Lease::STATE_DEFAULT));
EXPECT_EQ("declined", Lease6::statesToText(Lease::STATE_DECLINED));
EXPECT_EQ("expired-reclaimed", Lease6::statesToText(Lease::STATE_EXPIRED_RECLAIMED));
+ EXPECT_EQ("released", Lease6::statesToText(Lease::STATE_RELEASED));
}
} // end of anonymous namespace
diff --git a/src/lib/mysql/mysql_constants.h b/src/lib/mysql/mysql_constants.h
index a0d79f97fc..443faa4490 100644
--- a/src/lib/mysql/mysql_constants.h
+++ b/src/lib/mysql/mysql_constants.h
@@ -52,7 +52,7 @@ const int MLM_MYSQL_FETCH_FAILURE = 0;
/// @name Current database schema version values.
//@{
-const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 22;
+const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 23;
const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 0;
//@}
diff --git a/src/lib/pgsql/pgsql_connection.h b/src/lib/pgsql/pgsql_connection.h
index e61d0763c6..e9f9fb2be3 100644
--- a/src/lib/pgsql/pgsql_connection.h
+++ b/src/lib/pgsql/pgsql_connection.h
@@ -18,7 +18,7 @@ namespace isc {
namespace db {
/// @brief Define the PostgreSQL backend version.
-const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 22;
+const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 23;
const uint32_t PGSQL_SCHEMA_VERSION_MINOR = 0;
// Maximum number of parameters that can be used a statement
diff --git a/src/share/database/scripts/mysql/.gitignore b/src/share/database/scripts/mysql/.gitignore
index 6d84ac028e..653d970974 100644
--- a/src/share/database/scripts/mysql/.gitignore
+++ b/src/share/database/scripts/mysql/.gitignore
@@ -30,4 +30,5 @@
/upgrade_019_to_020.sh
/upgrade_020_to_021.sh
/upgrade_021_to_022.sh
+/upgrade_022_to_023.sh
/wipe_data.sh
diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql
index abcb6ce1ee..c3c51ecb02 100644
--- a/src/share/database/scripts/mysql/dhcpdb_create.mysql
+++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql
@@ -5907,6 +5907,26 @@ UPDATE schema_version
-- This line concludes the schema upgrade to version 22.0.
+-- This line starts the schema upgrade to version 22.0.
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '22', minor = '0';
+
+-- This line concludes the schema upgrade to version 22.0.
+
+-- This line starts the schema upgrade to version 23.0.
+
+-- Introduce new lease state indicating that the lease has been
+-- released by a client.
+INSERT INTO lease_state VALUES (3, 'released');
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '23', minor = '0';
+
+-- This line concludes the schema upgrade to version 23.0.
+
# Notes:
#
# Indexes
diff --git a/src/share/database/scripts/mysql/upgrade_022_to_023.sh.in b/src/share/database/scripts/mysql/upgrade_022_to_023.sh.in
new file mode 100644
index 0000000000..3a4b7abed5
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_022_to_023.sh.in
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+# Copyright (C) 2024 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/.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities based on location of this script. Check for sources first,
+# so that the unexpected situations with weird paths fall on the default
+# case of installed.
+script_path=$(cd "$(dirname "${0}")" && pwd)
+if test "${script_path}" = "@abs_top_builddir@/src/share/database/scripts/mysql"; then
+ # shellcheck source=./src/bin/admin/admin-utils.sh.in
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+else
+ # shellcheck source=./src/bin/admin/admin-utils.sh.in
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+fi
+
+# Check version.
+version=$(mysql_version "${@}")
+if test "${version}" != "22.0"; then
+ printf 'This script upgrades 22.0 to 23.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${version}"
+ exit 0
+fi
+
+# Get the schema name from database argument. We need this to
+# query information_schema for the right database.
+for arg in "${@}"
+do
+ if ! printf '%s' "${arg}" | grep -Eq -- '^--'
+ then
+ schema="$arg"
+ break
+ fi
+done
+
+# Make sure we have the schema.
+if [ -z "$schema" ]
+then
+ printf "Could not find database schema name in cmd line args: %s\n" "${*}"
+ exit 255
+fi
+
+mysql "$@" <<EOF
+
+-- This line starts the schema upgrade to version 23.0.
+
+-- Introduce new lease state indicating that the lease has been
+-- released by a client.
+INSERT INTO lease_state VALUES (3, 'released');
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '23', minor = '0';
+
+-- This line concludes the schema upgrade to version 23.0.
+EOF
diff --git a/src/share/database/scripts/pgsql/.gitignore b/src/share/database/scripts/pgsql/.gitignore
index 5a8ec39377..b039f2d937 100644
--- a/src/share/database/scripts/pgsql/.gitignore
+++ b/src/share/database/scripts/pgsql/.gitignore
@@ -25,4 +25,5 @@
/upgrade_019_to_020.sh
/upgrade_020_to_021.sh
/upgrade_021_to_022.sh
+/upgrade_022_to_023.sh
/wipe_data.sh
diff --git a/src/share/database/scripts/pgsql/dhcpdb_create.pgsql b/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
index 79fee4f5a5..8459ffff4c 100644
--- a/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
+++ b/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
@@ -6377,6 +6377,19 @@ UPDATE schema_version
-- This line concludes the schema upgrade to version 22.0.
+-- This line starts the schema upgrade to version 23.0.
+
+-- Introduce new lease state indicating that the lease has been
+-- released by a client.
+INSERT INTO lease_state VALUES (3, 'released');
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '23', minor = '0';
+
+-- This line concludes the schema upgrade to version 23.0.
+
+
-- Commit the script transaction.
COMMIT;
diff --git a/src/share/database/scripts/pgsql/upgrade_022_to_023.sh.in b/src/share/database/scripts/pgsql/upgrade_022_to_023.sh.in
new file mode 100644
index 0000000000..13d6d5a8f9
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_022_to_023.sh.in
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# Copyright (C) 2024 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/.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities based on location of this script. Check for sources first,
+# so that the unexpected situations with weird paths fall on the default
+# case of installed.
+script_path=$(cd "$(dirname "${0}")" && pwd)
+if test "${script_path}" = "@abs_top_builddir@/src/share/database/scripts/pgsql"; then
+ # shellcheck source=./src/bin/admin/admin-utils.sh.in
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+else
+ # shellcheck source=./src/bin/admin/admin-utils.sh.in
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "22.0" ]; then
+ printf 'This script upgrades 22.0 to 23.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+START TRANSACTION;
+
+-- This line starts the schema upgrade to version 23.0.
+
+-- Introduce new lease state indicating that the lease has been
+-- released by a client.
+INSERT INTO lease_state VALUES (3, 'released');
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '23', minor = '0';
+
+-- This line concludes the schema upgrade to version 23.0.
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF