diff options
Diffstat (limited to 'src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc')
-rw-r--r-- | src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc | 2510 |
1 files changed, 2510 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc new file mode 100644 index 0000000..ece583f --- /dev/null +++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc @@ -0,0 +1,2510 @@ +// Copyright (C) 2014-2023 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 +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <cc/data.h> +#include <dhcp/classify.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option_string.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/cfg_shared_networks.h> +#include <dhcpsrv/cfg_subnets6.h> +#include <dhcpsrv/iterative_allocator.h> +#include <dhcpsrv/lease_mgr_factory.h> +#include <dhcpsrv/parsers/dhcp_parsers.h> +#include <dhcpsrv/subnet.h> +#include <dhcpsrv/subnet_id.h> +#include <dhcpsrv/subnet_selector.h> +#include <dhcpsrv/cfg_hosts.h> +#include <stats/stats_mgr.h> +#include <testutils/gtest_utils.h> +#include <testutils/log_utils.h> +#include <testutils/test_to_element.h> +#include <util/doubles.h> + +#include <gtest/gtest.h> +#include <string> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using namespace isc::stats; +using namespace isc::test; +using namespace isc::util; + +namespace { + +/// @brief An allocator recording calls to @c initAfterConfigure. +class InitRecordingAllocator : public IterativeAllocator { +public: + + /// @brief Constructor. + /// + /// @param type specifies the type of allocated leases. + /// @param subnet weak pointer to the subnet owning the allocator. + InitRecordingAllocator(Lease::Type type, const WeakSubnetPtr& subnet) + : IterativeAllocator(type, subnet), callcount_(0) { + } + + /// @brief Increases the call count of this function. + /// + /// The call count can be later examined to check whether or not + /// the function was called. + virtual void initAfterConfigureInternal() { + ++callcount_; + }; + + /// @brief Call count of the @c initAllocatorsAfterConfigure. + int callcount_; +}; + +/// @brief Generates interface id option. +/// +/// @param text Interface id in a textual format. +OptionPtr +generateInterfaceId(const std::string& text) { + OptionBuffer buffer(text.begin(), text.end()); + 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 subnets 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().get()) << "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) { + CfgSubnets6 cfg; + + // Create 3 subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, + SubnetID(5))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4, + SubnetID(8))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4, + SubnetID(10))); + + // Store the subnets in a vector to make it possible to loop over + // all configured subnets. + std::vector<Subnet6Ptr> subnets; + subnets.push_back(subnet1); + subnets.push_back(subnet2); + subnets.push_back(subnet3); + + // Add all subnets to the configuration. + for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); ++subnet) { + ASSERT_NO_THROW(cfg.add(*subnet)) << "failed to add subnet with id: " + << (*subnet)->getID(); + } + + // Iterate over all subnets and make sure they can be retrieved by + // subnet identifier. + for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) { + ConstSubnet6Ptr subnet_returned = cfg.getBySubnetId((*subnet)->getID()); + ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: " + << (*subnet)->getID(); + EXPECT_EQ((*subnet)->getID(), subnet_returned->getID()); + EXPECT_EQ((*subnet)->toText(), subnet_returned->toText()); + } + + // Repeat the previous test, but this time retrieve subnets by their + // prefixes. + for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) { + ConstSubnet6Ptr subnet_returned = cfg.getByPrefix((*subnet)->toText()); + ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: " + << (*subnet)->getID(); + EXPECT_EQ((*subnet)->getID(), subnet_returned->getID()); + EXPECT_EQ((*subnet)->toText(), subnet_returned->toText()); + } + + // Make sure that null pointers are returned for non-existing subnets. + EXPECT_FALSE(cfg.getBySubnetId(SubnetID(123))); + EXPECT_FALSE(cfg.getByPrefix("3000::/16")); +} + +// This test verifies that a single subnet can be removed from the configuration. +TEST(CfgSubnets6Test, deleteSubnet) { + CfgSubnets6 cfg; + + // Create 3 subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), + 48, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), + 48, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), + 48, 1, 2, 3, 4, SubnetID(3))); + + ASSERT_NO_THROW(cfg.add(subnet1)); + ASSERT_NO_THROW(cfg.add(subnet2)); + ASSERT_NO_THROW(cfg.add(subnet3)); + + // There should be three subnets. + ASSERT_EQ(3, cfg.getAll()->size()); + // We're going to remove the subnet #2. Let's make sure it exists before + // we remove it. + ASSERT_TRUE(cfg.getByPrefix("2001:db8:2::/48")); + + // Remove the subnet and make sure it is gone. + ASSERT_NO_THROW(cfg.del(subnet2)); + ASSERT_EQ(2, cfg.getAll()->size()); + EXPECT_FALSE(cfg.getByPrefix("2001:db8:2::/48")); + + // Remove another subnet by ID. + ASSERT_NO_THROW(cfg.del(subnet1->getID())); + ASSERT_EQ(1, cfg.getAll()->size()); + EXPECT_FALSE(cfg.getByPrefix("2001:db8:1::/48")); +} + +// This test verifies that replace a subnet works as expected. +TEST(CfgSubnets6Test, replaceSubnet) { + CfgSubnets6 cfg; + + // Create 3 subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), + 48, 1, 2, 3, 100, SubnetID(10))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), + 48, 1, 2, 3, 100, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), + 48, 1, 2, 3, 100, SubnetID(13))); + + ASSERT_NO_THROW(cfg.add(subnet1)); + ASSERT_NO_THROW(cfg.add(subnet2)); + ASSERT_NO_THROW(cfg.add(subnet3)); + + // There should be three subnets. + ASSERT_EQ(3, cfg.getAll()->size()); + // We're going to replace the subnet #2. Let's make sure it exists before + // we replace it. + ASSERT_TRUE(cfg.getByPrefix("2001:db8:2::/48")); + + // Replace the subnet and make sure it was updated. + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:2::"), + 48, 10, 20, 30, 1000, SubnetID(2))); + Subnet6Ptr replaced = cfg.replace(subnet); + ASSERT_TRUE(replaced); + EXPECT_TRUE(replaced == subnet2); + ASSERT_EQ(3, cfg.getAll()->size()); + Subnet6Ptr returned = cfg.getSubnet(SubnetID(2)); + ASSERT_TRUE(returned); + EXPECT_TRUE(returned == subnet); + + // Restore. + replaced = cfg.replace(replaced); + ASSERT_TRUE(replaced); + EXPECT_TRUE(replaced == subnet); + ASSERT_EQ(3, cfg.getAll()->size()); + returned = cfg.getSubnet(SubnetID(2)); + ASSERT_TRUE(returned); + EXPECT_TRUE(returned == subnet2); + + // Prefix conflict returns null. + subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), + 48, 10, 20, 30, 1000, SubnetID(2))); + replaced = cfg.replace(subnet); + EXPECT_FALSE(replaced); + returned = cfg.getSubnet(SubnetID(2)); + ASSERT_TRUE(returned); + EXPECT_TRUE(returned == subnet2); + + // Changing prefix works even it is highly not recommended. + subnet.reset(new Subnet6(IOAddress("2001:db8:10::"), + 48, 10, 20, 30, 1000, SubnetID(2))); + replaced = cfg.replace(subnet); + ASSERT_TRUE(replaced); + EXPECT_TRUE(replaced == subnet2); + returned = cfg.getSubnet(SubnetID(2)); + ASSERT_TRUE(returned); + EXPECT_TRUE(returned == subnet); +} + +// This test checks that the subnet can be selected using a relay agent's +// link address. +TEST(CfgSubnets6Test, selectSubnetByRelayAddress) { + CfgSubnets6 cfg; + + // Let's configure 3 subnets + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), + 48, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), + 48, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), + 48, 1, 2, 3, 4, SubnetID(3))); + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + // Make sure that none of the subnets is selected when there is no relay + // information configured for them. + SubnetSelector selector; + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + + // Now specify relay information. + subnet1->addRelayAddress(IOAddress("2001:db8:ff::1")); + subnet2->addRelayAddress(IOAddress("2001:db8:ff::2")); + subnet3->addRelayAddress(IOAddress("2001:db8:ff::3")); + + // And try again. This time relay-info is there and should match. + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1"); + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2"); + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3"); + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); +} + +// This test checks that subnet can be selected using a relay agent's +// link address specified on the shared network level. +TEST(CfgSubnets6Test, selectSubnetByNetworkRelayAddress) { + // Create 2 shared networks. + SharedNetwork6Ptr network1(new SharedNetwork6("net1")); + SharedNetwork6Ptr network2(new SharedNetwork6("net2")); + SharedNetwork6Ptr network3(new SharedNetwork6("net3")); + + // Let's configure 3 subnets + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), + 48, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), + 48, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), + 48, 1, 2, 3, 4, SubnetID(3))); + + // Allow subnet class of clients to use the subnets. + subnet1->allowClientClass("subnet"); + subnet2->allowClientClass("subnet"); + subnet3->allowClientClass("subnet"); + + // Associate subnets with shared networks. + network1->add(subnet1); + network2->add(subnet2); + network3->add(subnet3); + + // Add subnets to the configuration. + CfgSubnets6 cfg; + + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + // Make sure that none of the subnets is selected when there is no relay + // information configured for them. + SubnetSelector selector; + selector.client_classes_.insert("subnet"); + + // The relays are not set for networks, so none of the subnets should + // be selected. + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + + // Now specify relay information. + network1->addRelayAddress(IOAddress("2001:db8:ff::1")); + network2->addRelayAddress(IOAddress("2001:db8:ff::2")); + network3->addRelayAddress(IOAddress("2001:db8:ff::3")); + + // And try again. This time relay-info is there and should match. + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1"); + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2"); + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3"); + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); + + // Modify the client classes associated with the first two subnets. + subnet1->allowClientClass("subnet1"); + subnet2->allowClientClass("subnet2"); + + // This time the non-matching classes should prevent selection. + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3"); + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); +} + +// This test checks that the subnet can be selected using an interface +// name associated with a asubnet. +TEST(CfgSubnets6Test, selectSubnetByInterfaceName) { + CfgSubnets6 cfg; + + // Let's create 3 subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), + 48, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), + 48, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), + 48, 1, 2, 3, 4, SubnetID(3))); + subnet1->setIface("foo"); + subnet2->setIface("bar"); + subnet3->setIface("foobar"); + + // Until subnets are added to the configuration, there should be nothing + // returned. + SubnetSelector selector; + selector.iface_name_ = "foo"; + EXPECT_FALSE(cfg.selectSubnet(selector)); + + // Add one of the subnets. + cfg.add(subnet1); + + // The subnet should be now selected for the interface name "foo". + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + + // Check that the interface name is checked even when there is + // only one subnet defined: there should be nothing returned when + // other interface name is specified. + selector.iface_name_ = "bar"; + EXPECT_FALSE(cfg.selectSubnet(selector)); + + // Add other subnets. + cfg.add(subnet2); + cfg.add(subnet3); + + // When we specify correct interface names, the subnets should be returned. + selector.iface_name_ = "foobar"; + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); + selector.iface_name_ = "bar"; + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + + // When specifying a non-existing interface the subnet should not be + // returned. + selector.iface_name_ = "xyzzy"; + EXPECT_FALSE(cfg.selectSubnet(selector)); +} + +// This test checks that the subnet can be selected using an Interface ID +// option inserted by a relay. +TEST(CfgSubnets6Test, selectSubnetByInterfaceId) { + CfgSubnets6 cfg; + + // Create 3 subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), + 48, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), + 48, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), + 48, 1, 2, 3, 4, SubnetID(3))); + + // Create Interface-id options used in subnets 1,2, and 3 + OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0"); + OptionPtr ifaceid2 = generateInterfaceId("VL32"); + // That's a strange interface-id, but this is a real life example + OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110"); + + // Bogus interface-id. + OptionPtr ifaceid_bogus = generateInterfaceId("non-existent"); + + // Assign interface ids to the respective subnets. + subnet1->setInterfaceId(ifaceid1); + subnet2->setInterfaceId(ifaceid2); + subnet3->setInterfaceId(ifaceid3); + + // There shouldn't be any subnet configured at this stage. + SubnetSelector selector; + selector.interface_id_ = ifaceid1; + // Note that some link address must be specified to indicate that it is + // a relayed message! + selector.first_relay_linkaddr_ = IOAddress("5000::1"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + + // Add one of the subnets. + cfg.add(subnet1); + + // If only one subnet has been specified, it should be returned when the + // interface id matches. But, for a different interface id there should be + // no match. + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + selector.interface_id_ = ifaceid2; + EXPECT_FALSE(cfg.selectSubnet(selector)); + + // Add other subnets. + cfg.add(subnet2); + cfg.add(subnet3); + + // Now that we have all subnets added. we should be able to retrieve them + // using appropriate interface ids. + selector.interface_id_ = ifaceid3; + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); + selector.interface_id_ = ifaceid2; + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + + // For invalid interface id, there should be nothing returned. + selector.interface_id_ = ifaceid_bogus; + EXPECT_FALSE(cfg.selectSubnet(selector)); +} + +// Test that the client classes are considered when the subnet is selected by +// the relay link address. +TEST(CfgSubnets6Test, selectSubnetByRelayAddressAndClassify) { + CfgSubnets6 cfg; + + // Let's configure 3 subnets + Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), + 48, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), + 48, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), + 48, 1, 2, 3, 4, SubnetID(3))); + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + // Let's sanity check that we can use that configuration. + SubnetSelector selector; + selector.first_relay_linkaddr_ = IOAddress("2000::123"); + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("3000::345"); + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("4000::567"); + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); + + // Client now belongs to bar class. + selector.client_classes_.insert("bar"); + + // There are no class restrictions defined, so everything should work + // as before. + selector.first_relay_linkaddr_ = IOAddress("2000::123"); + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("3000::345"); + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("4000::567"); + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); + + // Now let's add client class restrictions. + subnet1->allowClientClass("foo"); // Serve here only clients from foo class + subnet2->allowClientClass("bar"); // Serve here only clients from bar class + subnet3->allowClientClass("baz"); // Serve here only clients from baz class + + // The same check as above should result in client being served only in + // bar class, i.e. subnet2 + selector.first_relay_linkaddr_ = IOAddress("2000::123"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("3000::345"); + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("4000::567"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + + // Now let's check that client with wrong class is not supported + selector.client_classes_.clear(); + selector.client_classes_.insert("some_other_class"); + selector.first_relay_linkaddr_ = IOAddress("2000::123"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("3000::345"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("4000::567"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + + // Finally, let's check that client without any classes is not supported + selector.client_classes_.clear(); + selector.first_relay_linkaddr_ = IOAddress("2000::123"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("3000::345"); + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.first_relay_linkaddr_ = IOAddress("4000::567"); + EXPECT_FALSE(cfg.selectSubnet(selector)); +} + +// Test that client classes are considered when the subnet is selected by the +// interface name. +TEST(CfgSubnets6Test, selectSubnetByInterfaceNameAndClassify) { + CfgSubnets6 cfg; + + Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), + 48, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), + 48, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), + 48, 1, 2, 3, 4, SubnetID(3))); + subnet1->setIface("foo"); + subnet2->setIface("bar"); + subnet3->setIface("foobar"); + + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + // Now we have only one subnet, any request will be served from it + SubnetSelector selector; + selector.client_classes_.insert("bar"); + selector.iface_name_ = "foo"; + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + selector.iface_name_ = "bar"; + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.iface_name_ = "foobar"; + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); + + subnet1->allowClientClass("foo"); // Serve here only clients from foo class + subnet2->allowClientClass("bar"); // Serve here only clients from bar class + subnet3->allowClientClass("baz"); // Serve here only clients from baz class + + selector.iface_name_ = "foo"; + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.iface_name_ = "bar"; + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.iface_name_ = "foobar"; + EXPECT_FALSE(cfg.selectSubnet(selector)); +} + +// Test that client classes are considered when the interface is selected by +// the interface id. +TEST(CfgSubnets6Test, selectSubnetByInterfaceIdAndClassify) { + CfgSubnets6 cfg; + + Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), + 48, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), + 48, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), + 48, 1, 2, 3, 4, SubnetID(3))); + + // interface-id options used in subnets 1,2, and 3 + OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0"); + OptionPtr ifaceid2 = generateInterfaceId("VL32"); + // That's a strange interface-id, but this is a real life example + OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110"); + + // bogus interface-id + OptionPtr ifaceid_bogus = generateInterfaceId("non-existent"); + + subnet1->setInterfaceId(ifaceid1); + subnet2->setInterfaceId(ifaceid2); + subnet3->setInterfaceId(ifaceid3); + + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + // If we have only a single subnet and the request came from a local + // address, let's use that subnet + SubnetSelector selector; + selector.first_relay_linkaddr_ = IOAddress("5000::1"); + selector.client_classes_.insert("bar"); + selector.interface_id_ = ifaceid1; + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + selector.interface_id_ = ifaceid2; + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.interface_id_ = ifaceid3; + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); + + subnet1->allowClientClass("foo"); // Serve here only clients from foo class + subnet2->allowClientClass("bar"); // Serve here only clients from bar class + subnet3->allowClientClass("baz"); // Serve here only clients from baz class + + EXPECT_FALSE(cfg.selectSubnet(selector)); + selector.interface_id_ = ifaceid2; + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.interface_id_ = ifaceid3; + EXPECT_FALSE(cfg.selectSubnet(selector)); +} + +// Checks that detection of duplicated subnet IDs works as expected. It should +// not be possible to add two IPv6 subnets holding the same ID. +TEST(CfgSubnets6Test, duplication) { + CfgSubnets6 cfg; + + Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4, 123)); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4, 124)); + Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4, 123)); + Subnet6Ptr subnet4(new Subnet6(IOAddress("2000::1"), 48, 1, 2, 3, 4, 125)); + + ASSERT_NO_THROW(cfg.add(subnet1)); + EXPECT_NO_THROW(cfg.add(subnet2)); + // Subnet 3 has the same ID as subnet 1. It shouldn't be able to add it. + EXPECT_THROW(cfg.add(subnet3), isc::dhcp::DuplicateSubnetID); + // Subnet 4 has a similar but different subnet as subnet 1. + EXPECT_NO_THROW(cfg.add(subnet4)); +} + +// This test check if IPv6 subnets can be unparsed in a predictable way, +TEST(CfgSubnets6Test, unparseSubnet) { + CfgSubnets6 cfg; + + // Add some subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), + 48, 1, 2, 3, 4, 123)); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), + 48, 1, 2, 3, 4, 124)); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), + 48, 1, 2, 3, 4, 125)); + + OptionPtr ifaceid = generateInterfaceId("relay.eth0"); + subnet1->setInterfaceId(ifaceid); + subnet1->allowClientClass("foo"); + + subnet1->setT1Percent(0.45); + subnet1->setT2Percent(0.70); + subnet1->setCacheThreshold(0.20); + + subnet2->setIface("lo"); + subnet2->addRelayAddress(IOAddress("2001:db8:ff::2")); + subnet2->setValid(Triplet<uint32_t>(200)); + subnet2->setPreferred(Triplet<uint32_t>(100)); + subnet2->setStoreExtendedInfo(true); + subnet2->setCacheMaxAge(80); + + subnet3->setIface("eth1"); + subnet3->requireClientClass("foo"); + subnet3->requireClientClass("bar"); + subnet3->setReservationsGlobal(false); + subnet3->setReservationsInSubnet(true); + subnet3->setReservationsOutOfPool(false); + subnet3->setRapidCommit(false); + subnet3->setCalculateTeeTimes(true); + subnet3->setT1Percent(0.50); + subnet3->setT2Percent(0.65); + subnet3->setValid(Triplet<uint32_t>(100, 200, 300)); + subnet3->setPreferred(Triplet<uint32_t>(50, 100, 150)); + subnet3->setDdnsSendUpdates(true); + subnet3->setDdnsOverrideNoUpdate(true); + subnet3->setDdnsOverrideClientUpdate(true); + subnet3->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS); + subnet3->setDdnsGeneratedPrefix("prefix"); + subnet3->setDdnsQualifyingSuffix("example.com."); + subnet3->setHostnameCharSet("[^A-Z]"); + subnet3->setHostnameCharReplacement("x"); + + data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }"); + subnet1->setContext(ctx1); + data::ElementPtr ctx2 = data::Element::createMap(); + subnet2->setContext(ctx2); + + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + // Unparse + std::string expected = "[\n" + "{\n" + " \"id\": 123,\n" + " \"subnet\": \"2001:db8:1::/48\",\n" + " \"t1-percent\": 0.45," + " \"t2-percent\": 0.7," + " \"cache-threshold\": .20,\n" + " \"interface-id\": \"relay.eth0\",\n" + " \"renew-timer\": 1,\n" + " \"rebind-timer\": 2,\n" + " \"relay\": { \"ip-addresses\": [ ] },\n" + " \"preferred-lifetime\": 3,\n" + " \"min-preferred-lifetime\": 3,\n" + " \"max-preferred-lifetime\": 3,\n" + " \"valid-lifetime\": 4,\n" + " \"min-valid-lifetime\": 4,\n" + " \"max-valid-lifetime\": 4,\n" + " \"client-class\": \"foo\",\n" + " \"pools\": [ ],\n" + " \"pd-pools\": [ ],\n" + " \"option-data\": [ ],\n" + " \"user-context\": { \"comment\": \"foo\" }\n" + "},{\n" + " \"id\": 124,\n" + " \"subnet\": \"2001:db8:2::/48\",\n" + " \"interface\": \"lo\",\n" + " \"renew-timer\": 1,\n" + " \"rebind-timer\": 2,\n" + " \"relay\": { \"ip-addresses\": [ \"2001:db8:ff::2\" ] },\n" + " \"preferred-lifetime\": 100,\n" + " \"min-preferred-lifetime\": 100,\n" + " \"max-preferred-lifetime\": 100,\n" + " \"valid-lifetime\": 200,\n" + " \"min-valid-lifetime\": 200,\n" + " \"max-valid-lifetime\": 200,\n" + " \"user-context\": { },\n" + " \"pools\": [ ],\n" + " \"pd-pools\": [ ],\n" + " \"option-data\": [ ],\n" + " \"store-extended-info\": true,\n" + " \"cache-max-age\": 80\n" + "},{\n" + " \"id\": 125,\n" + " \"subnet\": \"2001:db8:3::/48\",\n" + " \"interface\": \"eth1\",\n" + " \"renew-timer\": 1,\n" + " \"rebind-timer\": 2,\n" + " \"relay\": { \"ip-addresses\": [ ] },\n" + " \"preferred-lifetime\": 100,\n" + " \"min-preferred-lifetime\": 50,\n" + " \"max-preferred-lifetime\": 150,\n" + " \"valid-lifetime\": 200,\n" + " \"min-valid-lifetime\": 100,\n" + " \"max-valid-lifetime\": 300,\n" + " \"rapid-commit\": false,\n" + " \"reservations-global\": false,\n" + " \"reservations-in-subnet\": true,\n" + " \"reservations-out-of-pool\": false,\n" + " \"pools\": [ ],\n" + " \"pd-pools\": [ ],\n" + " \"option-data\": [ ],\n" + " \"require-client-classes\": [ \"foo\", \"bar\" ],\n" + " \"calculate-tee-times\": true,\n" + " \"t1-percent\": 0.50,\n" + " \"t2-percent\": 0.65,\n" + " \"ddns-generated-prefix\": \"prefix\",\n" + " \"ddns-override-client-update\": true,\n" + " \"ddns-override-no-update\": true,\n" + " \"ddns-qualifying-suffix\": \"example.com.\",\n" + " \"ddns-replace-client-name\": \"always\",\n" + " \"ddns-send-updates\": true,\n" + " \"hostname-char-replacement\": \"x\",\n" + " \"hostname-char-set\": \"[^A-Z]\"\n" + "} ]\n"; + + runToElementTest<CfgSubnets6>(expected, cfg); +} + +// This test check if IPv6 pools can be unparsed in a predictable way, +TEST(CfgSubnets6Test, unparsePool) { + CfgSubnets6 cfg; + + // Add a subnet with pools + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), + 48, 1, 2, 3, 4, 123)); + Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8:1::100"), + IOAddress("2001:db8:1::199"))); + Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64)); + pool2->allowClientClass("bar"); + + std::string json1 = "{ \"comment\": \"foo\", \"version\": 1 }"; + data::ElementPtr ctx1 = data::Element::fromJSON(json1); + pool1->setContext(ctx1); + data::ElementPtr ctx2 = data::Element::fromJSON("{ \"foo\": \"bar\" }"); + pool2->setContext(ctx2); + pool2->requireClientClass("foo"); + + subnet->addPool(pool1); + subnet->addPool(pool2); + cfg.add(subnet); + + // Unparse + std::string expected = "[\n" + "{\n" + " \"id\": 123,\n" + " \"subnet\": \"2001:db8:1::/48\",\n" + " \"renew-timer\": 1,\n" + " \"rebind-timer\": 2,\n" + " \"relay\": { \"ip-addresses\": [ ] },\n" + " \"preferred-lifetime\": 3,\n" + " \"min-preferred-lifetime\": 3,\n" + " \"max-preferred-lifetime\": 3,\n" + " \"valid-lifetime\": 4,\n" + " \"min-valid-lifetime\": 4,\n" + " \"max-valid-lifetime\": 4,\n" + " \"pools\": [\n" + " {\n" + " \"pool\": \"2001:db8:1::100-2001:db8:1::199\",\n" + " \"user-context\": { \"version\": 1,\n" + " \"comment\": \"foo\" },\n" + " \"option-data\": [ ]\n" + " },{\n" + " \"pool\": \"2001:db8:1:1::/64\",\n" + " \"user-context\": { \"foo\": \"bar\" },\n" + " \"option-data\": [ ],\n" + " \"client-class\": \"bar\",\n" + " \"require-client-classes\": [ \"foo\" ]\n" + " }\n" + " ],\n" + " \"pd-pools\": [ ],\n" + " \"option-data\": [ ]\n" + "} ]\n"; + runToElementTest<CfgSubnets6>(expected, cfg); +} + +// This test check if IPv6 prefix delegation pools can be unparsed +// in a predictable way, +TEST(CfgSubnets6Test, unparsePdPool) { + CfgSubnets6 cfg; + + // Add a subnet with pd-pools + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), + 48, 1, 2, 3, 4, 123)); + Pool6Ptr pdpool1(new Pool6(Lease::TYPE_PD, + IOAddress("2001:db8:2::"), 48, 64)); + Pool6Ptr pdpool2(new Pool6(IOAddress("2001:db8:3::"), 48, 56, + IOAddress("2001:db8:3::"), 64)); + pdpool2->allowClientClass("bar"); + + data::ElementPtr ctx1 = data::Element::fromJSON("{ \"foo\": [ \"bar\" ] }"); + pdpool1->setContext(ctx1); + pdpool1->requireClientClass("bar"); + pdpool2->allowClientClass("bar"); + + subnet->addPool(pdpool1); + subnet->addPool(pdpool2); + cfg.add(subnet); + + // Unparse + std::string expected = "[\n" + "{\n" + " \"id\": 123,\n" + " \"subnet\": \"2001:db8:1::/48\",\n" + " \"renew-timer\": 1,\n" + " \"rebind-timer\": 2,\n" + " \"relay\": { \"ip-addresses\": [ ] },\n" + " \"preferred-lifetime\": 3,\n" + " \"min-preferred-lifetime\": 3,\n" + " \"max-preferred-lifetime\": 3,\n" + " \"valid-lifetime\": 4,\n" + " \"min-valid-lifetime\": 4,\n" + " \"max-valid-lifetime\": 4,\n" + " \"pools\": [ ],\n" + " \"pd-pools\": [\n" + " {\n" + " \"prefix\": \"2001:db8:2::\",\n" + " \"prefix-len\": 48,\n" + " \"delegated-len\": 64,\n" + " \"user-context\": { \"foo\": [ \"bar\" ] },\n" + " \"option-data\": [ ],\n" + " \"require-client-classes\": [ \"bar\" ]\n" + " },{\n" + " \"prefix\": \"2001:db8:3::\",\n" + " \"prefix-len\": 48,\n" + " \"delegated-len\": 56,\n" + " \"excluded-prefix\": \"2001:db8:3::\",\n" + " \"excluded-prefix-len\": 64,\n" + " \"option-data\": [ ],\n" + " \"client-class\": \"bar\"\n" + " }\n" + " ],\n" + " \"option-data\": [ ]\n" + "} ]\n"; + runToElementTest<CfgSubnets6>(expected, cfg); +} + +// This test verifies that it is possible to retrieve a subnet using subnet-id. +TEST(CfgSubnets6Test, getSubnet) { + CfgSubnets6 cfg; + + // Let's configure 3 subnets + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 100)); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4, 200)); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4, 300)); + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + EXPECT_EQ(subnet1, cfg.getSubnet(100)); + EXPECT_EQ(subnet2, cfg.getSubnet(200)); + EXPECT_EQ(subnet3, cfg.getSubnet(300)); + 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, "isc", "string")))); + + 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 with the same ID. + Subnet6Ptr subnet1b(new Subnet6(IOAddress("2001:10::"), + 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, false, "isc")); + + // subnet 3b updates subnet 3 with different UD and removes it + // from network 2 + Subnet6Ptr subnet3b(new Subnet6(IOAddress("2001:3::"), + 64, 3, 4, 100, 500, SubnetID(30))); + + // 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, 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, 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, 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:10::/64", + SubnetID(1), 400, shared_network1)); + + // Let's verify that our option is there and populated correctly. + auto subnet = cfg_to.getByPrefix("2001:10::/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(30), 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()); +} + +// This test verifies the Subnet6 parser's validation logic for +// t1-percent and t2-percent parameters. +TEST(CfgSubnets6Test, teeTimePercentValidation) { + + // Describes a single test scenario. + struct Scenario { + std::string label; // label used for logging test failures + bool calculate_tee_times; // value of calculate-tee-times parameter + double t1_percent; // value of t1-percent parameter + double t2_percent; // value of t2-percent parameter + std::string error_message; // expected error message is parsing should fail + }; + + // Test Scenarios. + std::vector<Scenario> tests = { + {"off and valid", false, .5, .95, ""}, + {"on and valid", true, .5, .95, ""}, + {"t2_negative", true, .5, -.95, + "subnet configuration failed: t2-percent:" + " -0.95 is invalid, it must be greater than 0.0 and less than 1.0" + }, + {"t2_too_big", true, .5, 1.95, + "subnet configuration failed: t2-percent:" + " 1.95 is invalid, it must be greater than 0.0 and less than 1.0" + }, + {"t1_negative", true, -.5, .95, + "subnet configuration failed: t1-percent:" + " -0.5 is invalid it must be greater than 0.0 and less than 1.0" + }, + {"t1_too_big", true, 1.5, .95, + "subnet configuration failed: t1-percent:" + " 1.5 is invalid it must be greater than 0.0 and less than 1.0" + }, + {"t1_bigger_than_t2", true, .85, .45, + "subnet configuration failed: t1-percent:" + " 0.85 is invalid, it must be less than t2-percent: 0.45" + } + }; + + // First we create a set of elements that provides all + // required for a Subnet6. + std::string json = + " {" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/64\", \n" + " \"interface\": \"\", \n" + " \"renew-timer\": 100, \n" + " \"rebind-timer\": 200, \n" + " \"valid-lifetime\": 300, \n" + " \"client-class\": \"\", \n" + " \"require-client-classes\": [] \n," + " \"reservations-global\": false, \n" + " \"reservations-in-subnet\": true, \n" + " \"reservations-out-of-pool\": false \n" + " }"; + + + data::ElementPtr elems; + ASSERT_NO_THROW(elems = data::Element::fromJSON(json)) + << "invalid JSON:" << json << "\n test is broken"; + + // Iterate over the test scenarios, verifying each prescribed + // outcome. + for (auto test = tests.begin(); test != tests.end(); ++test) { + { + SCOPED_TRACE("test: " + (*test).label); + + // Set this scenario's configuration parameters + elems->set("calculate-tee-times", data::Element::create((*test).calculate_tee_times)); + elems->set("t1-percent", data::Element::create((*test).t1_percent)); + elems->set("t2-percent", data::Element::create((*test).t2_percent)); + + Subnet6Ptr subnet; + try { + // Attempt to parse the configuration. + Subnet6ConfigParser parser; + subnet = parser.parse(elems); + } catch (const std::exception& ex) { + if (!(*test).error_message.empty()) { + // We expected a failure, did we fail the correct way? + EXPECT_EQ((*test).error_message, ex.what()); + } else { + // Should not have failed. + ADD_FAILURE() << "Scenario should not have failed: " << ex.what(); + } + + // Either way we're done with this scenario. + continue; + } + + // We parsed correctly, make sure the values are right. + EXPECT_EQ((*test).calculate_tee_times, subnet->getCalculateTeeTimes()); + EXPECT_TRUE(util::areDoublesEquivalent((*test).t1_percent, subnet->getT1Percent())) + << "expected:" << (*test).t1_percent << " actual: " << subnet->getT1Percent(); + EXPECT_TRUE(util::areDoublesEquivalent((*test).t2_percent, subnet->getT2Percent())) + << "expected:" << (*test).t2_percent << " actual: " << subnet->getT2Percent(); + } + } +} + +// This test verifies the Subnet6 parser's validation logic for +// preferred-lifetime and indirectly shared lifetime parsing. +// Note the valid-lifetime stuff is similar and already done for Subnet4. +TEST(CfgSubnets6Test, preferredLifetimeValidation) { + // First we create a set of elements that provides all + // required for a Subnet6. + std::string json = + " {" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/64\", \n" + " \"interface\": \"\", \n" + " \"renew-timer\": 100, \n" + " \"rebind-timer\": 200, \n" + " \"valid-lifetime\": 300, \n" + " \"client-class\": \"\", \n" + " \"require-client-classes\": [] \n," + " \"reservations-global\": false, \n" + " \"reservations-in-subnet\": true, \n" + " \"reservations-out-of-pool\": false \n" + " }"; + + + data::ElementPtr elems; + ASSERT_NO_THROW(elems = data::Element::fromJSON(json)) + << "invalid JSON:" << json << "\n test is broken"; + + { + SCOPED_TRACE("no preferred-lifetime"); + + data::ElementPtr copied = data::copy(elems); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + ASSERT_TRUE(subnet); + EXPECT_TRUE(subnet->getPreferred().unspecified()); + data::ConstElementPtr repr = subnet->toElement(); + EXPECT_FALSE(repr->get("preferred-lifetime")); + EXPECT_FALSE(repr->get("min-preferred-lifetime")); + EXPECT_FALSE(repr->get("max-preferred-lifetime")); + } + + { + SCOPED_TRACE("preferred-lifetime only"); + + data::ElementPtr copied = data::copy(elems); + copied->set("preferred-lifetime", data::Element::create(100)); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getPreferred().unspecified()); + EXPECT_EQ(100, subnet->getPreferred().get()); + EXPECT_EQ(100, subnet->getPreferred().getMin()); + EXPECT_EQ(100, subnet->getPreferred().getMax()); + data::ConstElementPtr repr = subnet->toElement(); + data::ConstElementPtr value = repr->get("preferred-lifetime"); + ASSERT_TRUE(value); + EXPECT_EQ("100", value->str()); + data::ConstElementPtr min_value = repr->get("min-preferred-lifetime"); + ASSERT_TRUE(min_value); + EXPECT_EQ("100", min_value->str()); + data::ConstElementPtr max_value = repr->get("max-preferred-lifetime"); + ASSERT_TRUE(max_value); + EXPECT_EQ("100", max_value->str()); + } + + { + SCOPED_TRACE("min-preferred-lifetime only"); + data::ElementPtr copied = data::copy(elems); + copied->set("min-preferred-lifetime", data::Element::create(100)); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getPreferred().unspecified()); + EXPECT_EQ(100, subnet->getPreferred().get()); + EXPECT_EQ(100, subnet->getPreferred().getMin()); + EXPECT_EQ(100, subnet->getPreferred().getMax()); + data::ConstElementPtr repr = subnet->toElement(); + data::ConstElementPtr value = repr->get("preferred-lifetime"); + ASSERT_TRUE(value); + EXPECT_EQ("100", value->str()); + data::ConstElementPtr min_value = repr->get("min-preferred-lifetime"); + ASSERT_TRUE(min_value); + EXPECT_EQ("100", min_value->str()); + data::ConstElementPtr max_value = repr->get("max-preferred-lifetime"); + ASSERT_TRUE(max_value); + EXPECT_EQ("100", max_value->str()); + } + + { + SCOPED_TRACE("max-preferred-lifetime only"); + data::ElementPtr copied = data::copy(elems); + copied->set("max-preferred-lifetime", data::Element::create(100)); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getPreferred().unspecified()); + EXPECT_EQ(100, subnet->getPreferred().get()); + EXPECT_EQ(100, subnet->getPreferred().getMin()); + EXPECT_EQ(100, subnet->getPreferred().getMax()); + data::ConstElementPtr repr = subnet->toElement(); + data::ConstElementPtr value = repr->get("preferred-lifetime"); + ASSERT_TRUE(value); + EXPECT_EQ("100", value->str()); + data::ConstElementPtr min_value = repr->get("min-preferred-lifetime"); + ASSERT_TRUE(min_value); + EXPECT_EQ("100", min_value->str()); + data::ConstElementPtr max_value = repr->get("max-preferred-lifetime"); + ASSERT_TRUE(max_value); + EXPECT_EQ("100", max_value->str()); + } + + { + SCOPED_TRACE("min-preferred-lifetime and preferred-lifetime"); + data::ElementPtr copied = data::copy(elems); + copied->set("min-preferred-lifetime", data::Element::create(100)); + // Use a different (and greater) value for the default. + copied->set("preferred-lifetime", data::Element::create(200)); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getPreferred().unspecified()); + EXPECT_EQ(200, subnet->getPreferred().get()); + EXPECT_EQ(100, subnet->getPreferred().getMin()); + EXPECT_EQ(200, subnet->getPreferred().getMax()); + data::ConstElementPtr repr = subnet->toElement(); + data::ConstElementPtr value = repr->get("preferred-lifetime"); + ASSERT_TRUE(value); + EXPECT_EQ("200", value->str()); + data::ConstElementPtr min_value = repr->get("min-preferred-lifetime"); + ASSERT_TRUE(min_value); + EXPECT_EQ("100", min_value->str()); + data::ConstElementPtr max_value = repr->get("max-preferred-lifetime"); + ASSERT_TRUE(max_value); + EXPECT_EQ("200", max_value->str()); + } + + { + SCOPED_TRACE("max-preferred-lifetime and preferred-lifetime"); + data::ElementPtr copied = data::copy(elems); + copied->set("max-preferred-lifetime", data::Element::create(200)); + // Use a different (and smaller) value for the default. + copied->set("preferred-lifetime", data::Element::create(100)); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getPreferred().unspecified()); + EXPECT_EQ(100, subnet->getPreferred().get()); + EXPECT_EQ(100, subnet->getPreferred().getMin()); + EXPECT_EQ(200, subnet->getPreferred().getMax()); + data::ConstElementPtr repr = subnet->toElement(); + data::ConstElementPtr value = repr->get("preferred-lifetime"); + ASSERT_TRUE(value); + EXPECT_EQ("100", value->str()); + data::ConstElementPtr min_value = repr->get("min-preferred-lifetime"); + ASSERT_TRUE(min_value); + EXPECT_EQ("100", min_value->str()); + data::ConstElementPtr max_value = repr->get("max-preferred-lifetime"); + ASSERT_TRUE(max_value); + EXPECT_EQ("200", max_value->str()); + } + + { + SCOPED_TRACE("min-preferred-lifetime and max-preferred-lifetime"); + data::ElementPtr copied = data::copy(elems); + copied->set("min-preferred-lifetime", data::Element::create(100)); + copied->set("max-preferred-lifetime", data::Element::create(200)); + Subnet6ConfigParser parser; + // No idea about the value to use for the default so failing. + ASSERT_THROW(parser.parse(copied), DhcpConfigError); + } + + { + SCOPED_TRACE("all 3 (min, max and default) preferred-lifetime"); + data::ElementPtr copied = data::copy(elems); + copied->set("min-preferred-lifetime", data::Element::create(100)); + // Use a different (and greater) value for the default. + copied->set("preferred-lifetime", data::Element::create(200)); + // Use a different (and greater than both) value for max. + copied->set("max-preferred-lifetime", data::Element::create(300)); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getPreferred().unspecified()); + EXPECT_EQ(200, subnet->getPreferred().get()); + EXPECT_EQ(100, subnet->getPreferred().getMin()); + EXPECT_EQ(300, subnet->getPreferred().getMax()); + data::ConstElementPtr repr = subnet->toElement(); + data::ConstElementPtr value = repr->get("preferred-lifetime"); + ASSERT_TRUE(value); + EXPECT_EQ("200", value->str()); + data::ConstElementPtr min_value = repr->get("min-preferred-lifetime"); + ASSERT_TRUE(min_value); + EXPECT_EQ("100", min_value->str()); + data::ConstElementPtr max_value = repr->get("max-preferred-lifetime"); + ASSERT_TRUE(max_value); + EXPECT_EQ("300", max_value->str()); + } + + { + SCOPED_TRACE("default value too small"); + data::ElementPtr copied = data::copy(elems); + copied->set("min-preferred-lifetime", data::Element::create(200)); + // 100 < 200 so it will fail. + copied->set("preferred-lifetime", data::Element::create(100)); + // Use a different (and greater than both) value for max. + copied->set("max-preferred-lifetime", data::Element::create(300)); + Subnet6ConfigParser parser; + ASSERT_THROW(parser.parse(copied), DhcpConfigError); + } + + { + SCOPED_TRACE("default value too large"); + data::ElementPtr copied = data::copy(elems); + copied->set("min-preferred-lifetime", data::Element::create(100)); + // Use a different (and greater) value for the default. + copied->set("preferred-lifetime", data::Element::create(300)); + // 300 > 200 so it will fail. + copied->set("max-preferred-lifetime", data::Element::create(200)); + Subnet6ConfigParser parser; + ASSERT_THROW(parser.parse(copied), DhcpConfigError); + } + + { + SCOPED_TRACE("equal bounds are no longer ignored"); + data::ElementPtr copied = data::copy(elems); + copied->set("min-preferred-lifetime", data::Element::create(100)); + copied->set("preferred-lifetime", data::Element::create(100)); + copied->set("max-preferred-lifetime", data::Element::create(100)); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getPreferred().unspecified()); + EXPECT_EQ(100, subnet->getPreferred().get()); + EXPECT_EQ(100, subnet->getPreferred().getMin()); + EXPECT_EQ(100, subnet->getPreferred().getMax()); + data::ConstElementPtr repr = subnet->toElement(); + data::ConstElementPtr value = repr->get("preferred-lifetime"); + ASSERT_TRUE(value); + EXPECT_EQ("100", value->str()); + data::ConstElementPtr min_value = repr->get("min-preferred-lifetime"); + ASSERT_TRUE(min_value); + EXPECT_EQ("100", min_value->str()); + data::ConstElementPtr max_value = repr->get("max-preferred-lifetime"); + ASSERT_TRUE(max_value); + EXPECT_EQ("100", max_value->str()); + } +} + +// This test verifies the Subnet6 parser's validation logic for +// hostname sanitizer values. +TEST(CfgSubnets6Test, hostnameSanitizierValidation) { + + // First we create a set of elements that provides all + // required for a Subnet6. + std::string json = + " {" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/64\", \n" + " \"interface\": \"\", \n" + " \"renew-timer\": 100, \n" + " \"rebind-timer\": 200, \n" + " \"valid-lifetime\": 300, \n" + " \"client-class\": \"\", \n" + " \"require-client-classes\": [] \n," + " \"reservations-global\": false, \n" + " \"reservations-in-subnet\": true, \n" + " \"reservations-out-of-pool\": false \n" + " }"; + + data::ElementPtr elems; + ASSERT_NO_THROW(elems = data::Element::fromJSON(json)) + << "invalid JSON:" << json << "\n test is broken"; + + { + SCOPED_TRACE("invalid regular expression"); + + data::ElementPtr copied = data::copy(elems); + copied->set("hostname-char-set", data::Element::create("^[A-")); + copied->set("hostname-char-replacement", data::Element::create("x")); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + EXPECT_THROW_MSG(subnet = parser.parse(copied), DhcpConfigError, + "subnet configuration failed: hostname-char-set " + "'^[A-' is not a valid regular expression"); + + } + { + SCOPED_TRACE("valid regular expression"); + + data::ElementPtr copied = data::copy(elems); + copied->set("hostname-char-set", data::Element::create("^[A-Z]")); + copied->set("hostname-char-replacement", data::Element::create("x")); + Subnet6Ptr subnet; + Subnet6ConfigParser parser; + ASSERT_NO_THROW(subnet = parser.parse(copied)); + EXPECT_EQ("^[A-Z]", subnet->getHostnameCharSet().get()); + EXPECT_EQ("x", subnet->getHostnameCharReplacement().get()); + } +} + +// This test verifies the Subnet6 parser's validation logic for +// lease cache parameters. +TEST(CfgSubnets6Test, cacheParamValidation) { + + // Describes a single test scenario. + struct Scenario { + std::string label; // label used for logging test failures + double threshold; // value of cache-threshold + std::string error_message; // expected error message is parsing should fail + }; + + // Test Scenarios. + std::vector<Scenario> tests = { + {"valid", .25, ""}, + {"negative", -.25, + "subnet configuration failed: cache-threshold:" + " -0.25 is invalid, it must be greater than 0.0 and less than 1.0" + }, + {"too big", 1.05, + "subnet configuration failed: cache-threshold:" + " 1.05 is invalid, it must be greater than 0.0 and less than 1.0" + } + }; + + // First we create a set of elements that provides all + // required for a Subnet6. + std::string json = + " {" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/64\", \n" + " \"interface\": \"\", \n" + " \"renew-timer\": 100, \n" + " \"rebind-timer\": 200, \n" + " \"valid-lifetime\": 300, \n" + " \"client-class\": \"\", \n" + " \"require-client-classes\": [] \n," + " \"reservations-global\": false, \n" + " \"reservations-in-subnet\": true, \n" + " \"reservations-out-of-pool\": false \n" + " }"; + + data::ElementPtr elems; + ASSERT_NO_THROW(elems = data::Element::fromJSON(json)) + << "invalid JSON:" << json << "\n test is broken"; + + // Iterate over the test scenarios, verifying each prescribed + // outcome. + for (auto test = tests.begin(); test != tests.end(); ++test) { + { + SCOPED_TRACE("test: " + (*test).label); + + // Set this scenario's configuration parameters + elems->set("cache-threshold", data::Element::create((*test).threshold)); + + Subnet6Ptr subnet; + try { + // Attempt to parse the configuration. + Subnet6ConfigParser parser; + subnet = parser.parse(elems); + } catch (const std::exception& ex) { + if (!(*test).error_message.empty()) { + // We expected a failure, did we fail the correct way? + EXPECT_EQ((*test).error_message, ex.what()); + } else { + // Should not have failed. + ADD_FAILURE() << "Scenario should not have failed: " << ex.what(); + } + + // Either way we're done with this scenario. + continue; + } + + // We parsed correctly, make sure the values are right. + EXPECT_TRUE(util::areDoublesEquivalent((*test).threshold, subnet->getCacheThreshold())); + } + } +} + +// This test verifies that the optional interface check works as expected. +TEST(CfgSubnets6Test, iface) { + // Create a configuration. + std::string json = + " {" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/64\", \n" + " \"interface\": \"eth1961\"\n" + " }"; + + data::ElementPtr elems; + ASSERT_NO_THROW(elems = data::Element::fromJSON(json)) + << "invalid JSON:" << json << "\n test is broken"; + + // The interface check can be disabled. + Subnet6ConfigParser parser_no_check(false); + Subnet6Ptr subnet; + EXPECT_NO_THROW(subnet = parser_no_check.parse(elems)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getIface().unspecified()); + EXPECT_EQ("eth1961", subnet->getIface().get()); + + // Retry with the interface check enabled. + Subnet6ConfigParser parser; + EXPECT_THROW(parser.parse(elems), DhcpConfigError); + + // Configure default test interfaces. + IfaceMgrTestConfig config(true); + + EXPECT_NO_THROW(subnet = parser_no_check.parse(elems)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getIface().unspecified()); + EXPECT_EQ("eth1961", subnet->getIface().get()); + + EXPECT_NO_THROW(subnet = parser.parse(elems)); + ASSERT_TRUE(subnet); + EXPECT_FALSE(subnet->getIface().unspecified()); + EXPECT_EQ("eth1961", subnet->getIface().get()); +} + +// This test verifies that update statistics works as expected. +TEST(CfgSubnets6Test, updateStatistics) { + CfgMgr::instance().clear(); + + CfgSubnets6Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6(); + ObservationPtr observation; + SubnetID subnet_id = 100; + + LeaseMgrFactory::create("type=memfile universe=6 persist=false"); + + // remove all statistics + StatsMgr::instance().removeAll(); + + // Create subnet. + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 100)); + + Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), IOAddress("2001:db8:1::FF"))); + subnet->addPool(pool); + + pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"), 96, 112)); + subnet->addPool(pool); + + // Add subnet. + cfg->add(subnet); + + observation = StatsMgr::instance().getObservation( + "cumulative-assigned-nas"); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + "cumulative-assigned-pds"); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + "declined-addresses"); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + "reclaimed-declined-addresses"); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + "reclaimed-leases"); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "total-nas")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "total-nas"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "total-pds")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "total-pds"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "assigned-nas")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "assigned-nas"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "assigned-pds")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "assigned-pds"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-nas")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "cumulative-assigned-nas"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-pds")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "declined-addresses")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "declined-addresses"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-leases"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "reclaimed-leases"))); + ASSERT_FALSE(observation); + + cfg->updateStatistics(); + + observation = StatsMgr::instance().getObservation( + "cumulative-assigned-nas"); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + "cumulative-assigned-pds"); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + "declined-addresses"); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + "reclaimed-declined-addresses"); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + "reclaimed-leases"); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "total-nas")); + ASSERT_TRUE(observation); + ASSERT_EQ(256, observation->getBigInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "total-nas"))); + ASSERT_TRUE(observation); + ASSERT_EQ(256, observation->getBigInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "total-pds")); + ASSERT_TRUE(observation); + ASSERT_EQ(65536, observation->getBigInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "total-pds"))); + ASSERT_TRUE(observation); + ASSERT_EQ(65536, observation->getBigInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "assigned-nas")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "assigned-nas"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "assigned-pds")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "assigned-pds"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-nas")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "cumulative-assigned-nas"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-pds")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "declined-addresses")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "declined-addresses"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-leases"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "reclaimed-leases"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); +} + +// This test verifies that remove statistics works as expected. +TEST(CfgSubnets6Test, removeStatistics) { + CfgSubnets6 cfg; + ObservationPtr observation; + SubnetID subnet_id = 100; + + // remove all statistics and then set them all to 0 + StatsMgr::instance().removeAll(); + + // Create subnet. + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 100)); + + Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), IOAddress("2001:db8:1::FF"))); + subnet->addPool(pool); + + pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"), 96, 112)); + subnet->addPool(pool); + + // Add subnet. + cfg.add(subnet); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "total-nas"), + int128_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "total-nas")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getBigInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "total-nas")), + int128_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "total-nas"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getBigInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "total-pds"), + int128_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "total-pds")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getBigInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "total-pds")), + int128_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "total-pds"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getBigInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "assigned-nas"), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "assigned-nas")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "assigned-nas")), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "assigned-nas"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "assigned-pds"), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "assigned-pds")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "assigned-pds")), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "assigned-pds"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-nas"), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-nas")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "cumulative-assigned-nas")), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "cumulative-assigned-nas"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-pds"), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-pds")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds")), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "declined-addresses"), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "declined-addresses")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "declined-addresses")), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "declined-addresses"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses"), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases"), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases")); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-leases")), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-leases"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + StatsMgr::instance().setValue( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "reclaimed-leases")), + int64_t(0)); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "reclaimed-leases"))); + ASSERT_TRUE(observation); + ASSERT_EQ(0, observation->getInteger().first); + + // remove all statistics + cfg.removeStatistics(); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "total-nas")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "total-nas"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "total-pds")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "total-pds"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "assigned-nas")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "assigned-nas"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "assigned-pds")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "assigned-pds"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-nas")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "cumulative-assigned-nas"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "cumulative-assigned-pds")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "declined-addresses")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "declined-addresses"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases")); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", 0, "reclaimed-leases"))); + ASSERT_FALSE(observation); + + observation = StatsMgr::instance().getObservation( + StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", 0, "reclaimed-leases"))); + ASSERT_FALSE(observation); +} + +// This test verifies that in range host reservation works as expected. +TEST(CfgSubnets6Test, hostNA) { + // Create a configuration. + std::string json = + " {" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/48\", \n" + " \"reservations\": [ {\n" + " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n" + " \"ip-addresses\": [\"2001:db8:1::1\"] } ]\n" + " }"; + + data::ElementPtr elems; + ASSERT_NO_THROW(elems = data::Element::fromJSON(json)) + << "invalid JSON:" << json << "\n test is broken"; + + Subnet6ConfigParser parser; + Subnet6Ptr subnet; + EXPECT_NO_THROW(subnet = parser.parse(elems)); + ASSERT_TRUE(subnet); + CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts(); + ASSERT_TRUE(cfg_hosts); + HostCollection hosts = cfg_hosts->getAll6(SubnetID(1)); + ASSERT_EQ(1, hosts.size()); + ConstHostPtr host = hosts[0]; + ASSERT_TRUE(host); + EXPECT_EQ(1, host->getIPv6SubnetID()); + EXPECT_EQ("hwaddr=AABBCCDDEEFF", host->getIdentifierAsText()); + IPv6ResrvRange addresses = host->getIPv6Reservations(IPv6Resrv::TYPE_NA); + ASSERT_EQ(1, std::distance(addresses.first, addresses.second)); + EXPECT_EQ("2001:db8:1::1", addresses.first->second.getPrefix().toText()); + + CfgMgr::instance().clear(); +} + +// This test verifies that an out of range host reservation is rejected. +TEST(CfgSubnets6Test, outOfRangeHost) { + // Create a configuration. + std::string json = + " {" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/48\", \n" + " \"reservations\": [ {\n" + " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n" + " \"ip-addresses\": [\"2001:db8:1::1\", \n" + " \"2001:db8:2::1\"] } ]\n" + " }"; + + data::ElementPtr elems; + ASSERT_NO_THROW(elems = data::Element::fromJSON(json)) + << "invalid JSON:" << json << "\n test is broken"; + + Subnet6ConfigParser parser; + std::string msg = "specified reservation '2001:db8:2::1' is "; + msg += "not within the IPv6 subnet '2001:db8:1::/48'"; + EXPECT_THROW_MSG(parser.parse(elems), DhcpConfigError, msg); +} + +// This test verifies that in range validation does not applies to prefixes. +TEST(CfgSubnets6Test, hostPD) { + // Create a configuration. + std::string json = + " {" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/48\", \n" + " \"reservations\": [ {\n" + " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n" + " \"prefixes\": [\"2001:db8:2::/64\"] } ]\n" + " }"; + + data::ElementPtr elems; + ASSERT_NO_THROW(elems = data::Element::fromJSON(json)) + << "invalid JSON:" << json << "\n test is broken"; + + Subnet6ConfigParser parser; + Subnet6Ptr subnet; + try { + subnet = parser.parse(elems); + } catch (const std::exception& ex) { + std::cerr << "parse fail with " << ex.what() << std::endl; + } + //EXPECT_NO_THROW(subnet = parser.parse(elems)); + ASSERT_TRUE(subnet); + CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts(); + ASSERT_TRUE(cfg_hosts); + HostCollection hosts = cfg_hosts->getAll6(SubnetID(1)); + ASSERT_EQ(1, hosts.size()); + ConstHostPtr host = hosts[0]; + ASSERT_TRUE(host); + EXPECT_EQ(1, host->getIPv6SubnetID()); + EXPECT_EQ("hwaddr=AABBCCDDEEFF", host->getIdentifierAsText()); + IPv6ResrvRange prefixes = host->getIPv6Reservations(IPv6Resrv::TYPE_PD); + ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second)); + EXPECT_EQ("2001:db8:2::", prefixes.first->second.getPrefix().toText()); + EXPECT_EQ(64, prefixes.first->second.getPrefixLen()); + + // Verify the prefix is not in the subnet range. + EXPECT_FALSE(subnet->inRange(prefixes.first->second.getPrefix())); + + CfgMgr::instance().clear(); +} + +// This test verifies that the getLinks tool works as expected. +TEST(CfgSubnets6Test, getLinks) { + CfgSubnets6 cfg; + + // Create 4 subnets. + Subnet6Ptr subnet0(new Subnet6(IOAddress("::"), 48, 1, 2, 3, 4, + SubnetID(111))); + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4, + SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 64, 1, 2, 3, 4, + SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 64, 1, 2, 3, 4, + SubnetID(3))); + + // Add all subnets to the configuration. + ASSERT_NO_THROW(cfg.add(subnet0)); + ASSERT_NO_THROW(cfg.add(subnet1)); + ASSERT_NO_THROW(cfg.add(subnet2)); + ASSERT_NO_THROW(cfg.add(subnet3)); + + // No 2001:db8:4:: subnet. + SubnetIDSet links; + uint8_t link_len = 111; + EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:4::"), link_len)); + EXPECT_TRUE(links.empty()); + EXPECT_EQ(111, link_len); + + // A 2001:db8:2::/64 subnet. + links.clear(); + link_len = 111; + EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2::"), link_len)); + SubnetIDSet expected = { 2 }; + EXPECT_EQ(expected, links); + EXPECT_EQ(64, link_len); + + // Check that any address in the subnet works. + links.clear(); + link_len = 111; + EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2::1234:5678:9abc:def0"), + link_len)); + EXPECT_EQ(expected, links); + EXPECT_EQ(64, link_len); + + // Check that an address outside the subnet does not work. + links.clear(); + link_len = 111; + EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2:1::"), link_len)); + EXPECT_TRUE(links.empty()); + EXPECT_EQ(111, link_len); + + // Add a second 2001:db8:2::/64 subnet. + Subnet6Ptr subnet10(new Subnet6(IOAddress("2001:db8:2::10"), 64, 1, 2, 3, + 4, SubnetID(10))); + ASSERT_NO_THROW(cfg.add(subnet10)); + + // Now we should get 2 subnets. + links.clear(); + link_len = 111; + EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2::"), link_len)); + expected = { 2, 10 }; + EXPECT_EQ(expected, links); + EXPECT_EQ(64, link_len); + + // Add a larger subnet. + Subnet6Ptr subnet20(new Subnet6(IOAddress("2001:db8:2::20"), 56, 1, 2, 3, + 4, SubnetID(20))); + ASSERT_NO_THROW(cfg.add(subnet20)); + + // Now we should get 3 subnets and a smaller prefix length. + links.clear(); + link_len = 111; + EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2::"), link_len)); + expected = { 2, 10, 20 }; + EXPECT_EQ(expected, links); + EXPECT_EQ(56, link_len); + + // But only the larger subnet if the address is only in it. + links.clear(); + link_len = 111; + EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2:1::"), link_len)); + expected = { 20 }; + EXPECT_EQ(expected, links); + EXPECT_EQ(56, link_len); + + // Even it is not used for Bulk Leasequery, it works for :: too. + links.clear(); + link_len = 111; + EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("::"), link_len)); + expected = { 111 }; + EXPECT_EQ(expected, links); + EXPECT_EQ(48, link_len); +} + +// This test verifies that for each subnet in the configuration it calls +// the registerLeaseMgrCallback function. +TEST(CfgSubnets6Test, initAllocatorsAfterConfigure) { + CfgSubnets6 cfg; + + // Create 4 subnets. + Subnet6Ptr subnet0(new Subnet6(IOAddress("2001:db8:1::"), + 64, 1, 2, 3, 4, SubnetID(111))); + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:2::"), + 64, 1, 2, 3, 4, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:3::"), + 64, 1, 2, 3, 4, SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:4::"), + 64, 1, 2, 3, 4, SubnetID(3))); + + auto allocator0 = boost::make_shared<InitRecordingAllocator>(Lease::TYPE_NA, subnet0); + subnet0->setAllocator(Lease::TYPE_NA, allocator0); + + auto allocator2 = boost::make_shared<InitRecordingAllocator>(Lease::TYPE_PD, subnet2); + subnet2->setAllocator(Lease::TYPE_PD, allocator2); + + cfg.add(subnet0); + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + EXPECT_EQ(0, allocator0->callcount_); + EXPECT_EQ(0, allocator2->callcount_); + + cfg.initAllocatorsAfterConfigure(); + + EXPECT_EQ(1, allocator0->callcount_); + EXPECT_EQ(1, allocator2->callcount_); +} + + +/// @brief Test fixture for parsing v6 Subnets that can verify log output. +class Subnet6ParserTest : public LogContentTest { +public: + + /// @brief virtual destructor + virtual ~Subnet6ParserTest() = default; +}; + +// This test verifies that subnet parser for IPv6 works properly +// when using invalid renew and rebind timers. +TEST_F(Subnet6ParserTest, parseWithInvalidRenewRebind) { + std::string config = + "{\n" + " \"id\": 1,\n" + " \"subnet\": \"2001:db8:1::/64\",\n" + " \"valid-lifetime\": 399,\n" + " \"rebind-timer\": 199,\n" + " \"renew-timer\": 200\n" + "}"; + + // Basic configuration for subnet6 but with a renew-timer value + // larger than rebind-timer. + ElementPtr config_element = Element::fromJSON(config); + + // Parse configuration specified above. + Subnet6ConfigParser parser(false); + Subnet6Ptr subnet; + + // Parser should not throw. + ASSERT_NO_THROW(subnet = parser.parse(config_element)); + ASSERT_TRUE(subnet); + + // Veriy we emitted the proper log message. + addString("DHCPSRV_CFGMGR_RENEW_GTR_REBIND in subnet-id 1," + " the value of renew-timer 200 is greater than the value" + " of rebind-timer 199, ignoring renew-timer"); + EXPECT_TRUE(checkFile()); +} + +} // end of anonymous namespace |