Browse Source

[master] Merge branch 'trac2324' (DHCPv6 allocation engine)

Conflicts:
	ChangeLog
	src/lib/dhcp/subnet.cc
	src/lib/dhcp/subnet.h
	src/lib/dhcp/tests/subnet_unittest.cc
Tomek Mrugalski 12 years ago
parent
commit
869e658fbc

+ 7 - 0
ChangeLog

@@ -1,3 +1,10 @@
+496.	[func]		tomek
+	DHCPv6 Allocation Engine implemented. It allows address allocation
+	from the configured subnets/pools. It currently features a single
+	allocator: IterativeAllocator, which assigns addresses iteratively.
+	Other allocators (hashed, random) are planned.
+	(Trac #2324, git 8aa188a10298e3a55b725db36502a99d2a8d638a)
+
 495.	[func]		team
 	b10-auth now handles reconfiguration of data sources in
 	background using a separate thread.  This means even if the new

+ 0 - 72
doc/devel/02-dhcp.dox

@@ -57,76 +57,4 @@
  * that does not support msgq. That is useful for embedded environments.
  * It may also be useful in validation.
  *
- * @page libdhcp libdhcp++
- *
- * @section libdhcpIntro Libdhcp++ Library Introduction
- *
- * libdhcp++ is an all-purpose DHCP-manipulation library, written in
- * C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6
- * options parsing and ssembly, interface detection (currently on
- * Linux systems only) and socket operations. Following classes are
- * implemented:
- *
- * - isc::dhcp::Pkt4 - represents DHCPv4 packet.
- * - isc::dhcp::Pkt6 - represents DHCPv6 packet.
- *
- * There are two pointer types defined: Pkt4Ptr and Pkt6Ptr. They are
- * smart pointer and are using boost::shared_ptr. There are not const
- * versions defined, as we assume that hooks can modify any aspect of
- * the packet at almost any stage of processing.
- *
- * Both packets use collection of Option objects to represent DHCPv4
- * and DHCPv6 options. The base class -- Option -- can be used to
- * represent generic option that contains collection of
- * bytes. Depending on if the option is instantiated as v4 or v6
- * option, it will adjust its header (DHCPv4 options use 1 octet for
- * type and 1 octet for length, while DHCPv6 options use 2 bytes for
- * each).
- *
- * There are many specialized classes that are intended to handle options with
- * specific content:
- * - isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses;
- * - isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses;
- * - isc::dhcp::Option6IAAddr -- DHCPv6 option, represents IAADDR_OPTION (an option that
- *                     contains IPv6 address with extra parameters);
- * - isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions.
- *
- * All options can store sub-options (i.e. options that are stored within option
- * rather than in a message directly). This functionality is commonly used in
- * DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
- * isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
- * for that purpose.
- *
- * @section libdhcpIfaceMgr Interface Manager
- *
- * Interface Manager (or IfaceMgr) is an abstraction layer about low-level
- * network operations. In particlar, it provides information about existing
- * network interfaces See isc::dhcp::IfaceMgr::Iface class and
- * isc::dhcp::IfaceMgr::detectIfaces() and isc::dhcp::IfaceMgr::getIface().
- *
- * Currently there is interface detection is implemented in Linux only. There
- * are plans to implement such support for other OSes, but they remain low
- * priority for now.
- *
- * Generic parts of the code are isc::dhcp::IfaceMgr class in
- * src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate
- * files, e.g. iface_mgr_linux.cc. Such separation should be maintained when
- * additional code will be developed.
- *
- * For systems that interface detection is not supported on, there is a stub
- * mechanism implemented. It assumes that interface name is read from a text
- * file. This is a temporary solution and will be removed as soon as proper
- * interface detection is implemented. It is not going to be developed further.
- * To use this feature, store interfaces.txt file. It uses a simple syntax.
- * Each line represents an interface name, followed by IPv4 or IPv6 address
- * that follows it. This is usually link-local IPv6 address that the server
- * should bind to. In theory this mechanism also supports IPv4, but it was
- * never tested. The code currently supports only a single interface defined
- * that way.
- *
- * Another useful methods are dedicated to transmission
- * (isc::dhcp::IfaceMgr::send(), 2 overloads) and reception
- * (isc::dhcp::IfaceMgr::receive4() and isc::dhcp::IfaceMgr::receive6()).
- * Note that receive4() and receive6() methods may return NULL, e.g.
- * when timeout is reached or if dhcp daemon receives a signal.
  */

+ 4 - 0
doc/devel/mainpage.dox

@@ -29,6 +29,10 @@
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIfaceMgr
+ * - @subpage libdhcpsrv
+ *   - @subpage leasemgr
+ *   - @subpage cfgmgr
+ *   - @subpage allocengine
  * - @subpage perfdhcpInternals
  *
  * @section misc Miscellaneous topics

+ 3 - 3
src/bin/dhcp6/dhcp6.dox

@@ -35,7 +35,7 @@
 
  This method iterates over list of received configuration elements and creates a
  list of parsers for each received entry. Parser is an object that is derived
- from a \ref isc::dhcp::Dhcp6ConfigParser class. Once a parser is created
+ from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created
  (constructor), its value is set (using build() method). Once all parsers are
  build, the configuration is then applied ("commited") and commit() method is
  called.
@@ -51,7 +51,7 @@
 
  @section dhcpv6-config-inherit DHCPv6 Configuration Inheritance
 
- One notable useful features of DHCP configuration is its parameter inheritance.
+ One notable useful feature of DHCP configuration is its parameter inheritance.
  For example, renew-timer value may be specified at a global scope and it then
  applies to all subnets. However, some subnets may have it overwritten with more
  specific values that takes precedence over global values that are considered
@@ -64,7 +64,7 @@
  phase (commit() method), appropriate parsers can use apply parameter inheritance.
 
  Debugging configuration parser may be confusing. Therefore there is a special
- class called \ref isc::dhcp::DummyParser. It does not configure anything, but just
+ class called \ref isc::dhcp::DebugParser. It does not configure anything, but just
  accepts any parameter of any type. If requested to commit configuration, it will
  print out received parameter name and its value. This class is not currently used,
  but it is convenient to have it every time a new parameter is added to DHCP

+ 2 - 1
src/lib/dhcp/Makefile.am

@@ -16,7 +16,6 @@ CLEANFILES = *.gcno *.gcda
 lib_LTLIBRARIES = libb10-dhcp++.la libb10-dhcpsrv.la
 libb10_dhcp___la_SOURCES  =
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
-libb10_dhcp___la_SOURCES += lease_mgr.cc lease_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
@@ -39,7 +38,9 @@ libb10_dhcpsrv_la_SOURCES  = cfgmgr.cc cfgmgr.h
 libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
 libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libb10_dhcpsrv_la_SOURCES += triplet.h
+libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
+libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcpsrv_la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la

+ 272 - 0
src/lib/dhcp/alloc_engine.cc

@@ -0,0 +1,272 @@
+// Copyright (C) 2012 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <alloc_engine.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+AllocEngine::IterativeAllocator::IterativeAllocator()
+    :Allocator() {
+}
+
+isc::asiolink::IOAddress
+AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) {
+    uint8_t packed[V6ADDRESS_LEN];
+    int len;
+
+    // First we copy the whole address as 16 bytes.
+    if (addr.getFamily()==AF_INET) {
+        // IPv4
+        memcpy(packed, addr.getAddress().to_v4().to_bytes().data(), 4);
+        len = 4;
+    } else {
+        // IPv6
+        memcpy(packed, addr.getAddress().to_v6().to_bytes().data(), 16);
+        len = 16;
+    }
+
+    for (int i = len - 1; i >= 0; --i) {
+        ++packed[i];
+        if (packed[i] != 0) {
+            break;
+        }
+    }
+
+    return (IOAddress::from_bytes(addr.getFamily(), packed));
+}
+
+
+isc::asiolink::IOAddress
+AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
+                                             const DuidPtr&,
+                                             const IOAddress&) {
+
+    // Let's get the last allocated address. It is usually set correctly,
+    // but there are times when it won't be (like after removing a pool or
+    // perhaps restaring the server).
+    IOAddress last = subnet->getLastAllocated();
+
+    const Pool6Collection& pools = subnet->getPools();
+
+    if (pools.size() == 0) {
+        isc_throw(AllocFailed, "No pools defined in selected subnet");
+    }
+
+    // first we need to find a pool the last address belongs to.
+    Pool6Collection::const_iterator it;
+    for (it = pools.begin(); it != pools.end(); ++it) {
+        if ((*it)->inRange(last)) {
+            break;
+        }
+    }
+
+    // last one was bogus for one of several reasons:
+    // - we just booted up and that's the first address we're allocating
+    // - a subnet was removed or other reconfiguration just completed
+    // - perhaps allocation algorithm was changed
+    if (it == pools.end()) {
+        // ok to access first element directly. We checked that pools is non-empty
+        IOAddress next = pools[0]->getFirstAddress();
+        subnet->setLastAllocated(next);
+        return (next);
+    }
+
+    // Ok, we have a pool that the last address belonged to, let's use it.
+
+    IOAddress next = increaseAddress(last); // basically addr++
+    if ((*it)->inRange(next)) {
+        // the next one is in the pool as well, so we haven't hit pool boundary yet
+        subnet->setLastAllocated(next);
+        return (next);
+    }
+
+    // We hit pool boundary, let's try to jump to the next pool and try again
+    ++it;
+    if (it == pools.end()) {
+        // Really out of luck today. That was the last pool. Let's rewind
+        // to the beginning.
+        next = pools[0]->getFirstAddress();
+        subnet->setLastAllocated(next);
+        return (next);
+    }
+
+    // there is a next pool, let's try first adddress from it
+    next = (*it)->getFirstAddress();
+    subnet->setLastAllocated(next);
+    return (next);
+}
+
+AllocEngine::HashedAllocator::HashedAllocator()
+    :Allocator() {
+    isc_throw(NotImplemented, "Hashed allocator is not implemented");
+}
+
+
+isc::asiolink::IOAddress
+AllocEngine::HashedAllocator::pickAddress(const Subnet6Ptr&,
+                                             const DuidPtr&,
+                                             const IOAddress&) {
+    isc_throw(NotImplemented, "Hashed allocator is not implemented");
+}
+
+AllocEngine::RandomAllocator::RandomAllocator()
+    :Allocator() {
+    isc_throw(NotImplemented, "Random allocator is not implemented");
+}
+
+
+isc::asiolink::IOAddress
+AllocEngine::RandomAllocator::pickAddress(const Subnet6Ptr&,
+                                             const DuidPtr&,
+                                             const IOAddress&) {
+    isc_throw(NotImplemented, "Random allocator is not implemented");
+}
+
+
+AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts)
+    :attempts_(attempts) {
+    switch (engine_type) {
+    case ALLOC_ITERATIVE:
+        allocator_ = boost::shared_ptr<Allocator>(new IterativeAllocator());
+        break;
+    case ALLOC_HASHED:
+        allocator_ = boost::shared_ptr<Allocator>(new HashedAllocator());
+        break;
+    case ALLOC_RANDOM:
+        allocator_ = boost::shared_ptr<Allocator>(new RandomAllocator());
+        break;
+
+    default:
+        isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
+    }
+}
+
+Lease6Ptr
+AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
+                              const DuidPtr& duid,
+                              uint32_t iaid,
+                              const IOAddress& hint,
+                              bool fake_allocation /* = false */ ) {
+
+    // That check is not necessary. We create allocator in AllocEngine
+    // constructor
+    if (!allocator_) {
+        isc_throw(InvalidOperation, "No allocator selected");
+    }
+
+    // check if there's existing lease for that subnet/duid/iaid combination.
+    Lease6Ptr existing = LeaseMgr::instance().getLease6(*duid, iaid, subnet->getID());
+    if (existing) {
+        // we have a lease already. This is a returning client, probably after
+        // his reboot.
+        return (existing);
+    }
+
+    // check if the hint is in pool and is available
+    if (subnet->inPool(hint)) {
+        existing = LeaseMgr::instance().getLease6(hint);
+        if (!existing) {
+            /// @todo: check if the hint is reserved once we have host support
+            /// implemented
+
+            // the hint is valid and not currently used, let's create a lease for it
+            Lease6Ptr lease = createLease(subnet, duid, iaid, hint, fake_allocation);
+
+            // It can happen that the lease allocation failed (we could have lost
+            // the race condition. That means that the hint is lo longer usable and
+            // we need to continue the regular allocation path.
+            if (lease) {
+                return (lease);
+            }
+        }
+    }
+
+    unsigned int i = attempts_;
+    do {
+        IOAddress candidate = allocator_->pickAddress(subnet, duid, hint);
+
+        /// @todo: check if the address is reserved once we have host support
+        /// implemented
+
+        Lease6Ptr existing = LeaseMgr::instance().getLease6(candidate);
+        // there's no existing lease for selected candidate, so it is
+        // free. Let's allocate it.
+        if (!existing) {
+            Lease6Ptr lease = createLease(subnet, duid, iaid, candidate,
+                                          fake_allocation);
+            if (lease) {
+                return (lease);
+            }
+
+            // Although the address was free just microseconds ago, it may have
+            // been taken just now. If the lease insertion fails, we continue
+            // allocation attempts.
+        }
+
+        // continue trying allocation until we run out of attempts
+        // (or attempts are set to 0, which means infinite)
+        --i;
+    } while ( i || !attempts_);
+
+    isc_throw(AllocFailed, "Failed to allocate address after " << attempts_
+              << " tries");
+}
+
+Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
+                                   const DuidPtr& duid,
+                                   uint32_t iaid,
+                                   const IOAddress& addr,
+                                   bool fake_allocation /*= false */ ) {
+
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
+                               subnet->getPreferred(), subnet->getValid(),
+                               subnet->getT1(), subnet->getT2(), subnet->getID()));
+
+    if (!fake_allocation) {
+        // That is a real (REQUEST) allocation
+        bool status = LeaseMgr::instance().addLease(lease);
+
+        if (status) {
+
+            return (lease);
+        } else {
+            // One of many failures with LeaseMgr (e.g. lost connection to the
+            // database, database failed etc.). One notable case for that
+            // is that we are working in multi-process mode and we lost a race
+            // (some other process got that address first)
+            return (Lease6Ptr());
+        }
+    } else {
+        // That is only fake (SOLICIT without rapid-commit) allocation
+
+        // It is for advertise only. We should not insert the lease into LeaseMgr,
+        // but rather check that we could have inserted it.
+        Lease6Ptr existing = LeaseMgr::instance().getLease6(addr);
+        if (!existing) {
+            return (lease);
+        } else {
+            return (Lease6Ptr());
+        }
+    }
+}
+
+AllocEngine::~AllocEngine() {
+    // no need to delete allocator. smart_ptr will do the trick for us
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 228 - 0
src/lib/dhcp/alloc_engine.h

@@ -0,0 +1,228 @@
+// Copyright (C) 2012 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ALLOC_ENGINE_H
+#define ALLOC_ENGINE_H
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+#include <dhcp/duid.h>
+#include <dhcp/subnet.h>
+#include <asiolink/io_address.h>
+#include <dhcp/lease_mgr.h>
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown when allocation module fails (e.g. due to
+/// lack of available addresses)
+class AllocFailed : public isc::Exception {
+public:
+
+    /// @brief constructor
+    ///
+    /// @param file name of the file, where exception occurred
+    /// @param line line of the file, where exception occurred
+    /// @param what text description of the issue that caused exception
+    AllocFailed(const char* file, size_t line, const char* what)
+        : isc::Exception(file, line, what) {}
+};
+
+/// @brief DHCPv4 and DHCPv6 allocation engine
+///
+/// This class represents DHCP allocation engine. It is responsible
+/// for picking subnets, choosing and allocating a lease, extending,
+/// renewing, releasing and possibly expiring leases.
+///
+/// @todo: Does not handle out of leases well
+/// @todo: Does not handle out of allocation attempts well
+class AllocEngine : public boost::noncopyable {
+protected:
+
+    /// @brief base class for all address/prefix allocation algorithms
+    ///
+    /// This is an abstract class that should not be used directly, but rather
+    /// specialized implementations should be used instead.
+    class Allocator {
+    public:
+
+        /// @brief picks one address out of available pools in a given subnet
+        ///
+        /// This method returns one address from the available pools in the
+        /// specified subnet. It should not check if the address is used or
+        /// reserved - AllocEngine will check that and will call pickAddress
+        /// again if necessary. The number of times this method is called will
+        /// increase as the number of available leases will decrease.
+        virtual isc::asiolink::IOAddress
+        pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                    const isc::asiolink::IOAddress& hint) = 0;
+
+        /// @brief virtual destructor
+        virtual ~Allocator() {
+        }
+    protected:
+    };
+
+    /// @brief Address/prefix allocator that iterates over all addresses
+    ///
+    /// This class implements iterative algorithm that returns all addresses in
+    /// a pool iteratively, one after another. Once the last address is reached,
+    /// it starts allocating from the beginning of the first pool (i.e. it loops
+    /// over).
+    class IterativeAllocator : public Allocator {
+    public:
+
+        /// @brief default constructor
+        ///
+        /// Does not do anything
+        IterativeAllocator();
+
+        /// @brief returns the next address from pools in a subnet
+        ///
+        /// @param subnet next address will be returned from pool of that subnet
+        /// @param duid Client's DUID (ignored)
+        /// @param hint client's hint (ignored)
+        /// @return the next address
+        virtual isc::asiolink::IOAddress
+            pickAddress(const Subnet6Ptr& subnet,
+                        const DuidPtr& duid,
+                        const isc::asiolink::IOAddress& hint);
+    private:
+
+        /// @brief returns an address by one
+        /// @param addr address to be increased
+        /// @return address increased by one
+        isc::asiolink::IOAddress increaseAddress(const isc::asiolink::IOAddress& addr);
+
+    };
+
+    /// @brief Address/prefix allocator that gets an address based on a hash
+    ///
+    /// @todo: This is a skeleton class for now and is missing implementation.
+    class HashedAllocator : public Allocator {
+    public:
+
+        /// @brief default constructor (does nothing)
+        HashedAllocator();
+
+        /// @brief returns an address based on hash calculated from client's DUID.
+        ///
+        /// @todo: Implement this method
+        ///
+        /// @param subnet an address will be picked from pool of that subnet
+        /// @param duid Client's DUID
+        /// @param hint a hint (last address that was picked)
+        /// @return selected address
+        virtual isc::asiolink::IOAddress pickAddress(const Subnet6Ptr& subnet,
+                                                     const DuidPtr& duid,
+                                                     const isc::asiolink::IOAddress& hint);
+    };
+
+    /// @brief Random allocator that picks address randomly
+    ///
+    /// @todo: This is a skeleton class for now and is missing implementation.
+    class RandomAllocator : public Allocator {
+    public:
+
+        /// @brief default constructor (does nothing)
+        RandomAllocator();
+
+        /// @brief returns an random address from pool of specified subnet
+        ///
+        /// @todo: Implement this method
+        ///
+        /// @param subnet an address will be picked from pool of that subnet
+        /// @param duid Client's DUID (ignored)
+        /// @param hint the last address that was picked (ignored)
+        /// @return a random address from the pool
+        virtual isc::asiolink::IOAddress
+        pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                    const isc::asiolink::IOAddress& hint);
+    };
+
+    public:
+
+    /// @brief specifies allocation type
+    typedef enum {
+        ALLOC_ITERATIVE, // iterative - one address after another
+        ALLOC_HASHED,    // hashed - client's DUID/client-id is hashed
+        ALLOC_RANDOM     // random - an address is randomly selected
+    } AllocType;
+
+
+    /// @brief Default constructor.
+    ///
+    /// Instantiates necessary services, required to run DHCPv6 server.
+    /// In particular, creates IfaceMgr that will be responsible for
+    /// network interaction. Will instantiate lease manager, and load
+    /// old or create new DUID.
+    ///
+    /// @param engine_type selects allocation algorithm
+    /// @param attempts number of attempts for each lease allocation before
+    ///        we give up (0 means unlimited)
+    AllocEngine(AllocType engine_type, unsigned int attempts);
+
+    /// @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'd DUID
+    /// @param iaid iaid field from the IA_NA container that client sent
+    /// @param hint a hint that the client provided
+    /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+    ///        an address for SOLICIT that is not really allocated (true)
+    /// @return Allocated IPv6 lease (or NULL if allocation failed)
+    Lease6Ptr
+    allocateAddress6(const Subnet6Ptr& subnet,
+                     const DuidPtr& duid,
+                     uint32_t iaid,
+                     const isc::asiolink::IOAddress& hint,
+                     bool fake_allocation);
+
+    /// @brief Destructor. Used during DHCPv6 service shutdown.
+    virtual ~AllocEngine();
+private:
+
+    /// @brief creates a lease and inserts it in LeaseMgr if necessary
+    ///
+    /// Creates a lease based on specified parameters and tries to insert it
+    /// into the database. That may fail in some cases, i.e. when there is another
+    /// 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 addr an address that was selected and is confirmed to be available
+    /// @param 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
+    ///        becomed unavailable)
+    Lease6Ptr createLease(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                          uint32_t iaid, const isc::asiolink::IOAddress& addr,
+                          bool fake_allocation = false);
+
+    /// @brief a pointer to currently used allocator
+    boost::shared_ptr<Allocator> allocator_;
+
+    /// @brief number of attempts before we give up lease allocation (0=unlimited)
+    unsigned int attempts_;
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // ALLOC_ENGINE_H

+ 9 - 3
src/lib/dhcp/duid.h

@@ -12,11 +12,13 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#ifndef DUID_H
+#define DUID_H
+
+#include <asiolink/io_address.h>
+#include <vector>
 #include <stdint.h>
 #include <unistd.h>
-#include <vector>
-#include <asiolink/io_address.h>
-
 
 namespace isc {
 namespace dhcp {
@@ -70,6 +72,8 @@ class DUID {
     std::vector<uint8_t> duid_;
 };
 
+typedef boost::shared_ptr<DUID> DuidPtr;
+
 /// @brief Holds Client identifier or client IPv4 address
 ///
 /// This class is intended to be a generic IPv4 client identifier. It can hold
@@ -96,3 +100,5 @@ class ClientId : DUID {
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
+
+#endif /* DUID_H */

+ 43 - 4
src/lib/dhcp/lease_mgr.cc

@@ -12,6 +12,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include "lease_mgr.h"
+#include <exceptions/exceptions.h>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
 #include <sstream>
 #include <iostream>
 #include <map>
@@ -20,16 +24,50 @@
 #include <sstream>
 #include <algorithm>
 #include <iterator>
-#include <exceptions/exceptions.h>
-#include <boost/foreach.hpp>
-#include <boost/algorithm/string.hpp>
-#include "lease_mgr.h"
+#include <time.h>
 
 using namespace std;
 
 using namespace isc::dhcp;
 
+LeaseMgr* LeaseMgr::instance_ = NULL;
+
+Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
+               uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
+               uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
+    :type_(type), addr_(addr), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+     preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
+     subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
+     fqdn_rev_(false) {
+    if (!duid) {
+        isc_throw(InvalidOperation, "DUID must be specified for a lease");
+    }
+
+    cltt_ = time(NULL);
+}
+
+LeaseMgr& LeaseMgr::instance() {
+    if (!instance_) {
+        isc_throw(InvalidOperation, "LeaseManager not instantiated yet");
+    }
+    return (*instance_);
+}
+
+void LeaseMgr::destroy_instance() {
+    if (!instance_) {
+        isc_throw(InvalidOperation, "LeaseManager not instantiated yet");
+    }
+    delete instance_;
+    instance_ = NULL;
+}
+
 LeaseMgr::LeaseMgr(const std::string& dbconfig) {
+    if (instance_) {
+        isc_throw(InvalidOperation, "LeaseManager already instantiated");
+    }
+
+    // remember the pointer to the singleton instance
+    instance_ = this;
 
     if (dbconfig.length() == 0) {
         return;
@@ -65,4 +103,5 @@ std::string LeaseMgr::getParameter(const std::string& name) const {
 }
 
 LeaseMgr::~LeaseMgr() {
+    instance_ = NULL;
 }

+ 57 - 21
src/lib/dhcp/lease_mgr.h

@@ -12,14 +12,19 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#ifndef LEASE_MGR_H
+#define LEASE_MGR_H
+
 #include <string>
 #include <fstream>
 #include <vector>
 #include <map>
 #include <asiolink/io_address.h>
+#include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 #include <dhcp/option.h>
 #include <dhcp/duid.h>
+#include <dhcp/subnet.h>
 
 /// @file dhcp/lease_mgr.h
 /// @brief An abstract API for lease database
@@ -55,10 +60,6 @@
 namespace isc {
 namespace dhcp {
 
-/// @brief specifies unique subnet identifier
-/// @todo: Move this to subnet.h once ticket #2237 is merged
-typedef uint32_t SubnetID;
-
 /// @brief Structure that holds a lease for IPv4 address
 ///
 /// For performance reasons it is a simple structure, not a class. If we chose
@@ -161,6 +162,10 @@ struct Lease6 {
         LEASE_IA_PD  /// the lease contains IPv6 prefix (for prefix delegation)
     } LeaseType;
 
+    Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
+           uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
+           uint32_t t2, SubnetID subnet_id, uint8_t prefixlen_ = 0);
+
     /// @brief specifies lease type (normal addr, temporary addr, prefix)
     LeaseType type_;
 
@@ -208,6 +213,8 @@ struct Lease6 {
     /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
     /// for the same IA, each must have consistent T1 and T2 values. Specified in
     /// seconds since cltt.
+    /// This value will also be useful for failover to calculate the next expected
+    /// client transmission time.
     uint32_t t1_;
 
     /// @brief T2 timer
@@ -268,33 +275,42 @@ typedef std::vector< boost::shared_ptr<Lease6Ptr> > Lease6Collection;
 /// interface to all backends. As this is an abstract class, it should not
 /// be used directly, but rather specialized derived class should be used
 /// instead.
-class LeaseMgr {
+///
+/// This class is a meta-singleton. At any given time, there is only one
+/// instance of any classes derived from that class. That is achieved with
+/// defining only a single protected constructor, so every derived class has
+/// to use it. Furthermore, this sole constructor registers the first instance
+/// (and throws InvalidOperation if there is an attempt to create a second one).
+class LeaseMgr : public boost::noncopyable {
 public:
-
     /// Client Hardware address
     typedef std::vector<uint8_t> HWAddr;
 
-    /// @brief The sole lease manager constructor
+    /// @brief returns a single instance of LeaseMgr
     ///
-    /// dbconfig is a generic way of passing parameters. Parameters
-    /// are passed in the "name=value" format, separated by spaces.
-    /// Values may be enclosed in double quotes, if needed.
-    ///
-    /// @param dbconfig database configuration
-    LeaseMgr(const std::string& dbconfig);
+    /// LeaseMgr is a singleton and this method is the only way of
+    /// accessing it. LeaseMgr must be created first. See
+    /// isc::dhcp::LeaseMgrFactory class (work of ticket #2342.
+    /// Otherwise instance() will throw InvalidOperation exception.
+    /// @throw InvalidOperation if LeaseMgr not instantiated
+    static LeaseMgr& instance();
 
-    /// @brief Destructor (closes file)
-    virtual ~LeaseMgr();
+    /// @brief destroys the only instance of LeaseMgr
+    ///
+    /// This method is used mostly in tests, where LeaseMgr is destroyed
+    /// at the end of each test, just to be created at the beginning of
+    /// the next one.
+    static void destroy_instance();
 
     /// @brief Adds an IPv4 lease.
     ///
     /// @param lease lease to be added
-    virtual bool addLease(Lease4Ptr lease) = 0;
+    virtual bool addLease(const Lease4Ptr& lease) = 0;
 
     /// @brief Adds an IPv6 lease.
     ///
     /// @param lease lease to be added
-    virtual bool addLease(Lease6Ptr lease) = 0;
+    virtual bool addLease(const Lease6Ptr& lease) = 0;
 
     /// @brief Returns existing IPv4 lease for specified IPv4 address and subnet_id
     ///
@@ -382,7 +398,7 @@ public:
     /// @param addr address of the searched lease
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const = 0;
+    virtual Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const = 0;
 
     /// @brief Returns existing IPv6 leases for a given DUID+IA combination
     ///
@@ -413,14 +429,14 @@ public:
     /// @param lease4 The lease to be updated.
     ///
     /// If no such lease is present, an exception will be thrown.
-    virtual void updateLease4(Lease4Ptr lease4) = 0;
+    virtual void updateLease4(const Lease4Ptr& lease4) = 0;
 
     /// @brief Updates IPv4 lease.
     ///
     /// @param lease4 The lease to be updated.
     ///
     /// If no such lease is present, an exception will be thrown.
-    virtual void updateLease6(Lease6Ptr lease6) = 0;
+    virtual void updateLease6(const Lease6Ptr& lease6) = 0;
 
     /// @brief Deletes a lease.
     ///
@@ -434,7 +450,7 @@ public:
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
-    virtual bool deleteLease6(isc::asiolink::IOAddress addr) = 0;
+    virtual bool deleteLease6(const isc::asiolink::IOAddress& addr) = 0;
 
     /// @brief Returns backend name.
     ///
@@ -464,6 +480,22 @@ public:
     /// is currently postponed.
 
 protected:
+    /// @brief The sole lease manager constructor
+    ///
+    /// dbconfig is a generic way of passing parameters. Parameters are passed
+    /// in the "name=value" format, separated by spaces. Values may be enclosed
+    /// in double quotes, if needed. This ctor guarantees that there will be
+    /// only one instance of any derived classes. If there is a second instance
+    /// being created with the first one still around, it will throw
+    /// InvalidOperation.
+    ///
+    /// @param dbconfig database configuration
+    /// @throw InvalidOperation when trying to create second LeaseMgr
+    LeaseMgr(const std::string& dbconfig);
+
+    /// @brief Destructor
+    virtual ~LeaseMgr();
+
     /// @brief returns value of the parameter
     std::string getParameter(const std::string& name) const;
 
@@ -473,8 +505,12 @@ protected:
     /// password and other parameters required for DB access. It is not
     /// intended to keep any DHCP-related parameters.
     std::map<std::string, std::string> parameters_;
+
+    static LeaseMgr* instance_;
 };
 
 }; // end of isc::dhcp namespace
 
 }; // end of isc namespace
+
+#endif // LEASE_MGR_H

+ 79 - 0
src/lib/dhcp/libdhcp++.dox

@@ -0,0 +1,79 @@
+/**
+@page libdhcp libdhcp++
+
+@section libdhcpIntro Libdhcp++ Library Introduction
+
+libdhcp++ is an all-purpose DHCP-manipulation library, written in
+C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6
+options parsing and ssembly, interface detection (currently on
+Linux systems only) and socket operations. It is a generic purpose library that
+can be used by server, client, relay, performance tools and other DHCP-related
+tools. For server specific library, see \ref libdhcpsrv. Please do not
+add any server-specific code to libdhcp++ and use \ref libdhcpsrv instead.
+
+The following classes for packet manipulation are implemented:
+
+- isc::dhcp::Pkt4 - represents DHCPv4 packet.
+- isc::dhcp::Pkt6 - represents DHCPv6 packet.
+
+There are two pointer types defined: Pkt4Ptr and Pkt6Ptr. They are
+smart pointer and are using boost::shared_ptr. There are not const
+versions defined, as we assume that hooks can modify any aspect of
+the packet at almost any stage of processing.
+
+Both packets use collection of Option objects to represent DHCPv4
+and DHCPv6 options. The base class -- Option -- can be used to
+represent generic option that contains collection of
+bytes. Depending on if the option is instantiated as v4 or v6
+option, it will adjust its header (DHCPv4 options use 1 octet for
+type and 1 octet for length, while DHCPv6 options use 2 bytes for
+each).
+
+There are many specialized classes that are intended to handle options with
+specific content:
+- isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses;
+- isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses;
+- isc::dhcp::Option6IAAddr -- DHCPv6 option, represents IAADDR_OPTION (an option that
+                    contains IPv6 address with extra parameters);
+- isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions.
+
+All options can store sub-options (i.e. options that are stored within option
+rather than in a message directly). This functionality is commonly used in
+DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
+isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
+for that purpose.
+
+@section libdhcpIfaceMgr Interface Manager
+
+Interface Manager (or IfaceMgr) is an abstraction layer about low-level
+network operations. In particlar, it provides information about existing
+network interfaces See isc::dhcp::IfaceMgr::Iface class and
+isc::dhcp::IfaceMgr::detectIfaces() and isc::dhcp::IfaceMgr::getIface().
+
+Currently there is interface detection is implemented in Linux only. There
+are plans to implement such support for other OSes, but they remain low
+priority for now.
+
+Generic parts of the code are isc::dhcp::IfaceMgr class in
+src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate
+files, e.g. iface_mgr_linux.cc. Such separation should be maintained when
+additional code will be developed.
+
+For systems that interface detection is not supported on, there is a stub
+mechanism implemented. It assumes that interface name is read from a text
+file. This is a temporary solution and will be removed as soon as proper
+interface detection is implemented. It is not going to be developed further.
+To use this feature, store interfaces.txt file. It uses a simple syntax.
+Each line represents an interface name, followed by IPv4 or IPv6 address
+that follows it. This is usually link-local IPv6 address that the server
+should bind to. In theory this mechanism also supports IPv4, but it was
+never tested. The code currently supports only a single interface defined
+that way.
+
+Another useful methods are dedicated to transmission
+(isc::dhcp::IfaceMgr::send(), 2 overloads) and reception
+(isc::dhcp::IfaceMgr::receive4() and isc::dhcp::IfaceMgr::receive6()).
+Note that receive4() and receive6() methods may return NULL, e.g.
+when timeout is reached or if dhcp daemon receives a signal.
+
+*/

+ 86 - 0
src/lib/dhcp/libdhcsrv.dox

@@ -0,0 +1,86 @@
+/**
+ @page libdhcpsrv libdhcpsrv - Server DHCP library
+
+This library contains code useful for DHCPv4 and DHCPv6 server operations, like
+Lease Manager that stores leases information, configuration manager that stores
+configuration etc. The code here is server specific. For generic (useful in
+server, client, relay and other tools like perfdhcp) code, please see
+\ref libdhcp.
+
+This library contains several crucial elements of the DHCP server operation:
+
+- isc::dhcp::LeaseMgr - Lease Manager is a name for database backend that stores
+  leases.
+- isc::dhcp::CfgMgr - Configuration Manager that holds DHCP specific
+  configuration information (subnets, pools, options, timer values etc.) in
+  easy to use format.
+- AllocEngine - allocation engine that handles new requestes and allocates new
+  leases.
+
+@section leasemgr Lease Manager
+
+LeaseMgr provides a common, unified abstract API for all database backends. All
+backends are derived from the base class isc::dhcp::LeaseMgr. Currently the
+only available backend is MySQL (see \ref isc::dhcp::MySqlLeaseMgr).
+
+@section cfgmgr Configuration Manager
+
+Configuration Manager (\ref isc::dhcp::CfgMgr) stores configuration information
+necessary for DHCPv4 and DHCPv6 server operation. In particular, it stores
+subnets (\ref isc::dhcp::Subnet4 and \ref isc::dhcp::Subnet6) together with
+their pools (\ref isc::dhcp::Pool4 and \ref isc::dhcp::Pool6), options and
+other information specified by the used in BIND10 configuration.
+
+@section allocengine Allocation Engine
+
+Allocation Engine (\ref isc::dhcp::AllocEngine) is what its name say - an engine
+that handles allocation of new leases. It takes parameters that the client
+provided (client-id, DUID, subnet, a hint if the user provided one, etc.) and
+then attempts to allocate a lease.
+
+There is no single best soluction to the address assignment problem. Server
+is expected to pick an address from its available pools is currently not used.
+There are many possible algorithms that can do that, each with its own advantages
+and drawbacks. This allocation engine must provide robust operation is radically
+different scenarios, so there address selection problem was abstracted into
+separate module, called allocator. Its sole purpose is to pick an address from
+a pool. Allocation engine will then check if the picked address is free and if
+it is not, then will ask allocator to pick again.
+
+At lease 3 allocators will be implemented:
+
+- Iterative - it iterates over all addresses in available pools, one
+by one. The advantages of this approach are speed (typically it only needs to
+increase last address), the guarantee to cover all addresses and predictability.
+This allocator behaves very good in case of nearing depletion. Even when pools
+are almost completely allocated, it still will be able to allocate outstanding
+leases efficiently. Predictability can also be considered a serious flaw in
+some environments, as prediction of the next address is trivial and can be
+leveraged by an attacker. Another drawback of this allocator is that it does
+not attempt to give the same address to returning clients (clients that released
+or expired their leases and are requesting a new lease will likely to get a 
+different lease). This allocator is implemented in \ref isc::dhcp::AllocEngine::IterativeAllocator.
+
+- Hashed - ISC-DHCP uses hash of the client-id or DUID to determine, which
+address is tried first. If that address is not available, the result is hashed
+again. That procedure is repeated until available address is found or there
+are no more addresses left. The benefit of that approach is that it provides
+a relative lease stability, so returning old clients are likely to get the same
+address again. The drawbacks are increased computation cost, as each iteration
+requires use of a hashing function. That is especially difficult when the 
+pools are almost depleted. It also may be difficult to guarantee that the
+repeated hashing will iterate over all available addresses in all pools. Flawed
+hash algorithm can go into cycles that iterate over only part of the addresses.
+It is difficult to detect such issues as only some initial seed (client-id
+or DUID) values may trigger short cycles. This allocator is currently not
+implemented.
+
+- Random - Another possible approach to address selection is randomization. This
+allocator can pick an address randomly from the configured pool. The benefit
+of this approach is that it is easy to implement and makes attacks based on
+address prediction more difficult. The drawback of this approach is that
+returning clients are almost guaranteed to get a different address. Another
+drawback is that with almost depleted pools it is increasingly difficult to
+"guess" an address that is free. This allocator is currently not implemented.
+
+*/

+ 2 - 0
src/lib/dhcp/pool.h

@@ -141,6 +141,7 @@ public:
 
     /// @brief the constructor for Pool6 "min-max" style definition
     ///
+    /// @param type type of the pool (IA, TA or PD)
     /// @param first the first address in a pool
     /// @param last the last address in a pool
     Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
@@ -148,6 +149,7 @@ public:
 
     /// @brief the constructor for Pool6 "prefix/len" style definition
     ///
+    /// @param type type of the pool (IA, TA or PD)
     /// @param prefix specifies prefix of the pool
     /// @param prefix_len specifies length of the prefix of the pool
     Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,

+ 35 - 2
src/lib/dhcp/subnet.cc

@@ -15,7 +15,6 @@
 #include <dhcp/addr_utilities.h>
 #include <asiolink/io_address.h>
 #include <dhcp/subnet.h>
-#include <dhcp/pool.h>
 
 using namespace isc::asiolink;
 
@@ -27,7 +26,8 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
                const Triplet<uint32_t>& t2,
                const Triplet<uint32_t>& valid_lifetime)
     :id_(getNextID()), prefix_(prefix), prefix_len_(len), t1_(t1),
-     t2_(t2), valid_(valid_lifetime) {
+     t2_(t2), valid_(valid_lifetime),
+     last_allocated_(lastAddrInPrefix(prefix, len)) {
     if ( (prefix.getFamily() == AF_INET6 && len > 128) ||
          (prefix.getFamily() == AF_INET && len > 32) ) {
         isc_throw(BadValue, "Invalid prefix length specified for subnet: " << len);
@@ -105,6 +105,22 @@ Subnet4::validateOption(const OptionPtr& option) const {
     }
 }
 
+bool Subnet4::inPool(const isc::asiolink::IOAddress& addr) const {
+
+    // Let's start with checking if it even belongs to that subnet.
+    if (!inRange(addr)) {
+        return (false);
+    }
+
+    for (Pool4Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+        if ((*pool)->inRange(addr)) {
+            return (true);
+        }
+    }
+    // there's no pool that address belongs to
+    return (false);
+}
+
 Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t1,
                  const Triplet<uint32_t>& t2,
@@ -159,5 +175,22 @@ Subnet6::validateOption(const OptionPtr& option) const {
         isc_throw(isc::BadValue, "expected V6 option to be added to the subnet");
     }
 }
+
+bool Subnet6::inPool(const isc::asiolink::IOAddress& addr) const {
+
+    // Let's start with checking if it even belongs to that subnet.
+    if (!inRange(addr)) {
+        return (false);
+    }
+
+    for (Pool6Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+        if ((*pool)->inRange(addr)) {
+            return (true);
+        }
+    }
+    // there's no pool that address belongs to
+    return (false);
+}
+
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 82 - 5
src/lib/dhcp/subnet.h

@@ -40,6 +40,13 @@ namespace dhcp {
 /// of DHCP option instances configured for the subnet. These options are
 /// included in DHCP messages being sent to clients which are connected
 /// to the particular subnet.
+///
+/// @todo: Implement support for options here
+
+
+/// @brief Unique indentifier for a subnet (both v4 and v6)
+typedef uint32_t SubnetID;
+
 class Subnet {
 public:
 
@@ -204,6 +211,20 @@ public:
     /// @brief Delete all options configured for the subnet.
     void delOptions();
 
+    /// @brief checks if the specified address is in pools
+    ///
+    /// Note the difference between inSubnet() and inPool(). For a given
+    /// subnet (e.g. 2001::/64) there may be one or more pools defined
+    /// that may or may not cover entire subnet, e.g. pool 2001::1-2001::10).
+    /// inPool() returning true implies inSubnet(), but the reverse implication
+    /// is not always true. For the given example, 2001::1234:abcd would return
+    /// true for inSubnet(), but false for inPool() check.
+    ///
+    /// @param addr this address will be checked if it belongs to any pools in
+    ///        that subnet
+    /// @return true if the address is in any of the pools
+    virtual bool inPool(const isc::asiolink::IOAddress& addr) const = 0;
+
     /// @brief return valid-lifetime for addresses in that prefix
     Triplet<uint32_t> getValid() const {
         return (valid_);
@@ -228,6 +249,36 @@ public:
         return (options_);
     }
 
+    /// @brief returns the last address that was tried from this pool
+    ///
+    /// This method returns the last address that was attempted to be allocated
+    /// from this subnet. This is used as helper information for the next
+    /// iteration of the allocation algorithm.
+    ///
+    /// @todo: Define map<SubnetID, IOAddress> somewhere in the
+    ///        AllocEngine::IterativeAllocator and keep the data there
+    ///
+    /// @return address that was last tried from this pool
+    isc::asiolink::IOAddress getLastAllocated() const {
+        return (last_allocated_);
+    }
+
+    /// @brief sets the last address that was tried from this pool
+    ///
+    /// This method sets the last address that was attempted to be allocated
+    /// from this subnet. This is used as helper information for the next
+    /// iteration of the allocation algorithm.
+    ///
+    /// @todo: Define map<SubnetID, IOAddress> somewhere in the
+    ///        AllocEngine::IterativeAllocator and keep the data there
+    void setLastAllocated(const isc::asiolink::IOAddress& addr) {
+        last_allocated_ = addr;
+    }
+
+    /// @brief returns unique ID for that subnet
+    /// @return unique ID for that subnet
+    SubnetID getID() const { return (id_); }
+
 protected:
     /// @brief protected constructor
     //
@@ -247,8 +298,8 @@ protected:
     /// @brief returns the next unique Subnet-ID
     ///
     /// @return the next unique Subnet-ID
-    static uint32_t getNextID() {
-        static uint32_t id = 0;
+    static SubnetID getNextID() {
+        static SubnetID id = 0;
         return (id++);
     }
 
@@ -261,7 +312,7 @@ protected:
     ///
     /// Subnet-id is a unique value that can be used to find or identify
     /// a Subnet4 or Subnet6.
-    uint32_t id_;
+    SubnetID id_;
 
     /// @brief a prefix of the subnet
     isc::asiolink::IOAddress prefix_;
@@ -280,6 +331,17 @@ protected:
 
     /// @brief a collection of DHCP options configured for a subnet.
     OptionContainer options_;
+
+    /// @brief last allocated address
+    ///
+    /// This is the last allocated address that was previously allocated from
+    /// this particular subnet. Some allocation algorithms (e.g. iterative) use
+    /// that value, others do not. It should be noted that although the value
+    /// is usually correct, there are cases when it is invalid, e.g. after
+    /// removing a pool, restarting or changing allocation algorithms. For
+    /// that purpose it should be only considered a help that should not be
+    /// fully trusted.
+    isc::asiolink::IOAddress last_allocated_;
 };
 
 /// @brief A configuration holder for IPv4 subnet.
@@ -313,14 +375,21 @@ public:
 
     /// @brief returns all pools
     ///
-    /// The reference is only valid as long as the object that
-    /// returned it.
+    /// The reference is only valid as long as the object that returned it.
     ///
     /// @return a collection of all pools
     const Pool4Collection& getPools() const {
         return pools_;
     }
 
+    /// @brief checks if the specified address is in pools
+    ///
+    /// See the description in \ref Subnet::inPool().
+    ///
+    /// @param addr this address will be checked if it belongs to any pools in that subnet
+    /// @return true if the address is in any of the pools
+    bool inPool(const isc::asiolink::IOAddress& addr) const;
+
 protected:
 
     /// @brief Check if option is valid and can be added to a subnet.
@@ -389,6 +458,14 @@ public:
         return pools_;
     }
 
+    /// @brief checks if the specified address is in pools
+    ///
+    /// See the description in \ref Subnet::inPool().
+    ///
+    /// @param addr this address will be checked if it belongs to any pools in that subnet
+    /// @return true if the address is in any of the pools
+    bool inPool(const isc::asiolink::IOAddress& addr) const;
+
 protected:
 
     /// @brief Check if option is valid and can be added to a subnet.

+ 4 - 1
src/lib/dhcp/tests/Makefile.am

@@ -28,7 +28,6 @@ TESTS += libdhcp++_unittests libdhcpsrv_unittests
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
-libdhcp___unittests_SOURCES += lease_mgr_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
@@ -49,6 +48,9 @@ libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc triplet_unittest.cc
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc subnet_unittest.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
+libdhcpsrv_unittests_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
+libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 
 libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 libdhcpsrv_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
@@ -56,6 +58,7 @@ libdhcpsrv_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 libdhcpsrv_unittests_LDADD  = $(GTEST_LDADD)
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la

+ 349 - 0
src/lib/dhcp/tests/alloc_engine_unittest.cc

@@ -0,0 +1,349 @@
+// Copyright (C) 2012 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/lease_mgr.h>
+#include <dhcp/duid.h>
+#include <dhcp/alloc_engine.h>
+#include <dhcp/cfgmgr.h>
+#include "memfile_lease_mgr.h"
+#include <boost/shared_ptr.hpp>
+#include <iostream>
+#include <sstream>
+#include <map>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test; // Memfile_LeaseMgr
+using namespace boost;
+
+namespace {
+
+class NakedAllocEngine : public AllocEngine {
+public:
+    NakedAllocEngine(AllocEngine::AllocType engine_type, unsigned int attempts)
+        :AllocEngine(engine_type, attempts) {
+    }
+    using AllocEngine::Allocator;
+    using AllocEngine::IterativeAllocator;
+};
+
+// empty class for now, but may be extended once Addr6 becomes bigger
+class AllocEngineTest : public ::testing::Test {
+public:
+    AllocEngineTest() {
+        duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
+        iaid_ = 42;
+
+        // instantiate cfg_mgr
+        CfgMgr& cfg_mgr = CfgMgr::instance();
+
+        subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+        pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1::10"),
+                                   IOAddress("2001:db8:1::20")));
+        subnet_->addPool6(pool_);
+        cfg_mgr.addSubnet6(subnet_);
+
+        leasemgr_ = new Memfile_LeaseMgr("");
+    }
+
+    void checkLease6(const Lease6Ptr& lease) {
+        // that is belongs to the right subnet
+        EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+        EXPECT_TRUE(subnet_->inRange(lease->addr_));
+        EXPECT_TRUE(subnet_->inPool(lease->addr_));
+
+        // that it have proper parameters
+        EXPECT_EQ(iaid_, lease->iaid_);
+        EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+        EXPECT_EQ(subnet_->getPreferred(), lease->preferred_lft_);
+        EXPECT_EQ(subnet_->getT1(), lease->t1_);
+        EXPECT_EQ(subnet_->getT2(), lease->t2_);
+        EXPECT_EQ(0, lease->prefixlen_); // this is IA_NA, not IA_PD
+        EXPECT_TRUE(false == lease->fqdn_fwd_);
+        EXPECT_TRUE(false == lease->fqdn_rev_);
+        EXPECT_TRUE(*lease->duid_ == *duid_);
+        // @todo: check cltt
+     }
+
+    ~AllocEngineTest() {
+        LeaseMgr::instance().destroy_instance();
+        leasemgr_ = NULL;
+    }
+
+    DuidPtr duid_;
+    uint32_t iaid_;
+    Subnet6Ptr subnet_;
+    Pool6Ptr pool_;
+    LeaseMgr* leasemgr_;
+};
+
+// This test checks if the Allocation Engine can be instantiated and that it
+// parses parameters string properly.
+TEST_F(AllocEngineTest, constructor) {
+
+    AllocEngine* x = NULL;
+
+    // Hashed and random allocators are not supported yet
+    ASSERT_THROW(x = new AllocEngine(AllocEngine::ALLOC_HASHED, 5), NotImplemented);
+    ASSERT_THROW(x = new AllocEngine(AllocEngine::ALLOC_RANDOM, 5), NotImplemented);
+
+    ASSERT_NO_THROW(x = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+
+    delete x;
+}
+
+/// @todo: This method is taken from mysql_lease_mgr_utilities.cc from ticket
+/// #2342. Get rid of one instance once the code is merged
+void
+detailCompareLease6(const Lease6Ptr& first, const Lease6Ptr& second) {
+    EXPECT_EQ(first->type_, second->type_);
+
+    // Compare address strings - odd things happen when they are different
+    // as the EXPECT_EQ appears to call the operator uint32_t() function,
+    // which causes an exception to be thrown for IPv6 addresses.
+    EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
+    EXPECT_EQ(first->prefixlen_, second->prefixlen_);
+    EXPECT_EQ(first->iaid_, second->iaid_);
+    EXPECT_TRUE(first->hwaddr_ == second->hwaddr_);
+    EXPECT_TRUE(*first->duid_ == *second->duid_);
+    EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
+    EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+    EXPECT_EQ(first->cltt_, second->cltt_);
+    EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+}
+
+
+// This test checks if the simple allocation can succeed
+TEST_F(AllocEngineTest, simpleAlloc) {
+
+    AllocEngine* engine = NULL;
+    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    ASSERT_TRUE(engine);
+
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+                                               false);
+
+    // check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // do all checks on the lease
+    checkLease6(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgr::instance().getLease6(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease6(lease, from_mgr);
+}
+
+// This test checks if the fake allocation (for SOLICIT) can succeed
+TEST_F(AllocEngineTest, fakeAlloc) {
+
+    AllocEngine* engine = NULL;
+    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    ASSERT_TRUE(engine);
+
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+                                               true);
+
+    // check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // do all checks on the lease
+    checkLease6(lease);
+
+    // Check that the lease is NOT in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgr::instance().getLease6(lease->addr_);
+    ASSERT_FALSE(from_mgr);
+}
+
+// This test checks if the allocation with a hint that is valid (in range,
+// in pool and free) can succeed
+TEST_F(AllocEngineTest, allocWithValidHint) {
+
+    AllocEngine* engine = NULL;
+    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    ASSERT_TRUE(engine);
+
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+                                               IOAddress("2001:db8:1::15"),
+                                               false);
+
+    // check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // we should get what we asked for
+    EXPECT_EQ(lease->addr_.toText(), "2001:db8:1::15");
+
+    // do all checks on the lease
+    checkLease6(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgr::instance().getLease6(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease6(lease, from_mgr);
+}
+
+// This test checks if the allocation with a hint that is in range,
+// in pool, but is currently used) can succeed
+TEST_F(AllocEngineTest, allocWithUsedHint) {
+
+    AllocEngine* engine = NULL;
+    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    ASSERT_TRUE(engine);
+
+    // let's create a lease and put it in the LeaseMgr
+    DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
+    Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"),
+                              duid2, 1, 2, 3, 4, 5, subnet_->getID()));
+    ASSERT_TRUE(LeaseMgr::instance().addLease(used));
+
+    // another client comes in and request an address that is in pool, but
+    // unfortunately it is used already. The same address must not be allocated
+    // twice.
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+                                               IOAddress("2001:db8:1::1f"),
+                                               false);
+    // check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // allocated address must be different
+    EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
+
+    // we should NOT get what we asked for, because it is used already
+    EXPECT_TRUE(lease->addr_.toText() != "2001:db8:1::1f");
+
+    // do all checks on the lease
+    checkLease6(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgr::instance().getLease6(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease6(lease, from_mgr);
+}
+
+// This test checks if the allocation with a hint that is out the blue
+// can succeed. The invalid hint should be ignored completely.
+TEST_F(AllocEngineTest, allocBogusHint) {
+
+    AllocEngine* engine = NULL;
+    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    ASSERT_TRUE(engine);
+
+    // Client would like to get a 3000::abc lease, which does not belong to any
+    // supported lease. Allocation engine should ignore it and carry on
+    // with the normal allocation
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+                                               IOAddress("3000::abc"),
+                                               false);
+    // check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // we should NOT get what we asked for, because it is used already
+    EXPECT_TRUE(lease->addr_.toText() != "3000::abc");
+
+    // do all checks on the lease
+    checkLease6(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgr::instance().getLease6(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease6(lease, from_mgr);
+}
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(AllocEngineTest, IterativeAllocator) {
+    NakedAllocEngine::Allocator* alloc = new NakedAllocEngine::IterativeAllocator();
+
+    for (int i = 0; i < 1000; ++i) {
+        IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
+
+        EXPECT_TRUE(subnet_->inPool(candidate));
+    }
+
+    delete alloc;
+}
+
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice
+// unless it runs out of pool space and must start over.
+TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
+    NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+
+    // let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+    for (int i = 2; i < 10; ++i) {
+        stringstream min, max;
+
+        min << "2001:db8:1::" << hex << i*16 + 1;
+        max << "2001:db8:1::" << hex << i*16 + 9;
+
+        Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, IOAddress(min.str()),
+                                IOAddress(max.str())));
+        // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
+        subnet_->addPool6(pool);
+    }
+
+    int total = 17 + 8*9; // first pool (::10 - ::20) has 17 addresses in it,
+                          // there are 8 extra pools with 9 addresses in each.
+
+    // Let's keep picked addresses here and check their uniqueness.
+    std::map<IOAddress, int> generated_addrs;
+    int cnt = 0;
+    while (++cnt) {
+        IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
+        EXPECT_TRUE(subnet_->inPool(candidate));
+
+        // One way to easily verify that the iterative allocator really works is
+        // to uncomment the following line and observe its output that it
+        // covers all defined subnets.
+        // cout << candidate.toText() << endl;
+
+        if (generated_addrs.find(candidate) == generated_addrs.end()) {
+            // we haven't had this
+            generated_addrs[candidate] = 0;
+        } else {
+            // we have seen this address before. That should mean that we
+            // iterated over all addresses.
+            if (generated_addrs.size() == total) {
+                // we have exactly the number of address in all pools
+                break;
+            }
+            ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+            break;
+        }
+
+        if ( cnt>total ) {
+            ADD_FAILURE() << "Too many unique addresses generated.";
+            break;
+        }
+    }
+
+    delete alloc;
+}
+
+}; // end of anonymous namespace

+ 109 - 235
src/lib/dhcp/tests/lease_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012 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,281 +16,155 @@
 #include <iostream>
 #include <sstream>
 #include <gtest/gtest.h>
-
 #include <asiolink/io_address.h>
 #include <dhcp/lease_mgr.h>
+#include <dhcp/duid.h>
+#include "memfile_lease_mgr.h"
 
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::dhcp::test; // Memfile_LeaseMgr
 
-// This is a concrete implementation of a Lease database.
-// It does not do anything useful now, and is used for abstract LeaseMgr
-// class testing. It may later evolve into more useful backend if the
-// need arises. We can reuse code from memfile benchmark. See code in
-// tests/tools/dhcp-ubench/memfile_bench.{cc|h}
-class Memfile_LeaseMgr : public LeaseMgr {
+namespace {
+// empty class for now, but may be extended once Addr6 becomes bigger
+class LeaseMgrTest : public ::testing::Test {
 public:
+    LeaseMgrTest() {
+    }
+};
 
-    /// @brief The sole lease manager constructor
-    ///
-    /// dbconfig is a generic way of passing parameters. Parameters
-    /// are passed in the "name=value" format, separated by spaces.
-    /// Values may be enclosed in double quotes, if needed.
-    ///
-    /// @param dbconfig database configuration
-    Memfile_LeaseMgr(const std::string& dbconfig);
-
-    /// @brief Destructor (closes file)
-    virtual ~Memfile_LeaseMgr();
-
-    /// @brief Adds an IPv4 lease.
-    ///
-    /// @param lease lease to be added
-    virtual bool addLease(Lease4Ptr lease);
-
-    /// @brief Adds an IPv6 lease.
-    ///
-    /// @param lease lease to be added
-    virtual bool addLease(Lease6Ptr lease);
-
-    /// @brief Returns existing IPv4 lease for specified IPv4 address.
-    ///
-    /// @param addr address of the searched lease
-    ///
-    /// @return a collection of leases
-    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
-
-    /// @brief Returns existing IPv4 lease for specific address and subnet
-    /// @param addr address of the searched lease
-    /// @param subnet_id ID of the subnet the lease must belong to
-    ///
-    /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
-                                SubnetID subnet_id) const;
-
-    /// @brief Returns existing IPv4 leases for specified hardware address.
-    ///
-    /// Although in the usual case there will be only one lease, for mobile
-    /// clients or clients with multiple static/fixed/reserved leases there
-    /// can be more than one. Thus return type is a container, not a single
-    /// pointer.
-    ///
-    /// @param hwaddr hardware address of the client
-    ///
-    /// @return lease collection
-    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
-
-    /// @brief Returns existing IPv4 leases for specified hardware address
-    ///        and a subnet
-    ///
-    /// There can be at most one lease for a given HW address in a single
-    /// pool, so this method with either return a single lease or NULL.
-    ///
-    /// @param hwaddr hardware address of the client
-    /// @param subnet_id identifier of the subnet that lease must belong to
-    ///
-    /// @return a pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
-                                SubnetID subnet_id) const;
-
-    /// @brief Returns existing IPv4 lease for specified client-id
-    ///
-    /// @param clientid client identifier
-    virtual Lease4Collection getLease4(const ClientId& clientid) const;
-
-    /// @brief Returns existing IPv4 lease for specified client-id
-    ///
-    /// There can be at most one lease for a given HW address in a single
-    /// pool, so this method with either return a single lease or NULL.
-    ///
-    /// @param clientid client identifier
-    /// @param subnet_id identifier of the subnet that lease must belong to
-    ///
-    /// @return a pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(const ClientId& clientid,
-                                SubnetID subnet_id) const;
-
-    /// @brief Returns existing IPv6 lease for a given IPv6 address.
-    ///
-    /// @param addr address of the searched lease
-    ///
-    /// @return smart pointer to the lease (or NULL if a lease is not found)
-    Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const;
-
-    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
-    ///
-    /// @param duid client DUID
-    /// @param iaid IA identifier
-    ///
-    /// @return collection of IPv6 leases
-    Lease6Collection getLease6(const DUID& duid, uint32_t iaid) const;
-
-    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
-    ///
-    /// @param duid client DUID
-    /// @param iaid IA identifier
-    /// @param subnet_id identifier of the subnet the lease must belong to
-    ///
-    /// @return smart pointer to the lease (or NULL if a lease is not found)
-    Lease6Ptr getLease6(const DUID& duid, uint32_t iaid, SubnetID subnet_id) const;
-
-    /// @brief Updates IPv4 lease.
-    ///
-    /// @param lease4 The lease to be updated.
-    ///
-    /// If no such lease is present, an exception will be thrown.
-    void updateLease4(Lease4Ptr lease4);
-
-    /// @brief Updates IPv4 lease.
-    ///
-    /// @param lease4 The lease to be updated.
-    ///
-    /// If no such lease is present, an exception will be thrown.
-    void updateLease6(Lease6Ptr lease6);
-
-    /// @brief Deletes a lease.
-    ///
-    /// @param addr IPv4 address of the lease to be deleted.
-    ///
-    /// @return true if deletion was successful, false if no such lease exists
-    bool deleteLease4(uint32_t addr);
-
-    /// @brief Deletes a lease.
-    ///
-    /// @param addr IPv4 address of the lease to be deleted.
-    ///
-    /// @return true if deletion was successful, false if no such lease exists
-    bool deleteLease6(isc::asiolink::IOAddress addr);
-
-    /// @brief Returns backend name.
-    ///
-    /// Each backend have specific name, e.g. "mysql" or "sqlite".
-    std::string getName() const { return "memfile"; }
-
-    /// @brief Returns description of the backend.
-    ///
-    /// This description may be multiline text that describes the backend.
-    std::string getDescription() const;
-
-    /// @brief Returns backend version.
-    std::string getVersion() const { return "test-version"; }
-
-    using LeaseMgr::getParameter;
-
-protected:
+// This test checks if the LeaseMgr can be instantiated and that it
+// parses parameters string properly.
+TEST_F(LeaseMgrTest, constructor) {
 
+    // should not throw any exceptions here
+    Memfile_LeaseMgr * leaseMgr = new Memfile_LeaseMgr("");
+    delete leaseMgr;
 
-};
+    leaseMgr = new Memfile_LeaseMgr("param1=value1 param2=value2");
 
-Memfile_LeaseMgr::Memfile_LeaseMgr(const std::string& dbconfig)
-    : LeaseMgr(dbconfig) {
-}
+    EXPECT_EQ("value1", leaseMgr->getParameter("param1"));
+    EXPECT_EQ("value2", leaseMgr->getParameter("param2"));
+    EXPECT_THROW(leaseMgr->getParameter("param3"), BadValue);
 
-Memfile_LeaseMgr::~Memfile_LeaseMgr() {
+    delete leaseMgr;
 }
 
-bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease4>) {
-    return (false);
-}
+// There's no point in calling any other methods in LeaseMgr, as they
+// are purely virtual, so we would only call Memfile_LeaseMgr methods.
+// Those methods are just stubs that does not return anything.
+// It seems likely that we will need to extend the memfile code for
+// allocation engine tests, so we may implement tests that call
+// Memfile_LeaseMgr methods then.
 
-bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease6>) {
-    return (false);
-}
+TEST_F(LeaseMgrTest, addGetDelete) {
+    Memfile_LeaseMgr * leaseMgr = new Memfile_LeaseMgr("");
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress) const {
-    return (Lease4Ptr());
-}
+    IOAddress addr("2001:db8:1::456");
 
-Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
-    return (Lease4Collection());
-}
+    uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+    DuidPtr duid(new DUID(llt, sizeof(llt)));
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress ,
-                                      SubnetID) const {
-    return (Lease4Ptr());
-}
+    uint32_t iaid = 7; // just a number
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
-                                      SubnetID) const {
-    return (Lease4Ptr());
-}
+    SubnetID subnet_id = 8; // just another number
 
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr,
+                               duid, iaid, 100, 200, 50, 80,
+                               subnet_id));
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
-                                      SubnetID) const {
-    return (Lease4Ptr());
-}
+    EXPECT_TRUE(leaseMgr->addLease(lease));
 
-Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
-    return (Lease4Collection());
-}
+    // should not be allowed to add a second lease with the same address
+    EXPECT_FALSE(leaseMgr->addLease(lease));
 
-Lease6Ptr Memfile_LeaseMgr::getLease6(isc::asiolink::IOAddress) const {
-    return (Lease6Ptr());
-}
+    Lease6Ptr x = leaseMgr->getLease6(IOAddress("2001:db8:1::234"));
+    EXPECT_EQ(Lease6Ptr(), x);
 
-Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
-    return (Lease6Collection());
-}
+    x = leaseMgr->getLease6(IOAddress("2001:db8:1::456"));
+    ASSERT_TRUE(x);
 
-Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID&, uint32_t,
-                                      SubnetID) const {
-    return (Lease6Ptr());
-}
+    EXPECT_EQ(x->addr_.toText(), addr.toText());
+    EXPECT_TRUE(*x->duid_ == *duid);
+    EXPECT_EQ(x->iaid_, iaid);
+    EXPECT_EQ(x->subnet_id_, subnet_id);
 
-void Memfile_LeaseMgr::updateLease4(Lease4Ptr ) {
-}
+    // These are not important from lease management perspective, but
+    // let's check them anyway.
+    EXPECT_EQ(x->type_, Lease6::LEASE_IA_NA);
+    EXPECT_EQ(x->preferred_lft_, 100);
+    EXPECT_EQ(x->valid_lft_, 200);
+    EXPECT_EQ(x->t1_, 50);
+    EXPECT_EQ(x->t2_, 80);
 
