]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3649] Improve reclamation in terminated state
authorMarcin Siodelski <marcin@isc.org>
Wed, 27 Nov 2024 07:24:50 +0000 (08:24 +0100)
committerMarcin Siodelski <marcin@isc.org>
Thu, 23 Jan 2025 09:03:59 +0000 (09:03 +0000)
15 files changed:
src/hooks/dhcp/high_availability/ha_callouts.cc
src/hooks/dhcp/high_availability/ha_impl.cc
src/hooks/dhcp/high_availability/ha_impl.h
src/hooks/dhcp/high_availability/ha_messages.cc
src/hooks/dhcp/high_availability/ha_messages.h
src/hooks/dhcp/high_availability/ha_messages.mes
src/hooks/dhcp/high_availability/ha_service.cc
src/hooks/dhcp/high_availability/ha_service.h
src/hooks/dhcp/high_availability/query_filter.cc
src/hooks/dhcp/high_availability/query_filter.h
src/hooks/dhcp/high_availability/tests/ha_impl_unittest.cc
src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc
src/hooks/dhcp/high_availability/tests/ha_test.cc
src/hooks/dhcp/high_availability/tests/ha_test.h
src/hooks/dhcp/high_availability/tests/query_filter_unittest.cc

index 0075d71320f570aca8ef770af914b74acfe6ebe7..9fba3cfd1faff03b0edcfd66bcb44fa2c07b7e78 100644 (file)
@@ -152,6 +152,25 @@ int lease4_server_decline(CalloutHandle& handle) {
     return (0);
 }
 
+/// @brief lease4_expire callout implementation.
+///
+/// @param handle callout handle.
+int lease4_expire(CalloutHandle& handle) {
+    CalloutHandle::CalloutNextStep status = handle.getStatus();
+    if (status == CalloutHandle::NEXT_STEP_SKIP) {
+        return (0);
+    }
+
+    try {
+        impl->lease4Expire(handle);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(ha_logger, HA_LEASE4_EXPIRE_FAILED)
+            .arg(ex.what());
+        return (1);
+    }
+
+    return (0);
+}
 
 /// @brief dhcp6_srv_configured callout implementation.
 ///
@@ -241,6 +260,26 @@ int leases6_committed(CalloutHandle& handle) {
     return (0);
 }
 
+/// @brief lease6_expire callout implementation.
+///
+/// @param handle callout handle.
+int lease6_expire(CalloutHandle& handle) {
+    CalloutHandle::CalloutNextStep status = handle.getStatus();
+    if (status == CalloutHandle::NEXT_STEP_SKIP) {
+        return (0);
+    }
+
+    try {
+        impl->lease6Expire(handle);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(ha_logger, HA_LEASE6_EXPIRE_FAILED)
+            .arg(ex.what());
+        return (1);
+    }
+
+    return (0);
+}
+
 /// @brief command_processed callout implementation.
 ///
 /// @param handle callout handle.
index ab542e0eed5fda0b3226dde16cf30e30161e1af2..cc34fa33b3a9539a03273862027670193599c4ce 100644 (file)
@@ -14,6 +14,7 @@
 #include <cc/command_interpreter.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt6.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
@@ -335,6 +336,63 @@ HAImpl::lease4ServerDecline(CalloutHandle& callout_handle) {
     callout_handle.setArgument("peers_to_update", peers_to_update);
 }
 
+void
+HAImpl::lease4Expire(CalloutHandle& callout_handle) {
+    Lease4Ptr lease4;
+    callout_handle.getArgument("lease4", lease4);
+
+    // If there are multiple relationships we need to take a detour and find
+    // a subnet the lease belongs to. The subnet will contain the information
+    // required to select appropriate HA service.
+    HAServicePtr service;
+    if (services_->hasMultiple()) {
+        auto subnet4 = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease4->subnet_id_);
+        if (!subnet4) {
+            // No subnet means that we possibly have some stale leases that don't
+            // really belong to us. Therefore, there we return early and rely on the
+            // DHCP server to reclaim them. The HA hook has no way to juristiction here.
+            return;
+        }
+
+        std::string server_name;
+        try {
+            server_name = HAConfig::getSubnetServerName(subnet4);
+            if (server_name.empty()) {
+                // Again, this subnet has no hint for HA where our lease belongs.
+                // We have to rely on the server to run reclamation of this lease.
+                return;
+            }
+        } catch (...) {
+            // Someone has tried to configure the hint for HA in the subnet but
+            // it was poorly specified. We will log an error and leave again.
+            LOG_ERROR(ha_logger, HA_LEASE4_EXPIRE_INVALID_HA_SERVER_NAME)
+                .arg(lease4->addr_.toText())
+                .arg(subnet4->toText());
+            return;
+        }
+        service = services_->get(server_name);
+
+    } else {
+        service = services_->get();
+    }
+
+    if (!service) {
+        // This is highly unlikely but better handle null pointers.
+        return;
+    }
+
+    if (!shouldReclaim(service, lease4)) {
+        // While the server is in the terminated state it has to be careful about
+        // reclaiming the leases to avoid conflicting DNS updates with a server that
+        // owns the lease. This lease apparently belongs to another server, so we
+        // should not reclaim it.
+        LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LEASE4_EXPIRE_RECLAMATION_SKIP)
+            .arg(lease4->addr_.toText());
+        callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+        return;
+    }
+}
+
 void
 HAImpl::buffer6Receive(hooks::CalloutHandle& callout_handle) {
     // If there are multiple relationships, the HA-specific processing is
@@ -559,6 +617,63 @@ HAImpl::leases6Committed(CalloutHandle& callout_handle) {
     callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
 }
 
+void
+HAImpl::lease6Expire(CalloutHandle& callout_handle) {
+    Lease6Ptr lease6;
+    callout_handle.getArgument("lease6", lease6);
+
+    // If there are multiple relationships we need to take a detour and find
+    // a subnet the lease belongs to. The subnet will contain the information
+    // required to select appropriate HA service.
+    HAServicePtr service;
+    if (services_->hasMultiple()) {
+        auto subnet6 = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease6->subnet_id_);
+        if (!subnet6) {
+            // No subnet means that we possibly have some stale leases that don't
+            // really belong to us. Therefore, there we return early and rely on the
+            // DHCP server to reclaim them. The HA hook has no way to juristiction here.
+            return;
+        }
+
+        std::string server_name;
+        try {
+            server_name = HAConfig::getSubnetServerName(subnet6);
+            if (server_name.empty()) {
+                // Again, this subnet has no hint for HA where our lease belongs.
+                // We have to rely on the server to run reclamation of this lease.
+                return;
+            }
+        } catch (...) {
+            // Someone has tried to configure the hint for HA in the subnet but
+            // it was poorly specified. We will log an error and leave again.
+            LOG_ERROR(ha_logger, HA_LEASE6_EXPIRE_INVALID_HA_SERVER_NAME)
+                .arg(lease6->addr_.toText())
+                .arg(subnet6->toText());
+            return;
+        }
+        service = services_->get(server_name);
+
+    } else {
+        service = services_->get();
+    }
+
+    if (!service) {
+        // This is highly unlikely but better handle null pointers.
+        return;
+    }
+
+    if (!shouldReclaim(service, lease6)) {
+        // While the server is in the terminated state it has to be careful about
+        // reclaiming the leases to avoid conflicting DNS updates with a server that
+        // owns the lease. This lease apparently belongs to another server, so we
+        // should not reclaim it.
+        LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LEASE6_EXPIRE_RECLAMATION_SKIP)
+            .arg(lease6->addr_.toText());
+        callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+        return;
+    }
+}
+
 void
 HAImpl::commandProcessed(hooks::CalloutHandle& callout_handle) {
     std::string command_name;
@@ -933,5 +1048,16 @@ HAImpl::getHAServiceByServerName(const std::string& command_name, ConstElementPt
     return (service);
 }
 
+bool
+HAImpl::shouldReclaim(const HAServicePtr& service, const dhcp::Lease4Ptr& lease4) const {
+    return (service->shouldReclaim(lease4));
+}
+
+bool
+HAImpl::shouldReclaim(const HAServicePtr& service, const dhcp::Lease6Ptr& lease6) const {
+    return (service->shouldReclaim(lease6));
+}
+
+
 } // end of namespace isc::ha
 } // end of namespace isc
index 9c5e652f3d6b9a9556877edd9445f9a3b3dcb1cc..ac17cb65f8d0542d7f909be2fdcb3043516e9ca5 100644 (file)
@@ -53,7 +53,7 @@ public:
                        const HAServerType& server_type);
 
     /// @brief Destructor.
-    ~HAImpl();
+    virtual ~HAImpl();
 
     /// @brief Returns a configuration for the first relationship.
     ///
@@ -118,6 +118,11 @@ public:
     /// @param callout_handle Callout handle provided to the callout.
     void lease4ServerDecline(hooks::CalloutHandle& callout_handle);
 
+    /// @brief Implementation of the "lease4_expire" callout.
+    ///
+    /// @param callout_handle Callout handle provided to the callout.
+    void lease4Expire(hooks::CalloutHandle& callout_handle);
+
     /// @brief Implementation of the "buffer6_receive" callout.
     ///
     /// This callout uses HA service to check if the query should be processed
@@ -154,6 +159,11 @@ public:
     /// @param callout_handle Callout handle provided to the callout.
     void leases6Committed(hooks::CalloutHandle& callout_handle);
 
+    /// @brief Implementation of the "lease6_expire" callout.
+    ///
+    /// @param callout_handle Callout handle provided to the callout.
+    void lease6Expire(hooks::CalloutHandle& callout_handle);
+
     /// @brief Implementation of the "command_processed" callout.
     ///
     /// It adds the HA servers information to "status-get" command responses by
