Browse Source

[master] Merge branch 'trac3563' (Reservations in DHCPv6)

Conflicts:
	src/lib/dhcpsrv/alloc_engine.cc
Tomek Mrugalski 10 years ago
parent
commit
e2a3e5e74a

+ 22 - 28
src/bin/dhcp6/dhcp6_srv.cc

@@ -1294,24 +1294,21 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         hostname = fqdn->getDomainName();
     }
 
-    // Attempt to get MAC address using configured mechanisms.
-    // It's ok if there response is NULL. Hardware address is optional in Lease6.
-    HWAddrPtr hwaddr = getMAC(query);
-
     // Use allocation engine to pick a lease for this client. Allocation engine
     // will try to honour the hint, but it is just a hint - some other address
     // may be used instead. If fake_allocation is set to false, the lease will
     // be inserted into the LeaseMgr as well.
-    Lease6Collection old_leases;
-    Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid,
-                                                             ia->getIAID(),
-                                                             hint, Lease::TYPE_NA,
-                                                             do_fwd, do_rev,
-                                                             hostname,
-                                                             fake_allocation,
-                                                             callout_handle,
-                                                             old_leases,
-                                                             hwaddr);
+    AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(),
+                                    hint, Lease::TYPE_NA, do_fwd, do_rev,
+                                    hostname, fake_allocation);
+    ctx.callout_handle_ = callout_handle;
+
+    // Attempt to get MAC address using configured mechanisms.
+    // It's ok if there response is NULL. Hardware address is optional in Lease6.
+    ctx.hwaddr_ = getMAC(query);
+
+    Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
+
     /// @todo: Handle more than one lease
     Lease6Ptr lease;
     if (!leases.empty()) {
@@ -1347,8 +1344,8 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         // code is considered a success.
 
         Lease6Ptr old_lease;
-        if (!old_leases.empty()) {
-            old_lease = *old_leases.begin();
+        if (!ctx.old_leases_.empty()) {
+            old_lease = *ctx.old_leases_.begin();
         }
         // Allocation engine may have returned an existing lease. If so, we
         // have to check that the FQDN settings we provided are the same
@@ -1414,10 +1411,6 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
         hint = hint_opt->getAddress();
     }
 
-    // Attempt to get MAC address using any of available mechanisms.
-    // It's ok if there response is NULL. Hardware address is optional in Lease6
-    HWAddrPtr hwaddr = getMAC(query);
-
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_PD_REQUEST)
         .arg(duid ? duid->toText() : "(no-duid)").arg(ia->getIAID())
         .arg(hint_opt ? hint.toText() : "(no hint)");
@@ -1437,14 +1430,15 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // may be used instead. If fake_allocation is set to false, the lease will
     // be inserted into the LeaseMgr as well.
     Lease6Collection old_leases;
-    Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid,
-                                                             ia->getIAID(),
-                                                             hint, Lease::TYPE_PD,
-                                                             false, false,
-                                                             string(),
-                                                             fake_allocation,
-                                                             callout_handle,
-                                                             old_leases, hwaddr);
+    AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(), hint, Lease::TYPE_PD,
+                                    false, false, string(), fake_allocation);
+    ctx.callout_handle_ = callout_handle;
+
+    // Attempt to get MAC address using any of available mechanisms.
+    // It's ok if there response is NULL. Hardware address is optional in Lease6
+    ctx.hwaddr_ = getMAC(query);
+
+    Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
 
     if (!leases.empty()) {
 

+ 485 - 191
src/lib/dhcpsrv/alloc_engine.cc

@@ -15,6 +15,7 @@
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/host.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 
 #include <hooks/server_hooks.h>
@@ -23,6 +24,7 @@
 #include <cstring>
 #include <limits>
 #include <vector>
+#include <stdint.h>
 #include <string.h>
 
 using namespace isc::asiolink;
@@ -295,190 +297,498 @@ AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts,
 }
 
 Lease6Collection
