]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#991] added multi-threading unittests for v4 server
authorRazvan Becheriu <razvan@isc.org>
Tue, 8 Dec 2020 20:37:50 +0000 (22:37 +0200)
committerRazvan Becheriu <razvan@isc.org>
Thu, 10 Dec 2020 19:37:50 +0000 (19:37 +0000)
src/bin/dhcp4/tests/dhcp4_client.cc
src/bin/dhcp4/tests/dhcp4_test_utils.cc
src/bin/dhcp4/tests/dhcp4_test_utils.h
src/bin/dhcp4/tests/direct_client_unittest.cc
src/bin/dhcp4/tests/dora_unittest.cc

index 56813aae5fe82d5e8f07246e5dfd9886c615d2f5..a92d9e75f27386cec34a4eec70584f9d64f6032e 100644 (file)
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/lease.h>
 #include <dhcp4/tests/dhcp4_client.h>
+#include <util/multi_threading_mgr.h>
 #include <util/range_utilities.h>
 #include <boost/pointer_cast.hpp>
 #include <cstdlib>
 
 using namespace isc::asiolink;
+using namespace isc::util;
 
 namespace isc {
 namespace dhcp {
@@ -497,12 +499,10 @@ Dhcp4Client::requestOptions(const uint8_t option1, const uint8_t option2,
 
 Pkt4Ptr
 Dhcp4Client::receiveOneMsg() {
-    // Return empty pointer if server hasn't responded.
-    if (srv_->fake_sent_.empty()) {
+    Pkt4Ptr msg = srv_->receiveOneMsg();
+    if (!msg) {
         return (Pkt4Ptr());
     }
-    Pkt4Ptr msg = srv_->fake_sent_.front();
-    srv_->fake_sent_.pop_front();
 
     // Copy the original message to simulate reception over the wire.
     msg->pack();
@@ -562,6 +562,12 @@ Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
     } catch (...) {
         // Suppress errors, as the DHCPv4 server does.
     }
+
+    // make sure the server processed all packets in MT.
+    while (isc::util::MultiThreadingMgr::instance().getThreadPool().count()) {
+        usleep(100);
+    }
+    isc::util::MultiThreadingCriticalSection cs;
 }
 
 void
index 8f9b131fc831599be9d3bff55e5909bf264efd69..59a0ecdc26dbc543a367cf2accc9e33cf01d0d69 100644 (file)
@@ -20,6 +20,7 @@
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/pkt_captures.h>
 #include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfg_multi_threading.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -31,6 +32,7 @@
 using namespace std;
 using namespace isc::asiolink;
 using namespace isc::data;
+using namespace isc::util;
 
 namespace isc {
 namespace dhcp {
@@ -55,7 +57,7 @@ BaseServerTest::~BaseServerTest() {
 }
 
 Dhcpv4SrvTest::Dhcpv4SrvTest()
-:rcode_(-1), srv_(0) {
+    : rcode_(-1), srv_(0), multi_threading_(false) {
 
     // Wipe any existing statistics
     isc::stats::StatsMgr::instance().removeAll();
@@ -455,6 +457,7 @@ Dhcpv4SrvTest::TearDown() {
                << ex.what();
     }
 
+    setMultiThreading(false);
 }
 
 void
@@ -620,11 +623,12 @@ Dhcpv4SrvTest::configure(const std::string& config,
                          NakedDhcpv4Srv& srv,
                          const bool commit,
                          const bool open_sockets) {
+    MultiThreadingCriticalSection cs;
     ConstElementPtr json;
     try {
         json = parseJSON(config);
     } catch (const std::exception& ex){
-        // Fatal falure on parsing error
+        // Fatal failure on parsing error
         FAIL() << "parsing failure:"
                 << "config:" << config << std::endl
                 << "error: " << ex.what();
@@ -635,6 +639,9 @@ Dhcpv4SrvTest::configure(const std::string& config,
     // Disable the re-detect flag
     disableIfacesReDetect(json);
 
+    // Set up multi-threading
+    configureMultiThreading(multi_threading_, json);
+
     // Configure the server and make sure the config is accepted
     EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
     ASSERT_TRUE(status);
@@ -649,6 +656,13 @@ Dhcpv4SrvTest::configure(const std::string& config,
         cfg_db->createManagers();
     } );
 
+    try {
+        CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading());
+    } catch (const std::exception& ex) {
+        ADD_FAILURE() << "Error applying multi threading settings: "
+            << ex.what();
+    }
+
     if (commit) {
         CfgMgr::instance().commit();
     }
@@ -705,7 +719,7 @@ Dhcpv4SrvTest::configureWithStatus(const std::string& config, NakedDhcpv4Srv& sr
     }
 
     return (std::make_pair(rcode, comment->stringValue()));
- }
+}
 
 Dhcpv4Exchange
 Dhcpv4SrvTest::createExchange(const Pkt4Ptr& query) {
index c0f99253d69a81f89ffbe75be1881f9023e03e81..533f0a181cba2c74b3b7fe268b7d25d9fa13c602 100644 (file)
@@ -25,6 +25,7 @@
 #include <dhcp4/parser_context.h>
 #include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
+#include <util/multi_threading_mgr.h>
 #include <list>
 
 #include <boost/shared_ptr.hpp>
@@ -148,6 +149,13 @@ public:
             return (pkt);
         }
 
+        // make sure the server processed all packets in MT.
+        while (isc::util::MultiThreadingMgr::instance().getThreadPool().count()) {
+            usleep(100);
+        }
+        {
+            isc::util::MultiThreadingCriticalSection cs;
+        }
         // If not, just trigger shutdown and return immediately
         shutdown();
         return (Pkt4Ptr());
@@ -158,7 +166,31 @@ public:
     /// Pretend to send a packet, but instead just store it in fake_send_ list
     /// where test can later inspect server's response.
     virtual void sendPacket(const Pkt4Ptr& pkt) {
-        fake_sent_.push_back(pkt);
+        if (isc::util::MultiThreadingMgr::instance().getMode()) {
+            std::lock_guard<std::mutex> lk(mutex_);
+            fake_sent_.push_back(pkt);
+        } else {
+            fake_sent_.push_back(pkt);
+        }
+    }
+
+    Pkt4Ptr receiveOneMsg() {
+        if (isc::util::MultiThreadingMgr::instance().getMode()) {
+            std::lock_guard<std::mutex> lk(mutex_);
+            return (receiveOneMsgInternal());
+        } else {
+            return (receiveOneMsgInternal());
+        }
+    }
+
+    Pkt4Ptr receiveOneMsgInternal() {
+        // Return empty pointer if server hasn't responded.
+        if (fake_sent_.empty()) {
+            return (Pkt4Ptr());
+        }
+        Pkt4Ptr msg = fake_sent_.front();
+        fake_sent_.pop_front();
+        return (msg);
     }
 
     /// @brief adds a packet to fake receive queue
@@ -207,6 +239,7 @@ public:
     /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
     std::list<Pkt4Ptr> fake_received_;
 
+    /// @brief packets we pretend to send
     std::list<Pkt4Ptr> fake_sent_;
 
     using Dhcpv4Srv::adjustIfaceData;
@@ -232,6 +265,9 @@ public:
     using Dhcpv4Srv::alloc_engine_;
     using Dhcpv4Srv::server_port_;
     using Dhcpv4Srv::client_port_;
+
+    /// @brief Mutex to protect the packet buffers.
+    std::mutex mutex_;
 };
 
 // We need to pass one reference to the Dhcp4Client, which is defined in
@@ -268,6 +304,17 @@ public:
         SHOULD_FAIL  // fail = reject the decline
     };
 
+    class Dhcpv4SrvMTTestGuard {
+    public:
+        Dhcpv4SrvMTTestGuard(Dhcpv4SrvTest& test, bool mt_enabled) : test_(test) {
+            test_.setMultiThreading(mt_enabled);
+        }
+        ~Dhcpv4SrvMTTestGuard() {
+            test_.setMultiThreading(false);
+        }
+        Dhcpv4SrvTest& test_;
+    };
+
     /// @brief Constructor
     ///
     /// Initializes common objects used in many tests.
@@ -512,9 +559,18 @@ public:
                            const std::string& client_id_2,
                            ExpectedResult expected_result);
 
+    /// @brief Checks if received relay agent info option is echoed back to the
+    /// client
+    void relayAgentInfoEcho();
+
     /// @brief This function cleans up after the test.
     virtual void TearDown();
 
+    /// @brief Set multi-threading mode.
+    void setMultiThreading(bool enabled) {
+        multi_threading_ = enabled;
+    }
+
     /// @brief A subnet used in most tests
     Subnet4Ptr subnet_;
 
@@ -524,12 +580,17 @@ public:
     /// @brief A client-id used in most tests
     ClientIdPtr client_id_;
 
+    /// @brief Return code
     int rcode_;
 
+    /// @brief Comment
     isc::data::ConstElementPtr comment_;
 
     /// @brief Server object under test.
     NakedDhcpv4Srv srv_;
+
+    /// @brief The multi-threading flag
+    bool multi_threading_;
 };
 
 /// @brief Patch the server config to add interface-config/re-detect=false
@@ -544,13 +605,35 @@ disableIfacesReDetect(isc::data::ConstElementPtr json) {
     }
 }
 
