]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#413,!288] kea-dhcp6 now uses subnets from config backends
authorThomas Markwalder <tmark@isc.org>
Mon, 1 Apr 2019 12:36:22 +0000 (08:36 -0400)
committerThomas Markwalder <tmark@isc.org>
Wed, 10 Apr 2019 17:40:49 +0000 (13:40 -0400)
src/bin/dhcp6/tests/config_backend_unittest.cc
    TEST_F(Dhcp6CBTest, mergeSubnets) - updated and enabled

src/lib/dhcpsrv/cfg_subnets6.*
    CfgSubnets6::merge() - new method

src/lib/dhcpsrv/srv_config.cc
    SrvConfig::merge6() - now merges subnets

src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
    minor cleanup

src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
   checkMergedSubnet() - new function
   TEST(CfgSubnets6Test, mergeSubnets)  - new test

src/bin/dhcp6/tests/config_backend_unittest.cc
src/lib/dhcpsrv/cfg_subnets6.cc
src/lib/dhcpsrv/cfg_subnets6.h
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc

index b7108e522ebbff60bd6859931ca0852f3966452b..00a8da44cf92c0facb3671d2da47715fbde20d3f 100644 (file)
@@ -424,7 +424,7 @@ TEST_F(Dhcp6CBTest, mergeSharedNetworks) {
 
 // This test verifies that externally configured subnets are
 // merged correctly into staging configuration.
-TEST_F(Dhcp6CBTest, DISABLED_mergeSubnets) {
+TEST_F(Dhcp6CBTest, mergeSubnets) {
     string base_config =
         "{ \n"
         "    \"interfaces-config\": { \n"
@@ -444,15 +444,15 @@ TEST_F(Dhcp6CBTest, DISABLED_mergeSubnets) {
         "   \"subnet6\": [ \n"
         "   { \n"
         "       \"id\": 2,\n"
-        "       \"subnet\": \"192.0.3.0/24\" \n"
+        "       \"subnet\": \"2001:2::/64\" \n"
         "   } ]\n"
         "} \n";
 
     extractConfig(base_config);
 
     // Make a few subnets
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("192.0.2.0"), 26, 1, 2, 3, SubnetID(1)));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("192.0.4.0"), 26, 1, 2, 3, SubnetID(3)));
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:1::"), 64, 1, 2, 100, 100, SubnetID(1)));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:3::"), 64, 1, 2, 100, 100, SubnetID(3)));
 
     // Add subnet1 to db1 and subnet3 to db2
     db1_->createUpdateSubnet6(ServerSelector::ALL(), subnet1);
index b087ef4f59723baa3e719c2edbf3db449b8d9a2d..3167e3b7b459be4d2a9fd31ab4c1010289b51f46 100644 (file)
@@ -63,6 +63,79 @@ CfgSubnets6::del(const SubnetID& subnet_id) {
         .arg(subnet->toText());
 }
 