-AllocEngine::allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                             const uint32_t iaid, const IOAddress& hint,
-                             Lease::Type type, const bool fwd_dns_update,
-                             const bool rev_dns_update,
-                             const std::string& hostname, bool fake_allocation,
-                             const isc::hooks::CalloutHandlePtr& callout_handle,
-                             Lease6Collection& old_leases, const HWAddrPtr& hwaddr) {
+AllocEngine::allocateLeases6(ClientContext6& ctx) {
 
     try {
-        AllocatorPtr allocator = getAllocator(type);
-
-        if (!allocator) {
-            isc_throw(InvalidOperation, "No allocator specified for "
-                      << Lease6::typeToText(type));
+        if (!ctx.subnet_) {
+            isc_throw(InvalidOperation, "Subnet is required for IPv6 lease allocation");
+        } else
+        if (!ctx.duid_) {
+            isc_throw(InvalidOperation, "DUID is mandatory for IPv6 lease allocation");
         }
 
-        if (!subnet) {
-            isc_throw(InvalidOperation, "Subnet is required for allocation");
-        }
+        // Check which host reservation mode is supported in this subnet.
+        Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
 
-        if (!duid) {
-            isc_throw(InvalidOperation, "DUID is mandatory for allocation");
+        // 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 {
+            // Let's explicitly set it to NULL if reservations are disabled.
+            ctx.host_.reset();
         }
 
-        // Check if there's existing lease for that subnet/duid/iaid
+        // Check if there are existing leases for that subnet/duid/iaid
         // combination.
-        /// @todo: Make this generic (cover temp. addrs and prefixes)
-        Lease6Collection existing = LeaseMgrFactory::instance().getLeases6(type,
-                                    *duid, iaid, subnet->getID());
-
-        // There is at least one lease for this client. We will return these
-        // leases for the client, but we may need to update FQDN information.
-        if (!existing.empty()) {
-            // Return old leases so the server can see what has changed.
-            old_leases = existing;
-            return (updateFqdnData(existing, fwd_dns_update, rev_dns_update,
-                                   hostname, fake_allocation));
+        Lease6Collection leases =
+            LeaseMgrFactory::instance().getLeases6(ctx.type_, *ctx.duid_, ctx.iaid_,
+                                                   ctx.subnet_->getID());
+
+        // Now do the checks:
+        // Case 1. if there are no leases, and there are reservations...
+        //   1.1. are the reserved addresses are used by someone else?
+        //       yes: we have a problem
+        //       no: assign them => done
+        // Case 2. if there are leases and there are no reservations...
+        //   2.1 are the leases reserved for someone else?
+        //       yes: release them, assign something else
+        //       no: renew them => done
+        // Case 3. if there are leases and there are reservations...
+        //   3.1 are the leases matching reservations?
+        //       yes: renew them => done
+        //       no: release existing leases, assign new ones based on reservations
+        // Case 4/catch-all. if there are no leases and no reservations...
+        //       assign new leases
+        //
+        // We could implement those checks as nested ifs, but the performance
+        // gain would be minimal and the code readibility loss would be substantial.
+        // Hence independent checks.
+
+        // Case 1: There are no leases and there's a reservation for this host.
+        if (leases.empty() && ctx.host_) {
+
+            // Try to allocate leases that match reservations. Typically this will
+            // succeed, except cases where the reserved addresses are used by
+            // someone else.
+            allocateReservedLeases6(ctx, leases);
+
+            // If we got at least one lease, we're good to go.
+            if (!leases.empty()) {
+                return (leases);
+            }
+
+            // If not, we'll need to continue and will eventually fall into case 4:
+            // getting a regular lease. That could happen when we're processing
+            // request from client X, there's a reserved address A for X, but
+            // A is currently used by client Y. We can't immediately reassign A
+            // from X to Y, because Y keeps using it, so X would send Decline right
+            // away. Need to wait till Y renews, then we can release A, so it
+            // will become available for X.
+
+        // Case 2: There are existing leases and there are no reservations.
+        //
+        // There is at least one lease for this client and there are no reservations.
+        // We will return these leases for the client, but we may need to update
+        // FQDN information.
+        } else if (!leases.empty() && !ctx.host_) {
+
+            // 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 (!leases.empty()) {
+                // Return old leases so the server can see what has changed.
+                return (updateFqdnData(leases, ctx.fwd_dns_update_,
+                                       ctx.rev_dns_update_,
+                                       ctx.hostname_, ctx.fake_allocation_));
+            }
+
+            // If leases are empty at this stage, it means that we used to have
+            // leases for this client, but we checked and those leases are reserved
+            // for someone else, so we lost them. We will need to continue and
+            // will finally end up in case 4 (no leases, no reservations), so we'll
+            // assign something new.
+
+        // Case 3: There are leases and there are reservations.
+        } else if (!leases.empty() && ctx.host_) {
+
+            // First, check if have leases matching reservations, and add new
+            // leases if we don't have them.
+            allocateReservedLeases6(ctx, leases);
+
+            // leases now contain both existing and new leases that were created
+            // from reservations.
+
+            // Second, let's remove leases that are reserved for someone else.
+            // This applies to any existing leases. This will not happen frequently,
+            // but it may happen with the following chain of events:
+            // 1. client A gets address X;
+            // 2. reservation for client B for address X is made by a administrator;
+            // 3. client A reboots
+            // 4. client A requests the address (X) he got previously
+            removeNonmatchingReservedLeases6(ctx, leases);
+
+            // leases now contain existing and new leases, but we removed those
+            // leases that are reserved for someone else (non-matching reserved).
+
+            // 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);
+
+            // All checks are done. Let's hope we have some leases left.
+
+            // If we have any leases left, let's return them and we're done.
+            if (!leases.empty()) {
+                return (leases);
+            }
+
+            // If we don't have any leases at this stage, it means that we hit
+            // one of the following cases:
+            // - we have a reservation, but it's not for this IAID/ia-type and
+            //   we had to return the address we were using
+            // - we have a reservation for this iaid/ia-type, but the reserved
+            //   address is currently used by someone else. We can't assign it
+            //   yet.
+            // - we had an address, but we just discovered that it's reserved for
+            //   someone else, so we released it.
+        }
+
+        // Case 4/catch-all: One of the following is true:
+        // - we don't have leases and there are no reservations
+        // - we used to have leases, but we lost them, because they are now
+        //   reserved for someone else
+        // - we have a reservation, but it is not usable yet, because the address
+        //   is still used by someone else
+        //
+        // In any case, we need to go through normal lease assignment process
+        // for now. This is also a catch-all or last resort approach, when we
+        // couldn't find any reservations (or couldn't use them).
+
+        leases = allocateUnreservedLeases6(ctx);
+
+        if (!leases.empty()) {
+            return (leases);
         }
 
-        // check if the hint is in pool and is available
-        // This is equivalent of subnet->inPool(hint), but returns the pool
-        Pool6Ptr pool = boost::dynamic_pointer_cast<
-            Pool6>(subnet->getPool(type, hint, false));
+        // Unable to allocate an address, return an empty lease.
+        LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_FAIL).arg(attempts_);
+
+    } catch (const isc::Exception& e) {
+
+        // Some other error, return an empty lease.
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_ERROR).arg(e.what());
+    }
+
+    return (Lease6Collection());
+}
+
+Lease6Collection
+AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
+
+    AllocatorPtr allocator = getAllocator(ctx.type_);
+
+    if (!allocator) {
+        isc_throw(InvalidOperation, "No allocator specified for "
+                  << Lease6::typeToText(ctx.type_));
+    }
+
+    // Check which host reservation mode is supported in this subnet.
+    Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
+
+    Lease6Collection leases;
+
+    IOAddress hint("::");
+    if (!ctx.hints_.empty()) {
+        /// @todo: We support only one hint for now
+        hint = ctx.hints_[0];
+    }
+
+    // check if the hint is in pool and is available
+    // This is equivalent of subnet->inPool(hint), but returns the pool
+    Pool6Ptr pool = boost::dynamic_pointer_cast<
+        Pool6>(ctx.subnet_->getPool(ctx.type_, hint, false));
+
+    if (pool) {
+        /// @todo: We support only one hint for now
+        Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(ctx.type_, hint);
+        if (!lease) {
 
-        if (pool) {
-            /// @todo: We support only one hint for now
-            Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(type, hint);
-            if (!lease) {
-                /// @todo: check if the hint is reserved once we have host
-                /// support implemented
+            // In-pool reservations: Check if this address is reserved for someone
+            // else. There is no need to check for whom it is reserved, because if
+            // it has been reserved for us we would have already allocated a lease.
+
+            ConstHostPtr host;
+            if (hr_mode != Subnet::HR_DISABLED) {
+                host = HostMgr::instance().get6(ctx.subnet_->getID(), hint);
+            }
+
+            if (!host) {
+                // If the in-pool reservations are disabled, or there is no
+                // reservation for a given hint, we're good to go.
 
                 // The hint is valid and not currently used, let's create a
                 // lease for it
-                lease = createLease6(subnet, duid, iaid, hint,
-                                     pool->getLength(), type,
-                                     fwd_dns_update, rev_dns_update,
-                                     hostname, hwaddr, callout_handle, fake_allocation);
+                lease = createLease6(ctx, hint, pool->getLength());
 
                 // It can happen that the lease allocation failed (we could
                 // have lost the race condition. That means that the hint is
-                // lo longer usable and we need to continue the regular
+                // no longer usable and we need to continue the regular
                 // allocation path.
                 if (lease) {
-                    // We are allocating a new lease (not renewing). So, the
-                    // old lease should be NULL.
-                    old_leases.push_back(Lease6Ptr());
 
                     /// @todo: We support only one lease per ia for now
                     Lease6Collection collection;
                     collection.push_back(lease);
                     return (collection);
                 }
-            } else {
-                if (lease->expired()) {
+            }
+        } else {
+
+            // If the lease is expired, we may likely reuse it, but...
+            if (lease->expired()) {
+
+                ConstHostPtr host;
+                if (hr_mode != Subnet::HR_DISABLED) {
+                    host = HostMgr::instance().get6(ctx.subnet_->getID(), hint);
+                }
+
+                // Let's check if there is a reservation for this address.
+                if (!host) {
+
                     // Copy an existing, expired lease so as it can be returned
                     // to the caller.
                     Lease6Ptr old_lease(new Lease6(*lease));
-                    old_leases.push_back(old_lease);
+                    ctx.old_leases_.push_back(old_lease);
 
                     /// We found a lease and it is expired, so we can reuse it
-                    lease = reuseExpiredLease(lease, subnet, duid, iaid,
-                                              pool->getLength(),
-                                              fwd_dns_update, rev_dns_update,
-                                              hostname, callout_handle,
-                                              fake_allocation);
+                    lease = reuseExpiredLease(lease, ctx, pool->getLength());
 
                     /// @todo: We support only one lease per ia for now
-                    Lease6Collection collection;
-                    collection.push_back(lease);
-                    return (collection);
+                    leases.push_back(lease);
+                    return (leases);
                 }
-
             }
         }
+    }
 
-        // Hint is in the pool but is not available. Search the pool until first of
-        // the following occurs:
-        // - we find a free address
-        // - we find an address for which the lease has expired
-        // - we exhaust number of tries
-        //
-        // @todo: Current code does not handle pool exhaustion well. It will be
-        // improved. Current problems:
-        // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
-        // 10 addresses), we will iterate over it 100 times before giving up
-        // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
-        // 3. the whole concept of infinite attempts is just asking for infinite loop
-        // We may consider some form or reference counting (this pool has X addresses
-        // left), but this has one major problem. We exactly control allocation
-        // moment, but we currently do not control expiration time at all
-
-        // Initialize the maximum number of attempts to pick and allocate an
-        // address. The value of 0 means "infinite", which is maximum uint32_t
-        // value.
-        uint32_t max_attempts = (attempts_ == 0) ?
-            std::numeric_limits<uint32_t>::max() : attempts_;
-
-        for (uint32_t i = 0; i < max_attempts; ++i) {
-            IOAddress candidate = allocator->pickAddress(subnet, duid, hint);
-
-            /// @todo: check if the address is reserved once we have host support
-            /// implemented
-
-            // The first step is to find out prefix length. It is 128 for
-            // non-PD leases.
-            uint8_t prefix_len = 128;
-            if (type == Lease::TYPE_PD) {
-                Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
-                    subnet->getPool(type, candidate, false));
-                prefix_len = pool->getLength();
-            }
+    // The hint was useless (it was not provided at all, was used by someone else,
+    // was out of pool or reserved for someone else). Search the pool until first
+    // of the following occurs:
+    // - we find a free address
+    // - we find an address for which the lease has expired
+    // - we exhaust number of tries
+    //
+    // @todo: Current code does not handle pool exhaustion well. It will be
+    // improved. Current problems:
+    // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
+    // 10 addresses), we will iterate over it 100 times before giving up
+    // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
+    // 3. the whole concept of infinite attempts is just asking for infinite loop
+    // We may consider some form or reference counting (this pool has X addresses
+    // left), but this has one major problem. We exactly control allocation
+    // moment, but we currently do not control expiration time at all
+
+    // Initialize the maximum number of attempts to pick and allocate an
+    // address. The value of 0 means "infinite", which is maximum uint32_t
+    // value.
+    uint32_t max_attempts = (attempts_ == 0) ?
+        std::numeric_limits<uint32_t>::max() : attempts_;
+    for (uint32_t i = 0; i < max_attempts; ++i)
+    {
+        IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.duid_, hint);
+
+        /// In-pool reservations: Check if this address is reserved for someone
+        /// else. There is no need to check for whom it is reserved, because if
+        /// it has been reserved for us we would have already allocated a lease.
+        if (hr_mode == Subnet::HR_IN_POOL &&
+            HostMgr::instance().get6(ctx.subnet_->getID(), candidate)) {
+
+            // Don't allocate.
+            continue;
+        }
 
-            Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(type,
-                                 candidate);
-            if (!existing) {
+        // The first step is to find out prefix length. It is 128 for
+        // non-PD leases.
+        uint8_t prefix_len = 128;
+        if (ctx.type_ == Lease::TYPE_PD) {
+            Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
+                ctx.subnet_->getPool(ctx.type_, candidate, false));
+            prefix_len = pool->getLength();
+        }
 
-                // there's no existing lease for selected candidate, so it is
-                // free. Let's allocate it.
+        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.type_,
+                                                                   candidate);
+        if (!existing) {
 
-                Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
-                                               prefix_len, type, fwd_dns_update,
-                                               rev_dns_update, hostname, hwaddr,
-                                               callout_handle, fake_allocation);
-                if (lease) {
-                    // We are allocating a new lease (not renewing). So, the
-                    // old lease should be NULL.
-                    old_leases.push_back(Lease6Ptr());
+            // there's no existing lease for selected candidate, so it is
+            // free. Let's allocate it.
 
-                    Lease6Collection collection;
-                    collection.push_back(lease);
-                    return (collection);
-                }
+            Lease6Ptr lease = createLease6(ctx, candidate, prefix_len);
+            if (lease) {
+                // We are allocating a new lease (not renewing). So, the
+                // old lease should be NULL.
+                ctx.old_leases_.clear();
 
-                // Although the address was free just microseconds ago, it may have
-                // been taken just now. If the lease insertion fails, we continue
-                // allocation attempts.
-            } else {
-                if (existing->expired()) {
-                    // Copy an existing, expired lease so as it can be returned
-                    // to the caller.
-                    Lease6Ptr old_lease(new Lease6(*existing));
-                    old_leases.push_back(old_lease);
+                leases.push_back(lease);
+                return (leases);
+            }
 
-                    existing = reuseExpiredLease(existing, subnet, duid, iaid,
-                                                 prefix_len, fwd_dns_update,
-                                                 rev_dns_update, hostname,
-                                                 callout_handle, fake_allocation);
-                    Lease6Collection collection;
-                    collection.push_back(existing);
-                    return (collection);
-                }
+            // Although the address was free just microseconds ago, it may have
+            // been taken just now. If the lease insertion fails, we continue
+            // allocation attempts.
+        } else {
+            if (existing->expired()) {
+                // Copy an existing, expired lease so as it can be returned
+                // to the caller.
+                Lease6Ptr old_lease(new Lease6(*existing));
+                ctx.old_leases_.push_back(old_lease);
+
+                existing = reuseExpiredLease(existing,
+                                             ctx,
+                                             prefix_len);
+
+                leases.push_back(existing);
+                return (leases);
             }
         }
+    }
 
-        // Unable to allocate an address, return an empty lease.
-        LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_FAIL).arg(attempts_);
+    // We failed to allocate anything. Let's return empty collection.
+    return (Lease6Collection());
+}
 
-    } catch (const isc::Exception& e) {
+void
+AllocEngine::allocateReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases) {
 
-        // Some other error, return an empty lease.
-        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_ERROR).arg(e.what());
+    // If there are no reservations or the reservation is v4, there's nothing to do.
+    if (!ctx.host_ || !ctx.host_->hasIPv6Reservation()) {
+        return;
     }
 