-void Memfile_LeaseMgr::updateLease6(Lease6Ptr ) {
+    // should return false - there's no such address
+    EXPECT_FALSE(leaseMgr->deleteLease6(IOAddress("2001:db8:1::789")));
 
-}
+    // this one should succeed
+    EXPECT_TRUE(leaseMgr->deleteLease6(IOAddress("2001:db8:1::456")));
 
-bool Memfile_LeaseMgr::deleteLease4(uint32_t ) {
-    return (false);
-}
+    // after the lease is deleted, it should really be gone
+    x = leaseMgr->getLease6(IOAddress("2001:db8:1::456"));
+    EXPECT_EQ(Lease6Ptr(), x);
 
-bool Memfile_LeaseMgr::deleteLease6(isc::asiolink::IOAddress ) {
-    return (false);
+    delete leaseMgr;
 }
 
-std::string Memfile_LeaseMgr::getDescription() const {
-    return (string("This is a dummy memfile backend implementation.\n"
-                   "It does not offer any useful lease management and its only\n"
-                   "purpose is to test abstract lease manager API."));
-}
+// This test checks there that leaseMgr is really a singleton and that
+// no more than one can be created.
+TEST_F(LeaseMgrTest, singleton) {
+    Memfile_LeaseMgr* leaseMgr1 = NULL;
+    Memfile_LeaseMgr* leaseMgr2 = NULL;
 
-namespace {
-// empty class for now, but may be extended once Addr6 becomes bigger
-class LeaseMgrTest : public ::testing::Test {
-public:
-    LeaseMgrTest() {
-    }
-};
+    EXPECT_THROW(LeaseMgr::instance(), InvalidOperation);
 
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
-TEST_F(LeaseMgrTest, constructor) {
+    EXPECT_NO_THROW( leaseMgr1 = new Memfile_LeaseMgr("") );
 
-    // should not throw any exceptions here
-    Memfile_LeaseMgr * leaseMgr = new Memfile_LeaseMgr("");
-    delete leaseMgr;
+    EXPECT_NO_THROW(LeaseMgr::instance());
 
-    leaseMgr = new Memfile_LeaseMgr("param1=value1 param2=value2");
+    // There can be only one instance of any LeaseMgr derived
+    // objects instantiated at any time.
+    ASSERT_THROW(leaseMgr2 = new Memfile_LeaseMgr(""), InvalidOperation);
 
-    EXPECT_EQ("value1", leaseMgr->getParameter("param1"));
-    EXPECT_EQ("value2", leaseMgr->getParameter("param2"));
-    EXPECT_THROW(leaseMgr->getParameter("param3"), BadValue);
+    delete leaseMgr1;
 
-    delete leaseMgr;
+    ASSERT_NO_THROW(leaseMgr2 = new Memfile_LeaseMgr("") );
+
+    delete leaseMgr2;
 }
 
-// There's no point in calling any other methods in LeaseMgr, as they
-// are purely virtual, so we would only call Memfile_LeaseMgr methods.
-// Those methods are just stubs that does not return anything.
-// It seems likely that we will need to extend the memfile code for
-// allocation engine tests, so we may implement tests that call
-// Memfile_LeaseMgr methods then.
+// This test checks if the Lease6 structure can be instantiated correctly
+TEST(Lease6, ctor) {
+
+    IOAddress addr("2001:db8:1::456");
+
+    uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+    DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+    uint32_t iaid = 7; // just a number
+
+    SubnetID subnet_id = 8; // just another number
+
+    Lease6Ptr x(new Lease6(Lease6::LEASE_IA_NA, addr,
+                           duid, iaid, 100, 200, 50, 80,
+                           subnet_id));
+
+    EXPECT_TRUE(x->addr_ == addr);
+    EXPECT_TRUE(*x->duid_ == *duid);
+    EXPECT_TRUE(x->iaid_ == iaid);
+    EXPECT_TRUE(x->subnet_id_ == subnet_id);
+    EXPECT_TRUE(x->type_ == Lease6::LEASE_IA_NA);
+    EXPECT_TRUE(x->preferred_lft_ == 100);
+    EXPECT_TRUE(x->valid_lft_ == 200);
+    EXPECT_TRUE(x->t1_ == 50);
+    EXPECT_TRUE(x->t2_ == 80);
+
+    // Lease6 must be instantiated with a DUID, not with NULL pointer
+    EXPECT_THROW(new Lease6(Lease6::LEASE_IA_NA, addr,
+                            DuidPtr(), iaid, 100, 200, 50, 80,
+                            subnet_id), InvalidOperation);
+}
 
 }; // end of anonymous namespace

+ 113 - 0
src/lib/dhcp/tests/memfile_lease_mgr.cc

@@ -0,0 +1,113 @@
+// Copyright (C) 2012 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "memfile_lease_mgr.h"
+
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+Memfile_LeaseMgr::Memfile_LeaseMgr(const std::string& dbconfig)
+    : LeaseMgr(dbconfig) {
+}
+
+Memfile_LeaseMgr::~Memfile_LeaseMgr() {
+}
+
+bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) {
+    return (false);
+}
+
+bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
+    if (getLease6(lease->addr_)) {
+        // there is a lease with specified address already
+        return (false);
+    }
+    storage6_.insert(lease);
+    return (true);
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
+    return (Lease4Collection());
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress ,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
+    return (Lease4Collection());
+}
+
+Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
+    Lease6Storage::iterator l = storage6_.find(addr);
+    if (l == storage6_.end()) {
+        return (Lease6Ptr());
+    } else {
+        return (*l);
+    }
+}
+
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+    return (Lease6Collection());
+}
+
+Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID&, uint32_t,
+                                      SubnetID) const {
+
+    return (Lease6Ptr());
+}
+
+void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& ) {
+}
+
+void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
+
+}
+
+bool Memfile_LeaseMgr::deleteLease4(uint32_t ) {
+    return (false);
+}
+
+bool Memfile_LeaseMgr::deleteLease6(const isc::asiolink::IOAddress& addr) {
+    Lease6Storage::iterator l = storage6_.find(addr);
+    if (l == storage6_.end()) {
+        // no such lease
+        return (false);
+    } else {
+        storage6_.erase(l);
+        return (true);
+    }
+}
+
+std::string Memfile_LeaseMgr::getDescription() const {
+    return (std::string("This is a dummy memfile backend implementation.\n"
+                        "It does not offer any useful lease management and its only\n"
+                        "purpose is to test abstract lease manager API."));
+}

