]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#399,!215] kea-dhcp4 now merges in config backend shared netwokrs
authorThomas Markwalder <tmark@isc.org>
Sat, 2 Feb 2019 09:46:07 +0000 (04:46 -0500)
committerThomas Markwalder <tmark@isc.org>
Sat, 2 Feb 2019 09:46:07 +0000 (04:46 -0500)
src/lib/dhcpsrv/cfg_shared_networks.*
    CfgSharedNetworks4::merge() - new method to merge
    shared network configs

src/lib/dhcpsrv/cfg_subnets4.*
    CfgSubnets4::merge() - reworked to reflect new rules

src/lib/dhcpsrv/srv_config.cc
    SrvConfig::merge() - now merges shared networks

src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
    checkMergedSubnet() - convenience function for
    verifying subnet content

    TEST(CfgSubnets4Test, mergeSubnets) - revamped to
    reflect new network assigment rules

src/bin/dhcp4/tests/config_backend_unittest.cc
    TEST_F(Dhcp4CBTest, mergeSharedNetworks)  - enabled test

src/bin/dhcp4/tests/config_backend_unittest.cc
src/lib/dhcpsrv/cfg_shared_networks.cc
src/lib/dhcpsrv/cfg_shared_networks.h
src/lib/dhcpsrv/cfg_subnets4.cc
src/lib/dhcpsrv/cfg_subnets4.h
src/lib/dhcpsrv/shared_network.cc
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/tests/cfg_shared_networks4_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

index a5ac6a6c987738eee75fa445454e5955224e41ae..a16a8cfc560a05a892ceef230ab93058d241f33b 100644 (file)
@@ -391,7 +391,7 @@ TEST_F(Dhcp4CBTest, DISABLED_mergeOptions) {
 // This test verifies that externally configured shared-networks are
 // merged correctly into staging configuration.
 // @todo enable test when SrvConfig can merge shared networks.
-TEST_F(Dhcp4CBTest, DISABLED_mergeSharedNetworks) {
+TEST_F(Dhcp4CBTest, mergeSharedNetworks) {
     string base_config =
         "{ \n"
         "    \"interfaces-config\": { \n"
@@ -442,7 +442,7 @@ TEST_F(Dhcp4CBTest, DISABLED_mergeSharedNetworks) {
     ASSERT_TRUE(staged_network);
 
     // Subnet3, which is in db2 should not have been merged, since it is
-    // first found, first used?
+    // backend data is first found, first used.
     staged_network = networks->getByName("three");
     ASSERT_FALSE(staged_network);
 }
index 8bdbe40a7188fe2bf78224d21ed25494674aa4fb..c1558f7350a93ed0d5b9a12d8694708c180af6f1 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2019 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
@@ -19,6 +19,51 @@ CfgSharedNetworks4::hasNetworkWithServerId(const IOAddress& server_id) const {
     return (network_it != index.cend());
 }
 
+void
+CfgSharedNetworks4::merge(const CfgSharedNetworks4& other) {
+    auto& index = networks_.get<SharedNetworkNameIndexTag>();
+
+    // 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_networks = other.getAll();
+    for (auto other_network = other_networks->begin();
+         other_network != other_networks->end(); ++other_network) {
+
+        // In theory we should drop subnet assignments from "other". The
+        // idea being  those that come from the CB should not have subnets_
+        // populated.  We will quietly throw them away, just in case.
+        (*other_network)->delAll();
+
+        // Check if the other network exists in this config.
+        auto existing_network = index.find((*other_network)->getName());
+        if (existing_network != index.end()) {
+
+            // Somehow the same instance is in both, skip it.
+            if (*existing_network == *other_network) {
+                continue;
+            }
+
+            // Network exists, which means we're updating it.
+            // First we need to move its subnets to the new
+            // version of the network.
+            const Subnet4Collection* subnets = (*existing_network)->getAllSubnets();
+
+            Subnet4Collection copy_subnets(*subnets);
+            for (auto subnet = copy_subnets.cbegin(); subnet != copy_subnets.cend(); ++subnet) {
+                (*existing_network)->del((*subnet)->getID());
+                (*other_network)->add(*subnet);
+            }
+
+            // Now we discard the existing copy of the network.
+            index.erase(existing_network);
+        }
+
+        // Add the new/updated nework.
+        networks_.push_back(*other_network);
+    }
+
+}
 
 } // end of namespace isc::dhcp
 } // end of namespace isc
index 291d2bb722634e6214aaa57500da8f6a1ebbd95b..c9094623f38c52bdcf43de5a1fae922b95c31701 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2019 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
@@ -124,7 +124,32 @@ public:
     /// @return true if there is a network with a specified server identifier.
     bool hasNetworkWithServerId(const asiolink::IOAddress& server_id) const;
 
-
+    /// @brief Merges specified shared network configuration into this configuration.
+    ///
+    /// This method merges networks from the @c other configuration into this
+    /// configuration. The general rule is that existing networks are replaced
+    /// by the networks from @c other.
+    ///
+    /// For each network in @c other, do the following:
+    ///
+    /// - Any associated subnets are removed.  Shared networks retreived from
+    /// config backends, do not carry their associated subnets (if any) with them.
+    /// Subnet assignments are maintained by subnet merges.
+    /// - If a shared network of the same name,  already exists in this
+    /// configuration:
+    ///     - All of its associated subnets are movde to the "other" network
+    ///     - The existing network is removed from this configuration
+    /// - The "other" network is added
+    ///
+    /// @warning The merge operation may affect 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 other the shared network configuration to be merged into this
+    /// configuration.
+    void merge(const CfgSharedNetworks4& other);
 };
 
 /// @brief Pointer to the configuration of IPv4 shared networks.
