]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4491] Fix SFLQ create functions
authorThomas Markwalder <tmark@isc.org>
Sat, 16 May 2026 17:28:27 +0000 (13:28 -0400)
committerThomas Markwalder <tmark@isc.org>
Fri, 15 May 2026 17:09:01 +0000 (17:09 +0000)
new file:   src/share/database/scripts/mysql/upgrade_034_to_035.sh.in
new file:   src/share/database/scripts/pgsql/upgrade_033_to_034.sh.in

/src/bin/admin/tests/mysql_tests.sh.in
    mysql_upgrade_34_to_35_test()
    - new test

/src/bin/admin/tests/pgsql_tests.sh.in
    pgsql_upgrade_33_to_34_test()
    - new test

/src/hooks/dhcp/mysql/mysql_lease_mgr.cc
    MySqlLeaseMgr::sflqCreateFlqPool4()
    MySqlLeaseMgr::sflqCreateFlqPool6()
    - wrap create call in transaction

/src/hooks/dhcp/pgsql/pgsql_lease_mgr.cc
    PgSqlLeaseMgr::sflqCreateFlqPool4()
    PgSqlLeaseMgr::sflqCreateFlqPool6()
    - wrap create call in transaction

/src/lib/dhcpsrv/testutils/generic_lease_mgr_unittest.cc
    GenericLeaseMgrTest::sflqCreateFlqPool4Concurrent()
    GenericLeaseMgrTest::sflqCreateFlqPool6Concurrent()
    - add more threads and test recreate

/src/lib/mysql/mysql_constants.h
/src/lib/pgsql/pgsql_connection.h
    Update schema version to 35

/src/share/database/scripts/mysql/dhcpdb_create.mysql
    sflqCreateFlqPool4()
    - remove transaction statements.

    sflqCreateFlqPool6()
    - remove transaction statements.
    - add recreate on delegated_len change logic
    - update flq_pool6 values on recreate

/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
    sflqCreateFlqPool6()
    - add recreate on delegated_len change logic
    - update flq_pool6 values on recreate
    - fix end address overrun

13 files changed:
src/bin/admin/tests/mysql_tests.sh.in
src/bin/admin/tests/pgsql_tests.sh.in
src/hooks/dhcp/mysql/mysql_lease_mgr.cc
src/hooks/dhcp/pgsql/pgsql_lease_mgr.cc
src/lib/dhcpsrv/testutils/generic_lease_mgr_unittest.cc
src/lib/mysql/mysql_constants.h
src/lib/pgsql/pgsql_connection.h
src/share/database/scripts/mysql/dhcpdb_create.mysql
src/share/database/scripts/mysql/meson.build
src/share/database/scripts/mysql/upgrade_034_to_035.sh.in [new file with mode: 0755]
src/share/database/scripts/pgsql/dhcpdb_create.pgsql
src/share/database/scripts/pgsql/meson.build
src/share/database/scripts/pgsql/upgrade_033_to_034.sh.in [new file with mode: 0755]

