]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1256] added unittests and addressed comments
authorRazvan Becheriu <razvan@isc.org>
Fri, 5 Jun 2020 16:41:34 +0000 (19:41 +0300)
committerFrancis Dupont <fdupont@isc.org>
Fri, 12 Jun 2020 14:57:32 +0000 (16:57 +0200)
src/lib/dhcpsrv/subnet.cc
src/lib/dhcpsrv/subnet.h
src/lib/dhcpsrv/tests/shared_network_unittest.cc
src/lib/dhcpsrv/tests/subnet_unittest.cc

index 7a54538f38950635e99782272e7b937089802acc..0a1bf5dedcb9ec85abb07e25b8af89cc95552aab 100644 (file)
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <util/multi_threading_mgr.h>
+
 #include <boost/lexical_cast.hpp>
 #include <boost/make_shared.hpp>
+
 #include <algorithm>
 #include <sstream>
 
@@ -64,7 +66,7 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
       last_allocated_time_(),
       iface_(),
       shared_network_name_(),
-      mutex_() {
+      mutex_(boost::make_shared<std::mutex>()) {
     if ((prefix.isV6() && len > 128) ||
         (prefix.isV4() && len > 32)) {
         isc_throw(BadValue,
@@ -76,8 +78,6 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
     last_allocated_time_[Lease::TYPE_NA] = boost::posix_time::neg_infin;
     last_allocated_time_[Lease::TYPE_TA] = boost::posix_time::neg_infin;
     last_allocated_time_[Lease::TYPE_PD] = boost::posix_time::neg_infin;
-
-    mutex_.reset(new std::mutex);
 }
 
 bool
index 94f4d17a03ecab105c4151e82094d18dd6944b21..e2ac1b364842257cb20973f49f0f1099169085d3 100644 (file)
@@ -36,10 +36,14 @@ namespace dhcp {
 class Subnet : public virtual Network {
 public:
 
-    /// @brief checks if specified address is in range
+    /// @brief checks if specified address is in range.
+    ///
+    /// @param addr this address will be checked if it is included in a specific
+    ///        range
+    /// @return true if address is in range, false otherwise
     bool inRange(const isc::asiolink::IOAddress& addr) const;
 
-    /// @brief checks if the specified address is in pools
+    /// @brief checks if the specified address is in pools.
     ///
     /// Note the difference between inRange() and inPool() for addresses
     /// (i.e. *not* prefixes). For a given subnet (e.g. 2001::/64) there
@@ -55,7 +59,7 @@ public:
     /// @return true if the address is in any of the pools
     bool inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const;
 
-    /// @brief checks if the specified address is in allowed pools
+    /// @brief checks if the specified address is in allowed pools.
     ///
     /// This takes also into account client classes
     ///
@@ -68,7 +72,7 @@ public:
                 const isc::asiolink::IOAddress& addr,
                 const ClientClasses& client_classes) const;
 
-    /// @brief returns the last address that was tried from this subnet
+    /// @brief returns the last address that was tried from this subnet.
     ///
     /// This method returns the last address that was attempted to be allocated
     /// from this subnet. This is used as helper information for the next
@@ -94,9 +98,10 @@ public:
     /// @return Time when a lease of a specified type has been allocated from
     /// this subnet. The negative infinity time is returned if a lease type is
     /// not recognized (which is unlikely).
-    boost::posix_time::ptime getLastAllocatedTime(const Lease::Type& lease_type) const;
+    boost::posix_time::ptime
+    getLastAllocatedTime(const Lease::Type& lease_type) const;
 
-    /// @brief sets the last address that was tried from this subnet
+    /// @brief sets the last address that was tried from this subnet.
     ///
     /// This method sets the last address that was attempted to be allocated
     /// from this subnet. This is used as helper information for the next
@@ -111,11 +116,12 @@ public:
     void setLastAllocated(Lease::Type type,
                           const isc::asiolink::IOAddress& addr);
 
-    /// @brief Returns unique ID for that subnet
+    /// @brief Returns unique ID for that subnet.
+    ///
     /// @return unique ID for that subnet
     SubnetID getID() const { return (id_); }
 
-    /// @brief Returns subnet parameters (prefix and prefix length)
+    /// @brief Returns subnet parameters (prefix and prefix length).
     ///
     /// @return (prefix, prefix length) pair
     std::pair<isc::asiolink::IOAddress, uint8_t> get() const {
@@ -146,13 +152,14 @@ public:
     /// within the subnet.
     void addPool(const PoolPtr& pool);
 
-    /// @brief Deletes all pools of specified type
+    /// @brief Deletes all pools of specified type.
     ///
     /// This method is used for testing purposes only
+    ///
     /// @param type type of pools to be deleted
     void delPools(Lease::Type type);
 
-    /// @brief Returns a pool that specified address belongs to
+    /// @brief Returns a pool that specified address belongs to.
     ///
     /// This method uses binary search to retrieve the pool. Thus, the number
     /// of comparisons performed by this method is logarithmic in the number
@@ -173,9 +180,9 @@ public:
     const PoolPtr getPool(Lease::Type type, const isc::asiolink::IOAddress& addr,
                           bool anypool = true) const;
 
-    /// @brief Returns a pool that specified address belongs to with classes
+    /// @brief Returns a pool that specified address belongs to with classes.
     ///
-    /// Variant using only pools allowing given classes
+    /// Variant using only pools allowing given classes.
     ///
     /// @param type pool type that the pool is looked for
     /// @param client_classes client class list which must be allowed
@@ -184,7 +191,7 @@ public:
                           const ClientClasses& client_classes,
                           const isc::asiolink::IOAddress& addr) const;
 
-    /// @brief Returns a pool without any address specified
+    /// @brief Returns a pool without any address specified.
     ///
     /// @param type pool type that the pool is looked for
     /// @return returns one of the pools defined
@@ -192,13 +199,13 @@ public:
         return (getPool(type, default_pool()));
     }
 
-    /// @brief Returns the default address that will be used for pool selection
+    /// @brief Returns the default address that will be used for pool selection.
     ///
     /// It must be implemented in derived classes (should return :: for Subnet6
-    /// and 0.0.0.0 for Subnet4)
+    /// and 0.0.0.0 for Subnet4).
     virtual isc::asiolink::IOAddress default_pool() const = 0;
 
-    /// @brief Returns all pools (const variant)
+    /// @brief Returns all pools (const variant).
     ///
     /// The reference is only valid as long as the object that returned it.
     ///
@@ -206,7 +213,7 @@ public:
     /// @return a collection of all pools
     const PoolCollection& getPools(Lease::Type type) const;
 
-    /// @brief Returns the number of possible leases for specified lease type
+    /// @brief Returns the number of possible leases for specified lease type.
     ///
     /// @param type type of the lease
     uint64_t getPoolCapacity(Lease::Type type) const;
@@ -215,17 +222,18 @@ public:
     /// allowed for a client which belongs to classes.
     ///
     /// @param type type of the lease
-    /// @param client_classes List of classes the client belongs to.
+    /// @param client_classes list of classes the client belongs to
+    /// @return number of leases matching lease type and classes
     uint64_t getPoolCapacity(Lease::Type type,
                              const ClientClasses& client_classes) const;
 
     /// @brief Returns textual representation of the subnet (e.g.
-    /// "2001:db8::/64")
+    /// "2001:db8::/64").
     ///
     /// @return textual representation
     virtual std::string toText() const;
 
-    /// @brief Resets subnet-id counter to its initial value (1)
+    /// @brief Resets subnet-id counter to its initial value (1).
     ///
     /// This should be called during reconfiguration, before any new
     /// subnet objects are created. It will ensure that the subnet_id will
@@ -268,6 +276,8 @@ public:
     }
 
     /// @brief Returns shared network name.
+    ///
+    /// @return shared network name
     std::string getSharedNetworkName() const {
         return (shared_network_name_);
     }
@@ -288,7 +298,7 @@ public:
         shared_network_name_ = shared_network_name;
     }
 
-    /// @brief Returns all pools (non-const variant)
+    /// @brief Returns all pools (non-const variant).
     ///
     /// The reference is only valid as long as the object that returned it.
     ///
@@ -298,7 +308,7 @@ public:
 
 protected:
 
-    /// @brief Protected constructor
+    /// @brief Protected constructor.
     //
     /// By making the constructor protected, we make sure that no one will
     /// ever instantiate that class. Subnet4 and Subnet6 should be used instead.
@@ -315,13 +325,13 @@ protected:
     Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
            const SubnetID id);
 