index 6491aec905102c670afb544e281df883c8527a7d..bdcf2fe58adaac81f081c36f949a513ee3f33643 100644 (file)
@@ -56,15 +56,15 @@ CfgSubnets4::del(const ConstSubnet4Ptr& subnet) {
 }
 
 void
-CfgSubnets4::merge(const CfgSubnets4& other) {
+CfgSubnets4::merge(CfgSharedNetworks4Ptr networks,
+                   const CfgSubnets4& 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();
+    for (auto other_subnet = other_subnets->begin(); other_subnet != other_subnets->end();
          ++other_subnet) {
 
         // Check if there is a subnet with the same ID.
@@ -72,55 +72,46 @@ CfgSubnets4::merge(const CfgSubnets4& other) {
         if (subnet_it != index.end()) {
 
             // Subnet found.
-            auto subnet = *subnet_it;
+            auto existing_subnet = *subnet_it;
 
-            // Continue if the merged and existing subnets are the same instance.
-            if (subnet == *other_subnet) {
+            // Continue if the existing subnet and other subnet
+            // are the same instance skip it.
+            if (existing_subnet == *other_subnet) {
                 continue;
             }
 
-            // If the merged subnet belongs to a shared network we need to
-            // discard this shared network so as it is merged into the existing
-            // shared network or left not unassigned if the existing subnet
-            // is unassigned.
-            SharedNetwork4Ptr other_network;
-            (*other_subnet)->getSharedNetwork(other_network);
-            if (other_network) {
-                other_network->del((*other_subnet)->getID());
-            }
-
-            // Check if the subnet belongs to a shared network.
+            // 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.
             SharedNetwork4Ptr network;
-            subnet->getSharedNetwork(network);
+            existing_subnet->getSharedNetwork(network);
             if (network) {
-                // The subnet belongs to a shared network. The shared network
-                // instance holds a pointer to the subnet so we need to remove
-                // the existing subnet from the shared network it belongs to.
-                network->del(subnet->getID());
-
-                // The new subnet instance must be added to the existing shared
-                // network.
-                network->add(*other_subnet);
+                network->del(existing_subnet->getID());
             }
 
-            // The existing subnet may now be removed.
+            // Now we remove the existing subnet.
             index.erase(subnet_it);
         }
-    }
 
-    // Make another pass over the merged subnets to add them. Any existing
-    // instances with the same IDs have been removed.
-    for (auto other_subnet = other_subnets->begin();
-         other_subnet != other_subnets->end();
-         ++other_subnet) {
+        // Add the "other" subnet to the our collection of subnets.
+        subnets_.push_back(*other_subnet);
 
-        // Continue if the merged and existing subnets are the same instance.
-        auto subnet_it = index.find((*other_subnet)->getID());
-        if ((subnet_it != index.end()) && ((*subnet_it) == (*other_subnet))) {
-            continue;
+        // 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()) {
+            SharedNetwork4Ptr 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");
+            }
         }
-
-        subnets_.push_back(*other_subnet);
     }
 }
 
index de15add4c14df92a054aa69e0be562948c37e256..762d8637f7033ce65106fe4d9b8a0689289b1d26 100644 (file)
@@ -10,6 +10,7 @@
 #include <asiolink/io_address.h>
 #include <cc/cfg_to_element.h>
 #include <dhcp/pkt4.h>
+#include <dhcpsrv/cfg_shared_networks.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_selector.h>
@@ -55,30 +56,23 @@ public:
     /// 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. Although, the subnets in this
-    /// configuration are replaced by the subnets from @c other, the existing
-    /// shared networks should not be affected. The new subnets must be
-    /// inserted into the exsiting shared networks. The @c CfgSharedNetworks4
-    /// is responsible for merging the shared networks and this merge must
-    /// be triggered before the merge of the subnets. Therefore, this method
-    /// assumes that existing shared networks have been already merged.
-    ///
-    /// These are the rules concerning the shared network associations that
-    /// this method follows:
-    /// - If there is a subnet in this configuration and it is associated with
-    ///   a shared network, the shared network is preserved and the new subnet
-    ///   instance (replacing existing one) is associated with it. The old
-    ///   subnet instance is removed from the shared network.
-    /// - If there is a subnet in this configuration and it is not associated
-    ///   with any shared network, the new subnet instance replaces the existing
-    ///   subnet instance and its association with a shared network is discarded.
-    ///   As a result, the configuration will contain new subnet instance but
-    ///   not associated with any shared network.
-    /// - If there is no subnet with the given ID, the new subnet instance is
-    ///   inserted into the configuration and the association with a shared
-    ///   network (if present) will be preserved. As a result, the configuration
-    ///   will hold the instance of the new subnet with the shared network
-    ///   it originally belonged to.
+    /// 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
+    ///
+    /// - Add the subnet from 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
@@ -86,9 +80,12 @@ public:
     /// not be modified after the call to @c merge because it may affect the
     /// merged configuration.
     ///
+    /// @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(const CfgSubnets4& other);
+    void merge(CfgSharedNetworks4Ptr networks, const CfgSubnets4& other);
 
     /// @brief Returns pointer to the collection of all IPv4 subnets.
     ///
index 7dc60603b2414cdb73697cd73458fa67ca0a594e..014442fa7b5fc2961f833bc34978834caab3fd17 100644 (file)
@@ -254,18 +254,21 @@ SharedNetwork4::add(const Subnet4Ptr& subnet) {
     Impl::add(subnets_, subnet);
     // Associate the subnet with this network.
     setSharedNetwork(subnet);
+    subnet->setSharedNetworkName(name_);
 }
 
 void
 SharedNetwork4::del(const SubnetID& subnet_id) {
     Subnet4Ptr subnet = Impl::del<Subnet4Ptr>(subnets_, subnet_id);
     clearSharedNetwork(subnet);
+    subnet->setSharedNetworkName("");
 }
 
 void
 SharedNetwork4::delAll() {
     for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
         clearSharedNetwork(*subnet);
+        (*subnet)->setSharedNetworkName("");
     }
     subnets_.clear();
 }
index b676687af1ef6019deb50512c63749036579fd90..ac04646ae3c1c091bd774f8cfd632a5e2967cfe5 100644 (file)
@@ -39,7 +39,7 @@ SrvConfig::SrvConfig()
       decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0),
       d2_client_config_(new D2ClientConfig()),
       configured_globals_(Element::createMap()),
