]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2764] Tracking lease manager
authorMarcin Siodelski <marcin@isc.org>
Mon, 20 Feb 2023 14:34:54 +0000 (15:34 +0100)
committerMarcin Siodelski <msiodelski@gmail.com>
Tue, 14 Mar 2023 18:23:31 +0000 (19:23 +0100)
The new lease manager layer implements callback mechanism. The callbacks
can be registered and are invoked when the leases are modified.

src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/dhcpsrv_messages.h
src/lib/dhcpsrv/dhcpsrv_messages.mes
src/lib/dhcpsrv/tests/Makefile.am
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
src/lib/dhcpsrv/tests/tracking_lease_mgr_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/testutils/Makefile.am
src/lib/dhcpsrv/testutils/concrete_lease_mgr.cc [new file with mode: 0644]
src/lib/dhcpsrv/testutils/concrete_lease_mgr.h [new file with mode: 0644]
src/lib/dhcpsrv/tracking_lease_mgr.cc [new file with mode: 0644]
src/lib/dhcpsrv/tracking_lease_mgr.h [new file with mode: 0644]

index 4639dc1f5c0279c95bb6021bda6dae90ccbd9c96..a314ea13d394a5654a53010f6eceb65067bb109f 100644 (file)
@@ -153,6 +153,7 @@ libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libkea_dhcpsrv_la_SOURCES += subnet_id.h
 libkea_dhcpsrv_la_SOURCES += subnet_selector.h
 libkea_dhcpsrv_la_SOURCES += timer_mgr.cc timer_mgr.h
+libkea_dhcpsrv_la_SOURCES += tracking_lease_mgr.cc tracking_lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += utils.h
 libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h
 
@@ -363,6 +364,7 @@ libkea_dhcpsrv_include_HEADERS = \
        ncr_generator.h \
        network.h \
        network_state.h \
+       tracking_lease_mgr.h \
        pool.h \
        random_allocation_state.h \
        random_allocator.h \
index b5905a374309f71064957458f37f1e9bdff4e8e1..637732f4fddf0141e3eac2b69d94b4d039940a4d 100644 (file)
@@ -70,6 +70,8 @@ extern const isc::log::MessageID DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL;
 extern const isc::log::MessageID DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED;
 extern const isc::log::MessageID DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL;
 extern const isc::log::MessageID DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED;
+extern const isc::log::MessageID DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION;
+extern const isc::log::MessageID DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION;
 extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FAIL;
 extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FAIL_DISCARD;
 extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FIXED;
index 368049d322dfb055a3c7d362db799164461edada..7a8226eee898215a03c8c9a92efaaa52476a78d1 100644 (file)
@@ -349,6 +349,25 @@ and the attempt ended in error. The access string in question - which
 should be of the form 'keyword=value keyword=value...' is included in
 the message.
 
+% DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION exception occurred in a lease manager callback for callback type %1, subnet id %2, and lease %3: %4
+This warning message is printed when one of the callback functions registered
+in the lease manager causes an error. The callback functions can serve
+different purposes and they likely log the detailed error messages. This
+error message possibly indicates an unhandled error. The first argument
+indicates a callback type. The second argument prints the subnet id.
+The third argument prints the lease for which the error has occurred.
+The last argument prints the error text.
+
+% DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION unknown exception occurred in a lease manager callback for callback type %1, subnet id %2, and lease %3
+This warning message is printed when one of the callback functions registered
+in the lease manager causes an unknown error. The callback functions can serve
+different purposes and they likely log the detailed error messages. This
+error message possibly indicates an unhandled error. The first argument
+indicates a callback type. The second argument prints the subnet id.
+The third argument prints the lease for which the error has occurred.
+This log message variant contains no error text because it is triggered
+by an unknown exception.
+
 % DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL extended info for lease %1 failed checks (%2)
 This error message is printed when a lease extended info failed to
 pass sanity checks. The detail of the found problem was displayed and
index da833009d1626da471af7836c2fd929d8497e2ef..f7228441baee75a3fa967cd9b4acfc74560fcc86 100644 (file)
@@ -133,6 +133,7 @@ libdhcpsrv_unittests_SOURCES += srv_config_unittest.cc
 libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
 libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h
 libdhcpsrv_unittests_SOURCES += timer_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += tracking_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += network_state_unittest.cc
 libdhcpsrv_unittests_SOURCES += network_unittest.cc
 
index b74bb9d10aaaf37346f5306b6e5f0c05aa039a1f..54c9332b9540ca5105c5dce7d01c1c237cf329b7 100644 (file)
@@ -10,6 +10,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcpsrv/testutils/test_utils.h>
+#include <dhcpsrv/testutils/concrete_lease_mgr.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 #include <testutils/gtest_utils.h>
 
