Browse Source

[3564] Updated allocateLease4 in allocation engine to use reservations.

Marcin Siodelski 10 years ago
parent
commit
bf3ea66a3e

+ 223 - 118
src/lib/dhcpsrv/alloc_engine.cc

@@ -14,6 +14,7 @@
 
 
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 
 
 #include <hooks/server_hooks.h>
 #include <hooks/server_hooks.h>
@@ -509,70 +510,94 @@ AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid
             isc_throw(InvalidOperation, "HWAddr must be defined");
             isc_throw(InvalidOperation, "HWAddr must be defined");
         }
         }
 
 
-        // Check if there's existing lease for that subnet/clientid/hwaddr combination.
-        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
-        if (existing) {
-            // Save the old lease, before renewal.
-            old_lease.reset(new Lease4(*existing));
-            // We have a lease already. This is a returning client, probably after
-            // its reboot.
-            existing = renewLease4(subnet, clientid, hwaddr,
-                                   fwd_dns_update, rev_dns_update, hostname,
-                                   existing, callout_handle, fake_allocation);
-            if (existing) {
-                return (existing);
+        // Build the processing context.
+        Context4 ctx;
+        ctx.subnet_ = subnet;
+        ctx.clientid_ = clientid;
+        ctx.hwaddr_ = hwaddr;
+        ctx.hint_ = hint;
+        ctx.fwd_dns_update_ = fwd_dns_update;
+        ctx.rev_dns_update_ = rev_dns_update;
+        ctx.hostname_ = hostname;
+        ctx.fake_allocation_ = fake_allocation;
+        ctx.callout_handle_ = callout_handle;
+        ctx.old_lease_ = old_lease;
+        ctx.host_ = HostMgr::instance().get4(subnet->getID(), hwaddr);
+
+        if (ctx.host_) {
+            if (ctx.hint_ == IOAddress("0.0.0.0")) {
+                ctx.hint_ = ctx.host_->getIPv4Reservation();
+
+            } else if (!ctx.fake_allocation_ &&
+                       (ctx.hint_ != ctx.host_->getIPv4Reservation())) {
+                return (Lease4Ptr());
             }
             }
+        }
 
 
-            // If renewal failed (e.g. the lease no longer matches current configuration)
-            // let's continue the allocation process
+        LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+        Lease4Ptr existing = lease_mgr.getLease4(*hwaddr, ctx.subnet_->getID());
+        if (!existing && clientid) {
+            existing = lease_mgr.getLease4(*clientid, ctx.subnet_->getID());
         }
         }
 
 
-        if (clientid) {
-            existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
-            if (existing) {
-                // Save the old lease before renewal.
-                old_lease.reset(new Lease4(*existing));
-                // we have a lease already. This is a returning client, probably after
-                // its reboot.
-                existing = renewLease4(subnet, clientid, hwaddr,
-                                       fwd_dns_update, rev_dns_update,
-                                       hostname, existing, callout_handle,
-                                       fake_allocation);
-                // @todo: produce a warning. We haven't found him using MAC address, but
-                // we found him using client-id
-                if (existing) {
-                    return (existing);
-                }
+        if (existing) {
+            existing = reallocateClientLease(existing, ctx);
+            if (ctx.host_ || existing) {
+                old_lease = ctx.old_lease_;
+                return (existing);
             }
             }
         }
         }
 
 
-        // check if the hint is in pool and is available
-        if (subnet->inPool(Lease::TYPE_V4, hint)) {
-            existing = LeaseMgrFactory::instance().getLease4(hint);
-            if (!existing) {
-                /// @todo: Check if the hint is reserved once we have host support
-                /// implemented
+        // Check if we have a specific address we would like to allocate for the
+        // client. It may either be an address which the client is currently
+        // using, or it may be a administratively reserved address.
+        if (ctx.host_ || subnet->inPool(Lease::TYPE_V4, hint)) {
+            // If a client is requesting specific IP address, but the
+            // reservation was made for a different address the server returns
+            // NAK to the client. By returning NULL lease here we indicate to
+            // the server that we're not able to fulfil client's request for the
+            // particular IP address.
+            if (!ctx.fake_allocation_ && ctx.host_ &&
+                (hint != IOAddress("0.0.0.0")) &&
+                (ctx.host_->getIPv4Reservation() != hint)) {
+                return (Lease4Ptr());
+            }
 
 
-                // The hint is valid and not currently used, let's create a lease for it
-                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint,
-                                               fwd_dns_update, rev_dns_update,
+            // The reserved address always takes precedence over hints. But, if
+            // there is no reservation, try to respect the client's hint.
+            const IOAddress& candidate = ctx.host_ ?
+                ctx.host_->getIPv4Reservation() : hint;
+
+            existing = LeaseMgrFactory::instance().getLease4(candidate);
+            if (!existing) {
+                // The candidate address is currently unused. Let's create a
+                // lease for it.
+                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr,
+                                               candidate, fwd_dns_update,
+                                               rev_dns_update,
                                                hostname, callout_handle,
                                                hostname, callout_handle,
                                                fake_allocation);
                                                fake_allocation);
 
 
-                // It can happen that the lease allocation failed (we could have lost
-                // the race condition. That means that the hint is lo longer usable and
-                // we need to continue the regular allocation path.
-                if (lease) {
+                // If we have allocated the lease let's return it. Also,
+                // always return when tried to allocate reserved address,
+                // regardless if allocation was successful or not. If it
+                // was not successful, we will return a NULL pointer which
+                // indicates to the server that it should send NAK to the
+                // client.
+                if (lease || ctx.host_) {
                     return (lease);
                     return (lease);
                 }
                 }
+
             } else {
             } else {
                 if (existing->expired()) {
                 if (existing->expired()) {
                     // Save the old lease, before reusing it.
                     // Save the old lease, before reusing it.
                     old_lease.reset(new Lease4(*existing));
                     old_lease.reset(new Lease4(*existing));
-                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
-                                              fwd_dns_update, rev_dns_update,
-                                              hostname, callout_handle,
-                                              fake_allocation));
+                    return (reuseExpiredLease(existing, ctx));
+
+                } else if (ctx.host_ && (ctx.host_->getIPv4Reservation() !=
+                                         IOAddress("0.0.0.0"))) {
+                    return (Lease4Ptr());
+
                 }
                 }
 
 
             }
             }