-    return (Lease6Collection());
+    // Let's convert this from Lease::Type to IPv6Reserv::Type
+    IPv6Resrv::Type type = ctx.type_ == Lease::TYPE_NA ? IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD;
+
+    // Get the IPv6 reservations of specified type.
+    const IPv6ResrvRange& reservs = ctx.host_->getIPv6Reservations(type);
+
+    if (std::distance(reservs.first, reservs.second) == 0) {
+        // No reservations? We're done here.
+        return;
+    }
+
+    for (IPv6ResrvIterator resv = reservs.first; resv != reservs.second; ++resv) {
+        // We do have a reservation for addr.
+        IOAddress addr = resv->second.getPrefix();
+        uint8_t prefix_len = resv->second.getPrefixLen();
+
+        // If there's a lease for this address, let's not create it.
+        // It doesn't matter whether it is for this client or for someone else.
+        if (LeaseMgrFactory::instance().getLease6(ctx.type_, addr)) {
+            continue;
+        }
+
+        // Ok, let's create a new lease...
+        Lease6Ptr lease = createLease6(ctx, addr, prefix_len);
+
+        // ... and add it to the existing leases list.
+        existing_leases.push_back(lease);
+    }
+}
+
+void
+AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
+                                              Lease6Collection& existing_leases) {
+    // If there are no leases (so nothing to remove) or
+    // host reservation is disabled (so there are no reserved leases),
+    // just return.
+    if (existing_leases.empty() || !ctx.subnet_ ||
+        (ctx.subnet_->getHostReservationMode() == Subnet::HR_DISABLED) ) {
+        return;
+    }
+
+    // We need a copy, so we won't be iterating over a container and
+    // removing from it at the same time. It's only a copy of pointers,
+    // so the operation shouldn't be that expensive.
+    Lease6Collection copy = existing_leases;
+
+    for (Lease6Collection::const_iterator candidate = copy.begin();
+         candidate != copy.end(); ++candidate) {
+
+        ConstHostPtr host = HostMgr::instance().get6(ctx.subnet_->getID(),
+                                                     (*candidate)->addr_);
+
+        if (!host || (host == ctx.host_)) {
+            // Not reserved or reserved for us. That's ok, let's check
+            // the next lease.
+            continue;
+        }
+
+        // Ok, we have a problem. This host has a lease that is reserved
+        // for someone else. We need to recover from this.
+
+        // Remove this lease from LeaseMgr
+        LeaseMgrFactory::instance().deleteLease((*candidate)->addr_);
+
+        /// @todo: Probably trigger a hook here
+
+        // Add this to the list of removed leases.
+        ctx.old_leases_.push_back(*candidate);
+
+        // Let's remove this candidate from existing leases
+        removeLeases(existing_leases, (*candidate)->addr_);
+    }
+}
+
+bool
+AllocEngine::removeLeases(Lease6Collection& container, const asiolink::IOAddress& addr) {
+
+    bool removed = false;
+    for (Lease6Collection::iterator lease = container.begin();
+         lease != container.end(); ++lease) {
+        if ((*lease)->addr_ == addr) {
+            lease->reset();
+            removed = true;
+        }
+    }
+
+    // Remove all elements that have NULL value
+    container.erase(std::remove(container.begin(), container.end(), Lease6Ptr()),
+                    container.end());
+
+    return (removed);
+}
+
+void
+AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
+                                      Lease6Collection& existing_leases) {
+    // This method removes leases that are not reserved for this host.
+    // It will keep at least one lease, though.
+    if (existing_leases.empty() || !ctx.host_ || !ctx.host_->hasIPv6Reservation()) {
+        return;
+    }
+
+    // This is the total number of leases. We should not remove the last one.
+    int total = existing_leases.size();
+
+    // This is officially not scary code anymore. iterates and marks specified
+    // leases for deletion, by setting appropriate pointers to NULL.
+    for (Lease6Collection::iterator lease = existing_leases.begin();
+         lease != existing_leases.end(); ++lease) {
+        IPv6Resrv resv(ctx.type_ == Lease::TYPE_NA ? IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD,
+                       (*lease)->addr_, (*lease)->prefixlen_);
+        if (!ctx.host_->hasReservation(resv)) {
+            // We have reservations, but not for this lease. Release it.
+
+            // Remove this lease from LeaseMgr
+            LeaseMgrFactory::instance().deleteLease((*lease)->addr_);
+
+            /// @todo: Probably trigger a hook here
+
+            // Add this to the list of removed leases.
+            ctx.old_leases_.push_back(*lease);
+
+            // Set this pointer to NULL. The pointer is still valid. We're just
+            // setting the Lease6Ptr to NULL value. We'll remove all NULL
+            // pointers once the loop is finished.
+            lease->reset();
+
+            if (--total == 1) {
+                // If there's only one lease left, break the loop.
+                break;
+            }
+        }
+
+    }
+
+    // Remove all elements that we previously marked for deletion (those that
+    // have NULL value).
+    existing_leases.erase(std::remove(existing_leases.begin(),
+        existing_leases.end(), Lease6Ptr()), existing_leases.end());
 }
 
 Lease4Ptr
@@ -813,15 +1123,8 @@ AllocEngine::renewLease4(const Lease4Ptr& lease,
 }
 
 Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
