Parcourir la source

[master] Merge branch 'trac4302_rebase'

Marcin Siodelski il y a 9 ans
Parent
commit
3979656c91

+ 47 - 1
src/bin/admin/scripts/mysql/dhcpdb_create.mysql

@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2015 Internet Systems Consortium.
+# Copyright (C) 2012-2016 Internet Systems Consortium.
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -395,6 +395,52 @@ SET version = '4', minor = '1';
 
 # This line concludes database upgrade to version 4.1.
 
+# Update index used for searching DHCPv4 reservations by identifier and subnet id.
+# This index is now unique (to prevent duplicates) and includes DHCPv4 subnet
+# identifier.
+DROP INDEX key_dhcp4_identifier_subnet_id ON hosts;
+CREATE UNIQUE INDEX key_dhcp4_identifier_subnet_id ON hosts (dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp4_subnet_id ASC);
+
+# Update index used for searching DHCPv6 reservations by identifier and subnet id.
+# This index is now unique to prevent duplicates.
+DROP INDEX key_dhcp6_identifier_subnet_id ON hosts;
+CREATE UNIQUE INDEX key_dhcp6_identifier_subnet_id ON hosts (dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp6_subnet_id ASC);
+
+# Create index to search for reservations using IP address and subnet id.
+# This unique index guarantees that there is only one occurence of the
+# particular IPv4 address for a given subnet.
+CREATE UNIQUE INDEX key_dhcp4_ipv4_address_subnet_id ON hosts (ipv4_address ASC , dhcp4_subnet_id ASC);
+
+# Create index to search for reservations using address/prefix and prefix
+# length.
+CREATE UNIQUE INDEX key_dhcp6_address_prefix_len ON ipv6_reservations (address ASC , prefix_len ASC);
+
+# Create a table mapping host identifiers to their names. Values in this
+# table are used as a foreign key in hosts table to guarantee that only
+# identifiers present in host_identifier_type table are used in hosts
+# table.
+CREATE TABLE IF NOT EXISTS host_identifier_type (
+    type TINYINT PRIMARY KEY NOT NULL,   # Lease type code.
+    name VARCHAR(32)                     # Name of the lease type
+) ENGINE = INNODB;
+
+START TRANSACTION;
+INSERT INTO host_identifier_type VALUES (0, "hw-address"); # Non-temporary v6 addresses
+INSERT INTO host_identifier_type VALUES (1, "duid");       # Temporary v6 addresses
+INSERT INTO host_identifier_type VALUES (2, "circuit-id"); # Prefix delegations
+COMMIT;
+
+# Add a constraint that any identifier type value added to the hosts
+# must map to a value in the host_identifier_type table.
+ALTER TABLE hosts
+    ADD CONSTRAINT fk_host_identifier_type FOREIGN KEY (dhcp_identifier_type)
+    REFERENCES host_identifier_type (type);
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '4', minor = '2';
+# This line concludes database upgrade to version 4.2.
+
 # Notes:
 #
 # Indexes

+ 8 - 1
src/bin/admin/tests/mysql_tests.sh.in

@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -178,6 +178,13 @@ EOF
     ERRCODE=$?
     assert_eq 0 $ERRCODE "dhcp6_options table is missing or broken. (returned status code %d, expected %d)"
 
+    # Sixth table: host_identifier_type
+    mysql -u$db_user -p$db_password $db_name >/dev/null 2>&1 <<EOF
+    SELECT type, name FROM host_identifier_type;
+EOF
+    ERRCODE=$?
+    assert_eq 0 $ERRCODE "host_identifier_type table is missing or broken. (returned status code %d, expected %d)"
+
     # Let's wipe the whole database
     mysql_wipe
 

+ 50 - 1
src/lib/dhcpsrv/base_host_data_source.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -98,6 +98,23 @@ public:
     virtual ConstHostCollection
     getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const = 0;
 
+    /// @brief Return all hosts connected to any subnet for which reservations
+    /// have been made using a specified identifier.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for a specified identifier. This method may return multiple hosts
+    /// because a particular client may have reservations in multiple subnets.
+    ///
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin,
+           const size_t identifier_len) 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
@@ -127,6 +144,23 @@ public:
     get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
          const DuidPtr& duid = DuidPtr()) const = 0;
 
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id,
+         const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin,
+         const size_t identifier_len) const = 0;
+
     /// @brief Returns a host connected to the IPv4 subnet and having
     /// a reservation for a specified IPv4 address.
     ///
@@ -165,6 +199,21 @@ public:
     get6(const SubnetID& subnet_id, const DuidPtr& duid,
          const HWAddrPtr& hwaddr = HWAddrPtr()) const = 0;
 
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id,
+         const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin,
+         const size_t identifier_len) const = 0;
 
     /// @brief Returns a host using the specified IPv6 prefix.
     ///

+ 190 - 51
src/lib/dhcpsrv/cfg_hosts.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -34,6 +34,29 @@ CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) {
 }
 
 ConstHostCollection
+CfgHosts::getAll(const Host::IdentifierType& identifier_type,
+                 const uint8_t* identifier_begin,
+                 const size_t identifier_len) const {
+    // Do not issue logging message here because it will be logged by
+    // the getAllInternal method.
+    ConstHostCollection collection;
+    getAllInternal<ConstHostCollection>(identifier_type, identifier_begin,
+                                        identifier_len, collection);
+    return (collection);
+}
+
+HostCollection
+CfgHosts::getAll(const Host::IdentifierType& identifier_type,
+                 const uint8_t* identifier_begin, const size_t identifier_len) {
+    // Do not issue logging message here because it will be logged by
+    // the getAllInternal method.
+    HostCollection collection;
+    getAllInternal<HostCollection>(identifier_type, identifier_begin,
+                                   identifier_len, collection);
+    return (collection);
+}
+
+ConstHostCollection
 CfgHosts::getAll4(const IOAddress& address) const {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal4 method.
@@ -71,27 +94,26 @@ CfgHosts::getAll6(const IOAddress& address) {
 
 template<typename Storage>
 void
-CfgHosts::getAllInternal(const std::vector<uint8_t>& identifier,
-                         const Host::IdentifierType& identifier_type,
+CfgHosts::getAllInternal(const Host::IdentifierType& identifier_type,
+                         const uint8_t* identifier,
+                         const size_t identifier_len,
                          Storage& storage) const {
     // We will need to transform the identifier into the textual format.
     // Until we do it, we mark it as invalid.
     std::string identifier_text = "(invalid)";
-    if (!identifier.empty()) {
-        try {
-            // Use Host object to find the textual form of the identifier.
-            // This may throw exception if the identifier is invalid.
-            Host host(&identifier[0], identifier.size(), identifier_type,
-                      SubnetID(0), SubnetID(0), IOAddress::IPV4_ZERO_ADDRESS());
-            identifier_text = host.getIdentifierAsText();
-
-        } catch (...) {
-            // Suppress exception and keep using (invalid) as an
-            // identifier. We will log that the identifier is
-            // invalid and return.
-        }
-
+    try {
+        // Use Host object to find the textual form of the identifier.
+        // This may throw exception if the identifier is invalid.
+        Host host(identifier, identifier_len, identifier_type,
+                  SubnetID(0), SubnetID(0), IOAddress::IPV4_ZERO_ADDRESS());
+        identifier_text = host.getIdentifierAsText();
+
+    } catch (...) {
+        // Suppress exception and keep using (invalid) as an
+        // identifier. We will log that the identifier is
+        // invalid and return.
     }
+
     // This will log that we're invoking this function with the specified
     // identifier. The identifier may also be marked as (invalid) if it
     // had 0 length or its type is unsupported.
@@ -106,10 +128,13 @@ CfgHosts::getAllInternal(const std::vector<uint8_t>& identifier,
     // Use the identifier and identifier type as a composite key.
     const HostContainerIndex0& idx = hosts_.get<0>();
     boost::tuple<const std::vector<uint8_t>, const Host::IdentifierType> t =
-        boost::make_tuple(identifier, identifier_type);
+        boost::make_tuple(std::vector<uint8_t>(identifier,
+                                               identifier + identifier_len),
+                                               identifier_type);
 
     // Append each Host object to the storage.
-    for (HostContainerIndex0::iterator host = idx.lower_bound(t); host != idx.upper_bound(t);
+    for (HostContainerIndex0::iterator host = idx.lower_bound(t);
+         host != idx.upper_bound(t);
          ++host) {
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
                   HOSTS_CFG_GET_ALL_IDENTIFIER_HOST)
@@ -133,12 +158,14 @@ CfgHosts::getAllInternal(const HWAddrPtr& hwaddr, const DuidPtr& duid,
         .arg(duid ? duid->toText() : "(no-duid)");
 
     // Get hosts using HW address.
-    if (hwaddr) {
-        getAllInternal<Storage>(hwaddr->hwaddr_, Host::IDENT_HWADDR, storage);
+    if (hwaddr && !hwaddr->hwaddr_.empty()) {
+        getAllInternal<Storage>(Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
+                                hwaddr->hwaddr_.size(), storage);
     }
     // Get hosts using DUID.
-    if (duid) {
-        getAllInternal<Storage>(duid->getDuid(), Host::IDENT_DUID, storage);
+    if (duid && !duid->getDuid().empty()) {
+        getAllInternal<Storage>(Host::IDENT_DUID, &duid->getDuid()[0],
+                                duid->getDuid().size(), storage);
     }
 }
 
@@ -200,13 +227,23 @@ CfgHosts::getAllInternal6(const IOAddress& address, Storage& storage) const {
         .arg(storage.size());
 }
 
-
 ConstHostPtr
 CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
                const DuidPtr& duid) const {
     // Do not log here because getHostInternal logs.
     // The false value indicates that it is an IPv4 subnet.
-    return (getHostInternal(subnet_id, false, hwaddr, duid));
+    HostPtr host;
+    if (hwaddr && !hwaddr->hwaddr_.empty()) {
+        host = getHostInternal(subnet_id, false, Host::IDENT_HWADDR,
+                               &hwaddr->hwaddr_[0],
+                               hwaddr->hwaddr_.size());
+    }
+    if (!host && duid && !duid->getDuid().empty()) {
+        host = getHostInternal(subnet_id, false, Host::IDENT_DUID,
+                               &duid->getDuid()[0],
+                               duid->getDuid().size());
+    }
+    return (host);
 }
 
 HostPtr
@@ -214,7 +251,36 @@ CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
                const DuidPtr& duid) {
     // Do not log here because getHostInternal logs.
     // The false value indicates that it is an IPv4 subnet.
-    return (getHostInternal(subnet_id, false, hwaddr, duid));
+    HostPtr host;
+    if (hwaddr && !hwaddr->hwaddr_.empty()) {
+        host = getHostInternal(subnet_id, false, Host::IDENT_HWADDR,
+                               &hwaddr->hwaddr_[0],
+                               hwaddr->hwaddr_.size());
+    }
+    if (!host && duid && !duid->getDuid().empty()) {
+        host = getHostInternal(subnet_id, false, Host::IDENT_DUID,
+                               &duid->getDuid()[0],
+                               duid->getDuid().size());
+    }
+    return (host);
+}
+
+ConstHostPtr
+CfgHosts::get4(const SubnetID& subnet_id,
+               const Host::IdentifierType& identifier_type,
+               const uint8_t* identifier_begin,
+               const size_t identifier_len) const {
+    return (getHostInternal(subnet_id, false, identifier_type, identifier_begin,
+                            identifier_len));
+}
+
+HostPtr
+CfgHosts::get4(const SubnetID& subnet_id,
+               const Host::IdentifierType& identifier_type,
+               const uint8_t* identifier_begin,
+               const size_t identifier_len) {
+    return (getHostInternal(subnet_id, false, identifier_type, identifier_begin,
+                            identifier_len));
 }
 
 ConstHostPtr
@@ -246,7 +312,19 @@ CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
                const HWAddrPtr& hwaddr) const {
     // Do not log here because getHostInternal logs.
     // The true value indicates that it is an IPv6 subnet.
-    return (getHostInternal(subnet_id, true, hwaddr, duid));
+    HostPtr host;
+    if (duid && !duid->getDuid().empty()) {
+        host = getHostInternal(subnet_id, true, Host::IDENT_DUID,
+                               &duid->getDuid()[0],
+                               duid->getDuid().size());
+    }
+    if (!host && hwaddr && !hwaddr->hwaddr_.empty()) {
+        host = getHostInternal(subnet_id, true, Host::IDENT_HWADDR,
+                               &hwaddr->hwaddr_[0],
+                               hwaddr->hwaddr_.size());
+    }
+
+    return (host);
 }
 
 HostPtr
@@ -254,18 +332,47 @@ CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
                const HWAddrPtr& hwaddr) {
     // Do not log here because getHostInternal logs.
     // The true value indicates that it is an IPv6 subnet.
-    return (getHostInternal(subnet_id, true, hwaddr, duid));
+    HostPtr host;
+    if (duid && !duid->getDuid().empty()) {
+        host = getHostInternal(subnet_id, true, Host::IDENT_DUID,
+                               &duid->getDuid()[0],
+                               duid->getDuid().size());
+    }
+    if (!host && hwaddr && !hwaddr->hwaddr_.empty()) {
+        host = getHostInternal(subnet_id, true, Host::IDENT_HWADDR,
+                               &hwaddr->hwaddr_[0],
+                               hwaddr->hwaddr_.size());
+    }
+
+    return (host);
 }
 
 ConstHostPtr
-CfgHosts::get6(const IOAddress&, const uint8_t) const {
-    isc_throw(isc::NotImplemented,
-              "get6(prefix, len) const is not implemented");
+CfgHosts::get6(const SubnetID& subnet_id,
+               const Host::IdentifierType& identifier_type,
+               const uint8_t* identifier_begin,
+               const size_t identifier_len) const {
+    return (getHostInternal(subnet_id, true, identifier_type, identifier_begin,
+                            identifier_len));
 }
 
 HostPtr
-CfgHosts::get6(const IOAddress&, const uint8_t) {
-    isc_throw(isc::NotImplemented, "get6(prefix, len) is not implemented");
+CfgHosts::get6(const SubnetID& subnet_id,
+               const Host::IdentifierType& identifier_type,
+               const uint8_t* identifier_begin,
+               const size_t identifier_len) {
+    return (getHostInternal(subnet_id, true, identifier_type, identifier_begin,
+                            identifier_len));
+}
+
+ConstHostPtr
+CfgHosts::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
+    return (getHostInternal6<ConstHostPtr>(prefix, prefix_len));
+}
+
+HostPtr
+CfgHosts::get6(const IOAddress& prefix, const uint8_t prefix_len) {
+    return (getHostInternal6<HostPtr>(prefix, prefix_len));
 }
 
 ConstHostPtr
@@ -316,6 +423,36 @@ CfgHosts::getHostInternal6(const SubnetID& subnet_id,
 
 }
 
+template<typename ReturnType>
+ReturnType
+CfgHosts::getHostInternal6(const asiolink::IOAddress& prefix,
+                           const uint8_t prefix_len) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_PREFIX)
+        .arg(prefix.toText()).arg(static_cast<int>(prefix_len));
+
+    // Let's get all reservations that match subnet_id, address.
+    const HostContainer6Index0& idx = hosts6_.get<0>();
+    HostContainer6Index0Range r = make_pair(idx.lower_bound(prefix),
+                                            idx.upper_bound(prefix));
+    for (HostContainer6Index0::iterator resrv = r.first; resrv != r.second;
+         ++resrv) {
+        if (resrv->resrv_.getPrefixLen() == prefix_len) {
+            LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+                      HOSTS_CFG_GET_ONE_PREFIX_HOST)
+                .arg(prefix.toText())
+                .arg(static_cast<int>(prefix_len))
+                .arg(resrv->host_->toText());
+            return (resrv->host_);
+        }
+    }
+
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+              HOSTS_CFG_GET_ONE_PREFIX_NULL)
+        .arg(prefix.toText())
+        .arg(static_cast<int>(prefix_len));
+    return (ReturnType());
+}
+
 template<typename Storage>
 void
 CfgHosts::getAllInternal6(const SubnetID& subnet_id,
@@ -344,7 +481,7 @@ CfgHosts::getAllInternal6(const SubnetID& subnet_id,
                   HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST)
             .arg(subnet_id)
             .arg(address.toText())
-            .arg(resrv->host_);
+            .arg(resrv->host_->toText());
         storage.push_back(resrv->host_);
     }
 
@@ -357,18 +494,21 @@ CfgHosts::getAllInternal6(const SubnetID& subnet_id,
 
 HostPtr
 CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
-                          const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
-    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID)
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier,
+                          const size_t identifier_len) const {
+
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER)
         .arg(subnet6 ? "IPv6" : "IPv4")
         .arg(subnet_id)
-        .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
-        .arg(duid ? duid->toText() : "(no-duid)");
+        .arg(Host::getIdentifierAsText(identifier_type, identifier, identifier_len));
 
-    // Get all hosts for the HW address and DUID. This may return multiple hosts
+    // Get all hosts for a specified identifier. This may return multiple hosts
     // for different subnets, but the number of hosts returned should be low
     // because one host presumably doesn't show up in many subnets.
     HostCollection hosts;
-    getAllInternal<HostCollection>(hwaddr, duid, hosts);
+    getAllInternal<HostCollection>(identifier_type, identifier, identifier_len,
+                                   hosts);
 
     HostPtr host;
     // Iterate over the returned hosts and select those for which the
@@ -393,10 +533,10 @@ CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
             } else {
                 isc_throw(DuplicateHost,  "more than one reservation found"
                           " for the host belonging to the subnet with id '"
-                          << subnet_id << "' and using the HW address '"
-                          << (hwaddr ? hwaddr->toText(false) : "(null)")
-                          << "' and DUID '"
-                          << (duid ? duid->toText() : "(null)")
+                          << subnet_id << "' and using the identifier '"
+                          << Host::getIdentifierAsText(identifier_type,
+                                                       identifier,
+                                                       identifier_len)
                           << "'");
             }
         }