-      cfg_consist_(new CfgConsistency()), 
+      cfg_consist_(new CfgConsistency()),
       server_tag_("") {
 }
 
@@ -162,10 +162,18 @@ SrvConfig::merge(const ConfigBase& other) {
     ConfigBase::merge(other);
 
     try {
-        /// @todo merge other parts of the configuration here.
-
         const SrvConfig& other_srv_config = dynamic_cast<const SrvConfig&>(other);
-        cfg_subnets4_->merge(*other_srv_config.getCfgSubnets4());
+
+        /// We merge objects in order of dependency (real or theoretical).
+        /// @todo merge globals
+        /// @todo merge option defs
+        /// @todo merge options
+
+        // Merge shared networks.
+        cfg_shared_networks4_->merge(*(other_srv_config.getCfgSharedNetworks4()));
+
+        /// Merge subnets.
+        cfg_subnets4_->merge(getCfgSharedNetworks4(), *(other_srv_config.getCfgSubnets4()));
 
         /// @todo merge other parts of the configuration here.
 
index dadf6b34ac4d7db286877daf1de8298ecd4b0d9a..918f46e278924a982b2a671327ae0f6195ea02ad 100644 (file)
@@ -17,6 +17,20 @@ using namespace asiolink;
 
 namespace {
 
+void checkMergedNetwork(const CfgSharedNetworks4& networks, const std::string& name,
+                       const Triplet<uint32_t>& exp_valid,
+                       const std::vector<SubnetID>& exp_subnets) {
+    auto network = networks.getByName(name);
+    ASSERT_TRUE(network) << "expected network: " << name << " not found";
+    ASSERT_EQ(exp_valid, network->getValid()) << " network valid lifetime wrong";
+    const Subnet4Collection* subnets = network->getAllSubnets();
+    ASSERT_EQ(exp_subnets.size(), subnets->size()) << " wrong number of subnets";
+    for (auto exp_id : exp_subnets) {
+        ASSERT_TRUE(network->getSubnet(exp_id))
+                    << " did not find expected subnet: " << exp_id;
+    }
+}
+
 // This test verifies that shared networks can be added to the configruation
 // and retrieved by name.
 TEST(CfgSharedNetworks4Test, getByName) {
@@ -158,4 +172,88 @@ TEST(CfgSharedNetworks4Test, unparse) {
     test::runToElementTest<CfgSharedNetworks4>(expected, cfg);
 }
 
+// This test verifies that shared-network configurations are properly merged.
+TEST(CfgSharedNetworks4Test, mergeNetworks) {
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"),
+                                   26, 1, 2, 100, SubnetID(1)));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"),
+                                   26, 1, 2, 100, SubnetID(2)));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"),
+                                   26, 1, 2, 100, SubnetID(3)));
+    Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.4.0"),
+                                   26, 1, 2, 100, SubnetID(4)));
+
+    // Create network1 and add two subnets to it
+    SharedNetwork4Ptr network1(new SharedNetwork4("network1"));
+    network1->setValid(Triplet<uint32_t>(100));
+    ASSERT_NO_THROW(network1->add(subnet1));
+    ASSERT_NO_THROW(network1->add(subnet2));
+
+    // Create network2 with no subnets.
+    SharedNetwork4Ptr network2(new SharedNetwork4("network2"));
+    network2->setValid(Triplet<uint32_t>(200));
+
+    // Create network3 with one subnets.
+    SharedNetwork4Ptr network3(new SharedNetwork4("network3"));
+    network3->setValid(Triplet<uint32_t>(300));
+    ASSERT_NO_THROW(network3->add(subnet3));
+
+    // Create our "existing" configured networks.
+    // Add all three networks to the existing config.
+    CfgSharedNetworks4 cfg_to;
+    ASSERT_NO_THROW(cfg_to.add(network1));
+    ASSERT_NO_THROW(cfg_to.add(network2));
+    ASSERT_NO_THROW(cfg_to.add(network3));
+
+    // Merge in an "empty" config. Should have the original config, still intact.
+    CfgSharedNetworks4 cfg_from;
+    ASSERT_NO_THROW(cfg_to.merge(cfg_from));
+
+    ASSERT_EQ(3, cfg_to.getAll()->size());
+    ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network1", Triplet<uint32_t>(100),
+                                               std::vector<SubnetID>{SubnetID(1), SubnetID(2)}));
+
+    ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network2", Triplet<uint32_t>(200),
+                                               std::vector<SubnetID>()));
+
+    ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network3", Triplet<uint32_t>(300),
+                                               std::vector<SubnetID>{SubnetID(3)}));
+
+    // Create network1b, this is an "update" of network1
+    // We'll double the valid time and add subnet4 to it
+    SharedNetwork4Ptr network1b(new SharedNetwork4("network1"));
+    network1b->setValid(Triplet<uint32_t>(200));
+    ASSERT_NO_THROW(network1b->add(subnet4));
+
+    // Network2 we will not touch.
+
+    // Create network3b, this is an "update" of network3.
+    // We'll double it's valid time, but leave off the subnet.
+    SharedNetwork4Ptr network3b(new SharedNetwork4("network3"));
+    network3b->setValid(Triplet<uint32_t>(600));
+
+    // Create our "existing" configured networks.
+    ASSERT_NO_THROW(cfg_from.add(network1b));
+    ASSERT_NO_THROW(cfg_from.add(network3b));
+
+    ASSERT_NO_THROW(cfg_to.merge(cfg_from));
+
+    // Should still have 3 networks.
+
+    // Network1 should have doubled its valid life time but still only have
+    // the orignal two subnets.  Merge should discard assocations on CB
+    // subnets and preserve the associations from existing config.
+    ASSERT_EQ(3, cfg_to.getAll()->size());
+    ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network1", Triplet<uint32_t>(200),
+                                               std::vector<SubnetID>{SubnetID(1), SubnetID(2)}));
+
+    // No changes to network2.
+    ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network2", Triplet<uint32_t>(200),
+                                               std::vector<SubnetID>()));
+
+    // Network1 should have doubled its valid life time and still subnet3.
+    ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network3", Triplet<uint32_t>(600),
+                                               std::vector<SubnetID>{SubnetID(3)}));
+}
+
 } // end of anonymous namespace
