Browse Source

[master] Merge branch 'trac3561'

Marcin Siodelski 10 years ago
parent
commit
faac5e9746

+ 1 - 0
doc/devel/mainpage.dox

@@ -88,6 +88,7 @@
  * - @subpage libdhcpsrv
  *   - @subpage leasemgr
  *   - @subpage cfgmgr
+ *   - @subpage hostmgr
  *   - @subpage optionsConfig
  *   - @subpage allocengine
  * - @subpage libdhcp_ddns

+ 2 - 0
src/lib/dhcpsrv/Makefile.am

@@ -64,6 +64,7 @@ libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += host_container.h
+libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
 libkea_dhcpsrv_la_SOURCES += host_reservation_parser.cc host_reservation_parser.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
@@ -87,6 +88,7 @@ libkea_dhcpsrv_la_SOURCES += subnet_id.h
 libkea_dhcpsrv_la_SOURCES += subnet_selector.h
 libkea_dhcpsrv_la_SOURCES += triplet.h
 libkea_dhcpsrv_la_SOURCES += utils.h
+libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h
 
 nodist_libkea_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
 

+ 0 - 71
src/lib/dhcpsrv/base_host_data_source.h

@@ -76,22 +76,6 @@ public:
     virtual ConstHostCollection
     getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const = 0;
 
-    /// @brief Non-const version of the @c getAll const method.
-    ///
-    /// Specifying both hardware address and DUID is allowed for this method
-    /// and results in returning all objects that are associated with hardware
-    /// address OR duid. For example: if one host is associated with the
-    /// specified hardware address and another host is associated with the
-    /// specified DUID, two hosts will be returned.
-    ///
-    /// @param hwaddr HW address of the client or NULL if no HW address
-    /// available.
-    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
-    ///
-    /// @return Collection of non-const @c Host objects.
-    virtual HostCollection
-    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) = 0;
-
     /// @brief Returns a collection of hosts using the specified IPv4 address.
     ///
     /// This method may return multiple @c Host objects if they are connected
@@ -103,17 +87,6 @@ public:
     virtual ConstHostCollection
     getAll4(const asiolink::IOAddress& address) const = 0;
 
-    /// @brief Returns a collection of hosts using the specified IPv4 address.
-    ///
-    /// This method may return multiple @c Host objects if they are connected
-    /// to different subnets.
-    ///
-    /// @param address IPv4 address for which the @c Host object is searched.
-    ///
-    /// @return Collection of @c Host objects.
-    virtual HostCollection
-    getAll4(const asiolink::IOAddress& address) = 0;
-
     /// @brief Returns a host connected to the IPv4 subnet.
     ///
     /// Implementations of this method should guard against the case when
@@ -132,24 +105,6 @@ public:
     get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
          const DuidPtr& duid = DuidPtr()) const = 0;
 
-    /// @brief Returns a host connected to the IPv4 subnet.
-    ///
-    /// Implementations of this method should guard against the case when
-    /// mutliple instances of the @c Host are present, e.g. when two
-    /// @c Host objects are found, one for the DUID, another one for the
-    /// HW address. In such case, an implementation of this method
-    /// should throw an exception.
-    ///
-    /// @param subnet_id Subnet identifier.
-    /// @param hwaddr HW address of the client or NULL if no HW address
-    /// available.
-    /// @param duid client id or NULL if not available.
-    ///
-    /// @return Non-const @c Host object using a specified HW address or DUID.
-    virtual HostPtr
-    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
-         const DuidPtr& duid = DuidPtr()) = 0;
-
     /// @brief Returns a host connected to the IPv6 subnet.
     ///
     /// Implementations of this method should guard against the case when
@@ -168,23 +123,6 @@ public:
     get6(const SubnetID& subnet_id, const DuidPtr& duid,
          const HWAddrPtr& hwaddr = HWAddrPtr()) const = 0;
 
