]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3375] Made cfg_host MT safe
authorFrancis Dupont <fdupont@isc.org>
Thu, 30 May 2024 23:14:18 +0000 (01:14 +0200)
committerFrancis Dupont <fdupont@isc.org>
Mon, 10 Jun 2024 07:19:53 +0000 (09:19 +0200)
src/lib/dhcpsrv/cfg_hosts.cc
src/lib/dhcpsrv/cfg_hosts.h
src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc

index 433aabe2e903d3e2a1da3705fff4de58f1728e93..2888e947eaba19df11e20ee936b797ec6c3f00c9 100644 (file)
@@ -13,6 +13,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <exceptions/exceptions.h>
 #include <util/encode/encode.h>
+#include <util/multi_threading_mgr.h>
 #include <boost/foreach.hpp>
 #include <ostream>
 #include <string>
 
 using namespace isc::asiolink;
 using namespace isc::data;
+using namespace isc::util;
 using namespace std;
 
 namespace isc {
 namespace dhcp {
 
+CfgHosts::CfgHosts() : mutex_(new mutex()) {
+}
+
 ConstHostCollection
 CfgHosts::getAll(const Host::IdentifierType& identifier_type,
                  const uint8_t* identifier_begin,
@@ -32,6 +37,7 @@ CfgHosts::getAll(const Host::IdentifierType& identifier_type,
     // Do not issue logging message here because it will be logged by
     // the getAllInternal method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal<ConstHostCollection>(identifier_type, identifier_begin,
                                         identifier_len, collection);
     return (collection);
@@ -43,6 +49,7 @@ CfgHosts::getAll(const Host::IdentifierType& identifier_type,
     // Do not issue logging message here because it will be logged by
     // the getAllInternal method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal<HostCollection>(identifier_type, identifier_begin,
                                    identifier_len, collection);
     return (collection);
@@ -53,6 +60,7 @@ CfgHosts::getAll4(const SubnetID& subnet_id) const {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal4 method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal4<ConstHostCollection>(subnet_id, collection);
     return (collection);
 }
@@ -62,6 +70,7 @@ CfgHosts::getAll4(const SubnetID& subnet_id) {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal4 method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal4<HostCollection>(subnet_id, collection);
     return (collection);
 }
@@ -71,6 +80,7 @@ CfgHosts::getAll6(const SubnetID& subnet_id) const {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal6 method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal6<ConstHostCollection>(subnet_id, collection);
     return (collection);
 }
@@ -80,6 +90,7 @@ CfgHosts::getAll6(const SubnetID& subnet_id) {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal6 method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal6<HostCollection>(subnet_id, collection);
     return (collection);
 }
@@ -89,6 +100,7 @@ CfgHosts::getAllbyHostname(const std::string& hostname) const {
     // Do not issue logging message here because it will be logged by
     // the getAllbyHostnameInternal method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllbyHostnameInternal<ConstHostCollection>(hostname, collection);
     return (collection);
 }
@@ -98,6 +110,7 @@ CfgHosts::getAllbyHostname(const std::string& hostname) {
     // Do not issue logging message here because it will be logged by
     // the getAllbyHostnameInternal method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllbyHostnameInternal<HostCollection>(hostname, collection);
     return (collection);
 }
@@ -108,6 +121,7 @@ CfgHosts::getAllbyHostname4(const std::string& hostname,
     // Do not issue logging message here because it will be logged by
     // the getAllbyHostnameInternal4 method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllbyHostnameInternal4<ConstHostCollection>(hostname, subnet_id, collection);
     return (collection);
 }
@@ -118,6 +132,7 @@ CfgHosts::getAllbyHostname4(const std::string& hostname,
     // Do not issue logging message here because it will be logged by
     // the getAllbyHostnameInternal4 method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllbyHostnameInternal4<HostCollection>(hostname, subnet_id, collection);
     return (collection);
 }
@@ -128,6 +143,7 @@ CfgHosts::getAllbyHostname6(const std::string& hostname,
     // Do not issue logging message here because it will be logged by
     // the getAllbyHostnameInternal6 method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllbyHostnameInternal6<ConstHostCollection>(hostname, subnet_id, collection);
     return (collection);
 }
@@ -138,6 +154,7 @@ CfgHosts::getAllbyHostname6(const std::string& hostname,
     // Do not issue logging message here because it will be logged by
     // the getAllbyHostnameInternal6 method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllbyHostnameInternal6<HostCollection>(hostname, subnet_id, collection);
     return (collection);
 }
@@ -150,6 +167,7 @@ CfgHosts::getPage4(const SubnetID& subnet_id,
     // Do not issue logging message here because it will be logged by
     // the getPageInternal4 method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getPageInternal4<ConstHostCollection>(subnet_id,
                                           lower_host_id,
                                           page_size,
@@ -165,6 +183,7 @@ CfgHosts::getPage4(const SubnetID& subnet_id,
     // Do not issue logging message here because it will be logged by
     // the getPageInternal4 method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getPageInternal4<HostCollection>(subnet_id,
                                      lower_host_id,
                                      page_size,
@@ -180,6 +199,7 @@ CfgHosts::getPage6(const SubnetID& subnet_id,
     // Do not issue logging message here because it will be logged by
     // the getPageInternal6 method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getPageInternal6<ConstHostCollection>(subnet_id,
                                           lower_host_id,
                                           page_size,
@@ -195,6 +215,7 @@ CfgHosts::getPage6(const SubnetID& subnet_id,
     // Do not issue logging message here because it will be logged by
     // the getPageInternal6 method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getPageInternal6<HostCollection>(subnet_id,
                                      lower_host_id,
                                      page_size,
@@ -209,6 +230,7 @@ CfgHosts::getPage4(size_t& /*source_index*/,
     // Do not issue logging message here because it will be logged by
     // the getPageInternal method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getPageInternal<ConstHostCollection>(lower_host_id,
                                          page_size,
                                          collection);
@@ -222,6 +244,7 @@ CfgHosts::getPage4(size_t& /*source_index*/,
     // Do not issue logging message here because it will be logged by
     // the getPageInternal method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getPageInternal<HostCollection>(lower_host_id,
                                     page_size,
                                     collection);
@@ -235,6 +258,7 @@ CfgHosts::getPage6(size_t& /*source_index*/,
     // Do not issue logging message here because it will be logged by
     // the getPageInternal method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getPageInternal<ConstHostCollection>(lower_host_id,
                                          page_size,
                                          collection);
@@ -248,6 +272,7 @@ CfgHosts::getPage6(size_t& /*source_index*/,
     // Do not issue logging message here because it will be logged by
     // the getPageInternal method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getPageInternal<HostCollection>(lower_host_id,
                                     page_size,
                                     collection);
@@ -259,6 +284,7 @@ CfgHosts::getAll4(const IOAddress& address) const {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal4 method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal4<ConstHostCollection>(address, collection);
     return (collection);
 }
@@ -268,6 +294,7 @@ CfgHosts::getAll4(const IOAddress& address) {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal4 method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal4<HostCollection>(address, collection);
     return (collection);
 }
@@ -277,6 +304,7 @@ CfgHosts::getAll6(const IOAddress& address) const {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal6 method.
     ConstHostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal6<ConstHostCollection>(address, collection);
     return (collection);
 }
@@ -286,6 +314,7 @@ CfgHosts::getAll6(const IOAddress& address) {
     // Do not issue logging message here because it will be logged by
     // the getAllInternal6 method.
     HostCollection collection;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal6<HostCollection>(address, collection);
     return (collection);
 }
@@ -632,6 +661,42 @@ CfgHosts::getAllInternal4(const IOAddress& address, Storage& storage) const {
         .arg(storage.size());
 }
 
+ConstHostCollection
+CfgHosts::getAllInternal4(const SubnetID& subnet_id,
+                          const IOAddress& address) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4)
+        .arg(subnet_id)
+        .arg(address.toText());
+
+    // Must not specify address other than IPv4.
+    if (!address.isV4()) {
+        isc_throw(BadHostAddress, "must specify an IPv4 address when searching"
+                  " for a host, specified address was " << address);
+    }
+    // Search for the Host using the reserved IPv4 address as a key.
+    ConstHostCollection hosts;
+    const HostContainerIndex1& idx = hosts_.get<1>();
+    HostContainerIndex1Range r = idx.equal_range(address);
+    // Append each Host object to the storage.
+    BOOST_FOREACH(auto const& host, r) {
+        if (host->getIPv4SubnetID() == subnet_id) {
+            LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+                      HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST)
+                .arg(subnet_id)
+                .arg(address.toText())
+                .arg(host->toText());
+            hosts.push_back(host);
+        }
+    }
+
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+              HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT)
+        .arg(subnet_id)
+        .arg(address.toText())
+        .arg(hosts.size());
+    return (hosts);
+}
+
 template<typename Storage>
 void
 CfgHosts::getAllInternal6(const IOAddress& address, Storage& storage) const {
@@ -665,6 +730,7 @@ CfgHosts::get4(const SubnetID& subnet_id,
                const Host::IdentifierType& identifier_type,
                const uint8_t* identifier_begin,
                const size_t identifier_len) const {
+    MultiThreadingLock lock(*mutex_);
     return (getHostInternal(subnet_id, false, identifier_type, identifier_begin,
                             identifier_len));
 }
@@ -674,6 +740,7 @@ CfgHosts::get4(const SubnetID& subnet_id,
                const Host::IdentifierType& identifier_type,
                const uint8_t* identifier_begin,
                const size_t identifier_len) {
+    MultiThreadingLock lock(*mutex_);
     return (getHostInternal(subnet_id, false, identifier_type, identifier_begin,
                             identifier_len));
 }
@@ -683,46 +750,29 @@ CfgHosts::get4(const SubnetID& subnet_id, const IOAddress& address) const {
     LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4)
         .arg(subnet_id).arg(address.toText());
 
-    ConstHostCollection hosts = getAll4(address);
-    for (auto const& host : hosts) {
-        if (host->getIPv4SubnetID() == subnet_id) {
-            LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
-                      HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST)
-                .arg(subnet_id)
-                .arg(address.toText())
-                .arg(host->toText());
-            return (host);
-        }
+    ConstHostCollection hosts = getAllInternal4(subnet_id, address);
+    if (hosts.empty()) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+                  HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL)
+            .arg(subnet_id)
+            .arg(address.toText());
+        return (ConstHostPtr());
+    } else {
+        ConstHostPtr host = *hosts.begin();
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+                  HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST)
+            .arg(subnet_id)
+            .arg(address.toText())
+            .arg(host->toText());
+        return (host);
     }
-
-    LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL)
-        .arg(subnet_id).arg(address.toText());
-    return (ConstHostPtr());
 }
 
 ConstHostCollection
 CfgHosts::getAll4(const SubnetID& subnet_id,
                   const asiolink::IOAddress& address) const {
-    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4)
-        .arg(subnet_id).arg(address.toText());
-
-    ConstHostCollection hosts;
-    for (auto const& host : getAll4(address)) {
-        if (host->getIPv4SubnetID() == subnet_id) {
-            LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
-                      HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST)
-                .arg(subnet_id)
-                .arg(address.toText())
-                .arg(host->toText());
-            hosts.push_back(host);
-        }
-    }
-    LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT)
-        .arg(subnet_id)
-        .arg(address.toText())
-        .arg(hosts.size());
-
-    return (hosts);
+    MultiThreadingLock lock(*mutex_);
+    return (getAllInternal4(subnet_id, address));
 }
 
 ConstHostPtr