@@ -404,24 +544,23 @@ CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
 
     if (host) {
         LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
-                  HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID)
+                  HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_HOST)
             .arg(subnet_id)
-            .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
-            .arg(duid ? duid->toText() : "(no-duid)")
+            .arg(Host::getIdentifierAsText(identifier_type, identifier,
+                                           identifier_len))
             .arg(host->toText());
 
     } else {
         LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
-                  HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID_NULL)
+                  HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_NULL)
             .arg(subnet_id)
-            .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
-            .arg(duid ? duid->toText() : "(no-duid)");
+            .arg(Host::getIdentifierAsText(identifier_type, identifier,
+                                           identifier_len));
     }
 
     return (host);
 }
 
-
 void
 CfgHosts::add(const HostPtr& host) {
     LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_ADD_HOST)

+ 115 - 16
src/lib/dhcpsrv/cfg_hosts.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -69,6 +69,40 @@ public:
     virtual HostCollection
     getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr());
 
+    /// @brief Return all hosts connected to any subnet for which reservations
+    /// have been made using a specified identifier.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for a specified identifier. This method may return multiple hosts
+    /// because a particular client may have reservations in multiple subnets.
+    ///
+    /// @param identifier_type One of the supported identifier types.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+    /// @brief Non-const version of the @c getAll const method.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for a specified identifier. This method may return multiple hosts
+    /// because a particular client may have reservations in multiple subnets.
+    ///
+    /// @param identifier_type One of the supported identifier types.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of non-const @c Host objects.
+    virtual HostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin,
+           const size_t identifier_len);
+
     /// @brief Returns a collection of hosts using the specified IPv4 address.
     ///
     /// This method may return multiple @c Host objects if they are connected
@@ -143,6 +177,34 @@ public:
     get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
          const DuidPtr& duid = DuidPtr());
 
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Non-const @c Host object for which reservation has been made
+    /// using the specified identifier.
+    virtual HostPtr
+    get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len);
+
     /// @brief Returns a host connected to the IPv4 subnet and having
     /// a reservation for a specified IPv4 address.
     ///
@@ -183,12 +245,40 @@ public:
     get6(const SubnetID& subnet_id, const DuidPtr& duid,
          const HWAddrPtr& hwaddr = HWAddrPtr());
 
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Non-const @c Host object for which reservation has been made
+    /// using the specified identifier.
+    virtual HostPtr
+    get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len);
+
     /// @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.
     ///
-    /// @throw isc::NotImplemented
+    /// @return Const @c Host object for which specified prefix is reserved.
     virtual ConstHostPtr
     get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
 
@@ -197,7 +287,8 @@ public:
     /// @param prefix IPv6 prefix for which the @c Host object is searched.
     /// @param prefix_len IPv6 prefix length.
     ///
-    /// @throw isc::NotImplemented
+    /// @return Non-const @c Host object for which specified prefix is
+    /// reserved.
     virtual HostPtr
     get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len);
 
@@ -243,18 +334,19 @@ private:
     /// @brief Returns @c Host objects for the specific identifier and type.
     ///
     /// This private method is called by the @c CfgHosts::getAllInternal
-    /// method which finds the @c Host objects using HW address or DUID.
+    /// method which finds the @c Host objects using specified identifier.
     /// The retrieved objects are appended to the @c storage container.
     ///
-    /// @param identifier Binary representation of the HW addres or DUID (or
-    /// other future identifier).
     /// @param identifier_type The type of the supplied identifier.
+    /// @param identifier Pointer to a first byte of the identifier.
+    /// @param identifier_len Length of the identifier.
     /// @param [out] storage Container to which the retreived objects are
     /// appended.
     /// @tparam One of the @c ConstHostCollection of @c HostCollection.
     template<typename Storage>