@@ -27,541 +28,6 @@ using namespace isc::db;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 
-// This is a concrete implementation of a Lease database.  It does not do
-// anything useful and is used for abstract LeaseMgr class testing.
-class ConcreteLeaseMgr : public LeaseMgr {
-public:
-
-    /// @brief The sole lease manager constructor
-    ///
-    /// dbconfig is a generic way of passing parameters. Parameters
-    /// are passed in the "name=value" format, separated by spaces.
-    /// Values may be enclosed in double quotes, if needed.
-    ConcreteLeaseMgr(const DatabaseConnection::ParameterMap&)
-        : LeaseMgr()
-    {}
-
-    /// @brief Destructor
-    virtual ~ConcreteLeaseMgr()
-    {}
-
-    /// @brief Adds an IPv4 lease.
-    ///
-    /// @param lease lease to be added
-    virtual bool addLease(const Lease4Ptr&) override {
-        return (false);
-    }
-
-    /// @brief Adds an IPv6 lease.
-    ///
-    /// @param lease lease to be added
-    virtual bool addLease(const Lease6Ptr&) override {
-        return (false);
-    }
-
-    /// @brief Returns existing IPv4 lease for specified IPv4 address.
-    ///
-    /// @param addr address of the searched lease
-    ///
-    /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress&) const override {
-        return (Lease4Ptr());
-    }
-
-    /// @brief Returns existing IPv4 leases for specified hardware address.
-    ///
-    /// Although in the usual case there will be only one lease, for mobile
-    /// clients or clients with multiple static/fixed/reserved leases there
-    /// can be more than one. Thus return type is a container, not a single
-    /// pointer.
-    ///
-    /// @param hwaddr hardware address of the client
-    ///
-    /// @return lease collection
-    virtual Lease4Collection getLease4(const HWAddr&) const override {
-        return (Lease4Collection());
-    }
-
-    /// @brief Returns existing IPv4 leases for specified hardware address
-    ///        and a subnet
-    ///
-    /// There can be at most one lease for a given HW address in a single
-    /// pool, so this method with either return a single lease or NULL.
-    ///
-    /// @param hwaddr hardware address of the client
-    /// @param subnet_id identifier of the subnet that lease must belong to
-    ///
-    /// @return a pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(const HWAddr&, SubnetID) const override {
-        return (Lease4Ptr());
-    }
-
-    /// @brief Returns existing IPv4 lease for specified client-id
-    ///
-    /// @param clientid client identifier
-    ///
-    /// @return lease collection
-    virtual Lease4Collection getLease4(const ClientId&) const override {
-        return (Lease4Collection());
-    }
-
-    /// @brief Returns existing IPv4 lease for specified client-id
-    ///
-    /// There can be at most one lease for a given HW address in a single
-    /// pool, so this method with either return a single lease or NULL.
-    ///
-    /// @param clientid client identifier
-    /// @param subnet_id identifier of the subnet that lease must belong to
-    ///
-    /// @return a pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(const ClientId&, SubnetID) const override {
-        return (Lease4Ptr());
-    }
-
-    /// @brief Returns all IPv4 leases for the particular subnet identifier.
-    ///
-    /// @param subnet_id subnet identifier.
-    ///
-    /// @return Lease collection (may be empty if no IPv4 lease found).
-    virtual Lease4Collection getLeases4(SubnetID) const override {
-        return (Lease4Collection());
-    }
-
-    /// @brief Returns all IPv4 leases for the particular hostname.
-    ///
-    /// @param hostname hostname in lower case.
-    ///
-    /// @return Lease collection (may be empty if no IPv4 lease found).
-    virtual Lease4Collection getLeases4(const std::string&) const override {
-        return (Lease4Collection());
-    }
-
-    /// @brief Returns all IPv4 leases.
-    ///
-    /// @return Lease collection (may be empty if no IPv4 lease found).
-    virtual Lease4Collection getLeases4() const override {
-        return (Lease4Collection());
-    }
-
-    /// @brief Returns range of IPv4 leases using paging.
-    ///
-    /// This method implements paged browsing of the lease database. The first
-    /// parameter specifies a page size. The second parameter is optional and
-    /// specifies the starting address of the range. This address is excluded
-    /// from the returned range. The IPv4 zero address (default) denotes that
-    /// the first page should be returned. There is no guarantee about the
-    /// order of returned leases.
-    ///
-    /// The typical usage of this method is as follows:
-    /// - Get the first page of leases by specifying IPv4 zero address as the
-    ///   beginning of the range.
-    /// - Last address of the returned range should be used as a starting
-    ///   address for the next page in the subsequent call.
-    /// - If the number of leases returned is lower than the page size, it
-    ///   indicates that the last page has been retrieved.
-    /// - If there are no leases returned it indicates that the previous page
-    ///   was the last page.
-    ///
-    /// @param lower_bound_address IPv4 address used as lower bound for the
-    /// returned range.
-    /// @param page_size maximum size of the page returned.
-    ///
-    /// @return Lease collection (may be empty if no IPv4 lease found).
-    virtual Lease4Collection
-    getLeases4(const asiolink::IOAddress& /* lower_bound_address */,
-               const LeasePageSize& /* page_size */) const override {
-        return (Lease4Collection());
-    }
-
-    /// @brief Returns existing IPv6 lease for a given IPv6 address.
-    ///
-    /// @param addr address of the searched lease
-    ///
-    /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease6Ptr getLease6(Lease::Type /* not used yet */,
-                                const isc::asiolink::IOAddress&) const override {
-        return (Lease6Ptr());
-    }
-
-    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
-    ///
-    /// @param duid ignored
-    /// @param iaid ignored
-    ///
-    /// @return whatever is set in leases6_ field
-    virtual Lease6Collection getLeases6(Lease::Type /* not used yet */,
-                                        const DUID&, uint32_t) const override {
-        return (leases6_);
-    }
-
-    /// @brief Returns existing IPv6 lease for a given DUID+IA+subnet-id combination
-    ///
-    /// @param duid ignored
-    /// @param iaid ignored
-    /// @param subnet_id ignored
-    ///
-    /// @return whatever is set in leases6_ field
-    virtual Lease6Collection getLeases6(Lease::Type /* not used yet */,
-                                        const DUID&, uint32_t, SubnetID) const override {
-        return (leases6_);
-    }
-
-    /// @brief Returns collection of lease for matching DUID
-    ///
-    /// @param duid ignored
-    /// @return whatever is set in leases6_ field
-    virtual Lease6Collection getLeases6(const DUID&) const override {
-        return (leases6_);
-    }
-
-    /// @brief Returns all IPv6 leases for the particular subnet identifier.
-    ///
-    /// @param subnet_id subnet identifier.
-    ///
-    /// @return Lease collection (may be empty if no IPv6 lease found).
-    virtual Lease6Collection getLeases6(SubnetID) const override {
-        return (Lease6Collection());
-    }
-
-    /// @brief Returns all IPv6 leases for the particular hostname.
-    ///
-    /// @param hostname hostname in lower case.
-    ///
-    /// @return Lease collection (may be empty if no IPv6 lease found).
-    virtual Lease6Collection getLeases6(const std::string&) const override {
-        return (Lease6Collection());
-    }
-
-    /// @brief Returns all IPv6 leases.
-    ///
-    /// @return Lease collection (may be empty if no IPv6 lease found).
-    virtual Lease6Collection getLeases6() const override {
-        return (Lease6Collection());
-    }
-
-    /// @brief Returns range of IPv6 leases using paging.
-    ///
-    /// This method implements paged browsing of the lease database. The first
-    /// parameter specifies a page size. The second parameter is optional and
-    /// specifies the starting address of the range. This address is excluded
-    /// from the returned range. The IPv6 zero address (default) denotes that
-    /// the first page should be returned. There is no guarantee about the
-    /// order of returned leases.
-    ///
-    /// The typical usage of this method is as follows:
-    /// - Get the first page of leases by specifying IPv6 zero address as the
-    ///   beginning of the range.
-    /// - Last address of the returned range should be used as a starting
-    ///   address for the next page in the subsequent call.
-    /// - If the number of leases returned is lower than the page size, it
-    ///   indicates that the last page has been retrieved.
-    /// - If there are no leases returned it indicates that the previous page
-    ///   was the last page.
-    ///
-    /// @param lower_bound_address IPv4 address used as lower bound for the
-    /// returned range.
-    /// @param page_size maximum size of the page returned.
-    ///
-    /// @return Lease collection (may be empty if no IPv6 lease found).
-    virtual Lease6Collection
-    getLeases6(const asiolink::IOAddress& /* lower_bound_address */,
-               const LeasePageSize& /* page_size */) const override {
-        return (Lease6Collection());
-    };
-
-    /// @brief Returns expired DHCPv6 leases.
-    ///
-    /// This method is not implemented.
-    virtual void getExpiredLeases6(Lease6Collection&, const size_t) const override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::getExpiredLeases6 is not"
-                  " implemented");
-    }
-
-    /// @brief Returns expired DHCPv4 leases.
-    ///
-    /// This method is not implemented.
-    virtual void getExpiredLeases4(Lease4Collection&, const size_t) const override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::getExpiredLeases4 is not"
-                  " implemented");
-    }
-
-    /// @brief Updates IPv4 lease.
-    ///
-    /// @param lease4 The lease to be updated.
-    ///
-    /// If no such lease is present, an exception will be thrown.
-    virtual void updateLease4(const Lease4Ptr&) override {}
-
-    /// @brief Updates IPv4 lease.
-    ///
-    /// @param lease4 The lease to be updated.
-    ///
-    /// If no such lease is present, an exception will be thrown.
-    virtual void updateLease6(const Lease6Ptr&) override {}
-
-    /// @brief Deletes an IPv4 lease.
-    ///
-    /// @param lease IPv4 lease to be deleted.
-    ///
-    /// @return true if deletion was successful, false if no such lease exists.
-    virtual bool deleteLease(const Lease4Ptr&) override {
-        return (false);
-    }
-
-    /// @brief Deletes an IPv6 lease.
-    ///
-    /// @param lease IPv6 lease to be deleted.
-    ///
-    /// @return true if deletion was successful, false if no such lease exists.
-    virtual bool deleteLease(const Lease6Ptr&) override {
-        return (false);
-    }
-
-    /// @brief Deletes all expired and reclaimed DHCPv4 leases.
-    ///
-    /// @param secs Number of seconds since expiration of leases before
-    /// they can be removed. Leases which have expired later than this
-    /// time will not be deleted.
-    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::deleteExpiredReclaimedLeases4"
-                  " is not implemented");
-    }
-
-    /// @brief Deletes all expired and reclaimed DHCPv6 leases.
-    ///
-    /// @param secs Number of seconds since expiration of leases before
-    /// they can be removed. Leases which have expired later than this
-    /// time will not be deleted.
-    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::deleteExpiredReclaimedLeases6"
-                  " is not implemented");
-    }
-
-    /// @brief Pretends to wipe all IPv4 leases from a subnet
-    /// @param subnet_id (ignored, but one day may specify the subnet)
-    virtual size_t wipeLeases4(const SubnetID&) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::wipeLeases4 not implemented");
-    }
-
-    /// @brief Pretends to wipe all IPv4 leases from a subnet
-    /// @param subnet_id (ignored, but one day may specify the subnet)
-    virtual size_t wipeLeases6(const SubnetID&) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::wipeLeases6 not implemented");
-    }
-
-    /// @brief Pretends to check if the IPv4 lease limits set in the given user
-    /// context are exceeded.
-    virtual std::string
-    checkLimits4(isc::data::ConstElementPtr const& /* user_context */) const override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits4() not implemented");
-    }
-
-    /// @brief Pretends to check if the IPv6 lease limits set in the given user
-    /// context are exceeded.
-    virtual std::string
-    checkLimits6(isc::data::ConstElementPtr const& /* user_context */) const override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits6() not implemented");
-    }
-
-    /// @brief Pretends to check if JSON support is enabled in the database.
-    ///
-    /// @return true if there is JSON support, false otherwise
-    virtual bool isJsonSupported() const override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::isJsonSupported() not implemented");
-    }
-
-    /// @brief Pretends to return the class lease count for a given class and lease type.
-    ///
-    /// @param client_class client class for which the count is desired
-    /// @param ltype type of lease for which the count is desired. Defaults to
-    /// Lease::TYPE_V4.
-    ///
-    /// @return number of leases
-    virtual size_t getClassLeaseCount(const ClientClass& /* client_class */,
-                              const Lease::Type& /* ltype = Lease::TYPE_V4 */) const override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::getClassLeaseCount() not implemented");
-    }
-
-    /// @brief Pretends to recount the leases per class for V4 leases.
-    virtual void recountClassLeases4() override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::recountClassLeases4() not implemented");
-    }
-
-    /// @brief Pretends to recount the leases per class for V6 leases.
-    virtual void recountClassLeases6() override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::recountClassLeases6() not implemented");
-    }
-
-    /// @brief Pretends to clear the class-lease count map.
-    virtual void clearClassLeaseCounts() override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::clearClassLeaseCounts() not implemented");
-    }
-
-    /// @brief Import addExtendedInfo6.
-    using LeaseMgr::addExtendedInfo6;
-
-    /// @brief Delete lease6 extended info from tables.
-    ///
-    /// @param addr The address of the lease.
-    void
-    deleteExtendedInfo6(const IOAddress& addr) override {
-        auto relay_id_it = relay_id6_.begin();
-        while (relay_id_it != relay_id6_.end()) {
-            if ((*relay_id_it)->lease_addr_ == addr) {
-                relay_id_it = relay_id6_.erase(relay_id_it);
-            } else {
-                ++relay_id_it;
-            }
-        }
-        auto remote_id_it = remote_id6_.begin();
-        while (remote_id_it != remote_id6_.end()) {
-            if ((*remote_id_it)->lease_addr_ == addr) {
-                remote_id_it = remote_id6_.erase(remote_id_it);
-            } else {
-                ++remote_id_it;
-            }
-        }
-    }
-
-    /// @brief Add lease6 extended info into by-relay-id table.
-    ///
-    /// @param lease_addr The address of the lease.
-    /// @param relay_id The relay id from the relay header options.
-    void
-    addRelayId6(const IOAddress& lease_addr,
-                const vector<uint8_t>& relay_id) override {
-        Lease6ExtendedInfoPtr ex_info;
-        ex_info.reset(new Lease6ExtendedInfo(lease_addr, relay_id));
-        relay_id6_.push_back(ex_info);
-    }
-
-    /// @brief Add lease6 extended info into by-remote-id table.
-    ///
-    /// @param lease_addr The address of the lease.
-    void
-    addRemoteId6(const IOAddress& lease_addr,
-                 const vector<uint8_t>& remote_id) override {
-        Lease6ExtendedInfoPtr ex_info;
-        ex_info.reset(new Lease6ExtendedInfo(lease_addr, remote_id));
-        remote_id6_.push_back(ex_info);
-    }
-
-    /// @brief Stub implementation.
-    Lease4Collection
-    getLeases4ByRelayId(const OptionBuffer& /* relay_id */,
-                        const IOAddress& /* lower_bound_address */,
-                        const LeasePageSize& /* page_size */,
-                        const time_t& /* qry_start_time = 0 */,
-                        const time_t& /* qry_end_time = 0 */) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases4ByRelayId not implemented");
-    }
-
-    /// @brief Stub implementation.
-    Lease4Collection
-    getLeases4ByRemoteId(const OptionBuffer& /* remote_id */,
-                         const IOAddress& /* lower_bound_address */,
-                         const LeasePageSize& /* page_size */,
-                         const time_t& /* qry_start_time = 0 */,
-                         const time_t& /* qry_end_time = 0 */) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases4ByRemoteId not implemented");
-    }
-
-    /// @brief Stub implementation.
-    Lease6Collection
-    getLeases6ByRelayId(const DUID& /* relay_id */,
-                        const IOAddress& /* link_addr */,
-                        uint8_t /* link_len */,
-                        const IOAddress& /* lower_bound_address */,
-                        const LeasePageSize& /* page_size */) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByRelayId not implemented");
-    }
-
-    /// @brief Stub implementation.
-    Lease6Collection
-    getLeases6ByRemoteId(const OptionBuffer& /* remote_id */,
-                         const IOAddress& /* link_addr */,
-                         uint8_t /* link_len */,
-                         const IOAddress& /* lower_bound_address */,
-                         const LeasePageSize& /* page_size*/) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByRemoteId not implemented");
-    }
-
-    /// @brief Stub implementation.
-    Lease6Collection
-    getLeases6ByLink(const IOAddress& /* link_addr */,
-                     uint8_t /* link_len */,
-                     const IOAddress& /* lower_bound_address */,
-                     const LeasePageSize& /* page_size */) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByLink not implemented");
-    }
-
-    /// @brief Stub implementation.
-    virtual size_t buildExtendedInfoTables6(bool /* update */,
-                                            bool /* current */) override {
-        isc_throw(isc::NotImplemented, "ConcreteLeaseMgr:buildExtendedInfoTables6 not implemented");
-    }
-
-    ///  @brief Pretends to write V4 leases to a file.
-    virtual void writeLeases4(const std::string&) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::writeLeases4() not implemented");
-    }
-
-    ///  @brief Pretends to write V6 leases to a file.
-    virtual void writeLeases6(const std::string&) override {
-        isc_throw(NotImplemented, "ConcreteLeaseMgr::writeLeases6() not implemented");
-    }
-
-    /// @brief Returns backend type.
-    ///
-    /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
-    ///
-    /// @return Type of the backend.
-    virtual std::string getType() const override {
-        return (std::string("concrete"));
-    }
-
-    /// @brief Returns backend name.
-    ///
-    /// If the backend is a database, this is the name of the database or the
-    /// file.  Otherwise it is just the same as the type.
-    ///
-    /// @return Name of the backend.
-    virtual std::string getName() const override {
-        return (std::string("concrete"));
-    }
-
-    /// @brief Returns description of the backend.
-    ///
-    /// This description may be multiline text that describes the backend.
-    ///
-    /// @return Description of the backend.
-    virtual std::string getDescription() const override {
-        return (std::string("This is a dummy concrete backend implementation."));
-    }
-
-    /// @brief Returns backend version.
-    virtual std::pair<uint32_t, uint32_t> getVersion() const override {
-        return (make_pair(uint32_t(0), uint32_t(0)));
-    }
-
-    /// @brief Commit transactions
-    virtual void commit() override {
-    }
-
-    /// @brief Rollback transactions
-    virtual void rollback() override {
-    }
-
-    // We need to use it in ConcreteLeaseMgr
-    using LeaseMgr::getLease6;
-
-    Lease6Collection leases6_; ///< getLease6 methods return this as is
-
-    // List supports easier erase.
-    list<Lease6ExtendedInfoPtr> relay_id6_;
-    list<Lease6ExtendedInfoPtr> remote_id6_;
-};
-
 class LeaseMgrTest : public GenericLeaseMgrTest {
 public:
     LeaseMgrTest() {
diff --git a/src/lib/dhcpsrv/tests/tracking_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/tracking_lease_mgr_unittest.cc
new file mode 100644 (file)
index 0000000..d5bde6b
--- /dev/null
@@ -0,0 +1,396 @@
+// Copyright (C) 2023 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 <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/testutils/concrete_lease_mgr.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <exceptions/exceptions.h>
+#include <functional>
+#include <gtest/gtest.h>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace std::placeholders;
+
+namespace {
+
+/// @brief Test fixture class for @c TrackingLeaseMgr.
+class TrackingLeaseMgrTest : public GenericLeaseMgrTest {
+public:
+
+    /// @brief A structure holding a single callback log entry.
+    ///
+    /// The @c logCallback function inserts logs of this type into the
+    /// @c logs vector. The vector can be later examined to see what
+    /// callbacks have been invoked.
+    typedef struct {
+        TrackingLeaseMgr::CallbackType type;
+        SubnetID subnet_id;
+        LeasePtr lease;
+    } Log;
+
+    /// @brief Reopen the database
+    ///
+    /// No-op implementation. We need to provide concrete implementation,
+    /// as this is a pure virtual method in GenericLeaseMgrTest.
+    virtual void reopen(Universe) {}
+
+    /// @brief Convenience function creating a lease instance.
+    ///
+    /// @param subnet_id subnet identifier for the lease.
+    /// @param address leased address.
+    /// @tparam LeaseType type of the lease: @c Lease4 or Lease6.
+    template<typename LeaseType>
+    boost::shared_ptr<LeaseType> createLease(int subnet_id, std::string address) const {
+        auto lease = boost::make_shared<LeaseType>();
+        lease->subnet_id_ = SubnetID(subnet_id);
+        lease->addr_ = IOAddress(address);
+        return (lease);
+    }
+
+    /// @brief Callback function recording its parameters.
+    ///
+    /// It is used in the unit tests that verify that appropriate callbacks
+    /// have been invoked.
+    ///
+    /// @param type callback type.
+    /// @param subnet_id subnet identifier.
+    /// @param lease lease instance.
+    void logCallback(const TrackingLeaseMgr::CallbackType type, SubnetID subnet_id, const LeasePtr& lease) {
+        logs_.push_back(Log{type, subnet_id, lease});
+    }
+
+    /// @brief Counts log entries.
+    ///
+    /// It counts the logs associated with the specific callback type and subnet id.
+    ///
+    /// @param type callback type.
+    /// @param subnet_id subnet identifier.
+    /// @return The number of callback logs associated with the specific type and
+    /// the subnet id.
+    int countLogs(TrackingLeaseMgr::CallbackType type, SubnetID subnet_id) const {
+        int count = 0;
+        for (auto log : logs_) {
+            if ((log.type == type) && (log.subnet_id == subnet_id)) {
+                ++count;
+            }
+        }
+        return (count);
+    }
+
+    /// Recorded callback logs.
+    std::vector<Log> logs_;
+};
+
+/// Tests that leases can be locked and unlocked. When a lease is locked
+/// an attempt to lock it again fails.
+TEST_F(TrackingLeaseMgrTest, tryLock) {
+    DatabaseConnection::ParameterMap pmap;
+    ConcreteLeaseMgr mgr(pmap);
+
+    // An attempt to lock an already locked lease should fail.
+    EXPECT_TRUE(mgr.tryLock(createLease<Lease4>(1, "192.0.2.1")));
+    EXPECT_FALSE(mgr.tryLock(createLease<Lease4>(1, "192.0.2.1")));
+
+    // We can lock another lease but we cannot lock an already locked one.
+    EXPECT_TRUE(mgr.tryLock(createLease<Lease4>(1, "192.0.2.2")));
+    EXPECT_FALSE(mgr.tryLock(createLease<Lease4>(1, "192.0.2.1")));
+    EXPECT_FALSE(mgr.tryLock(createLease<Lease4>(2, "192.0.2.2")));
+
+    // If we unlock the lease, it can be locked again. However, unlocking
+    // the lease should not affect other locks.
+    mgr.unlock(createLease<Lease4>(1, "192.0.2.1"));
+    EXPECT_FALSE(mgr.tryLock(createLease<Lease4>(2, "192.0.2.2")));
+    EXPECT_TRUE(mgr.tryLock(createLease<Lease4>(1, "192.0.2.1")));
+}
+
+/// Tests registering the callbacks.
+TEST_F(TrackingLeaseMgrTest, registerCallbacks) {
+    DatabaseConnection::ParameterMap pmap;
+    ConcreteLeaseMgr mgr(pmap);
+
+    // Callback for lease add and subnet id 0.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   0,
+                                                   _1)));
+    // Callback for lease add and subnet id 1.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 1, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   1,
+                                                   _1)));
+    // Callback for lease add and subnet id 2.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 2, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   1,
+                                                   _1)));
+    // Callback for lease update and subnet id 0.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+                                                   0,
+                                                   _1)));
+    // Callback for lease delete and subnet id 0.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_DELETE_LEASE,
+                                                   1,
+                                                   _1)));
+
+    // This call should trigger the lease add callbacks for subnet id 0 and 1.
+    EXPECT_NO_THROW(mgr.trackAddLease(createLease<Lease4>(1, "192.0.2.1"), false));
+    EXPECT_EQ(2, logs_.size());
+    EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, 0));
+    EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, 1));
+
+    // This call should trigger the lease add callback for subnet id 0 only. That's
+    // because we have no callback for the subnet id 3.
+    EXPECT_NO_THROW(mgr.trackAddLease(createLease<Lease4>(3, "192.0.2.1"), false));
+    EXPECT_EQ(3, logs_.size());
+    EXPECT_EQ(2, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, 0));
+}
+
+/// Test that registering the callbacks of the same type, for the same subnet id by the
+/// same owner fails.
+TEST_F(TrackingLeaseMgrTest, registerCallbacksConflicts) {
+    DatabaseConnection::ParameterMap pmap;
+    ConcreteLeaseMgr mgr(pmap);
+
+    // Add the callback for lease add and subnet id 0.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   0,
+                                                   _1)));
+
+    // Another attempt should fail.
+    EXPECT_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "flq",
+                                      std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                this,
+                                                TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                0,
+                                                _1)),
+                 InvalidOperation);
+
+    // It should succeed for a different owner.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "qlf",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   0,
+                                                   _1)));
+
+    // It should also succeed for a different subnet id.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 5, "qlf",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   5,
+                                                   _1)));
+
+    // But, another attempt for the subnet id should fail.
+    EXPECT_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 5, "qlf",
+                                      std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                this,
+                                                TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                5,
+                                                _1)),
+                 InvalidOperation);
+}
+
+/// Test invoking the registered lease update callbacks.
+TEST_F(TrackingLeaseMgrTest, trackUpdateLease) {
+    DatabaseConnection::ParameterMap pmap;
+    ConcreteLeaseMgr mgr(pmap);
+
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_DELETE_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.trackUpdateLease(createLease<Lease4>(1, "192.0.2.1"), false));
+    EXPECT_EQ(1, logs_.size());
+    EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_UPDATE_LEASE, 0));
+}
+
+/// Test invoking the registered lease delete callbacks.
+TEST_F(TrackingLeaseMgrTest, trackDeleteLease) {
+    DatabaseConnection::ParameterMap pmap;
+    ConcreteLeaseMgr mgr(pmap);
+
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_DELETE_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.trackDeleteLease(createLease<Lease4>(1, "192.0.2.1"), false));
+    EXPECT_EQ(1, logs_.size());
+    EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_DELETE_LEASE, 0));
+}
+
+// Test unregistering the callbacks by subnet id.
+TEST_F(TrackingLeaseMgrTest, unregisterCallbacksBySubnetID) {
+    DatabaseConnection::ParameterMap pmap;
+    ConcreteLeaseMgr mgr(pmap);
+
+    // Register different callback types for different subnet identifiers.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 1, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   1,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 2, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   2,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, 1, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+                                                   1,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, 2, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+                                                   2,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, 1, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_DELETE_LEASE,
+                                                   1,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, 2, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_DELETE_LEASE,
+                                                   2,
+                                                   _1)));
+
+    // Unregister the callbacks for subnet id 1.
+    EXPECT_NO_THROW(mgr.unregisterCallbacks(SubnetID(1)));
+
+    // Invoke the remaining callbacksm for the subnet id 1.
+    EXPECT_NO_THROW(mgr.trackAddLease(createLease<Lease4>(1, "192.0.2.1"), false));
+    EXPECT_NO_THROW(mgr.trackUpdateLease(createLease<Lease4>(1, "192.0.2.1"), false));
+    EXPECT_NO_THROW(mgr.trackDeleteLease(createLease<Lease4>(1, "192.0.2.1"), false));
+
+    // It should only run the callback for the subnet id 0 that is still
+    // registered.
+    EXPECT_EQ(1, logs_.size());
+    EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, 0));
+
+    // Unregister this callback.
+    EXPECT_NO_THROW(mgr.unregisterCallbacks(SubnetID(0)));
+
+    // Make sure it is no longer invoked.
+    EXPECT_NO_THROW(mgr.trackAddLease(createLease<Lease4>(1, "192.0.2.1"), false));
+    EXPECT_EQ(1, logs_.size());
+
+    // Unregistering it again should be no-op.
+    EXPECT_NO_THROW(mgr.unregisterCallbacks(SubnetID(0)));
+}
+
+/// Test unregistering all callbacks.
+TEST_F(TrackingLeaseMgrTest, unregisterAllCallbacks) {
+    DatabaseConnection::ParameterMap pmap;
+    ConcreteLeaseMgr mgr(pmap);
+
+    // Register some callbacks.
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+                                                   0,
+                                                   _1)));
+    // Make sure they have been registered.
+    EXPECT_TRUE(mgr.hasCallbacks());
+
+    // Unregister them and make sure it was successful.
+    EXPECT_NO_THROW(mgr.unregisterAllCallbacks());
+    EXPECT_FALSE(mgr.hasCallbacks());
+}
+
+/// Test the function checking if any callbacks have been registered.
+TEST_F(TrackingLeaseMgrTest, hasCallbacks) {
+    DatabaseConnection::ParameterMap pmap;
+    ConcreteLeaseMgr mgr(pmap);
+    EXPECT_FALSE(mgr.hasCallbacks());
+
+    EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, 0, "flq",
+                                         std::bind(&TrackingLeaseMgrTest::logCallback,
+                                                   this,
+                                                   TrackingLeaseMgr::TRACK_ADD_LEASE,
+                                                   0,
+                                                   _1)));
+    EXPECT_TRUE(mgr.hasCallbacks());
+}
+
+/// Test conversion of the callback type to string.
+TEST_F(TrackingLeaseMgrTest, callbackTypeToString) {
+    EXPECT_EQ("add_lease", ConcreteLeaseMgr::callbackTypeToString(TrackingLeaseMgr::TRACK_ADD_LEASE));
+    EXPECT_EQ("update_lease", ConcreteLeaseMgr::callbackTypeToString(TrackingLeaseMgr::TRACK_UPDATE_LEASE));
+    EXPECT_EQ("delete_lease", ConcreteLeaseMgr::callbackTypeToString(TrackingLeaseMgr::TRACK_DELETE_LEASE));
+}
+
+} // end of anonymous namespace
index 8a84acffb890a87b388c281dacbae33b93108807..e7060b5bce624dd0c2e24b638a3ec52a00cf3010 100644 (file)
@@ -12,7 +12,8 @@ if HAVE_GTEST
 
 noinst_LTLIBRARIES = libdhcpsrvtest.la
 