-    /// @brief Returns a host connected to the IPv6 subnet.
-    ///
-    /// Implementations of this method should guard against the case when
-    /// mutliple instances of the @c Host are present, e.g. when two
-    /// @c Host objects are found, one for the DUID, another one for the
-    /// HW address. In such case, an implementation of this method
-    /// should throw an exception.
-    ///
-    /// @param subnet_id Subnet identifier.
-    /// @param hwaddr HW address of the client or NULL if no HW address
-    /// available.
-    /// @param duid DUID or NULL if not available.
-    ///
-    /// @return Non-const @c Host object using a specified HW address or DUID.
-    virtual HostPtr
-    get6(const SubnetID& subnet_id, const DuidPtr& duid,
-         const HWAddrPtr& hwaddr = HWAddrPtr()) = 0;
 
     /// @brief Returns a host using the specified IPv6 prefix.
     ///
@@ -195,15 +133,6 @@ public:
     virtual ConstHostPtr
     get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const = 0;
 
-    /// @brief Returns a host using the specified IPv6 prefix.
-    ///
-    /// @param prefix IPv6 prefix for which the @c Host object is searched.
-    /// @param prefix_len IPv6 prefix length.
-    ///
-    /// @return Non-const @c Host object using a specified HW address or DUID.
-    virtual HostPtr
-    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) = 0;
-
     /// @brief Adds a new host to the collection.
     ///
     /// The implementations of this method should guard against duplicate

+ 10 - 4
src/lib/dhcpsrv/cfg_hosts.cc

@@ -165,12 +165,17 @@ CfgHosts::add(const HostPtr& host) {
         isc_throw(BadValue, "specified host object must not be NULL when it"
                   " is added to the configuration");
     }
-    /// @todo This may need further sanity checks. For example, a duplicate
-    /// should be rejected.
+    // At least one subnet ID must be non-zero
+    if (host->getIPv4SubnetID() == 0 && host->getIPv6SubnetID() == 0) {
+        isc_throw(BadValue, "must not use both IPv4 and IPv6 subnet ids of"
+                  " 0 when adding new host reservation");
+    }
+    /// @todo This may need further sanity checks.
     HWAddrPtr hwaddr = host->getHWAddress();
     DuidPtr duid = host->getDuid();
     // Check for duplicates for the specified IPv4 subnet.
