Browse Source

[5314] Added indexing to subnet searches.

Marcin Siodelski 7 years ago
parent
commit
63d6755e7c

+ 14 - 0
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -34,6 +34,20 @@ CfgSubnets4::add(const Subnet4Ptr& subnet) {
     subnets_.push_back(subnet);
     subnets_.push_back(subnet);
 }
 }
 
 
+ConstSubnet4Ptr
+CfgSubnets4::getBySubnetId(const SubnetID& subnet_id) const {
+    const auto& index = subnets_.get<SubnetIdIndexTag>();
+    auto subnet_it = index.find(subnet_id);
+    return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
+}
+
+ConstSubnet4Ptr
+CfgSubnets4::getByPrefix(const std::string& subnet_text) const {
+    const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+    auto subnet_it = index.find(subnet_text);
+    return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
+}
+
 Subnet4Ptr
 Subnet4Ptr
 CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {
 CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {
 
 

+ 36 - 0
src/lib/dhcpsrv/cfg_subnets4.h

@@ -10,8 +10,10 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <cc/cfg_to_element.h>
 #include <cc/cfg_to_element.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
+#include <string>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -48,6 +50,40 @@ public:
         return (&subnets_);
         return (&subnets_);
     }
     }
 
 
+    /// @brief Returns const pointer to a subnet identified by the specified
+    /// subnet identifier.
+    ///
+    /// The const pointer is returned by this method to prevent a caller from
+    /// modifying the subnet configuration. Modifications to subnet configuration
+    /// is dangerous and must be done carefully. The subnets' configruation is
+    /// held in the multi index container and any modifications to the subnet
+    /// id or subnet prefix must trigger re-indexing of multi index container.
+    /// There is no possibility to enforce this when the non-const pointer is
+    /// returned.
+    ///
+    /// @param subnet_id Subnet identifier.
+    ///
+    /// @return Pointer to the @c Subnet4 object or null pointer if such
+    /// subnet doesn't exist.
+    ConstSubnet4Ptr getBySubnetId(const SubnetID& subnet_id) const;
+
+    /// @brief Returns const pointer to a subnet which matches the specified
+    /// prefix in the canonical form.
+    ///
+    /// The const pointer is returned by this method to prevent a caller from
+    /// modifying the subnet configuration. Modifications to subnet configuration
+    /// is dangerous and must be done carefully. The subnets' configruation is
+    /// held in the multi index container and any modifications to the subnet
+    /// id or subnet prefix must trigger re-indexing of multi index container.
+    /// There is no possibility to enforce this when the non-const pointer is
+    /// returned.
+    ///
+    /// @param subnet_prefix Subnet prefix, e.g. 10.2.3.0/24
+    ///
+    /// @return Pointer to the @c Subnet4 object or null pointer if such
+    /// subnet doesn't exist.
+    ConstSubnet4Ptr getByPrefix(const std::string& subnet_prefix) const;
+
     /// @brief Returns a pointer to the selected subnet.
     /// @brief Returns a pointer to the selected subnet.
     ///
     ///
     /// This method tries to retrieve the subnet for the client using various
     /// This method tries to retrieve the subnet for the client using various

+ 14 - 0
src/lib/dhcpsrv/cfg_subnets6.cc

@@ -33,6 +33,20 @@ CfgSubnets6::add(const Subnet6Ptr& subnet) {
     subnets_.push_back(subnet);
     subnets_.push_back(subnet);
 }
 }
 
 
+ConstSubnet6Ptr
+CfgSubnets6::getBySubnetId(const SubnetID& subnet_id) const {
+    const auto& index = subnets_.get<SubnetIdIndexTag>();
+    auto subnet_it = index.find(subnet_id);
+    return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet6Ptr());
+}
+
+ConstSubnet6Ptr
+CfgSubnets6::getByPrefix(const std::string& subnet_text) const {
+    const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+    auto subnet_it = index.find(subnet_text);
+    return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet6Ptr());
+}
+
 Subnet6Ptr
 Subnet6Ptr
 CfgSubnets6::selectSubnet(const SubnetSelector& selector) const {
 CfgSubnets6::selectSubnet(const SubnetSelector& selector) const {
     Subnet6Ptr subnet;
     Subnet6Ptr subnet;

+ 37 - 1
src/lib/dhcpsrv/cfg_subnets6.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // 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
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,9 +11,11 @@
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <cc/cfg_to_element.h>
 #include <cc/cfg_to_element.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <util/optional_value.h>
 #include <util/optional_value.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
+#include <string>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -49,6 +51,40 @@ public:
         return (&subnets_);
         return (&subnets_);
     }
     }
 
 