@@ -730,6 +780,7 @@ CfgHosts::get6(const SubnetID& subnet_id,
                const Host::IdentifierType& identifier_type,
                const uint8_t* identifier_begin,
                const size_t identifier_len) const {
+    MultiThreadingLock lock(*mutex_);
     return (getHostInternal(subnet_id, true, identifier_type, identifier_begin,
                             identifier_len));
 }
@@ -739,17 +790,20 @@ CfgHosts::get6(const SubnetID& subnet_id,
                const Host::IdentifierType& identifier_type,
                const uint8_t* identifier_begin,
                const size_t identifier_len) {
+    MultiThreadingLock lock(*mutex_);
     return (getHostInternal(subnet_id, true, identifier_type, identifier_begin,
                             identifier_len));
 }
 
 ConstHostPtr
 CfgHosts::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
+    MultiThreadingLock lock(*mutex_);
     return (getHostInternal6<ConstHostPtr>(prefix, prefix_len));
 }
 
 HostPtr
 CfgHosts::get6(const IOAddress& prefix, const uint8_t prefix_len) {
+    MultiThreadingLock lock(*mutex_);
     return (getHostInternal6<HostPtr>(prefix, prefix_len));
 }
 
@@ -757,6 +811,7 @@ ConstHostPtr
 CfgHosts::get6(const SubnetID& subnet_id,
                const asiolink::IOAddress& address) const {
     // Do not log here because getHostInternal6 logs.
+    MultiThreadingLock lock(*mutex_);
     return (getHostInternal6<ConstHostPtr, ConstHostCollection>(subnet_id, address));
 }
 