-                                         const Subnet6Ptr& subnet,
-                                         const DuidPtr& duid,
-                                         const uint32_t iaid,
-                                         uint8_t prefix_len,
-                                         const bool fwd_dns_update,
-                                         const bool rev_dns_update,
-                                         const std::string& hostname,
-                                         const isc::hooks::CalloutHandlePtr& callout_handle,
-                                         bool fake_allocation /*= false */ ) {
+                                         ClientContext6& ctx,
+                                         uint8_t prefix_len) {
 
     if (!expired->expired()) {
         isc_throw(BadValue, "Attempt to recycle lease that is still valid");
@@ -832,57 +1135,57 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
     }
 
     // address, lease type and prefixlen (0) stay the same
-    expired->iaid_ = iaid;
-    expired->duid_ = duid;
-    expired->preferred_lft_ = subnet->getPreferred();
-    expired->valid_lft_ = subnet->getValid();
-    expired->t1_ = subnet->getT1();
-    expired->t2_ = subnet->getT2();
+    expired->iaid_ = ctx.iaid_;
+    expired->duid_ = ctx.duid_;
+    expired->preferred_lft_ = ctx.subnet_->getPreferred();
+    expired->valid_lft_ = ctx.subnet_->getValid();
+    expired->t1_ = ctx.subnet_->getT1();
+    expired->t2_ = ctx.subnet_->getT2();
     expired->cltt_ = time(NULL);
-    expired->subnet_id_ = subnet->getID();
+    expired->subnet_id_ = ctx.subnet_->getID();
     expired->fixed_ = false;
-    expired->hostname_ = hostname;
-    expired->fqdn_fwd_ = fwd_dns_update;
-    expired->fqdn_rev_ = rev_dns_update;
+    expired->hostname_ = ctx.hostname_;
+    expired->fqdn_fwd_ = ctx.fwd_dns_update_;
+    expired->fqdn_rev_ = ctx.rev_dns_update_;
     expired->prefixlen_ = prefix_len;
 
     /// @todo: log here that the lease was reused (there's ticket #2524 for
     /// logging in libdhcpsrv)
 
     // Let's execute all callouts registered for lease6_select
-    if (callout_handle &&
+    if (ctx.callout_handle_ &&
         HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
 
         // Delete all previous arguments
-        callout_handle->deleteAllArguments();
+        ctx.callout_handle_->deleteAllArguments();
 
         // Pass necessary arguments
         // Subnet from which we do the allocation
-        callout_handle->setArgument("subnet6", subnet);
+        ctx.callout_handle_->setArgument("subnet6", ctx.subnet_);
 
         // Is this solicit (fake = true) or request (fake = false)
-        callout_handle->setArgument("fake_allocation", fake_allocation);
+        ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
 
         // The lease that will be assigned to a client
-        callout_handle->setArgument("lease6", expired);
+        ctx.callout_handle_->setArgument("lease6", expired);
 
         // Call the callouts
-        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+        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 (callout_handle->getSkip()) {
+        if (ctx.callout_handle_->getSkip()) {
             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.
-        callout_handle->getArgument("lease6", expired);
+        ctx.callout_handle_->getArgument("lease6", expired);
     }
 
-    if (!fake_allocation) {
+    if (!ctx.fake_allocation_) {
         // for REQUEST we do update the lease
         LeaseMgrFactory::instance().updateLease6(expired);
     }
@@ -1085,65 +1388,56 @@ AllocEngine::reallocateClientLease(Lease4Ptr& lease,
 }
 
 
-Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
-                                    const DuidPtr& duid,
-                                    const uint32_t iaid,
+Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
                                     const IOAddress& addr,
-                                    uint8_t prefix_len,
-                                    const Lease::Type type,
-                                    const bool fwd_dns_update,
-                                    const bool rev_dns_update,
-                                    const std::string& hostname,
-                                    const HWAddrPtr& hwaddr,
-                                    const isc::hooks::CalloutHandlePtr& callout_handle,
-                                    bool fake_allocation /*= false */ ) {
+                                    uint8_t prefix_len) {
 
-    if (type != Lease::TYPE_PD) {
+    if (ctx.type_ != Lease::TYPE_PD) {
         prefix_len = 128; // non-PD lease types must be always /128
     }
 
-    Lease6Ptr lease(new Lease6(type, addr, duid, iaid,
-                               subnet->getPreferred(), subnet->getValid(),
-                               subnet->getT1(), subnet->getT2(), subnet->getID(),
-                               hwaddr, prefix_len));
+    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));
 
-    lease->fqdn_fwd_ = fwd_dns_update;
-    lease->fqdn_rev_ = rev_dns_update;
-    lease->hostname_ = hostname;
+    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 lease6_select
-    if (callout_handle &&
+    if (ctx.callout_handle_ &&
         HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
 
         // Delete all previous arguments
-        callout_handle->deleteAllArguments();
+        ctx.callout_handle_->deleteAllArguments();
 
         // Pass necessary arguments
 
         // Subnet from which we do the allocation
-        callout_handle->setArgument("subnet6", subnet);
+        ctx.callout_handle_->setArgument("subnet6", ctx.subnet_);
 
         // Is this solicit (fake = true) or request (fake = false)
-        callout_handle->setArgument("fake_allocation", fake_allocation);
-        callout_handle->setArgument("lease6", lease);
+        ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
+        ctx.callout_handle_->setArgument("lease6", lease);
 
         // This is the first callout, so no need to clear any arguments
-        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+        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 (callout_handle->getSkip()) {
+        if (ctx.callout_handle_->getSkip()) {
             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.
-        callout_handle->getArgument("lease6", lease);
+        ctx.callout_handle_->getArgument("lease6", lease);
     }
 
-    if (!fake_allocation) {
+    if (!ctx.fake_allocation_) {
         // That is a real (REQUEST) allocation
         bool status = LeaseMgrFactory::instance().addLease(lease);
 

+ 296 - 68
src/lib/dhcpsrv/alloc_engine.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -301,6 +301,133 @@ protected:
         }
     };
 
+
+    /// @brief Context information for the DHCPv6 leases allocation.
+    ///
+    /// This structure holds a set of information provided by the DHCPv6
+    /// 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 leases
+    /// which the client had before the allocation.
+    ///
+    /// This structure is expected to be common for a single client, even
+    /// if multiple IAs are used. Some of the fields will need to be
+    /// updated for every call (there's a separate call to the allocation
+    /// engine for each IA option).
+    ///
+    /// 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 ClientContext6 {
+        /// @brief Subnet selected for the client by the server.
+        Subnet6Ptr subnet_;
+
+        /// @brief Client identifier
+        DuidPtr duid_;
+
+        /// @brief iaid IAID field from IA_NA or IA_PD that is being processed
+        uint32_t iaid_;
+
+        /// @brief Lease type (IA or PD)
+        Lease::Type type_;
+
+        /// @brief Hardware/MAC address (if available, may be NULL)
+        HWAddrPtr hwaddr_;
+
+        /// @brief client's hints
+        ///
+        /// There will typically be just one address, but the protocol allows
+        /// more than one address or prefix for each IA container.
+        std::vector<isc::asiolink::IOAddress> hints_;
+
+        /// @brief A boolean value which indicates that server takes
+        ///        responsibility for the forward DNS Update for this lease
+        ///        (if true).
+        bool fwd_dns_update_;
+
+        /// @brief A boolean value which indicates that server takes
+        ///        responsibility for the reverse DNS Update for this lease
+        ///        (if true).
+        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 any old leases that the client had before update
+        ///        but are no longer valid after the update/allocation.
+        ///
+        /// This collection is typically empty, except cases when we are doing
+        /// address reassignment, e.g. because there is a host reservation that
+        /// gives this address to someone else, so we had to return the address,
+        /// and give a new one to this client.
+        Lease6Collection old_leases_;
+
+        /// @brief A pointer to the object identifying host reservations.
+        ///
+        /// May be NULL if there are no reservations.
+        ConstHostPtr host_;
+
+        /// @brief Default constructor.
+        ClientContext6()
+           : subnet_(), duid_(), iaid_(0), type_(Lease::TYPE_NA), hwaddr_(),
+             hints_(), fwd_dns_update_(false), rev_dns_update_(false), hostname_(""),
+             callout_handle_(), fake_allocation_(false), old_leases_(), host_() {
+        }
+
+        /// @brief Constructor with parameters.
+        ///
+        /// Note that several less frequently parameters (callout_handle,
+        /// old_leases, host) fields are not set. They should be set explicitly,
+        /// if needed.
+        ///
+        /// @param subnet subnet the allocation should come from
+        /// @param duid Client's DUID
+        /// @param iaid iaid field from the IA_NA container that client sent
+        /// @param hint a hint that the client provided
+        /// @param type lease type (IA, TA or PD)
+        /// @param fwd_dns A boolean value which indicates that server takes
+        ///        responsibility for the forward DNS Update for this lease
+        ///        (if true).
+        /// @param rev_dns A boolean value which indicates that server takes
+        ///        responsibility for the reverse DNS Update for this lease
+        ///        (if true).
+        /// @param hostname A fully qualified domain-name of the client.
+        /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+        ///        an address for SOLICIT that is not really allocated (true)
+        ClientContext6(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                       const uint32_t iaid, const isc::asiolink::IOAddress& hint,
+                       const Lease::Type type, const bool fwd_dns, const bool
+                       rev_dns, const std::string& hostname, const bool
+                       fake_allocation):
+        subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
+            hints_(), fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
+            hostname_(hostname), fake_allocation_(fake_allocation),
+            old_leases_(), host_() {
+            hints_.push_back(hint);
+            // callout_handle, host pointers initiated to NULL by their
+            // respective constructors.
+        }
+    };
+
     /// @brief Default constructor.
     ///
     /// Instantiates necessary services, required to run DHCPv6 server.
@@ -436,44 +563,86 @@ protected:
     Lease4Ptr
     renewLease4(const Lease4Ptr& lease, ClientContext4& ctx);
 
-    /// @brief Allocates an IPv6 lease
-    ///
-    /// This method uses currently selected allocator to pick an address from
-    /// specified subnet, creates a lease for that address and then inserts
-    /// it into LeaseMgr (if this allocation is not fake).
-    ///
-    /// @param subnet subnet the allocation should come from
-    /// @param duid Client's DUID
-    /// @param iaid iaid field from the IA_NA container that client sent
-    /// @param hint a hint that the client provided
-    /// @param type lease type (IA, TA or PD)
-    /// @param fwd_dns_update A boolean value which indicates that server takes
-    ///        responsibility for the forward DNS Update for this lease
-    ///        (if true).
-    /// @param rev_dns_update A boolean value which indicates that server takes
-    ///        responsibility for the reverse DNS Update for this lease
-    ///        (if true).
-    /// @param hostname A fully qualified domain-name of the client.
-    /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
-    ///        an address for SOLICIT 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_leases Collection to which this function will append
-    ///        old leases. Leases are stored in the same order as in the
-    ///        collection of new leases, being returned. For newly allocated
+    /// @brief Allocates IPv6 leases for a given IA container
+    ///
+    /// This method uses currently selected allocator to pick allocatable
+    /// resources (i.e. addresses or prefixes) from specified subnet, creates
+    /// a lease (one or more, if needed) for that resources and then inserts
+    /// it into LeaseMgr (if this allocation is not fake, i.e. this is not a
+    /// response to SOLICIT).
+    ///
+    /// This method uses host reservation if appropriate. The host reservation
+    /// is convenient, but incurs performance penalty, so it can be tweaked on
+    /// a per subnet basis. There are three possible modes:
+    /// 1. disabled (no host reservation at all). This is the most performant one
+    /// as the code can skip all checks;
+    /// 2. out-of-pool (only reservations that are outside
+    /// of the dynamic pools are allowed. This is a compromise - it requires
+    /// a sysadmin to be more careful with the reservations, but the code
+    /// can skip reservation checks while managing in-pool addresses);
+    /// 3. in-pool (which also allow out-of-pool; this is the most flexible
+    /// mode, but it means that the allocation engine has to do reservation
+    /// checks on every lease, even those dynamically assigned, which degrades
+    /// performance).
+    ///
+    /// The logic in this method is as follows:
+    /// -# Case 1. if there are no leases, and there are reservations...
+    ///    Are the reserved addresses/prefixes are used by someone else?
+    ///   -# yes: we have a problem. We can't assign the reserved address yet,
+    ///       because it is used by someone else. We can't immediately release
+    ///       the lease as there is some other client that is currently using it.
+    ///       We will temporarily assign a different, unreserved lease for this
+    ///       client. In the mean time, the other client will hopefully get back
+    ///       to us, so we could revoke his lease.
+    ///   -# no: assign them => done
+    /// -# Case 2. if there are leases and there are no reservations...
+    ///    Are the leases reserved for someone else?
+    ///    -# yes: release them, assign something else
+    ///    -#  no: renew them => done
+    /// -# Case 3. if there are leases and there are reservations...
+    ///    Are the leases matching reservations?
+    ///   -# yes: renew them => done
+    ///   -#  no: release existing leases, assign new ones based on reservations
+    /// -# Case 4. if there are no leases and no reservations...
+    ///       assign new leases (this is the "normal" case when the reservations
+    ///       are disabled).
+    ///
+    /// @param ctx client context that passes all necessary information. See
+    ///        @ref ClientContext6 for details.
+    ///
+    /// The following fields of ClientContext6 are used:
+    ///
+    /// @ref ClientContext6::subnet_ subnet the allocation should come from<br/>
+    /// @ref ClientContext6::duid_ Client's DUID<br/>
+    /// @ref ClientContext6::iaid_ iaid field from the IA_NA container
+    ///        that client sent<br/>
+    /// @ref ClientContext6::hints_ a hint that the client provided<br/>
+    /// @ref ClientContext6::type_ lease type (IA, TA or PD)<br/>
+    /// @ref ClientContext6::fwd_dns_update_ A boolean value which indicates
+    ///        that server takes responsibility for the forward DNS Update
+    ///        for this lease (if true).<br/>
+    /// @ref ClientContext6::rev_dns_update_ A boolean value which indicates
+    ///        that server takes responsibility for the reverse DNS Update for
+    ///        this lease (if true).<br/>
+    /// @ref ClientContext6::hostname_ A fully qualified domain-name of the client.<br/>
+    /// @ref ClientContext6::fake_allocation_ is this real i.e. REQUEST (false)
+    ///        or just picking an address for SOLICIT that is not really
+    ///        allocated (true)<br/>
+    /// @ref ClientContext6::callout_handle_ a callout handle (used in hooks). A
+    ///        lease callouts will be executed if this parameter is passed.<br/>
+    /// @ref ClientContext6::old_leases_ [out] Collection to which this function
+    ///        will append old leases. Leases are stored in the same order as in
+    ///        the collection of new leases, being returned. For newly allocated
     ///        leases (not renewed) the NULL pointers are stored in this
-    ///        collection as old leases.
-    /// @param hwaddr Hardware address (optional, may be null for Lease6)
+    ///        collection as old leases.<br/>
+    /// @ref ClientContext6::hwaddr_ Hardware address (optional, may be null if
+    ///        not available)<br/>
+    /// @ref ClientContext6::host_ Host reservation. allocateLeases6 will set
+    ///        this field, if appropriate reservation is found.
     ///
     /// @return Allocated IPv6 leases (may be empty if allocation failed)
     Lease6Collection
-    allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                    const uint32_t iaid,
-                    const isc::asiolink::IOAddress& hint, Lease::Type type,
-                    const bool fwd_dns_update, const bool rev_dns_update,
-                    const std::string& hostname, bool fake_allocation,
-                    const isc::hooks::CalloutHandlePtr& callout_handle,
-                    Lease6Collection& old_leases, const HWAddrPtr& hwaddr);
+    allocateLeases6(ClientContext6& ctx);
 
     /// @brief returns allocator for a given pool type
     /// @param type type of pool (V4, IA, TA or PD)
@@ -541,36 +710,88 @@ private:
     /// 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 subnet subnet the lease is allocated from
-    /// @param duid client's DUID
-    /// @param iaid IAID from the IA_NA container the client sent to us
+    /// @param ctx client context that passes all necessary information. See
+    ///        @ref ClientContext6 for details.
     /// @param addr an address that was selected and is confirmed to be
     ///        available
     /// @param prefix_len length of the prefix (for PD only)
     ///        should be 128 for other lease types
-    /// @param type lease type (IA, TA or PD)
-    /// @param fwd_dns_update A boolean value which indicates that server takes
+    ///
+    /// The following fields of the ctx structure are used:
+    /// @ref ClientContext6::subnet_ subnet the lease is allocated from
+    /// @ref ClientContext6::duid_ client's DUID
+    /// @ref ClientContext6::iaid_ IAID from the IA_NA container the client sent to us
+    /// @ref ClientContext6::type_ lease type (IA, TA or PD)
+    /// @ref ClientContext6::fwd_dns_update_ A boolean value which indicates that server takes
     ///        responsibility for the forward DNS Update for this lease
     ///        (if true).
-    /// @param rev_dns_update A boolean value which indicates that server takes
+    /// @ref ClientContext6::rev_dns_update_ A boolean value which indicates that server takes
     ///        responsibility for the reverse DNS Update for this lease
     ///        (if true).
-    /// @param hostname A fully qualified domain-name of the client.
-    /// @param hwaddr Hardware address (optional, may be null for Lease6)
-    /// @param callout_handle a callout handle (used in hooks). A lease callouts
+    /// @ref ClientContext6::hostname_ A fully qualified domain-name of the client.
+    /// @ref ClientContext6::hwaddr_ Hardware address (optional, may be null for Lease6)
+    /// @ref ClientContext6::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
+    /// @ref ClientContext6::fake_allocation_ is this real i.e. REQUEST (false) or just picking
     ///        an address for SOLICIT that is not really allocated (true)
     /// @return allocated lease (or NULL in the unlikely case of the lease just
     ///         became unavailable)
-    Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                           const uint32_t iaid, const isc::asiolink::IOAddress& addr,
-                           const uint8_t prefix_len, const Lease::Type type,
-                           const bool fwd_dns_update, const bool rev_dns_update,
-                           const std::string& hostname, const HWAddrPtr& hwaddr,
-                           const isc::hooks::CalloutHandlePtr& callout_handle,
-                           bool fake_allocation = false);
+    Lease6Ptr createLease6(ClientContext6& ctx,
+                           const isc::asiolink::IOAddress& addr,
+                           const uint8_t prefix_len);
+
+    /// @brief Allocates a normal, in-pool, unreserved lease from the pool.
+    ///
+    /// It attempts to pick a hint first, then uses allocator iteratively until
+    /// an available (not used, not reserved) lease is found. In principle, it
+    /// may return more than one lease, but we currently handle only one.
+    /// This may change in the future.
+    ///
+    /// @param ctx client context that contains all details (subnet, client-id, etc.)
+    /// @return collection of newly allocated leases
+    Lease6Collection allocateUnreservedLeases6(ClientContext6& ctx);
+
+    /// @brief Creates new leases based on reservations.
+    ///
+    /// This method allocates new leases, based on host reservation. Existing
+    /// leases are specified in existing_leases parameter. A new lease is not created,
+    /// if there is a lease for specified address on existing_leases list or there is
+    /// a lease used by someone else.
+    ///
+    /// @param ctx client context that contains all details (subnet, client-id, etc.)
+    /// @param existing_leases leases that are already associated with the client
+    void
+    allocateReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases);
+
+    /// @brief Removes leases that are reserved for someone else.
+    ///
+    /// Goes through the list specified in existing_leases and removes those that
+    /// are reserved by someone else. The removed leases are added to the
+    /// ctx.removed_leases_ collection.
+    ///
+    /// @param ctx client context that contains all details (subnet, client-id, etc.)
+    /// @param existing_leases [in/out] leases that should be checked
+    void
+    removeNonmatchingReservedLeases6(ClientContext6& ctx,
+                                     Lease6Collection& existing_leases);
+
+    /// @brief Removed leases that are not reserved for this client
+    ///
+    /// This method iterates over existing_leases and will remove leases that are
+    /// not reserved for this client. It will leave at least one lease on the list,
+    /// if possible. The reason to run this method is that if there is a reservation
+    /// for address A for client X and client X already has a lease for a
+    /// different address B, we should assign A and release B. However,
+    /// if for some reason we can't assign A, keeping B would be better than
+    /// not having a lease at all. Hence we may keep B if that's the only lease
+    /// left.
+    ///
+    /// @param ctx client context that contains all details (subnet, client-id, etc.)
+    /// @param existing_leases [in/out] leases that should be checked
+    void
+    removeNonreservedLeases6(ClientContext6& ctx,
+                             Lease6Collection& existing_leases);
 
     /// @brief Reuses expired DHCPv4 lease.
     ///
@@ -631,32 +852,31 @@ private:
     /// dummy allocation request (i.e. SOLICIT, fake_allocation = true).
     ///
     /// @param expired old, expired lease
-    /// @param subnet subnet the lease is allocated from
-    /// @param duid client's DUID
-    /// @param iaid IAID from the IA_NA container the client sent to us
+    /// @param ctx client context that contains all details.
     /// @param prefix_len prefix length (for PD leases)
     ///        Should be 128 for other lease types
-    /// @param fwd_dns_update A boolean value which indicates that server takes
+    ///
+    /// The following parameters are used from the ctx structure:
+    /// @ref ClientContext6::subnet_ subnet the lease is allocated from
+    /// @ref ClientContext6::duid_ client's DUID
+    /// @ref ClientContext6::iaid_ IAID from the IA_NA container the client sent to us
+    /// @ref ClientContext6::fwd_dns_update_ A boolean value which indicates that server takes
     ///        responsibility for the forward DNS Update for this lease
     ///        (if true).
-    /// @param rev_dns_update A boolean value which indicates that server takes
+    /// @ref ClientContext6::rev_dns_update_ A boolean value which indicates that server takes
     ///        responsibility for the reverse DNS Update for this lease
     ///        (if true).
-    /// @param hostname A fully qualified domain-name of the client.
-    /// @param callout_handle a callout handle (used in hooks). A lease callouts
+    /// @ref ClientContext6::hostname_ A fully qualified domain-name of the client.
+    /// @ref ClientContext6::callout_handle_ a callout handle (used in hooks). A lease callouts
     ///        will be executed if this parameter is passed.
-    /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+    /// @ref ClientContext6::fake_allocation_ is this real i.e. REQUEST (false) or just picking
     ///        an address for SOLICIT that is not really allocated (true)
+    ///
     /// @return refreshed lease
     /// @throw BadValue if trying to recycle lease that is still valid
-    Lease6Ptr reuseExpiredLease(Lease6Ptr& expired, const Subnet6Ptr& subnet,
-                                const DuidPtr& duid, const uint32_t iaid,
-                                uint8_t prefix_len,
-                                const bool fwd_dns_update,
-                                const bool rev_dns_update,
-                                const std::string& hostname,
-                                const isc::hooks::CalloutHandlePtr& callout_handle,
-                                bool fake_allocation = false);
+    Lease6Ptr reuseExpiredLease(Lease6Ptr& expired,
+                                ClientContext6& ctx,
+                                uint8_t prefix_len);
 
     /// @brief Updates FQDN data for a collection of leases.
     ///
@@ -680,6 +900,14 @@ private:
                                     const std::string& hostname,
                                     const bool fake_allocation);
 
+    /// @brief Utility function that removes all leases with a specified address
+    /// @param container A collection of Lease6 pointers
+    /// @param addr address to be removed
+    /// @return true if removed (false otherwise)
+    static bool
+    removeLeases(Lease6Collection& container,
+                 const asiolink::IOAddress& addr);
+
     /// @brief a pointer to currently used allocator
     ///
     /// For IPv4, there will be only one allocator: TYPE_V4

+ 138 - 1
src/lib/dhcpsrv/cfg_hosts.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -49,6 +49,20 @@ CfgHosts::getAll4(const IOAddress& address) {
     return (collection);
 }
 
+ConstHostCollection
+CfgHosts::getAll6(const IOAddress& address) const {
+    ConstHostCollection collection;
+    getAllInternal6<ConstHostCollection>(address, collection);
+    return (collection);
+}
+
+HostCollection
+CfgHosts::getAll6(const IOAddress& address) {
+    HostCollection collection;
+    getAllInternal6<HostCollection>(address, collection);
+    return (collection);
+}
+
 template<typename Storage>
 void
 CfgHosts::getAllInternal(const std::vector<uint8_t>& identifier,
@@ -98,6 +112,24 @@ CfgHosts::getAllInternal4(const IOAddress& address, Storage& storage) const {
     }
 }
 
+template<typename Storage>
+void
+CfgHosts::getAllInternal6(const IOAddress& address, Storage& storage) const {
+    // Must not specify address other than IPv6.
+    if (!address.isV6()) {
+        isc_throw(BadHostAddress, "must specify an IPv6 address when searching"
+                  " for a host, specified address was " << address);
+    }
+    // Search for the Host using the reserved IPv6 address as a key.
+    const HostContainerIndex1& idx = hosts_.get<1>();
+    HostContainerIndex1Range r = idx.equal_range(address);
+    // Append each Host object to the storage.
+    for (HostContainerIndex1::iterator host = r.first; host != r.second;
+         ++host) {
+        storage.push_back(*host);
+    }
+}
+
 
 ConstHostPtr
 CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
@@ -151,6 +183,66 @@ CfgHosts::get6(const IOAddress&, const uint8_t) {
     isc_throw(isc::NotImplemented, "get6(prefix, len) is not implemented");
 }
 
+ConstHostPtr
+CfgHosts::get6(const SubnetID& subnet_id,
+               const asiolink::IOAddress& address) const {
+    ConstHostCollection storage;
+    getAllInternal6(subnet_id, address, storage);
+
+    switch (storage.size()) {
+    case 0:
+        return (ConstHostPtr());
+    case 1:
+        return (*storage.begin());
+    default:
+        isc_throw(DuplicateHost,  "more than one reservation found"
+                  " for the host belonging to the subnet with id '"
+                  << subnet_id << "' and using the address '"
+                  << address.toText() << "'");
+    }
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal6(const SubnetID& subnet_id,
+                          const asiolink::IOAddress& address,
+                          Storage& storage) const {
+    // Must not specify address other than IPv6.
+    if (!address.isV6()) {
+        isc_throw(BadHostAddress, "must specify an IPv6 address when searching"
+                  " for a host, specified address was " << address);
+    }
+
+    // Let's get all reservations that match subnet_id, address.
+    const HostContainer6Index1& idx = hosts6_.get<1>();
+    HostContainer6Index1Range r = idx.equal_range(boost::make_tuple(subnet_id, address));
+
+    // For each IPv6 reservation, add the host to the results list. Fortunately,
+    // in all sane cases, there will be only one such host. (Each host can have
+    // multiple addresses reserved, but for each (address, subnet_id) there should
+    // be at most one host reserving it).
+    for(HostContainer6Index1::iterator resrv = r.first; resrv != r.second; ++resrv) {
+        storage.push_back(resrv->host_);
+    }
+}
+
+HostPtr
+CfgHosts::get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) {
+    HostCollection storage;
+    getAllInternal6<HostCollection>(subnet_id, address, storage);
+    switch (storage.size()) {
+    case 0:
+        return (HostPtr());
+    case 1:
+        return (*storage.begin());
+    default:
+        isc_throw(DuplicateHost,  "more than one reservation found"
+                  " for the host belonging to the subnet with id '"
+                  << subnet_id << "' and using the address '"
+                  << address.toText() << "'");
+    }
+}
+
 HostPtr
 CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
                           const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
@@ -207,6 +299,15 @@ CfgHosts::add(const HostPtr& host) {
         isc_throw(BadValue, "must not use both IPv4 and IPv6 subnet ids of"
                   " 0 when adding new host reservation");
     }
+
+    add4(host);
+
+    add6(host);
+}
+
+void
+CfgHosts::add4(const HostPtr& host) {
+
     /// @todo This may need further sanity checks.
     HWAddrPtr hwaddr = host->getHWAddress();
     DuidPtr duid = host->getDuid();
@@ -236,6 +337,7 @@ CfgHosts::add(const HostPtr& host) {
                   << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
                   << "' as this host has already been added");
 
+
     // Check for duplicates for the specified IPv6 subnet.
     } else if (host->getIPv6SubnetID() &&
                get6(host->getIPv6SubnetID(), duid, hwaddr)) {
@@ -252,5 +354,40 @@ CfgHosts::add(const HostPtr& host) {
     hosts_.insert(host);
 }
 
+void
+CfgHosts::add6(const HostPtr& host) {
+
+    /// @todo This may need further sanity checks.
+    HWAddrPtr hwaddr = host->getHWAddress();
+    DuidPtr duid = host->getDuid();
+
+    // Get all reservations for this host.
+    IPv6ResrvRange reservations = host->getIPv6Reservations();
+
+    // Check if there are any IPv6 reservations.
+    if (std::distance(reservations.first, reservations.second) == 0) {
+        // If there aren't, we don't need to add this to hosts6_, which is used
+        // for getting hosts by their IPv6 address reservations.
+        return;
+    }
+
+    // Now for each reservation, insert corresponding (address, host) tuple.
+    for (IPv6ResrvIterator it = reservations.first; it != reservations.second;
+         ++it) {
+
+        // If there's an entry for this (subnet-id, address), reject it.
+        if (get6(host->getIPv6SubnetID(), it->second.getPrefix())) {
+            isc_throw(DuplicateHost, "failed to add address reservation for "
+                      << "host using the HW address '"
+                      << (hwaddr ? hwaddr->toText(false) : "(null)")
+                      << " and DUID '" << (duid ? duid->toText() : "(null)")
+                      << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
+                      << "' for address/prefix " << it->second.getPrefix()
+                      << ": There's already reservation for this address/prefix");
+        }
+        hosts6_.insert(HostResrv6Tuple(it->second, host));
+    }
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 108 - 1
src/lib/dhcpsrv/cfg_hosts.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -95,6 +95,28 @@ public:
     virtual HostCollection
     getAll4(const asiolink::IOAddress& address);
 
+    /// @brief Returns a collection of hosts using the specified IPv6 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv6 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll6(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv6 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv6 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual HostCollection
+    getAll6(const asiolink::IOAddress& address);
+
     /// @brief Returns a host connected to the IPv4 subnet and matching
     /// specified identifiers.
     ///
@@ -183,6 +205,25 @@ public:
     virtual HostPtr
     get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len);
 
+    /// @brief Returns a host connected to the IPv6 subnet and having
+    /// a reservation for a specified IPv6 address.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address reserved IPv6 address.
+    ///
+    /// @return Const @c Host object using a specified IPv6 address.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet and having
+    /// a reservation for a specified IPv6 address.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address reserved IPv6 address.
+    ///
+    /// @return Const @c Host object using a specified IPv6 address.
+    virtual HostPtr
+    get6(const SubnetID& subnet_id, const asiolink::IOAddress& address);
 
     /// @brief Adds a new host to the collection.
     ///
@@ -241,6 +282,40 @@ private:
     void getAllInternal4(const asiolink::IOAddress& address,
                          Storage& storage) const;
 
+    /// @brief Returns @c Host objects for the specified IPv6 address.
+    ///
+    /// This private method is called by the @c CfgHosts::getAll6 methods
+    /// to retrieve the @c Host for which the specified IPv6 address is
+    /// reserved. The retrieved objects are appended to the @c storage
+    /// container.
+    ///
+    /// @param address IPv6 address.
+    /// @param [out] storage Container to which the retrieved objects are
+    /// appended.
+    /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+    template<typename Storage>
+    void getAllInternal6(const asiolink::IOAddress& address,
+                         Storage& storage) const;
+
+
+    /// @brief Returns @c Host objects for the specified (Subnet-id,IPv6 address) tuple.
+    ///
+    /// This private method is called by the @c CfgHosts::getAll6 methods
+    /// to retrieve the @c Host for which the specified IPv6 address is
+    /// reserved and is in specified subnet-id. The retrieved objects are
+    /// appended to the @c storage container.
+    ///
+    /// @param subnet_id Subnet Identifier.
+    /// @param address IPv6 address.
+    /// @param [out] storage Container to which the retrieved objects are
+    /// appended.
+    /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+    template<typename Storage>
+    void
+    getAllInternal6(const SubnetID& subnet_id,
+                    const asiolink::IOAddress& address,
+                    Storage& storage) const;
+
     /// @brief Returns @c Host object connected to a subnet.
     ///
     /// This private method returns a pointer to the @c Host object identified
@@ -260,9 +335,41 @@ private:
                             const HWAddrPtr& hwaddr,
                             const DuidPtr& duid) const;
 
+    /// @brief Adds a new host to the v4 collection.
+    ///
+    /// This is an internal method called by public @ref add.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    ///
+    /// @throw DuplicateHost If a host for a particular HW address or DUID
+    /// has already been added to the IPv4 subnet.
+    virtual void add4(const HostPtr& host);
+
+    /// @brief Adds a new host to the v6 collection.
+    ///
+    /// This is an internal method called by public @ref add.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    ///
+    /// @throw DuplicateHost If a host for a particular HW address or DUID
+    /// or for the particular address or prefix has already been added to
+    /// the IPv6 subnet.
+    virtual void add6(const HostPtr& host);
+
     /// @brief Multi-index container holding @c Host objects.
+    ///
+    /// It can be used for finding hosts by the following criteria:
+    /// - IPv4 address
+    /// - DUID
+    /// - HW/MAC address
     HostContainer hosts_;
 
+    /// @brief Multi-index container holding @c Host objects with v6 reservations.
+    ///
+    /// It can be used for finding hosts by the following criteria:
+    /// - IPv6 address
+    /// - IPv6 prefix
+    HostContainer6 hosts6_;
 };
 
 /// @name Pointers to the @c CfgHosts objects.