+/// @brief Patch the server config to add multi-threading/enable-multi-threading
+/// @param json the server config
+inline void
+configureMultiThreading(bool enabled, isc::data::ConstElementPtr json) {
+    isc::data::ConstElementPtr multi_threading = json->get("multi-threading");
+    if (!multi_threading) {
+        isc::data::ElementPtr mutable_cfg =
+                boost::const_pointer_cast<isc::data::Element>(json);
+        multi_threading = isc::data::Element::createMap();
+        mutable_cfg->set("multi-threading", multi_threading);
+    }
+
+    isc::data::ElementPtr mutable_cfg =
+        boost::const_pointer_cast<isc::data::Element>(multi_threading);
+    if (enabled) {
+        mutable_cfg->set("enable-multi-threading", isc::data::Element::create(true));
+        mutable_cfg->set("thread-pool-size", isc::data::Element::create(4));
+        mutable_cfg->set("packet-queue-size", isc::data::Element::create(4));
+    } else {
+        mutable_cfg->set("enable-multi-threading", isc::data::Element::create(false));
+    }
+}
+
 /// @brief Runs parser in JSON mode, useful for parser testing
 ///
 /// @param in string to be parsed
 /// @return ElementPtr structure representing parsed JSON
 inline isc::data::ElementPtr