-    void getAllInternal(const std::vector<uint8_t>& identifier,
-                        const Host::IdentifierType& identifier_type,
+    void getAllInternal(const Host::IdentifierType& identifier_type,
+                        const uint8_t* identifier,
+                        const size_t identifier_len,
                         Storage& storage) const;
 
     /// @brief Returns @c Host objects for the specified HW address or DUID.
@@ -323,22 +415,25 @@ private:
 
     /// @brief Returns @c Host object connected to a subnet.
     ///
-    /// This private method returns a pointer to the @c Host object identified
-    /// by the HW address or DUID and connected to an IPv4 or IPv6 subnet.
+    /// This private method returns a pointer to the @c Host object using
+    /// a specified identifier and connected to an IPv4 or IPv6 subnet.
     ///
     /// @param subnet_id IPv4 or IPv6 subnet identifier.
     /// @param subnet6 A boolean flag which indicates if the subnet identifier
     /// points to a IPv4 (if false) or IPv6 subnet (if true).
-    /// @param hwaddr HW address identifying a host.
-    /// @param duid DUID identifying a host.
+    /// @param identifier_type Indentifier type.
+    /// @param identifier Pointer to a first byte of the buffer holding an
+    /// identifier.
+    /// @param identifier_len Identifier length.
     ///
     /// @return Pointer to the found host, or NULL if no host found.
     /// @throw isc::dhcp::DuplicateHost if method found more than one matching
     /// @c Host object.
-    HostPtr getHostInternal(const SubnetID& subnet_id,
-                            const bool subnet6,
-                            const HWAddrPtr& hwaddr,
-                            const DuidPtr& duid) const;
+    HostPtr
+    getHostInternal(const SubnetID& subnet_id, const bool subnet6,
+                    const Host::IdentifierType& identifier_type,
+                    const uint8_t* identifier,
+                    const size_t identifier_len) const;
 
     /// @brief Returns the @c Host object holding reservation for the IPv6
     /// address and connected to the specific subnet.
@@ -357,6 +452,10 @@ private:
     ReturnType getHostInternal6(const SubnetID& subnet_id,
                                 const asiolink::IOAddress& adddress) const;
 
+    template<typename ReturnType>
+    ReturnType getHostInternal6(const asiolink::IOAddress& prefix,
+                                const uint8_t prefix_len) const;
+
     /// @brief Adds a new host to the v4 collection.
     ///
     /// This is an internal method called by public @ref add.

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

@@ -6,6 +6,7 @@
 
 #include <config.h>
 #include <dhcpsrv/host.h>
+#include <util/encode/hex.h>
 #include <util/strutil.h>
 #include <exceptions/exceptions.h>
 #include <sstream>
@@ -137,20 +138,47 @@ std::string
 Host::getIdentifierAsText() const {
     std::string txt;
     if (hw_address_) {
-        txt = "hwaddr=" + hw_address_->toText(false);
+        txt = getIdentifierAsText(IDENT_HWADDR, &hw_address_->hwaddr_[0],
+                                  hw_address_->hwaddr_.size());
+    } else if (duid_) {
+        txt = getIdentifierAsText(IDENT_DUID, &duid_->getDuid()[0],
+                                  duid_->getDuid().size());
     } else {
-        txt = "duid=";
-        if (duid_) {
-            txt += duid_->toText();
-        } else {
-            txt += "(none)";
-        }
+        txt = "(none)";
     }
 
     return (txt);
 
 }
 
+std::string
+Host::getIdentifierAsText(const IdentifierType& type, const uint8_t* value,
+                          const size_t length) {
+    // Length 0 doesn't make sense.
+    if (length == 0) {
+        isc_throw(BadValue, "invalid length 0 of the host identifier while"
+                  " converting the identifier to a textual form");
+    }
+
+    // Convert identifier into <type>=<value> form.
+    std::ostringstream s;
+    switch (type) {
+    case IDENT_HWADDR:
+        s << "hwaddr";
+        break;
+    case IDENT_DUID:
+        s << "duid";
+        break;
+    default:
+        isc_throw(BadValue, "requested conversion of the unsupported"
+                  " identifier into textual form");
+    }
+    std::vector<uint8_t> vec(value, value + length);
+    s << "=" << util::encode::encodeHex(vec);
+    return (s.str());
+}
+
+
 void
 Host::setIdentifier(const uint8_t* identifier, const size_t len,
                     const IdentifierType& type) {

+ 10 - 0
src/lib/dhcpsrv/host.h

@@ -307,6 +307,16 @@ public:
     /// @return text form of the identifier, including (duid= or mac=).
     std::string getIdentifierAsText() const;
 
+    /// @brief Returns host identifier in textual form.
+    ///
+    /// @param type Identifier type.
+    /// @param value Pointer to a buffer holding identifier.
+    /// @param length Length of the identifier.
+    /// @return Identifier in the form of <type>=<value>.
+    static std::string getIdentifierAsText(const IdentifierType& type,
+                                           const uint8_t* value,
+                                           const size_t length);
+
     /// @brief Sets new IPv4 subnet identifier.
     ///
     /// @param ipv4_subnet_id New subnet identifier.

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -77,6 +77,23 @@ HostMgr::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
 }
 
 ConstHostCollection
+HostMgr::getAll(const Host::IdentifierType& identifier_type,
+                const uint8_t* identifier_begin,
+                const size_t identifier_len) const {
+    ConstHostCollection hosts = getCfgHosts()->getAll(identifier_type,
+                                                      identifier_begin,
+                                                      identifier_len);
+    if (alternate_source_) {
+        ConstHostCollection hosts_plus =
+            alternate_source_->getAll(identifier_type, identifier_begin,
+                                      identifier_len);
+        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_) {
@@ -108,6 +125,20 @@ HostMgr::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
 
 ConstHostPtr
 HostMgr::get4(const SubnetID& subnet_id,
+              const Host::IdentifierType& identifier_type,
+              const uint8_t* identifier_begin,
+              const size_t identifier_len) const {
+    ConstHostPtr host = getCfgHosts()->get4(subnet_id, identifier_type,
+                                            identifier_begin, identifier_len);
+    if (!host && alternate_source_) {
+        host = alternate_source_->get4(subnet_id, identifier_type,
+                                       identifier_begin, identifier_len);
+    }
+    return (host);
+}
+
+ConstHostPtr
+HostMgr::get4(const SubnetID& subnet_id,
               const asiolink::IOAddress& address) const {
     ConstHostPtr host = getCfgHosts()->get4(subnet_id, address);
     if (!host && alternate_source_) {
@@ -156,6 +187,20 @@ HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
 
 ConstHostPtr
 HostMgr::get6(const SubnetID& subnet_id,
+              const Host::IdentifierType& identifier_type,
+              const uint8_t* identifier_begin,
+              const size_t identifier_len) const {
+    ConstHostPtr host = getCfgHosts()->get6(subnet_id, identifier_type,
+                                            identifier_begin, identifier_len);
+    if (!host && alternate_source_) {
+        host = alternate_source_->get6(subnet_id, identifier_type,
+                                       identifier_begin, identifier_len);
+    }
+    return (host);
+}
+
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id,
               const asiolink::IOAddress& addr) const {
     ConstHostPtr host = getCfgHosts()->get6(subnet_id, addr);
     if (!host && alternate_source_) {

+ 64 - 3
src/lib/dhcpsrv/host_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -88,7 +88,9 @@ public:
     ///
     /// 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.
+    /// reservations are in both sources, all of them are returned. The
+    /// reservations from the primary data source are placed before the
+    /// reservations from the alternate source.
     ///
     /// Note that returned collection may contain duplicates. It is the
     /// caller's responsibility to check for duplicates.
@@ -101,13 +103,38 @@ public:
     virtual ConstHostCollection
     getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
 
+    /// @brief Return all hosts connected to any subnet for which reservations
+    /// have been made using a specified identifier.
+    ///
+    /// This method returns all @c Host objects representing reservations for
+    /// a specified identifier 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. The
+    /// reservations from the primary data source are placed before the
+    /// reservations from the alternate source.
+    ///
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin,
+           const size_t identifier_len) 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.
+    /// data source, all of them are returned. The reservations from the
+    /// primary data source are placed before the reservations from the
+    /// alternate source.
     ///
     /// @param address IPv4 address for which the @c Host object is searched.
     ///
@@ -131,6 +158,23 @@ public:
     get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
          const DuidPtr& duid = DuidPtr()) const;
 
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// This method returns a single reservation for a particular host as
+    /// documneted in the @c BaseHostDataSource::get4.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) const;
+
     /// @brief Returns a host connected to the IPv4 subnet and having
     /// a reservation for a specified IPv4 address.
     ///
@@ -161,6 +205,23 @@ public:
     get6(const SubnetID& subnet_id, const DuidPtr& duid,
          const HWAddrPtr& hwaddr = HWAddrPtr()) const;
 
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// This method returns a host connected to the IPv6 subnet as described
+    /// in the @c BaseHostDataSource::get6.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) const;
+
     /// @brief Returns a host using the specified IPv6 prefix.
     ///
     /// This method returns a host using specified IPv6 prefix, as described

+ 25 - 11
src/lib/dhcpsrv/hosts_messages.mes

@@ -1,4 +1,4 @@
-# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -76,10 +76,24 @@ subnet id and address. The arguments specify subnet id, address and
 found host details respectively.
 
 % HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST using subnet id %1 and address %2, found host: %3
-This debug message include the details of the host found using the
+This debug message includes the details of the host found using the
 subnet id and address. The arguments specify subnet id, address and
 found host details respectively.
 
+% HOSTS_CFG_GET_ONE_PREFIX get one host with reservation for prefix %1/%2
+This debug message is issued when starting to retrieve a host having a
+reservation for a specified prefix. The arguments specify a prefix and
+prefix length.
+
+% HOSTS_CFG_GET_ONE_PREFIX_HOST using prefix %1/%2, found host: %3
+This debug message includes the details of the host found using the
+specific prefix/prefix length. The arguments specify prefix, prefix
+length and host details respectively.
+
+% HOSTS_CFG_GET_ONE_PREFIX_NULL host not found using prefix %1/%2
+This debug messsage is issued when no host was found for a specified
+prefix and prefix length.
+
 % HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4 get one host with reservation for subnet id %1 and IPv4 address %2
 This debug message is issued when starting to retrieve a host connected to the
 specific subnet and having the specific IPv4 address reserved. The
@@ -106,19 +120,19 @@ subnet id and IPv6 address.
 This debug message is issued when no host was found using the specified
 subnet if and IPv6 address.
 
-% HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID get one host with %1 reservation for subnet id %2, HWADDR %3, DUID %4
-This debug message is issued when starting to retrieve the host holding IPv4 or
-IPv6 reservations, which is connected to the specific subnet and is
-identified by the specific HW address or DUID. The first argument
+% HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER get one host with %1 reservation for subnet id %2, identified by %3
+This debug message is issued when starting to retrieve a host holding
+IPv4 or IPv6 reservations, which is connected to a specific subnet and
+is identified by a specific unique identifier. The first argument
 identifies if the IPv4 or IPv6 reservation is desired.
 
-% HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID_HOST using subnet id %1, HWADDR %2 and DUID %3, found host: %4
-This debug message includes the details of the host found using the
-subnet id, HW address and/or DUID.
+% HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_HOST using subnet id %1 and identifier %2, found host: %3
+This debug message includes the details of a host found using a
+subnet id and specific host identifier.
 
-% HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID_NULL host not found using subnet id %1, HW address %2 and DUID %3
+% HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_NULL host not found using subnet id %1 and identifier %2
 This debug message is issued when no host was found using the specified
-subnet id, HW address and DUID.
+subnet id and host identifier.
 
 % HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4 trying alternate source for host using subnet id %1 and address %2
 This debug message is issued when the Host Manager doesn't find the

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

@@ -36,8 +36,8 @@ extern const int MLM_MYSQL_FETCH_FAILURE;
 
 /// @name Current database schema version values.
 //@{
-const uint32_t CURRENT_VERSION_VERSION = 3;
-const uint32_t CURRENT_VERSION_MINOR = 0;
+const uint32_t CURRENT_VERSION_VERSION = 4;
+const uint32_t CURRENT_VERSION_MINOR = 2;
 
 //@}
 

+ 146 - 160
src/lib/dhcpsrv/mysql_host_data_source.cc

@@ -64,7 +64,7 @@ TaggedStatement tagged_statements[] = {
     // having a specified identifier will be returned from those subnets.
     // Because LEFT JOIN clause is used, the number of rows returned for
     // a single host depends on the number of reservations.
-    {MySqlHostDataSource::GET_HOST_HWADDR_DUID,
+    {MySqlHostDataSource::GET_HOST_DHCPID,
             "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
                 "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
                 "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
@@ -718,6 +718,8 @@ public:
         : MySqlHostExchange(), reserv_type_(0), reserv_type_null_(MLM_FALSE),
           ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0) {
 
+        memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
+
         // Append additional columns returned by the queries.
         columns_.push_back("address");
         columns_.push_back("prefix_len");
@@ -922,7 +924,7 @@ public:
     ///
     /// Initialize class members representing a single IPv6 reservation.
     MySqlIPv6ReservationExchange()
-        : host_id_(0), address_("::"), prefix_len_(0), type_(0),
+        : host_id_(0), address_("::"), address_len_(0), prefix_len_(0), type_(0),
           iaid_(0), resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
 
         // Reset error table.
@@ -1117,6 +1119,29 @@ public:
                            boost::shared_ptr<MySqlHostExchange> exchange,
                            ConstHostCollection& result, bool single) const;
 
+    /// @brief Retrieves a host by subnet and client's unique identifier.
+    ///
+    /// This method is used by both MySqlHostDataSource::get4 and
+    /// MySqlHOstDataSource::get6 methods.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    /// @param stindex Statement index.
+    /// @param exchange Pointer to the exchange object used for the
+    /// particular query.
+    ///
+    /// @return Pointer to const instance of Host or null pointer if
+    /// no host found.
+    ConstHostPtr getHost(const SubnetID& subnet_id,
+                         const Host::IdentifierType& identifier_type,
+                         const uint8_t* identifier_begin,
+                         const size_t identifier_len,
+                         MySqlHostDataSource::StatementIndex stindex,
+                         boost::shared_ptr<MySqlHostExchange> exchange) const;
+
     /// @brief Pointer to the object representing an exchange which
     /// can be used to retrieve DHCPv4 reservation.
     boost::shared_ptr<MySqlHostExchange> host_exchange_;
@@ -1276,6 +1301,50 @@ getHostCollection(MySqlHostDataSource::StatementIndex stindex,
     }
 }
 
+ConstHostPtr
+MySqlHostDataSourceImpl::
+getHost(const SubnetID& subnet_id,
+        const Host::IdentifierType& identifier_type,
+        const uint8_t* identifier_begin,
+        const size_t identifier_len,
+        MySqlHostDataSource::StatementIndex stindex,
+        boost::shared_ptr<MySqlHostExchange> exchange) const {
+
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[3];
+    memset(inbind, 0, sizeof(inbind));
+
+    uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
+    inbind[0].buffer_type = MYSQL_TYPE_LONG;
+    inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
+    inbind[0].is_unsigned = MLM_TRUE;
+
+    // Identifier value.
+    std::vector<char> identifier_vec(identifier_begin,
+                                     identifier_begin + identifier_len);
+    unsigned long length = identifier_vec.size();
+    inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+    inbind[2].buffer = &identifier_vec[0];
+    inbind[2].buffer_length = length;
+    inbind[2].length = &length;
+
+    // Identifier type.
+    char identifier_type_copy = static_cast<char>(identifier_type);
+    inbind[1].buffer_type = MYSQL_TYPE_TINY;
+    inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+    inbind[1].is_unsigned = MLM_TRUE;
+
+    ConstHostCollection collection;
+    getHostCollection(stindex, inbind, exchange, collection, true);
+
+    // Return single record if present, else clear the host.
+    ConstHostPtr result;
+    if (!collection.empty())
+        result = *collection.begin();
+
+    return (result);
+}
+
 
 MySqlHostDataSource::
 MySqlHostDataSource(const MySqlConnection::ParameterMap& parameters)
@@ -1286,86 +1355,72 @@ MySqlHostDataSource::~MySqlHostDataSource() {
     delete impl_;
 }
 
-bool
-MySqlHostDataSource::checkIfExists(const HostPtr& host){
-    /// @todo: Implement this as a single query get(identifier_type, identifier)
-    return (get4(host->getIPv4SubnetID(), host->getHWAddress(), host->getDuid()) ||
-            get6(host->getIPv6SubnetID(), host->getDuid(), host->getHWAddress()));
-}
-
 void
 MySqlHostDataSource::add(const HostPtr& host) {
-    // Check if the host is not a duplicate
-    if (checkIfExists(host)){
-        isc_throw(DuplicateEntry, "Host with same parameters already exists.");
-
-    } else {
-        // Create the MYSQL_BIND array for the host
-        std::vector<MYSQL_BIND> bind = impl_->host_exchange_->createBindForSend(host);
+    // Create the MYSQL_BIND array for the host
+    std::vector<MYSQL_BIND> bind = impl_->host_exchange_->createBindForSend(host);
 
-        // ... and call addHost() code.
-        impl_->addQuery(INSERT_HOST, bind);
+    // ... and call addHost() code.
+    impl_->addQuery(INSERT_HOST, bind);
 
-        IPv6ResrvRange v6resv = host->getIPv6Reservations();
-        if (std::distance(v6resv.first, v6resv.second) == 0) {
-            // If there are no v6 reservations, we're done here.
-            return;
-        }
+    IPv6ResrvRange v6resv = host->getIPv6Reservations();
+    if (std::distance(v6resv.first, v6resv.second) == 0) {
+        // If there are no v6 reservations, we're done here.
+        return;
+    }
 
-        // Gets the last inserted hosts id
-        uint64_t host_id = mysql_insert_id(impl_->conn_.mysql_);
-        for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second; ++resv) {
-            impl_->addResv(resv->second, host_id);
-        }
+    // Gets the last inserted hosts id
+    uint64_t host_id = mysql_insert_id(impl_->conn_.mysql_);
+    for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
+         ++resv) {
+        impl_->addResv(resv->second, host_id);
     }
 }
 
 ConstHostCollection
-MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
+                            const DuidPtr& duid) const {
 
-    // Set up the WHERE clause value
-    MYSQL_BIND inbind[2];
-    memset(inbind, 0, sizeof(inbind));
-
-    uint8_t dhcp_identifier_type = 0;
-    long unsigned int length = 0;
     if (duid){
-        // DUID
-        // set proper dhcp_identifier_type
-        dhcp_identifier_type = BaseHostDataSource::ID_DUID; // 1
-        inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type);
-
-        const vector<uint8_t>& duid_vector = duid->getDuid();
-        length = duid_vector.size();
-        inbind[0].buffer_type = MYSQL_TYPE_BLOB;
-        inbind[0].buffer = reinterpret_cast<char*>
-            (const_cast<uint8_t*>(&duid_vector[0]));
-        inbind[0].buffer_length = length;
-        inbind[0].length = &length;
+        return (getAll(Host::IDENT_DUID, &duid->getDuid()[0],
+                       duid->getDuid().size()));
+
     } else if (hwaddr) {
-        // HW Address
-        dhcp_identifier_type = BaseHostDataSource::ID_HWADDR; // 0
-        inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type);
-
-        const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
-        length = hwaddr_vector.size();
-        inbind[0].buffer_type = MYSQL_TYPE_BLOB;
-        inbind[0].buffer = reinterpret_cast<char*>
-            (const_cast<uint8_t*>(&hwaddr_vector[0]));
-        inbind[0].buffer_length = length;
-        inbind[0].length = &length;
+        return (getAll(Host::IDENT_HWADDR,
+                       &hwaddr->hwaddr_[0],
+                       hwaddr->hwaddr_.size()));
     }
 
-    // dhcp identifier type
+    return (ConstHostCollection());
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
+                            const uint8_t* identifier_begin,
+                            const size_t identifier_len) const {
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[2];
+    memset(inbind, 0, sizeof(inbind));
+
+    // Identifier type.
+    char identifier_type_copy = static_cast<char>(identifier_type);
+    inbind[1].buffer = &identifier_type_copy;
     inbind[1].buffer_type = MYSQL_TYPE_TINY;
-    inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type);
     inbind[1].is_unsigned = MLM_TRUE;
 
+    // Identifier value.
+    std::vector<char> identifier_vec(identifier_begin,
+                                     identifier_begin + identifier_len);
+    unsigned long int length = identifier_vec.size();
+    inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+    inbind[0].buffer = &identifier_vec[0];
+    inbind[0].buffer_length = length;
+    inbind[0].length = &length;
+
     ConstHostCollection result;
-    impl_->getHostCollection(GET_HOST_HWADDR_DUID, inbind,
+    impl_->getHostCollection(GET_HOST_DHCPID, inbind,
                              impl_->host_ipv6_exchange_,
                              result, false);
-
     return (result);
 }
 
@@ -1392,15 +1447,6 @@ ConstHostPtr
 MySqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
                           const DuidPtr& duid) const {
 
-    // Set up the WHERE clause value
-    MYSQL_BIND inbind[3];
-    memset(inbind, 0, sizeof(inbind));
-
-    uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
-    inbind[0].buffer_type = MYSQL_TYPE_LONG;
-    inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
-    inbind[0].is_unsigned = MLM_TRUE;
-
     /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
     if (hwaddr && duid) {
         isc_throw(BadValue, "MySQL host data source get4() called with both"
@@ -1411,53 +1457,28 @@ MySqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
                   "neither hwaddr or duid specified, one of them is required");
     }
 
-    unsigned long length = 0;
-    uint8_t dhcp_identifier_type_ = 0;
-
     // Choosing one of the identifiers
     if (hwaddr) {
-        // set identifier type
-        dhcp_identifier_type_ = BaseHostDataSource::ID_HWADDR; // 0
-
-        // set identifier value
-        const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
-        length = hwaddr_vector.size();
-        inbind[2].buffer_type = MYSQL_TYPE_BLOB;
-        inbind[2].buffer = reinterpret_cast<char*>
-            (const_cast<uint8_t*>(&hwaddr_vector[0]));
-        inbind[2].buffer_length = length;
-        inbind[2].length = &length;
+        return (get4(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
+                     hwaddr->hwaddr_.size()));
 
     } else if (duid) {
-        // set identifier type
-        dhcp_identifier_type_ = BaseHostDataSource::ID_DUID; // 1
-
-        // set identifier value
-        const vector<uint8_t>& duid_vector = duid->getDuid();
-        length = duid_vector.size();
-        inbind[2].buffer_type = MYSQL_TYPE_BLOB;
-        inbind[2].buffer = reinterpret_cast<char*>
-            (const_cast<uint8_t*>(&duid_vector[0]));
-        inbind[2].buffer_length = length;
-        inbind[2].length = &length;
+        return (get4(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
+                     duid->getDuid().size()));
     }
 
-    // dhcp identifier type
-    inbind[1].buffer_type = MYSQL_TYPE_TINY;
-    inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
-    inbind[1].is_unsigned = MLM_TRUE;
-
-    ConstHostCollection collection;
-    impl_->getHostCollection(GET_HOST_SUBID4_DHCPID, inbind,
-                             impl_->host_exchange_, collection,
-                             true);
+    return (ConstHostPtr());
+}
 