-    if (get4(host->getIPv4SubnetID(), hwaddr, duid)) {
+    if ((host->getIPv4SubnetID() > 0) &&
+        get4(host->getIPv4SubnetID(), hwaddr, duid)) {
         isc_throw(DuplicateHost, "failed to add new host using the HW"
                   " address '" << (hwaddr ? hwaddr->toText(false) : "(null)")
                   << " and DUID '" << (duid ? duid->toText() : "(null)")
@@ -178,7 +183,8 @@ CfgHosts::add(const HostPtr& host) {
                   << "' as this host has already been added");
 
     // Checek for duplicates for the specified IPv6 subnet.
-    } else if (get6(host->getIPv6SubnetID(), duid, hwaddr)) {
+    } else if (host->getIPv6SubnetID() &&
+               get6(host->getIPv6SubnetID(), duid, hwaddr)) {
         isc_throw(DuplicateHost, "failed to add new host using the HW"
                   " address '" << (hwaddr ? hwaddr->toText(false) : "(null)")
                   << " and DUID '" << (duid ? duid->toText() : "(null)")

+ 2 - 2
src/lib/dhcpsrv/cfg_hosts.h

@@ -18,10 +18,10 @@
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
-#include <dhcpsrv/base_host_data_source.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/host_container.h>
 #include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/writable_host_data_source.h>
 #include <boost/shared_ptr.hpp>
 #include <vector>
 
@@ -42,7 +42,7 @@ namespace dhcp {
 /// when the new configuration is applied for the server. The reservations
 /// are retrieved by the @c HostMgr class when the server is allocating or
 /// renewing an address or prefix for the particular client.
-class CfgHosts : public BaseHostDataSource {
+class CfgHosts : public WritableHostDataSource {
 public:
 
     /// @brief Return all hosts for the specified HW address or DUID.

+ 116 - 0
src/lib/dhcpsrv/host_mgr.cc

@@ -0,0 +1,116 @@
+// Copyright (C) 2014 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 <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host_mgr.h>
+
+namespace {
+
+/// @brief Convenience function returning a pointer to the hosts configuration.
+///
+/// This function is called by the @c HostMgr methods requiring access to the
+/// host reservations specified in the DHCP server configuration.
+///
+/// @return A pointer to the const hosts reservation configuration.
+isc::dhcp::ConstCfgHostsPtr getCfgHosts() {
+    return (isc::dhcp::CfgMgr::instance().getCurrentCfg()->getCfgHosts());
+}
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+using namespace isc::asiolink;
+
+boost::scoped_ptr<HostMgr>&
+HostMgr::getHostMgrPtr() {
+    static boost::scoped_ptr<HostMgr> host_mgr_ptr;
+    return (host_mgr_ptr);
+}
+
+void
+HostMgr::create(const std::string&) {
+    getHostMgrPtr().reset(new HostMgr());
+
+    /// @todo Initialize alternate_source here, using the parameter.
+    /// For example: alternate_source.reset(new MysqlHostDataSource(access)).
+}
+
+HostMgr&
+HostMgr::instance() {
+    boost::scoped_ptr<HostMgr>& host_mgr_ptr = getHostMgrPtr();
+    if (!host_mgr_ptr) {
+        create();
+    }
+    return (*host_mgr_ptr);
+}
+
+ConstHostCollection
+HostMgr::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    ConstHostCollection hosts = getCfgHosts()->getAll(hwaddr, duid);
+    if (alternate_source) {
+        ConstHostCollection hosts_plus = alternate_source->getAll(hwaddr, duid);
+        hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+    }
+    return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAll4(const IOAddress& address) const {
+    ConstHostCollection hosts = getCfgHosts()->getAll4(address);
+    if (alternate_source) {
+        ConstHostCollection hosts_plus = alternate_source->getAll4(address);
+        hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+    }
+    return (hosts);
+}
+
+ConstHostPtr
+HostMgr::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+              const DuidPtr& duid) const {
+    ConstHostPtr host = getCfgHosts()->get4(subnet_id, hwaddr, duid);
+    if (!host && alternate_source) {
+        host = alternate_source->get4(subnet_id, hwaddr, duid);
+    }
+    return (host);
+}
+
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+               const HWAddrPtr& hwaddr) const {
+    ConstHostPtr host = getCfgHosts()->get6(subnet_id, duid, hwaddr);
+    if (!host && alternate_source) {
+        host = alternate_source->get6(subnet_id, duid, hwaddr);
+    }
+    return (host);
+}
+
+ConstHostPtr
+HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
+    ConstHostPtr host = getCfgHosts()->get6(prefix, prefix_len);
+    if (!host && alternate_source) {
+        host = alternate_source->get6(prefix, prefix_len);
+    }
+    return (host);
+}
+
+void
+HostMgr::add(const HostPtr&) {
+    isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

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

@@ -0,0 +1,190 @@
+// Copyright (C) 2014 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 HOST_MGR_H
+#define HOST_MGR_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Host Manager.
+///
+/// This is a singleton class which provides access to multiple sources of
+/// information about static host reservations. These sources are also referred
+/// to as host data sources. Each source derives (directly or indirectly) from
+/// the @c BaseHostDataSource.
+///
+/// The @c HostMgr is a central point for providing information about the host
+/// reservations. Internally, it relays the queries (calls to the appropriate
+/// methods declared in the @c BaseHostDataSource) to the data sources it is
+/// connected to. The @c HostMgr is always connected to the server's
+/// configuration, accessible through the @c CfgHosts object in the @c CfgMgr.
+/// The @c CfgHosts object holds all reservations specified in the DHCP server
+/// configuration file. If a particular reservation is not found in the
+/// @c CfgHosts object, the @c HostMgr will try to find it using the alternate
+/// host data storage. The alternate host data storage is usually a database
+/// (e.g. SQL database), accessible through a dedicated host data source
+/// object (a.k.a. database backend). This datasource is responsible for
+/// managing the connection with the database and forming appropriate queries
+/// to retrieve (or update) the information about the reservations.
+///
+/// The use of the alternate host data source is optional and usually requires
+/// additional configuration to be specified by the server administrator.
+/// For example, for the SQL database the user's credentials, database address,
+/// and database name are required. The @c HostMgr passes these parameters
+/// to an appropriate datasource which is responsible for opening a connection
+/// and maintaining it.
+///
+/// It is possible to switch to a different alternate data source or disable
+/// the use of the alternate datasource, e.g. as a result of server's
+/// reconfiguration. However, the use of the primary host data source (i.e.
+/// reservations specified in the configuration file) can't be disabled.
+///
+/// @todo Implement alternate host data sources: MySQL, PostgreSQL, etc.
+class HostMgr : public boost::noncopyable, BaseHostDataSource {
+public:
+
+    /// @brief Creates new instance of the @c HostMgr.
+    ///
+    /// If an instance of the @c HostMgr already exists, it will be replaced
+    /// by the new instance. Thus, any instances of the alternate host data
+    /// sources will be dropped.
+    ///
+    /// @param access Host data source access parameters for the alternate
+    /// host data source. It holds "keyword=value" pairs, separated by spaces.
+    /// The supported values are specific to the alternate data source in use.
+    /// However, the "type" parameter will be common and it will specify which
+    /// data source is to be used. Currently, no parameters are supported
+    /// and the parameter is ignored.
+    static void create(const std::string& access = "");
+
+    /// @brief Returns a sole instance of the @c HostMgr.
+    ///
+    /// This method should be used to retrieve an instance of the @c HostMgr
+    /// to be used to gather/manage host reservations. It returns an instance
+    /// of the @c HostMgr created by the @c create method. If such instance
+    /// doesn't exist yet, it is created using the @c create method with the
+    /// default value of the data access string, which configures the host
+    /// manager to not use the alternate host data source.
+    static HostMgr& instance();
+
+    /// @brief Returns all hosts for the specified HW address or DUID.
+    ///
+    /// This method returns all @c Host objects representing reservations for
+    /// the specified HW address or/and DUID as documented in the
+    /// @c BaseHostDataSource::getAll.
+    ///
+    /// It retrieves reservations from both primary and alternate host data
+    /// source as a single collection of @c Host objects, i.e. if matching
+    /// reservations are in both sources, all of them are returned.
+    ///
+    /// Note that returned collection may contain duplicates. It is the
+    /// caller's responsibility to check for duplicates.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL of not available.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected to
+    /// different subnets.
+    ///
+    /// If matching reservations are both in the primary and the alternate
+    /// data source, all of them are returned.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// This method returns a single reservation for the particular host
+    /// (identified by the HW address or DUID) as documented in the
+    /// @c BaseHostDataSource::get4.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// This method returns a host connected to the IPv6 subnet and identified
+    /// by the HW address or DUID, as described in the
+    /// @c BaseHostDataSource::get6.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// This method returns a host using specified IPv6 prefix, as described
+    /// in the @c BaseHostDataSource::get6.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+    /// @brief Adds a new host to the alternate data source.
+    ///
+    /// This method will throw an exception if no alternate data source is
+    /// in use.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    virtual void add(const HostPtr& host);
+
+private:
+
+    /// @brief Private default constructor.
+    HostMgr() { }
+
+    /// @brief Pointer to an alternate host data source.
+    ///
+    /// If this pointer is NULL, the source is not in use.
+    boost::scoped_ptr<BaseHostDataSource> alternate_source;
+
+    /// @brief Returns a pointer to the currently used instance of the
+    /// @c HostMgr.
+    static boost::scoped_ptr<HostMgr>& getHostMgrPtr();
+
+};
+}
+}
+
+#endif // HOST_MGR_H

+ 34 - 1
src/lib/dhcpsrv/libdhcpsrv.dox

@@ -81,6 +81,39 @@ one that occurred before it etc.
 the \ref isc::dhcp::SrvConfig object. Kea developers are actively working
 on migrating the other configuration parameters to it.
 
+@section hostmgr Host Manager
+
+Host Manager implemented by the \ref isc::dhcp::HostMgr is a singleton object
+which provides means to retrieve resources statically assigned to the DHCP
+clients, such as IP addresses, prefixes or hostnames. The statically assigned
+resources are called reservations (or host reservations) and they are
+represented in the code by the \ref isc::dhcp::Host class.
+
+The reservations can be specified in the configuration file or in some
+other storage (typically in a database). A dedicated object, called
+host data source, is needed to retrieve the host reservations from the
+database. This object must implement the \ref isc::dhcp::BaseHostDataSource
+interface and its implementation is specific to the type of storage
+holding the reservations. For example, the host data source managing
+host reservations in the MySQL database is required to establish
+connection to the MySQL databse and issue specific queries. Once
+implemented, the \ref isc::dhcp::HostMgr::create method must be updated
+to create an instance of this datasource. Note, that this instance is
+created as "alternate host data source" as opposed to the primary data
+source which returns host reservations specified in the configuration file.
+The primary data source is implemented internally in the
+\ref isc::dhcp::HostMgr and uses the configuration data structures held by
+the \ref isc::dhcp::CfgMgr to retrieve the reservations. In general, the
+\ref isc::dhcp::HostMgr first searches for the reservations using the
+primary data source and falls back to the use of alternate data source
+when nothing has been found. For those methods which are meant to return
+multiple reservations (e.g. find all reservations for the particular
+client), the \ref isc::dhcp::HostMgr will use both primary and alternate
+data source (if present) and concatenate results.
+
+For more information about the \ref isc::dhcp::HostMgr please refer to its
+documentation.
+
 @section optionsConfig Options Configuration Information
 
 The \ref isc::dhcp::CfgOption object holds a collection of options being
@@ -117,7 +150,7 @@ 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
+There is no single best solution 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

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -68,6 +68,7 @@ libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_reservation_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h

+ 25 - 16
src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc

@@ -99,11 +99,11 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
     for (int i = 0; i < 25; ++i) {
         cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
                                  "hw-address",
-                                 SubnetID(i % 10), SubnetID(i % 5),
+                                 SubnetID(i % 10 + 1), SubnetID(i % 5 + 1),
                                  IOAddress("192.0.2.5"))));
 
         cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
-                                 SubnetID(i % 5), SubnetID(i % 10),
+                                 SubnetID(i % 5 + 1), SubnetID(i % 10 + 1),
                                  IOAddress("192.0.2.10"))));
 
     }