-    /// @brief virtual destructor
+    /// @brief virtual destructor.
     ///
     /// A virtual destructor is needed because other classes
     /// derive from this class.
     virtual ~Subnet() { };
 
-    /// @brief keeps the subnet-id value
+    /// @brief keeps the subnet-id value.
     ///
     /// It is incremented every time a new Subnet object is created. It is reset
     /// (@ref resetSubnetID) every time reconfiguration
@@ -330,7 +340,7 @@ protected:
     /// Static value initialized in subnet.cc.
     static SubnetID static_id_;
 
-    /// @brief returns the next unique Subnet-ID
+    /// @brief returns the next unique Subnet-ID.
     ///
     /// This method generates and returns the next unique subnet-id.
     /// It is a strictly monotonously increasing value (1,2,3,...) for
@@ -346,7 +356,7 @@ protected:
         return (static_id_++);
     }
 
-    /// @brief Checks if used pool type is valid
+    /// @brief Checks if used pool type is valid.
     ///
     /// Allowed type for Subnet4 is Pool::TYPE_V4.
     /// Allowed types for Subnet6 are Pool::TYPE_{IA,TA,PD}.
@@ -356,12 +366,14 @@ protected:
     /// @throw BadValue if invalid value is used
     virtual void checkType(Lease::Type type) const = 0;
 