@@ -237,6 +247,34 @@ public:
 
 protected:
 
+    /// @brief Checks if the lease should be reclaimed by this server.
+    ///
+    /// The lease must not be reclaimed by the server when the server is in the
+    /// terminated state and the lease belongs to another server (per load balancing
+    /// algorithm or when it is a standby server).
+    ///
+    /// This function is virtual so that it can be derived and mocked in the tests.
+    ///
+    /// @param service pointer to the HA service to which the lease belongs.
+    /// @param lease4 pointer to the DHCPv4 lease being reclaimed.
+    /// @return true if the DHCPv4 lease should be reclaimed by this server instance,
+    /// false otherwise.
+    virtual bool shouldReclaim(const HAServicePtr& service, const dhcp::Lease4Ptr& lease4) const;
+
+    /// @brief Checks if the lease should be reclaimed by this server.
+    ///
+    /// The lease must not be reclaimed by the server when the server is in the
+    /// terminated state and the lease belongs to another server (per load balancing
+    /// algorithm or when it is a standby server).
+    ///
+    /// This function is virtual so that it can be derived and mocked in the tests.
+    ///
+    /// @param service pointer to the HA service to which the lease belongs.
+    /// @param lease6 pointer to the DHCPv4 lease being reclaimed.
+    /// @return true if the DHCPv6 lease should be reclaimed by this server instance,
+    /// false otherwise.
+    virtual bool shouldReclaim(const HAServicePtr& service, const dhcp::Lease6Ptr& lease6) const;
+
     /// @brief The hook I/O service.
     isc::asiolink::IOServicePtr io_service_;
 
index b3f6377dd7a0d61f0fcef2627e813f53bf143ce2..19308e671a1a62eefdc98fc812cade0962560d0f 100644 (file)
@@ -49,7 +49,13 @@ extern const isc::log::MessageID HA_INIT_OK = "HA_INIT_OK";
 extern const isc::log::MessageID HA_INVALID_PARTNER_STATE_COMMUNICATION_RECOVERY = "HA_INVALID_PARTNER_STATE_COMMUNICATION_RECOVERY";
 extern const isc::log::MessageID HA_INVALID_PARTNER_STATE_HOT_STANDBY = "HA_INVALID_PARTNER_STATE_HOT_STANDBY";
 extern const isc::log::MessageID HA_INVALID_PARTNER_STATE_LOAD_BALANCING = "HA_INVALID_PARTNER_STATE_LOAD_BALANCING";
+extern const isc::log::MessageID HA_LEASE4_EXPIRE_FAILED = "HA_LEASE4_EXPIRE_FAILED";
+extern const isc::log::MessageID HA_LEASE4_EXPIRE_INVALID_HA_SERVER_NAME = "HA_LEASE4_EXPIRE_INVALID_HA_SERVER_NAME";
+extern const isc::log::MessageID HA_LEASE4_EXPIRE_RECLAMATION_SKIP = "HA_LEASE4_EXPIRE_RECLAMATION_SKIP";
 extern const isc::log::MessageID HA_LEASE4_SERVER_DECLINE_FAILED = "HA_LEASE4_SERVER_DECLINE_FAILED";
+extern const isc::log::MessageID HA_LEASE6_EXPIRE_FAILED = "HA_LEASE6_EXPIRE_FAILED";
+extern const isc::log::MessageID HA_LEASE6_EXPIRE_INVALID_HA_SERVER_NAME = "HA_LEASE6_EXPIRE_INVALID_HA_SERVER_NAME";
+extern const isc::log::MessageID HA_LEASE6_EXPIRE_RECLAMATION_SKIP = "HA_LEASE6_EXPIRE_RECLAMATION_SKIP";
 extern const isc::log::MessageID HA_LEASES4_COMMITTED_FAILED = "HA_LEASES4_COMMITTED_FAILED";
 extern const isc::log::MessageID HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE = "HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE";
 extern const isc::log::MessageID HA_LEASES4_COMMITTED_NO_RELATIONSHIP = "HA_LEASES4_COMMITTED_NO_RELATIONSHIP";
@@ -78,6 +84,8 @@ extern const isc::log::MessageID HA_LEASE_UPDATE_FAILED = "HA_LEASE_UPDATE_FAILE
 extern const isc::log::MessageID HA_LEASE_UPDATE_REJECTS_CAUSED_TERMINATION = "HA_LEASE_UPDATE_REJECTS_CAUSED_TERMINATION";
 extern const isc::log::MessageID HA_LOAD_BALANCING_DUID_MISSING = "HA_LOAD_BALANCING_DUID_MISSING";
 extern const isc::log::MessageID HA_LOAD_BALANCING_IDENTIFIER_MISSING = "HA_LOAD_BALANCING_IDENTIFIER_MISSING";
+extern const isc::log::MessageID HA_LOAD_BALANCING_LEASE_DUID_MISSING = "HA_LOAD_BALANCING_LEASE_DUID_MISSING";
+extern const isc::log::MessageID HA_LOAD_BALANCING_LEASE_IDENTIFIER_MISSING = "HA_LOAD_BALANCING_LEASE_IDENTIFIER_MISSING";
 extern const isc::log::MessageID HA_LOCAL_DHCP_DISABLE = "HA_LOCAL_DHCP_DISABLE";
 extern const isc::log::MessageID HA_LOCAL_DHCP_ENABLE = "HA_LOCAL_DHCP_ENABLE";
 extern const isc::log::MessageID HA_MAINTENANCE_CANCEL_HANDLER_FAILED = "HA_MAINTENANCE_CANCEL_HANDLER_FAILED";