+ 7 - 1
src/lib/dhcpsrv/host.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -194,6 +194,12 @@ Host::getIPv6Reservations(const IPv6Resrv::Type& type) const {
     return (ipv6_reservations_.equal_range(type));
 }
 
+IPv6ResrvRange
+Host::getIPv6Reservations() const {
+    return (IPv6ResrvRange(ipv6_reservations_.begin(),
+                           ipv6_reservations_.end()));
+}
+
 bool
 Host::hasIPv6Reservation() const {
     return (!ipv6_reservations_.empty());

+ 7 - 1
src/lib/dhcpsrv/host.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -352,6 +352,12 @@ public:
     /// the specified type.
     IPv6ResrvRange getIPv6Reservations(const IPv6Resrv::Type& type) const;
 
+    /// @brief Returns all IPv6 reservations.
+    ///
+    /// @return A range of iterators pointing to the reservations of
+    /// the specified type.
+    IPv6ResrvRange getIPv6Reservations() const;
+
     /// @brief Checks if there is at least one IPv6 reservation for this host.
     ///
     /// @return true if there is a reservation for the host, false otherwise.

+ 106 - 3
src/lib/dhcpsrv/host_container.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -16,10 +16,13 @@
 #define HOST_CONTAINER_H
 
 #include <dhcpsrv/host.h>
+#include <dhcpsrv/subnet_id.h>
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/composite_key.hpp>
 #include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/member.hpp>
 #include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/hashed_index.hpp>
 
 namespace isc {
 namespace dhcp {
@@ -96,7 +99,107 @@ typedef HostContainer::nth_index<1>::type HostContainerIndex1;
 typedef std::pair<HostContainerIndex1::iterator,
                   HostContainerIndex1::iterator> HostContainerIndex1Range;
 
-}
-}
+/// @brief Defines one entry for the Host Container for v6 hosts
+///
+/// It's essentially a pair of (IPv6 reservation, Host pointer).
+/// This structure is used as an intermediate structure in HostContainer6.
+/// For a single host that has reservations for X addresses or prefixes, there
+/// will be X HostResrv6Tuple structures.
+struct HostResrv6Tuple {
+
+    /// @brief Default constructor.
+    ///
+    /// @param resrv IPv6 address/prefix reservation
+    /// @param host pointer to the host object
+    HostResrv6Tuple(const IPv6Resrv& resrv, const HostPtr& host)
+    :resrv_(resrv), host_(host), subnet_id_(host ? host->getIPv6SubnetID() : 0) {
+    }
+
+    /// @brief Address or prefix reservation.
+    const IPv6Resrv resrv_;
+
+    /// @brief Pointer to the host object.
+    HostPtr host_;
+
+    /// @brief Value of the IPv6 Subnet-id
+    const SubnetID subnet_id_;
+
+    /// @brief Key extractor (used in the second composite key)
+    const asiolink::IOAddress& getKey() const {
+        return (resrv_.getPrefix());
+    }
+};
+
+/// @brief Multi-index container holding IPv6 reservations.
+///
+/// This container holds HostResrv6Tuples, i.e. pairs of (IPv6Resrv, HostPtr)
+/// pieces of information. This is needed for efficiently finding a host
+/// for a given IPv6 address or prefix.
+typedef boost::multi_index_container<
+
+    // This containers stores (IPv6Resrv, HostPtr) tuples
+    HostResrv6Tuple,
+
+    // Start specification of indexes here.
+    boost::multi_index::indexed_by<
+
+        // First index is used to search by an address.
+        boost::multi_index::ordered_non_unique<
+
+            // Address is extracted by calling IPv6Resrv::getPrefix()
+            // and it will return an IOAddress object.
+            boost::multi_index::const_mem_fun<
+                HostResrv6Tuple, const asiolink::IOAddress&, &HostResrv6Tuple::getKey>
+        >,
+
+        // Second index is used to search by (subnet_id, address) pair.
+        // This is
+        boost::multi_index::ordered_unique<
+
+            /// This is a composite key. It uses two keys: subnet-id and
+            /// IPv6 address reservation.
+            boost::multi_index::composite_key<
+
+                // Composite key uses members of the HostResrv6Tuple class.
+                HostResrv6Tuple,
+
+                // First key extractor. Gets subnet-id as a member of the
+                // HostResrv6Tuple structure.
+                boost::multi_index::member<HostResrv6Tuple, const SubnetID,
+                    &HostResrv6Tuple::subnet_id_>,
+
+                // Second key extractor. Address is extracted by calling
+                // IPv6Resrv::getPrefix() and it will return an IOAddress object.
+                boost::multi_index::const_mem_fun<
+                    HostResrv6Tuple, const asiolink::IOAddress&,
+                    &HostResrv6Tuple::getKey
+                >
+           >
+        >
+    >
+> HostContainer6;
+
+/// @brief First index type in the @c HostContainer6.
+///
+/// This index allows for searching for @c Host objects using an
+/// address.
+typedef HostContainer6::nth_index<0>::type HostContainer6Index0;
+
+/// @brief Results range returned using the @c HostContainer6Index0.
+typedef std::pair<HostContainer6Index0::iterator,
+                  HostContainer6Index0::iterator> HostContainer6Index0Range;
+
+/// @brief Second index type in the @c HostContainer6.
+///
+/// This index allows for searching for @c Host objects using a
+/// reserved (SubnetID, IPv6 address) tuple.
+typedef HostContainer6::nth_index<1>::type HostContainer6Index1;
+
+/// @brief Results range returned using the @c HostContainer6Index1.
+typedef std::pair<HostContainer6Index1::iterator,
+                  HostContainer6Index1::iterator> HostContainer6Index1Range;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
 
 #endif // HOST_CONTAINER_H

+ 12 - 1
src/lib/dhcpsrv/host_mgr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -118,6 +118,17 @@ HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
     return (host);
 }
 
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id,
+              const asiolink::IOAddress& addr) const {
+    ConstHostPtr host = getCfgHosts()->get6(subnet_id, addr);
+    if (!host && alternate_source) {
+        host = alternate_source->get6(subnet_id, addr);
+    }
+    return (host);
+}
+
+
 void
 HostMgr::add(const HostPtr&) {
     isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");

+ 10 - 1
src/lib/dhcpsrv/host_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -181,6 +181,15 @@ public:
     virtual ConstHostPtr
     get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
 
+    /// @brief Returns a host from specific subnet and reserved address.
+    ///
+    /// @param subnet_id subnet identfier.
+    /// @param addr specified address.
+    ///
+    /// @return Const @c host object that has a reservation for specified address.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const asiolink::IOAddress& addr) const;
+
     /// @brief Adds a new host to the alternate data source.
     ///
     /// This method will throw an exception if no alternate data source is

+ 36 - 0
src/lib/dhcpsrv/subnet.h

@@ -66,6 +66,27 @@ public:
         isc::asiolink::IOAddress addr_;
     };
 