-    // Return single record if present, else clear the host.
-    ConstHostPtr result;
-    if (!collection.empty())
-        result = *collection.begin();
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id,
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin,
+                          const size_t identifier_len) const {
 
-    return (result);
+    return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
+                   identifier_len, GET_HOST_SUBID4_DHCPID,
+                   impl_->host_exchange_));
 }
 
 ConstHostPtr
@@ -1493,15 +1514,6 @@ ConstHostPtr
 MySqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
                           const HWAddrPtr& hwaddr) const {
 
-    // Set up the WHERE clause value
-    MYSQL_BIND inbind[3];
-    memset(inbind, 0, sizeof(inbind));
-
-    uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
-    inbind[0].buffer_type = MYSQL_TYPE_LONG;
-    inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
-    inbind[0].is_unsigned = MLM_TRUE;
-
     /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid)
     if (hwaddr && duid) {
         isc_throw(BadValue, "MySQL host data source get6() called with both"
@@ -1512,53 +1524,27 @@ MySqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
                   "neither hwaddr or duid specified, one of them is required");
     }
 
-    unsigned long length = 0;
-    uint8_t dhcp_identifier_type_ = 0;
-
     // Choosing one of the identifiers
     if (hwaddr) {
-        // set identifier type
-        dhcp_identifier_type_ = BaseHostDataSource::ID_HWADDR; // 0
-
-        // set identifier value
-        const vector<uint8_t>& hwaddr_vector = hwaddr->hwaddr_;
-        length = hwaddr_vector.size();
-        inbind[2].buffer_type = MYSQL_TYPE_BLOB;
-        inbind[2].buffer = reinterpret_cast<char*>
-            (const_cast<uint8_t*>(&hwaddr_vector[0]));
-        inbind[2].buffer_length = length;
-        inbind[2].length = &length;
+        return (get6(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
+                     hwaddr->hwaddr_.size()));
     } else if (duid) {
-        // set identifier type
-        dhcp_identifier_type_ = BaseHostDataSource::ID_DUID; // 1
-
-        // set identifier value
-        const vector<uint8_t>& duid_vector = duid->getDuid();
-        length = duid_vector.size();
-        inbind[2].buffer_type = MYSQL_TYPE_BLOB;
-        inbind[2].buffer = reinterpret_cast<char*>
-            (const_cast<uint8_t*>(&duid_vector[0]));
-        inbind[2].buffer_length = length;
-        inbind[2].length = &length;
+        return (get6(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
+                     duid->getDuid().size()));
     }
 