@@ -115,14 +115,14 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
         // points to a host for which the reservation hasn't been added.
         HostCollection hosts = cfg.getAll(hwaddrs_[i], duids_[i + 25]);
         ASSERT_EQ(1, hosts.size());
-        EXPECT_EQ(i % 10, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ(i % 10 + 1, hosts[0]->getIPv4SubnetID());
         EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
 
         // Get host identified by DUID. The HW address is non-null but it
         // points to a host for which the reservation hasn't been added.
         hosts = cfg.getAll(hwaddrs_[i + 25], duids_[i]);
         ASSERT_EQ(1, hosts.size());
-        EXPECT_EQ(i % 5, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ(i % 5 + 1, hosts[0]->getIPv4SubnetID());
         EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
     }
 
@@ -292,6 +292,15 @@ TEST_F(CfgHostsTest, get6) {
     EXPECT_THROW(cfg.get6(SubnetID(1), duids_[0], hwaddrs_[0]), DuplicateHost);
 }
 
+TEST_F(CfgHostsTest, zeroSubnetIDs) {
+    CfgHosts cfg;
+    ASSERT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                          "hw-address",
+                                          SubnetID(0), SubnetID(0),
+                                          IOAddress("10.0.0.1")))),
+                 isc::BadValue);
+}
+
 // This test verifies that it is not possible to add the same Host to the
 // same IPv4 subnet twice.
 TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
