]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[4492] Added callouts and handlers and UTs
authorThomas Markwalder <tmark@isc.org>
Mon, 4 May 2026 19:16:28 +0000 (15:16 -0400)
committerThomas Markwalder <tmark@isc.org>
Tue, 12 May 2026 17:17:33 +0000 (17:17 +0000)
/src/hooks/dhcp/lease_cmds/sflq_cmds.cc
/src/hooks/dhcp/lease_cmds/sflq_cmds.h
    New files that implement the SFLQ command handlers

/src/hooks/dhcp/lease_cmds/libloadtests/sflq_cmds_unittests.cc
    New file with API tests

/src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc
    Add callouts to SLFQ command handlers

/src/hooks/dhcp/lease_cmds/lease_cmds_messages.mes
    New log messages for SFLQ commands

/src/lib/dhcpsrv/testutils/sflqtest_lease_mgr.*
    Implemented SFLQ commands

18 files changed:
src/hooks/dhcp/lease_cmds/lease_cmds_callouts.cc
src/hooks/dhcp/lease_cmds/lease_cmds_messages.cc
src/hooks/dhcp/lease_cmds/lease_cmds_messages.h
src/hooks/dhcp/lease_cmds/lease_cmds_messages.mes
src/hooks/dhcp/lease_cmds/libloadtests/lease_cmds_unittest.cc
src/hooks/dhcp/lease_cmds/libloadtests/meson.build
src/hooks/dhcp/lease_cmds/libloadtests/sflq_cmds_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/lease_cmds/meson.build
src/hooks/dhcp/lease_cmds/sflq_cmds.cc [new file with mode: 0644]
src/hooks/dhcp/lease_cmds/sflq_cmds.h [new file with mode: 0644]
src/lib/asiolink/addr_utilities.cc
src/lib/asiolink/addr_utilities.h
src/lib/dhcpsrv/lease_mgr.cc
src/lib/dhcpsrv/lease_mgr.h
src/lib/dhcpsrv/testutils/sflqtest_lease_mgr.cc
src/lib/dhcpsrv/testutils/sflqtest_lease_mgr.h
src/share/api/sflq-pool4-create.json [new file with mode: 0644]
src/share/api/sflq-pool4-get-all.json [new file with mode: 0644]

index d1ce480c3399ebf21702d4d300e2cc9957e15f6d..0ac03d1f7e13984550bc5eaf1da1fc16fc4f3cab 100644 (file)
@@ -17,6 +17,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <hooks/hooks.h>
 #include <process/daemon.h>
+#include <sflq_cmds.h>
 
 using namespace isc::config;
 using namespace isc::data;
@@ -334,6 +335,116 @@ int lease6_write(CalloutHandle& handle) {
     return(lease_cmds.leaseWriteHandler(handle));
 }
 
+/// @brief This is a command callout for 'sflq-pool4-create' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_create(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool4CreateHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool4-get-all' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_get_all(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool4GetAllHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool4-get-by-subnet' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_get_by_subnet(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool4GetBySubnetHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool4-get-by-range' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_get_by_range(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool4GetByRangeHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool4-del' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool4_del(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool4DelHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-create' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_create(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool6CreateHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-get-all' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_get_all(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool6GetAllHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-get-by-subnet' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_get_by_subnet(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool6GetBySubnetHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-get-by-range' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_get_by_range(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool6GetByRangeHandler(handle));
+}
+
+/// @brief This is a command callout for 'sflq-pool6-del' command.
+///
+/// @param handle Callout handle used to retrieve a command and
+/// provide a response.
+/// @return 0 if this callout has been invoked successfully,
+/// 1 otherwise.
+int sflq_pool6_del(CalloutHandle& handle) {
+    SflqCmds sflq_cmds;
+    return(sflq_cmds.sflqPool6DelHandler(handle));
+}
+
 /// @brief This function is called when the library is loaded.
 ///
 /// @param handle library handle
@@ -386,6 +497,16 @@ int load(LibraryHandle& handle) {
     handle.registerCommandCallout("lease6-resend-ddns", lease6_resend_ddns);
     handle.registerCommandCallout("lease4-write", lease4_write);
     handle.registerCommandCallout("lease6-write", lease6_write);
+    handle.registerCommandCallout("sflq-pool4-create", sflq_pool4_create);
+    handle.registerCommandCallout("sflq-pool4-get-all", sflq_pool4_get_all);
+    handle.registerCommandCallout("sflq-pool4-get-by-subnet", sflq_pool4_get_by_subnet);
+    handle.registerCommandCallout("sflq-pool4-get-by-range", sflq_pool4_get_by_range);
+    handle.registerCommandCallout("sflq-pool4-del", sflq_pool4_del);
+    handle.registerCommandCallout("sflq-pool6-create", sflq_pool6_create);
+    handle.registerCommandCallout("sflq-pool6-get-all", sflq_pool6_get_all);
+    handle.registerCommandCallout("sflq-pool6-get-by-subnet", sflq_pool6_get_by_subnet);
+    handle.registerCommandCallout("sflq-pool6-get-by-range", sflq_pool6_get_by_range);
+    handle.registerCommandCallout("sflq-pool6-del", sflq_pool6_del);
 
     // Instantiate the binding-variables manager singleton.
     binding_var_mgr.reset(new BindingVariableMgr(family));
@@ -487,5 +608,4 @@ int leases6_committed(CalloutHandle& handle) {
     return (0);
 }
 
-
 } // end extern "C"
index 9deb7f3d61a7ce452306397f40caab8cc2aeece9..d1aaf38ba628025e3f79e8c41f3e239a9fdc3de6 100644 (file)
@@ -41,6 +41,26 @@ extern const isc::log::MessageID LEASE_CMDS_WIPE4 = "LEASE_CMDS_WIPE4";
 extern const isc::log::MessageID LEASE_CMDS_WIPE4_FAILED = "LEASE_CMDS_WIPE4_FAILED";
 extern const isc::log::MessageID LEASE_CMDS_WIPE6 = "LEASE_CMDS_WIPE6";
 extern const isc::log::MessageID LEASE_CMDS_WIPE6_FAILED = "LEASE_CMDS_WIPE6_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_CREATE = "SFLQ_POOL4_CREATE";