index 6c1adc0b168096b95f9ea09400018fcd1c1bc5eb..bfea1173f8cc85e59e842091d51b44772cdd3a05 100644 (file)
@@ -13,6 +13,7 @@
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/cfg_shared_networks.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
@@ -32,6 +33,31 @@ using namespace isc::test;
 
 namespace {
 
+void checkMergedSubnet(CfgSubnets4& cfg_subnets,
+                       const SubnetID exp_subnet_id,
+                       const std::string& prefix,
+                       int exp_valid,
+                       SharedNetwork4Ptr exp_network) {
+
+    // The subnet1 should be replaced by subnet4 but the shared network
+    // should not be affected.
+    auto subnet = cfg_subnets.getByPrefix(prefix);
+    ASSERT_TRUE(subnet) << "subnet: " << prefix << " not found";
+    ASSERT_EQ(exp_subnet_id, subnet->getID()) << "subnet ID is wrong";
+    ASSERT_EQ(exp_valid, subnet->getValid()) << "subnet valid time is wrong";
+
+    SharedNetwork4Ptr 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(CfgSubnets4Test, getSpecificSubnet) {
@@ -111,110 +137,109 @@ TEST(CfgSubnets4Test, deleteSubnet) {
     EXPECT_FALSE(cfg.getByPrefix("192.0.3.0/26"));
 }
 
-// This test verifies that the subnets configuration is properly merged.
+// This test verifies that subnets configuration is properly merged.
 TEST(CfgSubnets4Test, mergeSubnets) {
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"),
+                                   26, 1, 2, 100, SubnetID(1)));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"),
+                                   26, 1, 2, 100, SubnetID(2)));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"),
+                                   26, 1, 2, 100, SubnetID(3)));
+    Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.4.0"),
+                                   26, 1, 2, 100, SubnetID(4)));
+
+
+    // Create the "existing" list of shared networks
+    CfgSharedNetworks4Ptr networks(new CfgSharedNetworks4());
+    SharedNetwork4Ptr shared_network1(new SharedNetwork4("shared-network1"));
+    networks->add(shared_network1);
+    SharedNetwork4Ptr shared_network2(new SharedNetwork4("shared-network2"));
+    networks->add(shared_network2);
+
+    // Empty network pointer.
+    SharedNetwork4Ptr no_network;
+
+    // Add Subnets1,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.
     CfgSubnets4 cfg_to;