@@ -620,10 +645,7 @@ AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid
                 if (existing->expired()) {
                 if (existing->expired()) {
                     // Save old lease before reusing it.
                     // Save old lease before reusing it.
                     old_lease.reset(new Lease4(*existing));
                     old_lease.reset(new Lease4(*existing));
-                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
-                                              fwd_dns_update, rev_dns_update,
-                                              hostname, callout_handle,
-                                              fake_allocation));
+                    return (reuseExpiredLease(existing, ctx));
                 }
                 }
             }
             }
 
 
@@ -643,18 +665,11 @@ AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid
     return (Lease4Ptr());
     return (Lease4Ptr());
 }
 }
 
 
-Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
-                                   const ClientIdPtr& clientid,
-                                   const HWAddrPtr& hwaddr,
-                                   const bool fwd_dns_update,
-                                   const bool rev_dns_update,
-                                   const std::string& hostname,
-                                   const Lease4Ptr& lease,
-                                   const isc::hooks::CalloutHandlePtr& callout_handle,
-                                   bool fake_allocation /* = false */) {
-
+Lease4Ptr
+AllocEngine::renewLease4(const Lease4Ptr& lease,
+                         const AllocEngine::Context4& ctx) {
     if (!lease) {
     if (!lease) {
-        isc_throw(InvalidOperation, "Lease4 must be specified");
+        isc_throw(BadValue, "null lease specified for renewLease4");
     }
     }
 
 
     // Let's keep the old data. This is essential if we are using memfile
     // Let's keep the old data. This is essential if we are using memfile
@@ -663,51 +678,46 @@ Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
     /// @todo: remove this once #3083 is implemented
     /// @todo: remove this once #3083 is implemented
     Lease4 old_values = *lease;
     Lease4 old_values = *lease;
 
 
-    lease->subnet_id_ = subnet->getID();
-    lease->hwaddr_ = hwaddr;
-    lease->client_id_ = clientid;
-    lease->cltt_ = time(NULL);
-    lease->t1_ = subnet->getT1();
-    lease->t2_ = subnet->getT2();
-    lease->valid_lft_ = subnet->getValid();
-    lease->fqdn_fwd_ = fwd_dns_update;
-    lease->fqdn_rev_ = rev_dns_update;
-    lease->hostname_ = hostname;
+    // Update the lease with the information from the context.
+    updateLease4Information(lease, ctx);
 
 
     bool skip = false;
     bool skip = false;
-    // Execute all callouts registered for packet6_send
-    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease4_renew_)) {
+    // Execute all callouts registered for lease4_renew.
+    if (HooksManager::getHooksManager().
+        calloutsPresent(Hooks.hook_index_lease4_renew_)) {
 
 
         // Delete all previous arguments
         // Delete all previous arguments
-        callout_handle->deleteAllArguments();
+        ctx.callout_handle_->deleteAllArguments();
 
 
         // Subnet from which we do the allocation. Convert the general subnet
         // Subnet from which we do the allocation. Convert the general subnet
         // pointer to a pointer to a Subnet4.  Note that because we are using
         // pointer to a pointer to a Subnet4.  Note that because we are using
         // boost smart pointers here, we need to do the cast using the boost
         // boost smart pointers here, we need to do the cast using the boost
         // version of dynamic_pointer_cast.
         // version of dynamic_pointer_cast.
-        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
+        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
 
 
         // Pass the parameters
         // Pass the parameters
-        callout_handle->setArgument("subnet4", subnet4);
-        callout_handle->setArgument("clientid", clientid);
-        callout_handle->setArgument("hwaddr", hwaddr);
+        ctx.callout_handle_->setArgument("subnet4", subnet4);
+        ctx.callout_handle_->setArgument("clientid", ctx.clientid_);
+        ctx.callout_handle_->setArgument("hwaddr", ctx.hwaddr_);
 
 
         // Pass the lease to be updated
         // Pass the lease to be updated
-        callout_handle->setArgument("lease4", lease);
+        ctx.callout_handle_->setArgument("lease4", lease);
 
 
         // Call all installed callouts
         // Call all installed callouts
-        HooksManager::callCallouts(Hooks.hook_index_lease4_renew_, *callout_handle);
+        HooksManager::callCallouts(Hooks.hook_index_lease4_renew_,
+                                   *ctx.callout_handle_);
 
 
         // Callouts decided to skip the next processing step. The next
         // Callouts decided to skip the next processing step. The next
         // processing step would to actually renew the lease, so skip at this
         // processing step would to actually renew the lease, so skip at this
         // stage means "keep the old lease as it is".
         // stage means "keep the old lease as it is".
-        if (callout_handle->getSkip()) {
+        if (ctx.callout_handle_->getSkip()) {
             skip = true;
             skip = true;
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_RENEW_SKIP);
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                      DHCPSRV_HOOK_LEASE4_RENEW_SKIP);
         }
         }
     }
     }
 
 
-    if (!fake_allocation && !skip) {
+    if (!ctx.fake_allocation_ && !skip) {
         // for REQUEST we do update the lease
         // for REQUEST we do update the lease
         LeaseMgrFactory::instance().updateLease4(lease);
         LeaseMgrFactory::instance().updateLease4(lease);
     }
     }
@@ -803,42 +813,29 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
     return (expired);
     return (expired);
 }
 }
 
 
-Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
-                                         const SubnetPtr& subnet,
-                                         const ClientIdPtr& clientid,
-                                         const HWAddrPtr& hwaddr,
-                                         const bool fwd_dns_update,
-                                         const bool rev_dns_update,
-                                         const std::string& hostname,
-                                         const isc::hooks::CalloutHandlePtr& callout_handle,
-                                         bool fake_allocation /*= false */ ) {
+Lease4Ptr
+AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
+                               const AllocEngine::Context4& ctx) {
+    if (!expired) {
+        isc_throw(BadValue, "null lease specified for reuseExpiredLease");
+    }
 
 
-    if (!expired->expired()) {
-        isc_throw(BadValue, "Attempt to recycle lease that is still valid");
+    if (!ctx.subnet_) {
+        isc_throw(BadValue, "null subnet specified for the reuseExpiredLease");
     }
     }
 
 
-    // address, lease type and prefixlen (0) stay the same
-    expired->client_id_ = clientid;
-    expired->hwaddr_ = hwaddr;
-    expired->valid_lft_ = subnet->getValid();
-    expired->t1_ = subnet->getT1();
-    expired->t2_ = subnet->getT2();
-    expired->cltt_ = time(NULL);
-    expired->subnet_id_ = subnet->getID();
+    updateLease4Information(expired, ctx);
     expired->fixed_ = false;
     expired->fixed_ = false;
-    expired->hostname_ = hostname;
-    expired->fqdn_fwd_ = fwd_dns_update;
-    expired->fqdn_rev_ = rev_dns_update;
 
 
     /// @todo: log here that the lease was reused (there's ticket #2524 for
     /// @todo: log here that the lease was reused (there's ticket #2524 for
     /// logging in libdhcpsrv)
     /// logging in libdhcpsrv)
 
 
     // Let's execute all callouts registered for lease4_select
     // Let's execute all callouts registered for lease4_select
-    if (callout_handle &&
-        HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {
+    if (ctx.callout_handle_ &&  HooksManager::getHooksManager()
+        .calloutsPresent(hook_index_lease4_select_)) {
 
 
         // Delete all previous arguments
         // Delete all previous arguments
-        callout_handle->deleteAllArguments();
+        ctx.callout_handle_->deleteAllArguments();
 
 
         // Pass necessary arguments
         // Pass necessary arguments
 
 
@@ -846,32 +843,34 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
         // pointer to a pointer to a Subnet4.  Note that because we are using
         // pointer to a pointer to a Subnet4.  Note that because we are using
         // boost smart pointers here, we need to do the cast using the boost
         // boost smart pointers here, we need to do the cast using the boost
         // version of dynamic_pointer_cast.
         // version of dynamic_pointer_cast.
-        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
-        callout_handle->setArgument("subnet4", subnet4);
+        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
+        ctx.callout_handle_->setArgument("subnet4", subnet4);
 
 
         // Is this solicit (fake = true) or request (fake = false)
         // Is this solicit (fake = true) or request (fake = false)
-        callout_handle->setArgument("fake_allocation", fake_allocation);
+        ctx.callout_handle_->setArgument("fake_allocation",
+                                         ctx.fake_allocation_);
 
 
         // The lease that will be assigned to a client
         // The lease that will be assigned to a client
-        callout_handle->setArgument("lease4", expired);
+        ctx.callout_handle_->setArgument("lease4", expired);
 
 
         // Call the callouts
         // Call the callouts
-        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+        HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_);
 
 
         // Callouts decided to skip the action. This means that the lease is not
         // Callouts decided to skip the action. This means that the lease is not
         // assigned, so the client will get NoAddrAvail as a result. The lease
         // assigned, so the client will get NoAddrAvail as a result. The lease
         // won't be inserted into the database.
         // won't be inserted into the database.
-        if (callout_handle->getSkip()) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+        if (ctx.callout_handle_->getSkip()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                      DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
             return (Lease4Ptr());
             return (Lease4Ptr());
         }
         }
 
 
         // Let's use whatever callout returned. Hopefully it is the same lease
         // Let's use whatever callout returned. Hopefully it is the same lease
         // we handled to it.
         // we handled to it.
-        callout_handle->getArgument("lease4", expired);
+        ctx.callout_handle_->getArgument("lease4", expired);
     }
     }
 
 
-    if (!fake_allocation) {
+    if (!ctx.fake_allocation_) {
         // for REQUEST we do update the lease
         // for REQUEST we do update the lease
         LeaseMgrFactory::instance().updateLease4(expired);
         LeaseMgrFactory::instance().updateLease4(expired);
     }
     }
@@ -884,6 +883,87 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
     return (expired);
     return (expired);
 }
 }
 
 
+Lease4Ptr
+AllocEngine::replaceClientLease(Lease4Ptr& lease, const Context4& ctx) {
+    if (!lease) {
+        isc_throw(BadValue, "null lease specified for replaceClientLease");
+    }
+
+    if (!ctx.subnet_) {
+        isc_throw(BadValue, "null subnet specified for replaceClientLease");
+    }
+
+    if (!ctx.host_) {
+        isc_throw(BadValue, "null host specified for replaceClientLease");
+    }
+
+    if (ctx.hint_ == IOAddress("0.0.0.0")) {
+        isc_throw(BadValue, "zero address specified for the"
+                  " replaceClientLease");
+    }
+
+    IOAddress prev_address = lease->addr_;
+    updateLease4Information(lease, ctx);
+    lease->addr_ = ctx.host_->getIPv4Reservation();
+
+    // Execute callouts registered for lease4_select.
+    if (ctx.callout_handle_ && HooksManager::getHooksManager()
+        .calloutsPresent(hook_index_lease4_select_)) {
+
+        // Delete all previous arguments.
+        ctx.callout_handle_->deleteAllArguments();
+
+        // Pass arguments.
+        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
+        ctx.callout_handle_->setArgument("subnet4", subnet4);
+
+        ctx.callout_handle_->setArgument("fake_allocation",
+                                         ctx.fake_allocation_);
+
+        ctx.callout_handle_->setArgument("lease4", lease);
+
+        HooksManager::callCallouts(hook_index_lease4_select_,
+                                   *ctx.callout_handle_);
+
+        if (ctx.callout_handle_->getSkip()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                      DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+            return (Lease4Ptr());
+        }
+
+        // Let's use whatever callout returned.
+        ctx.callout_handle_->getArgument("lease4", lease);
+    }
+
+    if (!ctx.fake_allocation_) {
+        LeaseMgrFactory::instance().deleteLease(prev_address);
+        LeaseMgrFactory::instance().addLease(lease);
+    }
+
+    return (lease);
+}
+
+Lease4Ptr
+AllocEngine::reallocateClientLease(Lease4Ptr& lease,
+                                   AllocEngine::Context4& ctx) {
+    // Save the old lease, before renewal.
+    ctx.old_lease_.reset(new Lease4(*lease));
+
+    if (ctx.host_ && ctx.host_->getIPv4Reservation() != lease->addr_) {
+        lease = replaceClientLease(lease, ctx);
+        return (lease);
+
+    } else {
+        lease = renewLease4(lease, ctx);
+        if (lease) {
+            return (lease);
+        }
+    }
+
+    return (Lease4Ptr());
+}
+
+
 Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
 Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
                                     const DuidPtr& duid,
                                     const DuidPtr& duid,
                                     const uint32_t iaid,
                                     const uint32_t iaid,
@@ -1064,6 +1144,31 @@ Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
     }
     }
 }
 }
 
 