-libdhcpsrvtest_la_SOURCES  = config_result_check.cc config_result_check.h
+libdhcpsrvtest_la_SOURCES  = concrete_lease_mgr.cc concrete_lease_mgr.h
+libdhcpsrvtest_la_SOURCES += config_result_check.cc config_result_check.h
 libdhcpsrvtest_la_SOURCES += dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h
 libdhcpsrvtest_la_SOURCES += host_data_source_utils.cc host_data_source_utils.h
 libdhcpsrvtest_la_SOURCES += memory_host_data_source.cc memory_host_data_source.h
diff --git a/src/lib/dhcpsrv/testutils/concrete_lease_mgr.cc b/src/lib/dhcpsrv/testutils/concrete_lease_mgr.cc
new file mode 100644 (file)
index 0000000..d04fd34
--- /dev/null
@@ -0,0 +1,340 @@
+// Copyright (C) 2023 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 <asiolink/io_address.h>
+#include <concrete_lease_mgr.h>
+
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+ConcreteLeaseMgr::ConcreteLeaseMgr(const DatabaseConnection::ParameterMap&)
+    : TrackingLeaseMgr() {
+}
+
+ConcreteLeaseMgr::~ConcreteLeaseMgr() {
+}
+
+bool
+ConcreteLeaseMgr::addLease(const Lease4Ptr&) {
+    return (false);
+}
+
+bool
+ConcreteLeaseMgr::addLease(const Lease6Ptr&) {
+    return (false);
+}
+
+Lease4Ptr
+ConcreteLeaseMgr::getLease4(const IOAddress&) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLease4(const HWAddr&) const {
+    return (Lease4Collection());
+}
+
+Lease4Ptr
+ConcreteLeaseMgr::getLease4(const HWAddr&, SubnetID) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLease4(const ClientId&) const {
+    return (Lease4Collection());
+}
+
+Lease4Ptr
+ConcreteLeaseMgr::getLease4(const ClientId&, SubnetID) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4(SubnetID) const {
+    return (Lease4Collection());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4(const std::string&) const {
+    return (Lease4Collection());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4() const {
+    return (Lease4Collection());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4(const IOAddress& /* lower_bound_address */,
+                             const LeasePageSize& /* page_size */) const {
+    return (Lease4Collection());
+}
+
+Lease6Ptr
+ConcreteLeaseMgr::getLease6(Lease::Type /* not used yet */,
+                            const IOAddress&) const {
+    return (Lease6Ptr());
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(Lease::Type /* not used yet */,
+                             const DUID&, uint32_t) const {
+    return (leases6_);
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(Lease::Type /* not used yet */,
+                             const DUID&, uint32_t, SubnetID) const {
+    return (leases6_);
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(const DUID&) const {
+    return (leases6_);
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(SubnetID) const {
+    return (Lease6Collection());
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(const std::string&) const {
+    return (Lease6Collection());
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6() const {
+    return (Lease6Collection());
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(const IOAddress& /* lower_bound_address */,
+                             const LeasePageSize& /* page_size */) const {
+    return (Lease6Collection());
+};
+
+void
+ConcreteLeaseMgr::getExpiredLeases6(Lease6Collection&, const size_t) const {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::getExpiredLeases6 is not"
+              " implemented");
+}
+
+void
+ConcreteLeaseMgr::getExpiredLeases4(Lease4Collection&, const size_t) const {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::getExpiredLeases4 is not"
+              " implemented");
+}
+
+void
+ConcreteLeaseMgr::updateLease4(const Lease4Ptr&) {}
+
+void
+ConcreteLeaseMgr::updateLease6(const Lease6Ptr&) {}
+
+bool
+ConcreteLeaseMgr::deleteLease(const Lease4Ptr&) {
+    return (false);
+}
+
+bool
+ConcreteLeaseMgr::deleteLease(const Lease6Ptr&) {
+    return (false);
+}
+
+uint64_t
+ConcreteLeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::deleteExpir§edReclaimedLeases4"
+              " is not implemented");
+}
+
+uint64_t
+ConcreteLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::deleteExpiredReclaimedLeases6"
+              " is not implemented");
+}
+
+size_t
+ConcreteLeaseMgr::wipeLeases4(const SubnetID&) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::wipeLeases4 not implemented");
+}
+
+size_t
+ConcreteLeaseMgr::wipeLeases6(const SubnetID&) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::wipeLeases6 not implemented");
+}
+
+std::string
+ConcreteLeaseMgr::checkLimits4(isc::data::ConstElementPtr const& /* user_context */) const {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits4() not implemented");
+}
+
+std::string
+ConcreteLeaseMgr::checkLimits6(isc::data::ConstElementPtr const& /* user_context */) const {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits6() not implemented");
+}
+
+bool
+ConcreteLeaseMgr::isJsonSupported() const {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::isJsonSupported() not implemented");
+}
+
+size_t
+ConcreteLeaseMgr::getClassLeaseCount(const ClientClass& /* client_class */,
+                                     const Lease::Type& /* ltype = Lease::TYPE_V4 */) const {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::getClassLeaseCount() not implemented");
+}
+
+void
+ConcreteLeaseMgr::recountClassLeases4() {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::recountClassLeases4() not implemented");
+}
+
+void
+ConcreteLeaseMgr::recountClassLeases6() {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::recountClassLeases6() not implemented");
+}
+
+void
+ConcreteLeaseMgr::clearClassLeaseCounts() {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::clearClassLeaseCounts() not implemented");
+}
+
+void
+ConcreteLeaseMgr::deleteExtendedInfo6(const IOAddress& addr) {
+    auto relay_id_it = relay_id6_.begin();
+    while (relay_id_it != relay_id6_.end()) {
+        if ((*relay_id_it)->lease_addr_ == addr) {
+            relay_id_it = relay_id6_.erase(relay_id_it);
+        } else {
+            ++relay_id_it;
+        }
+    }
+    auto remote_id_it = remote_id6_.begin();
+    while (remote_id_it != remote_id6_.end()) {
+        if ((*remote_id_it)->lease_addr_ == addr) {
+            remote_id_it = remote_id6_.erase(remote_id_it);
+        } else {
+            ++remote_id_it;
+        }
+    }
+}
+
+void
+ConcreteLeaseMgr::addRelayId6(const IOAddress& lease_addr,
+                              const vector<uint8_t>& relay_id) {
+    Lease6ExtendedInfoPtr ex_info;
+    ex_info.reset(new Lease6ExtendedInfo(lease_addr, relay_id));
+    relay_id6_.push_back(ex_info);
+}
+
+void
+ConcreteLeaseMgr::addRemoteId6(const IOAddress& lease_addr,
+                               const vector<uint8_t>& remote_id) {
+    Lease6ExtendedInfoPtr ex_info;
+    ex_info.reset(new Lease6ExtendedInfo(lease_addr, remote_id));
+    remote_id6_.push_back(ex_info);
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4ByRelayId(const OptionBuffer& /* relay_id */,
+                                      const IOAddress& /* lower_bound_address */,
+                                      const LeasePageSize& /* page_size */,
+                                      const time_t& /* qry_start_time = 0 */,
+                                      const time_t& /* qry_end_time = 0 */) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases4ByRelayId not implemented");
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& /* remote_id */,
+                                       const IOAddress& /* lower_bound_address */,
+                                       const LeasePageSize& /* page_size */,
+                                       const time_t& /* qry_start_time = 0 */,
+                                       const time_t& /* qry_end_time = 0 */) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases4ByRemoteId not implemented");
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6ByRelayId(const DUID& /* relay_id */,
+                                      const IOAddress& /* link_addr */,
+                                      uint8_t /* link_len */,
+                                      const IOAddress& /* lower_bound_address */,
+                                      const LeasePageSize& /* page_size */) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByRelayId not implemented");
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6ByRemoteId(const OptionBuffer& /* remote_id */,
+                                       const IOAddress& /* link_addr */,
+                                       uint8_t /* link_len */,
+                         const IOAddress& /* lower_bound_address */,
+                                       const LeasePageSize& /* page_size*/) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByRemoteId not implemented");
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6ByLink(const IOAddress& /* link_addr */,
+                                   uint8_t /* link_len */,
+                                   const IOAddress& /* lower_bound_address */,
+                                   const LeasePageSize& /* page_size */) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByLink not implemented");
+}
+
+size_t
+ConcreteLeaseMgr::buildExtendedInfoTables6(bool /* update */,
+                                           bool /* current */) {
+    isc_throw(isc::NotImplemented, "ConcreteLeaseMgr:buildExtendedInfoTables6 not implemented");
+}
+
+void
+ConcreteLeaseMgr::writeLeases4(const std::string&) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::writeLeases4() not implemented");
+}
+
+void
+ConcreteLeaseMgr::writeLeases6(const std::string&) {
+    isc_throw(NotImplemented, "ConcreteLeaseMgr::writeLeases6() not implemented");
+}
+
+std::string
+ConcreteLeaseMgr::getType() const {
+    return (std::string("concrete"));
+}
+
+std::string
+ConcreteLeaseMgr::getName() const {
+    return (std::string("concrete"));
+}
+
+std::string
+ConcreteLeaseMgr::getDescription() const {
+    return (std::string("This is a dummy concrete backend implementation."));
+}
+
+std::pair<uint32_t, uint32_t>
+ConcreteLeaseMgr::getVersion() const {
+    return (make_pair(uint32_t(0), uint32_t(0)));
+}
+
+void
+ConcreteLeaseMgr::commit() {
+}
+
+void
+ConcreteLeaseMgr::rollback() {
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/concrete_lease_mgr.h b/src/lib/dhcpsrv/testutils/concrete_lease_mgr.h
new file mode 100644 (file)
index 0000000..3b97a49
--- /dev/null
@@ -0,0 +1,443 @@
+// Copyright (C) 2023 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 TEST_CONCRETE_LEASE_MGR_H
+#define TEST_CONCRETE_LEASE_MGR_H
+
+#include <config.h>
+
+#include <database/database_connection.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+#include <list>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// This is a concrete implementation of a Lease database.  It does not do
+// anything useful and is used for abstract LeaseMgr class testing.
+class ConcreteLeaseMgr : public TrackingLeaseMgr {
+public:
+
+    /// @brief The sole lease manager constructor
+    ///
+    /// dbconfig is a generic way of passing parameters. Parameters
+    /// are passed in the "name=value" format, separated by spaces.
+    /// Values may be enclosed in double quotes, if needed.
+    ConcreteLeaseMgr(const db::DatabaseConnection::ParameterMap&);
+
+    /// @brief Destructor
+    virtual ~ConcreteLeaseMgr();
+
+    /// @brief Adds an IPv4 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(const Lease4Ptr&) override;
+
+    /// @brief Adds an IPv6 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(const Lease6Ptr&) override;
+
+    /// @brief Returns existing IPv4 lease for specified IPv4 address.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress&) const override;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address.
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param hwaddr hardware address of the client
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const HWAddr&) const override;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address
+    ///        and a subnet
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param hwaddr hardware address of the client
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const HWAddr&, SubnetID) const override;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// @param clientid client identifier
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const ClientId&) const override;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param clientid client identifier
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const ClientId&, SubnetID) const override;
+
+    /// @brief Returns all IPv4 leases for the particular subnet identifier.
+    ///
+    /// @param subnet_id subnet identifier.
+    ///
+    /// @return Lease collection (may be empty if no IPv4 lease found).
+    virtual Lease4Collection getLeases4(SubnetID) const override;
+
+    /// @brief Returns all IPv4 leases for the particular hostname.
+    ///
+    /// @param hostname hostname in lower case.
+    ///
+    /// @return Lease collection (may be empty if no IPv4 lease found).
+    virtual Lease4Collection getLeases4(const std::string&) const override;
+
+    /// @brief Returns all IPv4 leases.
+    ///
+    /// @return Lease collection (may be empty if no IPv4 lease found).
+    virtual Lease4Collection getLeases4() const override;
+
+    /// @brief Returns range of IPv4 leases using paging.
+    ///
+    /// This method implements paged browsing of the lease database. The first
+    /// parameter specifies a page size. The second parameter is optional and
+    /// specifies the starting address of the range. This address is excluded
+    /// from the returned range. The IPv4 zero address (default) denotes that
+    /// the first page should be returned. There is no guarantee about the
+    /// order of returned leases.
+    ///
+    /// The typical usage of this method is as follows:
+    /// - Get the first page of leases by specifying IPv4 zero address as the
+    ///   beginning of the range.
+    /// - Last address of the returned range should be used as a starting
+    ///   address for the next page in the subsequent call.
+    /// - If the number of leases returned is lower than the page size, it
+    ///   indicates that the last page has been retrieved.
+    /// - If there are no leases returned it indicates that the previous page
+    ///   was the last page.
+    ///
+    /// @param lower_bound_address IPv4 address used as lower bound for the
+    /// returned range.
+    /// @param page_size maximum size of the page returned.
+    ///
+    /// @return Lease collection (may be empty if no IPv4 lease found).
+    virtual Lease4Collection
+    getLeases4(const asiolink::IOAddress& /* lower_bound_address */,
+               const LeasePageSize& /* page_size */) const override;
+
+    /// @brief Returns existing IPv6 lease for a given IPv6 address.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease6Ptr getLease6(Lease::Type /* not used yet */,
+                                const isc::asiolink::IOAddress&) const override;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @param duid ignored
+    /// @param iaid ignored
+    ///
+    /// @return whatever is set in leases6_ field
+    virtual Lease6Collection getLeases6(Lease::Type /* not used yet */,
+                                        const DUID&, uint32_t) const override;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA+subnet-id combination
+    ///
+    /// @param duid ignored
+    /// @param iaid ignored
+    /// @param subnet_id ignored
+    ///
+    /// @return whatever is set in leases6_ field
+    virtual Lease6Collection getLeases6(Lease::Type /* not used yet */,
+                                        const DUID&, uint32_t, SubnetID) const override;
+
+    /// @brief Returns collection of lease for matching DUID
+    ///
+    /// @param duid ignored
+    /// @return whatever is set in leases6_ field
+    virtual Lease6Collection getLeases6(const DUID&) const override;
+
+    /// @brief Returns all IPv6 leases for the particular subnet identifier.
+    ///
+    /// @param subnet_id subnet identifier.
+    ///
+    /// @return Lease collection (may be empty if no IPv6 lease found).
+    virtual Lease6Collection getLeases6(SubnetID) const override;
+
+    /// @brief Returns all IPv6 leases for the particular hostname.
+    ///
+    /// @param hostname hostname in lower case.
+    ///
+    /// @return Lease collection (may be empty if no IPv6 lease found).
+    virtual Lease6Collection getLeases6(const std::string&) const override;
+
+    /// @brief Returns all IPv6 leases.
+    ///
+    /// @return Lease collection (may be empty if no IPv6 lease found).
+    virtual Lease6Collection getLeases6() const override;
+
+    /// @brief Returns range of IPv6 leases using paging.
+    ///
+    /// This method implements paged browsing of the lease database. The first
+    /// parameter specifies a page size. The second parameter is optional and
+    /// specifies the starting address of the range. This address is excluded
+    /// from the returned range. The IPv6 zero address (default) denotes that
+    /// the first page should be returned. There is no guarantee about the
+    /// order of returned leases.
+    ///
+    /// The typical usage of this method is as follows:
+    /// - Get the first page of leases by specifying IPv6 zero address as the
+    ///   beginning of the range.
+    /// - Last address of the returned range should be used as a starting
+    ///   address for the next page in the subsequent call.
+    /// - If the number of leases returned is lower than the page size, it
+    ///   indicates that the last page has been retrieved.
+    /// - If there are no leases returned it indicates that the previous page
+    ///   was the last page.
+    ///
+    /// @param lower_bound_address IPv4 address used as lower bound for the
+    /// returned range.
+    /// @param page_size maximum size of the page returned.
+    ///
+    /// @return Lease collection (may be empty if no IPv6 lease found).
+    virtual Lease6Collection
+    getLeases6(const asiolink::IOAddress& /* lower_bound_address */,
+               const LeasePageSize& /* page_size */) const override;
+
+    /// @brief Returns expired DHCPv6 leases.
+    ///
+    /// This method is not implemented.
+    virtual void getExpiredLeases6(Lease6Collection&, const size_t) const override;
+
+    /// @brief Returns expired DHCPv4 leases.
+    ///
+    /// This method is not implemented.
+    virtual void getExpiredLeases4(Lease4Collection&, const size_t) const override;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    virtual void updateLease4(const Lease4Ptr&) override;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    virtual void updateLease6(const Lease6Ptr&) override;
+
+    /// @brief Deletes an IPv4 lease.
+    ///
+    /// @param lease IPv4 lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists.
+    virtual bool deleteLease(const Lease4Ptr&) override;
+
+    /// @brief Deletes an IPv6 lease.
+    ///
+    /// @param lease IPv6 lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists.
+    virtual bool deleteLease(const Lease6Ptr&) override;
+
+    /// @brief Deletes all expired and reclaimed DHCPv4 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t) override;
+
+    /// @brief Deletes all expired and reclaimed DHCPv6 leases.
+    ///
+    /// @param secs Number of seconds since expiration of leases before
+    /// they can be removed. Leases which have expired later than this
+    /// time will not be deleted.
+    virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t) override;
+
+    /// @brief Pretends to wipe all IPv4 leases from a subnet
+    /// @param subnet_id (ignored, but one day may specify the subnet)
+    virtual size_t wipeLeases4(const SubnetID&) override;
+
+    /// @brief Pretends to wipe all IPv4 leases from a subnet
+    /// @param subnet_id (ignored, but one day may specify the subnet)
+    virtual size_t wipeLeases6(const SubnetID&) override;
+
+    /// @brief Pretends to check if the IPv4 lease limits set in the given user
+    /// context are exceeded.
+    virtual std::string
+    checkLimits4(isc::data::ConstElementPtr const& /* user_context */) const override;
+
+    /// @brief Pretends to check if the IPv6 lease limits set in the given user
+    /// context are exceeded.
+    virtual std::string
+    checkLimits6(isc::data::ConstElementPtr const& /* user_context */) const override;
+
+    /// @brief Pretends to check if JSON support is enabled in the database.
+    ///
+    /// @return true if there is JSON support, false otherwise
+    virtual bool isJsonSupported() const override;
+
+    /// @brief Pretends to return the class lease count for a given class and lease type.
+    ///
+    /// @param client_class client class for which the count is desired
+    /// @param ltype type of lease for which the count is desired. Defaults to
+    /// Lease::TYPE_V4.
+    ///
+    /// @return number of leases
+    virtual size_t getClassLeaseCount(const ClientClass& /* client_class */,
+                              const Lease::Type& /* ltype = Lease::TYPE_V4 */) const override;
+
+    /// @brief Pretends to recount the leases per class for V4 leases.
+    virtual void recountClassLeases4() override;
+
+    /// @brief Pretends to recount the leases per class for V6 leases.
+    virtual void recountClassLeases6() override;
+
+    /// @brief Pretends to clear the class-lease count map.
+    virtual void clearClassLeaseCounts() override;
+
+    /// @brief Import addExtendedInfo6.
+    using LeaseMgr::addExtendedInfo6;
+
+    /// @brief Delete lease6 extended info from tables.
+    ///
+    /// @param addr The address of the lease.
+    void
+    deleteExtendedInfo6(const asiolink::IOAddress& addr) override;
+
+    /// @brief Add lease6 extended info into by-relay-id table.
+    ///
+    /// @param lease_addr The address of the lease.
+    /// @param relay_id The relay id from the relay header options.
+    void
+    addRelayId6(const asiolink::IOAddress& lease_addr,
+                const std::vector<uint8_t>& relay_id) override;
+
+    /// @brief Add lease6 extended info into by-remote-id table.
+    ///
+    /// @param lease_addr The address of the lease.
+    void
+    addRemoteId6(const asiolink::IOAddress& lease_addr,
+                 const std::vector<uint8_t>& remote_id) override;
+
+    /// @brief Stub implementation.
+    Lease4Collection
+    getLeases4ByRelayId(const OptionBuffer& /* relay_id */,
+                        const asiolink::IOAddress& /* lower_bound_address */,
+                        const LeasePageSize& /* page_size */,
+                        const time_t& /* qry_start_time = 0 */,
+                        const time_t& /* qry_end_time = 0 */) override;
+
+    /// @brief Stub implementation.
+    Lease4Collection
+    getLeases4ByRemoteId(const OptionBuffer& /* remote_id */,
+                         const asiolink::IOAddress& /* lower_bound_address */,
+                         const LeasePageSize& /* page_size */,
+                         const time_t& /* qry_start_time = 0 */,
+                         const time_t& /* qry_end_time = 0 */) override;
+
+    /// @brief Stub implementation.
+    Lease6Collection
+    getLeases6ByRelayId(const DUID& /* relay_id */,
+                        const asiolink::IOAddress& /* link_addr */,
+                        uint8_t /* link_len */,
+                        const asiolink::IOAddress& /* lower_bound_address */,
+                        const LeasePageSize& /* page_size */) override;
+
+    /// @brief Stub implementation.
+    Lease6Collection
+    getLeases6ByRemoteId(const OptionBuffer& /* remote_id */,
+                         const asiolink::IOAddress& /* link_addr */,
+                         uint8_t /* link_len */,
+                         const asiolink::IOAddress& /* lower_bound_address */,
+                         const LeasePageSize& /* page_size*/) override;
+
+    /// @brief Stub implementation.
+    Lease6Collection
+    getLeases6ByLink(const asiolink::IOAddress& /* link_addr */,
+                     uint8_t /* link_len */,
+                     const asiolink::IOAddress& /* lower_bound_address */,
+                     const LeasePageSize& /* page_size */) override;
+
+    /// @brief Stub implementation.
+    virtual size_t buildExtendedInfoTables6(bool /* update */,
+                                            bool /* current */) override;
+
+    ///  @brief Pretends to write V4 leases to a file.
+    virtual void writeLeases4(const std::string&) override;
+
+    ///  @brief Pretends to write V6 leases to a file.
+    virtual void writeLeases6(const std::string&) override;
+
+    /// @brief Returns backend type.
+    ///
+    /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+    ///
+    /// @return Type of the backend.
+    virtual std::string getType() const override;
+
+    /// @brief Returns backend name.
+    ///
+    /// If the backend is a database, this is the name of the database or the
+    /// file.  Otherwise it is just the same as the type.
+    ///
+    /// @return Name of the backend.
+    virtual std::string getName() const override;
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    ///
+    /// @return Description of the backend.
+    virtual std::string getDescription() const override;
+
+    /// @brief Returns backend version.
+    virtual std::pair<uint32_t, uint32_t> getVersion() const override;
+
+    /// @brief Commit transactions
+    virtual void commit() override;
+
+    /// @brief Rollback transactions
+    virtual void rollback() override;
+
+    // We need to use them in ConcreteLeaseMgr
+    using LeaseMgr::getLease6;
+    using TrackingLeaseMgr::tryLock;
+    using TrackingLeaseMgr::unlock;
+    using TrackingLeaseMgr::trackAddLease;
+    using TrackingLeaseMgr::trackUpdateLease;
+    using TrackingLeaseMgr::trackDeleteLease;
+    using TrackingLeaseMgr::hasCallbacks;
+    using TrackingLeaseMgr::callbackTypeToString;
+
+    Lease6Collection leases6_; ///< getLease6 methods return this as is
+
+    // List supports easier erase.
+    std::list<Lease6ExtendedInfoPtr> relay_id6_;
+    std::list<Lease6ExtendedInfoPtr> remote_id6_;
+};
+
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // TEST_CONCRETE_LEASE_MGR_H
diff --git a/src/lib/dhcpsrv/tracking_lease_mgr.cc b/src/lib/dhcpsrv/tracking_lease_mgr.cc
new file mode 100644 (file)
index 0000000..79697f3
--- /dev/null
@@ -0,0 +1,151 @@
+// Copyright (C) 2023 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 <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/tuple/tuple.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+TrackingLeaseMgr::TrackingLeaseMgr()
+    : LeaseMgr() {
+}
+
+bool
+TrackingLeaseMgr::tryLock(const LeasePtr& lease) {
+    // Try inserting a lease. If such a lease already exists in the set, return false.
+    auto result = locked_leases_.insert(lease->addr_);
+    return (result.second);
+}
+
+void
+TrackingLeaseMgr::unlock(const LeasePtr& lease) {
+    // Remove the locked lease from the set.
+    locked_leases_.erase(lease->addr_);
+}
+
+void
+TrackingLeaseMgr::trackAddLease(const LeasePtr& lease, bool mt_safe) {
+    runCallbacks(TRACK_ADD_LEASE, lease, mt_safe);
+}
+
+void
+TrackingLeaseMgr::trackUpdateLease(const LeasePtr& lease, bool mt_safe) {
+    runCallbacks(TRACK_UPDATE_LEASE, lease, mt_safe);
+}
+
+void
+TrackingLeaseMgr::trackDeleteLease(const LeasePtr& lease, bool mt_safe) {
+    runCallbacks(TRACK_DELETE_LEASE, lease, mt_safe);
+}
+
+void
+TrackingLeaseMgr::registerCallback(TrackingLeaseMgr::CallbackType type,
+                                   SubnetID subnet_id,
+                                   std::string owner,
+                                   TrackingLeaseMgr::CallbackFn callback_fn) {
+    // The first index filters the callbacks by type and subnet_id.
+    auto& idx = callbacks_.get<0>();
+    auto range = idx.equal_range(boost::make_tuple(type, subnet_id));
+    if (range.first != range.second) {
+        // Make sure that the callback for this owner does not exist.
+        if (std::find_if(range.first, range.second,
+                         [&owner] (const Callback& cb) -> bool {
+                             return (cb.owner == owner);
+                         }) != range.second) {
+            isc_throw(InvalidOperation, "the callback owned by the " << owner
+                      << ", for subnet ID " << subnet_id
+                      << " has already been registered in the lease manager");
+        }
+    }
+    TrackingLeaseMgr::Callback callback{type, owner, subnet_id, callback_fn};
+    callbacks_.insert(callback);
+}
+
+void
+TrackingLeaseMgr::registerCallback(TrackingLeaseMgr::CallbackType type,
+                                   std::string owner,
+                                   TrackingLeaseMgr::CallbackFn callback_fn) {
+    registerCallback(type, 0, owner, callback_fn);
+}
+
+void
+TrackingLeaseMgr::unregisterCallbacks(SubnetID subnet_id) {
+    // The second index filters the callbacks by the subnet identifier.
+    auto& idx = callbacks_.get<1>();
+    idx.erase(subnet_id);
+}
+
+void
+TrackingLeaseMgr::unregisterAllCallbacks() {
+    callbacks_.clear();
+}
+
+bool
+TrackingLeaseMgr::hasCallbacks() const {
+    return (!callbacks_.empty());
+}
+
+std::string
+TrackingLeaseMgr::callbackTypeToString(CallbackType type) {
+    switch (type) {
+    case TrackingLeaseMgr::TRACK_ADD_LEASE:
+        return ("add_lease");
+    case TrackingLeaseMgr::TRACK_UPDATE_LEASE:
+        return ("update_lease");
+    case TrackingLeaseMgr::TRACK_DELETE_LEASE:
+        return ("delete_lease");
+    default:
+        return ("unknown");
+    }
+}
+
+void
+TrackingLeaseMgr::runCallbacks(TrackingLeaseMgr::CallbackType type, const LeasePtr& lease,
+                               bool mt_safe) {
+    runCallbacksForSubnetID(type, 0, lease, mt_safe);
+    runCallbacksForSubnetID(type, lease->subnet_id_, lease, mt_safe);
+}
+
+void
+TrackingLeaseMgr::runCallbacksForSubnetID(CallbackType type, SubnetID subnet_id,
+                                          const LeasePtr& lease, bool mt_safe) {
+    // The first index filters by callback type and subnet_id.
+    auto& idx_by_type = callbacks_.get<0>();
+    auto cbs = idx_by_type.equal_range(boost::make_tuple(type, subnet_id));
+    if (cbs.first == cbs.second) {
+        return;
+    }
+    for (auto it = cbs.first; it != cbs.second; ++it) {
+        auto cb = *it;
+        try {
+            cb.fn(lease, mt_safe);
+        } catch (const std::exception& ex) {
+            LOG_WARN(dhcpsrv_logger, DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION)
+                .arg(callbackTypeToString(type))
+                .arg(subnet_id)
+                .arg(lease->addr_.toText())
+                .arg(ex.what());
+        } catch (...) {
+            LOG_WARN(dhcpsrv_logger, DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION)
+                .arg(callbackTypeToString(type))
+                .arg(subnet_id)
+                .arg(lease->addr_.toText());
+        }
+    }
+}
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/tracking_lease_mgr.h b/src/lib/dhcpsrv/tracking_lease_mgr.h
new file mode 100644 (file)
index 0000000..7db4d5d
--- /dev/null
@@ -0,0 +1,278 @@
+// Copyright (C) 2023 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 TRACKING_LEASE_MGR_H
+#define TRACKING_LEASE_MGR_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <functional>
+#include <string>
+#include <unordered_set>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Introduces callbacks into the @c LeaseMgr.
+///
+/// The LeaseMgr is a central point of lease management and is aware of all
+/// lease changes within the server instance. Thus, it is a good opportunity
+/// to allow installing callbacks in the @c LeaseMgr to track all lease
+/// changes. An allocator maintaining a list of free leases (FLQ allocator)
+/// can benefit from it by/ installing the callbacks that add or remove
+/// free leases from this list, depending on the lease manager's activity.
+/// The callbacks are invoked regardless if the lease changes result from a
+/// normal lease allocation or a control command. The callbacks can also be
+/// useful for maintaining a log of lease changes. Such a log could be made
+/// available externally and consumed by another application (e.g., Stork).
+///
+/// The lease manager can track three types of calls: new lease insertion
+/// (add), an existing lease update, and lease deletion. Even though the
+/// lease reclamation is similar to deleting a lease because it becomes free,
+/// it is a lease update from the lease manager's standpoint. Currently,
+/// the lease manager cannot track the deletion of the reclaimed leases
+/// (i.e., the leases in the expired-reclaimed state).
+///
+/// The lease backends should call the suitable tracking functions:
+/// @c trackAddLease, @c trackUpdateLease, and @c trackDeleteLease.
+/// However, the backends must ensure that there are no race conditions
+/// between modifying the lease in the database and running the callbacks.
+/// Suppose that two threads modify the same lease. Thread A inserts the
+/// lease in the database, and thread B removes it. The callback for
+/// thread A should be invoked before the callback for thread B. If they
+/// are invoked in reverse order, it can result in an inconsistent state
+/// in the free lease queue allocator because the allocator should record
+/// the lease as available after the thread B callback. The reverse
+/// invocation order would result in marking the lease as unavailable for
+/// allocation after both callbacks.
+///
+/// The race condition does not occur for the Memfile backend because it
+/// guards entire functions with a mutex. However, the SQL backends rely
+/// on the database to guard against concurrent writes. In these cases,
+/// the backend must protect against the reverse order of callbacks. They
+/// should use the lease locking mechanism introduced in the
+/// @c TrackingLeaseMgr.
+///
+/// The lease locking is modeled on an @c unordered_set container holding
+/// the leases with the ongoing allocations. The leases are inserted into
+/// this container by the @c tryLock function. If another thread has already
+/// locked the lease, this function returns @c false to indicate an
+/// unsuccessful attempt. In this case, the thread should resign from updating
+/// the lease and return early. It can result in a lease allocation failure,
+/// but two concurrent threads extremely rarely work on allocating a lease for
+/// the same client. A passive wait could be another option here, but it is a
+/// much more complicated solution for a bit of gain.
+class TrackingLeaseMgr : public LeaseMgr {
+public:
+
+    /// @brief An enumeration differentiating between lease write operations.
+    typedef enum {
+        TRACK_ADD_LEASE,
+        TRACK_UPDATE_LEASE,
+        TRACK_DELETE_LEASE
+    } CallbackType;
+
+    /// @brief Type of a callback function invoked upon a lease insertion,
+    /// update or deletion.
+    ///
+    /// The first argument is a pointer to the lease for which the callback
+    /// is invoked. The second argument indicates whether or not the callback
+    /// is invoked in the thread-safe context.
+    typedef std::function<void(LeasePtr, bool)> CallbackFn;
+
+    /// @brief A structure representing a registered callback.
+    ///
+    /// It associates the callback with a type, its owner, and a subnet
+    /// identifier. The owner is a string specified by the registration
+    /// function caller. There must be at most one callback registered
+    /// for the particular owner and the subnet identifier.
+    typedef struct {
+        CallbackType type;
+        std::string owner;
+        SubnetID subnet_id;
+        CallbackFn fn;
+    } Callback;
+
+    /// @brief A multi-index container holding registered callbacks.
+    ///
+    /// The callbacks are accessible via two indexes. The first composite index
+    /// filters the callbacks by the callback type (i.e., lease add, update or delete)
+    /// and the subnet id. The second index filters the callbacks by the subnet id.
+    typedef boost::multi_index_container<
+        Callback,
+        boost::multi_index::indexed_by<
+            boost::multi_index::ordered_non_unique<
+                boost::multi_index::composite_key<
+                    Callback,
+                    boost::multi_index::member<Callback, CallbackType, &Callback::type>,
+                    boost::multi_index::member<Callback, SubnetID, &Callback::subnet_id>
+                >
+            >,
+            boost::multi_index::hashed_non_unique<
+                boost::multi_index::member<Callback, SubnetID, &Callback::subnet_id>
+            >
+        >
+    > CallbackContainer;
+
+protected:
+
+    /// @brief Constructor.
+    TrackingLeaseMgr();
+
+    /// @brief Attempts to lock a lease.
+    ///
+    /// If a lease is successfully locked, no other thread can lock it. It protects
+    /// against running the callbacks out of order when two threads modify the same
+    /// lease. Such a locking should only be used when the lease allocation followed by
+    /// the callbacks invocation are not protected by some other synchronization
+    /// mechanism. In particular, the Memfile backend uses a mutex for locking in the
+    /// lease allocation functions. In this case, it is unnecessary to apply a lock at the
+    /// lease level. The SQL backends rely on the database locking mechanisms to prevent
+    /// the concurrent updates of the same lease. These backends must use the lease locking
+    /// to ensure the correct callbacks invocation order.
+    ///
+    /// This function is not thread-safe and must be invoked in a thread-safe context.
+    ///
+    /// @param lease a lease instance for which the lock should be attempted.
+    /// @return true when locking was successful, false otherwise. In the latter case,
+    /// the thread should stop a lease allocation or deallocation attempt.
+    bool tryLock(const LeasePtr& lease);
+
+    /// @brief Attempts to unlock a lease.
+    ///
+    /// This function is not thread-safe and must be invoked in a thread-safe context.
+    ///
+    /// @param lease a lease instance for which unlocking should be attempted.
+    void unlock(const LeasePtr& lease);
+
+    /// @brief Invokes the callbacks when a new lease is added.
+    ///
+    /// It executes all callbacks of the @c TRACK_ADD_LEASE type for a subnet id of 0
+    /// and the subnet id associated with the lease.
+    ///
+    /// The callbacks execution order is not guaranteed.
+    ///
+    /// @param lease new lease instance.
+    /// @param mt_safe a boolean flag indicating whether the callbacks are
+    /// invoked in the MT-safe context.
+    void trackAddLease(const LeasePtr& lease, bool mt_safe);
+
+    /// @brief Invokes the callbacks when a lease is updated.
+    ///
+    /// It executes all callbacks of the @c TRACK_UPDATE_LEASE type for a subnet id of 0
+    /// and the subnet id associated with the lease.
+    ///
+    /// The callbacks execution order is not guaranteed.
+    ///
+    /// @param lease updated lease instance.
+    /// @param mt_safe a boolean flag indicating whether the callbacks are
+    /// invoked in the MT-safe context.
+    void trackUpdateLease(const LeasePtr& lease, bool mt_safe);
+
+    /// @brief Invokes the callbacks when a lease is deleted.
+    ///
+    /// It executes all callbacks of the @c TRACK_DELETE_LEASE type for a subnet id of 0
+    /// and the subnet id associated with the lease.
+    ///
+    /// The callbacks execution order is not guaranteed.
+    ///
+    /// @param lease deleted lease instance.
+    /// @param mt_safe a boolean flag indicating whether the callbacks are
+    /// invoked in the MT-safe context.
+    void trackDeleteLease(const LeasePtr& lease, bool mt_safe);
+
+public:
+
+    /// @brief Registers a callback function for a subnet.
+    ///
+    /// @param type callback type.
+    /// @param subnet_id subnet identifier; it can be set to 0 if the callback should be
+    /// called for subnets.
+    /// @param owner callback owner identifier.
+    /// @param callback_fn callback function instance.
+    /// @throw InvalidOperation when the callback has been already registered for the given owner and
+    /// the subnet identifier.
+    void registerCallback(CallbackType type, SubnetID subnet_id, std::string owner, CallbackFn callback_fn);
+
+    /// @brief Registers a callback function for all subnets.
+    ///
+    /// @param type callback type.
+    /// @param owner callback owner identifier.
+    /// @param callback_fn callback function instance.
+    /// @throw InvalidOperation when the callback has been already registered for the given owner and
+    /// all subnets.
+    void registerCallback(CallbackType type, std::string owner, CallbackFn callback_fn);
+
+    /// @brief Unregisters all callbacks for a given subnet identifier.
+    ///
+    /// @param subnet_id subnet identifier.
+    void unregisterCallbacks(SubnetID subnet_id);
+
+    /// @brief Unregisters all callbacks.
+    void unregisterAllCallbacks();
+
+protected:
+
+    /// @brief Checks if any callbacks has been registered.
+    ///
+    /// It is a quick check to be performed by the backends whether or not
+    /// the callbacks mechanism is used.
+    ///
+    /// @return true if any callbacks has been registered.
+    bool hasCallbacks() const;
+
+    /// @brief Converts callback type to string for logging purposes.
+    ///
+    /// @param type callback type.
+    /// @return callback type name or the 'unknown' string.
+    static std::string callbackTypeToString(CallbackType type);
+
+    /// @brief Runs registered callbacks of the particular type.
+    ///
+    /// The specified lease instance contains the subnet identifier used to
+    /// filter the callbacks to be invoked.
+    ///
+    /// @param type callback type.
+    /// @param lease lease instance for which the callbacks are invoked.
+    /// @param mt_safe a boolean flag indicating whether the callbacks are
+    /// invoked in the MT-safe context.
+    void runCallbacks(CallbackType type, const LeasePtr& lease, bool mt_safe);
+
+    /// @brief Runs registered callbacks of the particular type for a subnet id.
+    ///
+    /// It is called internally by the @c runCallbacks function.
+    ///
+    /// @param type callback type.
+    /// @param subnet_id subnet identifier for which the callbacks are invoked.
+    /// @param lease lease instance for which the callbacks are invoked.
+    /// @param mt_safe a boolean flag indicating whether the callbacks are
+    /// invoked in the MT-safe context.
+    void runCallbacksForSubnetID(CallbackType type, SubnetID subnet_id,
+                                 const LeasePtr& lease, bool mt_safe);
+
+    /// @brief The multi-index container holding registered callbacks.
+    CallbackContainer callbacks_;
+
+    /// @brief A set of locked leases.
+    ///
+    /// It is empty if locking is not used (e.g. Memfile backend) or when there
+    /// are no ongoing allocations.
+    std::unordered_set<asiolink::IOAddress, asiolink::IOAddress::Hash> locked_leases_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+    
+#endif // TRACKING_LEASE_MGR_H