From: Thomas Markwalder Date: Mon, 1 Apr 2019 12:36:22 +0000 (-0400) Subject: [#413,!288] kea-dhcp6 now uses subnets from config backends X-Git-Tag: Kea-1.6.0-beta~270^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96e2832176f384c2c450cbb5df52081fc7562e23;p=thirdparty%2Fkea.git [#413,!288] kea-dhcp6 now uses subnets from config backends 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 --- diff --git a/src/bin/dhcp6/tests/config_backend_unittest.cc b/src/bin/dhcp6/tests/config_backend_unittest.cc index b7108e522e..00a8da44cf 100644 --- a/src/bin/dhcp6/tests/config_backend_unittest.cc +++ b/src/bin/dhcp6/tests/config_backend_unittest.cc @@ -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); diff --git a/src/lib/dhcpsrv/cfg_subnets6.cc b/src/lib/dhcpsrv/cfg_subnets6.cc index b087ef4f59..3167e3b7b4 100644 --- a/src/lib/dhcpsrv/cfg_subnets6.cc +++ b/src/lib/dhcpsrv/cfg_subnets6.cc @@ -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(); + + // 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(); diff --git a/src/lib/dhcpsrv/cfg_subnets6.h b/src/lib/dhcpsrv/cfg_subnets6.h index ce3e1dd9f4..caf98e4ac3 100644 --- a/src/lib/dhcpsrv/cfg_subnets6.h +++ b/src/lib/dhcpsrv/cfg_subnets6.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -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 diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index 37b8a12bdb..a4d3b59a61 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -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. } diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc index e21b9bb33a..600adce08b 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc @@ -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"); diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc index c411abf1d0..9d631039da 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -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(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(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(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(desc.option_); + ASSERT_TRUE(opstr); + EXPECT_EQ("RULE!", opstr->getValue()); +} + } // end of anonymous namespace