@@ -299,21 +308,21 @@ TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                              "hw-address",
-                                             SubnetID(10), SubnetID(1),
+                                             SubnetID(10), SubnetID(0),
                                              IOAddress("10.0.0.1")))));
 
     // Try to add the host with the same HW address to the same subnet. The fact
     // that the IP address is different here shouldn't really matter.
     EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                           "hw-address",
-                                          SubnetID(10), SubnetID(12),
+                                          SubnetID(10), SubnetID(0),
                                           IOAddress("10.0.0.10")))),
                  isc::dhcp::DuplicateHost);
 
     // Now try to add it to a different subnet. It should go through.
     EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                              "hw-address",
-                                             SubnetID(11), SubnetID(12),
+                                             SubnetID(11), SubnetID(0),
                                              IOAddress("10.0.0.10")))));
 }
 
@@ -324,21 +333,21 @@ TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                              "duid",
-                                             SubnetID(10), SubnetID(1),
+                                             SubnetID(10), SubnetID(0),
                                              IOAddress("10.0.0.1")))));
 
     // Try to add the host with the same DUID to the same subnet. The fact
     // that the IP address is different here shouldn't really matter.
     EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                           "duid",
-                                          SubnetID(10), SubnetID(12),
+                                          SubnetID(10), SubnetID(0),
                                           IOAddress("10.0.0.10")))),
                  isc::dhcp::DuplicateHost);
 
     // Now try to add it to a different subnet. It should go through.
     EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                              "duid",
