diff options
author | Marcin Siodelski <marcin@isc.org> | 2024-04-18 15:25:48 +0200 |
---|---|---|
committer | Marcin Siodelski <marcin@isc.org> | 2024-06-19 12:34:18 +0200 |
commit | a52bf68db907f9dd9f7bc829bb711637909912b0 (patch) | |
tree | 96c0a561b3fa3412d56723ef7ba20f470263bcb1 | |
parent | [#3436] fixed compilation (diff) | |
download | kea-a52bf68db907f9dd9f7bc829bb711637909912b0.tar.xz kea-a52bf68db907f9dd9f7bc829bb711637909912b0.zip |
[#3246] Do not delete soft released leases
29 files changed, 1045 insertions, 108 deletions
@@ -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 |