+extern const isc::log::MessageID SFLQ_POOL4_CREATE_FAILED = "SFLQ_POOL4_CREATE_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_DEL = "SFLQ_POOL4_DEL";
+extern const isc::log::MessageID SFLQ_POOL4_DEL_FAILED = "SFLQ_POOL4_DEL_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_GET_ALL = "SFLQ_POOL4_GET_ALL";
+extern const isc::log::MessageID SFLQ_POOL4_GET_ALL_FAILED = "SFLQ_POOL4_GET_ALL_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_RANGE = "SFLQ_POOL4_GET_BY_RANGE";
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_RANGE_FAILED = "SFLQ_POOL4_GET_BY_RANGE_FAILED";
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_SUBNET = "SFLQ_POOL4_GET_BY_SUBNET";
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_SUBNET_FAILED = "SFLQ_POOL4_GET_BY_SUBNET_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_CREATE = "SFLQ_POOL6_CREATE";
+extern const isc::log::MessageID SFLQ_POOL6_CREATE_FAILED = "SFLQ_POOL6_CREATE_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_DEL = "SFLQ_POOL6_DEL";
+extern const isc::log::MessageID SFLQ_POOL6_DEL_FAILED = "SFLQ_POOL6_DEL_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_GET_ALL = "SFLQ_POOL6_GET_ALL";
+extern const isc::log::MessageID SFLQ_POOL6_GET_ALL_FAILED = "SFLQ_POOL6_GET_ALL_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_RANGE = "SFLQ_POOL6_GET_BY_RANGE";
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_RANGE_FAILED = "SFLQ_POOL6_GET_BY_RANGE_FAILED";
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_SUBNET = "SFLQ_POOL6_GET_BY_SUBNET";
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_SUBNET_FAILED = "SFLQ_POOL6_GET_BY_SUBNET_FAILED";
 
 namespace {
 
@@ -82,6 +102,26 @@ const char* values[] = {
     "LEASE_CMDS_WIPE4_FAILED", "lease4-wipe command failed (parameters: %1, reason: %2)",
     "LEASE_CMDS_WIPE6", "lease6-wipe command successful (parameters: %1)",
     "LEASE_CMDS_WIPE6_FAILED", "lease6-wipe command failed (parameters: %1, reason: %2)",
+    "SFLQ_POOL4_CREATE", "sflq-pool4-create command succeeded, (parameters: %1)",
+    "SFLQ_POOL4_CREATE_FAILED", "sflq-pool4-create command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL4_DEL", "sflq-pool4-del command succeeded, (parameters: %1) pools deleted: %2",
+    "SFLQ_POOL4_DEL_FAILED", "sflq-pool4-del command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL4_GET_ALL", "sflq-pool4-get-all command succeeded, (parameters: %1) pools found: %2",
+    "SFLQ_POOL4_GET_ALL_FAILED", "sflq-pool4-get-all command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL4_GET_BY_RANGE", "sflq-pool4-get-by-range command succeeded, (parameters: %1) pools found: %2",
+    "SFLQ_POOL4_GET_BY_RANGE_FAILED", "sflq-pool4-get-by-range command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL4_GET_BY_SUBNET", "sflq-pool4-get-by-subnet command succeeded, (parameters: %1) pools found: %2",
+    "SFLQ_POOL4_GET_BY_SUBNET_FAILED", "sflq-pool4-get-by-subnet command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL6_CREATE", "sflq-pool6-create command succeeded, (parameters: %1)",
+    "SFLQ_POOL6_CREATE_FAILED", "sflq-pool6-create command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL6_DEL", "sflq-pool4-del command succeeded, (parameters: %1) pools deleted: %2",
+    "SFLQ_POOL6_DEL_FAILED", "sflq-pool4-del command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL6_GET_ALL", "sflq-pool6-get-all command succeeded, (parameters: %1) pools found: %2",
+    "SFLQ_POOL6_GET_ALL_FAILED", "sflq-pool6-get-all command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL6_GET_BY_RANGE", "sflq-pool6-get-by-range command succeeded, (parameters: %1) pools found: %2",
+    "SFLQ_POOL6_GET_BY_RANGE_FAILED", "sflq-pool6-get-by-range command failed, (parameters: %1, reason: %2)",
+    "SFLQ_POOL6_GET_BY_SUBNET", "sflq-pool6-get-by-subnet command succeeded, (parameters: %1) pools found: %2",
+    "SFLQ_POOL6_GET_BY_SUBNET_FAILED", "sflq-pool6-get-by-subnet command failed, (parameters: %1, reason: %2)",
     NULL
 };
 
index c2baa81c8ca985cff212878aff6438754f0ad478..dda429bfe05de785652032e9e7156d44b513cdfb 100644 (file)
@@ -42,5 +42,25 @@ extern const isc::log::MessageID LEASE_CMDS_WIPE4;
 extern const isc::log::MessageID LEASE_CMDS_WIPE4_FAILED;
 extern const isc::log::MessageID LEASE_CMDS_WIPE6;
 extern const isc::log::MessageID LEASE_CMDS_WIPE6_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_CREATE;
+extern const isc::log::MessageID SFLQ_POOL4_CREATE_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_DEL;
+extern const isc::log::MessageID SFLQ_POOL4_DEL_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_GET_ALL;
+extern const isc::log::MessageID SFLQ_POOL4_GET_ALL_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_RANGE;
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_RANGE_FAILED;
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_SUBNET;
+extern const isc::log::MessageID SFLQ_POOL4_GET_BY_SUBNET_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_CREATE;
+extern const isc::log::MessageID SFLQ_POOL6_CREATE_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_DEL;
+extern const isc::log::MessageID SFLQ_POOL6_DEL_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_GET_ALL;
+extern const isc::log::MessageID SFLQ_POOL6_GET_ALL_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_RANGE;
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_RANGE_FAILED;
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_SUBNET;
+extern const isc::log::MessageID SFLQ_POOL6_GET_BY_SUBNET_FAILED;
 
 #endif // LEASE_CMDS_MESSAGES_H
index 3ce570b49dc460121c3243e982f084b5ae11002f..cb31b654d64e70242c338fb060c46a2a2ec2bdf3 100644 (file)
@@ -173,3 +173,73 @@ are logged.
 % LEASE_CMDS_WIPE6_FAILED lease6-wipe command failed (parameters: %1, reason: %2)
 The lease6-wipe command has failed. Both the reason as well as the
 parameters passed are logged.
+
+% SFLQ_POOL4_CREATE sflq-pool4-create command succeeded, (parameters: %1)
+The sflq-pool4-create command was successful.
+
+% SFLQ_POOL4_CREATE_FAILED sflq-pool4-create command failed, (parameters: %1, reason: %2)
+The sflq-pool4-create command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL4_GET_ALL sflq-pool4-get-all command succeeded, (parameters: %1) pools found: %2
+The sflq-pool4-get-all command was successful.
+
+% SFLQ_POOL4_GET_ALL_FAILED sflq-pool4-get-all command failed, (parameters: %1, reason: %2)
+The sflq-pool4-get-all command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL4_GET_BY_SUBNET sflq-pool4-get-by-subnet command succeeded, (parameters: %1) pools found: %2
+The sflq-pool4-get-by-subnet command was successful.
+
+% SFLQ_POOL4_GET_BY_SUBNET_FAILED sflq-pool4-get-by-subnet command failed, (parameters: %1, reason: %2)
+The sflq-pool4-get-subnet command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL4_GET_BY_RANGE sflq-pool4-get-by-range command succeeded, (parameters: %1) pools found: %2
+The sflq-pool4-get-by-range command was successful.
+
+% SFLQ_POOL4_GET_BY_RANGE_FAILED sflq-pool4-get-by-range command failed, (parameters: %1, reason: %2)
+The sflq-pool4-get-range command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL4_DEL sflq-pool4-del command succeeded, (parameters: %1) pools deleted: %2
+The sflq-pool4-del command was successful.
+
+% SFLQ_POOL4_DEL_FAILED sflq-pool4-del command failed, (parameters: %1, reason: %2)
+The sflq-pool4-del command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_CREATE sflq-pool6-create command succeeded, (parameters: %1)
+The sflq-pool6-create command was successful.
+
+% SFLQ_POOL6_CREATE_FAILED sflq-pool6-create command failed, (parameters: %1, reason: %2)
+The sflq-pool6-create command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_GET_ALL sflq-pool6-get-all command succeeded, (parameters: %1) pools found: %2
+The sflq-pool6-create command was successful.
+
+% SFLQ_POOL6_GET_ALL_FAILED sflq-pool6-get-all command failed, (parameters: %1, reason: %2)
+The sflq-pool6-get-all command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_GET_BY_SUBNET sflq-pool6-get-by-subnet command succeeded, (parameters: %1) pools found: %2
+The sflq-pool6-get-by-subnet command was successful.
+
+% SFLQ_POOL6_GET_BY_SUBNET_FAILED sflq-pool6-get-by-subnet command failed, (parameters: %1, reason: %2)
+The sflq-pool6-get-subnet command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_GET_BY_RANGE sflq-pool6-get-by-range command succeeded, (parameters: %1) pools found: %2
+The sflq-pool6-get-by-range command was successful.
+
+% SFLQ_POOL6_GET_BY_RANGE_FAILED sflq-pool6-get-by-range command failed, (parameters: %1, reason: %2)
+The sflq-pool6-get-range command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% SFLQ_POOL6_DEL sflq-pool4-del command succeeded, (parameters: %1) pools deleted: %2
+The sflq-pool6-del command was successful.
+
+% SFLQ_POOL6_DEL_FAILED sflq-pool4-del command failed, (parameters: %1, reason: %2)
+The sflq-pool6-del command has failed. Both the reason as well as the
+parameters passed are logged.
index 14ad72059691ae7c853901a4ff09a6334cc38310..e74dc48f1aa8010a875fc3ab4208f94e3fbfcb63 100644 (file)
@@ -53,7 +53,12 @@ TEST_F(LeaseCmdsTest, commands) {
         "lease4-del",               "lease6-del",
         "lease4-update",            "lease6-update",
         "lease4-wipe",              "lease6-wipe",
-        "lease4-resend-ddns",       "lease6-resend-ddns"
+        "lease4-resend-ddns",       "lease6-resend-ddns",
+        "sflq-pool4-create",        "sflq-pool6-create",
+        "sflq-pool4-get-all",       "sflq-pool6-get-all",
+        "sflq-pool4-get-by-subnet", "sflq-pool6-get-by-subnet",
+        "sflq-pool4-get-by-range",  "sflq-pool6-get-by-range",
+        "sflq-pool4-del",           "sflq-pool6-del",
     };
     setFamily(AF_INET);
     testCommands(cmds);
index 241fc83767ac8c2660a74a1439a279e1d305ba8b..cf09c20c2cba035d71aa7c25f711dd80c9ff3553 100644 (file)
@@ -3,6 +3,7 @@ if not TESTS_OPT.enabled()
 endif
 
 libs_testutils = [
+    kea_dhcpsrv_testutils_lib,
     kea_testutils_lib
 ]
 
@@ -13,6 +14,7 @@ dhcp_lease_cmds_libload_tests = executable(
     'lease_cmds_unittest.cc',
     'load_unload_unittests.cc',
     'run_unittests.cc',
+    'sflq_cmds_unittests.cc',
     cpp_args: [
         f'-DLIBDHCP_LEASE_CMDS_SO="@TOP_BUILD_DIR@/src/hooks/dhcp/lease_cmds/libdhcp_lease_cmds.so"',
     ],
diff --git a/src/hooks/dhcp/lease_cmds/libloadtests/sflq_cmds_unittests.cc b/src/hooks/dhcp/lease_cmds/libloadtests/sflq_cmds_unittests.cc
new file mode 100644 (file)
index 0000000..cc22b7c
--- /dev/null
@@ -0,0 +1,896 @@
+// 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/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
+#include <config/command_mgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/resource_handler.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <lease_cmds.h>
+#include <lease_cmds_unittest.h>
+#include <stats/stats_mgr.h>
+#include <util/filesystem.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/multi_threading_utils.h>
+#include <testutils/gtest_utils.h>
+#include <dhcpsrv/testutils/sflqtest_lease_mgr.h>
+#include <dhcpsrv/sflq_allocator.h>
+
+#include <gtest/gtest.h>
+
+#include <errno.h>
+#include <set>
+
+using namespace std;
+using namespace isc;
+using namespace isc::hooks;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::test;
+using namespace isc::lease_cmds;
+using namespace isc::dhcp::test;
+
+namespace {
+
+#define SCOPED_LINE(line) \
+    std::stringstream ss; \
+    ss << "Scenario at line: " << line; \
+    SCOPED_TRACE(ss.str());
+
+/// @brief Class dedicated to testing SFLQ commands in the lease_cmds library.
+///
+/// These tests use the dhcp::test::SflqTestLeaseMgr for tests as Memfile
+/// does not support SFLQ.
+///
+/// Provides convenience methods for loading, testing the SLFQ commands.
+class SflqCmdsTest : public LibLoadTest {
+public:
+    /// @brief Pointer to the lease manager
+    isc::dhcp::LeaseMgr* lmptr_;
+
+    /// @brief Constructor
+    ///
+    /// Sets the library filename and clears the lease manager pointer.
+    /// Also ensured there is no lease manager leftovers from previous
+    /// test.
+    SflqCmdsTest(uint16_t family)
+        : LibLoadTest(LIBDHCP_LEASE_CMDS_SO) {
+        setFamily(family);
+        isc::util::file::PathChecker::enableEnforcement(true);
+    }
+
+    /// @brief Pre-test setup.
+    ///
+    /// Installs the lease manager factory, creates a manager instance.
+    virtual void SetUp() {
+        LeaseMgrFactory::registerFactory("sflqtest", dhcp::test::SflqTestLeaseMgr::factory);
+        if (isc::dhcp::CfgMgr::instance().getFamily() == AF_INET) {
+            ASSERT_NO_THROW_LOG(LeaseMgrFactory::create("type=sflqtest universe=4"));
+        } else {
+            ASSERT_NO_THROW_LOG(LeaseMgrFactory::create("type=sflqtest universe=6"));
+        }
+
+        ASSERT_TRUE(LeaseMgrFactory::haveInstance());
+        ASSERT_EQ(LeaseMgrFactory::instance().getType(), "sflqtest");
+        // initSubnet4();
+        SharedFlqAllocator::setInUse(false);
+    }
+
+    /// @brief Destructor
+    ///
+    /// Removes library (if any), destroys lease manager (if any).
+    virtual ~SflqCmdsTest() {
+        // destroys lease manager first because the other order triggers
+        // a clang/boost bug
+        isc::dhcp::LeaseMgrFactory::destroy();
+        unloadLibs();
+        lmptr_ = 0;
+        isc::util::file::PathChecker::enableEnforcement(true);
+    }
+
+    /// @brief Creates JSON text command with arguments
+    ///
+    /// @param command command name
+    /// @param args Element map containing the command arguments. May be empty.
+    ///
+    /// @return Command with arguments as JSON text.
+    std::string buildCommand(const std::string& command,
+                             ElementPtr args = Element::createMap()) const {
+        std::ostringstream oss;
+        oss << "{ \"command\": \"" << command << "\", \"arguments\": " << *args << "}";
+        return (oss.str());
+    }
+
+    /// Compare two SflqPoolInfo structures for equality.
+    ///
+    /// All members are compared for equality except the timestamps. Those
+    /// are considered correct if the lhs timestamps are greater than or
+    /// equal to their rhs counterparts.  Asserts if they are not "equal".
+    ///
+    /// @param lhs left-side instance to compare
+    /// @param rhs reft-side instance to compare
+    /// @param lineno source line of invocation (pass in __LINE__)
+    void checkPoolInfos(const SflqPoolInfo& lhs, const SflqPoolInfo& rhs, int lineno) {
+        ASSERT_TRUE(lhs.lease_type_ == rhs.lease_type_ &&
+                    lhs.start_address_  == rhs.start_address_ &&
+                    lhs.end_address_ == rhs.end_address_ &&
+                    lhs.delegated_len_ == rhs.delegated_len_ &&
+                    lhs.subnet_id_ == rhs.subnet_id_ &&
+                    lhs.free_leases_ == rhs.free_leases_ &&
+                    lhs.created_ts_ >= rhs.created_ts_ &&
+                    lhs.modified_ts_ >= rhs.modified_ts_
+                    )
+            << "Pools don't match at line " << lineno << std::endl
+            << "lhs: " << *lhs.toElement() << std::endl
+            << "rhs: " << *rhs.toElement() << std::endl;
+    }
+
+    SflqPoolInfoCollectionPtr extractPools(ConstElementPtr cmd_rsp) {
+        if (!cmd_rsp) {
+            isc_throw(BadValue, "cmd_rsp is empty");
+        }
+
+        ConstElementPtr rsp_args = cmd_rsp->get("arguments");
+        if (!rsp_args || rsp_args->getType() != Element::map) {
+            isc_throw(BadValue, "rsp_args missing or not a map");
+        }
+
+        ConstElementPtr pools_elem = rsp_args->get("pools");
+        if (!pools_elem || pools_elem->getType() != Element::list) {
+            isc_throw(BadValue, "pools missing or not a list");
+        }
+
+        auto family = isc::dhcp::CfgMgr::instance().getFamily();
+        SflqPoolInfoCollectionPtr pool_infos(new SflqPoolInfoCollection());
+        for (int i = 0; i < pools_elem->size(); ++i) {
+            auto pool_elem = pools_elem->get(i);
+            SflqPoolInfoPtr pi(new SflqPoolInfo());
+            pi->start_address_ = SimpleParser::getAddress(pool_elem, "start-address");
+            pi->end_address_ = SimpleParser::getAddress(pool_elem, "end-address");
+            pi->subnet_id_ = SimpleParser::getInteger(pool_elem, "subnet-id");
+            pi->lease_type_ = extractLeaseType(pool_elem, family);
+            pi->delegated_len_ = SimpleParser::getInteger(pool_elem, "delegated-len");
+            pi->free_leases_ = SimpleParser::getInteger(pool_elem, "free-leases");
+            pi->created_ts_ = extractTimestamp(pool_elem, "created-ts");
+            pi->modified_ts_ = extractTimestamp(pool_elem, "modified-ts");
+            pool_infos->push_back(pi);
+        }
+
+        return (pool_infos);
+    }
+
+    Lease::Type extractLeaseType(ConstElementPtr& params, uint16_t family) {
+        auto tmp = SimpleParser::getString(params, "lease-type");
+        if (family == AF_INET) {
+            if (tmp == "V4" || tmp == "3") {
+                return (Lease::TYPE_V4);
+            }
+
+            isc_throw(BadValue, "invalid 'lease-type': " << tmp << " must be 'V4'");
+        }
+
+        if (tmp == "IA_NA" || tmp == "0") {
+            return (Lease::TYPE_NA);
+        } else if (tmp == "IA_PD" || tmp == "2") {
+            return (Lease::TYPE_PD);
+        }
+
+        isc_throw(BadValue, "invalid V6 'lease-type': "
+                  << tmp << ", valid values are IA_NA and IA_PD");
+    }
+
+    /// @brief Converts a string into a boost::posix_time::ptime
+    ///
+    ///  We're doing it this way because boost's function, time_from_string(),
+    ///  is part of their date/time library which is not header only.
+    ///
+    /// @param params Element map containing the timestamp
+    /// @param timestamp name of the timestamp element
+    ///
+    /// @throw BadValue if the timestamp is not found or the string
+    /// cannot be converted into a ptime
+    boost::posix_time::ptime extractTimestamp(ConstElementPtr& params,
+                                              const std::string& name) {
+        auto tmp = SimpleParser::getString(params, name);
+        struct tm tm_result;
+        if (!strptime(tmp.c_str(), "%Y-%m-%d %H:%M:%S", &tm_result)) {
+            isc_throw(BadValue, "cannot convert timestamp string: " << tmp);
+        }
+
+        return (boost::posix_time::ptime_from_tm(tm_result));
+    }
+};
+
+/// @brief Class dedicated to testing v4 SFLQ commands in the lease_cmds library.
+///
+/// These tests use the dhcp::test::SflqTestLeaseMgr for tests as Memfile
+/// does not support SFLQ.
+///
+/// Provides convenience methods for loading, testing the SLFQ commands.
+class SflqCmds4Test : public SflqCmdsTest {
+public:
+
+    /// @brief Constructor
+    SflqCmds4Test()
+        : SflqCmdsTest(AF_INET) {}
+
+    /// @brief Destructor
+    virtual ~SflqCmds4Test() {}
+
+    /// @brief Exercises invalid parameter checks for sflq-pool4-create.
+    void sflqPool4CreateBadParams();
+
+    /// @todo invalid arguments for get-by-subnet, get-by-range, and delete
+
+    /// @brief Exercises all the V4 SFLQ commands.
+    /// The intent is to verify that each command gets the arguments
+    /// it expects, carries out the appropriate action, and returns
+    /// the correct responses.
+    void testSflqCommands();
+};
+
+void SflqCmds4Test::sflqPool4CreateBadParams() {
+    struct Scenario {
+        int line_;
+        std::string args_;
+        std::string exp_rsp_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        __LINE__,
+        R"( )",
+        "missing 'start-address' parameter"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "178.0.0.10"
+        )",
+        "missing 'end-address' parameter"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "178.0.0.10",
+            "end-address" : "178.0.0.20"
+        )",
+        "missing 'subnet-id' parameter"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::",
+            "end-address" : "178.0.0.20",
+            "subnet-id" : 1
+        )",
+        "invalid V4 range - start_address 3001::, end_address 178.0.0.20,"
+        " must be V4 addresses where start_address <= end_address"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "178.0.0.20",
+            "end-address" : "178.0.0.10",
+            "subnet-id" : 1
+        )",
+        "invalid V4 range - start_address 178.0.0.20, end_address 178.0.0.10,"
+        " must be V4 addresses where start_address <= end_address"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "178.0.0.10",
+            "end-address" : "178.0.0.20",
+            "subnet-id" : "bogus"
+        )",
+        "'subnet-id' parameter is not an integer"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "178.0.0.10",
+            "end-address" : "178.0.0.20",
+            "subnet-id" : 0
+        )",
+        "'subnet-id' must be greater than zero"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "178.0.0.10",
+            "end-address" : "178.0.0.20",
+            "subnet-id" : 0,
+            "recreate" : 1
+        )",
+        "'recreate' parameter is not a boolean"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "178.0.0.10",
+            "end-address" : "178.0.0.20",
+            "subnet-id" : 0,
+            "recreate" : true,
+            "bogus" : "fluff"
+        )",
+        "spurious 'bogus' parameter"
+    }};
+
+    for ( auto const& scenario : scenarios) {
+        std::ostringstream oss;
+        oss << "Scenerio at line: " << scenario.line_;
+        SCOPED_TRACE(oss.str());
+        std::ostringstream command;
+        command << R"({ "command": "sflq-pool4-create", "arguments": {)"
+                << scenario.args_ << "}}";
+
+        testCommand(command.str(), CONTROL_RESULT_ERROR, scenario.exp_rsp_);
+    }
+}
+
+void
+SflqCmds4Test::testSflqCommands() {
+    auto test_start = boost::posix_time::second_clock::local_time();
+
+    // Fetching all pools should find none.
+    auto command = buildCommand("sflq-pool4-get-all");
+    testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+
+    // Create three test pool infos.
+    SflqPoolInfoCollection test_pools;
+    SflqPoolInfoPtr pi;
+
+    pi.reset(new SflqPoolInfo());
+    pi->start_address_ = IOAddress("192.0.1.0");
+    pi->end_address_ = IOAddress("192.0.1.2");
+    pi->subnet_id_ = 1;
+    pi->created_ts_ = test_start;
+    pi->modified_ts_ = test_start;
+    pi->free_leases_ = 3;
+    test_pools.push_back(pi);
+
+    pi.reset(new SflqPoolInfo());
+    pi->start_address_ = IOAddress("192.0.2.0");
+    pi->end_address_ = IOAddress("192.0.2.2");
+    pi->subnet_id_ = 2;
+    pi->created_ts_ = test_start;
+    pi->modified_ts_ = test_start;
+    pi->free_leases_ = 3;
+    test_pools.push_back(pi);
+
+    pi.reset(new SflqPoolInfo());
+    pi->start_address_ = IOAddress("192.0.3.0");
+    pi->end_address_ = IOAddress("192.0.3.2");
+    pi->subnet_id_ = 1;
+    pi->created_ts_ = test_start;
+    pi->modified_ts_ = test_start;
+    pi->free_leases_ = 3;
+    test_pools.push_back(pi);
+
+    // Now create the pools.
+    for ( auto const& test_pool : test_pools) {
+        auto args = test_pool->toElement();
+        // Take out what we don't need.
+        args->remove("lease-type");
+        args->remove("delegated-len");
+        args->remove("created-ts");
+        args->remove("modified-ts");
+        args->remove("free-leases");
+        // Add recreate flag.
+        args->set("recreate", Element::create(false));
+
+        // Initial create should work.
+        command = buildCommand("sflq-pool4-create", args);
+        testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool created.");
+
+        // Trying again without recreate should say it already exists.
+        testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool already exists.");
+
+        // Recreating it should work.
+        args->set("recreate", Element::create(true));
+        command = buildCommand("sflq-pool4-create", args);
+        testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool created.");
+    }
+
+    // Fetching all pools should find all three.
+    command = buildCommand("sflq-pool4-get-all");
+    auto cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "3 pool(s) found.");
+    auto pool_infos = extractPools(cmd_rsp);
+
+    // Should get them back in order they were created. Dummy back end doesn't sort.
+    for (int i = 0; i < test_pools.size(); ++i) {
+        checkPoolInfos(*(*pool_infos)[i], *test_pools[i], __LINE__);
+    }
+
+    // Fetch by subnet id for subnet_id = 1.
+    pool_infos.reset();
+    auto args = Element::fromJSON("{\"subnet-id\": 1}");
+    command = buildCommand("sflq-pool4-get-by-subnet", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "2 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(2, pool_infos->size());
+    checkPoolInfos(*(*pool_infos)[0], *test_pools[0], __LINE__);
+    checkPoolInfos(*(*pool_infos)[1], *test_pools[2], __LINE__);
+
+    // Fetch by subnet id for subnet_id = 2.
+    pool_infos.reset();
+    args = Element::fromJSON("{\"subnet-id\": 2}");
+    command = buildCommand("sflq-pool4-get-by-subnet", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "1 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(1, pool_infos->size());
+    checkPoolInfos(*(*pool_infos)[0], *test_pools[1], __LINE__);
+
+    // Fetch by subnet id for subnet_id = 99
+    pool_infos.reset();
+    pool_infos.reset();
+    args = Element::fromJSON("{\"subnet-id\": 99}");
+    command = buildCommand("sflq-pool4-get-by-subnet", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(0, pool_infos->size());
+
+    // Fetch by a range that excludes them all.
+    pool_infos.reset();
+    args = Element::fromJSON("{\"start-address\": \"1.2.3.4\","
+                             " \"end-address\": \"1.2.3.4\"}");
+    command = buildCommand("sflq-pool4-get-by-range", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(0, pool_infos->size());
+
+    // Fetch by a range that includes them all.
+    pool_infos.reset();
+    args = Element::fromJSON("{\"start-address\": \"192.0.0.0\","
+                             " \"end-address\": \"192.0.4.0\"}");
+    command = buildCommand("sflq-pool4-get-by-range", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "3 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(3, pool_infos->size());
+    checkPoolInfos(*(*pool_infos)[0], *test_pools[0], __LINE__);
+    checkPoolInfos(*(*pool_infos)[1], *test_pools[1], __LINE__);
+    checkPoolInfos(*(*pool_infos)[2], *test_pools[2], __LINE__);
+
+    // Fetch each by exact range match.
+    for ( auto const& test_pool : test_pools) {
+        pool_infos.reset();
+        args = Element::createMap();
+        args->set("start-address", Element::create(test_pool->start_address_.toText()));
+        args->set("end-address", Element::create(test_pool->end_address_.toText()));
+        command = buildCommand("sflq-pool4-get-by-range", args);
+        cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "1 pool(s) found.");
+        pool_infos = extractPools(cmd_rsp);
+        ASSERT_TRUE(pool_infos);
+        ASSERT_EQ(1, pool_infos->size());
+        checkPoolInfos(*(*pool_infos)[0], *test_pool, __LINE__);
+    }
+
+    // Delete each pool.
+    for ( auto const& test_pool : test_pools) {
+        // Delete the pool.
+        args = Element::createMap();
+        args->set("start-address", Element::create(test_pool->start_address_.toText()));
+        args->set("end-address", Element::create(test_pool->end_address_.toText()));
+        command = buildCommand("sflq-pool4-del", args);
+        testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool deleted");
+
+        // Try again, no such lease.
+        testCommand(command, CONTROL_RESULT_EMPTY, "SFLQ pool does not exist");
+    }
+}
+
+TEST_F(SflqCmds4Test, sflqPool4CreateBadParams) {
+    sflqPool4CreateBadParams();
+}
+
+TEST_F(SflqCmds4Test, sflqPool4CreateBadParamsMt) {
+    MultiThreadingTest mt(true);
+    sflqPool4CreateBadParams();
+}
+
+TEST_F(SflqCmds4Test, testSflqCommands) {
+    testSflqCommands();
+}
+
+TEST_F(SflqCmds4Test, testSflqCommandsMt) {
+    MultiThreadingTest mt(true);
+    testSflqCommands();
+}
+
+/// @brief Class dedicated to testing v6 SFLQ commands in the lease_cmds library.
+///
+/// These tests use the dhcp::test::SflqTestLeaseMgr for tests as Memfile
+/// does not support SFLQ.
+///
+/// Provides convenience methods for loading, testing the SLFQ commands.
+class SflqCmds6Test : public SflqCmdsTest {
+public:
+
+    /// @brief Constructor
+    SflqCmds6Test()
+        : SflqCmdsTest(AF_INET6) {}
+
+    /// @brief Destructor
+    virtual ~SflqCmds6Test() {}
+
+    /// @brief Exercises invalid parameter checks for sflq-pool6-create.
+    void sflqPool6CreateBadParams();
+
+    /// @todo invalid arguments for get-by-subnet, get-by-range, and delete
+
+    /// @brief Exercises all the V4 SFLQ commands.
+    ///
+    /// The intent is to verify that each command gets the arguments
+    /// it expects, carries out the appropriate action, and returns
+    /// the correct responses.
+    ///
+    /// @param lease_type lease type to test IA_NA or IA_PD
+    void testSflqCommands(Lease::Type lease_type);
+};
+
+void SflqCmds6Test::sflqPool6CreateBadParams() {
+    struct Scenario {
+        int line_;
+        std::string args_;
+        std::string exp_rsp_;
+    };
+
+    std::list<Scenario> scenarios = {
+    {
+        __LINE__,
+        R"( )",
+        "missing 'start-address' parameter"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10"
+        )",
+        "missing 'end-address' parameter"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20"
+        )",
+        "missing 'subnet-id' parameter"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : 1
+        )",
+        "missing 'lease-type' parameter"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "178.0.0.20",
+            "subnet-id" : 1,
+            "lease-type": "IA_NA"
+        )",
+        "invalid V6 range - start_address 3001::10, end_address 178.0.0.20,"
+        " must be V6 addresses where start_address <= end_address"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::20",
+            "end-address" : "3001::10",
+            "subnet-id" : 1,
+            "lease-type": "IA_NA"
+        )",
+        "invalid V6 range - start_address 3001::20, end_address 3001::10,"
+        " must be V6 addresses where start_address <= end_address"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : "bogus",
+            "lease-type": "IA_NA"
+        )",
+        "'subnet-id' parameter is not an integer"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : 0,
+            "lease-type": "IA_NA"
+        )",
+        "'subnet-id' must be greater than zero"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : 1,
+            "lease-type": "bogus"
+        )",
+        "invalid V6 'lease-type': bogus, valid values are IA_NA and IA_PD"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : 1,
+            "lease-type": "IA_PD",
+            "delegated-len": "bogus"
+        )",
+        "'delegated-len' parameter is not an integer"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : 1,
+            "lease-type": "IA_PD",
+            "delegated-len": 0
+        )",
+        "'delegated-len' invalid: 0, it must be >= 1 and =< 128"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : 1,
+            "lease-type": "IA_PD",
+            "delegated-len": 129
+        )",
+        "'delegated-len' invalid: 129, it must be >= 1 and =< 128"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : 0,
+            "lease-type": "IA_NA",
+            "recreate" : 1
+        )",
+        "'recreate' parameter is not a boolean"
+    },
+    {
+        __LINE__,
+        R"(
+            "start-address" : "3001::10",
+            "end-address" : "3001::20",
+            "subnet-id" : 0,
+            "lease-type": "IA_NA",
+            "recreate" : true,
+            "bogus" : "fluff"
+        )",
+        "spurious 'bogus' parameter"
+    }};
+
+    for ( auto const& scenario : scenarios) {
+        std::ostringstream oss;
+        oss << "Scenerio at line: " << scenario.line_;
+        SCOPED_TRACE(oss.str());
+        std::ostringstream command;
+        command << R"({ "command": "sflq-pool6-create", "arguments": {)"
+                << scenario.args_ << "}}";
+
+        testCommand(command.str(), CONTROL_RESULT_ERROR, scenario.exp_rsp_);
+    }
+}
+
+void
+SflqCmds6Test::testSflqCommands(Lease::Type lease_type) {
+    auto test_start = boost::posix_time::second_clock::local_time();
+
+    // Fetching all pools should find none.
+    auto command = buildCommand("sflq-pool6-get-all");
+    auto cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+    auto pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(0, pool_infos->size());
+
+    // Create three test pool infos.
+    SflqPoolInfoCollection test_pools;
+    SflqPoolInfoPtr pi;
+    pi.reset(new SflqPoolInfo());
+    pi->start_address_ = IOAddress("3001::10");
+    pi->end_address_ = IOAddress("3001::12");
+    pi->subnet_id_ = 1;
+    pi->lease_type_ = lease_type;
+    pi->created_ts_ = test_start;
+    pi->modified_ts_ = test_start;
+    pi->free_leases_ = 3;
+    test_pools.push_back(pi);
+
+    pi.reset(new SflqPoolInfo());
+    pi->start_address_ = IOAddress("3001::20");
+    pi->end_address_ = IOAddress("3001::22");
+    pi->subnet_id_ = 2;
+    pi->lease_type_ = lease_type;
+    pi->created_ts_ = test_start;
+    pi->modified_ts_ = test_start;
+    pi->free_leases_ = 3;
+    test_pools.push_back(pi);
+
+
+    pi.reset(new SflqPoolInfo());
+    pi->start_address_ = IOAddress("3001::30");
+    pi->end_address_ = IOAddress("3001::32");
+    pi->subnet_id_ = 1;
+    pi->lease_type_ = lease_type;
+    pi->created_ts_ = test_start;
+    pi->modified_ts_ = test_start;
+    pi->free_leases_ = 3;
+    test_pools.push_back(pi);
+
+    // Now create the pools.
+    for ( auto const& test_pool : test_pools) {
+        auto args = test_pool->toElement();
+        // Take out what we don't need.
+        args->remove("created-ts");
+        args->remove("modified-ts");
+        args->remove("free-leases");
+        // Add recreate flag.
+        args->set("recreate", Element::create(false));
+
+        // Initial create should work.
+        command = buildCommand("sflq-pool6-create", args);
+        testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool created.");
+
+        // Trying again without recreate should say it already exists.
+        testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool already exists.");
+
+        // Recreating it should work.
+        args->set("recreate", Element::create(true));
+        command = buildCommand("sflq-pool6-create", args);
+        testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool created.");
+
+    }
+
+    // Fetching all pools should find all three.
+    command = buildCommand("sflq-pool6-get-all");
+    cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "3 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+
+    // Should get them back in order they were created. Dummy back end doesn't sort.
+    for (int i = 0; i < test_pools.size(); ++i) {
+        checkPoolInfos(*(*pool_infos)[i], *test_pools[i], __LINE__);
+    }
+
+   // Fetch by subnet id for subnet_id = 1.
+    pool_infos.reset();
+    auto args = Element::fromJSON("{\"subnet-id\": 1}");
+    command = buildCommand("sflq-pool6-get-by-subnet", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "2 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(2, pool_infos->size());
+    checkPoolInfos(*(*pool_infos)[0], *test_pools[0], __LINE__);
+    checkPoolInfos(*(*pool_infos)[1], *test_pools[2], __LINE__);
+
+    // Fetch by subnet id for subnet_id = 2.
+    pool_infos.reset();
+    args = Element::fromJSON("{\"subnet-id\": 2}");
+    command = buildCommand("sflq-pool6-get-by-subnet", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "1 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(1, pool_infos->size());
+    checkPoolInfos(*(*pool_infos)[0], *test_pools[1], __LINE__);
+
+    // Fetch by subnet id for subnet_id = 99
+    pool_infos.reset();
+    args = Element::fromJSON("{\"subnet-id\": 99}");
+    command = buildCommand("sflq-pool6-get-by-subnet", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(0, pool_infos->size());
+
+    // Fetch by a range that excludes them all.
+    pool_infos.reset();
+    args = Element::fromJSON("{\"start-address\": \"2001::1\","
+                             " \"end-address\": \"2001::2\"}");
+    command = buildCommand("sflq-pool6-get-by-range", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_EMPTY, "0 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(0, pool_infos->size());
+
+    // Fetch by a range that includes them all.
+    pool_infos.reset();
+    args = Element::fromJSON("{\"start-address\": \"3001::\","
+                             " \"end-address\": \"3001::FF\"}");
+    command = buildCommand("sflq-pool6-get-by-range", args);
+    cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "3 pool(s) found.");
+    pool_infos = extractPools(cmd_rsp);
+    ASSERT_TRUE(pool_infos);
+    ASSERT_EQ(3, pool_infos->size());
+    checkPoolInfos(*(*pool_infos)[0], *test_pools[0], __LINE__);
+    checkPoolInfos(*(*pool_infos)[1], *test_pools[1], __LINE__);
+    checkPoolInfos(*(*pool_infos)[2], *test_pools[2], __LINE__);
+
+    // Fetch each by exact range match.
+    for ( auto const& test_pool : test_pools) {
+        pool_infos.reset();
+        args = Element::createMap();
+        args->set("start-address", Element::create(test_pool->start_address_.toText()));
+        args->set("end-address", Element::create(test_pool->end_address_.toText()));
+        command = buildCommand("sflq-pool6-get-by-range", args);
+        cmd_rsp = testCommand(command, CONTROL_RESULT_SUCCESS, "1 pool(s) found.");
+        pool_infos = extractPools(cmd_rsp);
+        ASSERT_TRUE(pool_infos);
+        ASSERT_EQ(1, pool_infos->size());
+        checkPoolInfos(*(*pool_infos)[0], *test_pool, __LINE__);
+    }
+
+    // Delete each pool.
+    for ( auto const& test_pool : test_pools) {
+        // Delete the pool.
+        args = Element::createMap();
+        args->set("start-address", Element::create(test_pool->start_address_.toText()));
+        args->set("end-address", Element::create(test_pool->end_address_.toText()));
+        command = buildCommand("sflq-pool6-del", args);
+        testCommand(command, CONTROL_RESULT_SUCCESS, "SFLQ pool deleted");
+
+        // Try again, no such lease.
+        testCommand(command, CONTROL_RESULT_EMPTY, "SFLQ pool does not exist");
+    }
+}
+
+TEST_F(SflqCmds6Test, sflqPool6CreateBadParams) {
+    sflqPool6CreateBadParams();
+}
+
+TEST_F(SflqCmds6Test, sflqPool6CreateBadParamsMt) {
+    MultiThreadingTest mt(true);
+    sflqPool6CreateBadParams();
+}
+
+TEST_F(SflqCmds6Test, testSflqCommandsNA) {
+    testSflqCommands(Lease::TYPE_NA);
+}
+
+TEST_F(SflqCmds6Test, testSflqCommandsNAMt) {
+    MultiThreadingTest mt(true);
+    testSflqCommands(Lease::TYPE_NA);
+}
+
+TEST_F(SflqCmds6Test, testSflqCommandsPD) {
+    testSflqCommands(Lease::TYPE_PD);
+}
+
+TEST_F(SflqCmds6Test, testSflqCommandsPDMt) {
+    MultiThreadingTest mt(true);
+    testSflqCommands(Lease::TYPE_PD);
+}
+
+
+} // end of anonymous namespace
index 86a4f1f659668bac922980b5349644df14ef85f4..c4151433f4c3d9682acb89509c4978d961aa01b4 100644 (file)
@@ -6,6 +6,7 @@ dhcp_lease_cmds_lib = shared_library(
     'lease_cmds_log.cc',
     'lease_cmds_messages.cc',
     'lease_parser.cc',
+    'sflq_cmds.cc',
     'version.cc',
     dependencies: [CRYPTO_DEP],
     include_directories: [include_directories('.')] + INCLUDES,
diff --git a/src/hooks/dhcp/lease_cmds/sflq_cmds.cc b/src/hooks/dhcp/lease_cmds/sflq_cmds.cc
new file mode 100644 (file)
index 0000000..44e465b
--- /dev/null
@@ -0,0 +1,542 @@
+// 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/.
+
+#include <config.h>
+#include <config/cmds_impl.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <database/db_exceptions.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet_id.h>
+#include <hooks/hooks.h>
+#include <exceptions/exceptions.h>
+#include <lease_cmds_log.h>
+#include <sflq_cmds.h>
+#include <util/multi_threading_mgr.h>
+
+#include <string>
+#include <sstream>
+
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::asiolink;
+using namespace isc::hooks;
+using namespace isc::util;
+using namespace isc::log;
+using namespace std;
+
+namespace isc {
+namespace lease_cmds {
+
+int
+SflqCmdsImpl::sflqPool4CreateHandler(CalloutHandle& handle) {
+    static const data::SimpleKeywords keywords =
+    {
+        { "start-address",  Element::string  },
+        { "end-address",    Element::string  },
+        { "subnet-id",      Element::integer },
+        { "recreate",       Element::boolean }
+    };
+
+    static const data::SimpleRequiredKeywords required_keywords =
+    {
+        "start-address",
+        "end-address",
+        "subnet-id",
+    };
+
+    stringstream resp;
+    try {
+        extractCommand(handle);
+        if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+            isc_throw(isc::BadValue, "no parameters specified for the command");
+        }
+
+        SimpleParser::checkRequired(required_keywords, cmd_args_);
+        SimpleParser::checkKeywords(keywords, cmd_args_);
+
+        // Fetch the command parameters.
+        IOAddress start_address(IOAddress::IPV4_ZERO_ADDRESS());
+        IOAddress end_address(IOAddress::IPV4_ZERO_ADDRESS());
+        extractRange(cmd_args_, AF_INET, start_address, end_address);
+
+        /// @todo Should we verify the subnet-id and/or check allocator for subnet?
+        SubnetID subnet_id = SimpleParser::getInteger(cmd_args_, "subnet-id");
+        if (subnet_id <= 0) {
+            isc_throw(isc::BadValue, "'subnet-id' must be greater than zero");
+        }
+
+        bool recreate = extractBool(cmd_args_, "recreate", false);
+
+        // Invoke the pool create function inside a CriticalSection.
+        MultiThreadingCriticalSection cs;
+        bool created = LeaseMgrFactory::instance().
+                       sflqCreateFlqPool4(start_address, end_address, subnet_id, recreate);
+
+        resp << (created ? "SFLQ pool created" : "SFLQ pool already exists") << ".";
+    } catch (const std::exception& ex) {
+        LOG_ERROR(lease_cmds_logger, SFLQ_POOL4_CREATE_FAILED)
+            .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+            .arg(ex.what());
+        setErrorResponse(handle, ex.what());
+        return (1);
+    }
+
+    LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA, SFLQ_POOL4_CREATE)
+        .arg(cmd_args_->str());
+
+    setSuccessResponse(handle, resp.str());
+    return (0);
+}
+
+int
+SflqCmdsImpl::sflqPool4GetAllHandler(CalloutHandle& handle) {
+    return (sflqPoolGetAll(handle, AF_INET));
+}
+
+int
+SflqCmdsImpl::sflqPool4GetBySubnetHandler(CalloutHandle& handle) {
+    return (sflqPoolGetBySubnet(handle, AF_INET));
+}
+
+int
+SflqCmdsImpl::sflqPool4GetByRangeHandler(CalloutHandle& handle) {
+    return (sflqPoolGetByRange(handle, AF_INET));
+}
+
+int
+SflqCmdsImpl::sflqPool4DelHandler(CalloutHandle& handle) {
+    return (sflqPoolDel(handle, AF_INET));
+}
+
+int
+SflqCmdsImpl::sflqPool6CreateHandler(CalloutHandle& handle) {
+    static const data::SimpleKeywords keywords =
+    {
+        { "start-address",  Element::string  },
+        { "end-address",    Element::string  },
+        { "subnet-id",      Element::integer },
+        { "lease-type",     Element::string  },
+        { "delegated-len",  Element::integer },
+        { "recreate",       Element::boolean }
+    };
+
+    static const data::SimpleRequiredKeywords required_keywords =
+    {
+        "start-address",
+        "end-address",
+        "subnet-id",
+        "lease-type"
+    };
+
+    stringstream resp;
+    try {
+        extractCommand(handle);
+        if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+            isc_throw(isc::BadValue, "no parameters specified for the command");
+        }
+
+        SimpleParser::checkRequired(required_keywords, cmd_args_);
+        SimpleParser::checkKeywords(keywords, cmd_args_);
+
+        // Fetch the command parameters.
+        IOAddress start_address(IOAddress::IPV6_ZERO_ADDRESS());
+        IOAddress end_address(IOAddress::IPV6_ZERO_ADDRESS());
+        extractRange(cmd_args_, AF_INET6, start_address, end_address);
+
+        /// @todo Should we verify the subnet-id and/or check allocator for subnet?
+        SubnetID subnet_id = SimpleParser::getInteger(cmd_args_, "subnet-id");
+        if (subnet_id <= 0) {
+            isc_throw(isc::BadValue, "'subnet-id' must be greater than zero");
+        }
+
+        auto lease_type = extractLeaseType(cmd_args_, AF_INET6);
+        uint8_t delegated_len = extractDelegatedLen(cmd_args_);
+        bool recreate = extractBool(cmd_args_, "recreate", false);
+
+        // Invoke the pool create function inside a CriticalSection.
+        MultiThreadingCriticalSection cs;
+        bool created = LeaseMgrFactory::instance().
+                       sflqCreateFlqPool6(start_address, end_address, lease_type,
+                                          delegated_len, subnet_id, recreate);
+
+        resp << (created ? "SFLQ pool created" : "SFLQ pool already exists") << ".";
+    } catch (const std::exception& ex) {
+        LOG_ERROR(lease_cmds_logger, SFLQ_POOL6_CREATE_FAILED)
+            .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+            .arg(ex.what());
+        setErrorResponse(handle, ex.what());
+        return (1);
+    }
+
+    LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA, SFLQ_POOL6_CREATE)
+        .arg(cmd_args_->str());
+
+    setSuccessResponse(handle, resp.str());
+    return (0);
+}
+
+int
+SflqCmdsImpl::sflqPool6GetAllHandler(CalloutHandle& handle) {
+    return (sflqPoolGetAll(handle, AF_INET6));
+}
+
+int
+SflqCmdsImpl::sflqPool6GetBySubnetHandler(CalloutHandle& handle) {
+    return (sflqPoolGetBySubnet(handle, AF_INET6));
+}
+
+int
+SflqCmdsImpl::sflqPool6GetByRangeHandler(CalloutHandle& handle) {
+    return (sflqPoolGetByRange(handle, AF_INET6));
+}
+
+int
+SflqCmdsImpl::sflqPool6DelHandler(CalloutHandle& handle) {
+    return (sflqPoolDel(handle, AF_INET6));
+}
+
+int
+SflqCmdsImpl::sflqPoolGetAll(CalloutHandle& handle, uint16_t family) {
+    static const data::SimpleKeywords keywords;
+    try {
+        extractCommand(handle);
+        if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+            isc_throw(isc::BadValue, "no parameters specified for the command");
+        }
+
+        // Command has no arguments, this will catch any supplied.
+        SimpleParser::checkKeywords(keywords, cmd_args_);
+
+        // Invoke the pool get function.
+        auto pools = (family == AF_INET ? LeaseMgrFactory::instance().sflqPool4GetAll()
+                                       : LeaseMgrFactory::instance().sflqPool6GetAll());
+
+        auto resp = buildGetResponse(pools);
+        setResponse(handle, resp);
+        LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA,
+                  (family == AF_INET ? SFLQ_POOL4_GET_ALL : SFLQ_POOL6_GET_ALL))
+                  .arg(cmd_args_->str())
+                  .arg(pools->size());
+    } catch (const std::exception& ex) {
+        LOG_ERROR(lease_cmds_logger, (family == AF_INET ? SFLQ_POOL4_GET_ALL_FAILED
+                                                        : SFLQ_POOL6_GET_ALL_FAILED))
+            .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+            .arg(ex.what());
+        setErrorResponse(handle, ex.what());
+        return (1);
+    }
+
+    return (0);
+}
+
+int
+SflqCmdsImpl::sflqPoolGetBySubnet(CalloutHandle& handle, uint16_t family) {
+    static const data::SimpleKeywords keywords =
+    {
+        { "subnet-id",      Element::integer },
+    };
+
+    static const data::SimpleRequiredKeywords required_keywords =
+    {
+        "subnet-id",
+    };
+
+    try {
+        extractCommand(handle);
+        if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+            isc_throw(isc::BadValue, "no parameters specified for the command");
+        }
+
+        SimpleParser::checkRequired(required_keywords, cmd_args_);
+        SimpleParser::checkKeywords(keywords, cmd_args_);
+        SubnetID subnet_id = SimpleParser::getInteger(cmd_args_, "subnet-id");
+
+        // Invoke the pool get by subnet function.
+        auto pools = (family == AF_INET ? LeaseMgrFactory::instance().sflqPool4Get(subnet_id)
+                      : LeaseMgrFactory::instance().sflqPool6Get(subnet_id));
+
+        auto resp = buildGetResponse(pools);
+        setResponse(handle, resp);
+        LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA,
+                  (family == AF_INET ? SFLQ_POOL4_GET_BY_SUBNET : SFLQ_POOL6_GET_BY_SUBNET))
+                  .arg(cmd_args_->str())
+                  .arg(pools->size());
+    } catch (const std::exception& ex) {
+        LOG_ERROR(lease_cmds_logger, (family == AF_INET ? SFLQ_POOL4_GET_BY_SUBNET_FAILED
+                                                        : SFLQ_POOL6_GET_BY_SUBNET_FAILED))
+            .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+            .arg(ex.what());
+        setErrorResponse(handle, ex.what());
+        return (1);
+    }
+
+    return (0);
+}
+
+int
+SflqCmdsImpl::sflqPoolGetByRange(CalloutHandle& handle, uint16_t family) {
+    static const data::SimpleKeywords keywords =
+    {
+        { "start-address",  Element::string  },
+        { "end-address",    Element::string  },
+    };
+
+    static const data::SimpleRequiredKeywords required_keywords =
+    {
+        "start-address",
+        "end-address",
+    };
+
+    try {
+        extractCommand(handle);
+        if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+            isc_throw(isc::BadValue, "no parameters specified for the command");
+        }
+
+        SimpleParser::checkRequired(required_keywords, cmd_args_);
+        SimpleParser::checkKeywords(keywords, cmd_args_);
+
+        // Fetch the command parameters.
+        IOAddress start_address(IOAddress::IPV4_ZERO_ADDRESS());
+        IOAddress end_address(IOAddress::IPV4_ZERO_ADDRESS());
+        extractRange(cmd_args_, family, start_address, end_address);
+
+        // Invoke the pool get by subnet function.
+        auto pools = (family == AF_INET
+                      ? LeaseMgrFactory::instance().sflqPool4Get(start_address, end_address)
+                      : LeaseMgrFactory::instance().sflqPool6Get(start_address, end_address));
+
+        auto resp = buildGetResponse(pools);
+        setResponse(handle, resp);
+        LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA,
+                  (family == AF_INET ? SFLQ_POOL4_GET_BY_RANGE : SFLQ_POOL6_GET_BY_RANGE))
+                  .arg(cmd_args_->str())
+                  .arg(pools->size());
+    } catch (const std::exception& ex) {
+        LOG_ERROR(lease_cmds_logger, (family == AF_INET ? SFLQ_POOL4_GET_BY_RANGE_FAILED
+                                                        : SFLQ_POOL6_GET_BY_RANGE_FAILED))
+            .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+            .arg(ex.what());
+        setErrorResponse(handle, ex.what());
+        return (1);
+    }
+
+    return (0);
+}
+
+int
+SflqCmdsImpl::sflqPoolDel(CalloutHandle& handle, uint16_t family) {
+    static const data::SimpleKeywords keywords =
+    {
+        { "start-address",  Element::string  },
+        { "end-address",    Element::string  },
+        { "force",          Element::boolean },
+    };
+
+    static const data::SimpleRequiredKeywords required_keywords =
+    {
+        "start-address",
+        "end-address",
+    };
+
+    try {
+        extractCommand(handle);
+        if (!cmd_args_ || cmd_args_->getType() != Element::map) {
+            isc_throw(isc::BadValue, "no parameters specified for the command");
+        }
+
+        SimpleParser::checkRequired(required_keywords, cmd_args_);
+        SimpleParser::checkKeywords(keywords, cmd_args_);
+
+        // Fetch the command parameters.
+        IOAddress start_address(IOAddress::IPV4_ZERO_ADDRESS());
+        IOAddress end_address(IOAddress::IPV4_ZERO_ADDRESS());
+        extractRange(cmd_args_, family, start_address, end_address);
+        bool force = extractBool(cmd_args_, "force", false);
+
+        // Invoke the pool get by subnet function.
+        bool deleted = (family == AF_INET
+                        ? LeaseMgrFactory::instance().sflqPool4Del(start_address,
+                                                                   end_address, force)
+                        : LeaseMgrFactory::instance().sflqPool6Del(start_address,
+                                                                   end_address, force));
+
+        if (deleted) {
+            auto response = createAnswer(CONTROL_RESULT_SUCCESS,
+                                         "SFLQ pool deleted", cmd_args_);
+            setResponse(handle, response);
+        } else {
+            auto response = createAnswer(CONTROL_RESULT_EMPTY,
+                                         "SFLQ pool does not exist", cmd_args_);
+            setResponse(handle, response);
+        }
+
+        LOG_DEBUG(lease_cmds_logger, LEASE_CMDS_DBG_COMMAND_DATA,
+                  (family == AF_INET ? SFLQ_POOL4_DEL : SFLQ_POOL6_DEL))
+                  .arg(cmd_args_->str())
+                  .arg(deleted ? 1 : 0);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(lease_cmds_logger, (family == AF_INET ? SFLQ_POOL4_DEL_FAILED
+                                                        : SFLQ_POOL6_DEL_FAILED))
+            .arg(cmd_args_ ? cmd_args_->str() : "<no args>")
+            .arg(ex.what());
+        setErrorResponse(handle, ex.what());
+        return (1);
+    }
+
+    return (0);
+}
+
+ConstElementPtr
+SflqCmdsImpl::buildGetResponse(SflqPoolInfoCollectionPtr pools) {
+    ElementPtr pools_json = Element::createList();
+    for (auto const& pool : *pools) {
+        pools_json->add(pool->toElement());
+    }
+
+    auto pool_cnt = pools_json->size();
+    stringstream resp_msg;
+    resp_msg << pool_cnt << " pool(s) found.";
+
+    ElementPtr args = Element::createMap();
+    args->set("pools", pools_json);
+
+    auto response = createAnswer(pool_cnt > 0 ?
+                                 CONTROL_RESULT_SUCCESS : CONTROL_RESULT_EMPTY,
+                                 resp_msg.str(), args);
+    return(response);
+}
+
+
+void
+SflqCmdsImpl::extractRange(ConstElementPtr& params, uint8_t family,
+                           IOAddress& start_address, IOAddress& end_address) {
+    start_address = SimpleParser::getAddress(params, "start-address");
+    end_address = SimpleParser::getAddress(params, "end-address");
+    if (family == AF_INET) {
+        validateV4Range(start_address, end_address);
+    } else {
+        validateV6Range(start_address, end_address);
+    }
+}
+
+bool
+SflqCmdsImpl::extractBool(ConstElementPtr& params, const std::string& name,
+                          bool default_value) {
+    auto tmp = params->get(name);
+    if (!tmp) {
+        return (default_value);
+    }
+
+    if (tmp->getType() != Element::boolean) {
+        isc_throw(BadValue, "'" << name << "' parameter is not boolean.");
+    }
+
+    return (tmp->boolValue());
+}
+
+Lease::Type
+SflqCmdsImpl::extractLeaseType(ConstElementPtr& params, uint16_t family) {
+    auto tmp = SimpleParser::getString(params, "lease-type");
+    if (family == AF_INET) {
+        if (tmp == "V4" || tmp == "3") {
+            return (Lease::TYPE_V4);
+        }
+
+        isc_throw(BadValue, "invalid 'lease-type': " << tmp << " must be 'V4'");
+    }
+
+    if (tmp == "IA_NA" || tmp == "0") {
+        return (Lease::TYPE_NA);
+    } else if (tmp == "IA_PD" || tmp == "2") {
+        return (Lease::TYPE_PD);
+    }
+
+    isc_throw(BadValue, "invalid V6 'lease-type': "
+              << tmp << ", valid values are IA_NA and IA_PD");
+}
+
+uint8_t
+SflqCmdsImpl::extractDelegatedLen(ConstElementPtr& params) {
+    auto tmp = params->get("delegated-len");
+    if (!tmp) {
+        return (128);
+    }
+
+    if (tmp->getType() != Element::integer) {
+        isc_throw(BadValue, "'delegated-len' parameter is not integer.");
+    }
+
+    auto val = tmp->intValue();
+    if (val <= 0 || val > 128) {
+        isc_throw(BadValue, "'delegated-len' invalid: " << val
+                  << ", it must be >= 1 and =< 128");
+    }
+
+    return (val);
+}
+
+SflqCmds::SflqCmds()
+    : sflq_impl_(new SflqCmdsImpl()) {
+}
+
+int
+SflqCmds::sflqPool4CreateHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool4CreateHandler(handle));
+}
+
+int
+SflqCmds::sflqPool4GetAllHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool4GetAllHandler(handle));
+}
+
+int
+SflqCmds::sflqPool4GetBySubnetHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool4GetBySubnetHandler(handle));
+}
+
+int
+SflqCmds::sflqPool4GetByRangeHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool4GetByRangeHandler(handle));
+}
+
+int
+SflqCmds::sflqPool4DelHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool4DelHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6CreateHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool6CreateHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6GetAllHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool6GetAllHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6GetBySubnetHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool6GetBySubnetHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6GetByRangeHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool6GetByRangeHandler(handle));
+}
+
+int
+SflqCmds::sflqPool6DelHandler(CalloutHandle& handle) {
+    return (sflq_impl_->sflqPool6DelHandler(handle));
+}
+
+} // end of namespace lease_cmds
+} // end of namespace isc
diff --git a/src/hooks/dhcp/lease_cmds/sflq_cmds.h b/src/hooks/dhcp/lease_cmds/sflq_cmds.h
new file mode 100644 (file)
index 0000000..5d5d45c
--- /dev/null
@@ -0,0 +1,619 @@
+// 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/.
+
+#ifndef SFLQ_CMDS_H
+#define SFLQ_CMDS_H
+
+#include <config.h>
+#include <config/cmds_impl.h>
+#include <cc/data.h>
+#include <asiolink/io_address.h>
+#include <database/db_exceptions.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/subnet_id.h>
+
+#include <string>
+#include <sstream>
+
+namespace isc {
+namespace lease_cmds {
+
+/// @brief Implements the logic for processing commands pertaining to
+/// SFLQ pools and data.
+class SflqCmdsImpl : private config::CmdsImpl {
+public:
+    /// @brief Consstructor.
+    SflqCmdsImpl() {};
+
+    /// @brief Destructor.
+    virtual ~SflqCmdsImpl() {};
+
+    /// @brief The 'sflq-pool4-create' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-create",
+    ///     "arguments": {
+    ///         "start-address": "192.0.2.0",
+    ///         "end-address": "192.0.2.255",
+    ///         "subnet-id": 77,
+    ///         "recreate": true
+    ///     }
+    /// }
+    ///
+    /// The 'recreate' parameter is optional, it defaults false.
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4CreateHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool4-get-all' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-get-all",
+    ///     "arguments": {
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4GetAllHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool4-by-subnet' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-get-by-subnet",
+    ///     "arguments": {
+    ///         "subnet-id" : 100
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4GetBySubnetHandler(hooks::CalloutHandle& handle);
+
+    /// @brief Handles a 'sflq-pool4-by-range' command
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-get-by-range",
+    ///     "arguments": {
+    ///         "start-address": "192.0.2.0",
+    ///         "end-address": "192.0.2.255"
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4GetByRangeHandler(hooks::CalloutHandle& handle);
+
+    /// @brief Delete the SFLQ V4 pool that matches a start and end address.
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-del",
+    ///     "arguments": {
+    ///         "start-address": "192.0.2.0",
+    ///         "end-address": "192.0.2.255",
+    ///         "force": false
+    ///     }
+    /// }
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4DelHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool6-create' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-create",
+    ///     "arguments": {
+    ///         "start-address": "3001::",
+    ///         "end-address": "3001::FF",
+    ///         "subnet-id": 77,
+    ///         "lease-type" : "IA_PD",
+    ///         "delegated_len: 64
+    ///         "recreate": true
+    ///     }
+    /// }
+    ///
+    /// The 'recreate' parameter is optional, it defaults false.
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6CreateHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool6-get-all' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-get-all",
+    ///     "arguments": {
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6GetAllHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool6-by-subnet' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-get-by-subnet",
+    ///     "arguments": {
+    ///         "subnet-id" : 100
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6GetBySubnetHandler(hooks::CalloutHandle& handle);
+
+    /// @brief Handles a 'sflq-pool6-by-range' command
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-get-by-range",
+    ///     "arguments": {
+    ///         "start-address": "3001::",
+    ///         "end-address": "3001::FFFF"
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6GetByRangeHandler(hooks::CalloutHandle& handle);
+
+    /// @brief Delete the SFLQ V6 pool that matches a start and end address.
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-del",
+    ///     "arguments": {
+    ///         "start-address": "3001::",
+    ///         "end-address": "3001::FFFF",
+    ///         "force": false
+    ///     }
+    /// }
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6DelHandler(hooks::CalloutHandle& handle);
+
+private:
+    /// @brief The 'sflq-poolX-get-all' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-poolX-get-all",
+    ///     "arguments": {
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @param family Protocol family AF_NET or AF_INET6
+    /// @return result of the operation
+    int sflqPoolGetAll(hooks::CalloutHandle& handle, uint16_t family);
+
+    /// @brief Handles a 'sflq-poolX-by-subnet' command
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-poolX-get-by-subnet",
+    ///     "arguments": {
+    ///         "subnet-id" : 100
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @param family Protocol family AF_NET or AF_INET6
+    /// @return result of the operation
+    int sflqPoolGetBySubnet(hooks::CalloutHandle& handle, uint16_t family);
+
+    /// @brief Handles a 'sflq-poolX-by-range' command
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-poolX-get-by-range",
+    ///     "arguments": {
+    ///         "start-address": "192.0.2.0",
+    ///         "end-address": "192.0.2.255"
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @param family Protocol family AF_NET or AF_INET6
+    /// @return result of the operation
+    int sflqPoolGetByRange(hooks::CalloutHandle& handle, uint16_t family);
+
+    /// @brief Handles a 'sflq-poolX-del' command
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-poolX-del",
+    ///     "arguments": {
+    ///         "start-address": "192.0.2.0",
+    ///         "end-address": "192.0.2.255",
+    ///         "force": false
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @param family Protocol family AF_NET or AF_INET6
+    /// @return result of the operation
+    int sflqPoolDel(hooks::CalloutHandle& handle, uint16_t family);
+
+public:
+    /// @brief Creates a success response from a list of SqlPoolInfos.
+    ///
+    /// @param pools list of pools to include in the response. May be empty.
+    ///
+    /// @return API response structure with a result code of CONTROL_RESULT_SUCCESS
+    /// if the list of pools is not empty, otherwise CONTROL_RESULT_EMPTY.
+    static data::ConstElementPtr buildGetResponse(dhcp::SflqPoolInfoCollectionPtr pools);
+
+    /// @brief Extracts an ip address range from given parameters map
+    ///
+    /// Expects the map to contain valid addressess of the the given
+    /// family (AF_INET or AF_INET6), specified as 'start-address' and
+    /// 'end-address' and where the former is less than or equal to the
+    /// latter.
+    ///
+    /// @param params Element map containging the command arguments.
+    /// @param family protocol family (AF_INET or AF_INET6).
+    /// @param[out] start_address IOAddress reference which receives the
+    /// extracted start-address value
+    /// @param[out] end_address IOAddress reference which receives the
+    /// extracted end-address value
+    ///
+    /// @throw BadValue is either parameter is missing, is not a valid
+    /// ip address or they do not constitute a valid range.
+    static void extractRange(data::ConstElementPtr& params, uint8_t family,
+                             asiolink::IOAddress& start_address,
+                             asiolink::IOAddress& end_address);
+
+    /// @brief Extracts a boolean from given parameters map
+    ///
+    /// Extracts a boolean element from the given map and returns its
+    /// value. If the parameter is not found in the map, the function will
+    /// the function will return the default value.
+    ///
+    /// @param params Element map containging the command arguments.
+    /// @param name name of the desired parameter.
+    /// @param default_value value to return if the parameter is optional and
+    /// not specified.
+    ///
+    /// @throw BadValue is either parameter is missing, is not a valid
+    /// ip address or they do not constitute a valid range.
+    static bool extractBool(data::ConstElementPtr& params, const std::string& name,
+                            bool default_value = false);
+
+    /// @brief Extracts 'delegated-len' from given parameters map
+    ///
+    /// Extracts an integer element, 'delegate-len' from the given map
+    /// if it is present and is >= 1 and <= 128. If it is not present
+    /// it returns a value of 128.
+    ///
+    /// @param params Element map containging the command arguments.
+    ///
+    /// @throw BadValue if the parameter is specified as anything other
+    /// than an integer value >= 1 and <= 128.
+    static uint8_t extractDelegatedLen(data::ConstElementPtr& params);
+
+    /// @brief Extracts 'delegated-len' from given parameters map
+    ///
+    /// Extracts a string element, 'lease-type' from the given map
+    /// For v4 it can be either "V4" or "3", for V6 it can be "IA_NA"
+    /// "0", "IA_PD", or "2".
+    ///
+    /// @param params Element map containging the command arguments.
+    /// @param family protocol family (AF_INET or AF_INET6).
+    ///
+    /// @throw BadValue if the parameter is not valid for the protocol
+    /// family.
+    static dhcp::Lease::Type extractLeaseType(data::ConstElementPtr& params,
+                                              uint16_t family);
+};
+
+/// @brief Wrapper class around SFLQ pool commands.
+///
+/// This provides the interfacew through which callouts
+/// execute the commands.
+class SflqCmds {
+public:
+    /// @brief Constructor.
+    ///
+    /// It creates an instance of the @c SflqCmds
+    SflqCmds();
+
+    /// @brief sflq-pool4-create handler.
+    ///
+    /// Invokes LeaseMgr::sflqCreateFlqPool4() after parsing the arguments.
+    /// @brief lease6-resend-ddns command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-create",
+    ///     "arguments": {
+    ///         "start-address": "192.0.2.0",
+    ///         "end-address": "192.0.2.255",
+    ///         "subnet-id": 77,
+    ///         "recreate": true
+    ///     }
+    /// }
+    ///
+    /// The 'recreate' parameter is optional, it defaults false.
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4CreateHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool4-get-all' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-get-all",
+    ///     "arguments": {
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4GetAllHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool4-get-by-subnet' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-get-by-subnet",
+    ///     "arguments": {
+    ///         "subnet-id" : 100
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4GetBySubnetHandler(hooks::CalloutHandle& handle);
+
+    /// @brief Handles a 'sflq-pool4-by-range' command
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-get-by-range",
+    ///     "arguments": {
+    ///         "start-address": "192.0.2.0",
+    ///         "end-address": "192.0.2.255"
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4GetByRangeHandler(hooks::CalloutHandle& handle);
+
+    /// @brief Delete the SFLQ V4 pool that matches a start and end address.
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool4-del",
+    ///     "arguments": {
+    ///         "start-address": "192.0.2.0",
+    ///         "end-address": "192.0.2.255",
+    ///         "force": false
+    ///     }
+    /// }
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool4DelHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool6-create' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-create",
+    ///     "arguments": {
+    ///         "start-address": "3001::",
+    ///         "end-address": "3001::FF",
+    ///         "subnet-id": 77,
+    ///         "lease-type" : "IA_PD",
+    ///         "delegated_len: 64
+    ///         "recreate": true
+    ///     }
+    /// }
+    ///
+    /// The 'delegated-len' is optional, it defaults to 128.
+    /// The 'recreate' parameter is optional, it defaults false.
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6CreateHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool6-get-all' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-get-all",
+    ///     "arguments": {
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6GetAllHandler(hooks::CalloutHandle& handle);
+
+    /// @brief The 'sflq-pool6-get-by-subnet' command handler
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-get-by-subnet",
+    ///     "arguments": {
+    ///         "subnet-id" : 100
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6GetBySubnetHandler(hooks::CalloutHandle& handle);
+
+    /// @brief Handles a 'sflq-pool6-by-range' command
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-get-by-range",
+    ///     "arguments": {
+    ///         "start-address": "3001::",
+    ///         "end-address": "3001::FFFF"
+    ///     }
+    /// }
+    ///
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6GetByRangeHandler(hooks::CalloutHandle& handle);
+
+    /// @brief Delete the SFLQ V6 pool that matches a start and end address.
+    ///
+    /// It extracts the command name and arguments from the given CalloutHandle,
+    /// attempts to process them, and then set's the handle's "response"
+    /// argument accordingly.
+    ///
+    /// An example command with all supported arguments is shown below:
+    /// {
+    ///     "command": "sflq-pool6-del",
+    ///     "arguments": {
+    ///         "start-address": "3001::",
+    ///         "end-address": "3001::FFFF",
+    ///         "force": false
+    ///     }
+    /// }
+    /// @param handle Callout context - which is expected to contain the
+    /// the command JSON text in the "command" argument
+    /// @return result of the operation
+    int sflqPool6DelHandler(hooks::CalloutHandle& handle);
+
+private:
+    /// Pointer to the SFLQ commands implementation
+    boost::shared_ptr<SflqCmdsImpl> sflq_impl_;
+};
+
+};
+};
+#endif
index 99bae1917eec1a06840399d4fe853072325fc72d..a25b0508130f585f99fccadbbf553d18c9628f65 100644 (file)
@@ -411,19 +411,19 @@ IOAddress offsetAddress(const IOAddress& addr, uint128_t offset) {
     return (IOAddress::fromBytes(AF_INET6, &addr_bytes[0]));
 }
 