-                                             SubnetID(11), SubnetID(12),
+                                             SubnetID(11), SubnetID(0),
                                              IOAddress("10.0.0.10")))));
 }
 
@@ -349,21 +358,21 @@ TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                              "hw-address",
-                                             SubnetID(10), SubnetID(1),
+                                             SubnetID(0), SubnetID(1),
                                              IOAddress("0.0.0.0")))));
 
     // Try to add the host with the same HW address to the same subnet. The fact
     // that the IP address is different here shouldn't really matter.
     EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                           "hw-address",
-                                          SubnetID(11), SubnetID(1),
+                                          SubnetID(0), SubnetID(1),
                                           IOAddress("0.0.0.0")))),
                  isc::dhcp::DuplicateHost);
 
     // Now try to add it to a different subnet. It should go through.
     EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                              "hw-address",
-                                             SubnetID(11), SubnetID(2),
+                                             SubnetID(0), SubnetID(2),
                                              IOAddress("0.0.0.0")))));
 }
 
@@ -374,21 +383,21 @@ TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                              "duid",
-                                             SubnetID(10), SubnetID(1),
+                                             SubnetID(0), SubnetID(1),
                                              IOAddress("0.0.0.0")))));
 
     // Try to add the host with the same DUID to the same subnet. The fact
     // that the IP address is different here shouldn't really matter.
     EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                           "duid",
-                                          SubnetID(11), SubnetID(1),
+                                          SubnetID(0), SubnetID(1),
                                           IOAddress("0.0.0.0")))),
                  isc::dhcp::DuplicateHost);
 
     // Now try to add it to a different subnet. It should go through.
     EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                              "duid",
-                                             SubnetID(11), SubnetID(2),
+                                             SubnetID(0), SubnetID(2),
                                              IOAddress("0.0.0.0")))));
 }
 

+ 230 - 0
src/lib/dhcpsrv/tests/host_mgr_unittest.cc