@@ -174,7 +182,13 @@ const char* values[] = {
     "HA_INVALID_PARTNER_STATE_COMMUNICATION_RECOVERY", "%1: partner is in the communication-recovery state unexpectedly",
     "HA_INVALID_PARTNER_STATE_HOT_STANDBY", "%1: partner is in the hot-standby state unexpectedly",
     "HA_INVALID_PARTNER_STATE_LOAD_BALANCING", "%1: partner is in the load-balancing state unexpectedly",
+    "HA_LEASE4_EXPIRE_FAILED", "lease4_expire callout failed: %1",
+    "HA_LEASE4_EXPIRE_INVALID_HA_SERVER_NAME", "%1: invalid ha-server-name value for subnet %2",
+    "HA_LEASE4_EXPIRE_RECLAMATION_SKIP", "%1: skipping reclamation of the lease that belongs to a partner",
     "HA_LEASE4_SERVER_DECLINE_FAILED", "lease4_server_decline callout failed: %1",
+    "HA_LEASE6_EXPIRE_FAILED", "lease4_expire callout failed: %1",
+    "HA_LEASE6_EXPIRE_INVALID_HA_SERVER_NAME", "%1: invalid ha-server-name value for subnet %2",
+    "HA_LEASE6_EXPIRE_RECLAMATION_SKIP", "%1: skipping reclamation of the lease that belongs to a partner",
     "HA_LEASES4_COMMITTED_FAILED", "leases4_committed callout failed: %1",
     "HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE", "%1: leases4_committed callout was invoked without any leases",
     "HA_LEASES4_COMMITTED_NO_RELATIONSHIP", "%1: HA relationship not found: %2",
@@ -203,6 +217,8 @@ const char* values[] = {
     "HA_LEASE_UPDATE_REJECTS_CAUSED_TERMINATION", "%1: too many rejected lease updates cause the HA service to terminate",
     "HA_LOAD_BALANCING_DUID_MISSING", "%1: load balancing failed for the DHCPv6 message (transaction id: %2) because DUID is missing",
     "HA_LOAD_BALANCING_IDENTIFIER_MISSING", "%1: load balancing failed for the DHCPv4 message (transaction id: %2) because HW address and client identifier are missing",
+    "HA_LOAD_BALANCING_LEASE_DUID_MISSING", "%1: load balancing failed for the DHCPv6 lease %2 because DUID is missing",
+    "HA_LOAD_BALANCING_LEASE_IDENTIFIER_MISSING", "%1: load balancing failed for the DHCPv4 lease %2 because HW address and client identifier are missing",
     "HA_LOCAL_DHCP_DISABLE", "local DHCP service is disabled while the %1 is in the %2 state",
     "HA_LOCAL_DHCP_ENABLE", "local DHCP service is enabled while the %1 is in the %2 state",
     "HA_MAINTENANCE_CANCEL_HANDLER_FAILED", "ha-maintenance-cancel command failed: %1",
index c2597a8d4f37059ebed696928f51f01450a84026..334c999210fe5a3deb3344b13aa7e0d00a616e7d 100644 (file)
@@ -50,7 +50,13 @@ extern const isc::log::MessageID HA_INIT_OK;
 extern const isc::log::MessageID HA_INVALID_PARTNER_STATE_COMMUNICATION_RECOVERY;
 extern const isc::log::MessageID HA_INVALID_PARTNER_STATE_HOT_STANDBY;
 extern const isc::log::MessageID HA_INVALID_PARTNER_STATE_LOAD_BALANCING;
+extern const isc::log::MessageID HA_LEASE4_EXPIRE_FAILED;
+extern const isc::log::MessageID HA_LEASE4_EXPIRE_INVALID_HA_SERVER_NAME;
+extern const isc::log::MessageID HA_LEASE4_EXPIRE_RECLAMATION_SKIP;
 extern const isc::log::MessageID HA_LEASE4_SERVER_DECLINE_FAILED;
+extern const isc::log::MessageID HA_LEASE6_EXPIRE_FAILED;
+extern const isc::log::MessageID HA_LEASE6_EXPIRE_INVALID_HA_SERVER_NAME;
+extern const isc::log::MessageID HA_LEASE6_EXPIRE_RECLAMATION_SKIP;
 extern const isc::log::MessageID HA_LEASES4_COMMITTED_FAILED;
 extern const isc::log::MessageID HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE;
 extern const isc::log::MessageID HA_LEASES4_COMMITTED_NO_RELATIONSHIP;
@@ -79,6 +85,8 @@ extern const isc::log::MessageID HA_LEASE_UPDATE_FAILED;
 extern const isc::log::MessageID HA_LEASE_UPDATE_REJECTS_CAUSED_TERMINATION;
 extern const isc::log::MessageID HA_LOAD_BALANCING_DUID_MISSING;
 extern const isc::log::MessageID HA_LOAD_BALANCING_IDENTIFIER_MISSING;
+extern const isc::log::MessageID HA_LOAD_BALANCING_LEASE_DUID_MISSING;
+extern const isc::log::MessageID HA_LOAD_BALANCING_LEASE_IDENTIFIER_MISSING;
 extern const isc::log::MessageID HA_LOCAL_DHCP_DISABLE;
 extern const isc::log::MessageID HA_LOCAL_DHCP_ENABLE;
 extern const isc::log::MessageID HA_MAINTENANCE_CANCEL_HANDLER_FAILED;
index bd1e2e2a8fae6f24672543335578238164146407..771c22354015f8d1501fa7aea337e9c43def1224 100644 (file)
@@ -449,6 +449,46 @@ number of lease updates for which a conflict status code was returned
 by the partner exceeds the limit set with max-rejected-lease-updates
 configuration parameter.
 
+% HA_LEASE4_EXPIRE_FAILED lease4_expire callout failed: %1
+This error message is issued when the callout for the lease4_expire hook
+point failed. This includes unexpected errors like wrong arguments provided to
+the callout by the DHCP server (unlikely internal server error).
+The argument contains a reason for the error.
+
+% HA_LEASE4_EXPIRE_INVALID_HA_SERVER_NAME %1: invalid ha-server-name value for subnet %2
+This error message is issued when the reclaimed DHCPv4 lease belongs to
+a subnet which includes ha-server-name value in the user-context but this
+value is not a string or is empty. It is a server's misconifguration.
+The first argument holds the lease information. The second argument is a
+subnet prefix.
+
+% HA_LEASE4_EXPIRE_RECLAMATION_SKIP %1: skipping reclamation of the lease that belongs to a partner
+Logged at debug log level 40.
+This debug message is issued when the server is in the terminated state and
+skips reclamation of the lease that was probably allocated by another server,
+or is maintained by the other server while the servers are in the HA terminated
+state. The argument is the lease address.
+
+% HA_LEASE6_EXPIRE_FAILED lease4_expire callout failed: %1
+This error message is issued when the callout for the lease4_expire hook
+point failed. This includes unexpected errors like wrong arguments provided to
+the callout by the DHCP server (unlikely internal server error).
+The argument contains a reason for the error.
+
+% HA_LEASE6_EXPIRE_INVALID_HA_SERVER_NAME %1: invalid ha-server-name value for subnet %2
+This error message is issued when the reclaimed DHCPv6 lease belongs to
+a subnet which includes ha-server-name value in the user-context but this
+value is not a string or is empty. It is a server's misconifguration.
+The first argument holds the lease information. The second argument is a
+subnet prefix.
+
+% HA_LEASE6_EXPIRE_RECLAMATION_SKIP %1: skipping reclamation of the lease that belongs to a partner
+Logged at debug log level 40.
+This debug message is issued when the server is in the terminated state and
+skips reclamation of the lease that was probably allocated by another server,
+or is maintained by the other server while the servers are in the HA terminated
+state. The argument is the lease address.
+
 % HA_LOAD_BALANCING_DUID_MISSING %1: load balancing failed for the DHCPv6 message (transaction id: %2) because DUID is missing
 Logged at debug log level 40.
 This debug message is issued when the HA hook library was unable to load
@@ -463,6 +503,18 @@ balance an incoming DHCPv4 query because neither client identifier nor
 HW address was included in the query. The query will be dropped. The
 sole argument contains transaction id.
 
+% HA_LOAD_BALANCING_LEASE_DUID_MISSING %1: load balancing failed for the DHCPv6 lease %2 because DUID is missing
+Logged at debug log level 40.
+This debug message is issued when the HA hook library was unable to load
+balance a reclaimed DHCPv6 lease because client identifier was not included
+found in the lease.
+
+% HA_LOAD_BALANCING_LEASE_IDENTIFIER_MISSING %1: load balancing failed for the DHCPv4 lease %2 because HW address and client identifier are missing
+Logged at debug log level 40.
+This debug message is issued when the HA hook library was unable to load
+balance a reclaimed DHCPv4 lease because neither client identifier nor
+HW address was included in the query.
+
 % HA_LOCAL_DHCP_DISABLE local DHCP service is disabled while the %1 is in the %2 state
 This informational message is issued to indicate that the local DHCP service
 is disabled because the server remains in a state in which the server
index c2de6b958cb3d47fa51d4b5c9e7cfcc19a488e41..2a8e80b3cb09bba95c11a949426f873cb571f10a 100644 (file)
@@ -1085,6 +1085,22 @@ HAService::inScopeInternal(QueryPtrType& query) {
     return (in_scope);
 }
 
+bool
+HAService::shouldReclaim(const dhcp::Lease4Ptr& lease4) const {
+    return (shouldReclaimInternal(lease4));
+}
+
+bool
+HAService::shouldReclaim(const dhcp::Lease6Ptr& lease6) const {
+    return (shouldReclaimInternal(lease6));
+}
+
+template<typename LeasePtrType>
+bool
+HAService::shouldReclaimInternal(const LeasePtrType& lease) const {
+    return (getCurrState() != HA_TERMINATED_ST || query_filter_.inScope(lease));
+}
+
 void
 HAService::adjustNetworkState() {
     std::string current_state_name = getStateLabel(getCurrState());
index 885254ea233782cf7080a6a1a935522a6aa5e00a..64e538c6db3733f71b120e53ec2a93f958519eb8 100644 (file)
@@ -492,6 +492,45 @@ private:
     template<typename QueryPtrType>
     bool inScopeInternal(QueryPtrType& query);
 
+public:
+
+    /// @brief Checks if the lease should be reclaimed by this server.
+    ///
+    /// The lease must not be reclaimed by the server when the server is in the
+    /// terminated state and the lease belongs to another server (per load balancing
+    /// algorithm or when it is a standby server).
+    ///
+    /// @param lease4 pointer to the DHCPv4 lease being reclaimed.
+    /// @return true if the DHCPv4 lease should be reclaimed by this server instance,
+    /// false otherwise.
+    bool shouldReclaim(const dhcp::Lease4Ptr& lease4) const;
+
+    /// @brief Checks if the lease should be reclaimed by this server.
+    ///
+    /// The lease must not be reclaimed by the server when the server is in the
+    /// terminated state and the lease belongs to another server (per load balancing
+    /// algorithm or when it is a standby server).
+    ///
+    /// @param lease6 pointer to the DHCPv4 lease being reclaimed.
+    /// @return true if the DHCPv6 lease should be reclaimed by this server instance,
+    /// false otherwise.
+    bool shouldReclaim(const dhcp::Lease6Ptr& lease6) const;
+
+private:
+
+    /// @brief Checks if the lease should be reclaimed by this server.
+    ///
+    /// The lease must not be reclaimed by the server when the server is in the
+    /// terminated state and the lease belongs to another server (per load balancing
+    /// algorithm or when it is a standby server).
+    ///
+    /// @tparam LeasePtrType type of the pointer to the DHCP lease.
+    /// @param lease pointer to the DHCP lease being reclaimed.
+    /// @return true if the DHCP lease should be reclaimed by this server instance,
+    /// false otherwise.
+    template<typename LeaseTypePtr>
+    bool shouldReclaimInternal(const LeaseTypePtr& lease) const;
+
 public:
 
     /// @brief Enables or disables network state depending on the served scopes.
index 2b2c08c304498da67cfa4e4fab12d9f13a05ef99..b172204277d04396c91a7e2eee0a1aa5bdb5bb2d 100644 (file)
@@ -363,9 +363,9 @@ bool
 QueryFilter::inScope(const dhcp::Pkt4Ptr& query4, std::string& scope_class) const {
     if (MultiThreadingMgr::instance().getMode()) {
         std::lock_guard<std::mutex> lock(*mutex_);
-        return (inScopeInternal(query4, scope_class));
+        return (queryInScopeInternal(query4, scope_class));
     } else {
-        return (inScopeInternal(query4, scope_class));
+        return (queryInScopeInternal(query4, scope_class));
     }
 }
 
@@ -373,21 +373,20 @@ bool
 QueryFilter::inScope(const dhcp::Pkt6Ptr& query6, std::string& scope_class) const {
     if (MultiThreadingMgr::instance().getMode()) {
         std::lock_guard<std::mutex> lock(*mutex_);
-        return (inScopeInternal(query6, scope_class));
+        return (queryInScopeInternal(query6, scope_class));
     } else {
-        return (inScopeInternal(query6, scope_class));
+        return (queryInScopeInternal(query6, scope_class));
     }
 }
 
 template<typename QueryPtrType>
 bool
-QueryFilter::inScopeInternal(const QueryPtrType& query,
+QueryFilter::queryInScopeInternal(const QueryPtrType& query,
                              std::string& scope_class) const {
     if (!query) {
         isc_throw(BadValue, "query must not be null");
     }
 
-
     // If it's not a type HA cares about, it's in scope for this peer.
     if (!isHaType(query)) {
         auto scope = peers_[0]->getName();
@@ -413,6 +412,50 @@ QueryFilter::inScopeInternal(const QueryPtrType& query,
     return ((candidate_server >= 0) && amServingScopeInternal(scope));
 }
 
+bool
+QueryFilter::inScope(const Lease4Ptr& lease4) const {
+    if (MultiThreadingMgr::instance().getMode()) {
+        std::lock_guard<std::mutex> lock(*mutex_);
+        return (leaseInScopeInternal(lease4));
+    } else {
+        return (leaseInScopeInternal(lease4));
+    }
+}
+
+bool
+QueryFilter::inScope(const Lease6Ptr& lease6) const {
+    if (MultiThreadingMgr::instance().getMode()) {
+        std::lock_guard<std::mutex> lock(*mutex_);
+        return (leaseInScopeInternal(lease6));
+    } else {
+        return (leaseInScopeInternal(lease6));
+    }
+}
+
+template<typename LeasePtrType>
+bool
+QueryFilter::leaseInScopeInternal(const LeasePtrType& lease) const {
+    if (!lease) {
+        isc_throw(BadValue, "lease must not be null");
+    }
+
+    int candidate_server = 0;
+
+    // If we're doing load balancing we have to check if this query
+    // belongs to us or the partner. If it belongs to a partner but
+    // we're configured to serve this scope, we should accept it.
+    if (config_->getHAMode() == HAConfig::LOAD_BALANCING) {
+        candidate_server = loadBalance(lease);
+        // Malformed query received.
+        if (candidate_server < 0) {
+            return (false);
+        }
+    }
+
+    auto scope = peers_[candidate_server]->getName();
+    return ((candidate_server >= 0) && amServingScopeInternal(scope));
+}
+
 int
 QueryFilter::loadBalance(const dhcp::Pkt4Ptr& query4) const {
     uint8_t lb_hash = 0;
@@ -470,6 +513,58 @@ QueryFilter::loadBalance(const dhcp::Pkt6Ptr& query6) const {
     return (active_servers_ > 0 ? static_cast<int>(lb_hash % active_servers_) : -1);
 }
 
+int
+QueryFilter::loadBalance(const dhcp::Lease4Ptr& lease4) const {
+    uint8_t lb_hash = 0;
+    // Try to compute the hash by client identifier if the client
+    // identifier has been specified.
+    if (lease4->client_id_ && !lease4->client_id_->getClientId().empty()) {
+        auto const& client_id_key = lease4->client_id_->getClientId();
+        lb_hash = loadBalanceHash(&client_id_key[0], client_id_key.size());
+
+    } else {
+        // No client identifier available. Use the HW address instead.
+        HWAddrPtr hwaddr = lease4->hwaddr_;
+        if (hwaddr && !hwaddr->hwaddr_.empty()) {
+            lb_hash = loadBalanceHash(&hwaddr->hwaddr_[0], hwaddr->hwaddr_.size());
+
+        } else {
+            // No client identifier and no HW address. Indicate an
+            // error.
+            LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LOAD_BALANCING_LEASE_IDENTIFIER_MISSING)
+                .arg(config_->getThisServerName())
+                .arg(lease4->addr_);
+            return (-1);
+        }
+    }
+
+    // The hash value modulo number of active servers gives an index
+    // of the server to process the packet.
+    return (active_servers_ > 0 ? static_cast<int>(lb_hash % active_servers_) : -1);
+}
+
+int
+QueryFilter::loadBalance(const dhcp::Lease6Ptr& lease6) const {
+    uint8_t lb_hash = 0;
+    // Compute the hash by DUID if the DUID.
+    auto duid = lease6->duid_;
+    if (duid && !duid->getDuid().empty()) {
+        auto const& duid_key = duid->getDuid();
+        lb_hash = loadBalanceHash(&duid_key[0], duid_key.size());
+
+    } else {
+        // No DUID. Indicate an error.
+        LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LOAD_BALANCING_LEASE_DUID_MISSING)
+            .arg(config_->getThisServerName())
+            .arg(lease6->addr_);
+        return (-1);
+    }
+
+    // The hash value modulo number of active servers gives an index
+    // of the server to process the packet.
+    return (active_servers_ > 0 ? static_cast<int>(lb_hash % active_servers_) : -1);
+}
+
 uint8_t
 QueryFilter::loadBalanceHash(const uint8_t* key, const size_t key_len) const {
     uint8_t hash  = static_cast<uint8_t>(key_len);
index 64405e43d0e3a0d666fd2f6ab1be32bcef11863b..56577981d18b6dbd9a090a1cfb0e8fd58fd42c97 100644 (file)
@@ -184,6 +184,36 @@ public:
     /// server, false otherwise.
     bool inScope(const dhcp::Pkt6Ptr& query6, std::string& scope_class) const;
 
+    /// @brief Checks if this server should reclaim the DHCPv4 lease.
+    ///
+    /// This method is used to select DHCPv4 leases that are allocated by this
+    /// server during the normal operation. It is used when the servers in the
+    /// terminated state must reclaim expired leases. In this case each server
+    /// can only reclaim the leases it is responsible for to avoid the situation
+    /// that one of the servers reclaims a lease that another server allocates,
+    /// as it would cause conflicts of the DNS updates between the servers.
+    ///
+    /// @param lease4 pointer to the DHCPv4 lease instance.
+    ///
+    /// @return true if the specified query should be processed by this
+    /// server, false otherwise.
+    bool inScope(const dhcp::Lease4Ptr& lease4) const;
+
+    /// @brief Checks if this server should process the DHCPv6 query.
+    ///
+    /// This method is used to select DHCPv4 leases that are allocated by this
+    /// server during the normal operation. It is used when the servers in the
+    /// terminated state must reclaim expired leases. In this case each server
+    /// can only reclaim the leases it is responsible for to avoid the situation
+    /// that one of the servers reclaims a lease that another server allocates,
+    /// as it would cause conflicts of the DNS updates between the servers.
+    ///
+    /// @param lease6 pointer to the DHCPv6 lease instance.
+    ///
+    /// @return true if the specified query should be processed by this
+    /// server, false otherwise.
+    bool inScope(const dhcp::Lease6Ptr& lease6) const;
+
     /// @brief Determines if a DHCPv4 query is a message type HA should process.
     ///
     /// @param query4 DHCPv4 packet to test. Must not be null.
@@ -285,7 +315,20 @@ private:
     /// @return true if the specified query should be processed by this
     /// server, false otherwise.
     template<typename QueryPtrType>
-    bool inScopeInternal(const QueryPtrType& query, std::string& scope_class) const;
+    bool queryInScopeInternal(const QueryPtrType& query, std::string& scope_class) const;
+
+    /// @brief Generic implementation of the @c inScope function for DHCPv4
+    /// and DHCPv6 leases.
+    ///
+    /// Should be called in a thread safe context.
+    ///
+    /// @tparam LeasePtrType type of the lease, i.e. DHCPv4 or DHCPv6 lease.
+    /// @param lease pointer to the DHCP lease instance.
+    ///
+    /// @return true if the specified lease should be reclaimed by this
+    /// server, false otherwise.
+    template<typename LeasePtrType>
+    bool leaseInScopeInternal(const LeasePtrType& lease) const;
 
 protected:
 
@@ -317,6 +360,34 @@ protected:
     /// no DUID.
     int loadBalance(const dhcp::Pkt6Ptr& query6) const;
 
+    /// @brief Performs load balancing of the DHCPv4 leases.
+    ///
+    /// This method returns an index of the server configuration
+    /// held within @c peers_ vector. This points to a server
+    /// which should process the given lease. Currently, we only
+    /// support load balancing between two servers, therefore this
+    /// value should be 0 or 1.
+    ///
+    /// @param lease4 pointer to the DHCPv4 lease instance.
+    /// @return Index of the server which should process the lease. It
+    /// returns negative value if the lease is malformed, i.e. contains
+    /// no HW address and no client identifier.
+    int loadBalance(const dhcp::Lease4Ptr& lease4) const;
+
+    /// @brief Performs load balancing of the DHCPv6 leases.
+    ///
+    /// This method returns an index of the server configuration
+    /// held within @c peers_ vector. This points to a server
+    /// which should process the given lease. Currently, we only
+    /// support load balancing between two servers, therefore this
+    /// value should be 0 or 1.
+    ///
+    /// @param lease6 pointer to the DHCPv6 lease instance.
+    /// @return Index of the server which should process the lease. It
+    /// returns negative value if the lease is malformed, i.e. contains
+    /// no DUID.
+    int loadBalance(const dhcp::Lease6Ptr& lease6) const;
+
     /// @brief Compute load balancing hash.
     ///
     /// The load balancing hash is computed according to section 6
index b5622c5b7ab4daf125f2277a6f7feadc6e54d0b9..9e264c640fd8a1af8c369fd712af731c2d3d5f12 100644 (file)
@@ -14,6 +14,7 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/network_state.h>
 #include <dhcpsrv/shared_network.h>
@@ -76,6 +77,37 @@ public:
 
     using HAImpl::config_;
     using HAImpl::services_;
+
+    /// @brief Constructor.
+    TestHAImpl() :
+        HAImpl(), should_reclaim_dhcpv4_lease_(true),
+        should_reclaim_dhcpv6_lease_(true) { }
+
+    /// @brief Mock function checking if the lease should be reclaimed by this server.
+    ///
+    /// @param service pointer to the HA service to which the lease belongs.
+    /// @param lease4 pointer to the DHCPv4 lease being reclaimed.
+    /// @return true if the DHCPv4 lease should be reclaimed by this server instance,
+    /// false otherwise.
+    virtual bool shouldReclaim(const HAServicePtr& service, const Lease4Ptr& lease4) const {
+        return (should_reclaim_dhcpv4_lease_);
+    }
+
+    /// @brief Mock function checking if the lease should be reclaimed by this server.
+    ///
+    /// @param service pointer to the HA service to which the lease belongs.
+    /// @param lease6 pointer to the DHCPv4 lease being reclaimed.
+    /// @return true if the DHCPv6 lease should be reclaimed by this server instance,
+    /// false otherwise.
+    virtual bool shouldReclaim(const HAServicePtr& service, const Lease6Ptr& lease6) const {
+        return (should_reclaim_dhcpv6_lease_);
+    }
+
+    /// @brief Custom value to be returned by the @c TestHAImpl::shouldReclaim function.
+    bool should_reclaim_dhcpv4_lease_;
+
+    /// @brief Custom value to be returned by the @c TestHAImpl::shouldReclaim function.
+    bool should_reclaim_dhcpv6_lease_;
 };
 
 /// @brief Test fixture class for @c HAImpl.
@@ -85,6 +117,8 @@ public:
     HAImplTest() {
         // Clear statistics.
         StatsMgr::instance().removeAll();
+        // Clear configuration.
+        CfgMgr::instance().clear();
     }
 
     /// @brief Destructor.
@@ -95,6 +129,8 @@ public:
         io_service_->stopAndPoll();
         // Clear statistics.
         StatsMgr::instance().removeAll();
+        // Clear configuration.
+        CfgMgr::instance().clear();
     }
 
     /// @brief Fetches the current value of the given statistic.
@@ -1624,6 +1660,418 @@ TEST_F(HAImplTest, leases6CommittedMultipleRelationshipsInvalidServerName) {
     EXPECT_TRUE(callout_handle->getParkingLotHandlePtr()->drop(query6));
 }
 
+// Tests lease4_expire callout implementation when the server is a hub
+// with multiple relationships.
+TEST_F(HAImplTest, lease4ExpireHub) {
+    ConstElementPtr ha_config = createValidHubJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv4));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease4 = createLease4(std::vector<uint8_t>(6, 1));
+    lease4->addr_ = IOAddress("192.0.2.1");
+    lease4->subnet_id_ = 7;
+    callout_handle->setArgument("lease4", lease4);
+
+    // Create the subnet and include the server name in the context.
+    auto context = Element::createMap();
+    context->set("ha-server-name", Element::create("server3"));
+    auto subnet4 = Subnet4::create(IOAddress("192.0.2.0"), 24, 30, 40, 50, 7);
+    subnet4->setContext(context);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet4);
+    CfgMgr::instance().commit();
+
+    // Invoke the lease4_expire callout and expect that the lease reclamation
+    // will continue because the server is responsible for the lease.
+    ASSERT_NO_THROW(test_ha_impl_->lease4Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Mark the server not responsible for the lease. This time the reclamation
+    // should be skipped.
+    test_ha_impl_->should_reclaim_dhcpv4_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease4Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, callout_handle->getStatus());
+}
+
+// Tests lease4_expire callout implementation when the server is a hub
+// with multiple relationships and no matching subnet has been configured.
+TEST_F(HAImplTest, lease4ExpireHubNoSubnet) {
+    ConstElementPtr ha_config = createValidHubJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv4));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease4 = createLease4(std::vector<uint8_t>(6, 1));
+    lease4->addr_ = IOAddress("192.0.2.1");
+    lease4->subnet_id_ = 7;
+    callout_handle->setArgument("lease4", lease4);
+
+    test_ha_impl_->should_reclaim_dhcpv4_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease4Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+}
+
+// Tests lease4_expire callout implementation when the server is a hub
+// but the server name is empty.
+TEST_F(HAImplTest, lease4ExpireHubEmptyServerName) {
+    ConstElementPtr ha_config = createValidHubJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv4));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease4 = createLease4(std::vector<uint8_t>(6, 1));
+    lease4->addr_ = IOAddress("192.0.2.1");
+    lease4->subnet_id_ = 7;
+    callout_handle->setArgument("lease4", lease4);
+
+    // Create the subnet and include empty server name in the context.
+    auto context = Element::createMap();
+    context->set("ha-server-name", Element::create(""));
+    auto subnet4 = Subnet4::create(IOAddress("192.0.2.0"), 24, 30, 40, 50, 7);
+    subnet4->setContext(context);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet4);
+    CfgMgr::instance().commit();
+
+    // Even though we intruct the server to skip the reclamation, the returned
+    // status should indicate otherwise because the callout returns early after
+    // checking that the server name is empty.
+    test_ha_impl_->should_reclaim_dhcpv4_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease4Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+}
+
+// Tests lease4_expire callout implementation when the server is a hub
+// but the server name has invalid type.
+TEST_F(HAImplTest, lease4ExpireHubInvalidServerName) {
+    ConstElementPtr ha_config = createValidHubJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv4));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease4 = createLease4(std::vector<uint8_t>(6, 1));
+    lease4->addr_ = IOAddress("192.0.2.1");
+    lease4->subnet_id_ = 7;
+    callout_handle->setArgument("lease4", lease4);
+
+    // Create the subnet and include the server name argument. It has an
+    // invalid type.
+    auto context = Element::createMap();
+    context->set("ha-server-name", Element::create(123));
+    auto subnet4 = Subnet4::create(IOAddress("192.0.2.0"), 24, 30, 40, 50, 7);
+    subnet4->setContext(context);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet4);
+    CfgMgr::instance().commit();
+
+    // Even though we intruct the server to skip the reclamation, the returned
+    // status should indicate otherwise because the callout returns early after
+    // checking that the server name has invalid type.
+    test_ha_impl_->should_reclaim_dhcpv4_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease4Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+}
+
+// Tests lease4_expire callout implementation when the server is a hub
+// with multiple relationship.
+TEST_F(HAImplTest, lease4ExpireSingleService) {
+    ConstElementPtr ha_config = createValidJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv4));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease4 = createLease4(std::vector<uint8_t>(6, 1));
+    lease4->subnet_id_ = 7;
+    lease4->addr_ = IOAddress("192.0.2.1");
+    callout_handle->setArgument("lease4", lease4);
+
+    // Invoke the lease4_expire callout and expect that the lease reclamation
+    // will continue because the server is responsible for the lease.
+    ASSERT_NO_THROW(test_ha_impl_->lease4Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Mark the server not responsible for the lease. This time the reclamation
+    // should be skipped.
+    test_ha_impl_->should_reclaim_dhcpv4_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease4Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, callout_handle->getStatus());
+}
+
+// Tests lease4_expire callout implementation when the server is a hub
+// with multiple relationships.
+TEST_F(HAImplTest, lease6ExpireHub) {
+    ConstElementPtr ha_config = createValidHubJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv6));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease6 = createLease6(std::vector<uint8_t>(8, 1));
+    lease6->addr_ = IOAddress("2001:db8:1::1");
+    lease6->subnet_id_ = 7;
+    callout_handle->setArgument("lease6", lease6);
+
+    // Create the subnet and include the server name in the context.
+    auto context = Element::createMap();
+    context->set("ha-server-name", Element::create("server3"));
+    auto subnet6 = Subnet6::create(IOAddress("2001:db8:1::"), 64, 30, 40, 50, 60, 7);
+    subnet6->setContext(context);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet6);
+    CfgMgr::instance().commit();
+
+    // Invoke the lease6_expire callout and expect that the lease reclamation
+    // will continue because the server is responsible for the lease.
+    ASSERT_NO_THROW(test_ha_impl_->lease6Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Mark the server not responsible for the lease. This time the reclamation
+    // should be skipped.
+    test_ha_impl_->should_reclaim_dhcpv6_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease6Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, callout_handle->getStatus());
+}
+
+// Tests lease6_expire callout implementation when the server is a hub
+// with multiple relationships and no matching subnet has been configured.
+TEST_F(HAImplTest, lease6ExpireHubNoSubnet) {
+    ConstElementPtr ha_config = createValidHubJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv6));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease6 = createLease6(std::vector<uint8_t>(8, 1));
+    lease6->addr_ = IOAddress("2001:db8:1::1");
+    lease6->subnet_id_ = 7;
+    callout_handle->setArgument("lease6", lease6);
+
+    test_ha_impl_->should_reclaim_dhcpv6_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease6Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+}
+
+// Tests lease6_expire callout implementation when the server is a hub
+// but the server name is empty.
+TEST_F(HAImplTest, lease6ExpireHubEmptyServerName) {
+    ConstElementPtr ha_config = createValidHubJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv6));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease6 = createLease6(std::vector<uint8_t>(8, 1));
+    lease6->addr_ = IOAddress("2001:db8:1::1");
+    lease6->subnet_id_ = 7;
+    callout_handle->setArgument("lease6", lease6);
+
+    // Create the subnet and include the server name in the context.
+    auto context = Element::createMap();
+    context->set("ha-server-name", Element::create(""));
+    auto subnet6 = Subnet6::create(IOAddress("2001:db8:1::"), 64, 30, 40, 50, 60, 7);
+    subnet6->setContext(context);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet6);
+    CfgMgr::instance().commit();
+
+    // Even though we intruct the server to skip the reclamation, the returned
+    // status should indicate otherwise because the callout returns early after
+    // checking that the server name is empty.
+    test_ha_impl_->should_reclaim_dhcpv6_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease6Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+}
+
+// Tests lease4_expire callout implementation when the server is a hub
+// but the server name has invalid type.
+TEST_F(HAImplTest, lease6ExpireHubInvalidServerName) {
+    ConstElementPtr ha_config = createValidHubJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv4));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease6 = createLease6(std::vector<uint8_t>(8, 1));
+    lease6->addr_ = IOAddress("2001:db8:1::1");
+    lease6->subnet_id_ = 7;
+    callout_handle->setArgument("lease6", lease6);
+
+    // Create the subnet and include the server name argument. It has an
+    // invalid type.
+
+    // Create the subnet and include the server name in the context.
+    auto context = Element::createMap();
+    context->set("ha-server-name", Element::create(123));
+    auto subnet6 = Subnet6::create(IOAddress("2001:db8:1::"), 64, 30, 40, 50, 60, 7);
+    subnet6->setContext(context);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet6);
+    CfgMgr::instance().commit();
+
+    // Even though we intruct the server to skip the reclamation, the returned
+    // status should indicate otherwise because the callout returns early after
+    // checking that the server name has invalid type.
+    test_ha_impl_->should_reclaim_dhcpv6_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease6Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+}
+
+// Tests lease4_expire callout implementation when the server is a hub
+// with multiple relationship.
+TEST_F(HAImplTest, lease6ExpireSingleService) {
+    ConstElementPtr ha_config = createValidJsonConfiguration();
+
+    // Create implementation object and configure it.
+    test_ha_impl_.reset(new TestHAImpl());
+    test_ha_impl_->setIOService(io_service_);
+    ASSERT_NO_THROW(test_ha_impl_->configure(ha_config));
+
+    // Starting the service is required before any callouts.
+    NetworkStatePtr network_state(new NetworkState());
+    ASSERT_NO_THROW(test_ha_impl_->startServices(network_state,
+                                                 HAServerType::DHCPv4));
+
+    // Create callout handle to be used for passing arguments to the
+    // callout.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+    ASSERT_TRUE(callout_handle);
+    ASSERT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Create the lease
+    auto lease6 = createLease6(std::vector<uint8_t>(8, 1));
+    lease6->addr_ = IOAddress("2001:db8:1::1");
+    lease6->subnet_id_ = 7;
+    callout_handle->setArgument("lease6", lease6);
+
+    // Invoke the lease6_expire callout and expect that the lease reclamation
+    // will continue because the server is responsible for the lease.
+    ASSERT_NO_THROW(test_ha_impl_->lease6Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+
+    // Mark the server not responsible for the lease. This time the reclamation
+    // should be skipped.
+    test_ha_impl_->should_reclaim_dhcpv6_lease_ = false;
+    ASSERT_NO_THROW(test_ha_impl_->lease6Expire(*callout_handle));
+    EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, callout_handle->getStatus());
+}
+
 // Tests ha-sync command handler with correct and incorrect arguments.
 TEST_F(HAImplTest, synchronizeHandler) {
     {
index 3a740243d5e7d9f3e4f6dfd2c053497782b6fa39..184f9f6facd6037c9d50916fb71ba7a1d535c318 100644 (file)
@@ -9137,6 +9137,56 @@ TEST_F(HAServiceStateMachineTest, doNotTerminateWhenPartnerUnavailable) {
     EXPECT_EQ(HA_COMMUNICATION_RECOVERY_ST, service_->getCurrState());
 }
 
+// This test verifies that the service correctly identifies the leases that can
+// be reclaimed while the server is the primary.
+TEST_F(HAServiceStateMachineTest, shouldReclaimLease4HotStandbyThisPrimary) {
+    startService(createValidConfiguration(HAConfig::HOT_STANDBY));
+    service_->verboseTransition(HA_TERMINATED_ST);
+    service_->runModel(HAService::NOP_EVT);
+
+    Lease4Ptr lease4 = createLease4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
+    EXPECT_TRUE(service_->shouldReclaim(lease4));
+}
+
+// This test verifies that the service correctly identifies the leases that can
+// be reclaimed while the server is the standby.
+TEST_F(HAServiceStateMachineTest, shouldReclaimLease4HotStandbyThisStandby) {
+    HAConfigPtr valid_config = createValidConfiguration(HAConfig::HOT_STANDBY);
+    valid_config->getPeerConfig("server2")->setRole("standby");
+    valid_config->setThisServerName("server2");
+    startService(valid_config);
+    service_->verboseTransition(HA_TERMINATED_ST);
+    service_->runModel(HAService::NOP_EVT);
+
+    Lease4Ptr lease4 = createLease4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
+    EXPECT_FALSE(service_->shouldReclaim(lease4));
+}
+
+// This test verifies that the service correctly identifies the leases that can
+// be reclaimed while the server is the primary.
+TEST_F(HAServiceStateMachineTest, shouldReclaimLease6HotStandbyThisPrimary) {
+    startService(createValidConfiguration(HAConfig::HOT_STANDBY));
+    service_->verboseTransition(HA_TERMINATED_ST);
+    service_->runModel(HAService::NOP_EVT);
+
+    Lease6Ptr lease6 = createLease6(randomKey(10));
+    EXPECT_TRUE(service_->shouldReclaim(lease6));
+}
+
+// This test verifies that the service correctly identifies the leases that can
+// be reclaimed while the server is the standby.
+TEST_F(HAServiceStateMachineTest, shouldReclaimLease6HotStandbyThisStandby) {
+    HAConfigPtr valid_config = createValidConfiguration(HAConfig::HOT_STANDBY);
+    valid_config->getPeerConfig("server2")->setRole("standby");
+    valid_config->setThisServerName("server2");
+    startService(valid_config);
+    service_->verboseTransition(HA_TERMINATED_ST);
+    service_->runModel(HAService::NOP_EVT);
+
+    Lease6Ptr lease6 = createLease6(randomKey(10));
+    EXPECT_FALSE(service_->shouldReclaim(lease6));
+}
+
 // Test scenario when a single lease4 update is sent successfully, parking is not
 // employed.
 TEST_F(HAServiceTest, successfulSendSingleLeaseUpdateWithoutParking) {
index b0d34759af5885c00dacda46a6a59ed410f874b9..7db18bcdc15ee8b4d3dcb389fd38e94f8347bce2 100644 (file)
@@ -426,6 +426,27 @@ HATest::createQuery6(const std::vector<uint8_t>& duid) const {
     return (query6);
 }
 
+Lease4Ptr
+HATest::createLease4(const std::vector<uint8_t>& hw_address_vec,
+                     const std::vector<uint8_t>& client_id_vec) const {
+    HWAddrPtr hwaddr(new HWAddr(hw_address_vec, HTYPE_ETHER));
+    Lease4Ptr lease4(new Lease4(IOAddress("192.1.2.3"), hwaddr,
+                                static_cast<const uint8_t*>(0), 0,
+                                60, 0, 1));
+    if (!client_id_vec.empty()) {
+        lease4->client_id_ = ClientIdPtr(new ClientId(client_id_vec));
+    }
+    return (lease4);
+}
+
+Lease6Ptr
+HATest::createLease6(const std::vector<uint8_t>& duid_vec) const {
+    DuidPtr duid(new DUID(duid_vec));
+    Lease6Ptr lease6(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::cafe"),
+                                duid, 1234, 50, 60, 1));
+    return (lease6);
+}
+
 Pkt6Ptr
 HATest::createMessage6(const uint8_t msg_type, const uint8_t duid_seed,
                        const uint16_t elapsed_time) const {
index 3b7d63fff59ceb5e52fb96775420072d50b45cac..d06df9674416be1ee67e1b9eb5b963be469fdb19 100644 (file)
@@ -240,6 +240,20 @@ public:
     /// @param duid DUI to be included in the query. It is used in load balancing.
     dhcp::Pkt6Ptr createQuery6(const std::vector<uint8_t>& duid) const;
 
+    /// @brief Creates test DHCPv4 lease instance.
+    ///
+    /// @param hw_address_vec HW address to be included in the lease. It is used
+    /// in load balancing.
+    /// @param client_id_vec optional client identifier.
+    dhcp::Lease4Ptr createLease4(const std::vector<uint8_t>& hw_address_vec,
+                                 const std::vector<uint8_t>& client_id_vec =
+                                 std::vector<uint8_t>()) const;
+
+    /// @brief Creates test DHCPv6 lease instance.
+    ///
+    /// @param duid_vec DUID to be included in the query. It is used in load balancing.
+    dhcp::Lease6Ptr createLease6(const std::vector<uint8_t>& duid_vec) const;
+
     /// @brief Generates simple DHCPv6 message.
     ///
     /// @param msg_type DHCPv6 message type to be created.
index 4f7f2c4f27794cb26e95fca29f6f2bea8ebc530a..b195533e46cd30936e4e6f91870e5b903ad77094 100644 (file)
@@ -129,14 +129,19 @@ QueryFilterTest::loadBalancingThisPrimary() {
     const unsigned queries_num = 65535;
     std::string scope_class;
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt4Ptr query4 = createQuery4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
+        // Create query and lease with with random HW address.
+        auto key = randomKey(HWAddr::ETHERNET_HWADDR_LEN);
+        Pkt4Ptr query4 = createQuery4(key);
+        Lease4Ptr lease4 = createLease4(key);
         // If the query is in scope, increase the counter of packets in scope.
         if (filter.inScope(query4, scope_class)) {
-            ASSERT_EQ("HA_server1", scope_class);
-            ASSERT_NE(scope_class, "HA_server2");
             ++in_scope;
+            EXPECT_EQ("HA_server1", scope_class);
+            EXPECT_TRUE(filter.inScope(lease4));
+        } else {
+            EXPECT_FALSE(filter.inScope(lease4));
         }
+        return;
     }
 
     // We should have roughly 50/50 split of in scope and out of scope queries.
@@ -156,10 +161,13 @@ QueryFilterTest::loadBalancingThisPrimary() {
 
     // Repeat the test, but this time all should be in scope.
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt4Ptr query4 = createQuery4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
-        // Every single query mist be in scope.
-        ASSERT_TRUE(filter.inScope(query4, scope_class));
+        // Create query and lease with random HW address.
+        auto key = randomKey(HWAddr::ETHERNET_HWADDR_LEN);
+        Pkt4Ptr query4 = createQuery4(key);
+        Lease4Ptr lease4 = createLease4(key);
+        // Every single query and lease must be in scope.
+        EXPECT_TRUE(filter.inScope(query4, scope_class));
+        EXPECT_TRUE(filter.inScope(lease4));
     }
 
     // However, the one that lacks HW address and client id should be out of
@@ -189,13 +197,17 @@ QueryFilterTest::loadBalancingClientIdThisPrimary() {
     const unsigned queries_num = 65535;
     std::string scope_class;
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random client identifier.
-        Pkt4Ptr query4 = createQuery4(hw_address, randomKey(8));
+        // Create query and lease with random client identifier.
+        auto key = randomKey(8);
+        Pkt4Ptr query4 = createQuery4(hw_address, key);
+        Lease4Ptr lease4 = createLease4(hw_address, key);
         // If the query is in scope, increase the counter of packets in scope.
         if (filter.inScope(query4, scope_class)) {
-            ASSERT_EQ("HA_server1", scope_class);
-            ASSERT_NE(scope_class, "HA_server2");
             ++in_scope;
+            EXPECT_EQ("HA_server1", scope_class);
+            EXPECT_TRUE(filter.inScope(lease4));
+        } else {
+            EXPECT_FALSE(filter.inScope(lease4));
         }
     }
 
@@ -216,10 +228,13 @@ QueryFilterTest::loadBalancingClientIdThisPrimary() {
 
     // Repeat the test, but this time all should be in scope.
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random client identifier.
-        Pkt4Ptr query4 = createQuery4(hw_address, randomKey(8));
-        // Every single query mist be in scope.
-        ASSERT_TRUE(filter.inScope(query4, scope_class));
+        // Create query and lease with random client identifier.
+        auto key = randomKey(8);
+        Pkt4Ptr query4 = createQuery4(hw_address, key);
+        Lease4Ptr lease4 = createLease4(hw_address, key);
+        // Every single query and lease must be in scope.
+        EXPECT_TRUE(filter.inScope(query4, scope_class));
+        EXPECT_TRUE(filter.inScope(lease4));
     }
 }
 
@@ -244,13 +259,17 @@ QueryFilterTest::loadBalancingThisSecondary() {
     const unsigned queries_num = 65535;
     std::string scope_class;
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt4Ptr query4 = createQuery4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
+        // Create query and lease with random HW address.
+        auto key = randomKey(HWAddr::ETHERNET_HWADDR_LEN);
+        Pkt4Ptr query4 = createQuery4(key);
+        Lease4Ptr lease4 = createLease4(key);
         // If the query is in scope, increase the counter of packets in scope.
         if (filter.inScope(query4, scope_class)) {
-            ASSERT_EQ("HA_server2", scope_class);
-            ASSERT_NE(scope_class, "HA_server1");
             ++in_scope;
+            EXPECT_EQ("HA_server2", scope_class);
+            EXPECT_TRUE(filter.inScope(lease4));
+        } else {
+            EXPECT_FALSE(filter.inScope(lease4));
         }
     }
 
@@ -271,10 +290,13 @@ QueryFilterTest::loadBalancingThisSecondary() {
 
     // Repeat the test, but this time all should be in scope.
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt4Ptr query4 = createQuery4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
-        // Every single query must be in scope.
-        ASSERT_TRUE(filter.inScope(query4, scope_class));
+        // Create query and lease with random HW address.
+        auto key = randomKey(HWAddr::ETHERNET_HWADDR_LEN);
+        Pkt4Ptr query4 = createQuery4(key);
+        Lease4Ptr lease4 = createLease4(key);
+        // Every single query and lease must be in scope.
+        EXPECT_TRUE(filter.inScope(query4, scope_class));
+        EXPECT_TRUE(filter.inScope(lease4));
     }
 }
 
@@ -295,10 +317,13 @@ QueryFilterTest::loadBalancingThisBackup() {
     const unsigned queries_num = 65535;
     std::string scope_class;
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt4Ptr query4 = createQuery4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
-        // None of the packets should be handlded by the backup server.
-        ASSERT_FALSE(filter.inScope(query4, scope_class));
+        // Create query and lease with random HW address.
+        auto key = randomKey(HWAddr::ETHERNET_HWADDR_LEN);
+        Pkt4Ptr query4 = createQuery4(key);
+        Lease4Ptr lease4 = createLease4(key);
+        // None of the packets should be handled by the backup server.
+        EXPECT_FALSE(filter.inScope(query4, scope_class));
+        EXPECT_FALSE(filter.inScope(lease4));
     }
 
     // Simulate failover. Although, backup server never starts handling
@@ -313,10 +338,13 @@ QueryFilterTest::loadBalancingThisBackup() {
 
     // Repeat the test, but this time all should be in scope.
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt4Ptr query4 = createQuery4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
-        // Every single query must be in scope.
-        ASSERT_TRUE(filter.inScope(query4, scope_class));
+        // Create query and lease with random HW address.
+        auto key = randomKey(HWAddr::ETHERNET_HWADDR_LEN);
+        Pkt4Ptr query4 = createQuery4(key);
+        Lease4Ptr lease4 = createLease4(key);
+        // Every single query and lease must be in scope.
+        EXPECT_TRUE(filter.inScope(query4, scope_class));
+        EXPECT_TRUE(filter.inScope(lease4));
     }
 }
 