-    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
-                                   26, 1, 2, 3, SubnetID(5)));
-    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.3.0"),
-                                   26, 1, 2, 3, SubnetID(8)));
-    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.4.0"),
-                                   26, 1, 2, 3, SubnetID(10)));
     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));
 
-    SharedNetwork4Ptr shared_network1(new SharedNetwork4("shared-network1"));
-    ASSERT_NO_THROW(shared_network1->add(subnet1));
-
-    SharedNetwork4Ptr shared_network2(new SharedNetwork4("shared-network2"));
-    ASSERT_NO_THROW(shared_network2->add(subnet2));
 
+    // Merge in an "empty" config. Should have the original config,
+    // still intact.
     CfgSubnets4 cfg_from;
+    ASSERT_NO_THROW(cfg_to.merge(networks, cfg_from));
 
-    ASSERT_NO_THROW(cfg_to.merge(cfg_from));
-    ASSERT_EQ(3, cfg_to.getAll()->size());
-
-    SharedNetwork4Ptr returned_network;
-
-    // The subnet1 should not be modified and should still belong
-    // to the same shared network.
-    auto returned_subnet1 = cfg_to.getByPrefix("192.0.2.0/26");
-    ASSERT_TRUE(returned_subnet1);
-    returned_subnet1->getSharedNetwork(returned_network);
-    EXPECT_TRUE(shared_network1 == returned_network);
+    // We should have all four subnets, with no changes.
+    ASSERT_EQ(4, cfg_to.getAll()->size());
 