@@ -0,0 +1,230 @@
+// Copyright (C) 2014 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 <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_mgr.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test fixture class for @c HostMgr class.
+class HostMgrTest : public ::testing::Test {
+protected:
+
+    /// @brief Prepares the class for a test.
+    ///
+    /// This method crates a handful of unique HW address and DUID objects
+    /// for use in unit tests. These objects are held in the @c hwaddrs_ and
+    /// @c duids_ members respectively.
+    ///
+    /// This method also resets the @c CfgMgr configuration and re-creates
+    /// the @c HostMgr object.
+    virtual void SetUp();
+
+    /// @brief Convenience method returning a pointer to the @c CfgHosts object
+    /// in the @c CfgMgr.
+    CfgHostsPtr getCfgHosts() const;
+
+    /// @brief HW addresses to be used by the tests.
+    std::vector<HWAddrPtr> hwaddrs_;
+    /// @brief DUIDs to be used by the tests.
+    std::vector<DuidPtr> duids_;
+};
+
+void
+HostMgrTest::SetUp() {
+    // Remove all configuration which may be dangling from the previous test.
+    CfgMgr::instance().clear();
+    // Recreate HostMgr instance. It drops any previous state.
+    HostMgr::create();
+    // Create HW addresses from the template.
+    const uint8_t mac_template[] = {
+        0x01, 0x02, 0x0A, 0xBB, 0x03, 0x00
+    };
+    for (int i = 0; i < 10; ++i) {
+        std::vector<uint8_t> vec(mac_template,
+                                 mac_template + sizeof(mac_template));
+        vec[vec.size() - 1] = i;
+        HWAddrPtr hwaddr(new HWAddr(vec, HTYPE_ETHER));
+        hwaddrs_.push_back(hwaddr);
+    }
+    // Create DUIDs from the template.
+    const uint8_t duid_template[] = {
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00
+    };
+    for (int i = 0; i < 10; ++i) {
+        std::vector<uint8_t> vec(duid_template,
+                                 duid_template + sizeof(mac_template));
+        vec[vec.size() - 1] = i;
+        DuidPtr duid(new DUID(vec));
+        duids_.push_back(duid);
+    }
+}
+
+CfgHostsPtr
+HostMgrTest::getCfgHosts() const {
+    return (CfgMgr::instance().getStagingCfg()->getCfgHosts());
+}
+
+/// This test verifies that HostMgr returns all reservations for the
+/// specified HW address. The reservations are defined in the server's
+/// configuration.
+TEST_F(HostMgrTest, getAll) {
+    // Initially, no reservations should be present.
+    ConstHostCollection hosts = HostMgr::instance().getAll(hwaddrs_[0]);
+    ASSERT_TRUE(hosts.empty());
+
+    // Add two reservations for the same HW address. They differ by the IP
+    // address reserved and the IPv4 subnet.
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                        "hw-address", SubnetID(1), SubnetID(0),
+                                        IOAddress("192.0.2.5"))));
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                        "hw-address", SubnetID(10), SubnetID(0),
+                                        IOAddress("192.0.3.10"))));
+    CfgMgr::instance().commit();
+
+    // If there non-matching HW address is specified, nothing should be
+    // returned.
+    ASSERT_TRUE(HostMgr::instance().getAll(hwaddrs_[1]).empty());
+    // For the correct HW address, there should be two reservations.
+    hosts = HostMgr::instance().getAll(hwaddrs_[0]);
+    ASSERT_EQ(2, hosts.size());
+
+    // We don't know the order in which the reservations are returned so
+    // we have to match with any of the two reservations returned.
+
+    // Look for the first reservation.
+    bool found = false;
+    for (int i = 0; i < 2; ++i) {
+        if (hosts[0]->getIPv4Reservation() == IOAddress("192.0.2.5")) {
+            ASSERT_EQ(1, hosts[0]->getIPv4SubnetID());
+            found = true;
+        }
+    }
+    if (!found) {
+        ADD_FAILURE() << "Reservation for the IPv4 address 192.0.2.5"
+            " not found using getAll method";
+    }
+
+    // Look for the second reservation.
+    found = false;
+    for (int i = 0; i < 2; ++i) {
+        if (hosts[1]->getIPv4Reservation() == IOAddress("192.0.3.10")) {
+            ASSERT_EQ(10, hosts[1]->getIPv4SubnetID());
+            found = true;
+        }
+    }
+    if (!found) {
+        ADD_FAILURE() << "Reservation for the IPv4 address 192.0.3.10"
+            " not found using getAll method";
+    }
+
+}
+
+// This test verifies that it is possible to gather all reservations for the
+// specified IPv4 address from the HostMgr. The reservations are specified in
+// the server's configuration. Note: this test is currently disabled because the
+// getAll4 method is not implemented in the CfgHosts object.
+TEST_F(HostMgrTest, DISABLED_getAll4) {
+    ConstHostCollection hosts =
+        HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(hosts.empty());
+
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                        "hw-address", SubnetID(1), SubnetID(0),
+                                        IOAddress("192.0.2.5"))));
+
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[1]->toText(false),
+                                        "hw-address", SubnetID(10), SubnetID(0),
+                                        IOAddress("192.0.2.5"))));
+    CfgMgr::instance().commit();
+
+    hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
+    ASSERT_EQ(2, hosts.size());
+
+    /// @todo Extend this test to sanity check the hosts, once the test
+    /// is enabled.
+}
+
+// This test verifies that it is possible to retrieve a reservation for the
+// particular host using HostMgr. The reservation is specified in the server's
+// configuration.
+TEST_F(HostMgrTest, get4) {
+    ConstHostPtr host = HostMgr::instance().get4(SubnetID(1), hwaddrs_[0]);
+    ASSERT_FALSE(host);
+
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                        "hw-address",
+                                        SubnetID(1), SubnetID(2),
+                                        IOAddress("192.0.2.5"))));
+    CfgMgr::instance().commit();
+
+    host = HostMgr::instance().get4(SubnetID(1), hwaddrs_[0]);
+    ASSERT_TRUE(host);
+    EXPECT_EQ(1, host->getIPv4SubnetID());
+    EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+}
+
+// This test verifies that it is possible to retrieve IPv6 reservations for
+// the particular host using HostMgr. The reservation is specified in the
+// server's configuration.
+TEST_F(HostMgrTest, get6) {
+    ConstHostPtr host = HostMgr::instance().get6(SubnetID(2), duids_[0]);
+    ASSERT_FALSE(host);
+
+    HostPtr new_host(new Host(duids_[0]->toText(), "duid", SubnetID(1),
+                              SubnetID(2), IOAddress("0.0.0.0")));
+    new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       IOAddress("2001:db8:1::1")));
+    getCfgHosts()->add(new_host);
+    CfgMgr::instance().commit();
+
+    host = HostMgr::instance().get6(SubnetID(2), duids_[0]);
+    ASSERT_TRUE(host);
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                               IOAddress("2001:db8:1::1"))));
+}
+
+// This test verifies that it is possible to retrieve the reservation of the
+// particular IPv6 prefix using HostMgr. Note: this test is currently disabled
+// because the get6(prefix, prefix_len) method is not implemented in the
+// CfgHosts class.
+TEST_F(HostMgrTest, DISABLED_get6ByPrefix) {
+    ConstHostPtr host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
+    ASSERT_FALSE(host);
+
+    HostPtr new_host(new Host(duids_[0]->toText(), "duid", SubnetID(1),
+                              SubnetID(2), IOAddress("0.0.0.0")));
+    new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                       IOAddress("2001:db8:1::"), 64));
+    getCfgHosts()->add(new_host);
+    CfgMgr::instance().commit();
+
+    host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
+    ASSERT_TRUE(host);
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+    IOAddress("2001:db8:1::"), 64)));
+}
+
+} // end of anonymous namespace