+void
+CfgSubnets6::merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks6Ptr networks,
+                   CfgSubnets6& other) {
+    auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+
+    // Iterate over the subnets to be merged. They will replace the existing
+    // subnets with the same id. All new subnets will be inserted into the
+    // configuration into which we're merging.
+    auto other_subnets = other.getAll();
+    for (auto other_subnet = other_subnets->begin();
+         other_subnet != other_subnets->end();
+         ++other_subnet) {
+
+        // Check if there is a subnet with the same ID.
+        auto subnet_it = index.find((*other_subnet)->getID());
+        if (subnet_it != index.end()) {
+
+            // Subnet found.
+            auto existing_subnet = *subnet_it;
+
+            // If the existing subnet and other subnet
+            // are the same instance skip it.
+            if (existing_subnet == *other_subnet) {
+                continue;
+            }
+
+            // We're going to replace the existing subnet with the other
+            // version. If it belongs to a shared network, we need
+            // remove it from that network.
+            SharedNetwork6Ptr network;
+            existing_subnet->getSharedNetwork(network);
+            if (network) {
+                network->del(existing_subnet->getID());
+            }
+
+            // Now we remove the existing subnet.
+            index.erase(subnet_it);
+        }
+
+        // Create the subnet's options based on the given definitions.
+        (*other_subnet)->getCfgOption()->createOptions(cfg_def);
+
+        // Create the options for pool based on the given definitions.
+        for (auto pool : (*other_subnet)->getPoolsWritable(Lease::TYPE_NA)) {
+            pool->getCfgOption()->createOptions(cfg_def);
+        }
+
+        for (auto pool : (*other_subnet)->getPoolsWritable(Lease::TYPE_PD)) {
+            pool->getCfgOption()->createOptions(cfg_def);
+        }
+
+        // Add the "other" subnet to the our collection of subnets.
+        subnets_.push_back(*other_subnet);
+
+        // If it belongs to a shared network, find the network and
+        // add the subnet to it
+        std::string network_name = (*other_subnet)->getSharedNetworkName();
+        if (!network_name.empty()) {
+            SharedNetwork6Ptr network = networks->getByName(network_name);
+            if (network) {
+                network->add(*other_subnet);
+            } else {
+                // This implies the shared-network collection we were given
+                // is out of sync with the subnets we were given.
+                isc_throw(InvalidOperation, "Cannot assign subnet ID of "
+                          << (*other_subnet)->getID()
+                          << " to shared network: " << network_name
+                          << ", network does not exist");
+            }
+        }
+    }
+}
+
 ConstSubnet6Ptr
 CfgSubnets6::getBySubnetId(const SubnetID& subnet_id) const {
     const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
index ce3e1dd9f402f7c38074f147a7397bafc99f507d..caf98e4ac30ee9a0abe69d987f8bb3ddf84fd2c8 100644 (file)
@@ -11,6 +11,7 @@
 #include <dhcp/option.h>
 #include <dhcp/pkt6.h>
 #include <cc/cfg_to_element.h>
+#include <dhcpsrv/cfg_shared_networks.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_selector.h>
@@ -55,6 +56,50 @@ public:
     /// @throw isc::BadValue if such subnet doesn't exist.
     void del(const SubnetID& subnet_id);
 
+    /// @brief Merges specified subnet configuration into this configuration.
+    ///
+    /// This method merges subnets from the @c other configuration into this
+    /// configuration. The general rule is that existing subnets are replaced
+    /// by the subnets from @c other. If there is no corresponding subnet in
+    /// this configuration the subnet from @c other configuration is inserted.
+    ///
+    /// The complexity of the merge process stems from the associations between
+    /// the subnets and shared networks.  It is assumed that subnets in @c other
+    /// are the authority on their shared network assignments. It is also
+    /// assumed that @ networks is the list of shared networks that should be
+    /// used in making assignments.  The general concept is that the overarching
+    /// merge process will first merge shared networks and then pass that list
+    /// of networks into this method. Subnets from @c other are then merged
+    /// into this configuration as follows:
+    ///
+    /// For each subnet in @c other:
+    ///
+    /// - If a subnet of the same ID already exists in this configuration:
+    ///    -# If it belongs to a shared network, remove it from that network
+    ///    -# Remove the subnet from this configuration and discard it
+    ///
+    /// - Create the subnet's option instance, as well as any options
+    ///   that belong to any of the subnet's pools.
+    /// - Add the subnet from @c other to this configuration.
+    /// - If that subnet is associated to shared network, find that network
+    ///   in @ networks and add that subnet to it.
+    ///
+    /// @warning The merge operation affects the @c other configuration.
+    /// Therefore, the caller must not rely on the data held in the @c other
+    /// object after the call to @c merge. Also, the data held in @c other must
+    /// not be modified after the call to @c merge because it may affect the
+    /// merged configuration.
+    ///
+    /// @param cfg_def set of of user-defined option definitions to use
+    /// when creating option instances.
+    /// @param networks collection of shared networks that to which assignments
+    /// should be added. In other words, the list of shared networks that belong
+    /// to the same SrvConfig instance we are merging into.
+    /// @param other the subnet configuration to be merged into this
+    /// configuration.
+    void merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks6Ptr networks,
+               CfgSubnets6& other);
+
     /// @brief Returns pointer to the collection of all IPv6 subnets.
     ///
     /// This is used in a hook (subnet6_select), where the hook is able