@@ -328,6 +356,7 @@ QueryFilterTest::hotStandbyThisPrimary() {
     QueryFilter filter(config);
 
     Pkt4Ptr query4 = createQuery4("11:22:33:44:55:66");
+    Lease4Ptr lease4 = createLease4(query4->getHWAddr()->hwaddr_);
 
     // By default, only the primary server is active.
     EXPECT_TRUE(filter.amServingScope("server1"));
@@ -338,6 +367,7 @@ QueryFilterTest::hotStandbyThisPrimary() {
 
     // It should process its queries.
     EXPECT_TRUE(filter.inScope(query4, scope_class));
+    EXPECT_TRUE(filter.inScope(lease4));
 
     // Simulate failover scenario, in which the active server detects a
     // failure of the standby server. This doesn't change anything in how
@@ -351,7 +381,7 @@ QueryFilterTest::hotStandbyThisPrimary() {
 
     EXPECT_TRUE(filter.inScope(query4, scope_class));
     EXPECT_EQ("HA_server1", scope_class);
-    EXPECT_NE(scope_class, "HA_server2");
+    EXPECT_TRUE(filter.inScope(lease4));
 }
 
 void
@@ -363,6 +393,7 @@ QueryFilterTest::hotStandbyThisSecondary() {
     QueryFilter filter(config);
 
     Pkt4Ptr query4 = createQuery4("11:22:33:44:55:66");
+    Lease4Ptr lease4 = createLease4(query4->getHWAddr()->hwaddr_);
 
     // The server2 doesn't process any queries by default. The whole
     // traffic is processed by the server1.
@@ -374,7 +405,7 @@ QueryFilterTest::hotStandbyThisSecondary() {
 
     EXPECT_FALSE(filter.inScope(query4, scope_class));
     EXPECT_EQ("HA_server1", scope_class);
-    EXPECT_NE(scope_class, "HA_server2");
+    EXPECT_FALSE(filter.inScope(lease4));
 
     // Simulate failover case whereby the standby server detects a
     // failure of the active server.
@@ -389,6 +420,7 @@ QueryFilterTest::hotStandbyThisSecondary() {
     EXPECT_TRUE(filter.inScope(query4, scope_class));
     EXPECT_EQ("HA_server1", scope_class);
     EXPECT_NE(scope_class, "HA_server2");
+    EXPECT_TRUE(filter.inScope(lease4));
 }
 
 void
@@ -400,6 +432,7 @@ QueryFilterTest::hotStandbyThisBackup() {
     QueryFilter filter(config);
 
     Pkt4Ptr query4 = createQuery4("11:22:33:44:55:66");
+    Lease4Ptr lease4 = createLease4(query4->getHWAddr()->hwaddr_);
 
     // By default the backup server doesn't process any traffic.
     EXPECT_FALSE(filter.amServingScope("server1"));
@@ -409,6 +442,7 @@ QueryFilterTest::hotStandbyThisBackup() {
     std::string scope_class;
 
     EXPECT_FALSE(filter.inScope(query4, scope_class));
+    EXPECT_FALSE(filter.inScope(lease4));
 
     // Simulate failover. Although, backup server never starts handling
     // other server's traffic automatically, it can be manually instructed
@@ -422,6 +456,7 @@ QueryFilterTest::hotStandbyThisBackup() {
     EXPECT_FALSE(filter.amServingScope("server3"));
 
     EXPECT_TRUE(filter.inScope(query4, scope_class));
+    EXPECT_TRUE(filter.inScope(lease4));
 }
 
 void
@@ -442,13 +477,17 @@ QueryFilterTest::loadBalancingThisPrimary6() {
     const unsigned queries_num = 65535;
     std::string scope_class;
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random DUID.
-        Pkt6Ptr query6 = createQuery6(randomKey(10));
+        // Create query and lease with random DUID.
+        auto key = randomKey(10);
+        Pkt6Ptr query6 = createQuery6(key);
+        Lease6Ptr lease6 = createLease6(key);
         // If the query is in scope, increase the counter of packets in scope.
         if (filter.inScope(query6, scope_class)) {
-            ASSERT_EQ("HA_server1", scope_class);
-            ASSERT_NE(scope_class, "HA_server2");
             ++in_scope;
+            EXPECT_EQ("HA_server1", scope_class);
+            EXPECT_TRUE(filter.inScope(lease6));
+        } else {
+            EXPECT_FALSE(filter.inScope(lease6));
         }
     }
 
@@ -469,10 +508,13 @@ QueryFilterTest::loadBalancingThisPrimary6() {
 
     // Repeat the test, but this time all should be in scope.
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt6Ptr query6 = createQuery6(randomKey(10));
-        // Every single query mist be in scope.
-        ASSERT_TRUE(filter.inScope(query6, scope_class));
+        // Create query and lease with random HW address.
+        auto key = randomKey(10);
+        Pkt6Ptr query6 = createQuery6(key);
+        Lease6Ptr lease6 = createLease6(key);
+        // Every single query and lease must be in scope.
+        EXPECT_TRUE(filter.inScope(query6, scope_class));
+        EXPECT_TRUE(filter.inScope(lease6));
     }
 
     // However, the one that lacks DUID should be out of scope.
@@ -501,13 +543,17 @@ QueryFilterTest::loadBalancingThisSecondary6() {
     const unsigned queries_num = 65535;
     std::string scope_class;
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt6Ptr query6 = createQuery6(randomKey(10));
+        // Create query and lease with random HW address.
+        auto key = randomKey(10);
+        Pkt6Ptr query6 = createQuery6(key);
+        Lease6Ptr lease6 = createLease6(key);
         // If the query is in scope, increase the counter of packets in scope.
         if (filter.inScope(query6, scope_class)) {
-            ASSERT_EQ("HA_server2", scope_class);
-            ASSERT_NE(scope_class, "HA_server1");
             ++in_scope;
+            EXPECT_EQ("HA_server2", scope_class);
+            EXPECT_TRUE(filter.inScope(lease6));
+        } else {
+            EXPECT_FALSE(filter.inScope(lease6));
         }
     }
 
@@ -528,10 +574,13 @@ QueryFilterTest::loadBalancingThisSecondary6() {
 
     // Repeat the test, but this time all should be in scope.
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt6Ptr query6 = createQuery6(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
-        // Every single query must be in scope.
-        ASSERT_TRUE(filter.inScope(query6, scope_class));
+        // Create query and lease with random HW address.
+        auto key = randomKey(HWAddr::ETHERNET_HWADDR_LEN);
+        Pkt6Ptr query6 = createQuery6(key);
+        Lease6Ptr lease6 = createLease6(key);
+        // Every single query and lease must be in scope.
+        EXPECT_TRUE(filter.inScope(query6, scope_class));
+        EXPECT_TRUE(filter.inScope(lease6));
     }
 }
 
@@ -552,10 +601,13 @@ QueryFilterTest::loadBalancingThisBackup6() {
     const unsigned queries_num = 65535;
     std::string scope_class;
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt6Ptr query6 = createQuery6(randomKey(10));
+        // Create query and lease with random HW address.
+        auto key = randomKey(10);
+        Pkt6Ptr query6 = createQuery6(key);
+        Lease6Ptr lease6 = createLease6(key);
         // None of the packets should be handlded by the backup server.
-        ASSERT_FALSE(filter.inScope(query6, scope_class));
+        EXPECT_FALSE(filter.inScope(query6, scope_class));
+        EXPECT_FALSE(filter.inScope(lease6));
     }
 
     // Simulate failover. Although, backup server never starts handling
@@ -570,10 +622,13 @@ QueryFilterTest::loadBalancingThisBackup6() {
 
     // Repeat the test, but this time all should be in scope.
     for (unsigned i = 0; i < queries_num; ++i) {
-        // Create query with random HW address.
-        Pkt6Ptr query6 = createQuery6(randomKey(10));
-        // Every single query must be in scope.
-        ASSERT_TRUE(filter.inScope(query6, scope_class));
+        // Create query and lease with random HW address.
+        auto key = randomKey(10);
+        Pkt6Ptr query6 = createQuery6(key);
+        Lease6Ptr lease6 = createLease6(key);
+        // Every single query and lease must be in scope.
+        EXPECT_TRUE(filter.inScope(query6, scope_class));
+        EXPECT_TRUE(filter.inScope(lease6));
     }
 }
 
@@ -584,7 +639,9 @@ QueryFilterTest::hotStandbyThisPrimary6() {
 
     QueryFilter filter(config);
 
-    Pkt6Ptr query6 = createQuery6("01:02:11:22:33:44:55:66");
+    auto key = randomKey(10);
+    Pkt6Ptr query6 = createQuery6(key);
+    Lease6Ptr lease6 = createLease6(key);
 
     // By default, only the primary server is active.
     EXPECT_TRUE(filter.amServingScope("server1"));
@@ -595,6 +652,7 @@ QueryFilterTest::hotStandbyThisPrimary6() {
 
     // It should process its queries.
     EXPECT_TRUE(filter.inScope(query6, scope_class));
+    EXPECT_TRUE(filter.inScope(lease6));
 
     // Simulate failover scenario, in which the active server detects a
     // failure of the standby server. This doesn't change anything in how
@@ -608,7 +666,7 @@ QueryFilterTest::hotStandbyThisPrimary6() {
 
     EXPECT_TRUE(filter.inScope(query6, scope_class));
     EXPECT_EQ("HA_server1", scope_class);
-    EXPECT_NE(scope_class, "HA_server2");
+    EXPECT_TRUE(filter.inScope(lease6));
 }
 
 void
@@ -619,7 +677,9 @@ QueryFilterTest::hotStandbyThisSecondary6() {
 
     QueryFilter filter(config);
 
-    Pkt6Ptr query6 = createQuery6("01:02:11:22:33:44:55:66");
+    auto key = randomKey(10);
+    Pkt6Ptr query6 = createQuery6(key);
+    Lease6Ptr lease6 = createLease6(key);
 
     // The server2 doesn't process any queries by default. The whole
     // traffic is processed by the server1.
@@ -631,7 +691,7 @@ QueryFilterTest::hotStandbyThisSecondary6() {
 
     EXPECT_FALSE(filter.inScope(query6, scope_class));
     EXPECT_EQ("HA_server1", scope_class);
-    EXPECT_NE(scope_class, "HA_server2");
+    EXPECT_FALSE(filter.inScope(lease6));
 
     // Simulate failover case whereby the standby server detects a
     // failure of the active server.
@@ -645,7 +705,7 @@ QueryFilterTest::hotStandbyThisSecondary6() {
 
     EXPECT_TRUE(filter.inScope(query6, scope_class));
     EXPECT_EQ("HA_server1", scope_class);
-    EXPECT_NE(scope_class, "HA_server2");
+    EXPECT_TRUE(filter.inScope(lease6));
 }
 
 void
@@ -656,7 +716,9 @@ QueryFilterTest::hotStandbyThisBackup6() {
 
     QueryFilter filter(config);
 
-    Pkt6Ptr query6 = createQuery6(randomKey(10));
+    auto key = randomKey(10);
+    Pkt6Ptr query6 = createQuery6(key);
+    Lease6Ptr lease6 = createLease6(key);
 
     // By default the backup server doesn't process any traffic.
     EXPECT_FALSE(filter.amServingScope("server1"));
@@ -666,6 +728,7 @@ QueryFilterTest::hotStandbyThisBackup6() {
     std::string scope_class;
 
     EXPECT_FALSE(filter.inScope(query6, scope_class));
+    EXPECT_FALSE(filter.inScope(lease6));
 
     // Simulate failover. Although, backup server never starts handling
     // other server's traffic automatically, it can be manually instructed
@@ -679,6 +742,7 @@ QueryFilterTest::hotStandbyThisBackup6() {
     EXPECT_FALSE(filter.amServingScope("server3"));
 
     EXPECT_TRUE(filter.inScope(query6, scope_class));
+    EXPECT_TRUE(filter.inScope(lease6));
 }
 
 void
@@ -748,16 +812,20 @@ QueryFilterTest::loadBalancingHaTypes4() {
     for (unsigned i = 0; i < max_scope_tries; ++i) {
         // Create query with random HW address.
         std::string scope_class;
-        Pkt4Ptr query4 = createQuery4(randomKey(HWAddr::ETHERNET_HWADDR_LEN));
+        auto key = randomKey(HWAddr::ETHERNET_HWADDR_LEN);
+        Pkt4Ptr query4 = createQuery4(key);
+        Lease4Ptr lease4 = createLease4(key);
         // If the query is in scope then we're done.
         if (filter.inScope(query4, scope_class)) {
-            ASSERT_EQ("HA_server1", scope_class);
+            EXPECT_EQ("HA_server1", scope_class);
+            EXPECT_TRUE(filter.inScope(lease4));
             server1_pkt = query4;
             if (server2_pkt) {
                 break;
             }
         } else {
-            ASSERT_EQ("HA_server2", scope_class);
+            EXPECT_EQ("HA_server2", scope_class);
+            EXPECT_FALSE(filter.inScope(lease4));
             server2_pkt = query4;
             if (server1_pkt) {
                 break;
@@ -822,10 +890,13 @@ QueryFilterTest::loadBalancingHaTypes6() {
         // Create query with random HW address.
         std::string scope_class;
 
-        // Create query with random DUID.
-        Pkt6Ptr query6 = createQuery6(randomKey(10));
+        // Create query and lease with random DUID.
+        auto key = randomKey(10);
+        Pkt6Ptr query6 = createQuery6(key);
+        Lease6Ptr lease6 = createLease6(key);
         if (filter.inScope(query6, scope_class)) {
-            ASSERT_EQ("HA_server1", scope_class);
+            EXPECT_EQ("HA_server1", scope_class);
+            EXPECT_TRUE(filter.inScope(lease6));
             // In scope for server1, save it.
             server1_pkt = query6;
             if (server2_pkt) {
@@ -833,7 +904,8 @@ QueryFilterTest::loadBalancingHaTypes6() {
                 break;
             }
         } else {
-            ASSERT_EQ("HA_server2", scope_class);
+            EXPECT_EQ("HA_server2", scope_class);
+            EXPECT_FALSE(filter.inScope(lease6));
             // In scope for server2, save it.
             server2_pkt = query6;
             if (server1_pkt) {