-void validateV4Range(const IOAddress& start, const IOAddress& end) {
-    if (!start.isV4() || !end.isV4() || end < start) {
+void validateV4Range(const IOAddress& start_address, const IOAddress& end_address) {
+    if (!start_address.isV4() || !end_address.isV4() || end_address < start_address) {
         isc_throw (BadValue, "invalid V4 range - start_address "
-                   << start.toText() << "r, end_address " << end.toText()
-                   << ", must be V4 addresses where start <= end");
+                   << start_address.toText() << ", end_address " << end_address.toText()
+                   << ", must be V4 addresses where start_address <= end_address");
     }
 }
 
-void validateV6Range(const IOAddress& start, const IOAddress& end) {
-    if (!start.isV6() || !end.isV6() || end < start) {
+void validateV6Range(const IOAddress& start_address, const IOAddress& end_address) {
+    if (!start_address.isV6() || !end_address.isV6() || end_address < start_address) {
         isc_throw (BadValue, "invalid V6 range - start_address "
-                   << start.toText() << "r, end_address " << end.toText()
-                   << ", must be V6 addresses where start <= end");
+                   << start_address.toText() << ", end_address " << end_address.toText()
+                   << ", must be V6 addresses where start_address <= end_address");
     }
 }
 
index 4cb0f51e2488236ea039c320d220e23c87c55467..8c9fd85b909c09faba81d573a0bcd1a1ec71640c 100644 (file)
@@ -95,19 +95,19 @@ IOAddress offsetAddress(const IOAddress& addr, isc::util::uint128_t offset);
 
 /// @brief Ensures address pair are both v4 and start <= end
 ///
-/// @param start input start address
-/// @param end input end address
+/// @param start_address start of the range
+/// @param end_address end of the range
 ///
 /// @throw BadValue if either address is not v4 or start > end
-void validateV4Range(const IOAddress& start, const IOAddress& end);
+void validateV4Range(const IOAddress& start_address, const IOAddress& end_address);
 
 /// @brief Ensures address pair are both v6 and start <= end
 ///
-/// @param start input start address
-/// @param end input end address
+/// @param start_address start of the range
+/// @param end_address end of the range
 ///
 /// @throw BadValue if either address is not v6 or start > end
-void validateV6Range(const IOAddress& start, const IOAddress& end);
+void validateV6Range(const IOAddress& start_address, const IOAddress& end_address);
 
 }  // namespace asiolink
 }  // namespace isc