-    // The subnet2 should not be modified and should still belong
-    // to the same shared network.
-    auto returned_subnet2 = cfg_to.getByPrefix("192.0.3.0/26");
-    ASSERT_TRUE(returned_subnet2);
-    returned_subnet2->getSharedNetwork(returned_network);
-    EXPECT_TRUE(shared_network2 == returned_network);
 
-    // The subnet3 should not be modified.
-    auto returned_subnet3 = cfg_to.getByPrefix("192.0.4.0/26");
-    ASSERT_TRUE(returned_subnet3);
-    returned_subnet3->getSharedNetwork(returned_network);
-    EXPECT_FALSE(returned_network);
+    // 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));
 
     // Fill cfg_from configuration with subnets.
-    Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.2.0"),
-                                   26, 2, 3, 4, SubnetID(5)));
-    Subnet4Ptr subnet5(new Subnet4(IOAddress("192.0.6.0"),
-                                   26, 1, 2, 3, SubnetID(32)));
-    Subnet4Ptr subnet6(new Subnet4(IOAddress("192.0.4.0"),
-                                   26, 3, 4, 5, SubnetID(10)));
-    ASSERT_NO_THROW(cfg_from.add(subnet4));
+    // subnet 1b updates subnet 1 but leaves it in network 1
+    Subnet4Ptr subnet1b(new Subnet4(IOAddress("192.0.1.0"),
+                                   26, 2, 3, 400, SubnetID(1)));
+    subnet1b->setSharedNetworkName("shared-network1");
+
+    // subnet 3b updates subnet 3 and removes it from network 2
+    Subnet4Ptr subnet3b(new Subnet4(IOAddress("192.0.3.0"),
+                                   26, 3, 4, 500, SubnetID(3)));
+
+    // subnet 4b updates subnet 4 and moves it from network2 to network 1
+    Subnet4Ptr subnet4b(new Subnet4(IOAddress("192.0.4.0"),
+                                   26, 3, 4, 500, SubnetID(4)));
+    subnet4b->setSharedNetworkName("shared-network1");
+
+    // subnet 5 is new and belongs to network 2
+    Subnet4Ptr subnet5(new Subnet4(IOAddress("192.0.5.0"),
+                                   26, 1, 2, 300, SubnetID(5)));
+    subnet5->setSharedNetworkName("shared-network2");
+
+    // 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));
-    ASSERT_NO_THROW(cfg_from.add(subnet6));
 