+    /// @brief Returns const pointer to a subnet identified by the specified
+    /// subnet identifier.
+    ///
+    /// The const pointer is returned by this method to prevent a caller from
+    /// modifying the subnet configuration. Modifications to subnet configuration
+    /// is dangerous and must be done carefully. The subnets' configruation is
+    /// held in the multi index container and any modifications to the subnet
+    /// id or subnet prefix must trigger re-indexing of multi index container.
+    /// There is no possibility to enforce this when the non-const pointer is
+    /// returned.
+    ///
+    /// @param subnet_id Subnet identifier.
+    ///
+    /// @return Pointer to the @c Subnet6 object or null pointer if such
+    /// subnet doesn't exist.
+    ConstSubnet6Ptr getBySubnetId(const SubnetID& subnet_id) const;
+
+    /// @brief Returns const pointer to a subnet which matches the specified
+    /// prefix in the canonical form.
+    ///
+    /// The const pointer is returned by this method to prevent a caller from
+    /// modifying the subnet configuration. Modifications to subnet configuration
+    /// is dangerous and must be done carefully. The subnets' configruation is
+    /// held in the multi index container and any modifications to the subnet
+    /// id or subnet prefix must trigger re-indexing of multi index container.
+    /// There is no possibility to enforce this when the non-const pointer is
+    /// returned.
+    ///
+    /// @param subnet_prefix Subnet prefix, e.g. 2001:db8:1::/64
+    ///
+    /// @return Pointer to the @c Subnet6 object or null pointer if such
+    /// subnet doesn't exist.
+    ConstSubnet6Ptr getByPrefix(const std::string& subnet_prefix) const;
+
     /// @brief Selects a subnet using parameters specified in the selector.
     /// @brief Selects a subnet using parameters specified in the selector.
     ///
     ///
     /// This method tries to retrieve the subnet for the client using various
     /// This method tries to retrieve the subnet for the client using various

+ 83 - 10
src/lib/dhcpsrv/subnet.h

@@ -19,6 +19,11 @@
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/triplet.h>
 #include <dhcpsrv/triplet.h>
 
 
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/random_access_index.hpp>
+#include <boost/multi_index_container.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
 namespace isc {
 namespace isc {
@@ -623,16 +628,12 @@ private:
     Cfg4o6 dhcp4o6_;
     Cfg4o6 dhcp4o6_;
 };
 };
 
 
-/// @brief A pointer to a @c Subnet4 object
+/// @brief A const pointer to a @c Subnet4 object.
+typedef boost::shared_ptr<const Subnet4> ConstSubnet4Ptr;
+
+/// @brief A pointer to a @c Subnet4 object.
 typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
 typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
 
 
-/// @brief A collection of @c Subnet4 objects
-///
-/// That is a simple vector of pointers. It does not make much sense to
-/// optimize access time (e.g. using a map), because typical search
-/// pattern will use calling inRange() method on each subnet until
-/// a match is found.
-typedef std::vector<Subnet4Ptr> Subnet4Collection;
 
 
 /// @brief A configuration holder for IPv6 subnet.
 /// @brief A configuration holder for IPv6 subnet.
 ///
 ///
@@ -731,11 +732,83 @@ private:
 
 
 };
 };
 
 
+/// @brief A const pointer to a @c Subnet6 object.
+typedef boost::shared_ptr<const Subnet6> ConstSubnet6Ptr;
+
 /// @brief A pointer to a Subnet6 object
 /// @brief A pointer to a Subnet6 object
 typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
 typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
 
 