+    /// @brief Specifies allowed host reservation mode.
+    ///
+    typedef enum  {
+
+        /// None - host reservation is disabled. No reservation types
+        /// are allowed.
+        HR_DISABLED,
+
+        /// Only out-of-pool reservations is allowed. This mode
+        /// allows AllocEngine to skip reservation checks when
+        /// dealing with with addresses that are in pool.
+        HR_OUT_OF_POOL,
+
+        /// Both out-of-pool and in-pool reservations are allowed. This is the
+        /// most flexible mode, where sysadmin have biggest liberty. However,
+        /// there is a non-trivial performance penalty for it, as the
+        /// AllocEngine code has to check whether there are reservations, even
+        /// when dealing with reservations from within the dynamic pools.
+        HR_IN_POOL
+    } HRMode;
+
     /// Pointer to the RelayInfo structure
     typedef boost::shared_ptr<Subnet::RelayInfo> RelayInfoPtr;
 
@@ -296,6 +317,21 @@ public:
     void
     allowClientClass(const isc::dhcp::ClientClass& class_name);
 
+    /// @brief Specifies what type of Host Reservations are supported.
+    ///
+    /// Host reservations may be either in-pool (they reserve an address that
+    /// is in the dynamic pool) or out-of-pool (they reserve an address that is
+    /// not in the dynamic pool). HR may also be completely disabled for
+    /// performance reasons.
+    ///
+    /// @todo: implement this.
+    ///
+    /// @return whether in-pool host reservations are allowed.
+    HRMode
+    getHostReservationMode() {
+        return (Subnet::HR_IN_POOL);
+    }
+
 protected:
     /// @brief Returns all pools (non-const variant)
     ///

+ 571 - 62
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc

@@ -172,13 +172,26 @@ public:
     /// @param lease lease to be checked
     /// @param exp_type expected lease type
     /// @param exp_pd_len expected prefix length
