// 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"
" \"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);
.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>();
#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>
/// @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
// 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.
}
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.
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
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");
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);
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");
#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>
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) {
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