+void
+AllocEngine::updateLease4Information(const Lease4Ptr& lease,
+                                     const AllocEngine::Context4& ctx) const {
+    // This should not happen in theory.
+    if (!lease) {
+        isc_throw(BadValue, "null lease specified for updateLease4Information");
+    }
+
+    if (!ctx.subnet_) {
+        isc_throw(BadValue, "null subnet specified for"
+                  " updateLease4Information");
+    }
+
+    lease->subnet_id_ = ctx.subnet_->getID();
+    lease->hwaddr_ = ctx.hwaddr_;
+    lease->client_id_ = ctx.clientid_;
+    lease->cltt_ = time(NULL);
+    lease->t1_ = ctx.subnet_->getT1();
+    lease->t2_ = ctx.subnet_->getT2();
+    lease->valid_lft_ = ctx.subnet_->getValid();
+    lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+    lease->fqdn_rev_ = ctx.rev_dns_update_;
+    lease->hostname_ = ctx.hostname_;
+}
+
 Lease6Collection
 Lease6Collection
 AllocEngine::updateFqdnData(const Lease6Collection& leases,
 AllocEngine::updateFqdnData(const Lease6Collection& leases,
                             const bool fwd_dns_update,
                             const bool fwd_dns_update,

+ 146 - 57
src/lib/dhcpsrv/alloc_engine.h

@@ -18,6 +18,7 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
+#include <dhcpsrv/host.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <hooks/callout_handle.h>
 #include <hooks/callout_handle.h>
@@ -214,6 +215,72 @@ protected:
         ALLOC_RANDOM     // random - an address is randomly selected
         ALLOC_RANDOM     // random - an address is randomly selected
     } AllocType;
     } AllocType;
 
 
+    /// @brief Context information for the DHCPv4 lease allocation.
+    ///
+    /// This structure holds a set of information provided by the DHCPv4
+    /// server to the allocation engine. In particular, it holds the
+    /// client identifying information, such as HW address or client
+    /// identifier. It also holds the information about the subnet that
+    /// the client is connected to.
+    ///
+    /// This structure is also used to pass  some information from
+    /// the allocation engine back to the server, i.e. the old lease
+    /// which the client had before the allocation.
+    ///
+    /// This structure is meant to be extended in the future, if more
+    /// information should be passed to the allocation engine. Note
+    /// that the big advantage of using the context structure to pass
+    /// information to the allocation engine methods is that adding
+    /// new information doesn't modify the API of the allocation engine.
+    struct Context4 {
+        /// @brief Subnet selected for the client by the server.
+        SubnetPtr subnet_;
+
+        /// @brief Client identifier from the DHCP message.
+        ClientIdPtr clientid_;
+
+        /// @brief HW address from the DHCP message.
+        HWAddrPtr hwaddr_;
+
+        /// @brief An address that the client desires.
+        ///
+        /// If this address is set to 0 it indicates that this address
+        /// is unspecified.
+        asiolink::IOAddress hint_;
+
+        /// @brief Perform forward DNS update.
+        bool fwd_dns_update_;
+
+        /// @brief Perform reverse DNS update.
+        bool rev_dns_update_;
+
+        /// @brief Hostname.
+        std::string hostname_;
+
+        /// @brief Callout handle associated with the client's message.
+        hooks::CalloutHandlePtr callout_handle_;
+
+        /// @brief Indicates if this is a real or fake allocation.
+        ///
+        /// The real allocation is when the allocation engine is supposed
+        /// to make an update in a lease database: create new lease, or
+        /// update existing lease.
+        bool fake_allocation_;
+
+        /// @brief A pointer to an old lease that the client had before update.
+        Lease4Ptr old_lease_;
+
+        /// @brief A pointer to the object identifying host reservations.
+        ConstHostPtr host_;
+
+        /// @brief Default constructor.
+        Context4()
+            : subnet_(), clientid_(), hwaddr_(), hint_("0.0.0.0"),
+              fwd_dns_update_(false), rev_dns_update_(false),
+              hostname_(""), callout_handle_(), fake_allocation_(false),
+              old_lease_(), host_() {
+        }
+    };
 
 
     /// @brief Default constructor.
     /// @brief Default constructor.
     ///
     ///
@@ -283,38 +350,20 @@ protected:
                    const isc::hooks::CalloutHandlePtr& callout_handle,
                    const isc::hooks::CalloutHandlePtr& callout_handle,
                    Lease4Ptr& old_lease);
                    Lease4Ptr& old_lease);
 
 
-    /// @brief Renews a IPv4 lease
+    /// @brief Renews an DHCPv4 lease.
     ///
     ///
-    /// Since both request and renew are implemented in DHCPv4 as the sending of
-    /// a REQUEST packet, it is difficult to easily distinguish between those
-    /// cases. Therefore renew for DHCPv4 is done in the allocation engine.
-    /// This method is also used when client crashed/rebooted and tries
-    /// to get a new lease. It thinks that it gets a new lease, but in fact
-    /// we are only renewing the still valid lease for that client.
+    /// This method updates the lease with the information from the provided
+    /// context and invokes the lease4_renew callout.
     ///
     ///
-    /// @param subnet A subnet the client is attached to
-    /// @param clientid Client identifier
-    /// @param hwaddr Client's hardware address
-    /// @param fwd_dns_update Indicates whether forward DNS update will be
-    ///        performed for the client (true) or not (false).
-    /// @param rev_dns_update Indicates whether reverse DNS update will be
-    ///        performed for the client (true) or not (false).
-    /// @param hostname A string carrying hostname to be used for DNS updates.
-    /// @param lease A lease to be renewed
-    /// @param callout_handle a callout handle (used in hooks). A lease callouts
-    ///        will be executed if this parameter is passed.
-    /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
-    ///        an address for DISCOVER that is not really allocated (true)
+    /// The address of the lease being renewed is NOT updated.
+    ///
+    /// @param ctx Message processing context. It holds various information
+    /// extracted from the client's message and required to allocate a lease.
+    ///
+    /// @return Returns renewed lease. Note that the lease is only updated when
+    /// it is an actual allocation (not processing DHCPDISCOVER message).
     Lease4Ptr
     Lease4Ptr
-    renewLease4(const SubnetPtr& subnet,
-                const ClientIdPtr& clientid,
-                const HWAddrPtr& hwaddr,
-                const bool fwd_dns_update,
-                const bool rev_dns_update,
-                const std::string& hostname,
-                const Lease4Ptr& lease,
-                const isc::hooks::CalloutHandlePtr& callout_handle,
-                bool fake_allocation /* = false */);
+    renewLease4(const Lease4Ptr& lease, const Context4& ctx);
 
 
     /// @brief Allocates an IPv6 lease
     /// @brief Allocates an IPv6 lease
     ///
     ///
@@ -396,6 +445,25 @@ private:
                            const isc::hooks::CalloutHandlePtr& callout_handle,
                            const isc::hooks::CalloutHandlePtr& callout_handle,
                            bool fake_allocation = false);
                            bool fake_allocation = false);
 
 
+    /// @brief Updates the specified lease with the information from a context.
+    ///
+    /// The context, specified as an argument to this method, holds various
+    /// information gathered from the client's message and passed to the
+    /// allocation engine. The allocation engine uses this information to make
+    /// lease allocation decisions. Some public methods of the allocation engine
+    /// requires updating the lease information with the data gathered from the
+    /// context, e.g. @c AllocEngine::reuseExpiredLease requires updating the
+    /// expired lease with a fresh information from the context to create a
+    /// lease to be held for the client.
+    ///
+    /// Note that this doesn't update the lease address.
+    ///
+    /// @param [out] lease A pointer to the lease to be updated.
+    /// @param ctx A context containing information from the server about the
+    /// client and its message.
+    void updateLease4Information(const Lease4Ptr& lease,
+                                 const Context4& ctx) const;
+
     /// @brief creates a lease and inserts it in LeaseMgr if necessary
     /// @brief creates a lease and inserts it in LeaseMgr if necessary
     ///
     ///
     /// Creates a lease based on specified parameters and tries to insert it
     /// Creates a lease based on specified parameters and tries to insert it
@@ -433,36 +501,57 @@ private:
                            const isc::hooks::CalloutHandlePtr& callout_handle,
                            const isc::hooks::CalloutHandlePtr& callout_handle,
                            bool fake_allocation = false);
                            bool fake_allocation = false);
 
 
-    /// @brief Reuses expired IPv4 lease
+    /// @brief Reuses expired DHCPv4 lease.
     ///
     ///
-    /// Updates existing expired lease with new information. Lease database
-    /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
-    /// dummy allocation request (i.e. DISCOVER, fake_allocation = true).
+    /// Makes new allocation using an expired lease. The lease is updated with
+    /// the information from the provided context. Typically, an expired lease
+    /// lease which belonged to one client may be assigned to another client
+    /// which asked for the specific address.
     ///
     ///
-    /// @param expired Old, expired lease
-    /// @param subnet Subnet the lease is allocated from
-    /// @param clientid Client identifier
-    /// @param hwaddr Client's hardware address
-    /// @param fwd_dns_update Indicates whether forward DNS update will be
-    ///        performed for the client (true) or not (false).
-    /// @param rev_dns_update Indicates whether reverse DNS update will be
-    ///        performed for the client (true) or not (false).
-    /// @param hostname A string carrying hostname to be used for DNS updates.
-    /// @param callout_handle A callout handle (used in hooks). A lease callouts
-    ///        will be executed if this parameter is passed.
-    /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
-    ///        an address for DISCOVER that is not really allocated (true)
-    /// @return refreshed lease
-    /// @throw BadValue if trying to recycle lease that is still valid
-    Lease4Ptr reuseExpiredLease(Lease4Ptr& expired,
-                                const SubnetPtr& subnet,
-                                const ClientIdPtr& clientid,
-                                const HWAddrPtr& hwaddr,
-                                const bool fwd_dns_update,
-                                const bool rev_dns_update,
-                                const std::string& hostname,
-                                const isc::hooks::CalloutHandlePtr& callout_handle,
-                                bool fake_allocation = false);
+    /// @param expired An old, expired lease.
+    /// @param ctx Message processing context. It holds various information
+    /// extracted from the client's message and required to allocate a lease.
+    ///
+    /// @return Updated lease instance.
+    /// @throw BadValue if trying to reuse a lease which is still valid or
+    /// when the provided parameters are invalid.
+    Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const Context4& ctx);
+
+    /// @brief Updates the existing, non expired lease with a information from
+    /// the context.
+    ///
+    /// This method is invoked when the client requests allocation of the
+    /// (reserved) lease but there is a lease for this client with a different
+    /// address in the database already. In this case the existing lease must
+    /// be updated in the database with a new information. In particular,
+    /// with a new address.
+    ///
+    /// This method invokes the lease4_release and lease4_select callouts.
+    ///
+    /// @param lease A pointer to the lease to be updated.
+    /// @param ctx A context to be used to update the lease.
+    ///
+    /// @return Pointer to the updated lease.
+    /// @throw BadValue if the provided parameters are invalid.
+    Lease4Ptr replaceClientLease(Lease4Ptr& lease, const Context4& ctx);
+
+    /// @brief Replace or renew client's lease.
+    ///
+    /// This method is ivoked by the @c AllocEngine::allocateLease4 when it
+    /// finds that the lease for the particular client already exists in the
+    /// database. If the existing lease has the same IP address as the one
+    /// that the client should be allocated the existing lease is renewed.
+    /// If the client should be allocated a different address, e.g. there
+    /// is a static reservation for the client, the existing lease is replaced
+    /// with a new one. This method handles both cases.
+    ///
+    /// @param lease Existing lease.
+    /// @param ctx Context holding parameters to be used for the lease
+    /// allocation.
+    ///
+    /// @return Updated lease, or NULL if allocation was unsucessful.
+    /// @throw BadValue if specified parameters are invalid.
+    Lease4Ptr reallocateClientLease(Lease4Ptr& lease, Context4& ctx);
 
 
     /// @brief Reuses expired IPv6 lease
     /// @brief Reuses expired IPv6 lease
     ///
     ///

+ 6 - 0
src/lib/dhcpsrv/host_mgr.h

@@ -15,8 +15,14 @@
 #ifndef HOST_MGR_H
 #ifndef HOST_MGR_H
 #define HOST_MGR_H
 #define HOST_MGR_H
 
 
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/subnet_id.h>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
+#include <string>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {

+ 568 - 11
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc

@@ -19,6 +19,7 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp4.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
@@ -410,14 +411,25 @@ public:
     /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
     /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
     /// used in many tests, initializes cfg_mgr configuration and creates
     /// used in many tests, initializes cfg_mgr configuration and creates
     /// lease database.
     /// lease database.
+    ///
+    /// It also re-initializes the Host Manager.
     AllocEngine4Test() {
     AllocEngine4Test() {
+        // Create fresh instance of the HostMgr, and drop any previous HostMgr
+        // state.
+        HostMgr::instance().create();
+
         clientid_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x44)));
         clientid_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x44)));
-        static uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
+        uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
 
 
         // Let's use odd hardware type to check if there is no Ethernet
         // Let's use odd hardware type to check if there is no Ethernet
         // hardcoded anywhere.
         // hardcoded anywhere.
         hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
         hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
 
 
+        // Allocate different MAC address for the tests that require two
+        // different MAC addresses.
+        ++mac[sizeof(mac) - 1];
+        hwaddr2_ = HWAddrPtr(new HWAddr(mac, sizeof (mac), HTYPE_FDDI));
+
         // instantiate cfg_mgr
         // instantiate cfg_mgr
         CfgMgr& cfg_mgr = CfgMgr::instance();
         CfgMgr& cfg_mgr = CfgMgr::instance();
 
 
@@ -429,6 +441,13 @@ public:
         cfg_mgr.commit();
         cfg_mgr.commit();
 
 
         factory_.create("type=memfile universe=4 persist=false");
         factory_.create("type=memfile universe=4 persist=false");
+
+        // Create a default context. Note that remaining parameters must be
+        // assigned when needed.
+        ctx_.subnet_ = subnet_;
+        ctx_.clientid_ = clientid_;
+        ctx_.hwaddr_ = hwaddr_;
+        ctx_.callout_handle_ = HooksManager::createCalloutHandle();
     }
     }
 
 
     /// @brief checks if Lease4 matches expected configuration
     /// @brief checks if Lease4 matches expected configuration
@@ -461,12 +480,15 @@ public:
         factory_.destroy();
         factory_.destroy();
     }
     }
 
 
-    ClientIdPtr clientid_;    ///< Client-identifier (value used in tests)
-    HWAddrPtr hwaddr_;        ///< Hardware address (value used in tests)
-    Subnet4Ptr subnet_;       ///< Subnet4 (used in tests)
-    Pool4Ptr pool_;           ///< Pool belonging to subnet_
-    LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory
-    Lease4Ptr old_lease_;     ///< Holds previous instance of the lease.
+    ClientIdPtr clientid_;      ///< Client-identifier (value used in tests)
+    HWAddrPtr hwaddr_;          ///< Hardware address (value used in tests)
+    HWAddrPtr hwaddr2_;         ///< Alternative hardware address.
+    Subnet4Ptr subnet_;         ///< Subnet4 (used in tests)
+    Pool4Ptr pool_;             ///< Pool belonging to subnet_
+    LeaseMgrFactory factory_;   ///< Pointer to LeaseMgr factory
+    Lease4Ptr old_lease_;       ///< Holds previous instance of the lease.
+    AllocEngine::Context4 ctx_; ///< Context information passed to various
+                                ///< allocation engine functions.
 };
 };
 
 
 // This test checks if the v6 Allocation Engine can be instantiated, parses
 // This test checks if the v6 Allocation Engine can be instantiated, parses
@@ -1532,7 +1554,6 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
 // called
 // called
 TEST_F(AllocEngine4Test, renewLease4) {
 TEST_F(AllocEngine4Test, renewLease4) {
     boost::scoped_ptr<AllocEngine> engine;
     boost::scoped_ptr<AllocEngine> engine;
-    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
 
 
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
                                                  100, false)));
                                                  100, false)));