+    /// @param expected_in_subnet whether the lease is expected to be in subnet
+    /// @param expected_in_pool whether the lease is expected to be in dynamic
     void checkLease6(const Lease6Ptr& lease, Lease::Type exp_type,
-                     uint8_t exp_pd_len = 128) {
+                     uint8_t exp_pd_len = 128, bool expected_in_subnet = true,
+                     bool expected_in_pool = true) {
 
         // that is belongs to the right subnet
         EXPECT_EQ(lease->subnet_id_, subnet_->getID());
-        EXPECT_TRUE(subnet_->inRange(lease->addr_));
-        EXPECT_TRUE(subnet_->inPool(exp_type, lease->addr_));
+
+        if (expected_in_subnet) {
+            EXPECT_TRUE(subnet_->inRange(lease->addr_));
+        } else {
+            EXPECT_FALSE(subnet_->inRange(lease->addr_));
+        }
+
+        if (expected_in_pool) {
+            EXPECT_TRUE(subnet_->inPool(exp_type, lease->addr_));
+        } else {
+            EXPECT_FALSE(subnet_->inPool(exp_type, lease->addr_));
+        }
 
         // that it have proper parameters
         EXPECT_EQ(exp_type, lease->type_);
@@ -231,9 +244,10 @@ public:
     /// @param pool pool from which the lease will be allocated from
     /// @param hint address to be used as a hint
     /// @param fake true - this is fake allocation (SOLICIT)
+    /// @param in_pool specifies whether the lease is expected to be in pool
     /// @return allocated lease (or NULL)
     Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool, const IOAddress& hint,
-                               bool fake) {
+                               bool fake, bool in_pool = true) {
         Lease::Type type = pool->getType();
         uint8_t expected_len = pool->getLength();
 
@@ -247,9 +261,10 @@ public:
         }
 
         Lease6Ptr lease;
-        EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                        duid_, iaid_, hint, type, false, false,
-                        "", fake, CalloutHandlePtr(), old_leases_, HWAddrPtr())));
+        AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type,
+                                        false, false, "", fake);
+
+        EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
 
         // Check that we got a lease
         EXPECT_TRUE(lease);
@@ -258,7 +273,7 @@ public:
         }
 
         // Do all checks on the lease
-        checkLease6(lease, type, expected_len);
+        checkLease6(lease, type, expected_len, in_pool, in_pool);
 
         // Check that the lease is indeed in LeaseMgr
         Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(type,
@@ -311,9 +326,9 @@ public:
         // unfortunately it is used already. The same address must not be allocated
         // twice.
         Lease6Ptr lease;
-        EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                        duid_, iaid_, requested, type, false, false, "", false,
-                        CalloutHandlePtr(), old_leases_, HWAddrPtr())));
+        AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, requested, type,
+                                        false, false, "", false);
+        EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
 
         // Check that we got a lease
         ASSERT_TRUE(lease);
@@ -355,10 +370,9 @@ public:
         // supported lease. Allocation engine should ignore it and carry on
         // with the normal allocation
         Lease6Ptr lease;
-        EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                        duid_, iaid_, hint, type, false,
-                        false, "", false, CalloutHandlePtr(), old_leases_,
-                        HWAddrPtr())));
+        AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type, false,
+                                        false, "", false);
+        EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
 
         // Check that we got a lease
         ASSERT_TRUE(lease);
@@ -369,19 +383,37 @@ public:
         // Do all checks on the lease
         checkLease6(lease, type, expected_pd_len);
 
-    // Check that the lease is indeed in LeaseMgr
-    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+        // Check that the lease is indeed in LeaseMgr
+        Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
                                                                lease->addr_);
-    ASSERT_TRUE(from_mgr);
-
-    // Now check that the lease in LeaseMgr has the same parameters
-    detailCompareLease(lease, from_mgr);
-
-
-
+        ASSERT_TRUE(from_mgr);
 
+        // Now check that the lease in LeaseMgr has the same parameters
+        detailCompareLease(lease, from_mgr);
     }
 
+    /// @brief Utility function that creates a host reservation
+    ///
+    /// @param add_to_host_mgr true if the reservation should be added
+    /// @param type specifies reservation type
+    /// @param addr specifies reserved address or prefix
+    /// @param prefix_len prefix length (should be 128 for addresses)
+    /// @return created Host object.
+    HostPtr
+    createHost6(bool add_to_host_mgr, IPv6Resrv::Type type,
+                const asiolink::IOAddress& addr, uint8_t prefix_len) {
+        HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+                              Host::IDENT_DUID, SubnetID(0), subnet_->getID(),
+                              IOAddress("0.0.0.0")));
+        IPv6Resrv resv(type, addr, prefix_len);
+        host->addReservation(resv);
+
+        if (add_to_host_mgr) {
+            CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+            CfgMgr::instance().commit();
+        }
+        return (host);
+    }
 
     virtual ~AllocEngine6Test() {
         factory_.destroy();
@@ -396,10 +428,6 @@ public:
     bool fqdn_fwd_;           ///< Perform forward update for a lease.
     bool fqdn_rev_;           ///< Perform reverse update for a lease.
     LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
-
-    /// @brief Collection of leases being replaced by newly allocated or renewed
-    /// leases.
-    Lease6Collection old_leases_;
 };
 
 /// @brief Used in Allocation Engine tests for IPv4
@@ -552,6 +580,7 @@ TEST_F(AllocEngine6Test, allocWithValidHint6) {
 
     Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::15"),
                                        false);
+    ASSERT_TRUE(lease);
 
     // We should get what we asked for
     EXPECT_EQ("2001:db8:1::15", lease->addr_.toText());
@@ -597,17 +626,15 @@ TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
 
     // Allocations without subnet are not allowed
     Lease6Ptr lease;
-    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(
-                    Subnet6Ptr(), duid_, iaid_, IOAddress("::"), Lease::TYPE_NA,
-                    false, false, "", false, CalloutHandlePtr(), old_leases_,
-                    HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx1(Subnet6Ptr(), duid_, iaid_, IOAddress("::"),
+                                     Lease::TYPE_NA, false, false, "", false);
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx1)));
     ASSERT_FALSE(lease);
 
     // Allocations without DUID are not allowed either
-    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                    DuidPtr(), iaid_, IOAddress("::"), Lease::TYPE_NA, false,
-                    false, "", false, CalloutHandlePtr(), old_leases_,
-                    HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx2(subnet_, DuidPtr(), iaid_, IOAddress("::"),
+                                     Lease::TYPE_NA, false, false, "", false);
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
     ASSERT_FALSE(lease);
 }
 
@@ -853,10 +880,10 @@ TEST_F(AllocEngine6Test, smallPool6) {
     initFqdn("myhost.example.com", true, true);
 
     Lease6Ptr lease;
-    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                    duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, fqdn_fwd_,
-                    fqdn_rev_, hostname_, false, CalloutHandlePtr(),
-                    old_leases_, HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    Lease::TYPE_NA, fqdn_fwd_, fqdn_rev_,
+                                    hostname_, false);
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
 
     // Check that we got that single lease
     ASSERT_TRUE(lease);
@@ -876,8 +903,7 @@ TEST_F(AllocEngine6Test, smallPool6) {
 
     // This is a new lease allocation. The old lease corresponding to a newly
     // allocated lease should be NULL.
-    ASSERT_EQ(1, old_leases_.size());
-    EXPECT_FALSE(old_leases_[0]);
+    ASSERT_TRUE(ctx.old_leases_.empty());
 }
 
 // This test checks if all addresses in a pool are currently used, the attempt
@@ -909,9 +935,9 @@ TEST_F(AllocEngine6Test, outOfAddresses6) {
     // There is just a single address in the pool and allocated it to someone
     // else, so the allocation should fail
     Lease6Ptr lease2;
-    EXPECT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(subnet_,
-                    duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
-                    "", false, CalloutHandlePtr(), old_leases_, HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    Lease::TYPE_NA, false, false, "", false);
+    EXPECT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx)));
     EXPECT_FALSE(lease2);
 
 }
@@ -944,10 +970,9 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
     ASSERT_TRUE(lease->expired());
 
     // CASE 1: Asking for any address
-    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                    duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, fqdn_fwd_,
-                    fqdn_rev_, hostname_, true, CalloutHandlePtr(),
-                    old_leases_, HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx1(subnet_, duid_, iaid_, IOAddress("::"),
+                                     Lease::TYPE_NA, fqdn_fwd_, fqdn_rev_, hostname_, true);
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx1)));
     // Check that we got that single lease
     ASSERT_TRUE(lease);
     EXPECT_EQ(addr, lease->addr_);
@@ -956,9 +981,9 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
     checkLease6(lease, Lease::TYPE_NA, 128);
 
     // CASE 2: Asking specifically for this address
-    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                    duid_, iaid_, addr, Lease::TYPE_NA, false, false, "",
-                    true, CalloutHandlePtr(), old_leases_, HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx2(subnet_, duid_, iaid_, addr, Lease::TYPE_NA,
+                                     false, false, "", true);
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
 
     // Check that we got that single lease
     ASSERT_TRUE(lease);
@@ -997,9 +1022,9 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
     ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
 
     // A client comes along, asking specifically for this address
-    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                    duid_, iaid_, addr, Lease::TYPE_NA, false, false, "",
-                    false, CalloutHandlePtr(), old_leases_, HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, addr, Lease::TYPE_NA,
+                                    false, false, "", false);
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
 
     // Check that he got that single lease
     ASSERT_TRUE(lease);
@@ -1010,7 +1035,7 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
     EXPECT_FALSE(lease->fqdn_rev_);
 
     // Check that the old lease has been returned.
-    Lease6Ptr old_lease = expectOneLease(old_leases_);
+    Lease6Ptr old_lease = expectOneLease(ctx.old_leases_);
     // It should at least have the same IPv6 address.
     EXPECT_EQ(lease->addr_, old_lease->addr_);
     // Check that it carries not updated FQDN data.
@@ -1027,6 +1052,488 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
     detailCompareLease(lease, from_mgr);
 }
 
+// --- v6 host reservation ---
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitNoHint) {
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressInPoolRequestNoHint) {
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitValidHint) {
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressInPoolRequestValidHint) {
+    // Create reservation for the client This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does matches reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitMatchingHint) {
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), true);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressInPoolRequestMatchingHint) {
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), false);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (out-of-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitNoHint) {
+    // Create reservation for the client. This is out-of-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true, false);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestNoHint) {
+    // Create reservation for the client. This is out-of-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false, false);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitValidHint) {
+    // Create reservation for the client. This is out-of-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true, false);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestValidHint) {
+    // Create reservation for the client. This is out-of-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false, false);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does matches reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitMatchingHint) {
+    // Create reservation for the client. This is out-of-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), true, false);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestMatchingHint) {
+    // Create reservation for the client. This is out-of-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Let's pretend the client sends hint 2001:db8:1::10.
+    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), false, false);
+    ASSERT_TRUE(lease);
+
+    // The hint should be ignored and the reserved address should be assigned
+    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+}
+
+// In the following situation:
+// - client is assigned an address A
+// - HR is made for *this* client to get B
+// - client tries to get address A:
+//    Check that his existing lease for lease A is removed
+//    Check that he is assigned a new lease for B
+TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedThis) {
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Client gets an address
+    Lease6Ptr lease1 = simpleAlloc6Test(pool_, IOAddress("::"), false);
+    ASSERT_TRUE(lease1);
+
+    // Just check that if the client requests again, it will get the same
+    // address.
+    Lease6Ptr lease2 = simpleAlloc6Test(pool_, lease1->addr_, false);
+    ASSERT_TRUE(lease2);
+    detailCompareLease(lease1, lease2);
+
+    // Now admin creates a reservation for this client. This is in-pool
+    // reservation, as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+    // Just check that this time the client will get.
+    Lease6Ptr lease3 = simpleAlloc6Test(pool_, lease1->addr_, false);
+    ASSERT_TRUE(lease3);
+
+    // Check that previous lease was not used anymore.
+    EXPECT_NE(lease1->addr_.toText(), lease3->addr_.toText());
+
+    // Check that the reserved address was indeed assigned.
+    EXPECT_EQ("2001:db8:1::1c", lease3->addr_.toText());
+
+    // Check that the old lease is gone.
+    Lease6Ptr old = LeaseMgrFactory::instance().getLease6(lease1->type_,
+                                                          lease1->addr_);
+    EXPECT_FALSE(old);
+
+    // Check that the reserved lease is in the database.
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease1->type_,
+                                                  IOAddress("2001:db8:1::1c"));
+
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease3, from_mgr);
+}
+
+// In the following situation:
+// - client X is assigned an address A
+// - HR is made for client Y (*other* client) to get A
+// - client X tries to get address A:
+//    Check that his existing lease for lease A is removed
+//    Check that he is assigned a new lease
+TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedOther) {
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Client gets an address
+    Lease6Ptr lease1 = simpleAlloc6Test(pool_, IOAddress("::"), false);
+    ASSERT_TRUE(lease1);
+
+    // Just check that if the client requests again, it will get the same
+    // address.
+    Lease6Ptr lease2 = simpleAlloc6Test(pool_, lease1->addr_, false);
+    ASSERT_TRUE(lease2);
+    detailCompareLease(lease1, lease2);
+
+    // Now admin creates a reservation for this client. Let's use the
+    // address client X just received. Let's generate a host, but don't add it
+    // to the HostMgr yet.
+    HostPtr host = createHost6(false, IPv6Resrv::TYPE_NA, lease1->addr_, 128);
+
+    // We need to tweak reservation id: use a different DUID for client Y
+    vector<uint8_t> other_duid(8, 0x45);
+    host->setIdentifier(&other_duid[0], other_duid.size(), Host::IDENT_DUID);
+
+    // Ok, now add it to the HostMgr
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Just check that this time the client will get a different lease.
+    Lease6Ptr lease3 = simpleAlloc6Test(pool_, lease1->addr_, false);
+    ASSERT_TRUE(lease3);
+
+    // Check that previous lease was not used anymore.
+    EXPECT_NE(lease1->addr_.toText(), lease3->addr_.toText());
+
+    // Check that the old lease is gone.
+    Lease6Ptr old = LeaseMgrFactory::instance().getLease6(lease1->type_,
+                                                          lease1->addr_);
+    EXPECT_FALSE(old);
+
+    // Check that the reserved lease is in the database.
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease1->type_,
+                                                               lease3->addr_);
+
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease3, from_mgr);
+}
+
+// Checks that a reserved address for client A is not assigned when
+// other clients are requesting addresses. The scenario is as follows:
+// we use a regular pool with 17 addresses in it. One of them is
+// reserved for client A. Now we try to allocate addresses for 30 clients
+// (A is not one of them). The first 16 attempts should succeed. Then
+// we run out of addresses and remaining 14 clients will get nothing.
+// Finally, we check that client A still can get his reserved address.
+TEST_F(AllocEngine6Test, reservedAddress) {
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, true);
+
+    // Create reservation for the client. This is in-pool reservation,
+    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::12"), 128);
+
+    // Let's generate 30 DUIDs, each of them 16 bytes long
+    vector<DuidPtr> clients;
+    for (int i = 0; i < 30; i++) {
+        vector<uint8_t> data(16, i);
+        clients.push_back(DuidPtr(new DUID(data)));
+    }
+
+    // The default pool is 2001:db8:1::10 to 2001:db8:1::20. There's 17
+    // addresses in it. One of them is reserved, so this means that we should
+    // get 16 successes and 14 (20-16) failures.
+    int success = 0;
+    int failure = 0;
+    for (int i = 0; i < 30; i++) {
+        AllocEngine::ClientContext6 ctx(subnet_, clients[i], iaid_, IOAddress("::"),
+                                        Lease::TYPE_NA,  false, false, "", false);
+        Lease6Collection leases = engine.allocateLeases6(ctx);
+        if (leases.empty()) {
+            failure++;
+            std::cout << "Alloc for client " << (int)i << " failed." << std::endl;
+        } else {
+            success++;
+            std::cout << "Alloc for client " << (int)i << " succeeded:"
+                      << leases[0]->addr_.toText() << std::endl;
+
+            // The assigned addresses must not match the one reserved.
+            EXPECT_NE("2001:db8:1::12", leases[0]->addr_.toText());
+        }
+    }
+
+    EXPECT_EQ(16, success);
+    EXPECT_EQ(14, failure);
+
+    // We're now pretty sure that any clients other than the reserved address
+    // will not get any service. Now let's check if the client that has the
+    // address reserved, will get it (despite the pool being depleted).
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    Lease::TYPE_NA,  false, false, "", false);
+    Lease6Collection leases = engine.allocateLeases6(ctx);
+    ASSERT_EQ(1, leases.size());
+    EXPECT_EQ("2001:db8:1::12", leases[0]->addr_.toText());
+}
+
+// Checks if the allocateLeases throws exceptions for invalid input data.
+TEST_F(AllocEngine6Test, allocateLeasesInvalidData) {
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, true);
+
+    // That looks like a valid context.
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    Lease::TYPE_NA,  false, false, "", false);
+    Lease6Collection leases;
+
+    // Let's break it!
+    ctx.subnet_.reset();
+
+    // Subnet is required for allocation, so we should get no leases.
+    EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
+    EXPECT_TRUE(leases.empty());
+
+    // Let's fix this and break it in a different way.
+    ctx.subnet_ = subnet_;
+    ctx.duid_.reset();
+
+    // We must know who we're allocating for. No duid = no service.
+    EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
+    EXPECT_TRUE(leases.empty());
+
+}
+
+/// @todo: The following methods are tested indirectly by allocateLeases6()
+/// tests, but could use more direct testing:
+/// - AllocEngine::allocateUnreservedLeases6
+/// - AllocEngine::allocateReservedLeases6
+/// - AllocEngine::removeNonmatchingReservedLeases6
+/// - AllocEngine::removeLeases
+/// - AllocEngine::removeNonreservedLeases6
+
 // --- IPv4 ---
 
 // This test checks if the v4 Allocation Engine can be instantiated, parses
@@ -2438,9 +2945,10 @@ TEST_F(HookAllocEngine6Test, lease6_select) {
     CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
 
     Lease6Ptr lease;
-    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                    duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
-                    "", false, callout_handle, old_leases_, HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    Lease::TYPE_NA, false, false, "", false);
+    ctx.callout_handle_ = callout_handle;
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
     // Check that we got a lease
     ASSERT_TRUE(lease);
 
@@ -2509,9 +3017,10 @@ TEST_F(HookAllocEngine6Test, change_lease6_select) {
 
     // Call allocateLeases6. Callouts should be triggered here.
     Lease6Ptr lease;
-    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
-                    duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
-                    "", false, callout_handle, old_leases_, HWAddrPtr())));
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    Lease::TYPE_NA, false, false, "", false);
+    ctx.callout_handle_ = callout_handle;
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
     // Check that we got a lease
     ASSERT_TRUE(lease);
 

+ 111 - 1
src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@
 #include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/host.h>
 #include <gtest/gtest.h>
+#include <sstream>
 #include <set>
 
 using namespace isc;
@@ -322,6 +323,115 @@ TEST_F(CfgHostsTest, get6) {
     EXPECT_THROW(cfg.get6(SubnetID(1), duids_[0], hwaddrs_[0]), DuplicateHost);
 }
 
+// This test checks that the IPv6 reservations can be retrieved for a particular
+// (subnet-id, address) tuple.
+TEST_F(CfgHostsTest, get6ByAddr) {
+    CfgHosts cfg;
+    // Add hosts.
+    for (int i = 0; i < 25; ++i) {
+
+        // Add host identified by DUID.
+        HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+                                        SubnetID(0), SubnetID(1 + i % 2),
+                                        IOAddress("0.0.0.0")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       increase(IOAddress("2001:db8:2::1"),
+                                                i)));
+        cfg.add(host);
+    }
+
+    for (int i = 0; i < 25; ++i) {
+        // Retrieve host by (subnet-id,address).
+        HostPtr host = cfg.get6(SubnetID(1 + i % 2),
+                                increase(IOAddress("2001:db8:2::1"), i));
+        ASSERT_TRUE(host);
+
+        EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+        IPv6ResrvRange reservations =
+            host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+        ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+        EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+                  reservations.first->second.getPrefix());
+    }
+}
+
+// This test checks that the IPv6 reservations can be retrieved for a particular
+// (subnet-id, address) tuple.
+TEST_F(CfgHostsTest, get6MultipleAddrs) {
+    CfgHosts cfg;
+
+    // Add 25 hosts. Each host has reservations for 5 addresses.
+    for (int i = 0; i < 25; ++i) {
+
+        // Add host identified by DUID.
+        HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+                                        SubnetID(0), SubnetID(1 + i % 2),
+                                        IOAddress("0.0.0.0")));
+
+        // Generate 5 unique addresses for this host.
+        for (int j = 0; j < 5; ++j) {
+            std::stringstream tmp;
+            tmp << "2001:db8:" << i << "::" << j;
+            host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, tmp.str()));
+        }
+        cfg.add(host);
+    }
+
+    // We don't care about HW/MAC addresses for now.
+    HWAddrPtr hwaddr_not_used;
+
+    // Now check if we can retrieve each of those 25 hosts by using each
+    // of their addresses.
+    for (int i = 0; i < 25; ++i) {
+
+        // Check that the host is there.
+        HostPtr by_duid = cfg.get6(SubnetID(1 + i % 2), duids_[i], hwaddr_not_used);
+        ASSERT_TRUE(by_duid);
+
+        for (int j = 0; j < 5; ++j) {
+            std::stringstream tmp;
+            tmp << "2001:db8:" << i << "::" << j;
+
+            // Retrieve host by (subnet-id,address).
+            HostPtr by_addr = cfg.get6(SubnetID(1 + i % 2), tmp.str());
+            ASSERT_TRUE(by_addr);
+
+            // The pointers should match. Maybe we should compare contents
+            // rather than just pointers? I think there's no reason why
+            // the code would make any copies of the Host object, so
+            // the pointers should always point to the same object.
+            EXPECT_EQ(by_duid, by_addr);
+        }
+    }
+}
+
+
+// Checks that it's not possible for two hosts to have the same address
+// reserved at the same time.
+TEST_F(CfgHostsTest, add6Invalid2Hosts) {
+    CfgHosts cfg;
+
+    // First host has a reservation for address 2001:db8::1
+    HostPtr host1 = HostPtr(new Host(duids_[0]->toText(), "duid",
+                                     SubnetID(0), SubnetID(1),
+                                     IOAddress("0.0.0.0")));
+    host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                    IOAddress("2001:db8::1")));
+    // Adding this should work.
+    EXPECT_NO_THROW(cfg.add(host1));
+
+    // The second host has a reservation for the same address.
+    HostPtr host2 = HostPtr(new Host(duids_[1]->toText(), "duid",
+                                     SubnetID(0), SubnetID(1),
+                                     IOAddress("0.0.0.0")));
+    host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                    IOAddress("2001:db8::1")));
+
+    // This second host has a reservation for an address that is already
+    // reserved for the first host, so it should be rejected.
+    EXPECT_THROW(cfg.add(host2), isc::dhcp::DuplicateHost);
+}
+
 TEST_F(CfgHostsTest, zeroSubnetIDs) {
     CfgHosts cfg;
     ASSERT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),