-/// @brief A collection of Subnet6 objects
-typedef std::vector<Subnet6Ptr> Subnet6Collection;
+/// @name Definition of the multi index container holding subnet information
+///
+//@{
+
+/// @brief Tag for the random access index.
+struct SubnetRandomAccessIndexTag { };
+
+/// @brief Tag for the index for searching by subnet identifier.
+struct SubnetIdIndexTag { };
+
+/// @brief Tag for the index for searching by subnet prefix.
+struct SubnetPrefixIndexTag { };
+
+/// @brief Multi index container holding subnets.
+///
+/// This multi index container can hold pointers to @ref Subnet4 or
+/// @ref Subnet6 objects representing subnets. It provides indexes for
+/// subnet lookups using subnet properties such as: subnet identifier
+/// or subnet prefix. It also provides a random access index which
+/// allows for using the container like a vector.
+///
+/// The random access index is used by the DHCP servers which perform
+/// a full scan on subnets to find the one that matches some specific
+/// criteria for subnet selection.
+///
+/// The remaining indexes are used for searching for a specific subnet
+/// as a result of receiving a command over the control API, e.g.
+/// when 'subnet-get' command is received.
+///
+/// @todo We should consider optimizing subnet selection by leveraging
+/// the indexing capabilities of this container, e.g. searching for
+/// a subnet by interface name, relay address etc.
+///
+/// @tparam SubnetType Type of the subnet: @ref Subnet4 or @ref Subnet6.
+template<typename SubnetType>
+using SubnetCollection = boost::multi_index_container<
+    // Multi index container holds pointers to the subnets.
+    boost::shared_ptr<SubnetType>,
+    // The following holds all indexes.
+    boost::multi_index::indexed_by<
+        // First is the random access index allowing for accessing
+        // objects just like we'd do with a vector.
+        boost::multi_index::random_access<
+            boost::multi_index::tag<SubnetRandomAccessIndexTag>
+        >,
+        // Second index allows for searching using subnet identifier.
+        boost::multi_index::ordered_unique<
+            boost::multi_index::tag<SubnetIdIndexTag>,
+            boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
+        >,
+        // Third index allows for searching using an output from toText function.
+        boost::multi_index::ordered_unique<
+            boost::multi_index::tag<SubnetTextIndexTag>,
+            boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+        >
+    >
+>;
+
+/// @brief A collection of @c Subnet4 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by various properties.
+typedef SubnetCollection<Subnet4> Subnet4Collection;
+
+/// @brief A collection of @c Subnet6 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by various properties.
+typedef SubnetCollection<Subnet6> Subnet6Collection;
+
+//@}
 
 
 } // end of isc::dhcp namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace

+ 2 - 0
src/lib/dhcpsrv/tests/alloc_engine_utils.cc