-    /// @brief Returns a sum of possible leases in all pools
+    /// @brief Returns a sum of possible leases in all pools.
+    ///
     /// @param pools list of pools
     /// @return sum of possible leases
     uint64_t sumPoolCapacity(const PoolCollection& pools) const;
 
-    /// @brief Returns a sum of possible leases in all pools allowing classes
+    /// @brief Returns a sum of possible leases in all pools allowing classes.
+    ///
     /// @param pools list of pools
     /// @param client_classes list of classes
     /// @return sum of possible/allowed leases
@@ -375,7 +387,7 @@ protected:
     /// it overlaps with any existing pool within this subnet.
     ///
     /// @return true if pool overlaps with an existing pool of a specified
-    /// type.
+    /// type, false otherwise
     bool poolOverlaps(const Lease::Type& pool_type, const PoolPtr& pool) const;
 
     /// @brief Unparse a subnet object.
@@ -400,22 +412,22 @@ protected:
     /// a Subnet4 or Subnet6.
     SubnetID id_;
 
-    /// @brief collection of IPv4 or non-temporary IPv6 pools in that subnet
+    /// @brief collection of IPv4 or non-temporary IPv6 pools in that subnet.
     PoolCollection pools_;
 
-    /// @brief collection of IPv6 temporary address pools in that subnet
+    /// @brief collection of IPv6 temporary address pools in that subnet.
     PoolCollection pools_ta_;
 
-    /// @brief collection of IPv6 prefix pools in that subnet
+    /// @brief collection of IPv6 prefix pools in that subnet.
     PoolCollection pools_pd_;
 
-    /// @brief a prefix of the subnet
+    /// @brief a prefix of the subnet.
     isc::asiolink::IOAddress prefix_;
 
-    /// @brief a prefix length of the subnet
+    /// @brief a prefix length of the subnet.
     uint8_t prefix_len_;
 
-    /// @brief last allocated address
+    /// @brief last allocated address.
     ///
     /// This is the last allocated address that was previously allocated from
     /// this particular subnet. Some allocation algorithms (e.g. iterative) use
@@ -426,12 +438,12 @@ protected:
     /// fully trusted.
     isc::asiolink::IOAddress last_allocated_ia_;
 
-    /// @brief last allocated temporary address
+    /// @brief last allocated temporary address.
     ///
     /// See @ref last_allocated_ia_ for details.
     isc::asiolink::IOAddress last_allocated_ta_;
 