@@ -764,6 +819,7 @@ HostPtr
 CfgHosts::get6(const SubnetID& subnet_id,
                const asiolink::IOAddress& address) {
     // Do not log here because getHostInternal6 logs.
+    MultiThreadingLock lock(*mutex_);
     return (getHostInternal6<HostPtr, HostCollection>(subnet_id, address));
 }
 
@@ -771,6 +827,7 @@ ConstHostCollection
 CfgHosts::getAll6(const SubnetID& subnet_id,
                   const asiolink::IOAddress& address) const {
     ConstHostCollection hosts;
+    MultiThreadingLock lock(*mutex_);
     getAllInternal6(subnet_id, address, hosts);
     return (hosts);
 }
@@ -963,6 +1020,8 @@ CfgHosts::add(const HostPtr& host) {
                   " 0 when adding new host reservation");
     }
 
+    MultiThreadingLock lock(*mutex_);
+
     add4(host);
 
     add6(host);
@@ -977,16 +1036,16 @@ CfgHosts::add4(const HostPtr& host) {
     // Check for duplicates for the specified IPv4 subnet.
     if (host->getIPv4SubnetID() != SUBNET_ID_UNUSED) {
         if (hwaddr && !hwaddr->hwaddr_.empty() &&
-            get4(host->getIPv4SubnetID(), Host::IDENT_HWADDR,
-                 &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size())) {
+            getHostInternal(host->getIPv4SubnetID(), false, Host::IDENT_HWADDR,
+                            &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size())) {
             isc_throw(DuplicateHost, "failed to add new host using the HW"
                       << " address '" << hwaddr->toText(false)
                       << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
                       << "' as this host has already been added");
         }
         if (duid && !duid->getDuid().empty() &&
-            get4(host->getIPv4SubnetID(), Host::IDENT_DUID,
-                 &duid->getDuid()[0], duid->getDuid().size())) {
+            getHostInternal(host->getIPv4SubnetID(), false, Host::IDENT_DUID,
+                            &duid->getDuid()[0], duid->getDuid().size())) {
             isc_throw(DuplicateHost, "failed to add new host using the "
                       << "DUID '" << duid->toText()
                       << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
@@ -995,16 +1054,16 @@ CfgHosts::add4(const HostPtr& host) {
     // Check for duplicates for the specified IPv6 subnet.
     } else if (host->getIPv6SubnetID() != SUBNET_ID_UNUSED) {
         if (duid && !duid->getDuid().empty() &&
-            get6(host->getIPv6SubnetID(), Host::IDENT_DUID,
-                 &duid->getDuid()[0], duid->getDuid().size())) {
+            getHostInternal(host->getIPv6SubnetID(), true, Host::IDENT_DUID,
+                            &duid->getDuid()[0], duid->getDuid().size())) {
             isc_throw(DuplicateHost, "failed to add new host using the "
                       << "DUID '" << duid->toText()
                       << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
                       << "' as this host has already been added");
         }
         if (hwaddr && !hwaddr->hwaddr_.empty() &&
-            get6(host->getIPv6SubnetID(), Host::IDENT_HWADDR,
-                 &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size())) {
+            getHostInternal(host->getIPv6SubnetID(), true, Host::IDENT_HWADDR,
+                            &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size())) {
             isc_throw(DuplicateHost, "failed to add new host using the HW"
                       << " address '" << hwaddr->toText(false)
                       << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
@@ -1015,7 +1074,8 @@ CfgHosts::add4(const HostPtr& host) {
     // Check if the address is already reserved for the specified IPv4 subnet.
     if (ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero() &&
         (host->getIPv4SubnetID() != SUBNET_ID_UNUSED) &&
-        get4(host->getIPv4SubnetID(), host->getIPv4Reservation())) {
+        !getAllInternal4(host->getIPv4SubnetID(),
+                         host->getIPv4Reservation()).empty()) {
         isc_throw(ReservedAddress, "failed to add new host using the HW"
                   " address '" << (hwaddr ? hwaddr->toText(false) : "(null)")
                   << " and DUID '" << (duid ? duid->toText() : "(null)")
@@ -1027,8 +1087,8 @@ CfgHosts::add4(const HostPtr& host) {
     // Check if the (identifier type, identifier) tuple is already used.
     const std::vector<uint8_t>& id = host->getIdentifier();
     if ((host->getIPv4SubnetID() != SUBNET_ID_UNUSED) && !id.empty()) {
-        if (get4(host->getIPv4SubnetID(), host->getIdentifierType(), &id[0],
-                 id.size())) {
+        if (getHostInternal(host->getIPv4SubnetID(), false,
+                            host->getIdentifierType(), &id[0], id.size())) {
             isc_throw(DuplicateHost, "failed to add duplicate IPv4 host using identifier: "
                       << Host::getIdentifierAsText(host->getIdentifierType(),
                                                    &id[0], id.size()));
@@ -1066,7 +1126,8 @@ CfgHosts::add6(const HostPtr& host) {
 
         if (ip_reservations_unique_) {
             // If there's an entry for this (subnet-id, address), reject it.
-            if (get6(host->getIPv6SubnetID(), it.second.getPrefix())) {
+            if (getHostInternal6<ConstHostPtr, ConstHostCollection>
+                (host->getIPv6SubnetID(), it.second.getPrefix())) {
                 isc_throw(DuplicateHost, "failed to add address reservation for "
                           << "host using the HW address '"
                           << (hwaddr ? hwaddr->toText(false) : "(null)")
@@ -1084,10 +1145,16 @@ bool
 CfgHosts::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
     size_t erased_hosts = 0;
     size_t erased_addresses = 0;
+    MultiThreadingLock lock(*mutex_);
     if (addr.isV4()) {
         HostContainerIndex4& idx = hosts_.get<4>();
+        ConstHostCollection hosts;
+        getAllInternal4<ConstHostCollection>(addr, hosts);
         // Delete IPv4 reservation and host.
-        for (auto const& host : getAll4(subnet_id, addr)) {
+        for (auto const& host : hosts) {
+            if (host->getIPv4SubnetID() != subnet_id) {
+                continue;
+            }
             erased_hosts += idx.erase(host->getHostId());
         }
         erased_addresses = erased_hosts;
@@ -1116,6 +1183,7 @@ CfgHosts::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
 size_t
 CfgHosts::delAll4(const SubnetID& subnet_id) {
     HostContainerIndex2& idx = hosts_.get<2>();
+    MultiThreadingLock lock(*mutex_);
     size_t erased = idx.erase(subnet_id);
 
     LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_DEL_ALL_SUBNET4)
@@ -1131,6 +1199,7 @@ CfgHosts::del4(const SubnetID& subnet_id,
                const uint8_t* identifier_begin,
                const size_t identifier_len) {
     HostContainerIndex0& idx = hosts_.get<0>();
+    MultiThreadingLock lock(*mutex_);
     auto const t = boost::make_tuple(std::vector<uint8_t>(identifier_begin,
                                                           identifier_begin + identifier_len),
                                                           identifier_type);
@@ -1159,6 +1228,7 @@ size_t
 CfgHosts::delAll6(const SubnetID& subnet_id) {
     // Delete IPv6 reservations.
     HostContainer6Index2& idx6 = hosts6_.get<2>();
+    MultiThreadingLock lock(*mutex_);
     size_t erased_addresses = idx6.erase(subnet_id);
 
     // Delete hosts.
@@ -1184,6 +1254,7 @@ CfgHosts::del6(const SubnetID& subnet_id,
     auto const t = boost::make_tuple(std::vector<uint8_t>(identifier_begin,
                                                           identifier_begin + identifier_len),
                                                           identifier_type);
+    MultiThreadingLock lock(*mutex_);
     auto const& range = idx.equal_range(t);
     size_t erased_hosts = 0;
     size_t erased_reservations = 0;
@@ -1213,6 +1284,7 @@ CfgHosts::del6(const SubnetID& subnet_id,
 
 bool
 CfgHosts::setIPReservationsUnique(const bool unique) {
+    MultiThreadingLock lock(*mutex_);
     ip_reservations_unique_ = unique;
     return (true);
 }
@@ -1236,6 +1308,7 @@ CfgHosts::toElement4() const {
     CfgHostsList result;
     // Iterate using arbitrary the index 0
     const HostContainerIndex0& idx = hosts_.get<0>();
+    MultiThreadingLock lock(*mutex_);
     for (auto const& host : idx) {
 
         // Convert host to element representation
@@ -1253,6 +1326,7 @@ CfgHosts::toElement6() const {
     CfgHostsList result;
     // Iterate using arbitrary the index 0
     const HostContainerIndex0& idx = hosts_.get<0>();
+    MultiThreadingLock lock(*mutex_);
     for (auto const& host : idx) {
 
         // Convert host to Element representation
index fb6226a4e0ef6861fac234225d26f9b8cbb271e6..7b675a93c994a07f8af6481aaf76912e96dcfdb2 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2024 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
 #include <dhcpsrv/host_container.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/writable_host_data_source.h>
+#include <boost/scoped_ptr.hpp>
 #include <boost/shared_ptr.hpp>
+#include <set>
+#include <mutex>
 #include <vector>
 
 namespace isc {
@@ -38,6 +41,9 @@ class CfgHosts : public BaseHostDataSource, public WritableHostDataSource,
                  public isc::data::CfgToElement {
 public:
 
+    /// @brief Constructor.
+    CfgHosts();
+
     /// @brief Destructor.
     virtual ~CfgHosts() { }
 
@@ -536,13 +542,13 @@ public:
     /// has already been added to the IPv4 or IPv6 subnet.
     virtual void add(const HostPtr& host);
 
-    /// @brief Attempts to delete hosts by address.
+    /// @brief Attempts to delete hosts by address.
     ///
     /// This method supports both v4 and v6.
-    /// @todo: Not implemented.
     ///
     /// @param subnet_id subnet identifier.
     /// @param addr specified address.
+    /// @return true if deletion was successful, false otherwise.
     virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
 
     /// @brief Attempts to delete all hosts for a given IPv4 subnet.
@@ -555,7 +561,6 @@ public:
     /// @brief Attempts to delete a host by (subnet4-id, identifier, identifier-type)
     ///
     /// This method supports v4 only.
-    /// @todo: Not implemented.
     ///
     /// @param subnet_id IPv4 Subnet identifier.
     /// @param identifier_type Identifier type.
@@ -577,7 +582,6 @@ public:
     /// @brief Attempts to delete a host by (subnet6-id, identifier, identifier-type)
     ///
     /// This method supports v6 only.
-    /// @todo: Not implemented.
     ///
     /// @param subnet_id IPv6 Subnet identifier.
     /// @param identifier_type Identifier type.
@@ -635,6 +639,9 @@ public:
 
 private:
 
+    /// @note: all private/internal methods suppose the caller takes
+    /// the mutex at the exception of toElement[46].
+
     /// @brief Returns @c Host objects for the specific identifier and type.
     ///
     /// This private method is called by the @c CfgHosts::getAll
@@ -812,6 +819,19 @@ private:
     void getAllInternal6(const asiolink::IOAddress& address,
                          Storage& storage) const;
 
+    /// @brief Returns @c Host objects for the specified (Subnet-id,IPv6 address) tuple.
+    ///
+    /// This private method is called by the @c CfgHosts::getAll4 methods
+    /// to retrieve the @c Host for which the specified IPv4 address is
+    /// reserved and is in specified subnet-id. The retrieved objects are
+    /// appended to the returned container.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address IPv4 address.
+    /// @return Collection of const @c Host objects.
+    ConstHostCollection
+    getAllInternal4(const SubnetID& subnet_id,
+                   const asiolink::IOAddress& address) const;
 
     /// @brief Returns @c Host objects for the specified (Subnet-id,IPv6 address) tuple.
     ///
@@ -924,6 +944,9 @@ private:
     /// may be non-unique.
     bool ip_reservations_unique_ = true;
 
+    /// @brief The mutex used to protect host containers.
+    const boost::scoped_ptr<std::mutex> mutex_;
+
     /// @brief Unparse a configuration object (DHCPv4 reservations)
     ///
     /// @return a pointer to unparsed configuration
index 33133284f90fe06918b523f2ca2dae5046e19aae..0811c013637b4ce98928fe6c1938d98c0d775360 100644 (file)
@@ -14,6 +14,7 @@
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
 
 #include <gtest/gtest.h>
 
 #include <set>
 
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::util;
 
 namespace {
 
@@ -55,6 +58,40 @@ public:
     /// @param address Address to be increased.
     IOAddress increase(const IOAddress& address, const uint8_t num) const;
 
+    /// @brief test methods.
+    void testGetAllNonRepeatingHosts();
+    void testGetAllRepeatingHosts();
+    void testGetAll4BySubnet();
+    void testGetAll6BySubnet();
+    void testGetAll6ByAddress();
+    void testGetPage4();
+    void testGetPage6();
+    void testGetPage4All();
+    void testGetPage6All();
+    void testGetAll4ByAddress();
+    void testDeleteForIPv4();
+    void testDeleteForIPv6();
+    void testDel4();
+    void testDel6();
+    void testDeleteAll4();
+    void testGet4();
+    void testUnparsed4();
+    void testGet6();
+    void testDeleteAll6();
+    void testUnparse6();
+    void testGet6ByAddr();
+    void testGet6MultipleAddrs();
+    void testAdd4AlreadyReserved();
+    void testAllow4AlreadyReserved();
+    void testAdd6Invalid2Hosts();
+    void testAllowAddress6AlreadyReserved();
+    void testAllowPrefix6AlreadyReserved();
+    void testDuplicatesSubnet4HWAddr();
+    void testDuplicatesSubnet4DUID();
+    void testDuplicatesSubnet6HWAddr();
+    void testDuplicatesSubnet6DUID();
+    void testUpdate();
+
     /// @brief Collection of HW address objects allocated for unit tests.
     std::vector<HWAddrPtr> hwaddrs_;
     /// @brief Collection of DUIDs allocated for unit tests.
@@ -95,10 +132,12 @@ CfgHostsTest::CfgHostsTest() {
         IOAddress addrb(addrb_template + i);
         addressesb_.push_back(addrb);
     }
+    MultiThreadingMgr::instance().setMode(false);
 }
 
 CfgHostsTest::~CfgHostsTest() {
     CfgMgr::instance().setFamily(AF_INET);
+    MultiThreadingMgr::instance().setMode(false);
 }
 
 IOAddress
@@ -113,7 +152,8 @@ CfgHostsTest::increase(const IOAddress& address, const uint8_t num) const {
 
 // This test checks that hosts with unique HW addresses and DUIDs can be
 // retrieved from the host configuration.
-TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
+void
+CfgHostsTest::testGetAllNonRepeatingHosts() {
     CfgHosts cfg;
     // Add 25 hosts identified by HW address and 25 hosts identified by
     // DUID. They are added to different subnets.
@@ -161,9 +201,19 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
     }
 }
 
+TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
+    testGetAllNonRepeatingHosts();
+}
+
+TEST_F(CfgHostsTest, getAllNonRepeatingHostsMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetAllNonRepeatingHosts();
+}
+
 // This test verifies that the host can be added to multiple subnets and
 // that the getAll message retrieves all instances of the host.
-TEST_F(CfgHostsTest, getAllRepeatingHosts) {
+void
+CfgHostsTest::testGetAllRepeatingHosts() {
     CfgHosts cfg;
     // Add hosts.
     for (unsigned i = 0; i < 25; ++i) {
@@ -219,9 +269,19 @@ TEST_F(CfgHostsTest, getAllRepeatingHosts) {
     }
 }
 
+TEST_F(CfgHostsTest, getAllRepeatingHosts) {
+    testGetAllRepeatingHosts();
+}
+
+TEST_F(CfgHostsTest, getAllRepeatingHostsMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetAllRepeatingHosts();
+}
+
 // This test checks that hosts in the same subnet can be retrieved from
 // the host configuration.
-TEST_F(CfgHostsTest, getAll4BySubnet) {
+void
+CfgHostsTest::testGetAll4BySubnet() {
     CfgHosts cfg;
     // Add 25 hosts identified by HW address in the same subnet.
     for (unsigned i = 0; i < 25; ++i) {
@@ -245,9 +305,19 @@ TEST_F(CfgHostsTest, getAll4BySubnet) {
     }
 }
 
+TEST_F(CfgHostsTest, getAll4BySubnet) {
+    testGetAll4BySubnet();
+}
+
+TEST_F(CfgHostsTest, getAll4BySubnetMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetAll4BySubnet();
+}
+
 // This test checks that hosts in the same subnet can be retrieved from
 // the host configuration.
-TEST_F(CfgHostsTest, getAll6BySubnet) {
+void
+CfgHostsTest::testGetAll6BySubnet() {
     CfgHosts cfg;
     // Add 25 hosts identified by DUID in the same subnet.
     for (unsigned i = 0; i < 25; ++i) {
@@ -277,9 +347,19 @@ TEST_F(CfgHostsTest, getAll6BySubnet) {
     }
 }
 
+TEST_F(CfgHostsTest, getAll6BySubnet) {
+    testGetAll6BySubnet();
+}
+
+TEST_F(CfgHostsTest, getAll6BySubnetMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetAll6BySubnet();
+}
+
 // This test checks that hosts with the same reserved address can be retrieved
 // from the host configuration.
-TEST_F(CfgHostsTest, getAll6ByAddress) {
+void
+CfgHostsTest::testGetAll6ByAddress() {
     CfgHosts cfg;
     // Add 25 hosts identified by DUID in the same subnet.
     for (unsigned i = 0; i < 25; ++i) {
@@ -305,9 +385,19 @@ TEST_F(CfgHostsTest, getAll6ByAddress) {
     }
 }
 
+TEST_F(CfgHostsTest, getAll6ByAddress) {
+    testGetAll6ByAddress();
+}
+
+TEST_F(CfgHostsTest, getAll6ByAddressMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetAll6ByAddress();
+}
+
 // This test checks that hosts in the same subnet can be retrieved from
 // the host configuration by pages.
-TEST_F(CfgHostsTest, getPage4) {
+void
+CfgHostsTest::testGetPage4() {
     CfgHosts cfg;
     // Add 25 hosts identified by DUID in the same subnet.
     for (unsigned i = 0; i < 25; ++i) {
@@ -342,9 +432,19 @@ TEST_F(CfgHostsTest, getPage4) {
     EXPECT_EQ(0, page.size());
 }
 
+TEST_F(CfgHostsTest, getPage4) {
+    testGetPage4();
+}
+
+TEST_F(CfgHostsTest, getPage4MultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetPage4();
+}
+
 // This test checks that hosts in the same subnet can be retrieved from
 // the host configuration by pages.
-TEST_F(CfgHostsTest, getPage6) {
+void
+CfgHostsTest::testGetPage6() {
     CfgHosts cfg;
     // Add 25 hosts identified by HW address in the same subnet.
     for (unsigned i = 0; i < 25; ++i) {
@@ -384,9 +484,19 @@ TEST_F(CfgHostsTest, getPage6) {
     EXPECT_EQ(0, page.size());
 }
 
+TEST_F(CfgHostsTest, getPage6) {
+    testGetPage6();
+}
+
+TEST_F(CfgHostsTest, getPage6MultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetPage6();
+}
+
 // This test checks that all hosts can be retrieved from the host
 // configuration by pages.
-TEST_F(CfgHostsTest, getPage4All) {
+void
+CfgHostsTest::testGetPage4All() {
     CfgHosts cfg;
     // Add 25 hosts identified by DUID.
     for (unsigned i = 0; i < 25; ++i) {
@@ -417,9 +527,19 @@ TEST_F(CfgHostsTest, getPage4All) {
     EXPECT_EQ(0, page.size());
 }
 
+TEST_F(CfgHostsTest, getPage4All) {
+    testGetPage4All();
+}
+
+TEST_F(CfgHostsTest, getPage4AllMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetPage4All();
+}
+
 // This test checks that all hosts can be retrieved from the host
 // configuration by pages.
-TEST_F(CfgHostsTest, getPage6All) {
+void
+CfgHostsTest::testGetPage6All() {
     CfgHosts cfg;
     // Add 25 hosts identified by HW address.
     for (unsigned i = 0; i < 25; ++i) {
@@ -455,9 +575,19 @@ TEST_F(CfgHostsTest, getPage6All) {
     EXPECT_EQ(0, page.size());
 }
 
+TEST_F(CfgHostsTest, getPage6All) {
+    testGetPage6All();
+}
+
+TEST_F(CfgHostsTest, getPage6AllMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetPage6All();
+}
+
 // This test checks that all reservations for the specified IPv4 address can
 // be retrieved.
-TEST_F(CfgHostsTest, getAll4ByAddress) {
+void
+CfgHostsTest::testGetAll4ByAddress() {
     CfgHosts cfg;
     // Add hosts.
     for (unsigned i = 0; i < 25; ++i) {
@@ -483,9 +613,19 @@ TEST_F(CfgHostsTest, getAll4ByAddress) {
     EXPECT_EQ(25, *subnet_ids.rbegin());
 }
 
+TEST_F(CfgHostsTest, getAll4ByAddress) {
+    testGetAll4ByAddress();
+}
+
+TEST_F(CfgHostsTest, getAll4ByAddressMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetAll4ByAddress();
+}
+
 // This test checks that the IPv4 reservation for the specified IPv4 address can
 // be deleted.
-TEST_F(CfgHostsTest, deleteForIPv4) {
+void
+CfgHostsTest::testDeleteForIPv4() {
     CfgHosts cfg;
     // Add hosts.
     IOAddress address("10.0.0.42");
@@ -517,9 +657,19 @@ TEST_F(CfgHostsTest, deleteForIPv4) {
     EXPECT_EQ(0, hosts_by_address.size());
 }
 
+TEST_F(CfgHostsTest, deleteForIPv4) {
+    testDeleteForIPv4();
+}
+
+TEST_F(CfgHostsTest, deleteForIPv4MultiThreading) {
+    MultiThreadingTest mt(true);
+    testDeleteForIPv4();
+}
+
 // This test checks that the IPv6 reservation for the specified subnet ID and
 // IPv6 address can be deleted.
-TEST_F(CfgHostsTest, deleteForIPv6) {
+void
+CfgHostsTest::testDeleteForIPv6() {
     CfgHosts cfg;
     // Add hosts.
     IOAddress address("2001:db8:1::1");
@@ -554,6 +704,15 @@ TEST_F(CfgHostsTest, deleteForIPv6) {
     EXPECT_EQ(host_count-1, hosts_by_subnet.size());
 }
 
+TEST_F(CfgHostsTest, deleteForIPv6) {
+    testDeleteForIPv6();
+}
+
+TEST_F(CfgHostsTest, deleteForIPv6MultiThreading) {
+    MultiThreadingTest mt(true);
+    testDeleteForIPv6();
+}
+
 // This test checks that false is returned for deleting the IPv4 reservation
 // that doesn't exist.
 TEST_F(CfgHostsTest, deleteForMissingIPv4) {
@@ -561,6 +720,9 @@ TEST_F(CfgHostsTest, deleteForMissingIPv4) {
 
     // Delete non-existent host.
     EXPECT_FALSE(cfg.del(SubnetID(42), IOAddress(("10.0.0.42"))));
+
+    MultiThreadingTest mt(true);
+    EXPECT_FALSE(cfg.del(SubnetID(42), IOAddress(("10.0.0.42"))));
 }
 
 // This test checks that false is returned for deleting the IPv6 reservation
@@ -570,11 +732,15 @@ TEST_F(CfgHostsTest, deleteForMissingIPv6) {
 
     // Delete non-existent host.
     EXPECT_FALSE(cfg.del(SubnetID(42), IOAddress(("2001:db8:1::1"))));
+
+    MultiThreadingTest mt(true);
+    EXPECT_FALSE(cfg.del(SubnetID(42), IOAddress(("2001:db8:1::1"))));
 }
 
 // This test checks that the reservation for the specified IPv4 subnet and
 // identifier can be deleted.
-TEST_F(CfgHostsTest, del4) {
+void
+CfgHostsTest::testDel4() {
     CfgHosts cfg;
 
     // Add hosts.
@@ -627,9 +793,19 @@ TEST_F(CfgHostsTest, del4) {
     EXPECT_FALSE(host);
 }
 
+TEST_F(CfgHostsTest, del4) {
+    testDel4();
+}
+
+TEST_F(CfgHostsTest, del4MultiThreading) {
+    MultiThreadingTest mt(true);
+    testDel4();
+}
+
 // This test checks that the host and its reservations for the specified IPv6
 // subnet and identifier can be deleted.
-TEST_F(CfgHostsTest, del6) {
+void
+CfgHostsTest::testDel6() {
     CfgHosts cfg;
 
     // Add hosts.
@@ -686,12 +862,24 @@ TEST_F(CfgHostsTest, del6) {
     EXPECT_FALSE(host);
 }
 
+TEST_F(CfgHostsTest, del6) {
+    testDel6();
+}
+
+TEST_F(CfgHostsTest, del6MultiThreading) {
+    MultiThreadingTest mt(true);
+    testDel6();
+}
+
 // This test checks that false is returned for deleting the IPv4 host that
 // doesn't exist.
 TEST_F(CfgHostsTest, del4MissingHost) {
     CfgHosts cfg;
     EXPECT_FALSE(cfg.del4(SubnetID(42), Host::IdentifierType::IDENT_DUID,
                          &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()));
+    MultiThreadingTest mt(true);
+    EXPECT_FALSE(cfg.del4(SubnetID(42), Host::IdentifierType::IDENT_DUID,
+                         &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()));
 }
 
 // This test checks that false is returned for deleting the IPv6 host that
@@ -700,11 +888,15 @@ TEST_F(CfgHostsTest, del6MissingHost) {
     CfgHosts cfg;
     EXPECT_FALSE(cfg.del6(SubnetID(42), Host::IdentifierType::IDENT_DUID,
                          &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()));
+    MultiThreadingTest mt(true);
+    EXPECT_FALSE(cfg.del6(SubnetID(42), Host::IdentifierType::IDENT_DUID,
+                         &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()));
 }
 
 // This test checks that all reservations for the specified IPv4 subnet can
 // be deleted.
-TEST_F(CfgHostsTest, deleteAll4) {
+void
+CfgHostsTest::testDeleteAll4() {
     CfgHosts cfg;
     // Add hosts.
     for (unsigned i = 0; i < 25; ++i) {
@@ -748,9 +940,19 @@ TEST_F(CfgHostsTest, deleteAll4) {
     EXPECT_EQ(1, *subnet_ids.begin());
 }
 
+TEST_F(CfgHostsTest, deleteAll4) {
+    testDeleteAll4();
+}
+
+TEST_F(CfgHostsTest, deleteAll4MultiThreading) {
+    MultiThreadingTest mt(true);
+    testDeleteAll4();
+}
+
 // This test checks that the reservations can be retrieved for the particular
 // host connected to the specific IPv4 subnet (by subnet id).
-TEST_F(CfgHostsTest, get4) {
+void
+CfgHostsTest::testGet4() {
     CfgHosts cfg;
     // Add hosts.
     for (unsigned i = 0; i < 25; ++i) {
@@ -787,8 +989,18 @@ TEST_F(CfgHostsTest, get4) {
     }
 }
 
+TEST_F(CfgHostsTest, get4) {
+    testGet4();
+}
+
+TEST_F(CfgHostsTest, get4MultiThreading) {
+    MultiThreadingTest mt(true);
+    testGet4();
+}
+
 // This test checks that the DHCPv4 reservations can be unparsed
-TEST_F(CfgHostsTest, unparsed4) {
+void
+CfgHostsTest::testUnparsed4() {
     CfgMgr::instance().setFamily(AF_INET);
     CfgHosts cfg;
     CfgHostsList list;
@@ -871,9 +1083,19 @@ TEST_F(CfgHostsTest, unparsed4) {
     }
 }
 
+TEST_F(CfgHostsTest, unparsed4) {
+    testUnparsed4();
+}
+
+TEST_F(CfgHostsTest, unparsed4MultiThreading) {
+    MultiThreadingTest mt(true);
+    testUnparsed4();
+}
+
 // This test checks that the reservations can be retrieved for the particular
 // host connected to the specific IPv6 subnet (by subnet id).
-TEST_F(CfgHostsTest, get6) {
+void
+CfgHostsTest::testGet6() {
     CfgHosts cfg;
     // Add hosts.
     for (unsigned i = 0; i < 25; ++i) {
@@ -922,9 +1144,19 @@ TEST_F(CfgHostsTest, get6) {
     }
 }
 
+TEST_F(CfgHostsTest, get6) {
+    testGet6();
+}
+
+TEST_F(CfgHostsTest, get6MultiThreading) {
+    MultiThreadingTest mt(true);
+    testGet6();
+}
+
 // This test checks that all reservations for the specified IPv6 subnet can
 // be deleted.
-TEST_F(CfgHostsTest, deleteAll6) {
+void
+CfgHostsTest::testDeleteAll6() {
     CfgHosts cfg;
     // Add hosts.
     for (unsigned i = 0; i < 25; ++i) {
@@ -969,8 +1201,18 @@ TEST_F(CfgHostsTest, deleteAll6) {
     }
 }
 
+TEST_F(CfgHostsTest, deleteAll6) {
+    testDeleteAll6();
+}
+
+TEST_F(CfgHostsTest, deleteAll6MultiThreading) {
+    MultiThreadingTest mt(true);
+    testDeleteAll6();
+}
+
 // This test checks that the DHCPv6 reservations can be unparsed
-TEST_F(CfgHostsTest, unparse6) {
+void
+CfgHostsTest::testUnparse6() {
     CfgMgr::instance().setFamily(AF_INET6);
     CfgHosts cfg;
     CfgHostsList list;
@@ -1071,9 +1313,19 @@ TEST_F(CfgHostsTest, unparse6) {
     }
 }
 
+TEST_F(CfgHostsTest, unparse6) {
+    testUnparse6();
+}
+
+TEST_F(CfgHostsTest, unparse6MultiThreading) {
+    MultiThreadingTest mt(true);
+    testUnparse6();
+}
+
 // This test checks that the IPv6 reservations can be retrieved for a particular
 // (subnet-id, address) tuple.
-TEST_F(CfgHostsTest, get6ByAddr) {
+void
+CfgHostsTest::testGet6ByAddr() {
     CfgHosts cfg;
     // Add hosts.
     for (unsigned i = 0; i < 25; ++i) {
@@ -1103,9 +1355,19 @@ TEST_F(CfgHostsTest, get6ByAddr) {
     }
 }
 
+TEST_F(CfgHostsTest, get6ByAddr) {
+    testGet6ByAddr();
+}
+
+TEST_F(CfgHostsTest, get6ByAddrMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGet6ByAddr();
+}
+
 // This test checks that the IPv6 reservations can be retrieved for a particular
 // (subnet-id, address) tuple.
-TEST_F(CfgHostsTest, get6MultipleAddrs) {
+void
+CfgHostsTest::testGet6MultipleAddrs() {
     CfgHosts cfg;
 
     // Add 25 hosts. Each host has reservations for 5 addresses.
@@ -1154,10 +1416,19 @@ TEST_F(CfgHostsTest, get6MultipleAddrs) {
     }
 }
 
+TEST_F(CfgHostsTest, get6MultipleAddrs) {
+    testGet6MultipleAddrs();
+}
+
+TEST_F(CfgHostsTest, get6MultipleAddrsMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGet6MultipleAddrs();
+}
 
 // Checks that it's not possible for a second host to reserve an address
 // which is already reserved.
-TEST_F(CfgHostsTest, add4AlreadyReserved) {
+void
+CfgHostsTest::testAdd4AlreadyReserved() {
     CfgHosts cfg;
 
     // First host has a reservation for address 192.0.2.1
@@ -1179,9 +1450,19 @@ TEST_F(CfgHostsTest, add4AlreadyReserved) {
     EXPECT_THROW(cfg.add(host2), isc::dhcp::ReservedAddress);
 }
 
+TEST_F(CfgHostsTest, add4AlreadyReserved) {
+    testAdd4AlreadyReserved();
+}
+
+TEST_F(CfgHostsTest, add4AlreadyReservedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testAdd4AlreadyReserved();
+}
+
 // Test that it is possible to allow inserting multiple reservations for
 // the same IP address.
-TEST_F(CfgHostsTest, allow4AlreadyReserved) {
+void
+CfgHostsTest::testAllow4AlreadyReserved() {
     CfgHosts cfg;
     // Allow creating multiple reservations for the same IP address.
     ASSERT_TRUE(cfg.setIPReservationsUnique(false));
@@ -1212,9 +1493,19 @@ TEST_F(CfgHostsTest, allow4AlreadyReserved) {
               returned[1]->getIPv4Reservation().toText());
 }
 
+TEST_F(CfgHostsTest, allow4AlreadyReserved) {
+    testAllow4AlreadyReserved();
+}
+
+TEST_F(CfgHostsTest, allow4AlreadyReservedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testAllow4AlreadyReserved();
+}
+
 // Checks that it's not possible for two hosts to have the same address
 // reserved at the same time.
-TEST_F(CfgHostsTest, add6Invalid2Hosts) {
+void
+CfgHostsTest::testAdd6Invalid2Hosts() {
     CfgHosts cfg;
 
     // First host has a reservation for address 2001:db8::1
@@ -1238,9 +1529,19 @@ TEST_F(CfgHostsTest, add6Invalid2Hosts) {
     EXPECT_THROW(cfg.add(host2), isc::dhcp::DuplicateHost);
 }
 
+TEST_F(CfgHostsTest, add6Invalid2Hosts) {
+    testAdd6Invalid2Hosts();
+}
+
+TEST_F(CfgHostsTest, add6Invalid2HostsMultiThreading) {
+    MultiThreadingTest mt(true);
+    testAdd6Invalid2Hosts();
+}
+
 // Test that it is possible to allow inserting multiple reservations for
 // the same IPv6 address.
-TEST_F(CfgHostsTest, allowAddress6AlreadyReserved) {
+void
+CfgHostsTest::testAllowAddress6AlreadyReserved() {
     CfgHosts cfg;
     // Allow creating multiple reservations for the same IP address.
     ASSERT_TRUE(cfg.setIPReservationsUnique(false));
@@ -1279,9 +1580,19 @@ TEST_F(CfgHostsTest, allowAddress6AlreadyReserved) {
               range1.first->second.getPrefix().toText());
 }
 
+TEST_F(CfgHostsTest, allowAddress6AlreadyReserved) {
+    testAllowAddress6AlreadyReserved();
+}
+
+TEST_F(CfgHostsTest, allowAddress6AlreadyReservedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testAllowAddress6AlreadyReserved();
+}
+
 // Test that it is possible to allow inserting multiple reservations for
 // the same IPv6 delegated prefix.
-TEST_F(CfgHostsTest, allowPrefix6AlreadyReserved) {
+void
+CfgHostsTest::testAllowPrefix6AlreadyReserved() {
     CfgHosts cfg;
     // Allow creating multiple reservations for the same delegated prefix.
     ASSERT_TRUE(cfg.setIPReservationsUnique(false));
@@ -1320,6 +1631,15 @@ TEST_F(CfgHostsTest, allowPrefix6AlreadyReserved) {
               range1.first->second.getPrefix().toText());
 }
 
+TEST_F(CfgHostsTest, allowPrefix6AlreadyReserved) {
+    testAllowPrefix6AlreadyReserved();
+}
+
+TEST_F(CfgHostsTest, allowPrefix6AlreadyReservedMultiThreading) {
+    MultiThreadingTest mt(true);
+    testAllowPrefix6AlreadyReserved();
+}
+
 // Check that no error is reported when adding a host with subnet
 // ids equal to global.
 TEST_F(CfgHostsTest, globalSubnetIDs) {
@@ -1344,7 +1664,8 @@ TEST_F(CfgHostsTest, unusedSubnetIDs) {
 
 // This test verifies that it is not possible to add the same Host to the
 // same IPv4 subnet twice.
-TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
+void
+CfgHostsTest::testDuplicatesSubnet4HWAddr() {
     CfgHosts cfg;
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
@@ -1367,9 +1688,19 @@ TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
                                              IOAddress("10.0.0.10")))));
 }
 
+TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
+    testDuplicatesSubnet4HWAddr();
+}
+
+TEST_F(CfgHostsTest, duplicatesSubnet4HWAddrMultiThreading) {
+    MultiThreadingTest mt(true);
+    testDuplicatesSubnet4HWAddr();
+}
+
 // This test verifies that it is not possible to add the same Host to the
 // same IPv4 subnet twice.
-TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
+void
+CfgHostsTest::testDuplicatesSubnet4DUID() {
     CfgHosts cfg;
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
@@ -1392,9 +1723,19 @@ TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
                                              IOAddress("10.0.0.10")))));
 }
 
+TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
+    testDuplicatesSubnet4DUID();
+}
+
+TEST_F(CfgHostsTest, duplicatesSubnet4DUIDMultiThreading) {
+    MultiThreadingTest mt(true);
+    testDuplicatesSubnet4DUID();
+}
+
 // This test verifies that it is not possible to add the same Host to the
 // same IPv6 subnet twice.
-TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
+void
+CfgHostsTest::testDuplicatesSubnet6HWAddr() {
     CfgHosts cfg;
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
@@ -1420,9 +1761,19 @@ TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
                                              "foo.example.com"))));
 }
 
+TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
+    testDuplicatesSubnet6HWAddr();
+}
+
+TEST_F(CfgHostsTest, duplicatesSubnet6HWAddrMultiThreading) {
+    MultiThreadingTest mt(true);
+    testDuplicatesSubnet6HWAddr();
+}
+
 // This test verifies that it is not possible to add the same Host to the
 // same IPv6 subnet twice.
-TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
+void
+CfgHostsTest::testDuplicatesSubnet6DUID() {
     CfgHosts cfg;
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
@@ -1448,8 +1799,18 @@ TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
                                              "foo.example.com"))));
 }
 
-// Checks that updates work correctly.
-TEST_F(CfgHostsTest, update) {
+TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
+    testDuplicatesSubnet6DUID();
+}
+
+TEST_F(CfgHostsTest, duplicatesSubnet6DUIDMultiThreading) {
+    MultiThreadingTest mt(true);
+    testDuplicatesSubnet6DUID();
+}
+
+// Checks that updates work correctly. Note it is not really MT safe.
+void
+CfgHostsTest::testUpdate() {
     CfgHosts cfg;
 
     HostPtr const host(boost::make_shared<Host>(duids_[0]->toText(), "duid", SUBNET_ID_UNUSED,
@@ -1506,4 +1867,13 @@ TEST_F(CfgHostsTest, update) {
               "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
 }
 
+TEST_F(CfgHostsTest, update) {
+    testUpdate();
+}
+
+TEST_F(CfgHostsTest, updateMultiThreading) {
+    MultiThreadingTest mt(true);
+    testUpdate();
+}
+
 }  // namespace