-    // dhcp identifier type
-    inbind[1].buffer_type = MYSQL_TYPE_TINY;
-    inbind[1].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
-    inbind[1].is_unsigned = MLM_TRUE;
-
-    ConstHostCollection collection;
-    impl_->getHostCollection(GET_HOST_SUBID6_DHCPID, inbind,
-                             impl_->host_ipv6_exchange_,
-                             collection, true);
-
-    // Return single record if present, else clear the host.
-    ConstHostPtr result;
-    if (!collection.empty()) {
-        result = *collection.begin();
-    }
+    return (ConstHostPtr());
+}
 
-    return (result);
+ConstHostPtr
+MySqlHostDataSource::get6(const SubnetID& subnet_id,
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin,
+                          const size_t identifier_len) const {
+
+    return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
+                   identifier_len, GET_HOST_SUBID6_DHCPID,
+                   impl_->host_ipv6_exchange_));
 }
 
 ConstHostPtr

+ 45 - 6
src/lib/dhcpsrv/mysql_host_data_source.h

@@ -77,6 +77,22 @@ public:
     virtual ConstHostCollection
     getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
 
+    /// @brief Return all hosts connected to any subnet for which reservations
+    /// have been made using a specified identifier.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for a specified identifier. This method may return multiple hosts
+    /// because a particular client may have reservations in multiple subnets.
+    ///
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin, const size_t identifier_len) const;
+
     /// @brief Returns a collection of hosts using the specified IPv4 address.
     ///
     /// This method may return multiple @c Host objects if they are connected
@@ -106,6 +122,20 @@ public:
     get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
          const DuidPtr& duid = DuidPtr()) const;
 
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) const;
+
     /// @brief Returns a host connected to the IPv4 subnet and having
     /// a reservation for a specified IPv4 address.
     ///
@@ -143,6 +173,20 @@ public:
     get6(const SubnetID& subnet_id, const DuidPtr& duid,
             const HWAddrPtr& hwaddr = HWAddrPtr()) const;
 
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Const @c Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) const;
+
     /// @brief Returns a host using the specified IPv6 prefix.
     ///
     /// @param prefix IPv6 prefix for which the @c Host object is searched.
@@ -213,7 +257,7 @@ public:
     enum StatementIndex {
         INSERT_HOST,            // Insert new host to collection
         INSERT_V6_RESRV,        // Insert v6 reservation
-        GET_HOST_HWADDR_DUID,   // Gets hosts by DUID and/or HW address
+        GET_HOST_DHCPID,        // Gets hosts by host identifier
         GET_HOST_ADDR,          // Gets hosts by IPv4 address
         GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
         GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
@@ -225,11 +269,6 @@ public:
 
 private:
 
-    /// @brief Checks if the specified host already exists in the database.
-    ///
-    /// @param host Pointer to the new @c Host object being added.
-    bool checkIfExists(const HostPtr& host);
-
     /// @brief Pointer to the implementation of the @ref MySqlHostDataSource.
     MySqlHostDataSourceImpl* impl_; 
 };

+ 40 - 47
src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -117,17 +117,19 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
     // Try to retrieve each added reservation using HW address and DUID. Do it
     // in the reverse order to make sure that the order doesn't matter.
     for (int i = 24; i >= 0; --i) {
-        // Get host identified by HW address. The DUID is non-zero but it
-        // points to a host for which the reservation hasn't been added.
-        HostCollection hosts = cfg.getAll(hwaddrs_[i], duids_[i + 25]);
+        // Get host identified by HW address.
+        HostCollection hosts = cfg.getAll(Host::IDENT_HWADDR,
+                                          &hwaddrs_[i]->hwaddr_[0],
+                                          hwaddrs_[i]->hwaddr_.size());
         ASSERT_EQ(1, hosts.size());
         EXPECT_EQ(i % 10 + 1, hosts[0]->getIPv4SubnetID());
         EXPECT_EQ(addressesa_[i].toText(),
                   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]);
+        // Get host identified by DUID.
+        hosts = cfg.getAll(Host::IDENT_DUID,
+                           &duids_[i]->getDuid()[0],
+                           duids_[i]->getDuid().size());
         ASSERT_EQ(1, hosts.size());
         EXPECT_EQ(i % 5 + 1, hosts[0]->getIPv4SubnetID());
         EXPECT_EQ(addressesb_[i].toText(),
@@ -137,8 +139,10 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
     // Make sure that the reservations do not exist for the hardware addresses
     // and DUIDs from the range of 25 to 49.
     for (int i = 49; i >= 25; --i) {
-        EXPECT_TRUE(cfg.getAll(hwaddrs_[i]).empty());
-        EXPECT_TRUE(cfg.getAll(HWAddrPtr(), duids_[i]).empty());
+        EXPECT_TRUE(cfg.getAll(Host::IDENT_HWADDR, &hwaddrs_[i]->hwaddr_[0],
+                               hwaddrs_[i]->hwaddr_.size()).empty());
+        EXPECT_TRUE(cfg.getAll(Host::IDENT_DUID, &duids_[i]->getDuid()[0],
+                               duids_[i]->getDuid().size()).empty());
     }
 }
 
@@ -244,31 +248,24 @@ TEST_F(CfgHostsTest, get4) {
     }
 
     for (unsigned i = 0; i < 25; ++i) {
-        // Retrieve host by HW address. The DUID is non-null but there is no
-        // reservation made for the DUID so the reservation is returned for
-        // HW address.
-        HostPtr host = cfg.get4(SubnetID(1 + i % 2), hwaddrs_[i],
-                                duids_[i + 25]);
+        // Retrieve host by HW address.
+        HostPtr host = cfg.get4(SubnetID(1 + i % 2), Host::IDENT_HWADDR,
+                                &hwaddrs_[i]->hwaddr_[0],
+                                hwaddrs_[i]->hwaddr_.size());
         ASSERT_TRUE(host);
         EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
         EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
                   host->getIPv4Reservation());
 
-        // Retrieve host by DUID. The HW address is non-null but there is no
-        // reservation made for the HW address so the reservation is returned
-        // for the DUID.
-        host = cfg.get4(SubnetID(1 + i % 2), hwaddrs_[i + 25], duids_[i]);
+        // Retrieve host by DUID.
+        host = cfg.get4(SubnetID(1 + i % 2), Host::IDENT_DUID,
+                        &duids_[i]->getDuid()[0], duids_[i]->getDuid().size());
         ASSERT_TRUE(host);
         EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
         EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
                   host->getIPv4Reservation());
 
     }
-
-    // Also check that when the get4 finds multiple Host objects that fulfil
-    // search criteria, it will throw an exception. Note that the reservation
-    // exist both for hwaddrs_[0] and duids_[0].
-    EXPECT_THROW(cfg.get4(SubnetID(1), hwaddrs_[0], duids_[0]), DuplicateHost);
 }
 
 // This test checks that the reservations can be retrieved for the particular
@@ -298,11 +295,10 @@ TEST_F(CfgHostsTest, get6) {
     }
 
     for (unsigned i = 0; i < 25; ++i) {
-        // Retrieve host by HW address. The DUID is non-null but there is no
-        // reservation made for the DUID so the reservation is returned for
-        // HW address.
-        HostPtr host = cfg.get6(SubnetID(1 + i % 2), duids_[i + 25],
-                                hwaddrs_[i]);
+        // Retrieve host by HW address.
+        HostPtr host = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_HWADDR,
+                                &hwaddrs_[i]->hwaddr_[0],
+                                hwaddrs_[i]->hwaddr_.size());
         ASSERT_TRUE(host);
         EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
         IPv6ResrvRange reservations =
@@ -311,10 +307,9 @@ TEST_F(CfgHostsTest, get6) {
         EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
                   reservations.first->second.getPrefix());
 
-        // Retrieve host by DUID. The HW address is non-null but there is no
-        // reservation made for the HW address so the reservation is returned
-        // for the DUID.
-        host = cfg.get6(SubnetID(1 + i % 2), duids_[i], hwaddrs_[i + 25]);
+        // Retrieve host by DUID.
+        host = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_DUID,
+                        &duids_[i]->getDuid()[0], duids_[i]->getDuid().size());
         ASSERT_TRUE(host);
         EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
         reservations = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
@@ -322,11 +317,6 @@ TEST_F(CfgHostsTest, get6) {
         EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
                   reservations.first->second.getPrefix());
     }
-
-    // Also check that when the get6 finds multiple Host objects that fulfil
-    // search criteria, it will throw an exception. Note that the reservation
-    // exist both for hwaddrs_[0] and duids_[0].
-    EXPECT_THROW(cfg.get6(SubnetID(1), duids_[0], hwaddrs_[0]), DuplicateHost);
 }
 
 // This test checks that the IPv6 reservations can be retrieved for a particular
@@ -376,30 +366,31 @@ TEST_F(CfgHostsTest, get6MultipleAddrs) {
 
         // Generate 5 unique addresses for this host.
         for (int j = 0; j < 5; ++j) {
-            std::stringstream tmp;
-            tmp << "2001:db8:" << i << "::" << j;
-            host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, tmp.str()));
+            std::stringstream address_stream;
+            address_stream << "2001:db8:" << i << "::" << j;
+            host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                           address_stream.str()));
         }
         cfg.add(host);
     }
 