-    /// @brief last allocated IPv6 prefix
+    /// @brief last allocated IPv6 prefix.
     ///
     /// See @ref last_allocated_ia_ for details.
     isc::asiolink::IOAddress last_allocated_pd_;
@@ -442,7 +454,7 @@ protected:
     /// @note: This map is protected by the mutex.
     std::map<Lease::Type, boost::posix_time::ptime> last_allocated_time_;
 
-    /// @brief Name of the network interface (if connected directly)
+    /// @brief Name of the network interface (if connected directly).
     std::string iface_;
 
     /// @brief Shared network name.
@@ -450,13 +462,15 @@ protected:
 
 private:
 
-    /// @brief returns the last address that was tried from this subnet
+    /// @brief returns the last address that was tried from this subnet.
+    ///
+    /// Should be called in a thread safe context.
     ///
     /// This method returns the last address that was attempted to be allocated
     /// from this subnet. This is used as helper information for the next
     /// iteration of the allocation algorithm.
     ///
-     /// @todo: Define map<SubnetID, ClientClass, IOAddress> somewhere in the
+    /// @todo: Define map<SubnetID, ClientClass, IOAddress> somewhere in the
     ///        AllocEngine::IterativeAllocator and keep the data there
     ///
     /// @param type lease type to be returned
@@ -466,6 +480,8 @@ private:
     /// @brief Returns the timestamp when the @c setLastAllocated function
     /// was called.
     ///
+    /// Should be called in a thread safe context.
+    ///
     /// @param lease_type Lease type for which last allocation timestamp should
     /// be returned.
     ///
@@ -475,7 +491,9 @@ private:
     boost::posix_time::ptime
     getLastAllocatedTimeInternal(const Lease::Type& lease_type) const;
 
-    /// @brief sets the last address that was tried from this subnet
+    /// @brief sets the last address that was tried from this subnet.
+    ///
+    /// Should be called in a thread safe context.
     ///
     /// This method sets the last address that was attempted to be allocated
     /// from this subnet. This is used as helper information for the next
@@ -490,7 +508,7 @@ private:
     void setLastAllocatedInternal(Lease::Type type,
                                   const isc::asiolink::IOAddress& addr);
 
-    /// @brief The mutex protecting the last_allocated_time_ map.
+    /// @brief Mutex to protect the internal state.
     boost::scoped_ptr<std::mutex> mutex_;
 };
 