index 670cac00e44114ca8dbc3e32bd87b08d713601ed..5f1ea3d8c84777245b401ddd28e68ebbb5c91b10 100644 (file)
@@ -1974,7 +1974,7 @@ SflqPoolInfo::SflqPoolInfo():
 }
 
 
-data::ConstElementPtr
+data::ElementPtr
 SflqPoolInfo::toElement() const {
     ElementPtr info = Element::createMap();
     info->set("lease-type", Element::create(Lease::typeToText(lease_type_)));
index 5b5dc3d64a6dc904021cc9b34378851c5d20f760..28455ae7113417f51f8c41a948d74c06a676c018 100644 (file)
@@ -235,6 +235,11 @@ typedef boost::shared_ptr<LeaseStatsQuery> LeaseStatsQueryPtr;
 /// @brief Defines a pointer to a LeaseStatsRow.
 typedef boost::shared_ptr<LeaseStatsRow> LeaseStatsRowPtr;
 
+// Forward declaration.
+class SflqPoolInfo;
+/// @brief A pointer to a SFLQPoolInfo instance.
+typedef boost::shared_ptr<SflqPoolInfo> SflqPoolInfoPtr;
+
 /// @brief Describes a SFLQ pool.
 class SflqPoolInfo {
 public:
@@ -251,12 +256,9 @@ public:
     boost::posix_time::ptime created_ts_;
     boost::posix_time::ptime modified_ts_;
 
-    data::ConstElementPtr toElement() const;
+    data::ElementPtr toElement() const;
 };
 
-/// @brief A pointer to a SFLQPoolInfo instance.
-typedef boost::shared_ptr<SflqPoolInfo> SflqPoolInfoPtr;
-
 /// @brief A collection of SFLQPoolInfo structures.
 typedef std::vector<SflqPoolInfoPtr> SflqPoolInfoCollection;
 typedef boost::shared_ptr<SflqPoolInfoCollection> SflqPoolInfoCollectionPtr;
index 3c4f591ab76e2d8bda70595066a4826e3dbf2cc1..a833143be4b185083d92f3ec8da88de6c088ad56 100644 (file)
@@ -65,6 +65,20 @@ SflqPool::popFreeAddress() {
     return (free_address);
 }
 
+SflqPoolInfoPtr
+SflqPool::toSflqPoolInfo() const {
+    SflqPoolInfoPtr pi(new SflqPoolInfo());
+    pi->lease_type_ = lease_type_;
+    pi->start_address_ = start_address_;
+    pi->end_address_ = end_address_;
+    pi->delegated_len_ = delegated_len_;
+    pi->subnet_id_ = subnet_id_;
+    pi->free_leases_ = free_addresses_.size();;
+    pi->created_ts_ = boost::posix_time::second_clock::local_time();
+    pi->modified_ts_ = pi->created_ts_;
+    return (pi);
+}
+
 TrackingLeaseMgrPtr
 SflqTestLeaseMgr::factory(const DatabaseConnection::ParameterMap& params) {
     return (TrackingLeaseMgrPtr(new SflqTestLeaseMgr(params)));
@@ -81,8 +95,13 @@ bool
 SflqTestLeaseMgr::sflqCreateFlqPool4(IOAddress start_address, IOAddress end_address,
                                      SubnetID subnet_id, bool recreate) {
     auto sflq_pool = findPool(start_address, end_address);
-    if (sflq_pool && recreate) {
-        sflq_pool->repopulateFreeLeases();
+    if (sflq_pool) {
+        if (recreate) {
+            sflq_pool->repopulateFreeLeases();
+            return (true);
+        }
+
+        return (false);
     }
 
     // Create the pool and add it to the list of pools.
@@ -106,8 +125,13 @@ SflqTestLeaseMgr::sflqCreateFlqPool6(IOAddress start_address, IOAddress end_addr
                                      Lease::Type lease_type, uint8_t delegated_len,
                                      SubnetID subnet_id, bool recreate) {
     auto sflq_pool = findPool(start_address, end_address);
-    if (sflq_pool && recreate) {
-        sflq_pool->repopulateFreeLeases();
+    if (sflq_pool) {
+        if (recreate) {
+            sflq_pool->repopulateFreeLeases();
+            return (true);
+        }
+
+        return (false);
     }
 
     // Create the pool and add it to the list of pools.
@@ -151,6 +175,114 @@ SflqTestLeaseMgr::getType() const {
     return (std::string("sflqtest"));
 }
 
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool4GetAll() {
+    SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+    for (auto pool : sflq_pools_) {
+        pools->push_back(pool->toSflqPoolInfo());
+    }
+
+    return (pools);
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool4Get(SubnetID subnet_id) {
+    SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+    for (auto pool : sflq_pools_) {
+        if (pool->subnet_id_ == subnet_id) {
+            pools->push_back(pool->toSflqPoolInfo());
+        }
+    }
+
+    return (pools);
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool4Get(IOAddress start_address, IOAddress end_address) {
+    SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+    for (auto pool : sflq_pools_) {
+        if ((pool->start_address_ <= start_address && start_address <= pool->end_address_) ||
+            (pool->start_address_ <= end_address && end_address <= pool->end_address_) ||
+            (start_address < pool->start_address_ && pool->end_address_ < end_address))
+            pools->push_back(pool->toSflqPoolInfo());
+    }
+
+    return (pools);
+}
+
+bool
+SflqTestLeaseMgr::sflqPool4Del(IOAddress start_address, IOAddress end_address,
+                               bool force /* = false */) {
+    return(sflqPoolDel(start_address, end_address, force));
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool6GetAll() {
+    SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+    for (auto pool : sflq_pools_) {
+        pools->push_back(pool->toSflqPoolInfo());
+    }
+
+    return (pools);
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool6Get(SubnetID subnet_id) {
+    SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+    for (auto pool : sflq_pools_) {
+        if (pool->subnet_id_ == subnet_id) {
+            pools->push_back(pool->toSflqPoolInfo());
+        }
+    }
+
+    return (pools);
+}
+
+SflqPoolInfoCollectionPtr
+SflqTestLeaseMgr::sflqPool6Get(IOAddress start_address, IOAddress end_address) {
+    SflqPoolInfoCollectionPtr pools(new SflqPoolInfoCollection());
+    for (auto pool : sflq_pools_) {
+        if ((pool->start_address_ <= start_address && start_address <= pool->end_address_) ||
+            (pool->start_address_ <= end_address && end_address <= pool->end_address_) ||
+            (start_address < pool->start_address_ && pool->end_address_ < end_address))
+            pools->push_back(pool->toSflqPoolInfo());
+    }
+
+    return (pools);
+}
+
+bool
+SflqTestLeaseMgr::sflqPool6Del(IOAddress start_address, IOAddress end_address,
+                               bool force /* = false */) {
+    return(sflqPoolDel(start_address, end_address, force));
+}
+
+bool
+SflqTestLeaseMgr::sflqPoolDel(IOAddress start_address, IOAddress end_address,
+                              bool force) {
+    auto pools = sflqPool6Get(start_address, end_address);
+    if (pools->size() == 0) {
+        return (false);
+    }
+
+    if (pools->size() > 1 && force == false) {
+        // Overlapping pools, warn and bail.
+        isc_throw(InvalidOperation, "Delete would affect "
+                  << pools->size() << " overlapping pools");
+    }
+
+    auto pool = sflq_pools_.begin();
+    while (pool != sflq_pools_.end()) {
+        if ((*pool)->start_address_ == start_address &&
+            (*pool)->end_address_ == end_address) {
+            sflq_pools_.erase(pool);
+            return (true);
+        }
+    }
+
+    return (false);
+}
+
 } // end of namespace isc::dhcp::test
 } // end of namespace isc::dhcp
 } // end of namespace isc
index 22a4259b7b7c6daa57918eadf3ca22e2b34d2435..c41cebeb998d1c19bcf9b5db467f14cfdffeab5f 100644 (file)
@@ -36,7 +36,7 @@ struct SflqPool {
              asiolink::IOAddress end_address_,
              SubnetID subnet_id_,
              Lease::Type lease_type = Lease::TYPE_V4,
-             uint8_t delegated_len  = 1);
+             uint8_t delegated_len  = 128);
 
     /// @brief Destructor.
     ~SflqPool(){};
@@ -57,6 +57,15 @@ struct SflqPool {
     /// IPV6_ZERO_ADDRESS() otherwise.
     asiolink::IOAddress zeroAddress();
 
+    /// @brief Creates an SflqPoolInfo instance from this pool.
+    ///
+    /// SflqPoolInfo is the pool representaion returned by SFLQ API
+    /// "get" functions. The SflqPoolInfo timestamp members are set
+    /// to the current time.
+    ///
+    /// @return A pointer to a new SflqPoolInfo instance.
+    SflqPoolInfoPtr toSflqPoolInfo() const;
+
     /// @brief First address in the pool.
     asiolink::IOAddress start_address_;
 
@@ -156,6 +165,90 @@ public:
     virtual asiolink::IOAddress sflqPickFreeLease6(asiolink::IOAddress start_address,
                                                    asiolink::IOAddress end_address)
                                                    override;
+    /// @brief Fetch all SFLQ V4 pools.
+    ///
+    /// @return A collection of the SFLQ V4 pools.
+    virtual SflqPoolInfoCollectionPtr sflqPool4GetAll() override;
+
+    /// @brief Fetch all SFLQ V4 pools belonging to a subnet.
+    ///
+    /// @param subnet_id id of the desired subnet.
+    ///
+    /// @return A collection of the SFLQ V4 pools.
+    virtual SflqPoolInfoCollectionPtr sflqPool4Get(SubnetID subnet_id) override;
+
+    /// @brief Fetch all SFLQ V4 pools that overlap a range
+    ///
+    /// @param start_address start of ip address range.
+    /// @param end_address end of ip address range.
+    ///
+    /// @return A collection of the SFLQ V4 pools.
+    virtual SflqPoolInfoCollectionPtr sflqPool4Get(asiolink::IOAddress start_address,
+                                                   asiolink::IOAddress end_address)
+                                                   override;
+
+    /// @brief Delete the SFLQ V4 pool that matches a start and end address.
+    ///
+    /// @param start_address start address of the pool to delete.
+    /// @param end_address end address of the pool to delete.
+    /// @param force overrides check for overlapping pools when true. Defaults
+    /// to false.
+    ///
+    /// @return True a pool was deleted.
+    /// @throw InvalidOperation if force is false and overlapping pools are
+    /// detected.
+    virtual bool sflqPool4Del(asiolink::IOAddress start_address,
+                              asiolink::IOAddress end_address,
+                              bool force = false) override;
+
+    /// @brief Fetch all SFLQ V6 pools.
+    ///
+    /// @return A collection of the SFLQ V6 pools.
+    virtual SflqPoolInfoCollectionPtr sflqPool6GetAll() override;
+
+    /// @brief Fetch all SFLQ V6 pools belonging to a subnet.
+    ///
+    /// @param subnet_id id of the desired subnet.
+    ///
+    /// @return A collection of the SFLQ V6 pools.
+    virtual SflqPoolInfoCollectionPtr sflqPool6Get(SubnetID subnet_id) override;
+
+    /// @brief Fetch all SFLQ V6 pools that overlap a range
+    ///
+    /// @param start_address start of ip address range.
+    /// @param end_address end of ip address range.
+    ///
+    /// @return A collection of the SFLQ V6 pools.
+    virtual SflqPoolInfoCollectionPtr sflqPool6Get(asiolink::IOAddress start_address,
+                                                   asiolink::IOAddress end_address)
+                                                   override;
+
+    /// @brief Delete the SFLQ V6 pool that matches a start and end address.
+    ///
+    /// @param start_address start address of the pool to delete.
+    /// @param end_address end address of the pool to delete.
+    /// @param force overrides check for overlapping pools when true. Defaults
+    /// to false.
+    ///
+    /// @return True a pool was deleted.
+    /// @throw InvalidOperation if force is false and overlapping pools are
+    /// detected.
+    virtual bool sflqPool6Del(asiolink::IOAddress start_address,
+                              asiolink::IOAddress end_address,
+                              bool force = false) override;
+
+    /// @brief Delete the SFLQ pool that matches a start and end address.
+    ///
+    /// @param start_address start address of the pool to delete.
+    /// @param end_address end address of the pool to delete.
+    /// @param force overrides check for overlapping pools when true.
+    ///
+    /// @return True a pool was deleted.
+    /// @throw InvalidOperation if force is false and overlapping pools are
+    /// detected.
+    bool sflqPoolDel(asiolink::IOAddress start_address,
+                     asiolink::IOAddress end_address,
+                     bool force);
 
     /// @brief Finds an SflqPool in the list of SflqPools
     ///
diff --git a/src/share/api/sflq-pool4-create.json b/src/share/api/sflq-pool4-create.json
new file mode 100644 (file)
index 0000000..13a4e1f
--- /dev/null
@@ -0,0 +1,30 @@
+{
+    "access": "write",
+    "avail": "3.2.0",
+    "brief": [
+        "This command requests the lease back end to a v4 SFLQ pool and its free lease data."
+    ],
+    "cmd-syntax": [
+        "{",
+        "    \"command\": \"sflq-pool4-create\",",
+        "    \"arguments\": {",
+        "       \"start-address\": \"1.2.3.4\",",  
+        "       \"end-address\": \"1.2.3.4\",",  
+        "       \"subnet-id\": 123",
+        "        } ]",
+        "    }",
+        "}"
+    ],
+    "description": "See <xref linkend=\"idp69\"/>",
+    "hook": "lease_cmds",
+    "name": "sflq-pool4-create",
+    "resp-syntax": [
+        "{",
+        "    \"result\": 0,",
+        "    \"text\": \"SFLQ pool created.\"",
+        "}"
+    ],
+    "support": [
+        "kea-dhcp4"
+    ]
+}
diff --git a/src/share/api/sflq-pool4-get-all.json b/src/share/api/sflq-pool4-get-all.json
new file mode 100644 (file)
index 0000000..929040a
--- /dev/null
@@ -0,0 +1,37 @@
+{
+    "access": "write",
+    "avail": "3.2.0",
+    "brief": [
+        "This command fetches a list of all V4 SFLQ pools from the lease back end."
+    ],
+    "cmd-syntax": [
+        "{",
+        "    \"command\": \"sflq-pool4-get-all\"",
+        "}"
+    ],
+    "description": "See <xref linkend=\"idp69\"/>",
+    "hook": "lease_cmds",
+    "name": "sflq-pool4-get-all",
+    "resp-syntax": [
+        "{",
+        "\"arguments\": {",
+        "   \"pools\": [",
+        "       {",
+        "            \"created-ts\": \"2026-05-02 10:54:55.000000\",",
+        "            \"delegated-len\": 128,",
+        "            \"end-address\": \"192.0.2.255\",",
+        "            \"free-leases\": 256,",
+        "            \"lease-type\": \"V4\",",
+        "            \"modified-ts\": \"2026-05-02 10:54:55.000000\",",
+        "            \"start-address\": \"192.0.2.0\",",
+        "            \"subnet-id\": 6",
+        "       }",
+        "]]",
+        \"result\": 0,",
+        \"text\": \"1 pool(s) found.\"",
+        "}"
+    ],
+    "support": [
+        "kea-dhcp4"
+    ]
+}