-    // We don't care about HW/MAC addresses for now.
-    HWAddrPtr hwaddr_not_used;
-
     // Now check if we can retrieve each of those 25 hosts by using each
     // of their addresses.
     for (unsigned i = 0; i < 25; ++i) {
 
         // Check that the host is there.
-        HostPtr by_duid = cfg.get6(SubnetID(1 + i % 2), duids_[i], hwaddr_not_used);
+        HostPtr by_duid = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_DUID,
+                                   &duids_[i]->getDuid()[0],
+                                   duids_[i]->getDuid().size());
         ASSERT_TRUE(by_duid);
 
         for (unsigned j = 0; j < 5; ++j) {
-            std::stringstream tmp;
-            tmp << "2001:db8:" << i << "::" << j;
+            std::stringstream address_stream;
+            address_stream << "2001:db8:" << i << "::" << j;
 
             // Retrieve host by (subnet-id,address).
-            HostPtr by_addr = cfg.get6(SubnetID(1 + i % 2), tmp.str());
+            HostPtr by_addr = cfg.get6(SubnetID(1 + i % 2),
+                                       address_stream.str());
             ASSERT_TRUE(by_addr);
 
             // The pointers should match. Maybe we should compare contents
@@ -462,6 +453,8 @@ TEST_F(CfgHostsTest, add6Invalid2Hosts) {
     EXPECT_THROW(cfg.add(host2), isc::dhcp::DuplicateHost);
 }
 
+// Check that error is reported when trying to add a host with subnet
+// ids equal to zero.
 TEST_F(CfgHostsTest, zeroSubnetIDs) {
     CfgHosts cfg;
     ASSERT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),

+ 118 - 53
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc

@@ -27,7 +27,7 @@ GenericHostDataSourceTest::~GenericHostDataSourceTest() {
 }
 
 std::string
-GenericHostDataSourceTest::generateHWAddr() {
+GenericHostDataSourceTest::generateHWAddr(const bool new_identifier) {
     /// @todo: Consider moving this somewhere to lib/testutils.
 
     // Let's use something that is easily printable. That's convenient
@@ -43,18 +43,20 @@ GenericHostDataSourceTest::generateHWAddr() {
             << static_cast<unsigned int>(hwaddr[i]);
     }
 
-    // Increase the address for the next time we use it.
-    // This is primitive, but will work for 65k unique
-    // addresses.
-    hwaddr[sizeof(hwaddr) - 1]++;
-    if (hwaddr[sizeof(hwaddr) - 1] == 0) {
-        hwaddr[sizeof(hwaddr) - 2]++;
+    if (new_identifier) {
+        // Increase the address for the next time we use it.
+        // This is primitive, but will work for 65k unique
+        // addresses.
+        hwaddr[sizeof(hwaddr) - 1]++;
+        if (hwaddr[sizeof(hwaddr) - 1] == 0) {
+            hwaddr[sizeof(hwaddr) - 2]++;
+        }
     }
     return (tmp.str());
 }
 
 std::string
-GenericHostDataSourceTest::generateDuid() {
+GenericHostDataSourceTest::generateDuid(const bool new_identifier) {
     /// @todo: Consider moving this somewhere to lib/testutils.
 
     // Let's use something that is easily printable. That's convenient
@@ -70,9 +72,11 @@ GenericHostDataSourceTest::generateDuid() {
     // Increase the DUID for the next time we use it.
     // This is primitive, but will work for 65k unique
     // DUIDs.
-    duid[sizeof(duid) - 1]++;
-    if (duid[sizeof(duid) - 1] == 0) {
-        duid[sizeof(duid) - 2]++;
+    if (new_identifier) {
+        duid[sizeof(duid) - 1]++;
+        if (duid[sizeof(duid) - 1] == 0) {
+            duid[sizeof(duid) - 2]++;
+        }
     }
     return (tmp.str());
 }
@@ -105,18 +109,19 @@ HostPtr GenericHostDataSourceTest::initializeHost4(std::string address,
 }
 
 HostPtr GenericHostDataSourceTest::initializeHost6(std::string address,
-                                                   BaseHostDataSource::IdType identifier,
-                                                   bool prefix) {
+                                                   Host::IdentifierType identifier,
+                                                   bool prefix,
+                                                   bool new_identifier) {
     string ident;
     string ident_type;
 
     switch (identifier) {
-    case BaseHostDataSource::ID_HWADDR:
-        ident = generateHWAddr();
+    case Host::IDENT_HWADDR:
+        ident = generateHWAddr(new_identifier);
         ident_type = "hw-address";
         break;
-    case BaseHostDataSource::ID_DUID:
-        ident = generateDuid();
+    case Host::IDENT_DUID:
+        ident = generateDuid(new_identifier);
         ident_type = "duid";
         break;
     default:
@@ -416,8 +421,14 @@ void GenericHostDataSourceTest::testGet4ByHWAddr() {
     SubnetID subnet1 = host1->getIPv4SubnetID();
     SubnetID subnet2 = host2->getIPv4SubnetID();
 
-    ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, host1->getHWAddress());
-    ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, host2->getHWAddress());
+    ConstHostPtr from_hds1 = hdsptr_->get4(subnet1,
+                                           Host::IDENT_HWADDR,
+                                           &host1->getIdentifier()[0],
+                                           host1->getIdentifier().size());
+    ConstHostPtr from_hds2 = hdsptr_->get4(subnet2,
+                                           Host::IDENT_HWADDR,
+                                           &host2->getIdentifier()[0],
+                                           host2->getIdentifier().size());
 
     // Now let's check if we got what we expected.
     ASSERT_TRUE(from_hds1);
@@ -445,8 +456,15 @@ void GenericHostDataSourceTest::testGet4ByClientId() {
     SubnetID subnet1 = host1->getIPv4SubnetID();
     SubnetID subnet2 = host2->getIPv4SubnetID();
 
-    ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, HWAddrPtr(), host1->getDuid());
-    ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, HWAddrPtr(), host2->getDuid());
+    ConstHostPtr from_hds1 = hdsptr_->get4(subnet1,
+                                           Host::IDENT_DUID,
+                                           &host1->getIdentifier()[0],
+                                           host1->getIdentifier().size());
+
+    ConstHostPtr from_hds2 = hdsptr_->get4(subnet2,
+                                           Host::IDENT_DUID,
+                                           &host2->getIdentifier()[0],
+                                           host2->getIdentifier().size());
 
     // Now let's check if we got what we expected.
     ASSERT_TRUE(from_hds1);
@@ -472,10 +490,14 @@ void GenericHostDataSourceTest::testHWAddrNotClientId() {
     DuidPtr duid = HWAddrToDuid(host->getHWAddress());
 
     // Get the host by HW address (should succeed)
-    ConstHostPtr by_hwaddr = hdsptr_->get4(subnet, host->getHWAddress(), DuidPtr());
+    ConstHostPtr by_hwaddr = hdsptr_->get4(subnet, Host::IDENT_HWADDR,
+                                           &host->getIdentifier()[0],
+                                           host->getIdentifier().size());
 
     // Get the host by DUID (should fail)
-    ConstHostPtr by_duid   = hdsptr_->get4(subnet, HWAddrPtr(), duid);
+    ConstHostPtr by_duid   = hdsptr_->get4(subnet, Host::IDENT_DUID,
+                                           &host->getIdentifier()[0],
+                                           host->getIdentifier().size());
 
     // Now let's check if we got what we expected.
     EXPECT_TRUE(by_hwaddr);
@@ -499,10 +521,15 @@ void GenericHostDataSourceTest::testClientIdNotHWAddr() {
     HWAddrPtr hwaddr = DuidToHWAddr(host->getDuid());
 
     // Get the host by DUID (should succeed)
-    ConstHostPtr by_duid   = hdsptr_->get4(subnet, HWAddrPtr(), host->getDuid());
+    ConstHostPtr by_duid   = hdsptr_->get4(subnet, Host::IDENT_DUID,
+                                           &host->getIdentifier()[0],
+                                           host->getIdentifier().size());
+
 
     // Get the host by HW address (should fail)
-    ConstHostPtr by_hwaddr = hdsptr_->get4(subnet, hwaddr, DuidPtr());
+    ConstHostPtr by_hwaddr = hdsptr_->get4(subnet, Host::IDENT_HWADDR,
+                                           &host->getIdentifier()[0],
+                                           host->getIdentifier().size());
 
     // Now let's check if we got what we expected.
     EXPECT_TRUE(by_duid);
@@ -588,7 +615,10 @@ GenericHostDataSourceTest::testMultipleSubnets(int subnets, bool hwaddr) {
         EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
 
         // Try to retrieve the host by either HW address of client-id
-        from_hds = hdsptr_->get4(i + 1000, host->getHWAddress(), host->getDuid());
+        from_hds = hdsptr_->get4(i + 1000,
+                                 hwaddr ? Host::IDENT_HWADDR : Host::IDENT_DUID,
+                                 &host->getIdentifier()[0],
+                                 host->getIdentifier().size());
         ASSERT_TRUE(from_hds);
         EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
     }
@@ -606,8 +636,10 @@ GenericHostDataSourceTest::testMultipleSubnets(int subnets, bool hwaddr) {
     }
 
     // Finally, check that the hosts can be retrived by HW address or DUID
-    ConstHostCollection all_by_id = hdsptr_->getAll(host->getHWAddress(),
-                                                    host->getDuid());
+    ConstHostCollection all_by_id =
+        hdsptr_->getAll(hwaddr ? Host::IDENT_HWADDR : Host::IDENT_DUID,
+                        &host->getIdentifier()[0],
+                        host->getIdentifier().size());
     ASSERT_EQ(subnets, all_by_id.size());
 
     // Check that the returned values are as expected.
@@ -624,8 +656,8 @@ void GenericHostDataSourceTest::testGet6ByHWAddr() {
     ASSERT_TRUE(hdsptr_);
 
     // Create a host reservations.
-    HostPtr host1 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_HWADDR, true);
-    HostPtr host2 = initializeHost6("2001:db8::2", BaseHostDataSource::ID_HWADDR, true);
+    HostPtr host1 = initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true);
+    HostPtr host2 = initializeHost6("2001:db8::2", Host::IDENT_HWADDR, true);
 
     // Sanity check: make sure the hosts have different HW addresses.
     ASSERT_TRUE(host1->getHWAddress());
@@ -640,8 +672,13 @@ void GenericHostDataSourceTest::testGet6ByHWAddr() {
     SubnetID subnet1 = host1->getIPv6SubnetID();
     SubnetID subnet2 = host2->getIPv6SubnetID();
 
-    ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, DuidPtr(), host1->getHWAddress());
-    ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, DuidPtr(), host2->getHWAddress());
+    ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, Host::IDENT_HWADDR,
+                                           &host1->getIdentifier()[0],
+                                           host1->getIdentifier().size());
+
+    ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, Host::IDENT_HWADDR,
+                                           &host2->getIdentifier()[0],
+                                           host2->getIdentifier().size());
 
     // Now let's check if we got what we expected.
     ASSERT_TRUE(from_hds1);
@@ -655,8 +692,8 @@ void GenericHostDataSourceTest::testGet6ByClientId() {
     ASSERT_TRUE(hdsptr_);
 
     // Create a host reservations.
-    HostPtr host1 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
-    HostPtr host2 = initializeHost6("2001:db8::2", BaseHostDataSource::ID_DUID, true);
+    HostPtr host1 = initializeHost6("2001:db8::1", Host::IDENT_DUID, true);
+    HostPtr host2 = initializeHost6("2001:db8::2", Host::IDENT_DUID, true);
 
     // Sanity check: make sure the hosts have different HW addresses.
     ASSERT_TRUE(host1->getDuid());
@@ -671,8 +708,13 @@ void GenericHostDataSourceTest::testGet6ByClientId() {
     SubnetID subnet1 = host1->getIPv6SubnetID();
     SubnetID subnet2 = host2->getIPv6SubnetID();
 
-    ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, host1->getDuid(), HWAddrPtr());
-    ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, host2->getDuid(), HWAddrPtr());
+    ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, Host::IDENT_DUID,
+                                           &host1->getIdentifier()[0],
+                                           host1->getIdentifier().size());
+
+    ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, Host::IDENT_DUID,
+                                           &host2->getIdentifier()[0],
+                                           host2->getIdentifier().size());
 
     // Now let's check if we got what we expected.
     ASSERT_TRUE(from_hds1);
@@ -682,35 +724,43 @@ void GenericHostDataSourceTest::testGet6ByClientId() {
 }
 
 void
-GenericHostDataSourceTest::testSubnetId6(int subnets, BaseHostDataSource::IdType id) {
+GenericHostDataSourceTest::testSubnetId6(int subnets, Host::IdentifierType id) {
 
     // Make sure we have a pointer to the host data source.
     ASSERT_TRUE(hdsptr_);
 
-    HostPtr host = initializeHost6("2001:db8::0", id, true);
-
+    HostPtr host;
+    IOAddress current_address("2001:db8::0");
     for (int i = 0; i < subnets; ++i) {
+        // Last boolean value set to false indicates that the same identifier
+        // must be used for each generated host.
+        host = initializeHost6(current_address.toText(), id, true, false);
+
         host->setIPv4SubnetID(i + 1000);
         host->setIPv6SubnetID(i + 1000);
 
         // Check that the same host can have reservations in multiple subnets.
         EXPECT_NO_THROW(hdsptr_->add(host));
+
+        // Increase address to make sure we don't assign the same address
+        // in different subnets.
+        current_address = IOAddress::increase(current_address);
     }
 
     // Check that the reservations can be retrieved from each subnet separately.
     for (int i = 0; i < subnets; ++i) {
 
         // Try to retrieve the host
-        ConstHostPtr from_hds = hdsptr_->get6(i + 1000, host->getDuid(),
-                                              host->getHWAddress());
+        ConstHostPtr from_hds = hdsptr_->get6(i + 1000, id, &host->getIdentifier()[0],
+                                              host->getIdentifier().size());
 
-        ASSERT_TRUE(from_hds);
+        ASSERT_TRUE(from_hds) << "failed for i=" << i;
         EXPECT_EQ(i + 1000, from_hds->getIPv6SubnetID());
     }
 
     // Check that the hosts can all be retrived by HW address or DUID
-    ConstHostCollection all_by_id = hdsptr_->getAll(host->getHWAddress(),
-                                                    host->getDuid());
+    ConstHostCollection all_by_id = hdsptr_->getAll(id, &host->getIdentifier()[0],
+                                                    host->getIdentifier().size());
     ASSERT_EQ(subnets, all_by_id.size());
 
     // Check that the returned values are as expected.
@@ -722,7 +772,7 @@ GenericHostDataSourceTest::testSubnetId6(int subnets, BaseHostDataSource::IdType
     }
 }
 
-void GenericHostDataSourceTest::testGetByIPv6(BaseHostDataSource::IdType id,
+void GenericHostDataSourceTest::testGetByIPv6(Host::IdentifierType id,
                                               bool prefix) {
     // Make sure we have a pointer to the host data source.
     ASSERT_TRUE(hdsptr_);
@@ -770,7 +820,7 @@ void GenericHostDataSourceTest::testAddDuplicate6WithSameDUID() {
     ASSERT_TRUE(hdsptr_);
 
     // Create a host reservations.
-    HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
+    HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_DUID, true);
 
     // Add this reservation once.
     ASSERT_NO_THROW(hdsptr_->add(host));
@@ -784,7 +834,7 @@ void GenericHostDataSourceTest::testAddDuplicate6WithSameHWAddr() {
     ASSERT_TRUE(hdsptr_);
 
     // Create a host reservations.
-    HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_HWADDR, true);
+    HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true);
 
     // Add this reservation once.
     ASSERT_NO_THROW(hdsptr_->add(host));
@@ -805,6 +855,17 @@ void GenericHostDataSourceTest::testAddDuplicate4() {
 
     // Then try to add it again, it should throw an exception.
     ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+    // This time use a different host identifier and try again.
+    // This update should be rejected because of duplicated
+    // address.
+    ASSERT_NO_THROW(host->setIdentifier("01:02:03:04:05:06", "hw-address"));
+    ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+    // Modify address to avoid its duplication and make sure
+    // we can now add the host.
+    ASSERT_NO_THROW(host->setIPv4Reservation(IOAddress("192.0.2.3")));
+    EXPECT_NO_THROW(hdsptr_->add(host));
 }
 
 void GenericHostDataSourceTest::testAddr6AndPrefix(){
@@ -812,7 +873,7 @@ void GenericHostDataSourceTest::testAddr6AndPrefix(){
     ASSERT_TRUE(hdsptr_);
 
     // Create a host reservations with prefix reservation (prefix = true)
-    HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
+    HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_DUID, true);
 
     // Create IPv6 reservation (for an address) and add it to the host
     IPv6Resrv resv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2"), 128);
@@ -822,13 +883,17 @@ void GenericHostDataSourceTest::testAddr6AndPrefix(){
     ASSERT_NO_THROW(hdsptr_->add(host));
 
     // Get this host by DUID
-    ConstHostPtr from_hds = hdsptr_->get6(host->getIPv6SubnetID(), host->getDuid(), HWAddrPtr());
+    ConstHostPtr from_hds = hdsptr_->get6(host->getIPv6SubnetID(),
+                                          Host::IDENT_DUID,
+                                          &host->getIdentifier()[0],
+                                          host->getIdentifier().size());
 
     // Make sure we got something back
     ASSERT_TRUE(from_hds);
 
     // Check if reservations are the same
-    compareReservations6(host->getIPv6Reservations(), from_hds->getIPv6Reservations());
+    compareReservations6(host->getIPv6Reservations(),
+                         from_hds->getIPv6Reservations());
 }
 
 void GenericHostDataSourceTest::testMultipleReservations(){
@@ -836,7 +901,7 @@ void GenericHostDataSourceTest::testMultipleReservations(){
     ASSERT_TRUE(hdsptr_);
     uint8_t len = 128;
 
-    HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, false);
+    HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
 
     // Add some reservations
     IPv6Resrv resv1(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::6"), len);
@@ -866,8 +931,8 @@ void GenericHostDataSourceTest::testMultipleReservationsDifferentOrder(){
     ASSERT_TRUE(hdsptr_);
     uint8_t len = 128;
 
-    HostPtr host1 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, false);
-    HostPtr host2 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, false);
+    HostPtr host1 = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+    HostPtr host2 = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
 
     // Add some reservations
     IPv6Resrv resv1(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::6"), len);

+ 17 - 9
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h

@@ -8,6 +8,7 @@
 #define GENERIC_HOST_DATA_SOURCE_UNITTEST_H
 
 #include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/host.h>
 #include <dhcp/classify.h>
 #include <gtest/gtest.h>
 #include <vector>
@@ -46,21 +47,28 @@ public:
     /// @brief Creates a host reservation for specified IPv6 address.
     ///
     /// @param address IPv6 address to be reserved
-    /// @param id type of identifier (ID_DUID or ID_HWADDR are supported)
+    /// @param id type of identifier (IDENT_DUID or IDENT_HWADDR are supported)
     /// @param prefix reservation type (true = prefix, false = address)
+    /// @param new_identifier Boolean value indicating if new host
+    /// identifier should be generated or the same as previously.
     ///
     /// @return generated Host object
-    HostPtr initializeHost6(std::string address, BaseHostDataSource::IdType id,
-                            bool prefix);
+    HostPtr initializeHost6(std::string address, Host::IdentifierType id,
+                            bool prefix, bool new_identifier = true);
 
     /// @brief Generates a hardware address in text version.
     ///
+    /// @param increase A boolean value indicating if new address (increased)
+    /// must be generated or the same address as previously.
     /// @return HW address in textual form acceptable by Host constructor
-    std::string generateHWAddr();
+    std::string generateHWAddr(const bool new_identifier = true);
 
     /// @brief Generates a hardware address in text version.
+    ///
+    /// @param increase A boolean value indicating if new DUID (increased)
+    /// must be generated or the same DUID as previously.
     /// @return DUID in textual form acceptable by Host constructor
-    std::string generateDuid();
+    std::string generateDuid(const bool new_identifier = true);
 
     /// @brief Compares hardware addresses of the two hosts.
     ///
@@ -183,9 +191,9 @@ public:
     ///        checks that they can be retrieved properly.
     ///
     /// Uses gtest macros to report failures.
-    /// @param id type of the identifier to be used (HWAddr or DUID)
+    /// @param id type of the identifier to be used (IDENT_HWADDR or IDENT_DUID)
     /// @param prefix true - reserve IPv6 prefix, false - reserve IPv6 address
-    void testGetByIPv6(BaseHostDataSource::IdType id, bool prefix);
+    void testGetByIPv6(Host::IdentifierType id, bool prefix);
 
     /// @brief Test that hosts can be retrieved by hardware address.
     ///
@@ -216,8 +224,8 @@ public:
     /// Uses gtest macros to report failures.
     ///
     /// @param subnets number of subnets to test
-    /// @param id identifier type (ID_HWADDR or ID_DUID)
-    void testSubnetId6(int subnets, BaseHostDataSource::IdType id);
+    /// @param id identifier type (IDENT_HWADDR or IDENT_DUID)
+    void testSubnetId6(int subnets, Host::IdentifierType id);
 
     /// @brief Test if the duplicate host with same DUID can't be inserted.
     ///

+ 0 - 6
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc

@@ -2213,9 +2213,6 @@ GenericLeaseMgrTest::testGetDeclinedLeases4() {
     EXPECT_NE(0, declined_state);
     EXPECT_NE(0, default_state);
 
-    // Remember expired leases returned.
-    std::vector<Lease4Ptr> saved_expired_leases = expired_leases;
-
     // Remove expired leases again.
     expired_leases.clear();
 
@@ -2366,9 +2363,6 @@ GenericLeaseMgrTest::testGetDeclinedLeases6() {
     EXPECT_NE(0, declined_state);
     EXPECT_NE(0, default_state);
 
-    // Remember expired leases returned.
-    std::vector<Lease6Ptr> saved_expired_leases = expired_leases;
-
     // Remove expired leases again.
     expired_leases.clear();
 

+ 48 - 14
src/lib/dhcpsrv/tests/host_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -99,9 +99,13 @@ TEST_F(HostMgrTest, getAll) {
 
     // If there non-matching HW address is specified, nothing should be
     // returned.
-    ASSERT_TRUE(HostMgr::instance().getAll(hwaddrs_[1]).empty());
+    ASSERT_TRUE(HostMgr::instance().getAll(Host::IDENT_HWADDR,
+                                           &hwaddrs_[1]->hwaddr_[0],
+                                           hwaddrs_[1]->hwaddr_.size()).empty());
     // For the correct HW address, there should be two reservations.
-    hosts = HostMgr::instance().getAll(hwaddrs_[0]);
+    hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR,
+                                       &hwaddrs_[0]->hwaddr_[0],
+                                       hwaddrs_[0]->hwaddr_.size());
     ASSERT_EQ(2, hosts.size());
 
     // We don't know the order in which the reservations are returned so
@@ -137,9 +141,8 @@ TEST_F(HostMgrTest, getAll) {
 
 // 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) {
+// the server's configuration.
+TEST_F(HostMgrTest, getAll4) {
     ConstHostCollection hosts =
         HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
     ASSERT_TRUE(hosts.empty());
@@ -156,8 +159,12 @@ TEST_F(HostMgrTest, DISABLED_getAll4) {
     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.
+    // Make sure that IPv4 address is correct.
+    EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_EQ("192.0.2.5", hosts[1]->getIPv4Reservation().toText());
+
+    // Make sure that two different hosts were returned.
+    EXPECT_NE(hosts[0]->getIPv4SubnetID(), hosts[1]->getIPv4SubnetID());
 }
 
 // This test verifies that it is possible to retrieve a reservation for the
@@ -173,7 +180,9 @@ TEST_F(HostMgrTest, get4) {
                                         IOAddress("192.0.2.5"))));
     CfgMgr::instance().commit();
 
-    host = HostMgr::instance().get4(SubnetID(1), hwaddrs_[0]);
+    host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR,
+                                    &hwaddrs_[0]->hwaddr_[0],
+                                    hwaddrs_[0]->hwaddr_.size());
     ASSERT_TRUE(host);
     EXPECT_EQ(1, host->getIPv4SubnetID());
     EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
@@ -193,31 +202,56 @@ TEST_F(HostMgrTest, get6) {
     getCfgHosts()->add(new_host);
     CfgMgr::instance().commit();
 
-    host = HostMgr::instance().get6(SubnetID(2), duids_[0]);
+    host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID,
+                                    &duids_[0]->getDuid()[0],
+                                    duids_[0]->getDuid().size());
     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) {
+// particular IPv6 prefix using HostMgr.
+TEST_F(HostMgrTest, get6ByPrefix) {
     ConstHostPtr host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
     ASSERT_FALSE(host);
 
+    // Add a host with a reservation for a prefix 2001:db8:1::/64.
     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);
+
+    // Add another host having a reservation for prefix 2001:db8:1:0:6::/72.
+    new_host.reset(new Host(duids_[1]->toText(), "duid", SubnetID(2),
+                            SubnetID(3), IOAddress::IPV4_ZERO_ADDRESS()));
+    new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                       IOAddress("2001:db8:1:0:6::"), 72));
+    getCfgHosts()->add(new_host);
     CfgMgr::instance().commit();
 
+    // Retrieve first reservation.
     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)));
+
+    // Make sure the first reservation is not retrieved when the prefix
+    // length is incorrect.
+    host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 72);
+    EXPECT_FALSE(host);
+
+    // Retrieve second reservation.
+    host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 72);
+    ASSERT_TRUE(host);
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+    IOAddress("2001:db8:1:0:6::"), 72)));
+
+    // Make sure the second reservation is not retrieved when the prefix
+    // length is incorrect.
+    host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 64);
+    EXPECT_FALSE(host);
 }
 
 } // end of anonymous namespace