@@ -514,7 +532,7 @@ typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
 class Subnet4 : public Subnet, public Network4 {
 public:
 
-    /// @brief Constructor with all parameters
+    /// @brief Constructor with all parameters.
     ///
     /// This constructor calls Subnet::Subnet, where subnet-id is generated.
     ///
@@ -627,13 +645,14 @@ public:
 
 private:
 
-    /// @brief Returns default address for pool selection
+    /// @brief Returns default address for pool selection.
+    ///
     /// @return ANY IPv4 address
     virtual isc::asiolink::IOAddress default_pool() const {
         return (isc::asiolink::IOAddress("0.0.0.0"));
     }
 
-    /// @brief Checks if used pool type is valid
+    /// @brief Checks if used pool type is valid.
     ///
     /// Allowed type for Subnet4 is Pool::TYPE_V4.
     ///
@@ -661,7 +680,7 @@ typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
 class Subnet6 : public Subnet, public Network6 {
 public:
 
-    /// @brief Constructor with all parameters
+    /// @brief Constructor with all parameters.
     ///
     /// This constructor calls Subnet::Subnet, where subnet-id is generated.
     ///
@@ -797,7 +816,7 @@ struct SubnetServerIdIndexTag { };
 /// @brief Tag for the index for searching by subnet modification time.
 struct SubnetModificationTimeIndexTag { };
 
-/// @brief A collection of @c Subnet4 objects
+/// @brief A collection of @c Subnet4 objects.
 ///
 /// This container provides a set of indexes which can be used to retrieve
 /// subnets by various properties.
index 7179e7832669c7b73d867adfa33b35e02cd44a57..678703a7cbaf4b500e7b26461601df3006437170 100644 (file)
@@ -1,10 +1,11 @@
-// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2020 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
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+
 #include <asiolink/io_address.h>
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
@@ -12,6 +13,8 @@
 #include <dhcpsrv/triplet.h>
 #include <exceptions/exceptions.h>
 #include <testutils/test_to_element.h>
+#include <util/multi_threading_mgr.h>
+
 #include <gtest/gtest.h>
 #include <cstdint>
 #include <vector>
@@ -440,6 +443,90 @@ TEST(SharedNetwork4Test, getPreferredSubnet) {
     EXPECT_EQ(subnet3->getID(), preferred->getID());
 }
 
+// This test verifies that preferred subnet is returned based on the timestamp
+// when the subnet was last used and allowed client classes.
+TEST(SharedNetwork4Test, getPreferredSubnetMultiThreading) {
+    MultiThreadingMgr::instance().setMode(true);
+    SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+    // Create four subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+                                   SubnetID(1)));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+                                   SubnetID(2)));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("172.16.25.0"), 24, 10, 20, 30,
+                                   SubnetID(3)));
+    Subnet4Ptr subnet4(new Subnet4(IOAddress("172.16.28.0"), 24, 10, 20, 30,
+                                   SubnetID(4)));
+
+    // Associate first two subnets with classes.
+    subnet1->allowClientClass("class1");
+    subnet2->allowClientClass("class1");
+
+    std::vector<Subnet4Ptr> subnets;
+    subnets.push_back(subnet1);
+    subnets.push_back(subnet2);
+    subnets.push_back(subnet3);
+    subnets.push_back(subnet4);
+
+    // Subnets have unique IDs so they should successfully be added to the
+    // network.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        ASSERT_NO_THROW(network->add(subnets[i]))
+            << "failed to add subnet with id " << subnets[i]->getID()
+            << " to shared network";
+    }
+
+    Subnet4Ptr preferred;
+
+    // Initially, for every subnet we should get the same subnet as the preferred
+    // one, because none of them have been used.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        preferred = network->getPreferredSubnet(subnets[i]);
+        EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+    }
+
+    // Allocating an address from subnet2 updates the last allocated timestamp
+    // for this subnet, which makes this subnet preferred over subnet1.
+    subnet2->setLastAllocated(Lease::TYPE_V4, IOAddress("192.0.2.25"));
+    preferred = network->getPreferredSubnet(subnet1);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // If selected is subnet2, the same is returned.
+    preferred = network->getPreferredSubnet(subnet2);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // Even though the subnet1 has been most recently used, the preferred
+    // subnet is subnet3 in this case, because of the client class
+    // mismatch.
+    preferred = network->getPreferredSubnet(subnet3);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+    // Same for subnet4.
+    preferred = network->getPreferredSubnet(subnet4);
+    EXPECT_EQ(subnet4->getID(), preferred->getID());
+
+    // Allocate an address from the subnet3. This makes it preferred to
+    // subnet4.
+    subnet3->setLastAllocated(Lease::TYPE_V4, IOAddress("172.16.25.23"));
+
+    // If the selected is subnet1, the preferred subnet is subnet2, because
+    // it has the same set of classes as subnet1. The subnet3 can't be
+    // preferred here because of the client class mismatch.
+    preferred = network->getPreferredSubnet(subnet1);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // If we select subnet4, the preferred subnet is subnet3 because
+    // it was used more recently.
+    preferred = network->getPreferredSubnet(subnet4);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+    // Repeat the test for subnet3 being a selected subnet.
+    preferred = network->getPreferredSubnet(subnet3);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+    MultiThreadingMgr::instance().setMode(false);
+}
+
 // This test verifies that subnetsIncludeMatchClientId() works as expected.
 TEST(SharedNetwork4Test, subnetsIncludeMatchClientId) {
     SharedNetwork4Ptr network(new SharedNetwork4("frog"));
@@ -1095,6 +1182,106 @@ TEST(SharedNetwork6Test, getPreferredSubnet) {
     EXPECT_EQ(subnet3->getID(), preferred->getID());
 }
 
+// This test verifies that preferred subnet is returned based on the timestamp
+// when the subnet was last used and allowed client classes.
+TEST(SharedNetwork6Test, getPreferredSubnetMultiThreading) {
+    MultiThreadingMgr::instance().setMode(true);
+    SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+    // Create four subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+                                   40, SubnetID(1)));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+                                   SubnetID(2)));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30,
+                                   40, SubnetID(3)));
+    Subnet6Ptr subnet4(new Subnet6(IOAddress("3000:1::"), 64, 10, 20, 30,
+                                   40, SubnetID(4)));
+
+
+    // Associate first two subnets with classes.
+    subnet1->allowClientClass("class1");
+    subnet2->allowClientClass("class1");
+
+    std::vector<Subnet6Ptr> subnets;
+    subnets.push_back(subnet1);
+    subnets.push_back(subnet2);
+    subnets.push_back(subnet3);
+    subnets.push_back(subnet4);
+
+    // Subnets have unique IDs so they should successfully be added to the
+    // network.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        ASSERT_NO_THROW(network->add(subnets[i]))
+            << "failed to add subnet with id " << subnets[i]->getID()
+            << " to shared network";
+    }
+
+    Subnet6Ptr preferred;
+
+    // Initially, for every subnet we should get the same subnet as the preferred
+    // one, because none of them have been used.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_NA);
+        EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+        preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_TA);
+        EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+        preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_PD);
+        EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+    }
+
+    // Allocating an address from subnet2 updates the last allocated timestamp
+    // for this subnet, which makes this subnet preferred over subnet1.
+    subnet2->setLastAllocated(Lease::TYPE_NA, IOAddress("2001:db8:1:2::"));
+    preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // If selected is subnet2, the same is returned.
+    preferred = network->getPreferredSubnet(subnet2, Lease::TYPE_NA);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // The preferred subnet is dependent on the lease type. For the PD
+    // we should get the same subnet as selected.
+    preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD);
+    EXPECT_EQ(subnet1->getID(), preferred->getID());
+
+    // Although, if we pick a prefix from the subnet2, we should get the
+    // subnet2 as preferred instead.
+    subnet2->setLastAllocated(Lease::TYPE_PD, IOAddress("3000:1234::"));
+    preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // Even though the subnet1 has been most recently used, the preferred
+    // subnet is subnet3 in this case, because of the client class
+    // mismatch.
+    preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+    // Same for subnet4.
+    preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA);
+    EXPECT_EQ(subnet4->getID(), preferred->getID());
+
+    // Allocate an address from the subnet3. This makes it preferred to
+    // subnet4.
+    subnet3->setLastAllocated(Lease::TYPE_NA, IOAddress("2001:db8:2:1234::"));
+
+    // If the selected is subnet1, the preferred subnet is subnet2, because
+    // it has the same set of classes as subnet1. The subnet3 can't be
+    // preferred here because of the client class mismatch.
+    preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // If we select subnet4, the preferred subnet is subnet3 because
+    // it was used more recently.
+    preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+    // Repeat the test for subnet3 being a selected subnet.
+    preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+    MultiThreadingMgr::instance().setMode(false);
+}
+
 // This test verifies that subnetsAllHRGlobal() works as expected.
 TEST(SharedNetwork6Test, subnetsAllHRGlobal) {
     SharedNetwork6Ptr network(new SharedNetwork6("frog"));
index 160c2a4caa86395a82dde7fb1d07bd2ef4534ebf..c36d987c4058d8f4213c98f7159a36ae5bd287ee 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2020 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
@@ -18,6 +18,7 @@
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
+#include <util/multi_threading_mgr.h>
 
 #include <boost/pointer_cast.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -683,7 +684,6 @@ TEST(Subnet4Test, get) {
     EXPECT_EQ(28, subnet->get().second);
 }
 