+ 112 - 0
src/lib/dhcpsrv/writable_host_data_source.h

@@ -0,0 +1,112 @@
+// Copyright (C) 2014 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 WRITABLE_HOST_DATA_SOURCE_H
+#define WRITABLE_HOST_DATA_SOURCE_H
+
+#include <dhcpsrv/base_host_data_source.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Interface for retrieving writable host reservations.
+///
+/// This interface extends the @c BaseHostDataSource with methods which return
+/// pointers to the @c Host objects, which can be modified.
+class WritableHostDataSource : public BaseHostDataSource {
+public:
+
+    using BaseHostDataSource::getAll;
+    using BaseHostDataSource::getAll4;
+    using BaseHostDataSource::get4;
+    using BaseHostDataSource::get6;
+
+    /// @brief Non-const version of the @c getAll const method.
+    ///
+    /// Specifying both hardware address and DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one host is associated with the
+    /// specified hardware address and another host is associated with the
+    /// specified DUID, two hosts will be returned.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of non-const @c Host objects.
+    virtual HostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) = 0;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of @c Host objects.
+    virtual HostCollection
+    getAll4(const asiolink::IOAddress& address) = 0;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) = 0;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) = 0;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) = 0;
+
+};
+
+}
+}
+
+#endif // WRITABLE_HOST_DATA_SOURCE_H