+ 9 - 7
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -631,16 +631,18 @@ TEST(HostTest, addOptions6) {
     EXPECT_TRUE(options->empty());
 }
 
+// This test verifies that it is possible to retrieve a textual
+// representation of the host identifier.
 TEST(HostTest, getIdentifierAsText) {
     Host host1("01:02:03:04:05:06", "hw-address",
                SubnetID(1), SubnetID(2),
                IOAddress("192.0.2.3"));
-    EXPECT_EQ("hwaddr=01:02:03:04:05:06", host1.getIdentifierAsText());
+    EXPECT_EQ("hwaddr=010203040506", host1.getIdentifierAsText());
 
     Host host2("0a:0b:0c:0d:0e:0f:ab:cd:ef", "duid",
                SubnetID(1), SubnetID(2),
                IOAddress("192.0.2.3"));
-    EXPECT_EQ("duid=0a:0b:0c:0d:0e:0f:ab:cd:ef",
+    EXPECT_EQ("duid=0A0B0C0D0E0FABCDEF",
               host2.getIdentifierAsText());
 }
 
@@ -666,7 +668,7 @@ TEST(HostTest, toText) {
     );
 
     // Make sure that the output is correct,
-    EXPECT_EQ("hwaddr=01:02:03:04:05:06 ipv4_subnet_id=1 ipv6_subnet_id=2"
+    EXPECT_EQ("hwaddr=010203040506 ipv4_subnet_id=1 ipv6_subnet_id=2"
               " hostname=myhost.example.com"
               " ipv4_reservation=192.0.2.3"
               " ipv6_reservation0=2001:db8:1::cafe"
@@ -680,7 +682,7 @@ TEST(HostTest, toText) {
     host->removeIPv4Reservation();
     host->setIPv4SubnetID(0);
 
-    EXPECT_EQ("hwaddr=01:02:03:04:05:06 ipv6_subnet_id=2"
+    EXPECT_EQ("hwaddr=010203040506 ipv6_subnet_id=2"
               " hostname=(empty) ipv4_reservation=(no)"
               " ipv6_reservation0=2001:db8:1::cafe"
               " ipv6_reservation1=2001:db8:1::1"
@@ -695,14 +697,14 @@ TEST(HostTest, toText) {
                                         IOAddress::IPV4_ZERO_ADDRESS(),
                                         "myhost")));
 
-    EXPECT_EQ("duid=11:12:13:14:15 hostname=myhost ipv4_reservation=(no)"
+    EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
               " ipv6_reservations=(none)", host->toText());
 
     // Add some classes.
     host->addClientClass4("modem");
     host->addClientClass4("router");
 
-    EXPECT_EQ("duid=11:12:13:14:15 hostname=myhost ipv4_reservation=(no)"
+    EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
               " ipv6_reservations=(none)"
               " dhcp4_class0=modem dhcp4_class1=router",
               host->toText());
@@ -710,7 +712,7 @@ TEST(HostTest, toText) {
     host->addClientClass6("hub");
     host->addClientClass6("device");
 
-    EXPECT_EQ("duid=11:12:13:14:15 hostname=myhost ipv4_reservation=(no)"
+    EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
               " ipv6_reservations=(none)"
               " dhcp4_class0=modem dhcp4_class1=router"
               " dhcp6_class0=device dhcp6_class1=hub",

+ 6 - 5
src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc

@@ -9,6 +9,7 @@
 #include <asiolink/io_address.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <exceptions/exceptions.h>
+#include <dhcpsrv/host.h>
 #include <dhcpsrv/mysql_connection.h>
 #include <dhcpsrv/mysql_host_data_source.h>
 #include <dhcpsrv/tests/generic_host_data_source_unittest.h>
@@ -272,25 +273,25 @@ TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
 // Test verifies that host with IPv6 address and DUID can be added and
 // later retrieved by IPv6 address.
 TEST_F(MySqlHostDataSourceTest, get6AddrWithDuid) {
-    testGetByIPv6(BaseHostDataSource::ID_DUID, false);
+    testGetByIPv6(Host::IDENT_DUID, false);
 }
 
 // Test verifies that host with IPv6 address and HWAddr can be added and
 // later retrieved by IPv6 address.
 TEST_F(MySqlHostDataSourceTest, get6AddrWithHWAddr) {
-    testGetByIPv6(BaseHostDataSource::ID_HWADDR, false);
+    testGetByIPv6(Host::IDENT_HWADDR, false);
 }
 
 // Test verifies that host with IPv6 prefix and DUID can be added and
 // later retrieved by IPv6 prefix.
 TEST_F(MySqlHostDataSourceTest, get6PrefixWithDuid) {
-    testGetByIPv6(BaseHostDataSource::ID_DUID, true);
+    testGetByIPv6(Host::IDENT_DUID, true);
 }
 
 // Test verifies that host with IPv6 prefix and HWAddr can be added and
 // later retrieved by IPv6 prefix.
 TEST_F(MySqlHostDataSourceTest, get6PrefixWithHWaddr) {
-    testGetByIPv6(BaseHostDataSource::ID_HWADDR, true);
+    testGetByIPv6(Host::IDENT_HWADDR, true);
 }
 
 // Test verifies if a host reservation can be added and later retrieved by
@@ -373,7 +374,7 @@ TEST_F(MySqlHostDataSourceTest, multipleSubnetsClientId) {
 // Insert 10 host reservations for different subnets. Make sure that
 // get6(subnet-id, ...) calls return correct reservation.
 TEST_F(MySqlHostDataSourceTest, subnetId6) {
-    testSubnetId6(10, BaseHostDataSource::ID_HWADDR);
+    testSubnetId6(10, Host::IDENT_HWADDR);
 }
 
 // Test if the duplicate host instances can't be inserted. The test logic is as

+ 38 - 0
src/lib/dhcpsrv/testutils/schema_mysql_copy.h

@@ -35,6 +35,7 @@ const char* destroy_statement[] = {
     "DROP TABLE hosts",
     "DROP TABLE dhcp4_options",
     "DROP TABLE dhcp6_options",
+    "DROP TABLE host_identifier_type",
 
     "DROP TRIGGER host_BDEL",
     NULL
@@ -255,6 +256,43 @@ const char* create_statement[] = {
 
     // Schema upgrade to 4.0 ends here.
 
+    "DROP INDEX key_dhcp4_identifier_subnet_id ON hosts",
+    "CREATE UNIQUE INDEX key_dhcp4_identifier_subnet_id "
+      "ON hosts "
+        "(dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp4_subnet_id ASC)",
+
+    "DROP INDEX key_dhcp6_identifier_subnet_id ON hosts",
+    "CREATE UNIQUE INDEX key_dhcp6_identifier_subnet_id "
+      "ON hosts "
+        "(dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp6_subnet_id ASC)",
+
+    "CREATE UNIQUE INDEX key_dhcp4_ipv4_address_subnet_id "
+      "ON hosts "
+        "(ipv4_address ASC, dhcp4_subnet_id ASC)",
+
+    "CREATE UNIQUE INDEX key_dhcp6_address_prefix_len "
+      "ON ipv6_reservations (address ASC , prefix_len ASC)",
+
+    "CREATE TABLE IF NOT EXISTS host_identifier_type ("
+      "type TINYINT PRIMARY KEY NOT NULL,"
+      "name VARCHAR(32)"
+    ") ENGINE = INNODB",
+
+    "START TRANSACTION",
+    "INSERT INTO host_identifier_type VALUES (0, \"hw-address\")",
+    "INSERT INTO host_identifier_type VALUES (1, \"duid\")",
+    "INSERT INTO host_identifier_type VALUES (2, \"circuit-id\")",
+    "COMMIT",
+
+    "ALTER TABLE hosts "
+    "ADD CONSTRAINT fk_host_identifier_type FOREIGN KEY (dhcp_identifier_type) "
+      "REFERENCES host_identifier_type (type)",
+
+    "UPDATE schema_version "
+      "SET version = '4', minor = '2'",
+
+    // Schema upgrade to 4.2 ends here.
+
     NULL
 };
 

+ 45 - 1
src/lib/dhcpsrv/writable_host_data_source.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -40,6 +40,22 @@ public:
     virtual HostCollection
     getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) = 0;
 
+    /// @brief Non-const version of the @c getAll const method.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for a specified identifier. This method may return multiple hosts
+    /// because a particular client may have reservations in multiple subnets.
+    ///
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of non-const @c Host objects.
+    virtual HostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin,
+           const size_t identifier_len) = 0;
+
     /// @brief Returns a collection of hosts using the specified IPv4 address.
     ///
     /// This method may return multiple @c Host objects if they are connected
@@ -69,6 +85,20 @@ public:
     get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
          const DuidPtr& duid = DuidPtr()) = 0;
 
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Non-const @c Host object for which reservation has been made
+    /// using the specified identifier.
+    virtual HostPtr
+    get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) = 0;
+
     /// @brief Returns a host connected to the IPv6 subnet.
     ///
     /// Implementations of this method should guard against the case when
@@ -87,6 +117,20 @@ public:
     get6(const SubnetID& subnet_id, const DuidPtr& duid,
          const HWAddrPtr& hwaddr = HWAddrPtr()) = 0;
 
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a begining of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Non-const @c Host object for which reservation has been made
+    /// using the specified identifier.
+    virtual HostPtr
+    get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+         const uint8_t* identifier_begin, const size_t identifier_len) = 0;
+
     /// @brief Returns a host using the specified IPv6 prefix.
     ///
     /// @param prefix IPv6 prefix for which the @c Host object is searched.