index 37b8a12bdb552d65815a2021977df07ca0f3b4f3..a4d3b59a61d661f93c05ab3e714fd70a285f26da 100644 (file)
@@ -205,11 +205,9 @@ SrvConfig::merge6(SrvConfig& other) {
     // Merge shared networks.
     cfg_shared_networks6_->merge(cfg_option_def_, *(other.getCfgSharedNetworks6()));
 
-#if 0
     // Merge subnets.
-    cfg_subnets6_->merge(cfg_option_def_, getCfgSharedNetworks4(),
+    cfg_subnets6_->merge(cfg_option_def_, getCfgSharedNetworks6(),
                          *(other.getCfgSubnets6()));
-#endif
 
     /// @todo merge other parts of the configuration here.
 }
index e21b9bb33a02010dffa3f1325cac94fe51c6cedc..600adce08bbaab3a601546a55969deaf4945d82d 100644 (file)
@@ -34,9 +34,16 @@ using namespace isc::test;
 
 namespace {
 
+/// @brief Verifies that a set of subnets contains a given a subnet
+///
+/// @param cfg_subnets set of sunbets in which to look
+/// @param prefix prefix of the target subnet
+/// @param exp_subnet_id expected id of the target subnet
+/// @param exp_valid expected valid lifetime of the subnet
+/// @param exp_network  pointer to the subnet's shared-network (if one)
 void checkMergedSubnet(CfgSubnets4& cfg_subnets,
-                       const SubnetID exp_subnet_id,
                        const std::string& prefix,
+                       const SubnetID exp_subnet_id,
                        int exp_valid,
                        SharedNetwork4Ptr exp_network) {
     // Look for the network by prefix.
@@ -190,14 +197,14 @@ TEST(CfgSubnets4Test, mergeSubnets) {
     ASSERT_EQ(4, cfg_to.getAll()->size());
 
     // Should be no changes to the configuration.
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(1),
-                                              "192.0.1.0/26", 100, shared_network1));
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(2),
-                                              "192.0.2.0/26", 100, shared_network2));
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(3),
-                                              "192.0.3.0/26", 100, no_network));
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(4),
-                                              "192.0.4.0/26", 100, shared_network2));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.1.0/26",
+                                              SubnetID(1), 100, shared_network1));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.2.0/26",
+                                              SubnetID(2), 100, shared_network2));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.3.0/26",
+                                              SubnetID(3), 100, no_network));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.4.0/26",
+                                              SubnetID(4), 100, shared_network2));
 
     // Fill cfg_from configuration with subnets.
     // subnet 1b updates subnet 1 but leaves it in network 1
@@ -259,8 +266,8 @@ TEST(CfgSubnets4Test, mergeSubnets) {
     ASSERT_EQ(5, cfg_to.getAll()->size());
 
     // The subnet1 should be replaced by subnet1b.
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(1),
-                                              "192.0.1.0/26", 400, shared_network1));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.1.0/26",
+                                              SubnetID(1), 400, shared_network1));
 
     // Let's verify that our option is there and populated correctly.
     auto subnet = cfg_to.getByPrefix("192.0.1.0/26");
@@ -271,12 +278,12 @@ TEST(CfgSubnets4Test, mergeSubnets) {
     EXPECT_EQ("Yay!", opstr->getValue());
 
     // The subnet2 should not be affected because it was not present.
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(2),
-                                              "192.0.2.0/26", 100, shared_network2));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.2.0/26",
+                                              SubnetID(2), 100, shared_network2));
 
     // subnet3 should be replaced by subnet3b and no longer assigned to a network.
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(3),
-                                              "192.0.3.0/26", 500, no_network));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.3.0/26",
+                                              SubnetID(3), 500, no_network));
     // Let's verify that our option is there and populated correctly.
     subnet = cfg_to.getByPrefix("192.0.3.0/26");
     desc = subnet->getCfgOption()->get("isc", 1);
@@ -286,12 +293,12 @@ TEST(CfgSubnets4Test, mergeSubnets) {
     EXPECT_EQ("Team!", opstr->getValue());
 
     // subnet4 should be replaced by subnet4b and moved to network1.
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(4),
-                                              "192.0.4.0/26", 500, shared_network1));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.4.0/26",
+                                              SubnetID(4), 500, shared_network1));
 
     // subnet5 should have been added to configuration.
-    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(5),
-                                              "192.0.5.0/26", 300, shared_network2));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.5.0/26",
+                                              SubnetID(5), 300, shared_network2));
 
     // Let's verify that both pools have the proper options.
     subnet = cfg_to.getByPrefix("192.0.5.0/26");
index c411abf1d00b6c3c2e4b71ca2c7350df499ad61a..9d631039daf3df8c297b08d3dd5256b0217e63bd 100644 (file)
@@ -8,6 +8,8 @@
 #include <dhcp/classify.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfg_shared_networks.h>
 #include <dhcpsrv/cfg_subnets6.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
@@ -32,6 +34,39 @@ generateInterfaceId(const std::string& text) {
     return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
 }
 