+ 228 - 0
src/lib/dhcp/tests/memfile_lease_mgr.h

@@ -0,0 +1,228 @@
+// Copyright (C) 2012 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MEMFILE_LEASE_MGR_H
+#define MEMFILE_LEASE_MGR_H
+
+#include <dhcp/lease_mgr.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/member.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// This is a concrete implementation of a Lease database.
+//
+// It is for testing purposes only. It is NOT a production code.
+//
+// It does not do anything useful now, and is used for abstract LeaseMgr
+// class testing. It may later evolve into more useful backend if the
+// need arises. We can reuse code from memfile benchmark. See code in
+// tests/tools/dhcp-ubench/memfile_bench.{cc|h}
+class Memfile_LeaseMgr : public LeaseMgr {
+public:
+
+    /// @brief The sole lease manager constructor
+    ///
+    /// dbconfig is a generic way of passing parameters. Parameters
+    /// are passed in the "name=value" format, separated by spaces.
+    /// Values may be enclosed in double quotes, if needed.
+    ///
+    /// @param dbconfig database configuration
+    Memfile_LeaseMgr(const std::string& dbconfig);
+
+    /// @brief Destructor (closes file)
+    virtual ~Memfile_LeaseMgr();
+
+    /// @brief Adds an IPv4 lease.
+    ///
+    /// @todo Not implemented yet
+    /// @param lease lease to be added
+    virtual bool addLease(const Lease4Ptr& lease);
+
+    /// @brief Adds an IPv6 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(const Lease6Ptr& lease);
+
+    /// @brief Returns existing IPv4 lease for specified IPv4 address.
+    ///
+    /// @todo Not implemented yet
+    /// @param addr address of the searched lease
+    ///
+    /// @return a collection of leases
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
+
+    /// @brief Returns existing IPv4 lease for specific address and subnet
+    ///
+    /// @todo Not implemented yet
+    /// @param addr address of the searched lease
+    /// @param subnet_id ID of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address.
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param hwaddr hardware address of the client
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address
+    ///        and a subnet
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param hwaddr hardware address of the client
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// @param clientid client identifier
+    virtual Lease4Collection getLease4(const ClientId& clientid) const;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// @param clientid client identifier
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const ClientId& clientid,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv6 lease for a given IPv6 address.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    ///
+    /// @return collection of IPv6 leases
+    Lease6Collection getLease6(const DUID& duid, uint32_t iaid) const;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    /// @param subnet_id identifier of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    Lease6Ptr getLease6(const DUID& duid, uint32_t iaid, SubnetID subnet_id) const;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    void updateLease4(const Lease4Ptr& lease4);
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    void updateLease6(const Lease6Ptr& lease6);
+
+    /// @brief Deletes a lease.
+    ///
+    /// @todo Not implemented yet
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    bool deleteLease4(uint32_t addr);
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    bool deleteLease6(const isc::asiolink::IOAddress& addr);
+
+    /// @brief Returns backend name.
+    ///
+    /// Each backend have specific name, e.g. "mysql" or "sqlite".
+    std::string getName() const { return ("memfile"); }
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    std::string getDescription() const;
+
+    /// @brief Returns backend version.
+    std::string getVersion() const { return ("test-version"); }
+
+    using LeaseMgr::getParameter;
+
+protected:
+
+    typedef boost::multi_index_container< // this is a multi-index container...
+    Lease6Ptr, // it will hold shared_ptr to leases6
+        boost::multi_index::indexed_by< // and will be sorted by
+            // IPv6 address that are unique. That particular key is a member
+            // of the Lease6 structure, is of type IOAddress and can be accessed
+            // by doing &Lease6::addr_
+            boost::multi_index::ordered_unique< 
+                boost::multi_index::member<Lease6, isc::asiolink::IOAddress, &Lease6::addr_> 
+            >
+        >
+    > Lease6Storage; // Let the whole contraption be called Lease6Storage.
+
+    Lease6Storage storage6_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // MEMFILE_LEASE_MGR_H

+ 0 - 1
src/lib/dhcp/tests/pool_unittest.cc

@@ -179,4 +179,3 @@ TEST(Pool6Test, unique_id) {
 }
 
 }; // end of anonymous namespace