index 2938d43d3d226b53131c17040e731d9eabe52472..5c924837d2b24a4c0290375b9f89a8c8eddcfff9 100755 (executable)
@@ -165,7 +165,7 @@ mysql_db_version_test() {
     run_command \
         "${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}"
     version="${OUTPUT}"
-    assert_str_eq "34.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+    assert_str_eq "35.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
 
     # Let's wipe the whole database
     mysql_wipe
@@ -1195,6 +1195,59 @@ mysql_upgrade_33_to_34_test() {
     assert_str_eq '3001::1' "${OUTPUT}"
 }
 
+mysql_upgrade_34_to_35_test() {
+    qry="DELETE from lease6; DELETE from flq_pool6; DELETE from free_lease6"
+    run_statement "Pre-test deletes" "${qry}"
+
+    # Steps 1 - 3 Verifies updated recreate on delegated_len change logic.
+
+    # Step 1 - create and verify an NA pool.
+    qry="CALL sflqCreateFlqPool6('3001::', '3001::ffff', 0, 128, 1, 0)"
+    run_statement "Step 1.1" "${qry}"
+
+    qry="SELECT count(*) from flq_pool6 where start_address = '3001::' AND delegated_len = 128"
+    run_statement "Step 1.2" "${qry}" "1"
+
+    qry="SELECT count(*) from free_lease6"
+    run_statement "Step 1.3" "${qry}" "65536"
+
+    # Step 2 - set recreate to true and call create again.
+    qry="DELETE from free_lease6"
+    run_statement "Step 2.1" "${qry}"
+
+    qry="CALL sflqCreateFlqPool6('3001::', '3001::ffff', 0, 128, 1, 1)"
+    run_statement "Step 2.2." "${qry}" 
+
+    qry="SELECT count(*) from free_lease6"
+    run_statement "Step 2.3" "${qry}" "65536"
+
+    # Step 3 - call create with different delegated_len.
+    qry="CALL sflqCreateFlqPool6('3001::', '3001::ffff', 0, 127, 1, 0)"
+    run_statement "Step 3.1" "${qry}"
+
+    qry="SELECT count(*) from free_lease6"
+    run_statement "Step 3.2" "${qry}" '32768'
+
+    # Step 4 - Verifies that MySQL does not have the one too many prefixes issue.
+    qry="DELETE from flq_pool6;DELETE from free_lease6"
+    run_statement "Step 4.1" "${qry}"
+
+    qry="CALL sflqCreateFlqPool6('2001::fff0', '2001::ffff', 2, 128, 1, 0)"
+    run_statement "Step 4.2" "${qry}"
+
+    # Verify free_lease6 has only the expected addresses.
+    sql="SELECT count(bin_address) from free_lease6 \
+            where bin_address >= inet6_aton('2001::fff0') \
+            and bin_address <= inet6_aton('2001::ffff')"
+    run_statement "Step 4.3" "$sql" "16"
+
+    sql="SELECT count(bin_address) from free_lease6"
+    run_statement "Step 4.4 failed" "$sql" "16"
+
+    qry="DELETE from lease6; DELETE from flq_pool6; DELETE from free_lease6"
+    run_statement "Post-test deletes" "${qry}"
+}
+
 mysql_upgrade_test() {
 
     test_start "mysql.upgrade"
@@ -1216,7 +1269,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 "34.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+    assert_str_eq "35.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
 
     # Let's check that the new tables are indeed there.
 
@@ -1914,6 +1967,9 @@ SET @disable_audit = 0"
     # Check upgrade from 33.0 to 34.0.
     mysql_upgrade_33_to_34_test
 
+    # Check upgrade from 34.0 to 35.0.
+    mysql_upgrade_34_to_35_test
+
     # Let's wipe the whole database
     mysql_wipe
 
index 97efbf7aae36292254a4fe7d4391f252707942f1..d317742b7aaa40c6537e4abe5ad3d3cf7d4f60ee 100755 (executable)
@@ -166,7 +166,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 "33.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
+    assert_str_eq "34.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
 
     # Let's wipe the whole database
     pgsql_wipe
@@ -1186,6 +1186,63 @@ pgsql_upgrade_32_to_33_test() {
     assert_str_eq '3001::' "${OUTPUT}"
 }
 
+pgsql_upgrade_33_to_34_test() {
+    qry="DELETE from lease6; DELETE from flq_pool6; DELETE from free_lease6"
+    run_statement "Pre-test deletes" "${qry}"
+
+    # Steps 1 - 3 Verifies updated recreate on delegated_len change logic.
+
+    # Step 1 - create and verify an NA pool.
+    qry="SELECT sflqCreateFlqPool6('3001::'::inet, '3001::ffff'::inet,\
+                                   0::smallint, 128::smallint, 1::bigint, 'f')"
+    run_statement "Step 1.1" "${qry}"
+
+    qry="SELECT count(*) from flq_pool6 where start_address = '3001::' AND delegated_len = 128"
+    run_statement "Step 1.2" "${qry}" "1"
+
+    qry="SELECT count(*) from free_lease6"
+    run_statement "Step 1.3" "${qry}" "65536"
+
+    # Step 2 - set recreate to true and call create again.
+    qry="DELETE from free_lease6"
+    run_statement "Step 2.1" "${qry}"
+
+    qry="SELECT sflqCreateFlqPool6('3001::'::inet, '3001::ffff'::inet,\
+                                   0::smallint, 128::smallint, 1::bigint, 't')"
+    run_statement "Step 2.2." "${qry}" 
+
+    qry="SELECT count(*) from free_lease6"
+    run_statement "Step 2.3" "${qry}" "65536"
+
+    # Step 3 - call create with different delegated_len.
+    qry="SELECT sflqCreateFlqPool6('3001::'::inet, '3001::ffff'::inet,\
+                                   0::smallint, 127::smallint, 1::bigint, 'f')"
+    run_statement "Step 3.1" "${qry}"
+
+    qry="SELECT count(*) from free_lease6"
+    run_statement "Step 3.2" "${qry}" '32768'
+
+    # Step 4 - Verifies that MySQL does not have the one too many prefixes issue.
+    qry="DELETE from flq_pool6;DELETE from free_lease6"
+    run_statement "Step 4.1" "${qry}"
+
+    qry="SELECT sflqCreateFlqPool6('2001::fff0'::inet, '2001::ffff'::inet,\
+                                   2::smallint, 128::smallint, 1::bigint, 'f')"
+    run_statement "Step 4.2" "${qry}"
+
+    # Verify free_lease6 has only the expected addresses.
+    sql="SELECT count(bin_address) from free_lease6 \
+            where bin_address >= inetToBytea('2001::fff0'::inet) \
+            and bin_address <= inetToBytea('2001::ffff'::inet)"
+    run_statement "Step 4.3" "$sql" "16"
+
+    sql="SELECT count(bin_address) from free_lease6"
+    run_statement "Step 4.4 failed" "$sql" "16"
+
+    qry="DELETE from lease6; DELETE from flq_pool6; DELETE from free_lease6"
+    run_statement "Postrtest deletes" "${qry}"
+}
+
 pgsql_upgrade_test() {
     test_start "pgsql.upgrade"
 
@@ -1204,7 +1261,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 "33.0" "${version}" 'Expected kea-admin to return %s, returned value was %s'
+    assert_str_eq "34.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
@@ -1296,6 +1353,9 @@ pgsql_upgrade_test() {
     # Check 32 to 33 upgrade
     pgsql_upgrade_32_to_33_test
 
+    # Check 33 to 34 upgrade
+    pgsql_upgrade_33_to_34_test
+
     # Let's wipe the whole database
     pgsql_wipe
 
index fb919b1c3d16ec3aae78e607ac26976e6a28d85a..b8e2e4608d1fb79d7fa5318e22b8df8c68048cad 100644 (file)
@@ -5174,11 +5174,13 @@ MySqlLeaseMgr::sflqCreateFlqPool4(IOAddress start_address, IOAddress end_address
     int status = mysql_stmt_bind_param(ctx->conn_.getStatement(stindex), &ibind_vec[0]);
     checkError(ctx, status, stindex, "unable to bind parameters");
 
-    // Execute
+    // Execute the create inside a transaction.
+    ScopedMySqlTransactionPtr trans(new MySqlTransaction(ctx->conn_));
     status = MysqlExecuteStatement(ctx->conn_.getStatement(stindex));
     if (status != 0) {
         // Failure: check for the special case of duplicate entry.  If this is
-        // the case, we ignore it.
+        // the case, we don't consider it an errror but still need to rollback
+        // to relinquish lock.
         if (mysql_errno(ctx->conn_.mysql_) == ER_DUP_ENTRY) {
             return (false);
         }
@@ -5186,6 +5188,9 @@ MySqlLeaseMgr::sflqCreateFlqPool4(IOAddress start_address, IOAddress end_address
         checkError(ctx, status, stindex, "unable to execute");
     }
 
+    // Commit any changes.
+    trans->commit();
+
     return (true);
 }
 
@@ -5301,11 +5306,13 @@ MySqlLeaseMgr::sflqCreateFlqPool6(IOAddress start_address, IOAddress end_address
     int status = mysql_stmt_bind_param(ctx->conn_.getStatement(stindex), &ibind_vec[0]);
     checkError(ctx, status, stindex, "unable to bind parameters");
 
-    // Execute
+    // Execute the create inside a transaction.
+    ScopedMySqlTransactionPtr trans(new MySqlTransaction(ctx->conn_));
     status = MysqlExecuteStatement(ctx->conn_.getStatement(stindex));
     if (status != 0) {
         // Failure: check for the special case of duplicate entry.  If this is
-        // the case, we ignore it.
+        // the case, we don't consider it an errror but still need to rollback
+        // to relinquish lock.
         if (mysql_errno(ctx->conn_.mysql_) == ER_DUP_ENTRY) {
             return (false);
         }
@@ -5313,6 +5320,9 @@ MySqlLeaseMgr::sflqCreateFlqPool6(IOAddress start_address, IOAddress end_address
         checkError(ctx, status, stindex, "unable to execute");
     }
 
+    // Commit any changes.
+    trans->commit();
+
     return (true);
 }
 
index 24fddb4e9ecc81f8bf948923276fe9c0d8d427e7..76db8cb4ccf31dbe4f015a44c7f34f8eff2609b5 100644 (file)
@@ -4171,6 +4171,7 @@ PgSqlLeaseMgr::sflqCreateFlqPool4(IOAddress start_address, IOAddress end_address
     ibind.add(recreate);
 
     // Execute the select.
+    ScopedPgSqlTransactionPtr trans(new PgSqlTransaction(ctx->conn_));
     PgSqlResult r(PQexecPrepared(ctx->conn_,
                                  tagged_statements[stindex].name,
                                  tagged_statements[stindex].nbparams,
@@ -4188,6 +4189,8 @@ PgSqlLeaseMgr::sflqCreateFlqPool4(IOAddress start_address, IOAddress end_address
         ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
     }
 
+    trans->commit();
+
     return (true);
 }
 
@@ -4278,6 +4281,7 @@ PgSqlLeaseMgr::sflqCreateFlqPool6(IOAddress start_address, IOAddress end_address
     ibind.add(subnet_id);
     ibind.add(recreate);
 
+    ScopedPgSqlTransactionPtr trans(new PgSqlTransaction(ctx->conn_));
     // Execute the select.
     PgSqlResult r(PQexecPrepared(ctx->conn_,
                                  tagged_statements[stindex].name,
@@ -4296,6 +4300,7 @@ PgSqlLeaseMgr::sflqCreateFlqPool6(IOAddress start_address, IOAddress end_address
         ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
     }
 
+    trans->commit();
     return (true);
 }
 
index 981be7871e85a88543e7de820db6f851123beec8..1eb26fbc9ad496fca36f1fb44d8c720d0ee7cba2 100644 (file)
@@ -6324,7 +6324,7 @@ GenericLeaseMgrTest::testSflqAPIFuncs6(Lease::Type lease_type) {
                                        test_pool->subnet_id_, false));
     }
 
-    // Fetching all pools should find all three. 
+    // Fetching all pools should find all three.
     ASSERT_NO_THROW_LOG(pool_infos = lmptr_->sflqPool6GetAll());
     ASSERT_TRUE(pool_infos);
     ASSERT_EQ(3, pool_infos->size());
@@ -6591,75 +6591,195 @@ GenericLeaseMgrTest::testSflqAPIOverlappingPools6(Lease::Type lease_type) {
     checkPoolInfos(*(*pool_infos)[1], *test_pools[2], __LINE__);
 }
 
+typedef boost::shared_ptr<thread> ThreadPtr;
+
 void
 GenericLeaseMgrTest::sflqCreateFlqPool4Concurrent() {
     // Enable Multi-Threading.
     isc::test::MultiThreadingTest mt(true);
 
-    // Create the same pool in different threads.
-    bool ret1 = false;
-    bool ret2 = false;
     IOAddress start_address("192.0.0.0");
     IOAddress end_address("192.0.255.255");
-    thread th1([this, &ret1, start_address, end_address]() {
-            ASSERT_NO_THROW_LOG(ret1 =
-                lmptr_->sflqCreateFlqPool4(start_address, end_address, false));
-    });
 
-    thread th2([this, &ret2, start_address, end_address]() {
-            ASSERT_NO_THROW_LOG(ret2 =
-                lmptr_->sflqCreateFlqPool4(start_address, end_address, false));
-    });
+    // First we'll do concurrent creates with recreate = false;
+    auto num_threads = 5;
+    std::vector<bool> rets(num_threads);
+    std::list<ThreadPtr> threads;
+    for (int i = 0; i < num_threads; ++i) {
+        rets[i] = false;
+        ThreadPtr th(new thread([this, &rets, i, start_address, end_address]() {
+            ASSERT_NO_THROW_LOG(rets[i] =
+                lmptr_->sflqCreateFlqPool4(start_address, end_address, 1, false));
+        }));
+
+        threads.push_back(th);
+    }
+
+    for (auto th : threads) {
+        th->join();
+    }
 
-    th1.join();
-    th2.join();
+    // Count the true returns.
+    int true_cnt = 0;
+    for (auto ret : rets) {
+        if (ret) {
+            ++true_cnt;
+        }
+    }
 
-    // One thread should create the pool, the other should not.
-    ASSERT_NE(ret1, ret2);
+    // Only 1 should be true (i.e. did the create).
+    ASSERT_EQ(1, true_cnt);
 
-    // Verify the pool and free leases were created.
+    // Verify the pool and free leases are correct.
     SflqPoolInfoCollectionPtr pool_infos;
     ASSERT_NO_THROW_LOG(pool_infos = lmptr_->sflqPool4Get(start_address, end_address));
     ASSERT_TRUE(pool_infos);
     ASSERT_EQ(1, pool_infos->size());
     ASSERT_EQ(65536, (*pool_infos)[0]->free_leases_);
+
+    // Now let's do concurrent recreates.
+    threads.clear();
+    for (int i = 0; i < num_threads; ++i) {
+        rets[i] = false;
+        ThreadPtr th(new thread([this, &rets, i, start_address, end_address]() {
+            ASSERT_NO_THROW_LOG(rets[i] =
+                lmptr_->sflqCreateFlqPool4(start_address, end_address, 1, true));
+            EXPECT_TRUE(rets[i]) << "not true pass: " << i;
+        }));
+
+        threads.push_back(th);
+    }
+
+    for (auto th : threads) {
+        th->join();
+    }
+
+    true_cnt = 0;
+    for (auto ret : rets) {
+        if (ret) {
+            ++true_cnt;
+        }
+    }
+
+    // All should report true.
+    ASSERT_EQ(num_threads, true_cnt);
+
+    // Verify the pool and free leases are correct.
+    ASSERT_NO_THROW_LOG(pool_infos = lmptr_->sflqPool4Get(start_address, end_address));
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(1, pool_infos->size());
+    ASSERT_EQ(65536, (*pool_infos)[0]->free_leases_);
 }
 
+
 void
 GenericLeaseMgrTest::sflqCreateFlqPool6Concurrent() {
     // Enable Multi-Threading.
     isc::test::MultiThreadingTest mt(true);
 
     // Create the same pool in different threads.
-    bool ret1 = false;
-    bool ret2 = false;
     IOAddress start_address("3001::");
     IOAddress end_address("3001::FFFF");
-    thread th1([this, &ret1, start_address, end_address]() {
-            ASSERT_NO_THROW_LOG(ret1 =
-                lmptr_->sflqCreateFlqPool6(start_address, end_address,
-                                           Lease::TYPE_NA, 128, 1, false));
-    });
 
-    thread th2([this, &ret2, start_address, end_address]() {
-            ASSERT_NO_THROW_LOG(ret2 =
+    // First we'll do concurrent creates with recreate = false;
+    auto num_threads = 5;
+    std::vector<bool> rets(num_threads);
+    std::list<ThreadPtr> threads;
+    for (int i = 0; i < num_threads; ++i) {
+        rets[i] = false;
+        ThreadPtr th(new thread([this, &rets, i, start_address, end_address]() {
+            ASSERT_NO_THROW_LOG(rets[i] =
                 lmptr_->sflqCreateFlqPool6(start_address, end_address,
-                                           Lease::TYPE_NA, 128, 1, false));
-    });
+                                           Lease::TYPE_PD, 128, 1, false));
+        }));
 
-    th1.join();
-    th2.join();
+        threads.push_back(th);
+    }
 
-    // One thread should create the pool, the other should not.
-    ASSERT_NE(ret1, ret2);
+    for (auto th : threads) {
+        th->join();
+    }
 
+    // Count the true returns.
+    int true_cnt = 0;
+    for (auto ret : rets) {
+        if (ret) {
+            ++true_cnt;
+        }
+    }
 
-    // Verify the pool and free leases were created.
+    // Only 1 should be true (i.e. did the create).
+    ASSERT_EQ(1, true_cnt);
+
+    // Verify the pool and free leases are correct.
     SflqPoolInfoCollectionPtr pool_infos;
     ASSERT_NO_THROW_LOG(pool_infos = lmptr_->sflqPool6Get(start_address, end_address));
     ASSERT_TRUE(pool_infos);
     ASSERT_EQ(1, pool_infos->size());
     ASSERT_EQ(65536, (*pool_infos)[0]->free_leases_);
+    ASSERT_EQ(Lease::TYPE_PD, (*pool_infos)[0]->lease_type_);
+
+    // Now let's do concurrent recreates.
+    threads.clear();
+    for (int i = 0; i < num_threads; ++i) {
+        rets[i] = false;
+        ThreadPtr th(new thread([this, &rets, i, start_address, end_address]() {
+            ASSERT_NO_THROW_LOG(rets[i] =
+                lmptr_->sflqCreateFlqPool6(start_address, end_address,
+                                           Lease::TYPE_PD, 128, 1, true));
+        }));
+
+        threads.push_back(th);
+    }
+
+    for (auto th : threads) {
+        th->join();
+    }
+
+    true_cnt = 0;
+    for (auto ret : rets) {
+        if (ret) {
+            ++true_cnt;
+        }
+    }
+
+    // All should report true.
+    ASSERT_EQ(num_threads, true_cnt);
+
+    // Verify the pool and free leases are correct.
+    ASSERT_NO_THROW_LOG(pool_infos = lmptr_->sflqPool6Get(start_address, end_address));
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(1, pool_infos->size());
+    ASSERT_EQ(65536, (*pool_infos)[0]->free_leases_);
+    ASSERT_EQ(Lease::TYPE_PD, (*pool_infos)[0]->lease_type_);
+
+    // Now let's try creates with a different delegated_len.
+    // This should make them act like recreates.
+    threads.clear();
+    for (int i = 0; i < num_threads; ++i) {
+        rets[i] = false;
+        ThreadPtr th(new thread([this, &rets, i, start_address, end_address]() {
+            ASSERT_NO_THROW_LOG(rets[i] =
+                lmptr_->sflqCreateFlqPool6(start_address, end_address,
+                                           Lease::TYPE_PD, 127, 1, false));
+        }));
+
+        threads.push_back(th);
+    }
+
+    for (auto th : threads) {
+        th->join();
+    }
+
+    true_cnt = 0;
+    for (auto ret : rets) {
+        if (ret) {
+            ++true_cnt;
+        }
+    }
+
+    // All should report true.
+    ASSERT_EQ(num_threads, true_cnt);
 }
 
 }  // namespace test
index 4993f9efb6927f6f778d84e6e59da1bc06cfedb7..4739ee4913a714aa7b99178e429c70dfb65d511e 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 = 34;
+const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 35;
 const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 0;
 
 //@}
index 5f5d70bbeb601bd0fd0798ca2db8aeff7dac7872..6fda05ba7a83da36b90aed4055e3b5b9f0dcc171 100644 (file)
@@ -18,7 +18,7 @@ namespace isc {
 namespace db {
 
 /// @brief Define the PostgreSQL backend version.
-const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 33;
+const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 34;
 const uint32_t PGSQL_SCHEMA_VERSION_MINOR = 0;
 
 // Maximum number of parameters that can be used a statement
index 1bd51ea67ea27473f1af10160a3ef20795e979e7..7b18b506817c76ac1c676bd99ef811aa77986d50 100644 (file)
@@ -7339,7 +7339,211 @@ DELIMITER ;
 UPDATE schema_version
     SET version = '34', minor = '0';
 
--- This line concludes the schema upgrade to version 34.0.
+-- This line starts the schema upgrade to version 35.0.
+
+-- Remove transaction statements.
+-- Populate flq_pool4 and free_lease4 based on an address range.
+-- This must be called from within a transaction.
+DROP PROCEDURE IF EXISTS sflqCreateFlqPool4;
+DELIMITER $$
+CREATE PROCEDURE sflqCreateFlqPool4(IN p_start_address INT UNSIGNED,
+                                    IN p_end_address INT UNSIGNED,
+                                    IN p_subnet_id INT UNSIGNED,
+                                    IN p_recreate TINYINT)
+BEGIN
+    DECLARE loop_limit INT UNSIGNED;
+    DECLARE next_start_address INT UNSIGNED;
+    DECLARE next_end_address INT UNSIGNED;
+    DECLARE remaining INT UNSIGNED;
+    DECLARE error_msg VARCHAR(512);
+
+    IF (p_start_address = inet_aton('0.0.0.0') OR
+        p_start_address > p_end_address)
+    THEN
+        SET error_msg = CONCAT('Invalid range: ', inet_ntoa(p_start_address),
+                               ' - ', inet_ntoa(p_end_address));
+        SIGNAL SQLSTATE '45000'
+        SET MESSAGE_TEXT = error_msg;
+    END IF;
+
+    -- Create the flq_pool4 row. Concurrent attempts will hang until
+    -- this one commits and then they will fail with duplicate key
+    -- error. Callers should treat the duplicate error as success.
+    IF p_recreate = 1
+    THEN
+        -- (Re)creating should ignore duplicate on insert
+        INSERT INTO flq_pool4 (start_address, end_address, subnet_id)
+           VALUES (p_start_address, p_end_address, p_subnet_id)
+        ON DUPLICATE KEY UPDATE start_address = start_address;
+    ELSE
+        INSERT INTO flq_pool4 (start_address, end_address, subnet_id)
+           VALUES (p_start_address, p_end_address, p_subnet_id);
+    END IF;
+
+    -- Wipe out existing free addresses. This ensures we fix any mixed allocation entries.
+    DELETE FROM free_lease4 WHERE address >= p_start_address and address <= p_end_address;
+
+    -- Insert available leases into free_lease4 table.  If insert fails on
+    -- duplicate ignore it. MySql limits recursive queries so we iterate
+    -- pool address range in chunks.
+    CALL setLoopLimit();
+    SET loop_limit = @kea_recursion_limit - 1;
+    SET next_start_address = p_start_address;
+    SET next_end_address = p_start_address;
+    start_loop: WHILE next_start_address <= p_end_address DO
+        SET remaining = p_end_address - next_start_address;
+        IF (remaining > loop_limit)
+        THEN
+            SET next_end_address = next_end_address + loop_limit;
+        ELSE
+            SET next_end_address = next_end_address + remaining;
+        END IF;
+
+        INSERT INTO free_lease4 (address)
+            WITH RECURSIVE addresses AS (
+                SELECT next_start_address AS avail
+                UNION ALL
+                SELECT avail + 1 FROM addresses WHERE avail < next_end_address
+            )
+            SELECT avail FROM addresses
+                LEFT JOIN lease4 on avail = lease4.address
+                WHERE (lease4.address IS NULL OR lease4.state = 2)
+            ON DUPLICATE KEY UPDATE free_lease4.address = avail;
+
+        IF next_start_address + loop_limit <= 4294967295 THEN
+            SET next_start_address = next_start_address + loop_limit;
+        ELSE
+            LEAVE start_loop;
+        END IF;
+    END WHILE start_loop;
+
+    -- Update the modification time in the flq_pool row.
+    UPDATE flq_pool4 SET modification_ts = now()
+        WHERE (start_address = p_start_address AND end_address = p_end_address);
+END $$
+DELIMITER ;
+
+-- Populate flq_pool6 and free_lease6 based on an address
+-- range and delegated len. Use 128 for NA addresses.
+-- This must be called from within a transaction.
+DROP PROCEDURE IF EXISTS sflqCreateFlqPool6;
+DELIMITER $$
+CREATE PROCEDURE sflqCreateFlqPool6(IN p_start_address VARCHAR(45),
+                                    IN p_end_address VARCHAR(45),
+                                    IN p_lease_type TINYINT UNSIGNED,
+                                    IN p_delegated_len TINYINT UNSIGNED,
+                                    IN p_subnet_id INT UNSIGNED,
+                                    IN p_recreate TINYINT)
+BEGIN
+    DECLARE bin_next_address BINARY(16);
+    DECLARE bin_end_address BINARY(16);
+    DECLARE lease_address VARCHAR(45);
+    DECLARE error_msg VARCHAR(512);
+    DECLARE x_delegated_len TINYINT UNSIGNED;
+    DECLARE pool_changed TINYINT;
+
+    SET bin_next_address = INET6_ATON(p_start_address);
+    SET bin_end_address = INET6_ATON(p_end_address);
+
+    IF (p_start_address =  "::" OR
+        bin_next_address > bin_end_address)
+    THEN
+        SET error_msg = CONCAT('Invalid range: ', p_start_address,
+                               ' - ', p_end_address);
+        SIGNAL SQLSTATE '45000'
+        SET MESSAGE_TEXT = error_msg;
+    END IF;
+
+    IF (p_delegated_len < 1 OR p_delegated_len > 128)
+    THEN
+        SET error_msg = CONCAT('Invalid p_delegated_len: ', p_delegated_len);
+        SIGNAL SQLSTATE '45000'
+        SET MESSAGE_TEXT = error_msg;
+    END IF;
+
+    -- If we are not re-creating the pool, look for a pre-existing pool
+    -- and see if delegated length has changed. If so we need to treat
+    -- it as recreate. This should catch configuration changes to
+    -- pools pre-existing pools.
+    SET pool_changed = 0;
+    IF (p_recreate = 0)
+    THEN
+        SELECT count(*) INTO pool_changed
+            FROM flq_pool6
+            WHERE (start_address = p_start_address AND
+                   end_address = p_end_address AND
+                   delegated_len != p_delegated_len)
+            LIMIT 1;
+    END IF;
+
+    -- Create the flq_pool6 row. Concurrent attempts will hang until
+    -- this one commits and then they will fail with duplicate key
+    -- error. Callers should treat the duplicate error as success.
+    IF (p_recreate = 1 OR pool_changed = 1)
+    THEN
+        -- (Re)creating should ignore duplicate on insert
+        INSERT INTO flq_pool6
+            (start_address, end_address, lease_type, delegated_len, subnet_id)
+            VALUES
+            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id)
+            ON DUPLICATE KEY UPDATE
+                    -- Recreate may have changed these
+                    lease_type = p_lease_type,
+                    delegated_len = p_delegated_len,
+                    subnet_id = p_subnet_id;
+    ELSE
+        INSERT INTO flq_pool6
+            (start_address, end_address, lease_type, delegated_len, subnet_id)
+            VALUES
+            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id);
+    END IF;
+
+    -- Wipe out existing free addresses. This ensures we fix any mixed allocation entries.
+    DELETE FROM free_lease6 WHERE bin_address >= bin_next_address and bin_address <= bin_end_address;
+    -- Enumerate the addresses into a temp table to use in a bulk insert.
+    DROP TEMPORARY TABLE IF EXISTS _flq6_candidates;
+    CREATE TEMPORARY TABLE _flq6_candidates (
+        bin_address BINARY(16) NOT NULL PRIMARY KEY,
+        address VARCHAR(45) NOT NULL
+    ) ENGINE=InnoDB;
+
+    label: WHILE bin_next_address <= bin_end_address
+    DO
+        INSERT INTO _flq6_candidates (bin_address, address)
+            VALUES (bin_next_address, INET6_NTOA(bin_next_address));
+
+        IF (bin_next_address = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
+        THEN
+            LEAVE label;
+        END IF;
+
+        SET bin_next_address = incrementV6Prefix(bin_next_address, p_delegated_len);
+    END WHILE label;
+
+    -- Bulk insert available leases into free_lease6 table. If insert fails on
+    -- duplicate ignore it.
+    INSERT INTO free_lease6 (address, bin_address)
+        SELECT c.address, c.bin_address
+        FROM _flq6_candidates c
+        WHERE NOT EXISTS (
+            SELECT 1 FROM lease6 l
+            WHERE l.address = c.address AND l.state != 2
+        )
+        ON DUPLICATE KEY UPDATE free_lease6.address = c.address;
+
+    DROP TEMPORARY TABLE _flq6_candidates;
+
+    -- Update the modification time in the flq_pool row.
+    UPDATE flq_pool6 SET modification_ts = now()
+        WHERE (start_address = p_start_address AND end_address = p_end_address);
+END $$
+DELIMITER ;
+
+-- Update the schema version number.
+UPDATE schema_version
+    SET version = '35', minor = '0';
+
+-- This line concludes the schema upgrade to version 35.0.
 
 # Notes:
 #
index b5ceda59f7e62d89c60f5cdaab98c329ba727ac8..6b1f2cbdbcaef2e958d20af720ab3ec79378af7e 100644 (file)
@@ -75,6 +75,7 @@ upgrade_scripts = [
     'upgrade_031_to_032.sh',
     'upgrade_032_to_033.sh',
     'upgrade_033_to_034.sh',
+    'upgrade_034_to_035.sh',
 ]
 list = run_command(
     GRABBER,
diff --git a/src/share/database/scripts/mysql/upgrade_034_to_035.sh.in b/src/share/database/scripts/mysql/upgrade_034_to_035.sh.in
new file mode 100755 (executable)
index 0000000..adbe8d7
--- /dev/null
@@ -0,0 +1,263 @@
+#!/bin/sh
+
+# Copyright (C) 2025-2026 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 only major version to allow for intermediary backported schema changes.
+version=$(mysql_version "${@}" | cut -d '.' -f 1)
+if test "${version}" != '34'; then
+    printf 'This script upgrades 34.* to 35.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 35.0.
+
+-- Remove transaction statements.
+-- Populate flq_pool4 and free_lease4 based on an address range.
+-- This must be called from within a transaction.
+DROP PROCEDURE IF EXISTS sflqCreateFlqPool4;
+DELIMITER $$
+CREATE PROCEDURE sflqCreateFlqPool4(IN p_start_address INT UNSIGNED,
+                                    IN p_end_address INT UNSIGNED,
+                                    IN p_subnet_id INT UNSIGNED,
+                                    IN p_recreate TINYINT)
+BEGIN
+    DECLARE loop_limit INT UNSIGNED;
+    DECLARE next_start_address INT UNSIGNED;
+    DECLARE next_end_address INT UNSIGNED;
+    DECLARE remaining INT UNSIGNED;
+    DECLARE error_msg VARCHAR(512);
+
+    IF (p_start_address = inet_aton('0.0.0.0') OR
+        p_start_address > p_end_address)
+    THEN
+        SET error_msg = CONCAT('Invalid range: ', inet_ntoa(p_start_address),
+                               ' - ', inet_ntoa(p_end_address));
+        SIGNAL SQLSTATE '45000'
+        SET MESSAGE_TEXT = error_msg;
+    END IF;
+
+    -- Create the flq_pool4 row. Concurrent attempts will hang until
+    -- this one commits and then they will fail with duplicate key
+    -- error. Callers should treat the duplicate error as success.
+    IF p_recreate = 1
+    THEN
+        -- (Re)creating should ignore duplicate on insert
+        INSERT INTO flq_pool4 (start_address, end_address, subnet_id)
+           VALUES (p_start_address, p_end_address, p_subnet_id)
+        ON DUPLICATE KEY UPDATE start_address = start_address;
+    ELSE
+        INSERT INTO flq_pool4 (start_address, end_address, subnet_id)
+           VALUES (p_start_address, p_end_address, p_subnet_id);
+    END IF;
+
+    -- Wipe out existing free addresses. This ensures we fix any mixed allocation entries.
+    DELETE FROM free_lease4 WHERE address >= p_start_address and address <= p_end_address;
+
+    -- Insert available leases into free_lease4 table.  If insert fails on
+    -- duplicate ignore it. MySql limits recursive queries so we iterate
+    -- pool address range in chunks.
+    CALL setLoopLimit();
+    SET loop_limit = @kea_recursion_limit - 1;
+    SET next_start_address = p_start_address;
+    SET next_end_address = p_start_address;
+    start_loop: WHILE next_start_address <= p_end_address DO
+        SET remaining = p_end_address - next_start_address;
+        IF (remaining > loop_limit)
+        THEN
+            SET next_end_address = next_end_address + loop_limit;
+        ELSE
+            SET next_end_address = next_end_address + remaining;
+        END IF;
+
+        INSERT INTO free_lease4 (address)
+            WITH RECURSIVE addresses AS (
+                SELECT next_start_address AS avail
+                UNION ALL
+                SELECT avail + 1 FROM addresses WHERE avail < next_end_address
+            )
+            SELECT avail FROM addresses
+                LEFT JOIN lease4 on avail = lease4.address
+                WHERE (lease4.address IS NULL OR lease4.state = 2)
+            ON DUPLICATE KEY UPDATE free_lease4.address = avail;
+
+        IF next_start_address + loop_limit <= 4294967295 THEN
+            SET next_start_address = next_start_address + loop_limit;
+        ELSE
+            LEAVE start_loop;
+        END IF;
+    END WHILE start_loop;
+
+    -- Update the modification time in the flq_pool row.
+    UPDATE flq_pool4 SET modification_ts = now()
+        WHERE (start_address = p_start_address AND end_address = p_end_address);
+END $$
+DELIMITER ;
+
+-- Populate flq_pool6 and free_lease6 based on an address
+-- range and delegated len. Use 128 for NA addresses.
+-- This must be called from within a transaction.
+DROP PROCEDURE IF EXISTS sflqCreateFlqPool6;
+DELIMITER $$
+CREATE PROCEDURE sflqCreateFlqPool6(IN p_start_address VARCHAR(45),
+                                    IN p_end_address VARCHAR(45),
+                                    IN p_lease_type TINYINT UNSIGNED,
+                                    IN p_delegated_len TINYINT UNSIGNED,
+                                    IN p_subnet_id INT UNSIGNED,
+                                    IN p_recreate TINYINT)
+BEGIN
+    DECLARE bin_next_address BINARY(16);
+    DECLARE bin_end_address BINARY(16);
+    DECLARE lease_address VARCHAR(45);
+    DECLARE error_msg VARCHAR(512);
+    DECLARE x_delegated_len TINYINT UNSIGNED;
+    DECLARE pool_changed TINYINT;
+
+    SET bin_next_address = INET6_ATON(p_start_address);
+    SET bin_end_address = INET6_ATON(p_end_address);
+
+    IF (p_start_address =  "::" OR
+        bin_next_address > bin_end_address)
+    THEN
+        SET error_msg = CONCAT('Invalid range: ', p_start_address,
+                               ' - ', p_end_address);
+        SIGNAL SQLSTATE '45000'
+        SET MESSAGE_TEXT = error_msg;
+    END IF;
+
+    IF (p_delegated_len < 1 OR p_delegated_len > 128)
+    THEN
+        SET error_msg = CONCAT('Invalid p_delegated_len: ', p_delegated_len);
+        SIGNAL SQLSTATE '45000'
+        SET MESSAGE_TEXT = error_msg;
+    END IF;
+
+    -- If we are not re-creating the pool, look for a pre-existing pool
+    -- and see if delegated length has changed. If so we need to treat
+    -- it as recreate. This should catch configuration changes to
+    -- pools pre-existing pools.
+    SET pool_changed = 0;
+    IF (p_recreate = 0)
+    THEN
+        SELECT count(*) INTO pool_changed
+            FROM flq_pool6
+            WHERE (start_address = p_start_address AND
+                   end_address = p_end_address AND
+                   delegated_len != p_delegated_len)
+            LIMIT 1;
+    END IF;
+
+    -- Create the flq_pool6 row. Concurrent attempts will hang until
+    -- this one commits and then they will fail with duplicate key
+    -- error. Callers should treat the duplicate error as success.
+    IF (p_recreate = 1 OR pool_changed = 1)
+    THEN
+        -- (Re)creating should ignore duplicate on insert
+        INSERT INTO flq_pool6
+            (start_address, end_address, lease_type, delegated_len, subnet_id)
+            VALUES
+            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id)
+            ON DUPLICATE KEY UPDATE
+                    -- Recreate may have changed these
+                    lease_type = p_lease_type,
+                    delegated_len = p_delegated_len,
+                    subnet_id = p_subnet_id;
+    ELSE
+        INSERT INTO flq_pool6
+            (start_address, end_address, lease_type, delegated_len, subnet_id)
+            VALUES
+            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id);
+    END IF;
+
+    -- Wipe out existing free addresses. This ensures we fix any mixed allocation entries.
+    DELETE FROM free_lease6 WHERE bin_address >= bin_next_address and bin_address <= bin_end_address;
+    -- Enumerate the addresses into a temp table to use in a bulk insert.
+    DROP TEMPORARY TABLE IF EXISTS _flq6_candidates;
+    CREATE TEMPORARY TABLE _flq6_candidates (
+        bin_address BINARY(16) NOT NULL PRIMARY KEY,
+        address VARCHAR(45) NOT NULL
+    ) ENGINE=InnoDB;
+
+    label: WHILE bin_next_address <= bin_end_address
+    DO
+        INSERT INTO _flq6_candidates (bin_address, address)
+            VALUES (bin_next_address, INET6_NTOA(bin_next_address));
+
+        IF (bin_next_address = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
+        THEN
+            LEAVE label;
+        END IF;
+
+        SET bin_next_address = incrementV6Prefix(bin_next_address, p_delegated_len);
+    END WHILE label;
+
+    -- Bulk insert available leases into free_lease6 table. If insert fails on
+    -- duplicate ignore it.
+    INSERT INTO free_lease6 (address, bin_address)
+        SELECT c.address, c.bin_address
+        FROM _flq6_candidates c
+        WHERE NOT EXISTS (
+            SELECT 1 FROM lease6 l
+            WHERE l.address = c.address AND l.state != 2
+        )
+        ON DUPLICATE KEY UPDATE free_lease6.address = c.address;
+
+    DROP TEMPORARY TABLE _flq6_candidates;
+
+    -- Update the modification time in the flq_pool row.
+    UPDATE flq_pool6 SET modification_ts = now()
+        WHERE (start_address = p_start_address AND end_address = p_end_address);
+END $$
+DELIMITER ;
+
+-- Update the schema version number.
+UPDATE schema_version
+    SET version = '35', minor = '0';
+
+-- This line concludes the schema upgrade to version 35.0.
+
+EOF
index 15d023a6be5b27e7be0734dcb886d69f864366ac..4f180696747c744fe35bad8605aac8fb482e2e58 100644 (file)
@@ -7631,6 +7631,139 @@ UPDATE schema_version
 
 -- This line concludes the schema upgrade to version 33.0.
 
+-- This line starts the schema upgrade to version 34.0.
+
+-- Populate flq_pool6 and free_lease6 based on an address
+-- range and delegated len. Use 128 for NA addresses.
+CREATE OR REPLACE FUNCTION sflqCreateFlqPool6(p_start_address INET,
+                                               p_end_address INET,
+                                               p_lease_type SMALLINT,
+                                               p_delegated_len SMALLINT,
+                                               p_subnet_id BIGINT,
+                                               p_recreate BOOLEAN)
+RETURNS VOID
+LANGUAGE plpgsql AS $$
+DECLARE
+    current_ts TIMESTAMP WITH TIME ZONE := now();
+    p_end_bin BYTEA := inetToBytea(p_end_address);
+    batch_start_address INET := p_start_address;
+    batch_start_bin BYTEA := inetToBytea(p_start_address);
+    max_prefixes_per_batch INTEGER := 10000;
+    last_bin BYTEA;
+    next_bin BYTEA;
+    pool_changed INTEGER;
+BEGIN
+    IF ((p_start_address = '::'::inet) OR (p_start_address > p_end_address))
+    THEN
+        RAISE EXCEPTION 'Invalid range: % - %', host(p_start_address), host(p_end_address);
+    END IF;
+
+    IF (p_delegated_len < 1 OR p_delegated_len > 128)
+    THEN
+        RAISE EXCEPTION 'Invalid p_delegated_len: %', p_delegated_len;
+    END IF;
+
+    -- If we are not re-creating the pool, look for a pre-existing pool
+    -- and see if delegated length has changed. If so we need to treat
+    -- it as recreate. This should catch configuration changes to
+    -- pre-existing pools.
+    pool_changed = 0;
+    IF (p_recreate = false)
+    THEN
+        SELECT count(*) INTO pool_changed
+            FROM flq_pool6
+            WHERE (start_address = p_start_address AND
+                   end_address = p_end_address AND
+                   delegated_len != p_delegated_len)
+            LIMIT 1;
+    END IF;
+
+    -- Create the flq_pool6 row. Concurrent attempts will hang until
+    -- this one commits and then they will fail with duplicate key
+    -- error. Callers should treat the duplicate error as success.
+    IF (p_recreate = true OR pool_changed > 0)
+    THEN
+        -- (Re)creating should ignore duplicate on insert
+        INSERT INTO flq_pool6
+            (start_address, end_address, lease_type, delegated_len, subnet_id)
+            VALUES
+            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id)
+            -- ON CONFLICT (start_address, end_address) DO UPDATE SET
+            ON CONFLICT (start_address, end_address) DO UPDATE SET
+                    -- Recreate may have changed these
+                    lease_type = p_lease_type,
+                    delegated_len = p_delegated_len,
+                    subnet_id = p_subnet_id;
+    ELSE
+        INSERT INTO flq_pool6
+            (start_address, end_address, lease_type, delegated_len, subnet_id)
+            VALUES
+            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id);
+    END IF;
+
+    -- Wipe out existing free addresses. This ensures we fix any mixed allocation entries.
+    DELETE FROM free_lease6 WHERE address >= p_start_address and address <= p_end_address;
+
+    -- Enumerate prefixes in bounded chunks and insert free entries in bulk.
+    WHILE batch_start_address <= p_end_address
+    LOOP
+        WITH RECURSIVE prefixes(depth, bin_address, address) AS (
+            SELECT 1, batch_start_bin, batch_start_address
+            UNION ALL
+            SELECT prefixes.depth + 1,
+                   next_prefix.bin_address,
+                   byteaToInet(next_prefix.bin_address)
+            FROM prefixes,
+                LATERAL (SELECT incrementV6Prefix(prefixes.bin_address, p_delegated_len)
+                    AS bin_address) AS next_prefix
+            WHERE (next_prefix.bin_address > prefixes.bin_address AND 
+                   next_prefix.bin_address < p_end_bin AND
+                   prefixes.depth < max_prefixes_per_batch)
+        ),
+        ins AS (
+            INSERT INTO free_lease6(address, bin_address)
+                SELECT p.address, p.bin_address
+                FROM prefixes p
+                LEFT JOIN lease6 l
+                    ON l.address = p.address AND l.state != 2
+                WHERE l.address IS NULL
+                ON CONFLICT DO NOTHING
+                RETURNING 1
+        )
+        SELECT last_prefix.bin_address INTO STRICT last_bin
+            FROM (
+                SELECT bin_address
+                FROM prefixes
+                ORDER BY depth DESC
+                LIMIT 1
+            ) AS last_prefix;
+
+        next_bin := incrementV6Prefix(last_bin, p_delegated_len);
+
+        -- Stepped past the pool end (binary order), or increment wrapped at
+        -- all-ones IPv6 (:: after FFFF:...:FFFF) so inet compare would loop forever.
+        IF next_bin > p_end_bin OR next_bin < last_bin
+        THEN
+            EXIT;
+        END IF;
+
+        batch_start_bin := next_bin;
+        batch_start_address := byteaToInet(next_bin);
+    END LOOP;
+
+    -- Update the modification time in the flq_pool row.
+    UPDATE flq_pool6 SET modification_ts = now()
+        WHERE (start_address = p_start_address AND end_address = p_end_address);
+END;
+$$;
+
+-- Update the schema version number.
+UPDATE schema_version
+    SET version = '34', minor = '0';
+
+-- This line concludes the schema upgrade to version 34.0.
+
+
 -- Commit the script transaction.
 COMMIT;
 
index 1bc6c23d6e6617db1f32b9cb61446389856e3ad2..bacbfaaa7f86ffc96ceae25f680002a466b61a43 100644 (file)
@@ -69,6 +69,7 @@ upgrade_scripts = [
     'upgrade_030_to_031.sh',
     'upgrade_031_to_032.sh',
     'upgrade_032_to_033.sh',
+    'upgrade_033_to_034.sh',
 ]
 list = run_command(
     GRABBER,
diff --git a/src/share/database/scripts/pgsql/upgrade_033_to_034.sh.in b/src/share/database/scripts/pgsql/upgrade_033_to_034.sh.in
new file mode 100755 (executable)
index 0000000..8c8e328
--- /dev/null
@@ -0,0 +1,175 @@
+#!/bin/sh
+
+# Copyright (C) 2026 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
+
+# Check only major version to allow for intermediary backported schema changes.
+version=$(pgsql_version "${@}" | cut -d '.' -f 1)
+if test "${version}" != '33'; then
+    printf 'This script upgrades 33.* to 34.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 34.0.
+
+-- Populate flq_pool6 and free_lease6 based on an address
+-- range and delegated len. Use 128 for NA addresses.
+CREATE OR REPLACE FUNCTION sflqCreateFlqPool6(p_start_address INET,
+                                               p_end_address INET,
+                                               p_lease_type SMALLINT,
+                                               p_delegated_len SMALLINT,
+                                               p_subnet_id BIGINT,
+                                               p_recreate BOOLEAN)
+RETURNS VOID
+LANGUAGE plpgsql AS \$\$
+DECLARE
+    current_ts TIMESTAMP WITH TIME ZONE := now();
+    p_end_bin BYTEA := inetToBytea(p_end_address);
+    batch_start_address INET := p_start_address;
+    batch_start_bin BYTEA := inetToBytea(p_start_address);
+    max_prefixes_per_batch INTEGER := 10000;
+    last_bin BYTEA;
+    next_bin BYTEA;
+    pool_changed INTEGER;
+BEGIN
+    IF ((p_start_address = '::'::inet) OR (p_start_address > p_end_address))
+    THEN
+        RAISE EXCEPTION 'Invalid range: % - %', host(p_start_address), host(p_end_address);
+    END IF;
+
+    IF (p_delegated_len < 1 OR p_delegated_len > 128)
+    THEN
+        RAISE EXCEPTION 'Invalid p_delegated_len: %', p_delegated_len;
+    END IF;
+
+    -- If we are not re-creating the pool, look for a pre-existing pool
+    -- and see if delegated length has changed. If so we need to treat
+    -- it as recreate. This should catch configuration changes to
+    -- pre-existing pools.
+    pool_changed = 0;
+    IF (p_recreate = false)
+    THEN
+        SELECT count(*) INTO pool_changed
+            FROM flq_pool6
+            WHERE (start_address = p_start_address AND
+                   end_address = p_end_address AND
+                   delegated_len != p_delegated_len)
+            LIMIT 1;
+    END IF;
+
+    -- Create the flq_pool6 row. Concurrent attempts will hang until
+    -- this one commits and then they will fail with duplicate key
+    -- error. Callers should treat the duplicate error as success.
+    IF (p_recreate = true OR pool_changed > 0)
+    THEN
+        -- (Re)creating should ignore duplicate on insert
+        INSERT INTO flq_pool6
+            (start_address, end_address, lease_type, delegated_len, subnet_id)
+            VALUES
+            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id)
+            -- ON CONFLICT (start_address, end_address) DO UPDATE SET
+            ON CONFLICT (start_address, end_address) DO UPDATE SET
+                    -- Recreate may have changed these
+                    lease_type = p_lease_type,
+                    delegated_len = p_delegated_len,
+                    subnet_id = p_subnet_id;
+    ELSE
+        INSERT INTO flq_pool6
+            (start_address, end_address, lease_type, delegated_len, subnet_id)
+            VALUES
+            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id);
+    END IF;
+
+    -- Wipe out existing free addresses. This ensures we fix any mixed allocation entries.
+    DELETE FROM free_lease6 WHERE address >= p_start_address and address <= p_end_address;
+
+    -- Enumerate prefixes in bounded chunks and insert free entries in bulk.
+    WHILE batch_start_address <= p_end_address
+    LOOP
+        WITH RECURSIVE prefixes(depth, bin_address, address) AS (
+            SELECT 1, batch_start_bin, batch_start_address
+            UNION ALL
+            SELECT prefixes.depth + 1,
+                   next_prefix.bin_address,
+                   byteaToInet(next_prefix.bin_address)
+            FROM prefixes,
+                LATERAL (SELECT incrementV6Prefix(prefixes.bin_address, p_delegated_len)
+                    AS bin_address) AS next_prefix
+            WHERE (next_prefix.bin_address > prefixes.bin_address AND 
+                   next_prefix.bin_address < p_end_bin AND
+                   prefixes.depth < max_prefixes_per_batch)
+        ),
+        ins AS (
+            INSERT INTO free_lease6(address, bin_address)
+                SELECT p.address, p.bin_address
+                FROM prefixes p
+                LEFT JOIN lease6 l
+                    ON l.address = p.address AND l.state != 2
+                WHERE l.address IS NULL
+                ON CONFLICT DO NOTHING
+                RETURNING 1
+        )
+        SELECT last_prefix.bin_address INTO STRICT last_bin
+            FROM (
+                SELECT bin_address
+                FROM prefixes
+                ORDER BY depth DESC
+                LIMIT 1
+            ) AS last_prefix;
+
+        next_bin := incrementV6Prefix(last_bin, p_delegated_len);
+
+        -- Stepped past the pool end (binary order), or increment wrapped at
+        -- all-ones IPv6 (:: after FFFF:...:FFFF) so inet compare would loop forever.
+        IF next_bin > p_end_bin OR next_bin < last_bin
+        THEN
+            EXIT;
+        END IF;
+
+        batch_start_bin := next_bin;
+        batch_start_address := byteaToInet(next_bin);
+    END LOOP;
+
+    -- Update the modification time in the flq_pool row.
+    UPDATE flq_pool6 SET modification_ts = now()
+        WHERE (start_address = p_start_address AND end_address = p_end_address);
+END;
+\$\$;
+
+-- Update the schema version number.
+UPDATE schema_version
+    SET version = '34', minor = '0';
+
+-- This line concludes the schema upgrade to version 34.0.
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF