]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[3694] Grouping DHCPv4 functions in the allocation engine.
authorMarcin Siodelski <marcin@isc.org>
Tue, 24 Feb 2015 11:38:30 +0000 (12:38 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 24 Feb 2015 11:38:30 +0000 (12:38 +0100)
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc

index 571650a025bf8d9a6a566290420abe6c210bdff7..64dcdb6b76e369c46f6db22a303dcff7c19eb6e2 100644 (file)
@@ -58,28 +58,6 @@ struct AllocEngineHooks {
 // module is called.
 AllocEngineHooks Hooks;
 
-bool
-addressReserved(const IOAddress& address, const AllocEngine::ClientContext4& ctx) {
-    ConstHostPtr host = HostMgr::instance().get4(ctx.subnet_->getID(), address);
-    HWAddrPtr host_hwaddr;
-    if (host) {
-        host_hwaddr = host->getHWAddress();
-        if (ctx.hwaddr_ && host_hwaddr) {
-            /// @todo Use the equality operators for HWAddr class.
-            /// Currently, this is impossible because the HostMgr uses the
-            /// HTYPE_ETHER type, whereas the unit tests may use other types
-            /// which HostMgr doesn't support yet.
-            return (host_hwaddr->hwaddr_ != ctx.hwaddr_->hwaddr_);
-
-        } else {
-            return (true);
-
-        }
-    }
-    return (false);
-}
-
-
 }; // anonymous namespace
 
 namespace isc {
@@ -90,8 +68,7 @@ AllocEngine::ClientContext4::ClientContext4()
       requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
       fwd_dns_update_(false), rev_dns_update_(false),
       hostname_(""), callout_handle_(), fake_allocation_(false),
-      old_lease_(), host_(), lease_for_host_(),
-      interrupt_processing_(false) {
+      old_lease_(), host_(), interrupt_processing_(false) {
 }
 
 bool
@@ -326,6 +303,20 @@ AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts,
     hook_index_lease6_select_ = Hooks.hook_index_lease6_select_;
 }
 
+AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
+    std::map<Lease::Type, AllocatorPtr>::const_iterator alloc = allocators_.find(type);
+
+    if (alloc == allocators_.end()) {
+        isc_throw(BadValue, "No allocator initialized for pool type "
+                  << Lease::typeToText(type));
+    }
+    return (alloc->second);
+}
+
+// ##########################################################################
+// #    DHCPv6 lease allocation code starts here.
+// ##########################################################################
+
 Lease6Collection
 AllocEngine::allocateLeases6(ClientContext6& ctx) {
 
@@ -853,159 +844,9 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
         existing_leases.end(), Lease6Ptr()), existing_leases.end());
 }
 
-Lease4Ptr
-AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
-                            const HWAddrPtr& hwaddr, const IOAddress& hint,
-                            const bool fwd_dns_update, const bool rev_dns_update,
-                            const std::string& hostname, bool fake_allocation,
-                            const isc::hooks::CalloutHandlePtr& callout_handle,
-                            Lease4Ptr& old_lease) {
-
-    // The NULL pointer indicates that the old lease didn't exist. It may
-    // be later set to non NULL value if existing lease is found in the
-    // database.
-    old_lease.reset();
-
-    Lease4Ptr new_lease;
-
-    /// @todo The context for lease allocation should really be created
-    /// by the DHCPv4 server and passed to this function. The reason for
-    /// this is that the server should retrieve the Host object for the
-    /// client because the Host object contains the data not only useful
-    /// for the address allocation but also hostname and DHCP options
-    /// for the client. The Host object should be passed in the context.
-    /// Making this change would require a change to the allocateLease4
-    /// API which would in turn require lots of changes in unit tests.
-    /// The ticket introducing a context and host reservation in the
-    /// allocation engine is complex enough by itself to warrant that
-    /// the API change is done with a separate ticket (#3709).
-    ClientContext4 ctx;
-    ctx.subnet_ = subnet;
-    ctx.clientid_ = clientid;
-    ctx.hwaddr_ = hwaddr;
-    ctx.requested_address_ = 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;
-
-    try {
-        if (!subnet) {
-            isc_throw(BadValue, "Can't allocate IPv4 address without subnet");
-        }
-
-        if (!hwaddr) {
-            isc_throw(BadValue, "HWAddr must be defined");
-        }
-
-        ctx.host_ = HostMgr::instance().get4(subnet->getID(), hwaddr);
-
-        new_lease = ctx.fake_allocation_ ? discoverLease4(ctx) : requestLease4(ctx);
-        if (!new_lease) {
-            // Unable to allocate an address, return an empty lease.
-            LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_FAIL).arg(attempts_);
-        }
-
-    } catch (const isc::Exception& e) {
-        // Some other error, return an empty lease.
-        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_ERROR).arg(e.what());
-    }
-
-    if (ctx.old_lease_) {
-        old_lease.reset(new Lease4(*ctx.old_lease_));
-    }
-
-    return (new_lease);
-}
-
-Lease4Ptr
-AllocEngine::renewLease4(const Lease4Ptr& lease,
-                         AllocEngine::ClientContext4& ctx) {
-    if (!lease) {
-        isc_throw(BadValue, "null lease specified for renewLease4");
-    }
-
-    // The ctx.host_ possibly contains a reservation for the client for which
-    // we are renewing a lease. If this reservation exists, we assume that
-    // there is no conflict in assigning the address to this client. Note
-    // that the reallocateClientLease checks if the address reserved for
-    // the client matches the address in the lease we're renewing here.
-    if (!ctx.host_) {
-        // Do not renew the lease if:
-        // - If address is reserved for someone else or ...
-        // - renewed address doesn't belong to a pool.
-        if (addressReserved(lease->addr_, ctx) ||
-            (!ctx.subnet_->inPool(Lease::TYPE_V4, lease->addr_))) {
-            ctx.interrupt_processing_ = !ctx.fake_allocation_;
-            return (Lease4Ptr());
-        }
-
-    }
-
-    // Let's keep the old data. This is essential if we are using memfile
-    // (the lease returned points directly to the lease4 object in the database)
-    // We'll need it if we want to skip update (i.e. roll back renewal)
-    /// @todo: remove this once #3083 is implemented
-    Lease4 old_values = *lease;
-    ctx.old_lease_.reset(new Lease4(old_values));
-
-    // Update the lease with the information from the context.
-    updateLease4Information(lease, ctx);
-
-    bool skip = false;
-    // Execute all callouts registered for lease4_renew.
-    if (HooksManager::getHooksManager().
-        calloutsPresent(Hooks.hook_index_lease4_renew_)) {
-
-        // Delete all previous arguments
-        ctx.callout_handle_->deleteAllArguments();
-
-        // Subnet from which we do the allocation. Convert the general subnet
-        // 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
-        // version of dynamic_pointer_cast.
-        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
-
-        // Pass the parameters
-        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
-        ctx.callout_handle_->setArgument("lease4", lease);
-
-        // Call all installed callouts
-        HooksManager::callCallouts(Hooks.hook_index_lease4_renew_,
-                                   *ctx.callout_handle_);
-
-        // Callouts decided to skip the next processing step. The next
-        // processing step would to actually renew the lease, so skip at this
-        // stage means "keep the old lease as it is".
-        if (ctx.callout_handle_->getSkip()) {
-            skip = true;
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
-                      DHCPSRV_HOOK_LEASE4_RENEW_SKIP);
-        }
-    }
-
-    if (!ctx.fake_allocation_ && !skip) {
-        // for REQUEST we do update the lease
-        LeaseMgrFactory::instance().updateLease4(lease);
-    }
-    if (skip) {
-        // Rollback changes (really useful only for memfile)
-        /// @todo: remove this once #3083 is implemented
-        *lease = old_values;
-    }
-
-    return (lease);
-}
-
-Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
-                                         ClientContext6& ctx,
-                                         uint8_t prefix_len) {
+Lease6Ptr
+AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
+                               uint8_t prefix_len) {
 
     if (!expired->expired()) {
         isc_throw(BadValue, "Attempt to recycle lease that is still valid");
@@ -1079,297 +920,483 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
     return (expired);
 }
 
-Lease4Ptr
-AllocEngine::allocateOrReuseLease(const IOAddress& candidate, ClientContext4& ctx) {
-    Lease4Ptr exist_lease = LeaseMgrFactory::instance().getLease4(candidate);
-    if (exist_lease) {
-        if (exist_lease->expired()) {
-            ctx.old_lease_.reset(new Lease4(*exist_lease));
-            return (reuseExpiredLease(exist_lease, ctx));
-        }
-
-    } else {
-        return (createLease4(ctx.subnet_, ctx.clientid_,
-                             ctx.hwaddr_, candidate, ctx.fwd_dns_update_,
-                             ctx.rev_dns_update_, ctx.hostname_, ctx.callout_handle_,
-                             ctx.fake_allocation_));
-    }
-    return (Lease4Ptr());
-}
-
-
-Lease4Ptr
-AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
-                               AllocEngine::ClientContext4& ctx) {
-    if (!expired) {
-        isc_throw(BadValue, "null lease specified for reuseExpiredLease");
-    }
+Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
+                                    const IOAddress& addr,
+                                    uint8_t prefix_len) {
 
-    if (!ctx.subnet_) {
-        isc_throw(BadValue, "null subnet specified for the reuseExpiredLease");
+    if (ctx.type_ != Lease::TYPE_PD) {
+        prefix_len = 128; // non-PD lease types must be always /128
     }
 
-    updateLease4Information(expired, ctx);
-    expired->fixed_ = false;
+    Lease6Ptr lease(new Lease6(ctx.type_, addr, ctx.duid_, ctx.iaid_,
+                               ctx.subnet_->getPreferred(), ctx.subnet_->getValid(),
+                               ctx.subnet_->getT1(), ctx.subnet_->getT2(),
+                               ctx.subnet_->getID(), ctx.hwaddr_, prefix_len));
 
-    /// @todo: log here that the lease was reused (there's ticket #2524 for
-    /// logging in libdhcpsrv)
+    lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+    lease->fqdn_rev_ = ctx.rev_dns_update_;
+    lease->hostname_ = ctx.hostname_;
 
-    // Let's execute all callouts registered for lease4_select
-    if (ctx.callout_handle_ &&  HooksManager::getHooksManager()
-        .calloutsPresent(hook_index_lease4_select_)) {
+    // Let's execute all callouts registered for lease6_select
+    if (ctx.callout_handle_ &&
+        HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
 
         // Delete all previous arguments
         ctx.callout_handle_->deleteAllArguments();
 
         // Pass necessary arguments
 
-        // Subnet from which we do the allocation. Convert the general subnet
-        // 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
-        // version of dynamic_pointer_cast.
-        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
-        ctx.callout_handle_->setArgument("subnet4", subnet4);
+        // Subnet from which we do the allocation
+        ctx.callout_handle_->setArgument("subnet6", ctx.subnet_);
 
         // Is this solicit (fake = true) or request (fake = false)
-        ctx.callout_handle_->setArgument("fake_allocation",
-                                         ctx.fake_allocation_);
-
-        // The lease that will be assigned to a client
-        ctx.callout_handle_->setArgument("lease4", expired);
+        ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
+        ctx.callout_handle_->setArgument("lease6", lease);
 
-        // Call the callouts
-        HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_);
+        // This is the first callout, so no need to clear any arguments
+        HooksManager::callCallouts(hook_index_lease6_select_, *ctx.callout_handle_);
 
         // 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
         // won't be inserted into the database.
         if (ctx.callout_handle_->getSkip()) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
-                      DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
-            return (Lease4Ptr());
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
+            return (Lease6Ptr());
         }
 
         // Let's use whatever callout returned. Hopefully it is the same lease
         // we handled to it.
-        ctx.callout_handle_->getArgument("lease4", expired);
+        ctx.callout_handle_->getArgument("lease6", lease);
     }
 
     if (!ctx.fake_allocation_) {
-        // for REQUEST we do update the lease
-        LeaseMgrFactory::instance().updateLease4(expired);
-    }
-
-    // We do nothing for SOLICIT. We'll just update database when
-    // the client gets back to us with REQUEST message.
-
-    // it's not really expired at this stage anymore - let's return it as
-    // an updated lease
-    return (expired);
-}
-
-Lease4Ptr
-AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) { 
-    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+        // That is a real (REQUEST) allocation
+        bool status = LeaseMgrFactory::instance().addLease(lease);
 
-    Lease4Ptr client_lease = lease_mgr.getLease4(*ctx.hwaddr_, ctx.subnet_->getID());
-    if (!client_lease && ctx.clientid_) {
-        client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
-    }
+        if (status) {
 
-    Lease4Ptr new_lease;
-    if (ctx.host_) {
-        new_lease = allocateOrReuseLease(ctx.host_->getIPv4Reservation(), ctx);
-        if (new_lease) {
-            if (client_lease) {
-                ctx.old_lease_.reset(new Lease4(*client_lease));
-            }
-            return (new_lease);
+            return (lease);
+        } else {
+            // One of many failures with LeaseMgr (e.g. lost connection to the
+            // database, database failed etc.). One notable case for that
+            // is that we are working in multi-process mode and we lost a race
+            // (some other process got that address first)
+            return (Lease6Ptr());
         }
-    }
+    } else {
+        // That is only fake (SOLICIT without rapid-commit) allocation
 
-    if (ctx.requested_address_.isV4Zero() && !addressReserved(ctx.requested_address_, ctx)) {
-        if (ctx.subnet_->inPool(Lease::TYPE_V4, ctx.requested_address_)) {
-            new_lease = allocateOrReuseLease(ctx.requested_address_, ctx);
-            if (new_lease) {
-                if (client_lease) {
-                    ctx.old_lease_.reset(new Lease4(*client_lease));
-                }
-                return (new_lease);
-            }
+        // It is for advertise only. We should not insert the lease into LeaseMgr,
+        // but rather check that we could have inserted it.
+        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(
+                             Lease::TYPE_NA, addr);
+        if (!existing) {
+            return (lease);
+        } else {
+            return (Lease6Ptr());
         }
+    }
+}
 
-        if (client_lease) {
-            if ((client_lease->addr_ == ctx.requested_address_) ||
-                ctx.requested_address_.isV4Zero()) {
-                return (renewLease4(client_lease, ctx));
-            }
+Lease6Collection
+AllocEngine::renewLeases6(ClientContext6& ctx) {
+    try {
+        if (!ctx.subnet_) {
+            isc_throw(InvalidOperation, "Subnet is required for allocation");
         }
-    }
 
-    AllocatorPtr allocator = getAllocator(Lease::TYPE_V4);
-    const uint64_t attempts = attempts_ == 0 ? std::numeric_limits<uint64_t>::max() : attempts_;
-    for (uint64_t i = ctx.subnet_->getPoolCapacity(Lease::TYPE_V4); i < attempts; ++i) {
-        IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.clientid_,
-                                                     ctx.requested_address_);
-        if (!addressReserved(candidate, ctx)) {
-            new_lease = allocateOrReuseLease(candidate, ctx);
-            if (new_lease) {
-                return (new_lease);
-            }
+        if (!ctx.duid_) {
+            isc_throw(InvalidOperation, "DUID is mandatory for allocation");
         }
-    }
 
-    return (Lease4Ptr());
-}
+        // Check which host reservation mode is supported in this subnet.
+        Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
 
-Lease4Ptr
-AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
-    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
-    Lease4Ptr client_lease = lease_mgr.getLease4(*ctx.hwaddr_, ctx.subnet_->getID());
-    if (!client_lease && ctx.clientid_) {
-        client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
-    }
+        // Check if there's a host reservation for this client. Attempt to get
+        // host info only if reservations are not disabled.
+        if (hr_mode != Subnet::HR_DISABLED) {
 
-    if (!ctx.requested_address_.isV4Zero()) {
-        if (addressReserved(ctx.requested_address_, ctx)) {
-            return (Lease4Ptr());
+            ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), ctx.duid_,
+                                                 ctx.hwaddr_);
+        } else {
+            // Host reservations disabled? Then explicitly set host to NULL
+            ctx.host_.reset();
         }
 
-    } else if (ctx.host_) {
-        ctx.requested_address_ = ctx.host_->getIPv4Reservation();
-    }
-
-    if (!ctx.requested_address_.isV4Zero()) {
-        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(ctx.requested_address_);
-        if (existing && !existing->expired()) {
-            if (!ctx.myLease(*existing)) {
-                return (Lease4Ptr());
-            }
+        // Check if there are any leases for this client.
+        Lease6Collection leases = LeaseMgrFactory::instance()
+            .getLeases6(ctx.type_, *ctx.duid_, ctx.iaid_, ctx.subnet_->getID());
 
-        } else if (ctx.host_ && (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) {
-            return (Lease4Ptr());
+        if (!leases.empty()) {
+            // Check if the existing leases are reserved for someone else.
+            // If they're not, we're ok to keep using them.
+            removeNonmatchingReservedLeases6(ctx, leases);
         }
-    }
 
-    if (client_lease) {
-        if ((client_lease->addr_ == ctx.requested_address_) ||
-            ctx.requested_address_.isV4Zero()) {
-            return (renewLease4(client_lease, ctx));
-        }
-    }
+        if (ctx.host_) {
+            // If we have host reservation, allocate those leases.
+            allocateReservedLeases6(ctx, leases);
 
-    if (!ctx.subnet_->inPool(Lease4::TYPE_V4, ctx.requested_address_)) {
-        if ((ctx.host_ && (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) ||
-            (!ctx.host_ && !ctx.requested_address_.isV4Zero())) {
-            return (Lease4Ptr());
+            // There's one more check to do. Let's remove leases that are not
+            // matching reservations, i.e. if client X has address A, but there's
+            // a reservation for address B, we should release A and reassign B.
+            // Caveat: do this only if we have at least one reserved address.
+            removeNonreservedLeases6(ctx, leases);
         }
-    }
 
-    Lease4Ptr new_lease;
-    if (!ctx.requested_address_.isV4Zero()) {
-        new_lease = allocateOrReuseLease(ctx.requested_address_, ctx);
-        if (new_lease) {
-            if (client_lease && (client_lease->addr_ != new_lease->addr_)) {
-                ctx.old_lease_ = client_lease;
-                lease_mgr.deleteLease(client_lease->addr_);
-            }
-            return (new_lease);
+        // If we happen to removed all leases, get something new for this guy.
+        // Depending on the configuration, we may enable or disable granting
+        // new leases during renewals. This is controlled with the
+        // allow_new_leases_in_renewals_ field.
+        if (leases.empty() && ctx.allow_new_leases_in_renewals_) {
+            leases = allocateUnreservedLeases6(ctx);
         }
-    }
 
-    AllocatorPtr allocator = getAllocator(Lease::TYPE_V4);
-    const uint64_t attempts = attempts_ == 0 ? std::numeric_limits<uint64_t>::max() : attempts_;
-    for (uint64_t i = ctx.subnet_->getPoolCapacity(Lease::TYPE_V4); i < attempts; ++i) {
-        IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.clientid_,
-                                                     ctx.requested_address_);
-        if (!addressReserved(candidate, ctx)) {
-            new_lease = allocateOrReuseLease(candidate, ctx);
-            if (new_lease) {
-                return (new_lease);
-            }
+        // Extend all existing leases that passed all checks.
+        for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
+            extendLease6(ctx, *l);
         }
+
+        return (leases);
+
+    } catch (const isc::Exception& e) {
+
+        // Some other error, return an empty lease.
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_RENEW6_ERROR).arg(e.what());
     }
 
-    return (Lease4Ptr());
+    return (Lease6Collection());
 }
 
-Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
-                                    const IOAddress& addr,
-                                    uint8_t prefix_len) {
+void
+AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
 
-    if (ctx.type_ != Lease::TYPE_PD) {
-        prefix_len = 128; // non-PD lease types must be always /128
+    if (!lease || !ctx.subnet_) {
+        return;
     }
 
-    Lease6Ptr lease(new Lease6(ctx.type_, addr, ctx.duid_, ctx.iaid_,
-                               ctx.subnet_->getPreferred(), ctx.subnet_->getValid(),
-                               ctx.subnet_->getT1(), ctx.subnet_->getT2(),
-                               ctx.subnet_->getID(), ctx.hwaddr_, prefix_len));
+    // Check if the lease still belongs to the subnet. If it doesn't,
+    // we'll need to remove it.
+    if ((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) {
+        // Oh dear, the lease is no longer valid. We need to get rid of it.
+
+        // Remove this lease from LeaseMgr
+        LeaseMgrFactory::instance().deleteLease(lease->addr_);
+
+        // Add it to the removed leases list.
+        ctx.old_leases_.push_back(lease);
+
+        return;
+    }
+
+    // Keep the old data in case the callout tells us to skip update.
+    Lease6 old_data = *lease;
 
+    lease->preferred_lft_ = ctx.subnet_->getPreferred();
+    lease->valid_lft_ = ctx.subnet_->getValid();
+    lease->t1_ = ctx.subnet_->getT1();
+    lease->t2_ = ctx.subnet_->getT2();
+    lease->cltt_ = time(NULL);
+    lease->hostname_ = ctx.hostname_;
     lease->fqdn_fwd_ = ctx.fwd_dns_update_;
     lease->fqdn_rev_ = ctx.rev_dns_update_;
-    lease->hostname_ = ctx.hostname_;
+    lease->hwaddr_ = ctx.hwaddr_;
 
-    // Let's execute all callouts registered for lease6_select
-    if (ctx.callout_handle_ &&
-        HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
+    bool skip = false;
+    // Get the callouts specific for the processed message and execute them.
+    int hook_point = ctx.query_->getType() == DHCPV6_RENEW ?
+        Hooks.hook_index_lease6_renew_ : Hooks.hook_index_lease6_rebind_;
+    if (HooksManager::calloutsPresent(hook_point)) {
+        CalloutHandlePtr callout_handle = ctx.callout_handle_;
 
         // Delete all previous arguments
-        ctx.callout_handle_->deleteAllArguments();
-
-        // Pass necessary arguments
-
-        // Subnet from which we do the allocation
-        ctx.callout_handle_->setArgument("subnet6", ctx.subnet_);
+        callout_handle->deleteAllArguments();
 
-        // Is this solicit (fake = true) or request (fake = false)
-        ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
-        ctx.callout_handle_->setArgument("lease6", lease);
+        // Pass the original packet
+        callout_handle->setArgument("query6", ctx.query_);
 
-        // This is the first callout, so no need to clear any arguments
-        HooksManager::callCallouts(hook_index_lease6_select_, *ctx.callout_handle_);
+        // Pass the lease to be updated
+        callout_handle->setArgument("lease6", lease);
 
-        // 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
-        // won't be inserted into the database.
-        if (ctx.callout_handle_->getSkip()) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
-            return (Lease6Ptr());
+        // Pass the IA option to be sent in response
+        if (lease->type_ == Lease::TYPE_NA) {
+            callout_handle->setArgument("ia_na", ctx.ia_rsp_);
+        } else {
+            callout_handle->setArgument("ia_pd", ctx.ia_rsp_);
         }
 
-        // Let's use whatever callout returned. Hopefully it is the same lease
-        // we handled to it.
-        ctx.callout_handle_->getArgument("lease6", lease);
-    }
+        // Call all installed callouts
+        HooksManager::callCallouts(hook_point, *callout_handle);
 
-    if (!ctx.fake_allocation_) {
-        // That is a real (REQUEST) allocation
-        bool status = LeaseMgrFactory::instance().addLease(lease);
+        // Callouts decided to skip the next processing step. The next
+        // processing step would to actually renew the lease, so skip at this
+        // stage means "keep the old lease as it is".
+        if (callout_handle->getSkip()) {
+            skip = true;
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                      DHCPSRV_HOOK_LEASE6_EXTEND_SKIP)
+                .arg(ctx.query_->getName());
+        }
+    }
 
-        if (status) {
+    if (!skip) {
+        LeaseMgrFactory::instance().updateLease6(lease);
+    } else {
+        // Copy back the original date to the lease. For MySQL it doesn't make
+        // much sense, but for memfile, the Lease6Ptr points to the actual lease
+        // in memfile, so the actual update is performed when we manipulate
+        // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
+        *lease = old_data;
+    }
+}
 
-            return (lease);
-        } else {
-            // One of many failures with LeaseMgr (e.g. lost connection to the
-            // database, database failed etc.). One notable case for that
-            // is that we are working in multi-process mode and we lost a race
-            // (some other process got that address first)
-            return (Lease6Ptr());
+Lease6Collection
+AllocEngine::updateFqdnData(ClientContext6& ctx, const Lease6Collection& leases) {
+    Lease6Collection updated_leases;
+    for (Lease6Collection::const_iterator lease_it = leases.begin();
+         lease_it != leases.end(); ++lease_it) {
+        Lease6Ptr lease(new Lease6(**lease_it));
+        lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+        lease->fqdn_rev_ = ctx.rev_dns_update_;
+        lease->hostname_ = ctx.hostname_;
+        if (!ctx.fake_allocation_ &&
+            ((lease->fqdn_fwd_ != (*lease_it)->fqdn_fwd_) ||
+             (lease->fqdn_rev_ != (*lease_it)->fqdn_rev_) ||
+             (lease->hostname_ != (*lease_it)->hostname_))) {
+            ctx.changed_leases_.push_back(*lease_it);
+            LeaseMgrFactory::instance().updateLease6(lease);
         }
-    } else {
-        // That is only fake (SOLICIT without rapid-commit) allocation
+        updated_leases.push_back(lease);
+    }
+    return (updated_leases);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+// ##########################################################################
+// #    DHCPv4 lease allocation code starts here.
+// ##########################################################################
+
+namespace {
+
+bool
+addressReserved(const IOAddress& address, const AllocEngine::ClientContext4& ctx) {
+    ConstHostPtr host = HostMgr::instance().get4(ctx.subnet_->getID(), address);
+    HWAddrPtr host_hwaddr;
+    if (host) {
+        host_hwaddr = host->getHWAddress();
+        if (ctx.hwaddr_ && host_hwaddr) {
+            /// @todo Use the equality operators for HWAddr class.
+            /// Currently, this is impossible because the HostMgr uses the
+            /// HTYPE_ETHER type, whereas the unit tests may use other types
+            /// which HostMgr doesn't support yet.
+            return (host_hwaddr->hwaddr_ != ctx.hwaddr_->hwaddr_);
 
-        // It is for advertise only. We should not insert the lease into LeaseMgr,
-        // but rather check that we could have inserted it.
-        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(
-                             Lease::TYPE_NA, addr);
-        if (!existing) {
-            return (lease);
         } else {
-            return (Lease6Ptr());
+            return (true);
+
+        }
+    }
+    return (false);
+}
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+Lease4Ptr
+AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
+                            const HWAddrPtr& hwaddr, const IOAddress& hint,
+                            const bool fwd_dns_update, const bool rev_dns_update,
+                            const std::string& hostname, bool fake_allocation,
+                            const isc::hooks::CalloutHandlePtr& callout_handle,
+                            Lease4Ptr& old_lease) {
+
+    // The NULL pointer indicates that the old lease didn't exist. It may
+    // be later set to non NULL value if existing lease is found in the
+    // database.
+    old_lease.reset();
+
+    Lease4Ptr new_lease;
+
+    /// @todo The context for lease allocation should really be created
+    /// by the DHCPv4 server and passed to this function. The reason for
+    /// this is that the server should retrieve the Host object for the
+    /// client because the Host object contains the data not only useful
+    /// for the address allocation but also hostname and DHCP options
+    /// for the client. The Host object should be passed in the context.
+    /// Making this change would require a change to the allocateLease4
+    /// API which would in turn require lots of changes in unit tests.
+    /// The ticket introducing a context and host reservation in the
+    /// allocation engine is complex enough by itself to warrant that
+    /// the API change is done with a separate ticket (#3709).
+    ClientContext4 ctx;
+    ctx.subnet_ = subnet;
+    ctx.clientid_ = clientid;
+    ctx.hwaddr_ = hwaddr;
+    ctx.requested_address_ = 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;
+
+    try {
+        if (!subnet) {
+            isc_throw(BadValue, "Can't allocate IPv4 address without subnet");
+        }
+
+        if (!hwaddr) {
+            isc_throw(BadValue, "HWAddr must be defined");
+        }
+
+        ctx.host_ = HostMgr::instance().get4(subnet->getID(), hwaddr);
+
+        new_lease = ctx.fake_allocation_ ? discoverLease4(ctx) : requestLease4(ctx);
+        if (!new_lease) {
+            // Unable to allocate an address, return an empty lease.
+            LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_FAIL).arg(attempts_);
+        }
+
+    } catch (const isc::Exception& e) {
+        // Some other error, return an empty lease.
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_ERROR).arg(e.what());
+    }
+
+    if (ctx.old_lease_) {
+        old_lease.reset(new Lease4(*ctx.old_lease_));
+    }
+
+    return (new_lease);
+}
+
+Lease4Ptr
+AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+
+    Lease4Ptr client_lease = lease_mgr.getLease4(*ctx.hwaddr_, ctx.subnet_->getID());
+    if (!client_lease && ctx.clientid_) {
+        client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
+    }
+
+    Lease4Ptr new_lease;
+    if (ctx.host_) {
+        new_lease = allocateOrReuseLease(ctx.host_->getIPv4Reservation(), ctx);
+        if (new_lease) {
+            if (client_lease) {
+                ctx.old_lease_.reset(new Lease4(*client_lease));
+            }
+            return (new_lease);
+        }
+    }
+
+    if (ctx.requested_address_.isV4Zero() && !addressReserved(ctx.requested_address_, ctx)) {
+        if (ctx.subnet_->inPool(Lease::TYPE_V4, ctx.requested_address_)) {
+            new_lease = allocateOrReuseLease(ctx.requested_address_, ctx);
+            if (new_lease) {
+                if (client_lease) {
+                    ctx.old_lease_.reset(new Lease4(*client_lease));
+                }
+                return (new_lease);
+            }
+        }
+
+        if (client_lease) {
+            if ((client_lease->addr_ == ctx.requested_address_) ||
+                ctx.requested_address_.isV4Zero()) {
+                return (renewLease4(client_lease, ctx));
+            }
+        }
+    }
+
+    AllocatorPtr allocator = getAllocator(Lease::TYPE_V4);
+    const uint64_t max_attempts = ctx.subnet_->getPoolCapacity(Lease::TYPE_V4);
+    for (uint64_t i = 0; i < max_attempts; ++i) {
+        IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.clientid_,
+                                                     ctx.requested_address_);
+        if (!addressReserved(candidate, ctx)) {
+            new_lease = allocateOrReuseLease(candidate, ctx);
+            if (new_lease) {
+                return (new_lease);
+            }
         }
     }
+
+    return (Lease4Ptr());
+}
+
+Lease4Ptr
+AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+    Lease4Ptr client_lease = lease_mgr.getLease4(*ctx.hwaddr_, ctx.subnet_->getID());
+    if (!client_lease && ctx.clientid_) {
+        client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
+    }
+
+    if (!ctx.requested_address_.isV4Zero()) {
+        if (addressReserved(ctx.requested_address_, ctx)) {
+            return (Lease4Ptr());
+        }
+
+    } else if (ctx.host_) {
+        ctx.requested_address_ = ctx.host_->getIPv4Reservation();
+    }
+
+    if (!ctx.requested_address_.isV4Zero()) {
+        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(ctx.requested_address_);
+        if (existing && !existing->expired()) {
+            if (!ctx.myLease(*existing)) {
+                return (Lease4Ptr());
+            }
+
+        } else if (ctx.host_ && (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) {
+            return (Lease4Ptr());
+        }
+    }
+
+    if (client_lease) {
+        if ((client_lease->addr_ == ctx.requested_address_) ||
+            ctx.requested_address_.isV4Zero()) {
+            return (renewLease4(client_lease, ctx));
+        }
+    }
+
+    if (!ctx.subnet_->inPool(Lease4::TYPE_V4, ctx.requested_address_)) {
+        if ((ctx.host_ && (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) ||
+            (!ctx.host_ && !ctx.requested_address_.isV4Zero())) {
+            return (Lease4Ptr());
+        }
+    }
+
+    Lease4Ptr new_lease;
+    if (!ctx.requested_address_.isV4Zero()) {
+        new_lease = allocateOrReuseLease(ctx.requested_address_, ctx);
+        if (new_lease) {
+            if (client_lease && (client_lease->addr_ != new_lease->addr_)) {
+                ctx.old_lease_ = client_lease;
+                lease_mgr.deleteLease(client_lease->addr_);
+            }
+            return (new_lease);
+        }
+    }
+
+    AllocatorPtr allocator = getAllocator(Lease::TYPE_V4);
+    const uint64_t max_attempts = ctx.subnet_->getPoolCapacity(Lease::TYPE_V4);
+    for (uint64_t i = 0; i < max_attempts; ++i) {
+        IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.clientid_,
+                                                     ctx.requested_address_);
+        if (!addressReserved(candidate, ctx)) {
+            new_lease = allocateOrReuseLease(candidate, ctx);
+            if (new_lease) {
+                return (new_lease);
+            }
+        }
+    }
+
+    return (Lease4Ptr());
 }
 
 Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
@@ -1418,51 +1445,222 @@ Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
         callout_handle->setArgument("subnet4", subnet4);
 
         // Is this solicit (fake = true) or request (fake = false)
-        callout_handle->setArgument("fake_allocation", fake_allocation);
+        callout_handle->setArgument("fake_allocation", fake_allocation);
+
+        // Pass the intended lease as well
+        callout_handle->setArgument("lease4", lease);
+
+        // This is the first callout, so no need to clear any arguments
+        HooksManager::callCallouts(hook_index_lease4_select_, *callout_handle);
+
+        // 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
+        // won't be inserted into the database.
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+            return (Lease4Ptr());
+        }
+
+        // Let's use whatever callout returned. Hopefully it is the same lease
+        // we handled to it.
+        callout_handle->getArgument("lease4", lease);
+    }
+
+    if (!fake_allocation) {
+        // That is a real (REQUEST) allocation
+        bool status = LeaseMgrFactory::instance().addLease(lease);
+        if (status) {
+            return (lease);
+        } else {
+            // One of many failures with LeaseMgr (e.g. lost connection to the
+            // database, database failed etc.). One notable case for that
+            // is that we are working in multi-process mode and we lost a race
+            // (some other process got that address first)
+            return (Lease4Ptr());
+        }
+    } else {
+        // That is only fake (DISCOVER) allocation
+
+        // It is for OFFER only. We should not insert the lease into LeaseMgr,
+        // but rather check that we could have inserted it.
+        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr);
+        if (!existing) {
+            return (lease);
+        } else {
+            return (Lease4Ptr());
+        }
+    }
+}
+
+Lease4Ptr
+AllocEngine::renewLease4(const Lease4Ptr& lease,
+                         AllocEngine::ClientContext4& ctx) {
+    if (!lease) {
+        isc_throw(BadValue, "null lease specified for renewLease4");
+    }
+
+    // The ctx.host_ possibly contains a reservation for the client for which
+    // we are renewing a lease. If this reservation exists, we assume that
+    // there is no conflict in assigning the address to this client. Note
+    // that the reallocateClientLease checks if the address reserved for
+    // the client matches the address in the lease we're renewing here.
+    if (!ctx.host_) {
+        // Do not renew the lease if:
+        // - If address is reserved for someone else or ...
+        // - renewed address doesn't belong to a pool.
+        if (addressReserved(lease->addr_, ctx) ||
+            (!ctx.subnet_->inPool(Lease::TYPE_V4, lease->addr_))) {
+            ctx.interrupt_processing_ = !ctx.fake_allocation_;
+            return (Lease4Ptr());
+        }
+
+    }
+
+    // Let's keep the old data. This is essential if we are using memfile
+    // (the lease returned points directly to the lease4 object in the database)
+    // We'll need it if we want to skip update (i.e. roll back renewal)
+    /// @todo: remove this once #3083 is implemented
+    Lease4 old_values = *lease;
+    ctx.old_lease_.reset(new Lease4(old_values));
+
+    // Update the lease with the information from the context.
+    updateLease4Information(lease, ctx);
+
+    bool skip = false;
+    // Execute all callouts registered for lease4_renew.
+    if (HooksManager::getHooksManager().
+        calloutsPresent(Hooks.hook_index_lease4_renew_)) {
+
+        // Delete all previous arguments
+        ctx.callout_handle_->deleteAllArguments();
+
+        // Subnet from which we do the allocation. Convert the general subnet
+        // 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
+        // version of dynamic_pointer_cast.
+        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
+
+        // Pass the parameters
+        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
+        ctx.callout_handle_->setArgument("lease4", lease);
+
+        // Call all installed callouts
+        HooksManager::callCallouts(Hooks.hook_index_lease4_renew_,
+                                   *ctx.callout_handle_);
+
+        // Callouts decided to skip the next processing step. The next
+        // processing step would to actually renew the lease, so skip at this
+        // stage means "keep the old lease as it is".
+        if (ctx.callout_handle_->getSkip()) {
+            skip = true;
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                      DHCPSRV_HOOK_LEASE4_RENEW_SKIP);
+        }
+    }
+
+    if (!ctx.fake_allocation_ && !skip) {
+        // for REQUEST we do update the lease
+        LeaseMgrFactory::instance().updateLease4(lease);
+    }
+    if (skip) {
+        // Rollback changes (really useful only for memfile)
+        /// @todo: remove this once #3083 is implemented
+        *lease = old_values;
+    }
+
+    return (lease);
+}
+
+Lease4Ptr
+AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
+                               AllocEngine::ClientContext4& ctx) {
+    if (!expired) {
+        isc_throw(BadValue, "null lease specified for reuseExpiredLease");
+    }
+
+    if (!ctx.subnet_) {
+        isc_throw(BadValue, "null subnet specified for the reuseExpiredLease");
+    }
+
+    updateLease4Information(expired, ctx);
+    expired->fixed_ = false;
+
+    /// @todo: log here that the lease was reused (there's ticket #2524 for
+    /// logging in libdhcpsrv)
+
+    // Let's execute all 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 necessary arguments
+
+        // Subnet from which we do the allocation. Convert the general subnet
+        // 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
+        // version of dynamic_pointer_cast.
+        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
+        ctx.callout_handle_->setArgument("subnet4", subnet4);
+
+        // Is this solicit (fake = true) or request (fake = false)
+        ctx.callout_handle_->setArgument("fake_allocation",
+                                         ctx.fake_allocation_);
 
-        // Pass the intended lease as well
-        callout_handle->setArgument("lease4", lease);
+        // The lease that will be assigned to a client
+        ctx.callout_handle_->setArgument("lease4", expired);
 
-        // This is the first callout, so no need to clear any arguments
-        HooksManager::callCallouts(hook_index_lease4_select_, *callout_handle);
+        // Call the callouts
+        HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_);
 
         // 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
         // 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());
         }
 
         // Let's use whatever callout returned. Hopefully it is the same lease
         // we handled to it.
-        callout_handle->getArgument("lease4", lease);
+        ctx.callout_handle_->getArgument("lease4", expired);
     }
 
-    if (!fake_allocation) {
-        // That is a real (REQUEST) allocation
-        bool status = LeaseMgrFactory::instance().addLease(lease);
-        if (status) {
-            return (lease);
-        } else {
-            // One of many failures with LeaseMgr (e.g. lost connection to the
-            // database, database failed etc.). One notable case for that
-            // is that we are working in multi-process mode and we lost a race
-            // (some other process got that address first)
-            return (Lease4Ptr());
-        }
-    } else {
-        // That is only fake (DISCOVER) allocation
+    if (!ctx.fake_allocation_) {
+        // for REQUEST we do update the lease
+        LeaseMgrFactory::instance().updateLease4(expired);
+    }
 
-        // It is for OFFER only. We should not insert the lease into LeaseMgr,
-        // but rather check that we could have inserted it.
-        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr);
-        if (!existing) {
-            return (lease);
-        } else {
-            return (Lease4Ptr());
+    // We do nothing for SOLICIT. We'll just update database when
+    // the client gets back to us with REQUEST message.
+
+    // it's not really expired at this stage anymore - let's return it as
+    // an updated lease
+    return (expired);
+}
+
+Lease4Ptr
+AllocEngine::allocateOrReuseLease(const IOAddress& candidate, ClientContext4& ctx) {
+    Lease4Ptr exist_lease = LeaseMgrFactory::instance().getLease4(candidate);
+    if (exist_lease) {
+        if (exist_lease->expired()) {
+            ctx.old_lease_.reset(new Lease4(*exist_lease));
+            return (reuseExpiredLease(exist_lease, ctx));
         }
+
+    } else {
+        return (createLease4(ctx.subnet_, ctx.clientid_,
+                             ctx.hwaddr_, candidate, ctx.fwd_dns_update_,
+                             ctx.rev_dns_update_, ctx.hostname_, ctx.callout_handle_,
+                             ctx.fake_allocation_));
     }
+    return (Lease4Ptr());
 }
 
 void
@@ -1490,192 +1688,5 @@ AllocEngine::updateLease4Information(const Lease4Ptr& lease,
     lease->hostname_ = ctx.hostname_;
 }
 
-Lease6Collection
-AllocEngine::updateFqdnData(ClientContext6& ctx, const Lease6Collection& leases) {
-    Lease6Collection updated_leases;
-    for (Lease6Collection::const_iterator lease_it = leases.begin();
-         lease_it != leases.end(); ++lease_it) {
-        Lease6Ptr lease(new Lease6(**lease_it));
-        lease->fqdn_fwd_ = ctx.fwd_dns_update_;
-        lease->fqdn_rev_ = ctx.rev_dns_update_;
-        lease->hostname_ = ctx.hostname_;
-        if (!ctx.fake_allocation_ &&
-            ((lease->fqdn_fwd_ != (*lease_it)->fqdn_fwd_) ||
-             (lease->fqdn_rev_ != (*lease_it)->fqdn_rev_) ||
-             (lease->hostname_ != (*lease_it)->hostname_))) {
-            ctx.changed_leases_.push_back(*lease_it);
-            LeaseMgrFactory::instance().updateLease6(lease);
-        }
-        updated_leases.push_back(lease);
-    }
-    return (updated_leases);
-}
-
-AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
-    std::map<Lease::Type, AllocatorPtr>::const_iterator alloc = allocators_.find(type);
-
-    if (alloc == allocators_.end()) {
-        isc_throw(BadValue, "No allocator initialized for pool type "
-                  << Lease::typeToText(type));
-    }
-    return (alloc->second);
-}
-
-Lease6Collection
-AllocEngine::renewLeases6(ClientContext6& ctx) {
-    try {
-        if (!ctx.subnet_) {
-            isc_throw(InvalidOperation, "Subnet is required for allocation");
-        }
-
-        if (!ctx.duid_) {
-            isc_throw(InvalidOperation, "DUID is mandatory for allocation");
-        }
-
-        // Check which host reservation mode is supported in this subnet.
-        Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
-
-        // Check if there's a host reservation for this client. Attempt to get
-        // host info only if reservations are not disabled.
-        if (hr_mode != Subnet::HR_DISABLED) {
-
-            ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), ctx.duid_,
-                                                 ctx.hwaddr_);
-        } else {
-            // Host reservations disabled? Then explicitly set host to NULL
-            ctx.host_.reset();
-        }
-
-        // Check if there are any leases for this client.
-        Lease6Collection leases = LeaseMgrFactory::instance()
-            .getLeases6(ctx.type_, *ctx.duid_, ctx.iaid_, ctx.subnet_->getID());
-
-        if (!leases.empty()) {
-            // Check if the existing leases are reserved for someone else.
-            // If they're not, we're ok to keep using them.
-            removeNonmatchingReservedLeases6(ctx, leases);
-        }
-
-        if (ctx.host_) {
-            // If we have host reservation, allocate those leases.
-            allocateReservedLeases6(ctx, leases);
-
-            // There's one more check to do. Let's remove leases that are not
-            // matching reservations, i.e. if client X has address A, but there's
-            // a reservation for address B, we should release A and reassign B.
-            // Caveat: do this only if we have at least one reserved address.
-            removeNonreservedLeases6(ctx, leases);
-        }
-
-        // If we happen to removed all leases, get something new for this guy.
-        // Depending on the configuration, we may enable or disable granting
-        // new leases during renewals. This is controlled with the
-        // allow_new_leases_in_renewals_ field.
-        if (leases.empty() && ctx.allow_new_leases_in_renewals_) {
-            leases = allocateUnreservedLeases6(ctx);
-        }
-
-        // Extend all existing leases that passed all checks.
-        for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
-            extendLease6(ctx, *l);
-        }
-
-        return (leases);
-
-    } catch (const isc::Exception& e) {
-
-        // Some other error, return an empty lease.
-        LOG_ERROR(dhcpsrv_logger, DHCPSRV_RENEW6_ERROR).arg(e.what());
-    }
-
-    return (Lease6Collection());
-}
-
-void
-AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
-
-    if (!lease || !ctx.subnet_) {
-        return;
-    }
-
-    // Check if the lease still belongs to the subnet. If it doesn't,
-    // we'll need to remove it.
-    if ((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) {
-        // Oh dear, the lease is no longer valid. We need to get rid of it.
-
-        // Remove this lease from LeaseMgr
-        LeaseMgrFactory::instance().deleteLease(lease->addr_);
-
-        // Add it to the removed leases list.
-        ctx.old_leases_.push_back(lease);
-
-        return;
-    }
-
-    // Keep the old data in case the callout tells us to skip update.
-    Lease6 old_data = *lease;
-
-    lease->preferred_lft_ = ctx.subnet_->getPreferred();
-    lease->valid_lft_ = ctx.subnet_->getValid();
-    lease->t1_ = ctx.subnet_->getT1();
-    lease->t2_ = ctx.subnet_->getT2();
-    lease->cltt_ = time(NULL);
-    lease->hostname_ = ctx.hostname_;
-    lease->fqdn_fwd_ = ctx.fwd_dns_update_;
-    lease->fqdn_rev_ = ctx.rev_dns_update_;
-    lease->hwaddr_ = ctx.hwaddr_;
-
-    bool skip = false;
-    // Get the callouts specific for the processed message and execute them.
-    int hook_point = ctx.query_->getType() == DHCPV6_RENEW ?
-        Hooks.hook_index_lease6_renew_ : Hooks.hook_index_lease6_rebind_;
-    if (HooksManager::calloutsPresent(hook_point)) {
-        CalloutHandlePtr callout_handle = ctx.callout_handle_;
-
-        // Delete all previous arguments
-        callout_handle->deleteAllArguments();
-
-        // Pass the original packet
-        callout_handle->setArgument("query6", ctx.query_);
-
-        // Pass the lease to be updated
-        callout_handle->setArgument("lease6", lease);
-
-        // Pass the IA option to be sent in response
-        if (lease->type_ == Lease::TYPE_NA) {
-            callout_handle->setArgument("ia_na", ctx.ia_rsp_);
-        } else {
-            callout_handle->setArgument("ia_pd", ctx.ia_rsp_);
-        }
-
-        // Call all installed callouts
-        HooksManager::callCallouts(hook_point, *callout_handle);
-
-        // Callouts decided to skip the next processing step. The next
-        // processing step would to actually renew the lease, so skip at this
-        // stage means "keep the old lease as it is".
-        if (callout_handle->getSkip()) {
-            skip = true;
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
-                      DHCPSRV_HOOK_LEASE6_EXTEND_SKIP)
-                .arg(ctx.query_->getName());
-        }
-    }
-
-    if (!skip) {
-        LeaseMgrFactory::instance().updateLease6(lease);
-    } else {
-        // Copy back the original date to the lease. For MySQL it doesn't make
-        // much sense, but for memfile, the Lease6Ptr points to the actual lease
-        // in memfile, so the actual update is performed when we manipulate
-        // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
-        *lease = old_data;
-    }
-}
-
-AllocEngine::~AllocEngine() {
-    // no need to delete allocator. smart_ptr will do the trick for us
-}
-
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
index fafa31e2731441aaea24cb029f3faec1318d7811..b63a5faa08de248e2bed5dae7e8f8c3acd466f0c 100644 (file)
@@ -58,6 +58,10 @@ public:
 class AllocEngine : public boost::noncopyable {
 protected:
 
+    /// @name Declaration of the allocator classes.
+    ///
+    //@{
+
     /// @brief base class for all address/prefix allocation algorithms
     ///
     /// This is an abstract class that should not be used directly, but rather
@@ -207,92 +211,63 @@ protected:
         ALLOC_RANDOM     // random - an address is randomly selected
     } AllocType;
 
-    /// @brief Context information for the DHCPv4 lease allocation.
+    //@}
+
+    /// @name Construction, destruction and allocator.
     ///
-    /// 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.
+    //@{
+
+    /// @brief Constructor.
     ///
-    /// 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.
+    /// Instantiates necessary services, required to run DHCP server.
+    /// In particular, creates IfaceMgr that will be responsible for
+    /// network interaction. Will instantiate lease manager, and load
+    /// old or create new DUID.
     ///
-    /// 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 ClientContext4 {
-        /// @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 requested_address_;
-
-        /// @brief Perform forward DNS update.
-        bool fwd_dns_update_;
+    /// @param engine_type selects allocation algorithm
+    /// @param attempts number of attempts for each lease allocation before
+    ///        we give up (0 means unlimited)
+    /// @param ipv6 specifies if the engine should work for IPv4 or IPv6
+    AllocEngine(AllocType engine_type, unsigned int attempts, bool ipv6 = true);
 
-        /// @brief Perform reverse DNS update.
-        bool rev_dns_update_;
+    /// @brief Destructor.
+    virtual ~AllocEngine() { }
 
-        /// @brief Hostname.
-        ///
-        /// The server retrieves the hostname from the Client FQDN option,
-        /// Hostname option or the host reservation record for the client.
-        std::string hostname_;
+    /// @brief Returns allocator for a given pool type
+    ///
+    /// @param type type of pool (V4, IA, TA or PD)
+    /// @throw BadValue if allocator for a given type is missing
+    /// @return pointer to allocator handing a given resource types
+    AllocatorPtr getAllocator(Lease::Type type);
 
-        /// @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_;
+private:
 
-        /// @brief A pointer to an old lease that the client had before update.
-        Lease4Ptr old_lease_;
+    /// @name Private members, common for DHCPv4 and DHCPv6 service.
+    ///
+    //@{
 
-        /// @brief A pointer to the object identifying host reservations.
-        ConstHostPtr host_;
+    /// @brief a pointer to currently used allocator
+    ///
+    /// For IPv4, there will be only one allocator: TYPE_V4
+    /// For IPv6, there will be 3 allocators: TYPE_NA, TYPE_TA, TYPE_PD
+    std::map<Lease::Type, AllocatorPtr> allocators_;
 
-        /// @brief A pointer to the existing lease for the host reservation.
-        Lease4Ptr lease_for_host_;
+    /// @brief number of attempts before we give up lease allocation (0=unlimited)
+    unsigned int attempts_;
 
-        /// @brief Signals that the allocation should be interrupted.
-        ///
-        /// This flag is set by the downstream methods called by the
-        /// @c AllocEngine::allocateLease4. This flag is set to true to
-        /// indicate that an attempt to allocate a lease should be
-        /// interrupted.
-        ///
-        /// One possible use case is when the allocation engine tries
-        /// to renew the client's lease and the leased address appears
-        /// to be reserved for someone else. In such case, the allocation
-        /// engine should signal to the server that the address that the
-        /// client should stop using this address. The
-        /// @c AllocEngine::renewLease4 sets this flag so as the
-        /// upstream methods return the NULL lease pointer to the server.
-        bool interrupt_processing_;
+    // hook name indexes (used in hooks callouts)
+    int hook_index_lease4_select_; ///< index for lease4_select hook
+    int hook_index_lease6_select_; ///< index for lease6_select hook
 
-        /// @brief Default constructor.
-        ClientContext4();
+    //@}
 
-        bool myLease(const Lease4& lease) const;
+public:
 
-    };
+    /// @name Public method, structure and types for DHCPv6 leass allocation.
+    ///
+    //@{
 
     /// @brief Defines a single hint (an address + prefix-length).
     ///
@@ -464,141 +439,6 @@ protected:
         }
     };
 
-    /// @brief Default constructor.
-    ///
-    /// Instantiates necessary services, required to run DHCPv6 server.
-    /// In particular, creates IfaceMgr that will be responsible for
-    /// network interaction. Will instantiate lease manager, and load
-    /// old or create new DUID.
-    ///
-    /// @param engine_type selects allocation algorithm
-    /// @param attempts number of attempts for each lease allocation before
-    ///        we give up (0 means unlimited)
-    /// @param ipv6 specifies if the engine should work for IPv4 or IPv6
-    AllocEngine(AllocType engine_type, unsigned int attempts, bool ipv6 = true);
-
-    /// @brief Returns IPv4 lease.
-    ///
-    /// This method finds a lease for a client using the following algorithm:
-    /// - If a lease exists for the combination of the HW address or client id
-    ///   and a subnet, try to use this lease for the client. If the client
-    ///   has a reservation for an address for which the lease was created or
-    ///   the client desires to renew the lease for this address (ciaddr or
-    ///   requested IP address option), the server renews the lease for the
-    ///   client. If the client desires a different address or the server has
-    ///   a (potentially new) reservation for a different address for this
-    ///   client, the existing lease is replaced with a new lease.
-    /// - If the client has no lease in the lease database the server will try
-    ///   to allocate a new lease. If the client has a reservation for the
-    ///   particular address or if it has specified a desired address the
-    ///   server will check if the particular address is not allocated to
-    ///   other client. If the address is available, the server will allocate
-    ///   this address for the client.
-    /// - If the desired address is unavailable the server checks if the
-    ///   lease for this address has expired. If the lease is expired, the
-    ///   server will allocate this lease to the client. The relevant
-    ///   information will be updated, e.g. new client HW address, host name
-    ///   etc.
-    /// - If the desired address is in use by other client, the server will try
-    ///   to allocate a different address. The server picks addresses from
-    ///   a dynamic pool and checks if the address is available and that
-    ///   it is not reserved for another client. If it is in use by another
-    ///   client or if it is reserved for another client, this address is not
-    ///   allocated. The server picks next address and repeats this check.
-    ///   Note that the server ceases allocation after configured number
-    ///   of unsuccessful attempts.
-    ///
-    /// The lease allocation process is slightly different for the
-    /// DHCPDISCOVER and DHCPREQUEST messages. In the former case, the client
-    /// may specify the requested IP address option with a desired address and
-    /// the server treats this address as hint. This means that the server may
-    /// allocate a different address on its discretion and send it to the
-    /// client in the DHCPOFFER. If the client accepts this offer it specifies
-    /// this address in the requested IP address option in the DHCPREQUEST.
-    /// At this point, the allocation engine will use the request IP address
-    /// as a hard requirement and if this address can't be allocated for
-    /// any reason, the allocation engine returns NULL lease. As a result,
-    /// the DHCP server sends a DHCPNAK to the client and the client
-    /// falls back to the DHCP server discovery.
-    ///
-    /// The only exception from this rule is when the client doesn't specify
-    /// a requested IP address option (invalid behavior) in which case the
-    /// allocation engine will try to allocate any address.
-    ///
-    /// If there is an address reservation specified for the particular client
-    /// the reserved address always takes precedence over addresses from the
-    /// dynamic pool or even an address currently allocated for this client.
-    ///
-    /// It is possible that the address reserved for the particular client
-    /// is in use by other client, e.g. as a result of pools reconfigruation.
-    /// In this case, when the client requests allocation of the reserved
-    /// address and the server determines that it is leased to someone else,
-    /// the allocation engine doesn't allocate a lease for the client having
-    /// a reservation. When the client having a lease returns to renew, the
-    /// allocation engine doesn't extend the lease for it and returns a NULL
-    /// pointer. The client falls back to the 4-way exchange and a different
-    /// lease is allocated. At this point, the reserved address is freed and
-    /// can be allocated to the client which holds this reservation.
-    ///
-    /// When a server should do DNS updates, it is required that allocation
-    /// returns the information how the lease was obtained by the allocation
-    /// engine. In particular, the DHCP server should be able to check whether
-    /// existing lease was returned, or new lease was allocated. When existing
-    /// lease was returned, server should check whether the FQDN has changed
-    /// between the allocation of the old and new lease. If so, server should
-    /// perform appropriate DNS update. If not, server may choose to not
-    /// perform the update. The information about the old lease is returned via
-    /// @c old_lease parameter. If NULL value is returned, it is an indication
-    /// that new lease was allocated for the client. If non-NULL value is
-    /// returned, it is an indication that allocation engine reused/renewed an
-    /// existing lease.
-    ///
-    /// @todo Replace parameters with a single parameter of a
-    /// @c ClientContext4 type.
-    ///
-    /// @param subnet subnet the allocation should come from
-    /// @param clientid Client identifier
-    /// @param hwaddr Client's hardware address info
-    /// @param hint A hint that the client provided
-    /// @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 fake_allocation Is this real i.e. REQUEST (false) or just picking
-    ///        an address for DISCOVER that is not really allocated (true)
-    /// @param callout_handle A callout handle (used in hooks). A lease callouts
-    ///        will be executed if this parameter is passed.
-    /// @param [out] old_lease Holds the pointer to a previous instance of a
-    ///        lease. The NULL pointer indicates that lease didn't exist prior
-    ///        to calling this function (e.g. new lease has been allocated).
-    ///
-    /// @return Allocated IPv4 lease (or NULL if allocation failed).
-    Lease4Ptr
-    allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
-                   const HWAddrPtr& hwaddr,
-                   const isc::asiolink::IOAddress& hint,
-                   const bool fwd_dns_update, const bool rev_dns_update,
-                   const std::string& hostname, bool fake_allocation,
-                   const isc::hooks::CalloutHandlePtr& callout_handle,
-                   Lease4Ptr& old_lease);
-
-    /// @brief Renews an DHCPv4 lease.
-    ///
-    /// This method updates the lease with the information from the provided
-    /// context and invokes the lease4_renew callout.
-    ///
-    /// The address of the lease being renewed is NOT updated.
-    ///
-    /// @param lease A lease to be renewed.
-    /// @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
-    renewLease4(const Lease4Ptr& lease, ClientContext4& ctx);
-
     /// @brief Allocates IPv6 leases for a given IA container
     ///
     /// This method uses currently selected allocator to pick allocatable
@@ -703,72 +543,18 @@ protected:
     Lease6Collection
     renewLeases6(ClientContext6& ctx);
 
+    //@}
 
-
-    /// @brief returns allocator for a given pool type
-    /// @param type type of pool (V4, IA, TA or PD)
-    /// @throw BadValue if allocator for a given type is missing
-    /// @return pointer to allocator handing a given resource types
-    AllocatorPtr getAllocator(Lease::Type type);
-
-    /// @brief Destructor. Used during DHCPv6 service shutdown.
-    virtual ~AllocEngine();
 private:
 
-    /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+    /// @name Private methods for DHCPv6 leases allocation
+    ///
+    //@{
+
+    /// @brief creates a lease and inserts it in LeaseMgr if necessary
     ///
     /// Creates a lease based on specified parameters and tries to insert it
-    /// into the database. That may fail in some cases, e.g. when there is another
-    /// allocation process and we lost a race to a specific lease.
-    ///
-    /// @param subnet Subnet the lease is allocated from
-    /// @param clientid Client identifier
-    /// @param hwaddr Client's hardware address
-    /// @param addr An address that was selected and is confirmed to be available
-    /// @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 (and there are callouts
-    ///        registered)
-    /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
-    ///        an address for DISCOVER that is not really allocated (true)
-    /// @return allocated lease (or NULL in the unlikely case of the lease just
-    ///        becomed unavailable)
-    Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
-                           const HWAddrPtr& hwaddr,
-                           const isc::asiolink::IOAddress& addr,
-                           const bool fwd_dns_update,
-                           const bool rev_dns_update,
-                           const std::string& hostname,
-                           const isc::hooks::CalloutHandlePtr& callout_handle,
-                           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,
-                                 ClientContext4& ctx) const;
-
-    /// @brief creates a lease and inserts it in LeaseMgr if necessary
-    ///
-    /// Creates a lease based on specified parameters and tries to insert it
-    /// into the database. That may fail in some cases, i.e. when there is another
+    /// into the database. That may fail in some cases, i.e. when there is another
     /// allocation process and we lost a race to a specific lease.
     ///
     /// @param ctx client context that passes all necessary information. See
@@ -854,65 +640,6 @@ private:
     removeNonreservedLeases6(ClientContext6& ctx,
                              Lease6Collection& existing_leases);
 
-    Lease4Ptr allocateOrReuseLease(const asiolink::IOAddress& address,
-                                   ClientContext4& ctx);
-
-    /// @brief Reuses expired DHCPv4 lease.
-    ///
-    /// 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 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, ClientContext4& 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, ClientContext4& 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, ClientContext4& ctx);
-
-    Lease4Ptr discoverLease4(ClientContext4& ctx);
-
-    Lease4Ptr requestLease4(ClientContext4& ctx);
-
     /// @brief Reuses expired IPv6 lease
     ///
     /// Updates existing expired lease with new information. Lease database
@@ -981,18 +708,301 @@ private:
     /// @param lease IPv6 lease to be extended.
     void extendLease6(ClientContext6& ctx, Lease6Ptr lease);
 
-    /// @brief a pointer to currently used allocator
+    //@}
+
+public:
+
+    /// @name Public method and structure for DHCPv4 lease allocation.
     ///
-    /// For IPv4, there will be only one allocator: TYPE_V4
-    /// For IPv6, there will be 3 allocators: TYPE_NA, TYPE_TA, TYPE_PD
-    std::map<Lease::Type, AllocatorPtr> allocators_;
+    //@{
 
-    /// @brief number of attempts before we give up lease allocation (0=unlimited)
-    unsigned int attempts_;
+    /// @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 ClientContext4 {
+        /// @brief Subnet selected for the client by the server.
+        SubnetPtr subnet_;
 
-    // hook name indexes (used in hooks callouts)
-    int hook_index_lease4_select_; ///< index for lease4_select hook
-    int hook_index_lease6_select_; ///< index for lease6_select hook
+        /// @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 requested_address_;
+
+        /// @brief Perform forward DNS update.
+        bool fwd_dns_update_;
+
+        /// @brief Perform reverse DNS update.
+        bool rev_dns_update_;
+
+        /// @brief Hostname.
+        ///
+        /// The server retrieves the hostname from the Client FQDN option,
+        /// Hostname option or the host reservation record for the client.
+        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 Signals that the allocation should be interrupted.
+        ///
+        /// This flag is set by the downstream methods called by the
+        /// @c AllocEngine::allocateLease4. This flag is set to true to
+        /// indicate that an attempt to allocate a lease should be
+        /// interrupted.
+        ///
+        /// One possible use case is when the allocation engine tries
+        /// to renew the client's lease and the leased address appears
+        /// to be reserved for someone else. In such case, the allocation
+        /// engine should signal to the server that the address that the
+        /// client should stop using this address. The
+        /// @c AllocEngine::renewLease4 sets this flag so as the
+        /// upstream methods return the NULL lease pointer to the server.
+        bool interrupt_processing_;
+
+        /// @brief Default constructor.
+        ClientContext4();
+
+        bool myLease(const Lease4& lease) const;
+
+    };
+
+    /// @brief Returns IPv4 lease.
+    ///
+    /// This method finds a lease for a client using the following algorithm:
+    /// - If a lease exists for the combination of the HW address or client id
+    ///   and a subnet, try to use this lease for the client. If the client
+    ///   has a reservation for an address for which the lease was created or
+    ///   the client desires to renew the lease for this address (ciaddr or
+    ///   requested IP address option), the server renews the lease for the
+    ///   client. If the client desires a different address or the server has
+    ///   a (potentially new) reservation for a different address for this
+    ///   client, the existing lease is replaced with a new lease.
+    /// - If the client has no lease in the lease database the server will try
+    ///   to allocate a new lease. If the client has a reservation for the
+    ///   particular address or if it has specified a desired address the
+    ///   server will check if the particular address is not allocated to
+    ///   other client. If the address is available, the server will allocate
+    ///   this address for the client.
+    /// - If the desired address is unavailable the server checks if the
+    ///   lease for this address has expired. If the lease is expired, the
+    ///   server will allocate this lease to the client. The relevant
+    ///   information will be updated, e.g. new client HW address, host name
+    ///   etc.
+    /// - If the desired address is in use by other client, the server will try
+    ///   to allocate a different address. The server picks addresses from
+    ///   a dynamic pool and checks if the address is available and that
+    ///   it is not reserved for another client. If it is in use by another
+    ///   client or if it is reserved for another client, this address is not
+    ///   allocated. The server picks next address and repeats this check.
+    ///   Note that the server ceases allocation after configured number
+    ///   of unsuccessful attempts.
+    ///
+    /// The lease allocation process is slightly different for the
+    /// DHCPDISCOVER and DHCPREQUEST messages. In the former case, the client
+    /// may specify the requested IP address option with a desired address and
+    /// the server treats this address as hint. This means that the server may
+    /// allocate a different address on its discretion and send it to the
+    /// client in the DHCPOFFER. If the client accepts this offer it specifies
+    /// this address in the requested IP address option in the DHCPREQUEST.
+    /// At this point, the allocation engine will use the request IP address
+    /// as a hard requirement and if this address can't be allocated for
+    /// any reason, the allocation engine returns NULL lease. As a result,
+    /// the DHCP server sends a DHCPNAK to the client and the client
+    /// falls back to the DHCP server discovery.
+    ///
+    /// The only exception from this rule is when the client doesn't specify
+    /// a requested IP address option (invalid behavior) in which case the
+    /// allocation engine will try to allocate any address.
+    ///
+    /// If there is an address reservation specified for the particular client
+    /// the reserved address always takes precedence over addresses from the
+    /// dynamic pool or even an address currently allocated for this client.
+    ///
+    /// It is possible that the address reserved for the particular client
+    /// is in use by other client, e.g. as a result of pools reconfigruation.
+    /// In this case, when the client requests allocation of the reserved
+    /// address and the server determines that it is leased to someone else,
+    /// the allocation engine doesn't allocate a lease for the client having
+    /// a reservation. When the client having a lease returns to renew, the
+    /// allocation engine doesn't extend the lease for it and returns a NULL
+    /// pointer. The client falls back to the 4-way exchange and a different
+    /// lease is allocated. At this point, the reserved address is freed and
+    /// can be allocated to the client which holds this reservation.
+    ///
+    /// When a server should do DNS updates, it is required that allocation
+    /// returns the information how the lease was obtained by the allocation
+    /// engine. In particular, the DHCP server should be able to check whether
+    /// existing lease was returned, or new lease was allocated. When existing
+    /// lease was returned, server should check whether the FQDN has changed
+    /// between the allocation of the old and new lease. If so, server should
+    /// perform appropriate DNS update. If not, server may choose to not
+    /// perform the update. The information about the old lease is returned via
+    /// @c old_lease parameter. If NULL value is returned, it is an indication
+    /// that new lease was allocated for the client. If non-NULL value is
+    /// returned, it is an indication that allocation engine reused/renewed an
+    /// existing lease.
+    ///
+    /// @todo Replace parameters with a single parameter of a
+    /// @c ClientContext4 type.
+    ///
+    /// @param subnet subnet the allocation should come from
+    /// @param clientid Client identifier
+    /// @param hwaddr Client's hardware address info
+    /// @param hint A hint that the client provided
+    /// @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 fake_allocation Is this real i.e. REQUEST (false) or just picking
+    ///        an address for DISCOVER that is not really allocated (true)
+    /// @param callout_handle A callout handle (used in hooks). A lease callouts
+    ///        will be executed if this parameter is passed.
+    /// @param [out] old_lease Holds the pointer to a previous instance of a
+    ///        lease. The NULL pointer indicates that lease didn't exist prior
+    ///        to calling this function (e.g. new lease has been allocated).
+    ///
+    /// @return Allocated IPv4 lease (or NULL if allocation failed).
+    Lease4Ptr allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
+                             const HWAddrPtr& hwaddr,
+                             const isc::asiolink::IOAddress& hint,
+                             const bool fwd_dns_update, const bool rev_dns_update,
+                             const std::string& hostname, bool fake_allocation,
+                             const isc::hooks::CalloutHandlePtr& callout_handle,
+                             Lease4Ptr& old_lease);
+
+    //@}
+
+private:
+
+    /// @name Private methods for DHCPv4 lease allocation
+    ///
+    //@{
+
+    Lease4Ptr discoverLease4(ClientContext4& ctx);
+
+    Lease4Ptr requestLease4(ClientContext4& ctx);
+
+    /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+    ///
+    /// Creates a lease based on specified parameters and tries to insert it
+    /// into the database. That may fail in some cases, e.g. when there is another
+    /// allocation process and we lost a race to a specific lease.
+    ///
+    /// @param subnet Subnet the lease is allocated from
+    /// @param clientid Client identifier
+    /// @param hwaddr Client's hardware address
+    /// @param addr An address that was selected and is confirmed to be available
+    /// @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 (and there are callouts
+    ///        registered)
+    /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
+    ///        an address for DISCOVER that is not really allocated (true)
+    /// @return allocated lease (or NULL in the unlikely case of the lease just
+    ///        becomed unavailable)
+    Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
+                                  const HWAddrPtr& hwaddr,
+                                  const isc::asiolink::IOAddress& addr,
+                                  const bool fwd_dns_update,
+                                  const bool rev_dns_update,
+                                  const std::string& hostname,
+                                  const isc::hooks::CalloutHandlePtr& callout_handle,
+                                  bool fake_allocation = false);
+
+    /// @brief Renews an DHCPv4 lease.
+    ///
+    /// This method updates the lease with the information from the provided
+    /// context and invokes the lease4_renew callout.
+    ///
+    /// The address of the lease being renewed is NOT updated.
+    ///
+    /// @param lease A lease to be renewed.
+    /// @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 renewLease4(const Lease4Ptr& lease, ClientContext4& ctx);
+
+    /// @brief Reuses expired DHCPv4 lease.
+    ///
+    /// 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 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, ClientContext4& ctx);
+
+    Lease4Ptr allocateOrReuseLease(const asiolink::IOAddress& address,
+                                   ClientContext4& ctx);
+
+
+    /// @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,
+                                 ClientContext4& ctx) const;
+
+    //@}
 };
 
 }; // namespace isc::dhcp
index e74d86cf6063b9858e34d1b1bbe94f67b8eb4b94..4ddf36597723c37d2e22637b4fa70f8e63d59ef3 100644 (file)
@@ -548,54 +548,6 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
 
 /// @todo write renewLease6
 
-// This test checks if a lease is really renewed when renewLease4 method is
-// called
-TEST_F(AllocEngine4Test, renewLease4) {
-    boost::scoped_ptr<AllocEngine> engine;
-
-    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
-    ASSERT_TRUE(engine);
-
-    IOAddress addr("192.0.2.102");
-    const uint32_t old_lifetime = 100;
-    const uint32_t old_t1 = 50;
-    const uint32_t old_t2 = 75;
-    const time_t old_timestamp = time(NULL) - 45; // Allocated 45 seconds ago
-
-    // Just a different hw/client-id for the second client
-    const uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
-    HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
-    const uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
-    Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2),
-                               old_lifetime, old_t1, old_t2,
-                               old_timestamp, subnet_->getID()));
-    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
-
-    // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
-    // renew it.
-    ASSERT_FALSE(lease->expired());
-    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
-    ASSERT_TRUE(lease);
-    EXPECT_EQ(addr, lease->addr_);
-
-    // Check that the lease matches subnet_, hwaddr_,clientid_ parameters
-    checkLease4(lease);
-
-    // Check that the lease is indeed updated in LeaseMgr
-    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
-    ASSERT_TRUE(from_mgr);
-
-    // Now check that the lease in LeaseMgr has the same parameters
-    detailCompareLease(lease, from_mgr);
-}
-
 TEST_F(AllocEngine4Test, requestOtherClientLease) {
     Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0,
                                100, 30, 60, time(NULL), subnet_->getID(),