-

+ 71 - 1
src/lib/dhcp/tests/subnet_unittest.cc

@@ -123,6 +123,41 @@ TEST(Subnet4Test, addInvalidOption) {
     EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
 }
 
+// This test verifies that inRange() and inPool() methods work properly.
+TEST(Subnet4Test, inRangeinPool) {
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.0.0"), 8, 1, 2, 3));
+
+    // this one is in subnet
+    Pool4Ptr pool1(new Pool4(IOAddress("192.2.0.0"), 16));
+    subnet->addPool4(pool1);
+
+    // 192.1.1.1 belongs to the subnet...
+    EXPECT_TRUE(subnet->inRange(IOAddress("192.1.1.1")));
+
+    // ... but it does not belong to any pool within
+    EXPECT_FALSE(subnet->inPool(IOAddress("192.1.1.1")));
+
+    // the last address that is in range, but out of pool
+    EXPECT_TRUE(subnet->inRange(IOAddress("192.1.255.255")));
+    EXPECT_FALSE(subnet->inPool(IOAddress("192.1.255.255")));
+
+    // the first address that is in range, in pool
+    EXPECT_TRUE(subnet->inRange(IOAddress("192.2.0.0")));
+    EXPECT_TRUE (subnet->inPool(IOAddress("192.2.0.0")));
+
+    // let's try something in the middle as well
+    EXPECT_TRUE(subnet->inRange(IOAddress("192.2.3.4")));
+    EXPECT_TRUE (subnet->inPool(IOAddress("192.2.3.4")));
+
+    // the last address that is in range, in pool
+    EXPECT_TRUE(subnet->inRange(IOAddress("192.2.255.255")));
+    EXPECT_TRUE (subnet->inPool(IOAddress("192.2.255.255")));
+
+    // the first address that is in range, but out of pool
+    EXPECT_TRUE(subnet->inRange(IOAddress("192.3.0.0")));
+    EXPECT_FALSE(subnet->inPool(IOAddress("192.3.0.0")));
+}
+
 // Tests for Subnet6
 
 TEST(Subnet6Test, constructor) {
@@ -180,7 +215,6 @@ TEST(Subnet6Test, Pool6InSubnet6) {
     mypool = subnet->getPool6(IOAddress("2001:db8:1:3::dead:beef"));
 
     EXPECT_EQ(mypool, pool3);
-
 }
 
 TEST(Subnet6Test, Subnet6_Pool6_checks) {
@@ -348,4 +382,40 @@ TEST(Subnet6Test, addPersistentOption) {
     options = subnet->getOptions();
     EXPECT_EQ(0, options.size());
 }
+
+// This test verifies that inRange() and inPool() methods work properly.
+TEST(Subnet6Test, inRangeinPool) {
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
+
+    // this one is in subnet
+    Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::10"),
+                             IOAddress("2001:db8::20")));
+    subnet->addPool6(pool1);
+
+    // 192.1.1.1 belongs to the subnet...
+    EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
+    // ... but it does not belong to any pool within
+    EXPECT_FALSE(subnet->inPool(IOAddress("2001:db8::1")));
+
+    // the last address that is in range, but out of pool
+    EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::f")));
+    EXPECT_FALSE(subnet->inPool(IOAddress("2001:db8::f")));
+
+    // the first address that is in range, in pool
+    EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::10")));
+    EXPECT_TRUE (subnet->inPool(IOAddress("2001:db8::10")));
+
+    // let's try something in the middle as well
+    EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::18")));
+    EXPECT_TRUE (subnet->inPool(IOAddress("2001:db8::18")));
+
+    // the last address that is in range, in pool
+    EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::20")));
+    EXPECT_TRUE (subnet->inPool(IOAddress("2001:db8::20")));
+
+    // the first address that is in range, but out of pool
+    EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::21")));
+    EXPECT_FALSE(subnet->inPool(IOAddress("2001:db8::21")));
+}
+
 };