]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3246] Do not delete soft released leases
authorMarcin Siodelski <marcin@isc.org>
Thu, 18 Apr 2024 13:25:48 +0000 (15:25 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 19 Jun 2024 10:34:18 +0000 (12:34 +0200)
29 files changed:
ChangeLog
configure.ac
src/bin/admin/tests/mysql_tests.sh.in
src/bin/admin/tests/pgsql_tests.sh.in
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/tests/release_unittest.cc
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/tests/dhcp6_test_utils.cc
src/hooks/dhcp/high_availability/command_creator.cc
src/hooks/dhcp/high_availability/ha_service.cc
src/hooks/dhcp/high_availability/tests/command_creator_unittest.cc
src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc
src/hooks/dhcp/lease_cmds/lease_parser.cc
src/hooks/dhcp/lease_cmds/tests/lease_cmds4_unittest.cc
src/hooks/dhcp/lease_cmds/tests/lease_cmds6_unittest.cc
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/lease.cc
src/lib/dhcpsrv/lease.h
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
src/lib/dhcpsrv/tests/lease_unittest.cc
src/lib/mysql/mysql_constants.h
src/lib/pgsql/pgsql_connection.h
src/share/database/scripts/mysql/.gitignore
src/share/database/scripts/mysql/dhcpdb_create.mysql
src/share/database/scripts/mysql/upgrade_022_to_023.sh.in [new file with mode: 0644]
src/share/database/scripts/pgsql/.gitignore
src/share/database/scripts/pgsql/dhcpdb_create.pgsql
src/share/database/scripts/pgsql/upgrade_022_to_023.sh.in [new file with mode: 0644]

index 2f6788a705fd1e7aed052b3032a1ccc8e03e1e1b..422427cf8de8848b4a752f384ac8bebebb321db5 100644 (file)
--- 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.
index 071c665963ec02e8c47e2931f8f9066845338264..b39fb75e1754ed1bca8ddd4d7e30f26130eed1c5 100644 (file)
@@ -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])
index 3bf7b513de338a81571d3dcef0badbb8c94ccad0..b0b555b3a45e33e45386bad447e800f0a984fa64 100644 (file)
@@ -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
 
index 893fd13e90c9d3226f9118b4995f945f19345687..7f8a3406b21756d6c44f0fe5cef978342bf65cf1 100644 (file)
@@ -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
 
index 664a0ecdbc2bcdeae5b3dcaf2db3dc14333b6a79..3b3c9de81eac9594b315bbf8e8d47acdd5398689 100644 (file)
@@ -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)
index 62952161ea8bd0aeaf82cffab7acf46f3f4dc168..9b784172e4a5bfe6c85423cff3aa8a0da4f7016b 100644 (file)
@@ -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
index 6ac3b6aa4de0650b42be708db955212b128b55c8..aca7ac1f16c0fed573e8416e0e3c7b5bba23c281 100644 (file)
@@ -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));
             }
         }
     }
index 8d3baded461971f2304e8e098cda114a159cecac..49d4d25680bbd8a86ca0b3c9dd30ac4ffedd87bc 100644 (file)
@@ -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);
     }
 }
 
index b5d079897c540feb5e9d00fe1882ebf2f2fcbabe..49710660281281c8e1b173d213d840fbfc8062aa 100644 (file)
@@ -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);
index ebd3e531fe485e44e51125ed0f277389290fea15..7847ef718ec8163b4097bd5bd6409f3c8200d1cb 100644 (file)
@@ -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.
index ee1fca1a0a0401377f03a0055c89a6140f0ece81..ac08966911ddf336bbffc807b6efc09832f4ee6b 100644 (file)
@@ -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
index 5688a95848f806bd6fd2282ccf9c5f96ca9f1786..610efb7ed06417a6f76390ff140b99d6bb2581fe 100644 (file)
@@ -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
index 585c079edbb4fff261e14f21ee0aa1d21e55c459..a531be1adbc87f63a3f7549457dbb469a1e81872 100644 (file)
@@ -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)) {
index 9416e34d45e1a02dcacb04c156d7b0531320cbc7..783d5ce52e22c708d85a539bcff1d3c3d8ff5343 100644 (file)
@@ -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();
 }
index 65cdda2275205553e0465c8acaab635db75d5dff..8bdda0b78b37afaf223ecb18827eca9cc52e6707 100644 (file)
@@ -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();
 }
index 6019012b1f911532735faaeed9ee60c27ec70930..5e84357079e46663547f6fe74cff15f88db4b40b 100644 (file)
@@ -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_,
index 2ea916794db04926a1fa169de3271ae9ee9e6e3a..373bd03504e5d662de7a1e4633929dab532783b8 100644 (file)
@@ -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
         ;
index f84302d276ed3d0ece06bf33e3ec2e064af5417d..8e4d297d765c734dcad79bd552d2f1d6e8fc8f21 100644 (file)
@@ -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.
index 8a4286ad590816598bc87d1d1e5e518f7c99dad8..906c3a9952b03e32ec7c1e49670201256760330b 100644 (file)
@@ -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.
index 6f3e88cc2e52441ff7b40b5f15f52a1993171d16..1e6469658e7ab0089174162b31aa5c7519ab9d5d 100644 (file)
@@ -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
index e50db582bbae8941ed086dda34493ededc306d08..8d3b1b6b56f5da8c416acd026ca7efd3464a129f 100644 (file)
@@ -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
index a0d79f97fc4797c900f51323b934c5995303107a..443faa4490a53759b74042ee4d8e28c0843ded31 100644 (file)
@@ -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;
 
 //@}
index e61d0763c6e1ec8f44ccf1018d61d73d3c5c3e12..e9f9fb2be3a5697e170c78b53fcd472e60fa48f5 100644 (file)
@@ -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
index 6d84ac028e393dd8c5bc96bdc13d5c8622888186..653d970974b8d69bf6924afef9211f2acf04db92 100644 (file)
@@ -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
index abcb6ce1eeba79c186724e76adfc4f8fdc86f3c1..c3c51ecb02d8712d8b8a4ae68e82116a1f833a29 100644 (file)
@@ -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 (file)
index 0000000..3a4b7ab
--- /dev/null
@@ -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
index 5a8ec39377a289c6aa34d844d14276c48c23983b..b039f2d93787c804d96ef5773573c3b6f3206248 100644 (file)
@@ -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
index 79fee4f5a50d7a1b3ebf192306545f9684c62f91..8459ffff4c90742a8fa4ac45e0136017d313a7db 100644 (file)
@@ -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 (file)
index 0000000..13d6d5a
--- /dev/null
@@ -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