Parcourir la source

[3561] Implemented basic host manager gathering hosts from the config.

Marcin Siodelski il y a 10 ans
Parent
commit
9b2b59ba5f

+ 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.

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

@@ -0,0 +1,91 @@
+// 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());
+}
+
+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 {
+    return (getCfgHosts()->getAll(hwaddr, duid));
+}
+
+ConstHostCollection
+HostMgr::getAll4(const IOAddress& address) const {
+    return (getCfgHosts()->getAll4(address));
+}
+
+ConstHostPtr
+HostMgr::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+              const DuidPtr& duid) const {
+    return (getCfgHosts()->get4(subnet_id, hwaddr, duid));
+}
+
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+               const HWAddrPtr& hwaddr) const {
+    return (getCfgHosts()->get6(subnet_id, duid, hwaddr));
+}
+
+ConstHostPtr
+HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
+    return (getCfgHosts()->get6(prefix, prefix_len));
+}
+
+void
+HostMgr::add(const HostPtr&) {
+    isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

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

@@ -0,0 +1,173 @@
+// 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/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 maintaing 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 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 is common and it specifies which data
+    /// source is to be used.
+    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 c
+    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 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. Note that this
+    /// 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.
+    ///
+    /// @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 Pointer to the new @c Host object being added.
+    virtual void add(const HostPtr& host);
+
+private:
+
+    /// @brief Returns a pointer to the currently used instance of the
+    /// @c HostMgr.
+    static boost::scoped_ptr<HostMgr>& getHostMgrPtr();
+};
+}
+}
+
+#endif // HOST_MGR_H

+ 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