From: Marcin Siodelski Date: Mon, 20 Feb 2023 14:34:54 +0000 (+0100) Subject: [#2764] Tracking lease manager X-Git-Tag: Kea-2.3.6~91 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d83c4f38d41a8a4fc784a0847782ae5ddc56aa73;p=thirdparty%2Fkea.git [#2764] Tracking lease manager The new lease manager layer implements callback mechanism. The callbacks can be registered and are invoked when the leases are modified. --- diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 4639dc1f5c..a314ea13d3 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -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 \ diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.h b/src/lib/dhcpsrv/dhcpsrv_messages.h index b5905a3743..637732f4fd 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.h +++ b/src/lib/dhcpsrv/dhcpsrv_messages.h @@ -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; diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes index 368049d322..7a8226eee8 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.mes +++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes @@ -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 diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index da833009d1..f7228441ba 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -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 diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc index b74bb9d10a..54c9332b95 100644 --- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -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& 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& 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 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 relay_id6_; - list 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 index 0000000000..d5bde6b268 --- /dev/null +++ b/src/lib/dhcpsrv/tests/tracking_lease_mgr_unittest.cc @@ -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 + +#include +#include +#include +#include +#include +#include +#include + +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 + boost::shared_ptr createLease(int subnet_id, std::string address) const { + auto lease = boost::make_shared(); + 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 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(1, "192.0.2.1"))); + EXPECT_FALSE(mgr.tryLock(createLease(1, "192.0.2.1"))); + + // We can lock another lease but we cannot lock an already locked one. + EXPECT_TRUE(mgr.tryLock(createLease(1, "192.0.2.2"))); + EXPECT_FALSE(mgr.tryLock(createLease(1, "192.0.2.1"))); + EXPECT_FALSE(mgr.tryLock(createLease(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(1, "192.0.2.1")); + EXPECT_FALSE(mgr.tryLock(createLease(2, "192.0.2.2"))); + EXPECT_TRUE(mgr.tryLock(createLease(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(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(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(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(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(1, "192.0.2.1"), false)); + EXPECT_NO_THROW(mgr.trackUpdateLease(createLease(1, "192.0.2.1"), false)); + EXPECT_NO_THROW(mgr.trackDeleteLease(createLease(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(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 diff --git a/src/lib/dhcpsrv/testutils/Makefile.am b/src/lib/dhcpsrv/testutils/Makefile.am index 8a84acffb8..e7060b5bce 100644 --- a/src/lib/dhcpsrv/testutils/Makefile.am +++ b/src/lib/dhcpsrv/testutils/Makefile.am @@ -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 index 0000000000..d04fd34ccc --- /dev/null +++ b/src/lib/dhcpsrv/testutils/concrete_lease_mgr.cc @@ -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 + +#include +#include + +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& 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& 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 +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 index 0000000000..3b97a492b3 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/concrete_lease_mgr.h @@ -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 + +#include +#include +#include +#include +#include + +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& 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& 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 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 relay_id6_; + std::list 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 index 0000000000..79697f3ced --- /dev/null +++ b/src/lib/dhcpsrv/tracking_lease_mgr.cc @@ -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 + +#include +#include +#include +#include +#include + +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 index 0000000000..7db4d5df1b --- /dev/null +++ b/src/lib/dhcpsrv/tracking_lease_mgr.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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, + boost::multi_index::member + > + >, + boost::multi_index::hashed_non_unique< + boost::multi_index::member + > + > + > 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 locked_leases_; +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + + +#endif // TRACKING_LEASE_MGR_H