-parseJSON(const std::string& in)
-{
+parseJSON(const std::string& in) {
     isc::dhcp::Parser4Context ctx;
     return (ctx.parseString(in, isc::dhcp::Parser4Context::PARSER_JSON));
 }
@@ -564,8 +647,7 @@ parseJSON(const std::string& in)
 /// @param verbose display the exception message when it fails
 /// @return ElementPtr structure representing parsed JSON
 inline isc::data::ElementPtr
-parseDHCP4(const std::string& in, bool verbose = false)
-{
+parseDHCP4(const std::string& in, bool verbose = false) {
     try {
         isc::dhcp::Parser4Context ctx;
         isc::data::ElementPtr json;
@@ -589,8 +671,7 @@ parseDHCP4(const std::string& in, bool verbose = false)
 /// @param verbose display the exception message when it fails
 /// @return ElementPtr structure representing parsed JSON
 inline isc::data::ElementPtr
-parseOPTION_DEFS(const std::string& in, bool verbose = false)
-{
+parseOPTION_DEFS(const std::string& in, bool verbose = false) {
     try {
         isc::dhcp::Parser4Context ctx;
         return (ctx.parseString(in, isc::dhcp::Parser4Context::PARSER_OPTION_DEFS));
@@ -603,8 +684,8 @@ parseOPTION_DEFS(const std::string& in, bool verbose = false)
     }
 }
 
-}; // end of isc::dhcp::test namespace
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
 
 #endif // DHCP4_TEST_UTILS_H
index dedb3b93860a53f539e09fa70e62870ccd0d62ee..3d98e4ec7e68a160f8f643878da3f3fadef48d5e 100644 (file)
@@ -104,6 +104,35 @@ public:
                                 const std::string& iface,
                                 const uint32_t ifindex);
 
+    /// @brief This test checks that the message from directly connected client
+    /// is processed and that client is offered IPv4 address from the subnet
+    /// which is suitable for the local interface on which the client's message
+    /// is received. This test uses two subnets, with two active interfaces
+    /// which IP addresses belong to these subnets. The address offered to the
+    /// client which message has been sent over eth0 should belong to a
+    /// different subnet than the address offered for the client sending its
+    /// message via eth1.
+    void twoSubnets();
+
+    /// @brief This test checks that server selects a subnet when receives a
+    /// message through an interface for which the subnet has been configured.
+    /// This interface has IPv4 address assigned which belongs to this subnet.
+    /// This test also verifies that when the message is received through the
+    /// interface for which there is no suitable subnet, the message is
+    /// discarded.
+    void oneSubnet();
+
+    /// @brief This test verifies that the server uses ciaddr to select a subnet
+    /// for a client which renews its lease.
+    void renew();
+
+    /// This test verifies that when a client in the Rebinding state broadcasts
+    /// a Request message through an interface for which a subnet is configured,
+    /// the server responds to this Request. It also verifies that when such a
+    /// Request is sent through the interface for which there is no subnet
+    /// configured the client's message is discarded.
+    void rebind();
+
     /// @brief classes the client belongs to
     ///
     /// This is empty in most cases, but it is needed as a parameter for all
@@ -133,7 +162,6 @@ DirectClientTest::configureSubnet(const std::string& prefix) {
         "\"valid-lifetime\": 4000 }";
 
     configure(config.str());
-
 }
 
 void
@@ -197,15 +225,8 @@ DirectClientTest::createClientMessage(const Pkt4Ptr& msg,
     return (received);
 }
 
-// This test checks that the message from directly connected client
-// is processed and that client is offered IPv4 address from the subnet which
-// is suitable for the local interface on which the client's message is
-// received. This test uses two subnets, with two active interfaces which IP
-// addresses belong to these subnets. The address offered to the client
-// which message has been sent over eth0 should belong to a different
-// subnet than the address offered for the client sending its message
-// via eth1.
-TEST_F(DirectClientTest,  twoSubnets) {
+void
+DirectClientTest::twoSubnets() {
     // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
     IfaceMgrTestConfig iface_config(true);
     // After creating interfaces we have to open sockets as it is required
@@ -227,40 +248,55 @@ TEST_F(DirectClientTest,  twoSubnets) {
     // Check that the server did send responses.
     ASSERT_EQ(2, srv_.fake_sent_.size());
 
-    // Make sure that we received a response.
-    Pkt4Ptr response = srv_.fake_sent_.front();
-    ASSERT_TRUE(response);
-    srv_.fake_sent_.pop_front();
+    // In multi-threading responses can be received out of order.
+    Pkt4Ptr offer;
+    Pkt4Ptr ack;
+
+    while (srv_.fake_sent_.size()) {
+        // Make sure that we received a response.
+        Pkt4Ptr response = srv_.fake_sent_.front();
+        ASSERT_TRUE(response);
+        srv_.fake_sent_.pop_front();
+
+        if (response->getType() == DHCPOFFER) {
+            offer = response;
+        } else if (response->getType() == DHCPACK) {
+            ack = response;
+        }
+    }
 
     // Client should get an Offer (not a NAK).
-    ASSERT_EQ(DHCPOFFER, response->getType());
+    ASSERT_TRUE(offer);
+
+    // Client should get an Ack (not a NAK).
+    ASSERT_TRUE(ack);
+
     // Check that the offered address belongs to the suitable subnet.
     Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
-        getCfgSubnets4()->selectSubnet(response->getYiaddr());
+        getCfgSubnets4()->selectSubnet(offer->getYiaddr());
     ASSERT_TRUE(subnet);
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
 
-    // A client that sent Request over the other interface should get Ack.
-    response = srv_.fake_sent_.front();
-    ASSERT_TRUE(response);
 
-    // Client should get an Ack (not a NAK).
-    ASSERT_EQ(DHCPACK, response->getType());
     // Check that the offered address belongs to the suitable subnet.
     subnet = CfgMgr::instance().getCurrentCfg()->
-        getCfgSubnets4()->selectSubnet(response->getYiaddr());
+        getCfgSubnets4()->selectSubnet(ack->getYiaddr());
     ASSERT_TRUE(subnet);
     EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
+}
 
+TEST_F(DirectClientTest, twoSubnets) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    twoSubnets();
 }
 
-// This test checks that server selects a subnet when receives a message
-// through an interface for which the subnet has been configured. This
-// interface has IPv4 address assigned which belongs to this subnet.
-// This test also verifies that when the message is received through
-// the interface for which there is no suitable subnet, the message
-// is discarded.
-TEST_F(DirectClientTest, oneSubnet) {
+TEST_F(DirectClientTest, twoSubnetsMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    twoSubnets();
+}
+
+void
+DirectClientTest::oneSubnet() {
     // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
     IfaceMgrTestConfig iface_config(true);
     // After creating interfaces we have to open sockets as it is required
@@ -298,12 +334,20 @@ TEST_F(DirectClientTest, oneSubnet) {
         getCfgSubnets4()->selectSubnet(response->getYiaddr());
     ASSERT_TRUE(subnet);
     EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+}
 
+TEST_F(DirectClientTest, oneSubnet) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    oneSubnet();
 }
 
-// This test verifies that the server uses ciaddr to select a subnet for a
-// client which renews its lease.
-TEST_F(DirectClientTest, renew) {
+TEST_F(DirectClientTest, oneSubnetMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    oneSubnet();
+}
+
+void
+DirectClientTest::renew() {
     // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
     IfaceMgrTestConfig iface_config(true);
     // After creating interfaces we have to open sockets as it is required
@@ -330,12 +374,18 @@ TEST_F(DirectClientTest, renew) {
     EXPECT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
 }
 
-// This test verifies that when a client in the Rebinding state broadcasts
-// a Request message through an interface for which a subnet is configured,
-// the server responds to this Request. It also verifies that when such a
-// Request is sent through the interface for which there is no subnet configured
-// the client's message is discarded.
-TEST_F(DirectClientTest, rebind) {
+TEST_F(DirectClientTest, renew) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    renew();
+}
+
+TEST_F(DirectClientTest, renewMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    renew();
+}
+
+void
+DirectClientTest::rebind() {
     // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
     IfaceMgrTestConfig iface_config(true);
     // After creating interfaces we have to open sockets as it is required
@@ -372,4 +422,14 @@ TEST_F(DirectClientTest, rebind) {
     EXPECT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
 }
 
+TEST_F(DirectClientTest, rebind) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    rebind();
+}
+
+TEST_F(DirectClientTest, rebindMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    rebind();
+}
+
 }
index bd7e83a66ac713949e509a2bb430d580f90cc23c..0faf40fe0a3e21e2027db48ba5d11f33059c24e8 100644 (file)
@@ -150,7 +150,7 @@ namespace {
 ///     respond to requests from unknown clients.
 ///
 const char* DORA_CONFIGS[] = {
-// Configuration 0
+    // Configuration 0
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -178,7 +178,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 1
+    // Configuration 1
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -205,7 +205,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 2
+    // Configuration 2
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -234,7 +234,7 @@ const char* DORA_CONFIGS[] = {
         "} ]"
     "}",
 
-// Configuration 3
+    // Configuration 3
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -251,7 +251,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 4
+    // Configuration 4
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -313,7 +313,7 @@ const char* DORA_CONFIGS[] = {
         "} ]"
     "}",
 
-// Configuration 6
+    // Configuration 6
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -335,7 +335,7 @@ const char* DORA_CONFIGS[] = {
         "} ]"
     "}",
 
-// Configuration 7
+    // Configuration 7
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -379,7 +379,7 @@ const char* DORA_CONFIGS[] = {
         "]"
     "}",
 
-// Configuration 8
+    // Configuration 8
     "{ \"interfaces-config\": {"
         "   \"interfaces\": [ \"*\" ]"
         "},"
@@ -397,7 +397,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 9
+    // Configuration 9
     "{ \"interfaces-config\": {"
         "   \"interfaces\": [ \"*\" ]"
         "},"
@@ -415,7 +415,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 10
+    // Configuration 10
     "{ \"interfaces-config\": {"
         "   \"interfaces\": [ \"*\" ]"
         "},"
@@ -433,7 +433,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 11
+    // Configuration 11
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -451,7 +451,7 @@ const char* DORA_CONFIGS[] = {
         "} ]"
     "}",
 
-// Configuration 12
+    // Configuration 12
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -471,7 +471,7 @@ const char* DORA_CONFIGS[] = {
         "} ]"
     "}",
 
-// Configuration 13
+    // Configuration 13
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -490,7 +490,7 @@ const char* DORA_CONFIGS[] = {
         "} ]"
     "}",
 
-// Configuration 14
+    // Configuration 14
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -514,7 +514,7 @@ const char* DORA_CONFIGS[] = {
         "} ]"
     "}",
 
-// Configuration 15
+    // Configuration 15
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -531,7 +531,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 16
+    // Configuration 16
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -548,7 +548,7 @@ const char* DORA_CONFIGS[] = {
         " } ]"
     "}",
 
-// Configuration 17
+    // Configuration 17
     "{ \"interfaces-config\": {"
         "      \"interfaces\": [ \"*\" ]"
         "},"
@@ -634,15 +634,194 @@ public:
     /// @c DORA_CONFIGS array.
     void testMultiStageBoot(const unsigned int config_index);
 
+    /// @brief This test verifies that the client in the SELECTING state can get
+    /// an address when it doesn't request any specific address in the
+    /// DHCPDISCOVER message.
+    void selectingDoNotRequestAddress();
+
+    /// @brief This test verifies that multiple clients may use the DHCPv4
+    /// server and obtain unique leases.
+    void selectingMultipleClients();
+
+    /// @brief This test verifies that the client in a SELECTING state can
+    /// request a specific address and that this address will be assigned when
+    /// available. It also tests that if the client requests an address which is
+    /// in use the client will get a different address.
+    void selectingRequestAddress();
+
+    /// @brief  This test verifies that the client will get the address that it
+    /// has been allocated when the client requests a different address.
+    void selectingRequestNonMatchingAddress();
+
+    /// @brief  Test that the client in the INIT-REBOOT state can request the IP
+    /// address it has and the address is returned. Also, check that if the
+    /// client requests invalid address the server sends a DHCPNAK.
+    void initRebootRequest();
+
+    /// @brief  Test that the client in the INIT-REBOOT state can request the IP
+    /// address it has and the address is returned. Also, check that if the
+    /// client is unknown the server sends a DHCPNAK.
+    void authoritative();
+
+    /// @brief  Test that the client in the INIT-REBOOT state can request the IP
+    /// address it has and the address is returned. Also, check that if  the
+    /// client is unknown the request is dropped.
+    void notAuthoritative();
+
+    /// @brief Check that the ciaddr returned by the server is correct for
+    /// DHCPOFFER and DHCPNAK according to RFC2131, section 4.3.1.
+    void ciaddr();
+
+    /// @brief This test checks the server behavior in the following situation:
+    /// - Client A identifies itself to the server using client identifier
+    ///   and the hardware address and requests allocation of the new lease.
+    /// - Server allocates the lease to the client.
+    /// - Client B has the same hardware address but is using a different
+    ///   client identifier then Client A.
+    /// - Client B sends DHCPDISCOVER.
+    /// - Server should determine that the client B is not client A, because
+    ///   it is using a different client identifier, even though they use the
+    ///   same HW address. As a consequence, the server should offer a
+    ///    different address to the client B.
+    /// - The client B performs the 4-way exchange again and the server
+    ///   allocates a new address to the client, which should be different
+    ///   than the address used by the client A.
+    /// - Client B is in the renewing state and it successfully renews its
+    ///   address.
+    /// - Client A also renews its address successfully.
+    void twoAllocationsOverlap();
+
+    /// @brief This is a simple test for the host reservation. It creates a
+    /// reservation for an address for a single client, identified by the HW
+    /// address. The test verifies that the client using this HW address will
+    /// obtain a lease for the reserved address. It also checks that the client
+    /// using a different HW address will obtain an address from the dynamic
+    /// pool.
+    void reservation();
+
+    /// @brief  This test checks that it is possible to make a reservation by
+    /// DUID carried in the Client Identifier option.
+    void reservationByDUID();
+
+    /// @brief This test checks that it is possible to make a reservation by
+    /// circuit-id inserted by the relay agent.
+    void reservationByCircuitId();
+
+    /// @brief This test checks that it is possible to make a reservation by
+    ///  client-id.
+    void reservationByClientId();
+
+    /// @brief  This test verifies that order in which host identifiers are used
+    /// to retrieve host reservations can be controlled.
+    void hostIdentifiersOrder();
+
+    /// @brief This test checks that setting the match-client-id value to false
+    /// causes the server to ignore changing client identifier when the client
+    /// is using consistent HW address.
+    void ignoreChangingClientId();
+
+    /// @brief This test checks that the match-client-id parameter doesn't have
+    /// effect on the lease lookup using the HW address.
+    void changingHWAddress();
+
+    /// @brief This test verifies that the server assigns reserved values for
+    /// the siaddr, sname and file fields carried within DHCPv4 message.
+    void messageFieldsReservations();
+
+    /// @brief This test checks the following scenario:
+    /// 1. Client A performs 4-way exchange and obtains a lease from the dynamic
+    /// pool.
+    /// 2. Reservation is created for the client A using an address out of the
+    ///    dynamic pool.
+    /// 3. Client A renews the lease.
+    /// 4. Server responds with DHCPNAK to indicate that the client should stop
+    ///    using an address for which it has a lease. Server doesn't want to
+    ///    renew an address for which the client doesn't have a reservation,
+    ///    while it has a reservation for a different address.
+    /// 5. Client A receives a DHCPNAK and returns to the DHCP server discovery.
+    /// 6. Client A performs a 4-way exchange with a server and the server
+    ///    allocates a reserved address to the Client A.
+    /// 7. Client A renews the allocated address and the server returns a
+    ///    DHCPACK.
+    /// 8. Reservation for the Client A is removed.
+    /// 9. Client A renews the (previously reserved) lease and the server
+    ///    returns DHCPNAK because the address in use is neither reserved nor
+    ///    belongs to the dynamic pool.
+    /// 10. Client A returns to the DHCP server discovery.
+    /// 11. Client A uses 4-way exchange to obtain a lease from the dynamic
+    ///     pool.
+    /// 12. The new address that the Client A is using is reserved for Client B.
+    ///     Client A still holds this address.
+    /// 13. Client B uses 4-way exchange to obtain a new lease.
+    /// 14. The server determines that the Client B has a reservation for the
+    ///     address which is in use by Client A and offers an address different
+    ///     than reserved.
+    /// 15. Client B requests the allocation of the offered address and the
+    ///     server allocates this address.
+    /// 16. Client A renews the lease.
+    /// 17. The server determines that the address that Client A is using is
+    ///     reserved for Client B. The server returns DHCPNAK to the Client A.
+    /// 18. Client B uses 4-way exchange to obtain the reserved lease but the
+    ///     lease for the Client A hasn't been removed yet. Client B is assigned
+    ///     the same address it has been using.
+    /// 19. Client A uses 4-way exchange to allocate a new lease.
+    /// 20. The server allocates a new lease from the dynamic pool but it avoids
+    ///     allocating the address reserved for the Client B.
+    /// 21. Client B uses 4-way exchange to obtain a new lease.
+    /// 22. The server finally allocates a reserved address to the Client B.
+    void reservationsWithConflicts();
+
+    /// @brief This test verifies that the allocation engine ignores
+    /// reservations when reservations flags are set to "disabled".
+    void reservationModeDisabled();
+
+    /// @brief This test verifies that allocation engine assigns a reserved
+    /// address to the client which doesn't own this reservation. We want to
+    /// avoid such cases in the real deployments, but this is just a test that
+    /// the allocation engine skips checking if the reservation exists when it
+    /// allocates an address. In the real deployment the reservation simply
+    /// wouldn't exist.
+    void reservationIgnoredInDisabledMode();
+
+    /// @brief This test verifies that in pool reservations are ignored when the
+    /// reservations-out-of-pool flag is set to true.
+    void reservationModeOutOfPool();
+
+    /// @brief This test verifies that the in-pool reservation can be assigned
+    /// to a client not owning this reservation when the
+    /// reservations-out-of-pool flag is set to true.
+    void reservationIgnoredInOutOfPoolMode();
+
+    /// @brief This test verifies that after a client completes its DORA
+    /// exchange, appropriate statistics are updated.
+    void statisticsDORA();
+
+    /// @brief This test verifies that after a client completes an exchange that
+    /// result in NAK, appropriate statistics are updated.
+    void statisticsNAK();
+
+    /// @brief This test verifies that custom server identifier can be specified
+    /// for a subnet.
+    void customServerIdentifier();
+
+    /// @brief This test verifies that reserved lease is not assigned to a
+    /// client which identifier doesn't match the identifier in the reservation.
+    void changingCircuitId();
+
+    /// @brief  Verifies that extended info is stored on the lease when
+    /// store-extended-info is enabled.
+    void storeExtendedInfoEnabled();
+
+    /// @brief Verifies that extended info is not stored on the lease when
+    ///  store-extended-info is disabled.
+    void storeExtendedInfoDisabled();
+
     /// @brief Interface Manager's fake configuration control.
     IfaceMgrTestConfig iface_mgr_test_config_;
-
 };
 
-/// This test verifies that the client in the SELECTING state can get
-/// an address when it doesn't request any specific address in the
-/// DHCPDISCOVER message.
-TEST_F(DORATest, selectingDoNotRequestAddress) {
+void
+DORATest::selectingDoNotRequestAddress() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -664,9 +843,18 @@ TEST_F(DORATest, selectingDoNotRequestAddress) {
     ASSERT_NE(client.config_.lease_.addr_.toText(), "0.0.0.0");
 }
 
-/// This test verifies that multiple clients may use the DHCPv4 server
-/// and obtain unique leases.
-TEST_F(DORATest, selectingMultipleClients) {
+TEST_F(DORATest, selectingDoNotRequestAddress) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    selectingDoNotRequestAddress();
+}
+
+TEST_F(DORATest, selectingDoNotRequestAddressMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    selectingDoNotRequestAddress();
+}
+
+void
+DORATest::selectingMultipleClients() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -707,11 +895,18 @@ TEST_F(DORATest, selectingMultipleClients) {
     EXPECT_NE(lease1.addr_, lease3.addr_);
 }
 
-// This test verifies that the client in a SELECTING state can request
-// a specific address and that this address will be assigned when
-// available. It also tests that if the client requests an address which
-// is in use the client will get a different address.
-TEST_F(DORATest, selectingRequestAddress) {
+TEST_F(DORATest, selectingMultipleClients) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    selectingMultipleClients();
+}
+
+TEST_F(DORATest, selectingMultipleClientsMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    selectingMultipleClients();
+}
+
+void
+DORATest::selectingRequestAddress() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -752,9 +947,18 @@ TEST_F(DORATest, selectingRequestAddress) {
     EXPECT_NE(client.config_.lease_.addr_.toText(), "10.0.0.50");
 }
 
-// This test verifies that the client will get the address that it has
-// been allocated when the client requests a different address.
-TEST_F(DORATest, selectingRequestNonMatchingAddress) {
+TEST_F(DORATest, selectingRequestAddress) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    selectingRequestAddress();
+}
+
+TEST_F(DORATest, selectingRequestAddressMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    selectingRequestAddress();
+}
+
+void
+DORATest::selectingRequestNonMatchingAddress() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -793,10 +997,18 @@ TEST_F(DORATest, selectingRequestNonMatchingAddress) {
     EXPECT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
 }
 
-// Test that the client in the INIT-REBOOT state can request the IP
-// address it has and the address is returned. Also, check that if
-// if the client requests invalid address the server sends a DHCPNAK.
-TEST_F(DORATest, initRebootRequest) {
+TEST_F(DORATest, selectingRequestNonMatchingAddress) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    selectingRequestNonMatchingAddress();
+}
+
+TEST_F(DORATest, selectingRequestNonMatchingAddressMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    selectingRequestNonMatchingAddress();
+}
+
+void
+DORATest::initRebootRequest() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -871,10 +1083,18 @@ TEST_F(DORATest, initRebootRequest) {
     ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
 }
 
-// Test that the client in the INIT-REBOOT state can request the IP
-// address it has and the address is returned. Also, check that if
-// if the client is unknown the server sends a DHCPNAK.
-TEST_F(DORATest, authoritative) {
+TEST_F(DORATest, initRebootRequest) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    initRebootRequest();
+}
+
+TEST_F(DORATest, initRebootRequestMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    initRebootRequest();
+}
+
+void
+DORATest::authoritative() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[15], *client.getServer());
@@ -966,10 +1186,18 @@ TEST_F(DORATest, authoritative) {
     ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
 }
 
-// Test that the client in the INIT-REBOOT state can request the IP
-// address it has and the address is returned. Also, check that if
-// if the client is unknown the request is dropped.
-TEST_F(DORATest, notAuthoritative) {
+TEST_F(DORATest, authoritative) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    authoritative();
+}
+
+TEST_F(DORATest, authoritativeMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    authoritative();
+}
+
+void
+DORATest::notAuthoritative() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[16], *client.getServer());
@@ -1059,9 +1287,18 @@ TEST_F(DORATest, notAuthoritative) {
     ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
 }
 
-// Check that the ciaddr returned by the server is correct for DHCPOFFER and
-// DHCPNAK according to RFC2131, section 4.3.1.
-TEST_F(DORATest, ciaddr) {
+TEST_F(DORATest, notAuthoritative) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    notAuthoritative();
+}
+
+TEST_F(DORATest, notAuthoritativeMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    notAuthoritative();
+}
+
+void
+DORATest::ciaddr() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -1112,6 +1349,16 @@ TEST_F(DORATest, ciaddr) {
     EXPECT_EQ("0.0.0.0", resp->getCiaddr().toText());
 }
 
+TEST_F(DORATest, ciaddr) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    ciaddr();
+}
+
+TEST_F(DORATest, ciaddrMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    ciaddr();
+}
+
 void
 DORATest::oneAllocationOverlapTest(const std::string& clientid_a,
                                    const std::string& clientid_b) {
@@ -1152,24 +1399,8 @@ DORATest::oneAllocationOverlapTest(const std::string& clientid_a,
     ASSERT_EQ(yiaddr, client.config_.lease_.addr_);
 }
 
-// This test checks the server behavior in the following situation:
-// - Client A identifies itself to the server using client identifier
-//   and the hardware address and requests allocation of the new lease.
-// - Server allocates the lease to the client.
-// - Client B has the same hardware address but is using a different
-//   client identifier then Client A.
-// - Client B sends DHCPDISCOVER.
-// - Server should determine that the client B is not client A, because
-//   it is using a different client identifier, even though they use the
-//   same HW address. As a consequence, the server should offer a
-//    different address to the client B.
-// - The client B performs the 4-way exchange again and the server
-//   allocates a new address to the client, which should be different
-//   than the address used by the client A.
-// - Client B is in the renewing state and it successfully renews its
-//   address.
-// - Client A also renews its address successfully.
-TEST_F(DORATest, twoAllocationsOverlap) {
+void
+DORATest::twoAllocationsOverlap() {
     // Allocate a lease by client A using the 4-way exchange.
     Dhcp4Client client_a(Dhcp4Client::SELECTING);
     client_a.includeClientId("12:34");
@@ -1240,6 +1471,16 @@ TEST_F(DORATest, twoAllocationsOverlap) {
     ASSERT_NE(client_a.config_.lease_.addr_, client_b.config_.lease_.addr_);
 }
 
+TEST_F(DORATest, twoAllocationsOverlap) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    twoAllocationsOverlap();
+}
+
+TEST_F(DORATest, twoAllocationsOverlapMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    twoAllocationsOverlap();
+}
+
 // This test checks the server behavior in the following situation:
 // - Client A identifies itself to the server using the hardware address
 //   and client identifier.
@@ -1255,6 +1496,26 @@ TEST_F(DORATest, twoAllocationsOverlap) {
 //   same reason.
 // - Client A renews its address successfully.
 TEST_F(DORATest, oneAllocationOverlap1) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    oneAllocationOverlapTest("12:34", "");
+}
+
+// This test checks the server behavior in the following situation:
+// - Client A identifies itself to the server using the hardware address
+//   and client identifier.
+// - Client A performs the 4-way exchange and obtains a lease from the server.
+// - Client B uses the same HW address as the client A, but it doesn't use
+//   the client identifier.
+// - Client B sends the DHCPDISCOVER to the server.
+//   The server determines that there is a lease for the client A using the
+//   same HW address as the client B. Server discards the client's message and
+//   doesn't offer the lease for the client B to prevent allocation of the
+//   lease without a unique identifier.
+// - The client sends the DHCPREQUEST and the server sends the DHCPNAK for the
+//   same reason.
+// - Client A renews its address successfully.
+TEST_F(DORATest, oneAllocationOverlap1MultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
     oneAllocationOverlapTest("12:34", "");
 }
 
@@ -1262,15 +1523,20 @@ TEST_F(DORATest, oneAllocationOverlap1) {
 // uses no client identifier, and the client B uses the HW address and the
 // client identifier. The server behaves as previously.
 TEST_F(DORATest, oneAllocationOverlap2) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
     oneAllocationOverlapTest("", "12:34");
 }
 
-// This is a simple test for the host reservation. It creates a reservation
-// for an address for a single client, identified by the HW address. The
-// test verifies that the client using this HW address will obtain a
-// lease for the reserved address. It also checks that the client using
-// a different HW address will obtain an address from the dynamic pool.
-TEST_F(DORATest, reservation) {
+// This test is similar to oneAllocationOverlap2 but this time the client A
+// uses no client identifier, and the client B uses the HW address and the
+// client identifier. The server behaves as previously.
+TEST_F(DORATest, oneAllocationOverlap2MultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    oneAllocationOverlapTest("", "12:34");
+}
+
+void
+DORATest::reservation() {
     // Client A is a one which will have a reservation.
     Dhcp4Client clientA(Dhcp4Client::SELECTING);
     // Set explicit HW address so as it matches the reservation in the
@@ -1309,9 +1575,18 @@ TEST_F(DORATest, reservation) {
     ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, clientB.config_.lease_.addr_));
 }
 
-// This test checks that it is possible to make a reservation by
-// DUID carried in the Client Identifier option.
-TEST_F(DORATest, reservationByDUID) {
+TEST_F(DORATest, reservation) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservation();
+}
+
+TEST_F(DORATest, reservationMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservation();
+}
+
+void
+DORATest:: reservationByDUID() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Use relay agent.
     client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
@@ -1341,9 +1616,18 @@ TEST_F(DORATest, reservationByDUID) {
     ASSERT_EQ("10.0.0.8", client.config_.lease_.addr_.toText());
 }
 
-// This test checks that it is possible to make a reservation by
-// circuit-id inserted by the relay agent.
-TEST_F(DORATest, reservationByCircuitId) {
+TEST_F(DORATest, reservationByDUID) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservationByDUID();
+}
+
+TEST_F(DORATest, reservationByDUIDMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservationByDUID();
+}
+
+void
+DORATest::reservationByCircuitId() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Use relay agent so as the circuit-id can be inserted.
     client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
@@ -1365,9 +1649,18 @@ TEST_F(DORATest, reservationByCircuitId) {
     ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText());
 }
 
-// This test checks that it is possible to make a reservation by
-// client-id.
-TEST_F(DORATest, reservationByClientId) {
+TEST_F(DORATest, reservationByCircuitId) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservationByCircuitId();
+}
+
+TEST_F(DORATest, reservationByCircuitIdMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservationByCircuitId();
+}
+
+void
+DORATest::reservationByClientId() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Use relay agent to make sure that the desired subnet is
     // selected for our client.
@@ -1390,9 +1683,18 @@ TEST_F(DORATest, reservationByClientId) {
     ASSERT_EQ("10.0.0.1", client.config_.lease_.addr_.toText());
 }
 
-// This test verifies that order in which host identifiers are used to
-// retrieve host reservations can be controlled.
-TEST_F(DORATest, hostIdentifiersOrder) {
+TEST_F(DORATest, reservationByClientId) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservationByClientId();
+}
+
+TEST_F(DORATest, reservationByClientIdMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservationByClientId();
+}
+
+void
+DORATest::hostIdentifiersOrder() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     client.setHWAddress("aa:bb:cc:dd:ee:ff");
     // Use relay agent so as the circuit-id can be inserted.
@@ -1468,10 +1770,18 @@ TEST_F(DORATest, hostIdentifiersOrder) {
     ASSERT_EQ("10.0.0.1", client.config_.lease_.addr_.toText());
 }
 
-// This test checks that setting the match-client-id value to false causes
-// the server to ignore changing client identifier when the client is
-// using consistent HW address.
-TEST_F(DORATest, ignoreChangingClientId) {
+TEST_F(DORATest, hostIdentifiersOrder) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    hostIdentifiersOrder();
+}
+
+TEST_F(DORATest, hostIdentifiersOrderMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    hostIdentifiersOrder();
+}
+
+void
+DORATest::ignoreChangingClientId() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[3], *client.getServer());
@@ -1507,9 +1817,18 @@ TEST_F(DORATest, ignoreChangingClientId) {
     EXPECT_FALSE(client.config_.lease_.client_id_);
 }
 
-// This test checks that the match-client-id parameter doesn't have
-// effect on the lease lookup using the HW address.
-TEST_F(DORATest, changingHWAddress) {
+TEST_F(DORATest, ignoreChangingClientId) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    ignoreChangingClientId();
+}
+
+TEST_F(DORATest, ignoreChangingClientIdMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    ignoreChangingClientId();
+}
+
+void
+DORATest::changingHWAddress() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[3], *client.getServer());
@@ -1547,9 +1866,18 @@ TEST_F(DORATest, changingHWAddress) {
     EXPECT_FALSE(client.config_.lease_.client_id_);
 }
 
-// This test verifies that the server assigns reserved values for the
-// siaddr, sname and file fields carried within DHCPv4 message.
-TEST_F(DORATest, messageFieldsReservations) {
+TEST_F(DORATest, changingHWAddress) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    changingHWAddress();
+}
+
+TEST_F(DORATest, changingHWAddressMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    changingHWAddress();
+}
+
+void
+DORATest::messageFieldsReservations() {
     // Client has a reservation.
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Set explicit HW address so as it matches the reservation in the
@@ -1573,45 +1901,18 @@ TEST_F(DORATest, messageFieldsReservations) {
     EXPECT_EQ("bootfile.efi", client.config_.boot_file_name_);
 }
 
-// This test checks the following scenario:
-// 1. Client A performs 4-way exchange and obtains a lease from the dynamic pool.
-// 2. Reservation is created for the client A using an address out of the dynamic
-//    pool.
-// 3. Client A renews the lease.
-// 4. Server responds with DHCPNAK to indicate that the client should stop using
-//    an address for which it has a lease. Server doesn't want to renew an
-//    address for which the client doesn't have a reservation, while it has
-//    a reservation for a different address.
-// 5. Client A receives a DHCPNAK and returns to the DHCP server discovery.
-// 6. Client A performs a 4-way exchange with a server and the server allocates
-//   a reserved address to the Client A.
-// 7. Client A renews the allocated address and the server returns a DHCPACK.
-// 8. Reservation for the Client A is removed.
-// 9. Client A renews the (previously reserved) lease and the server returns
-//    DHCPNAK because the address in use is neither reserved nor belongs to
-//    the dynamic pool.
-// 10. Client A returns to the DHCP server discovery.
-// 11. Client A uses 4-way exchange to obtain a lease from the dynamic pool.
-// 12. The new address that the Client A is using is reserved for Client B.
-//     Client A still holds this address.
-// 13. Client B uses 4-way exchange to obtain a new lease.
-// 14. The server determines that the Client B has a reservation for the
-//     address which is in use by Client A and offers an address different
-//     than reserved.
-// 15. Client B requests the allocation of the offered address and the server
-//     allocates this address.
-// 16. Client A renews the lease.
-// 17. The server determines that the address that Client A is using is reserved
-//     for Client B. The server returns DHCPNAK to the Client A.
-// 18. Client B uses 4-way exchange to obtain the reserved lease but the lease
-//     for the Client A hasn't been removed yet. Client B is assigned the same
-//     address it has been using.
-// 19. Client A uses 4-way exchange to allocate a new lease.
-// 20. The server allocates a new lease from the dynamic pool but it avoids
-//     allocating the address reserved for the Client B.
-// 21. Client B uses 4-way exchange to obtain a new lease.
-// 22. The server finally allocates a reserved address to the Client B.
-TEST_F(DORATest, reservationsWithConflicts) {
+TEST_F(DORATest, messageFieldsReservations) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    messageFieldsReservations();
+}
+
+TEST_F(DORATest, messageFieldsReservationsMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    messageFieldsReservations();
+}
+
+void
+DORATest::reservationsWithConflicts() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -1800,9 +2101,18 @@ TEST_F(DORATest, reservationsWithConflicts) {
     ASSERT_EQ(in_pool_addr, clientB.config_.lease_.addr_);
 }
 
-// This test verifies that the allocation engine ignores reservations when
-// reservations flags are set to "disabled".
-TEST_F(DORATest, reservationModeDisabled) {
+TEST_F(DORATest, reservationsWithConflicts) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservationsWithConflicts();
+}
+
+TEST_F(DORATest, reservationsWithConflictsMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservationsWithConflicts();
+}
+
+void
+DORATest::reservationModeDisabled() {
     // Client has a reservation.
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Set explicit HW address so as it matches the reservation in the
@@ -1836,12 +2146,18 @@ TEST_F(DORATest, reservationModeDisabled) {
     ASSERT_EQ("10.0.0.65", client.config_.lease_.addr_.toText());
 }
 
-// This test verifies that allocation engine assigns a reserved address to
-// the client which doesn't own this reservation. We want to avoid such
-// cases in the real deployments, but this is just a test that the allocation
-// engine skips checking if the reservation exists when it allocates an
-// address. In the real deployment the reservation simply wouldn't exist.
-TEST_F(DORATest, reservationIgnoredInDisabledMode) {
+TEST_F(DORATest, reservationModeDisabled) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservationModeDisabled();
+}
+
+TEST_F(DORATest, reservationModeDisabledMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservationModeDisabled();
+}
+
+void
+DORATest::reservationIgnoredInDisabledMode() {
     // Client has a reservation.
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Set MAC address which doesn't match the reservation configured.
@@ -1862,9 +2178,18 @@ TEST_F(DORATest, reservationIgnoredInDisabledMode) {
     ASSERT_EQ("10.0.0.65", client.config_.lease_.addr_.toText());
 }
 
-// This test verifies that in pool reservations are ignored when the
-// reservations-out-of-pool flag is set to true.
-TEST_F(DORATest, reservationModeOutOfPool) {
+TEST_F(DORATest, reservationIgnoredInDisabledMode) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservationIgnoredInDisabledMode();
+}
+
+TEST_F(DORATest, reservationIgnoredInDisabledModeMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservationIgnoredInDisabledMode();
+}
+
+void
+DORATest::reservationModeOutOfPool() {
     // Create the first client for which we have a reservation out of the
     // dynamic pool.
     Dhcp4Client clientA(Dhcp4Client::SELECTING);
@@ -1901,10 +2226,18 @@ TEST_F(DORATest, reservationModeOutOfPool) {
     ASSERT_EQ("10.0.0.40", clientB.config_.lease_.addr_.toText());
 }
 
-// This test verifies that the in-pool reservation can be assigned to a client
-// not owning this reservation when the reservations-out-of-pool flag is set to
-// true.
-TEST_F(DORATest, reservationIgnoredInOutOfPoolMode) {
+TEST_F(DORATest, reservationModeOutOfPool) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservationModeOutOfPool();
+}
+
+TEST_F(DORATest, reservationModeOutOfPoolMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservationModeOutOfPool();
+}
+
+void
+DORATest::reservationIgnoredInOutOfPoolMode() {
     // Create the first client for which we have a reservation out of the
     // dynamic pool.
     Dhcp4Client client(Dhcp4Client::SELECTING);
@@ -1924,9 +2257,18 @@ TEST_F(DORATest, reservationIgnoredInOutOfPoolMode) {
     ASSERT_EQ("10.0.0.65", client.config_.lease_.addr_.toText());
 }
 
-/// This test verifies that after a client completes its DORA exchange,
-/// appropriate statistics are updated.
-TEST_F(DORATest, statisticsDORA) {
+TEST_F(DORATest, reservationIgnoredInOutOfPoolMode) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    reservationIgnoredInOutOfPoolMode();
+}
+
+TEST_F(DORATest, reservationIgnoredInOutOfPoolModeMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    reservationIgnoredInOutOfPoolMode();
+}
+
+void
+DORATest::statisticsDORA() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -1983,9 +2325,18 @@ TEST_F(DORATest, statisticsDORA) {
     EXPECT_EQ(5, pkt4_sent->getInteger().first);
 }
 
-// This test verifies that after a client completes an exchange that result
-// in NAK, appropriate statistics are updated.
-TEST_F(DORATest, statisticsNAK) {
+TEST_F(DORATest, statisticsDORA) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    statisticsDORA();
+}
+
+TEST_F(DORATest, statisticsDORAMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    statisticsDORA();
+}
+
+void
+DORATest::statisticsNAK() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     configure(DORA_CONFIGS[0], *client.getServer());
@@ -2027,6 +2378,16 @@ TEST_F(DORATest, statisticsNAK) {
     EXPECT_EQ(1, pkt4_sent->getInteger().first);
 }
 
+TEST_F(DORATest, statisticsNAK) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    statisticsNAK();
+}
+
+TEST_F(DORATest, statisticsNAKMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    statisticsNAK();
+}
+
 void
 DORATest::testMultiStageBoot(const unsigned int config_index) {
     Dhcp4Client client(Dhcp4Client::SELECTING);
@@ -2105,17 +2466,24 @@ DORATest::testMultiStageBoot(const unsigned int config_index) {
     ASSERT_NE(leased_address2, leased_address3);
 }
 
-
 // Test that the client using the same hardware address but multiple
 // client identifiers will obtain multiple leases.
 TEST_F(DORATest, multiStageBoot) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
     // DORA_CONFIGS[0] to be used for server configuration.
     testMultiStageBoot(0);
 }
 
-// This test verifies that custom server identifier can be specified for
-// a subnet.
-TEST_F(DORATest, customServerIdentifier) {
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases.
+TEST_F(DORATest, multiStageBootMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    // DORA_CONFIGS[0] to be used for server configuration.
+    testMultiStageBoot(0);
+}
+
+void
+DORATest::customServerIdentifier() {
     Dhcp4Client client1(Dhcp4Client::SELECTING);
     // Configure DHCP server.
     ASSERT_NO_THROW(configure(DORA_CONFIGS[7], *client1.getServer()));
@@ -2154,9 +2522,18 @@ TEST_F(DORATest, customServerIdentifier) {
     EXPECT_EQ("3.4.5.6", client3.config_.serverid_.toText());
 }
 
-// This test verifies that reserved lease is not assigned to a client which
-// identifier doesn't match the identifier in the reservation.
-TEST_F(DORATest, changingCircuitId) {
+TEST_F(DORATest, customServerIdentifier) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    customServerIdentifier();
+}
+
+TEST_F(DORATest, customServerIdentifierMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    customServerIdentifier();
+}
+
+void
+DORATest::changingCircuitId() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     client.setHWAddress("aa:bb:cc:dd:ee:ff");
     // Use relay agent so as the circuit-id can be inserted.
@@ -2214,9 +2591,18 @@ TEST_F(DORATest, changingCircuitId) {
     EXPECT_NE("10.0.0.9", client.config_.lease_.addr_.toText());
 }
 
-// Verifies that extended info is stored on the lease when
-// store-extended-info is enabled.
-TEST_F(DORATest, storeExtendedInfoEnabled) {
+TEST_F(DORATest, changingCircuitId) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    changingCircuitId();
+}
+
+TEST_F(DORATest, changingCircuitIdMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    changingCircuitId();
+}
+
+void
+DORATest::storeExtendedInfoEnabled() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Use relay agent to make sure that the desired subnet is
     // selected for our client.
@@ -2253,9 +2639,18 @@ TEST_F(DORATest, storeExtendedInfoEnabled) {
     ASSERT_EQ(expected_context, ss.str());
 }
 
-// Verifies that extended info is not stored on the lease when
-// store-extended-info is disabled.
-TEST_F(DORATest, storeExtendedInfoDisabled) {
+TEST_F(DORATest, storeExtendedInfoEnabled) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    storeExtendedInfoEnabled();
+}
+
+TEST_F(DORATest, storeExtendedInfoEnabledMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    storeExtendedInfoEnabled();
+}
+
+void
+DORATest::storeExtendedInfoDisabled() {
     Dhcp4Client client(Dhcp4Client::SELECTING);
     // Use relay agent to make sure that the desired subnet is
     // selected for our client.
@@ -2285,7 +2680,15 @@ TEST_F(DORATest, storeExtendedInfoDisabled) {
     ASSERT_FALSE(lease->getContext());
 }
 
+TEST_F(DORATest, storeExtendedInfoDisabled) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    storeExtendedInfoDisabled();
+}
 
+TEST_F(DORATest, storeExtendedInfoDisabledMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    storeExtendedInfoDisabled();
+}
 
 // Starting tests which require MySQL backend availability. Those tests
 // will not be executed if Kea has been compiled without the
@@ -2315,7 +2718,16 @@ public:
 // Test that the client using the same hardware address but multiple
 // client identifiers will obtain multiple leases (MySQL lease database).
 TEST_F(DORAMySQLTest, multiStageBoot) {
-    // DORA_CONFIGS[9] to be used for server configuration.
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    // DORA_CONFIGS[8] to be used for server configuration.
+    testMultiStageBoot(8);
+}
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases (MySQL lease database).
+TEST_F(DORAMySQLTest, multiStageBootMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    // DORA_CONFIGS[8] to be used for server configuration.
     testMultiStageBoot(8);
 }
 
@@ -2349,6 +2761,15 @@ public:
 // Test that the client using the same hardware address but multiple
 // client identifiers will obtain multiple leases (PostgreSQL lease database).
 TEST_F(DORAPgSQLTest, multiStageBoot) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
+    // DORA_CONFIGS[9] to be used for server configuration.
+    testMultiStageBoot(9);
+}
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases (PostgreSQL lease database).
+TEST_F(DORAPgSQLTest, multiStageBootMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
     // DORA_CONFIGS[9] to be used for server configuration.
     testMultiStageBoot(9);
 }
@@ -2382,9 +2803,19 @@ public:
 // Test that the client using the same hardware address but multiple
 // client identifiers will obtain multiple leases (CQL lease database).
 TEST_F(DORACQLTest, multiStageBoot) {
+    Dhcpv4SrvMTTestGuard guard(*this, false);
     // DORA_CONFIGS[10] to be used for server configuration.
     testMultiStageBoot(10);
 }
+
+// Test that the client using the same hardware address but multiple
+// client identifiers will obtain multiple leases (CQL lease database).
+TEST_F(DORACQLTest, multiStageBootMultiThreading) {
+    Dhcpv4SrvMTTestGuard guard(*this, true);
+    // DORA_CONFIGS[10] to be used for server configuration.
+    testMultiStageBoot(10);
+}
+
 #endif
 
 } // end of anonymous namespace