-
 // Checks if last allocated address/prefix is stored/retrieved properly
 TEST(Subnet4Test, lastAllocated) {
     IOAddress addr("192.0.2.17");
@@ -705,6 +705,29 @@ TEST(Subnet4Test, lastAllocated) {
     EXPECT_THROW(subnet->setLastAllocated(Lease::TYPE_PD, addr), BadValue);
 }
 
+// Checks if last allocated address/prefix is stored/retrieved properly
+TEST(Subnet4Test, lastAllocatedMultiThreading) {
+    MultiThreadingMgr::instance().setMode(true);
+    IOAddress addr("192.0.2.17");
+
+    IOAddress last("192.0.2.255");
+
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+
+    // Check initial conditions (all should be set to the last address in range)
+    EXPECT_EQ(last.toText(), subnet->getLastAllocated(Lease::TYPE_V4).toText());
+
+    // Now set last allocated for IA
+    EXPECT_NO_THROW(subnet->setLastAllocated(Lease::TYPE_V4, addr));
+    EXPECT_EQ(addr.toText(), subnet->getLastAllocated(Lease::TYPE_V4).toText());
+
+    // No, you can't set the last allocated IPv6 address in IPv4 subnet
+    EXPECT_THROW(subnet->setLastAllocated(Lease::TYPE_TA, addr), BadValue);
+    EXPECT_THROW(subnet->setLastAllocated(Lease::TYPE_TA, addr), BadValue);
+    EXPECT_THROW(subnet->setLastAllocated(Lease::TYPE_PD, addr), BadValue);
+    MultiThreadingMgr::instance().setMode(false);
+}
+
 // Checks if the V4 is the only allowed type for Pool4 and if getPool()
 // is working properly.
 TEST(Subnet4Test, PoolType) {
@@ -1732,6 +1755,43 @@ TEST(Subnet6Test, lastAllocated) {
     EXPECT_THROW(subnet->setLastAllocated(Lease::TYPE_V4, ia), BadValue);
 }
 
+// Checks if last allocated address/prefix is stored/retrieved properly
+TEST(Subnet6Test, lastAllocatedMultiThreading) {
+    MultiThreadingMgr::instance().setMode(true);
+    IOAddress ia("2001:db8:1::1");
+    IOAddress ta("2001:db8:1::abcd");
+    IOAddress pd("2001:db8:1::1234:5678");
+
+    IOAddress last("2001:db8:1::ffff:ffff:ffff:ffff");
+
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4));
+
+    // Check initial conditions (all should be set to the last address in range)
+    EXPECT_EQ(last.toText(), subnet->getLastAllocated(Lease::TYPE_NA).toText());
+    EXPECT_EQ(last.toText(), subnet->getLastAllocated(Lease::TYPE_TA).toText());
+    EXPECT_EQ(last.toText(), subnet->getLastAllocated(Lease::TYPE_PD).toText());
+
+    // Now set last allocated for IA
+    EXPECT_NO_THROW(subnet->setLastAllocated(Lease::TYPE_NA, ia));
+    EXPECT_EQ(ia.toText(), subnet->getLastAllocated(Lease::TYPE_NA).toText());
+
+    // TA and PD should be unchanged
+    EXPECT_EQ(last.toText(), subnet->getLastAllocated(Lease::TYPE_TA).toText());
+    EXPECT_EQ(last.toText(), subnet->getLastAllocated(Lease::TYPE_PD).toText());
+
+    // Now set TA and PD
+    EXPECT_NO_THROW(subnet->setLastAllocated(Lease::TYPE_TA, ta));
+    EXPECT_NO_THROW(subnet->setLastAllocated(Lease::TYPE_PD, pd));
+
+    EXPECT_EQ(ia.toText(), subnet->getLastAllocated(Lease::TYPE_NA).toText());
+    EXPECT_EQ(ta.toText(), subnet->getLastAllocated(Lease::TYPE_TA).toText());
+    EXPECT_EQ(pd.toText(), subnet->getLastAllocated(Lease::TYPE_PD).toText());
+
+    // No, you can't set the last allocated IPv4 address in IPv6 subnet
+    EXPECT_THROW(subnet->setLastAllocated(Lease::TYPE_V4, ia), BadValue);
+    MultiThreadingMgr::instance().setMode(false);
+}
+
 // This test verifies that the IPv4 subnet can be fetched by id.
 TEST(SubnetFetcherTest, getSubnet4ById) {
     Subnet4Collection collection;