-    // First two subnets belong to shared networks.
-    SharedNetwork4Ptr shared_network3(new SharedNetwork4("shared-network3"));
-    ASSERT_NO_THROW(shared_network3->add(subnet4));
+    // Merge again.
+    ASSERT_NO_THROW(cfg_to.merge(networks, cfg_from));
+    ASSERT_EQ(5, cfg_to.getAll()->size());
 
-    SharedNetwork4Ptr shared_network4(new SharedNetwork4("shared-network4"));
-    ASSERT_NO_THROW(shared_network4->add(subnet5));
+    // The subnet1 should be replaced by subnet1b.
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(1),
+                                              "192.0.1.0/26", 400, shared_network1));
 
-    SharedNetwork4Ptr shared_network5(new SharedNetwork4("shared-network5"));
-    ASSERT_NO_THROW(shared_network5->add(subnet6));
+    // 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));
 
-    // Merge again. The subnet4 and subnet6 should replace the subnet1 and
-    // subnet3.
-    ASSERT_NO_THROW(cfg_to.merge(cfg_from));
-    ASSERT_EQ(4, cfg_to.getAll()->size());
+    // 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));
 
-    returned_subnet1 = cfg_to.getByPrefix("192.0.2.0/26");
-    ASSERT_TRUE(returned_subnet1);
-    EXPECT_EQ(4, returned_subnet1->getValid());
-    // The subnet1 should be replaced by subnet4 but the shared network
-    // should not be affected.
-    returned_subnet1->getSharedNetwork(returned_network);
-    EXPECT_TRUE(shared_network1 == returned_network);
-
-    // The subnet2 should not be affected because it was not present
-    // in the cfg_from.
-    returned_subnet2 = cfg_to.getByPrefix("192.0.3.0/26");
-    ASSERT_TRUE(returned_subnet2);
-    EXPECT_EQ(3, returned_subnet2->getValid());
-    returned_subnet2->getSharedNetwork(returned_network);
-    EXPECT_TRUE(shared_network2 == returned_network);
-
-    returned_subnet3 = cfg_to.getByPrefix("192.0.4.0/26");
-    ASSERT_TRUE(returned_subnet3);
-    EXPECT_EQ(5, returned_subnet3->getValid());
-    // subnet3 should be replaced by subnet6 but the shared network
-    // should not be assigned (regardless if the subnet6 belongs to
-    // a shared network or not).
-    returned_subnet3->getSharedNetwork(returned_network);
-    EXPECT_FALSE(returned_network);
-
-    // subnet5 should be merged to the configuration.
-    auto returned_subnet5 = cfg_to.getByPrefix("192.0.6.0/26");
-    ASSERT_TRUE(returned_subnet5);
-    EXPECT_EQ(3, returned_subnet5->getValid());
-    // subnet5 shared network should be preserved.
-    returned_subnet5->getSharedNetwork(returned_network);
-    EXPECT_TRUE(shared_network4 == returned_network);
+    // 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));
+
+    // subnet5 should have been added to configuration.
+    ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, SubnetID(5),
+                                              "192.0.5.0/26", 300, shared_network2));
 }
 
 // This test verifies that it is possible to retrieve a subnet using an