+/// @brief Verifies that a set of subnets contains a given a subnet
+///
+/// @param cfg_subnets set of sunbets in which to look
+/// @param exp_subnet_id expected id of the target subnet
+/// @param prefix prefix of the target subnet
+/// @param exp_valid expected valid lifetime of the subnet
+/// @param exp_network  pointer to the subnet's shared-network (if one)
+void checkMergedSubnet(CfgSubnets6& cfg_subnets,
+                       const std::string& prefix,
+                       const SubnetID exp_subnet_id,
+                       int exp_valid,
+                       SharedNetwork6Ptr exp_network) {
+    // Look for the network by prefix.
+    auto subnet = cfg_subnets.getByPrefix(prefix);
+    ASSERT_TRUE(subnet) << "subnet: " << prefix << " not found";
+
+    // Make sure we have the one we expect.
+    ASSERT_EQ(exp_subnet_id, subnet->getID()) << "subnet ID is wrong";
+    ASSERT_EQ(exp_valid, subnet->getValid()) << "subnetID:"
+              << subnet->getID() << ", subnet valid time is wrong";
+
+    SharedNetwork6Ptr shared_network;
+    subnet->getSharedNetwork(shared_network);
+    if (exp_network) {
+        ASSERT_TRUE(shared_network)
+            << " expected network: " << exp_network->getName() << " not found";
+        ASSERT_TRUE(shared_network == exp_network) << " networks do no match";
+    } else {
+        ASSERT_FALSE(shared_network) << " unexpected network assignment: "
+            << shared_network->getName();
+    }
+}
+
 // This test verifies that specific subnet can be retrieved by specifying
 // subnet identifier or subnet prefix.
 TEST(CfgSubnets6Test, getSpecificSubnet) {
@@ -634,4 +669,171 @@ TEST(CfgSubnets6Test, getSubnet) {
     EXPECT_EQ(Subnet6Ptr(), cfg.getSubnet(400)); // no such subnet
 }
 
+// This test verifies that subnets configuration is properly merged.
+TEST(CfgSubnets6Test, mergeSubnets) {
+    // Create custom options dictionary for testing merge. We're keeping it
+    // simple because they are more rigorous tests elsewhere.
+    CfgOptionDefPtr cfg_def(new CfgOptionDef());
+    cfg_def->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "string"))), "isc");
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:1::"),
+                                   64, 1, 2, 100, 100, SubnetID(1)));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:2::"),
+                                   64, 1, 2, 100, 100, SubnetID(2)));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:3::"),
+                                   64, 1, 2, 100, 100, SubnetID(3)));
+    Subnet6Ptr subnet4(new Subnet6(IOAddress("2001:4::"),
+                                   64, 1, 2, 100, 100, SubnetID(4)));
+
+    // Create the "existing" list of shared networks
+    CfgSharedNetworks6Ptr networks(new CfgSharedNetworks6());
+    SharedNetwork6Ptr shared_network1(new SharedNetwork6("shared-network1"));
+    networks->add(shared_network1);
+    SharedNetwork6Ptr shared_network2(new SharedNetwork6("shared-network2"));
+    networks->add(shared_network2);
+
+    // Empty network pointer.
+    SharedNetwork6Ptr no_network;
+
+    // Add Subnets 1, 2 and 4 to shared networks.
+    ASSERT_NO_THROW(shared_network1->add(subnet1));
+    ASSERT_NO_THROW(shared_network2->add(subnet2));
+    ASSERT_NO_THROW(shared_network2->add(subnet4));
+
+    // Create our "existing" configured subnets.
+    CfgSubnets6 cfg_to;
+    ASSERT_NO_THROW(cfg_to.add(subnet1));
+    ASSERT_NO_THROW(cfg_to.add(subnet2));
+    ASSERT_NO_THROW(cfg_to.add(subnet3));
+    ASSERT_NO_THROW(cfg_to.add(subnet4));
+
+    // Merge in an "empty" config. Should have the original config,
+    // still intact.
+    CfgSubnets6 cfg_from;
+    ASSERT_NO_THROW(cfg_to.merge(cfg_def, networks, cfg_from));
+
+    // We should have all four subnets, with no changes.
+    ASSERT_EQ(4, cfg_to.getAll()->size());
+
+    // Should be no changes to the configuration.
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:1::/64",
+                                              SubnetID(1), 100, shared_network1));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:2::/64",
+                                              SubnetID(2), 100, shared_network2));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:3::/64",
+                                              SubnetID(3), 100, no_network));
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:4::/64",
+                                              SubnetID(4), 100, shared_network2));
+
+    // Fill cfg_from configuration with subnets.
+    // subnet 1b updates subnet 1 but leaves it in network 1
+    Subnet6Ptr subnet1b(new Subnet6(IOAddress("2001:1::"),
+                                   64, 2, 3, 100, 400, SubnetID(1)));
+    subnet1b->setSharedNetworkName("shared-network1");
+
+    // Add generic option 1 to subnet 1b.
+    std::string value("Yay!");
+    OptionPtr option(new Option(Option::V6, 1));
+    option->setData(value.begin(), value.end());
+    ASSERT_NO_THROW(subnet1b->getCfgOption()->add(option, false, "isc"));
+
+    // subnet 3b updates subnet 3 and removes it from network 2
+    Subnet6Ptr subnet3b(new Subnet6(IOAddress("2001:3::"),
+                                   64, 3, 4, 100, 500, SubnetID(3)));
+
+    // Now Add generic option 1 to subnet 3b.
+    value = "Team!";
+    option.reset(new Option(Option::V6, 1));
+    option->setData(value.begin(), value.end());
+    ASSERT_NO_THROW(subnet3b->getCfgOption()->add(option, false, "isc"));
+
+    // subnet 4b updates subnet 4 and moves it from network2 to network 1
+    Subnet6Ptr subnet4b(new Subnet6(IOAddress("2001:4::"),
+                                   64, 3, 4, 100, 500, SubnetID(4)));
+    subnet4b->setSharedNetworkName("shared-network1");
+
+    // subnet 5 is new and belongs to network 2
+    // Has two pools both with an option 1
+    Subnet6Ptr subnet5(new Subnet6(IOAddress("2001:5::"),
+                                   64, 1, 2, 100, 300, SubnetID(5)));
+    subnet5->setSharedNetworkName("shared-network2");
+
+    // Add pool 1
+    Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:5::10"), IOAddress("2001:5::20")));
+    value = "POOLS";
+    option.reset(new Option(Option::V6, 1));
+    option->setData(value.begin(), value.end());
+    ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "isc"));
+    subnet5->addPool(pool);
+
+    // Add pool 2
+    pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("2001:6::"), 64));
+    value ="RULE!";
+    option.reset(new Option(Option::V6, 1));
+    option->setData(value.begin(), value.end());
+    ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "isc"));
+    subnet5->addPool(pool);
+
+    // Add subnets to the merge from config.
+    ASSERT_NO_THROW(cfg_from.add(subnet1b));
+    ASSERT_NO_THROW(cfg_from.add(subnet3b));
+    ASSERT_NO_THROW(cfg_from.add(subnet4b));
+    ASSERT_NO_THROW(cfg_from.add(subnet5));
+
+    // Merge again.
+    ASSERT_NO_THROW(cfg_to.merge(cfg_def, networks, cfg_from));
+    ASSERT_EQ(5, cfg_to.getAll()->size());
+
+    // The subnet1 should be replaced by subnet1b.
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:1::/64",
+                                              SubnetID(1), 400, shared_network1));
+
+    // Let's verify that our option is there and populated correctly.
+    auto subnet = cfg_to.getByPrefix("2001:1::/64");
+    auto desc = subnet->getCfgOption()->get("isc", 1);
+    ASSERT_TRUE(desc.option_);
+    OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+    ASSERT_TRUE(opstr);
+    EXPECT_EQ("Yay!", opstr->getValue());
+
+    // The subnet2 should not be affected because it was not present.
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:2::/64",
+                                              SubnetID(2), 100, shared_network2));
+
+    // subnet3 should be replaced by subnet3b and no longer assigned to a network.
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:3::/64",
+                                              SubnetID(3), 500, no_network));
+    // Let's verify that our option is there and populated correctly.
+    subnet = cfg_to.getByPrefix("2001:3::/64");
+    desc = subnet->getCfgOption()->get("isc", 1);
+    ASSERT_TRUE(desc.option_);
+    opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+    ASSERT_TRUE(opstr);
+    EXPECT_EQ("Team!", opstr->getValue());
+
+    // subnet4 should be replaced by subnet4b and moved to network1.
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:4::/64",
+                                              SubnetID(4), 500, shared_network1));
+
+    // subnet5 should have been added to configuration.
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:5::/64",
+                                              SubnetID(5), 300, shared_network2));
+
+    // Let's verify that both pools have the proper options.
+    subnet = cfg_to.getByPrefix("2001:5::/64");
+    const PoolPtr merged_pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:5::10"));
+    ASSERT_TRUE(merged_pool);
+    desc = merged_pool->getCfgOption()->get("isc", 1);
+    opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+    ASSERT_TRUE(opstr);
+    EXPECT_EQ("POOLS", opstr->getValue());
+
+    const PoolPtr merged_pool2 = subnet->getPool(Lease::TYPE_PD, IOAddress("2001:1::"));
+    ASSERT_TRUE(merged_pool2);
+    desc = merged_pool2->getCfgOption()->get("isc", 1);
+    opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+    ASSERT_TRUE(opstr);
+    EXPECT_EQ("RULE!", opstr->getValue());
+}
+
 } // end of anonymous namespace