@@ -1556,9 +1577,12 @@ TEST_F(AllocEngine4Test, renewLease4) {
     // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
     // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
     // renew it.
     // renew it.
     ASSERT_FALSE(lease->expired());
     ASSERT_FALSE(lease->expired());
-    lease = engine->renewLease4(subnet_, clientid_, hwaddr_, true,
-                                true, "host.example.com.", lease,
-                                callout_handle, false);
+    ctx_.fwd_dns_update_ = true;
+    ctx_.rev_dns_update_ = true;
+    ctx_.hostname_ = "host.example.com.";
+    ctx_.fake_allocation_ = false;
+    lease = engine->renewLease4(lease, ctx_);
+
     // Check that he got that single lease
     // Check that he got that single lease
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
     EXPECT_EQ(addr, lease->addr_);
     EXPECT_EQ(addr, lease->addr_);
@@ -1574,6 +1598,539 @@ TEST_F(AllocEngine4Test, renewLease4) {
     detailCompareLease(lease, from_mgr);
     detailCompareLease(lease, from_mgr);
 }
 }
 
 
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a reservation.
+// - Client sends DHCPREQUEST without requested IP Address, nor ciaddr.
+// - Client is allocated a reserved address.
+//
+// Note that client must normally include a requested IP address or ciaddr
+// in its message. But, we still want to provision clients that don't do that.
+// The server simply picks reserved address or any other available one if there
+// is no reservation.
+TEST_F(AllocEngine4Test, reservedAddressNoHint) {
+    // Create reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Try to allocate a lease without specifying a hint. This is actually
+    // incorrect behavior of the client to not send an address it wants to
+    // obtain but the server should handle this gracefully.
+    Lease4Ptr lease = engine.allocateLease4(subnet_, clientid_, hwaddr_,
+                                            IOAddress("0.0.0.0"),
+                                            false, false, "",
+                                            false, CalloutHandlePtr(),
+                                            old_lease_);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+    // Make sure that the lease has been committed to the lease database.
+    // And that the committed lease is equal to the one returned.
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+    detailCompareLease(lease, from_mgr);
+
+    // Initially, there was no lease for this client, so the returned old
+    // lease should be NULL.
+    EXPECT_FALSE(old_lease_);
+}
+
+// This test checks behavior of the allocation engine in the following scenario:
+// - Client has no lease in the database.
+// - Client has a reservation.
+// - Client sends DHCPDISCOVER without requested IP Address.
+// - Server returns DHCPOFFER with the reserved address.
+TEST_F(AllocEngine4Test,reservedAddressNoHintFakeAllocation) {
+    // Create reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Query allocation engine for the lease to be assigned to this
+    // client without specifying the address to be assigned.
+    Lease4Ptr lease = engine.allocateLease4(subnet_, clientid_, hwaddr_,
+                                            IOAddress("0.0.0.0"),
+                                            false, false, "",
+                                            true, CalloutHandlePtr(),
+                                            old_lease_);
+    ASSERT_TRUE(lease);
+    // The allocation engine should return a reserved address.
+    EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+    // This is a "fake" allocation so the returned lease should not be committed
+    // to the lease database.
+    EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+    // Client had no lease in the database, so the old lease returned should
+    // be NULL.
+    EXPECT_FALSE(old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a reservation.
+// - Client sends DHCPREQUEST with a requested IP address
+// - Server returns DHCPNAK when requested IP address is different than
+// the reserved address.
+// - Server allocates a reserved address to the client when the client requests
+// this address using requested IP address option.
+TEST_F(AllocEngine4Test, reservedAddressHint) {
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    Lease4Ptr lease = engine.allocateLease4(subnet_, clientid_, hwaddr_,
+                                            IOAddress("192.0.2.234"),
+                                            false, false, "",
+                                            false, CalloutHandlePtr(),
+                                            old_lease_);
+    ASSERT_FALSE(lease);
+    ASSERT_FALSE(old_lease_);
+
+    lease = engine.allocateLease4(subnet_, clientid_, hwaddr_,
+                                  IOAddress("192.0.2.123"),
+                                  false, false, "",
+                                  false, CalloutHandlePtr(),
+                                  old_lease_);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+    // Make sure that the lease has been committed to the lease database.
+    // And that the committed lease is equal to the one returned.
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+    detailCompareLease(lease, from_mgr);
+
+    EXPECT_FALSE(old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a reservation.
+// - Client sends DHCPDISCOVER with a requested IP address as a hint.
+// - Server offers a reserved address, even though it is different than the
+// requested address.
+TEST_F(AllocEngine4Test, reservedAddressHintFakeAllocation) {
+    // Create a reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Query the allocation engine for the lease to be assigned to the client
+    // and specify a hint being a different address than the reserved one.
+    Lease4Ptr lease = engine.allocateLease4(subnet_, clientid_, hwaddr_,
+                                            IOAddress("192.0.2.234"),
+                                            false, false, "",
+                                            true, CalloutHandlePtr(),
+                                            old_lease_);
+    ASSERT_TRUE(lease);
+    // Allocation engine should return reserved address.
+    EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+    // This is a "fake" allocation so the returned lease should not be committed
+    // to the lease database.
+    EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+    EXPECT_FALSE(old_lease_);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a lease for the address from the dynamic pool in the database.
+// - Client has a reservation for a different address than the one for which
+// the client has a lease.
+// - Client sends DHCPREQUEST, asking for the reserved address (as it has been
+// offered to it when it sent DHCPDISCOVER).
+// - Server allocates a reserved address and removes the lease for the address
+// previously allocated to the client.
+TEST_F(AllocEngine4Test, reservedAddressExistingLease) {
+    // Create the reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Create a lease for the client with a different address than the reserved
+    // one.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0,
+                               100, 30, 60, time(NULL), subnet_->getID(),
+                               false, false, ""));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Request allocation of the reserved address.
+    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                                      hwaddr_,
+                                                      IOAddress("192.0.2.123"),
+                                                      false, false, "",
+                                                      false, CalloutHandlePtr(),
+                                                      old_lease_);
+    ASSERT_TRUE(allocated_lease);
+    // The engine should have allocated the reserved address.
+    EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+    // Make sure that the lease has been committed to the lease database.
+    Lease4Ptr from_mgr =
+        LeaseMgrFactory::instance().getLease4(allocated_lease->addr_);
+    ASSERT_TRUE(from_mgr);
+    detailCompareLease(allocated_lease, from_mgr);
+
+    // The previous lease should have been replaced by a new one. The previous
+    // lease should be returned by the allocation engine to the caller.
+    ASSERT_TRUE(old_lease_);
+    EXPECT_EQ("192.0.2.101", old_lease_->addr_.toText());
+    detailCompareLease(old_lease_, lease);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client A has a lease in the database.
+// - Client B has a reservation for the address in use by client A.
+// - Client B sends a DHCPREQUEST requesting the allocation of the reserved
+// lease (in use by client A).
+// - Server determines that the reserved address is in use by a different client
+// and returns DHCPNAK to client B.
+TEST_F(AllocEngine4Test, reservedAddressHijacked) {
+    // Create host reservation for the client B.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Allocate a lease for the client A for the same address as reserved
+    // for the client B.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.2.123"), hwaddr2_, 0, 0,
+                               100, 30, 60, time(NULL), subnet_->getID(),
+                               false, false, ""));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Try to allocate the reserved lease to client B.
+    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                                      hwaddr_,
+                                                      IOAddress("192.0.2.123"),
+                                                      false, false, "",
+                                                      false, CalloutHandlePtr(),
+                                                      old_lease_);
+    // The lease is allocated to someone else, so the allocation should not
+    // succeed.
+    ASSERT_FALSE(allocated_lease);
+    EXPECT_FALSE(old_lease_);
+
+    // Make sure that the allocation engine didn't modify the lease of the
+    // client A.
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+    detailCompareLease(lease, from_mgr);
+
+    // Try doing the same thing, but this time do not request any specific
+    // address. It should have the same effect.
+    allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                            hwaddr_,
+                                            IOAddress("0.0.0.0"),
+                                            false, false, "",
+                                            false, CalloutHandlePtr(),
+                                            old_lease_);
+    ASSERT_FALSE(allocated_lease);
+    EXPECT_FALSE(old_lease_);
+
+    from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+    detailCompareLease(lease, from_mgr);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client A has a lease in the database.
+// - Client B has a reservation for the address in use by client A.
+// - Client B sends a DHCPDISCOVER.
+// - Server determines that the reserved address is in use by a different client
+// and that it can't allocate a lease to the client B.
+//
+// In the scenario presented here, the allocation engine should return a
+// NULL lease to the server. When the server receives NULL pointer from the
+// allocation engine the proper action for the server will be to not
+// respond to the client. Instead it should report to the administrator
+// that it was unable to allocate the (reserved) lease.
+TEST_F(AllocEngine4Test, reservedAddressHijackedFakeAllocation) {
+    // Create a reservation for the client B.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Create a lease for the client A.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.2.123"), hwaddr2_, 0, 0,
+                               100, 30, 60, time(NULL), subnet_->getID(),
+                               false, false, ""));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Query allocation engine for the lease to be allocated to the client B.
+    // The allocation engine is not able to allocate the lease to the client
+    // B, because the address is in use by client A.
+    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                                      hwaddr_,
+                                                      IOAddress("192.0.2.123"),
+                                                      false, false, "",
+                                                      true, CalloutHandlePtr(),
+                                                      old_lease_);
+    // The allocation engine should return no lease.
+    ASSERT_FALSE(allocated_lease);
+    EXPECT_FALSE(old_lease_);
+
+    // Do the same test. But, this time do not specify any address to be
+    // allocated.
+    allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                            hwaddr_,
+                                            IOAddress("0.0.0.0"),
+                                            false, false, "",
+                                            true, CalloutHandlePtr(),
+                                            old_lease_);
+    EXPECT_FALSE(allocated_lease);
+    EXPECT_FALSE(old_lease_);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a reservation.
+// - Client has a lease in the database for a different address than reserved.
+// - Client sends a DHCPREQUEST and asks for a different address than reserved,
+// and different than it has in a database.
+// - Server doesn't allocate the reserved address to the client because the
+// client asked for the different address.
+//
+// Note that in this case the client should get the DHCPNAK and should fall back
+// to the DHCPDISCOVER.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseInvalidHint) {
+    // Create a reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Create a lease for the client for a different address than reserved.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0,
+                               100, 30, 60, time(NULL), subnet_->getID(),
+                               false, false, ""));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Try to allocate a lease and specify a different address than reserved
+    // and different from the one that client is currently using.
+    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                                      hwaddr_,
+                                                      IOAddress("192.0.2.102"),
+                                                      false, false, "",
+                                                      false, CalloutHandlePtr(),
+                                                      old_lease_);
+    ASSERT_FALSE(allocated_lease);
+    ASSERT_FALSE(old_lease_);
+
+    // Repeat the test, but this time ask for the address that the client
+    // has allocated.
+    allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                            hwaddr_,
+                                            IOAddress("192.0.2.101"),
+                                            false, false, "",
+                                            false, CalloutHandlePtr(),
+                                            old_lease_);
+    // The client has reservation so the server wants to allocate a
+    // reserved address and doesn't want to renew the address that the
+    // client is currently using. This is equivalent of the case when
+    // the client tries to renew the lease but there is a new reservation
+    // for this client. The server doesn't allow for the renewal and
+    // responds with DHCPNAK to force the client to return to the
+    // DHCP server discovery.
+    EXPECT_FALSE(allocated_lease);
+    EXPECT_FALSE(old_lease_);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a lease in the database.
+// - Client has a reservation for a different address than the one for which it
+// has a lease.
+// - Client sends a DHCPDISCOVER and asks for a different address than reserved
+// and different from which it has a lease for.
+// - Server ignores the client's hint and offers a reserved address.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) {
+    // Create a reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Create a lease for a different address than reserved.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0,
+                               100, 30, 60, time(NULL), subnet_->getID(),
+                               false, false, ""));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Try to allocate a lease and use a completely different address
+    // as a hint.
+    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                                      hwaddr_,
+                                                      IOAddress("192.0.2.102"),
+                                                      false, false, "",
+                                                      true, CalloutHandlePtr(),
+                                                      old_lease_);
+    // Server should offer a lease for a reserved address.
+    ASSERT_TRUE(allocated_lease);
+    EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+    // The lease should not be allocated until the client sends a DHCPREQUEST.
+    EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(allocated_lease->addr_));
+
+    // Old lease should contain the currently used lease.
+    ASSERT_TRUE(old_lease_);
+    EXPECT_EQ("192.0.2.101", old_lease_->addr_.toText());
+
+    // Repeat the test but this time ask for the address for which the
+    // client has a lease.
+    allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                            hwaddr_,
+                                            IOAddress("192.0.2.101"),
+                                            false, false, "",
+                                            true, CalloutHandlePtr(),
+                                            old_lease_);
+    // The server should offer the lease, but not for the address that
+    // the client requested. The server should offer a reserved address.
+    ASSERT_TRUE(allocated_lease);
+    EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+    // Old lease should contain the currently used lease.
+    ASSERT_TRUE(old_lease_);
+    EXPECT_EQ("192.0.2.101", old_lease_->addr_.toText());
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a reservation.
+// - Client has a lease for a different address than reserved.
+// - Client sends a DHCPREQUEST to allocate a lease.
+// - The server determines that the client has a reservation for the
+// different address than it is currently using and should assign
+// a reserved address and remove the previous lease.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHint) {
+    // Create a reservation.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Create a lease for a different address than reserved.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0,
+                               100, 30, 60, time(NULL), subnet_->getID(),
+                               false, false, ""));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Try to allocate a lease with providing no hint.
+    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                                      hwaddr_,
+                                                      IOAddress("0.0.0.0"),
+                                                      false, false, "",
+                                                      false, CalloutHandlePtr(),
+                                                      old_lease_);
+    // The reserved address should be allocated.
+    ASSERT_TRUE(allocated_lease);
+    EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+    // The previous lease should be removed.
+    EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+    // Make sure that the allocated lease is committed in the lease database.
+    Lease4Ptr from_mgr =
+        LeaseMgrFactory::instance().getLease4(allocated_lease->addr_);
+    ASSERT_TRUE(from_mgr);
+    detailCompareLease(allocated_lease, from_mgr);
+
+    // Old lease should be returned.
+    ASSERT_TRUE(old_lease_);
+    detailCompareLease(lease, old_lease_);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a reservation.
+// - Client has a lease for a different address than reserved.
+// - Client sends a DHCPDISCOVER with no hint.
+// - Server determines that there is a reservation for the client and that
+// the current lease should be removed and the reserved address should be
+// allocated.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHintFakeAllocation) {
+    // Create a reservation.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Create a lease for a different address than reserved.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0,
+                               100, 30, 60, time(NULL), subnet_->getID(),
+                               false, false, ""));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Query the allocation engine for the lease to be allocated for the
+    // client.
+    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
+                                                      hwaddr_,
+                                                      IOAddress("0.0.0.0"),
+                                                      false, false, "",
+                                                      true, CalloutHandlePtr(),
+                                                      old_lease_);
+    // The server should offer the reserved address.
+    ASSERT_TRUE(allocated_lease);
+    EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+    // The lease should not be committed to the lease database until the
+    // client sends a DHCPREQUEST.
+    EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(allocated_lease->addr_));
+
+    // The old lease should reflect what is in the database.
+    ASSERT_TRUE(old_lease_);
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+    detailCompareLease(lease, from_mgr);
+}
+
 /// @brief helper class used in Hooks testing in AllocEngine6
 /// @brief helper class used in Hooks testing in AllocEngine6
 ///
 ///
 /// It features a couple of callout functions and buffers to store
 /// It features a couple of callout functions and buffers to store