@@ -529,6 +529,8 @@ AllocEngine4Test::initSubnet(const asiolink::IOAddress& pool_start,
 
 
 AllocEngine4Test::AllocEngine4Test() {
 AllocEngine4Test::AllocEngine4Test() {
 
 
+    CfgMgr::instance().clear();
+
     // This lease mgr needs to exist to before configuration commits.
     // This lease mgr needs to exist to before configuration commits.
     factory_.create("type=memfile universe=4 persist=false");
     factory_.create("type=memfile universe=4 persist=false");
 
 

+ 51 - 0
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -23,6 +23,57 @@ using namespace isc::test;
 
 
 namespace {
 namespace {
 
 
+// This test verifies that specific subnet can be retrieved by specifying
+// subnet identifier or subnet prefix.
+TEST(CfgSubnets4Test, getSpecificSubnet) {
+    CfgSubnets4 cfg;
+
+    // Create 3 subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+                                   26, 1, 2, 3, SubnetID(5)));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+                                   26, 1, 2, 3, SubnetID(8)));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+                                   26, 1, 2, 3, SubnetID(10)));
+
+    // Store the subnets in a vector to make it possible to loop over
+    // all configured subnets.
+    std::vector<Subnet4Ptr> subnets;
+    subnets.push_back(subnet1);
+    subnets.push_back(subnet2);
+    subnets.push_back(subnet3);
+
+    // Add all subnets to the configuration.
+    for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); ++subnet) {
+        ASSERT_NO_THROW(cfg.add(*subnet)) << "failed to add subnet with id: "
+            << (*subnet)->getID();
+    }
+
+    // Iterate over all subnets and make sure they can be retrieved by
+    // subnet identifier.
+    for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+        ConstSubnet4Ptr subnet_returned = cfg.getBySubnetId((*subnet)->getID());
+        ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+            << (*subnet)->getID();
+        EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+        EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+    }
+
+    // Repeat the previous test, but this time retrieve subnets by their
+    // prefixes.
+    for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+        ConstSubnet4Ptr subnet_returned = cfg.getByPrefix((*subnet)->toText());
+        ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+            << (*subnet)->getID();
+        EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+        EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+    }
+
+    // Make sure that null pointers are returned for non-existing subnets.
+    EXPECT_FALSE(cfg.getBySubnetId(SubnetID(123)));
+    EXPECT_FALSE(cfg.getByPrefix("10.20.30.0/29"));
+}
+
 // This test verifies that it is possible to retrieve a subnet using an
 // This test verifies that it is possible to retrieve a subnet using an
 // IP address.
 // IP address.
 TEST(CfgSubnets4Test, selectSubnetByCiaddr) {
 TEST(CfgSubnets4Test, selectSubnetByCiaddr) {

+ 51 - 0
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc

@@ -32,6 +32,57 @@ generateInterfaceId(const std::string& text) {
     return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
     return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
 }
 }
 
 
+// This test verifies that specific subnet can be retrieved by specifying
+// subnet identifier or subnet prefix.
+TEST(CfgSubnets6Test, getSpecificSubnet) {
+    CfgSubnets6 cfg;
+
+    // Create 3 subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4,
+                                   SubnetID(5)));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4,
+                                   SubnetID(8)));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4,
+                                   SubnetID(10)));
+
+    // Store the subnets in a vector to make it possible to loop over
+    // all configured subnets.
+    std::vector<Subnet6Ptr> subnets;
+    subnets.push_back(subnet1);
+    subnets.push_back(subnet2);
+    subnets.push_back(subnet3);
+
+    // Add all subnets to the configuration.
+    for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); ++subnet) {
+        ASSERT_NO_THROW(cfg.add(*subnet)) << "failed to add subnet with id: "
+            << (*subnet)->getID();
+    }
+
+    // Iterate over all subnets and make sure they can be retrieved by
+    // subnet identifier.
+    for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+        ConstSubnet6Ptr subnet_returned = cfg.getBySubnetId((*subnet)->getID());
+        ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+            << (*subnet)->getID();
+        EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+        EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+    }
+
+    // Repeat the previous test, but this time retrieve subnets by their
+    // prefixes.
+    for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+        ConstSubnet6Ptr subnet_returned = cfg.getByPrefix((*subnet)->toText());
+        ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+            << (*subnet)->getID();
+        EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+        EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+    }
+
+    // Make sure that null pointers are returned for non-existing subnets.
+    EXPECT_FALSE(cfg.getBySubnetId(SubnetID(123)));
+    EXPECT_FALSE(cfg.getByPrefix("3000::/16"));
+}
+
 // This test checks that the subnet can be selected using a relay agent's
 // This test checks that the subnet can be selected using a relay agent's
 // link address.
 // link address.
 TEST(CfgSubnets6Test, selectSubnetByRelayAddress) {
 TEST(CfgSubnets6Test, selectSubnetByRelayAddress) {

+ 1 - 2
src/lib/dhcpsrv/tests/srv_config_unittest.cc

@@ -51,10 +51,10 @@ public:
             test_subnets4_.push_back(subnet);
             test_subnets4_.push_back(subnet);
         }
         }
         // Create IPv6 subnets.
         // Create IPv6 subnets.
+        IOAddress prefix("2001:db8:1::0");
         for (int i = 0; i < TEST_SUBNETS_NUM; ++i) {
         for (int i = 0; i < TEST_SUBNETS_NUM; ++i) {
             // This is a base prefix. All other prefixes will be created by
             // This is a base prefix. All other prefixes will be created by
             // modifying this one.
             // modifying this one.
-            IOAddress prefix("2001:db8:1::0");
             std::vector<uint8_t> prefix_bytes = prefix.toBytes();
             std::vector<uint8_t> prefix_bytes = prefix.toBytes();
             // Modify 5th byte of the prefix, so 2001:db8:1::0 becomes
             // Modify 5th byte of the prefix, so 2001:db8:1::0 becomes
             // 2001:db8:2::0 etc.
             // 2001:db8:2::0 etc.
@@ -239,7 +239,6 @@ TEST_F(SrvConfigTest, summarySubnets) {
     addSubnet6(1);
     addSubnet6(1);
     EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 2",
     EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 2",
               conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
               conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
-
 }
 }
 
 
 // Verifies that we can get and set the client class dictionary
 // Verifies that we can get and set the client class dictionary