diff options
Diffstat (limited to 'src/lib/dhcpsrv/tests/shared_network_unittest.cc')
-rw-r--r-- | src/lib/dhcpsrv/tests/shared_network_unittest.cc | 1527 |
1 files changed, 1527 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/shared_network_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_unittest.cc new file mode 100644 index 0000000..f9378e7 --- /dev/null +++ b/src/lib/dhcpsrv/tests/shared_network_unittest.cc @@ -0,0 +1,1527 @@ +// Copyright (C) 2017-2022 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 <asiolink/io_address.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcpsrv/shared_network.h> +#include <dhcpsrv/subnet.h> +#include <dhcpsrv/subnet_id.h> +#include <util/triplet.h> +#include <exceptions/exceptions.h> +#include <testutils/test_to_element.h> +#include <testutils/multi_threading_utils.h> + +#include <gtest/gtest.h> +#include <cstdint> +#include <string> +#include <vector> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::test; + +namespace { + +// This test verifies that the SharedNetwork4 factory function creates a +// valid shared network instance. +TEST(SharedNetwork4Test, create) { + auto network = SharedNetwork4::create("frog"); + ASSERT_TRUE(network); + EXPECT_EQ("frog", network->getName()); +} + +// This test verifies the default values set for the shared +// networks and verifies that the optional values are unspecified. +TEST(SharedNetwork4Test, defaults) { + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + EXPECT_TRUE(network->getIface().unspecified()); + EXPECT_TRUE(network->getIface().empty()); + + EXPECT_TRUE(network->getClientClass().unspecified()); + EXPECT_TRUE(network->getClientClass().empty()); + + EXPECT_TRUE(network->getValid().unspecified()); + EXPECT_EQ(0, network->getValid().get()); + + EXPECT_TRUE(network->getT1().unspecified()); + EXPECT_EQ(0, network->getT1().get()); + + EXPECT_TRUE(network->getT2().unspecified()); + EXPECT_EQ(0, network->getT2().get()); + + EXPECT_TRUE(network->getReservationsGlobal().unspecified()); + EXPECT_FALSE(network->getReservationsGlobal().get()); + + EXPECT_TRUE(network->getReservationsInSubnet().unspecified()); + EXPECT_TRUE(network->getReservationsInSubnet().get()); + + EXPECT_TRUE(network->getReservationsOutOfPool().unspecified()); + EXPECT_FALSE(network->getReservationsOutOfPool().get()); + + EXPECT_TRUE(network->getCalculateTeeTimes().unspecified()); + EXPECT_FALSE(network->getCalculateTeeTimes().get()); + + EXPECT_TRUE(network->getT1Percent().unspecified()); + EXPECT_EQ(0.0, network->getT1Percent().get()); + + EXPECT_TRUE(network->getT2Percent().unspecified()); + EXPECT_EQ(0.0, network->getT2Percent().get()); + + EXPECT_TRUE(network->getMatchClientId().unspecified()); + EXPECT_TRUE(network->getMatchClientId().get()); + + EXPECT_TRUE(network->getAuthoritative().unspecified()); + EXPECT_FALSE(network->getAuthoritative().get()); + + EXPECT_TRUE(network->getDdnsSendUpdates().unspecified()); + EXPECT_FALSE(network->getDdnsSendUpdates().get()); + + EXPECT_TRUE(network->getDdnsOverrideNoUpdate().unspecified()); + EXPECT_FALSE(network->getDdnsOverrideNoUpdate().get()); + + EXPECT_TRUE(network->getDdnsOverrideClientUpdate().unspecified()); + EXPECT_FALSE(network->getDdnsOverrideClientUpdate().get()); + + EXPECT_TRUE(network->getDdnsReplaceClientNameMode().unspecified()); + EXPECT_EQ(D2ClientConfig::RCM_NEVER, network->getDdnsReplaceClientNameMode().get()); + + EXPECT_TRUE(network->getDdnsGeneratedPrefix().unspecified()); + EXPECT_TRUE(network->getDdnsGeneratedPrefix().empty()); + + EXPECT_TRUE(network->getDdnsQualifyingSuffix().unspecified()); + EXPECT_TRUE(network->getDdnsQualifyingSuffix().empty()); + + EXPECT_TRUE(network->getHostnameCharSet().unspecified()); + EXPECT_TRUE(network->getHostnameCharSet().empty()); + + EXPECT_TRUE(network->getHostnameCharReplacement().unspecified()); + EXPECT_TRUE(network->getHostnameCharReplacement().empty()); + + EXPECT_TRUE(network->getDdnsUpdateOnRenew().unspecified()); + EXPECT_FALSE(network->getDdnsSendUpdates().get()); +} + +// This test verifies that shared network can be given a name and that +// this name can be retrieved. +TEST(SharedNetwork4Test, getName) { + // Create shared network with an initial name "dog". + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + EXPECT_EQ("frog", network->getName()); + + // Override the name. + network->setName("dog"); + EXPECT_EQ("dog", network->getName()); +} + +// This test verifies that an IPv4 subnet can be added to a shared network. +// It also verifies that two subnets with the same ID can't be added to +// a shared network and that a single subnet can't be added to two different +// shared subnets. +TEST(SharedNetwork4Test, addSubnet4) { + // First, create a network. + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + + // Try to add null pointer. It should throw. + Subnet4Ptr subnet; + ASSERT_THROW(network->add(subnet), BadValue); + + // Create a valid subnet. It should now be added successfully. + subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(15))); + ASSERT_NO_THROW(network->add(subnet)); + ASSERT_EQ(1, network->getAllSubnets()->size()); + + // Retrieve the subnet from the network and make sure it is returned + // as expected. + ASSERT_FALSE(network->getAllSubnets()->empty()); + Subnet4Ptr returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(subnet->getID(), returned_subnet->getID()); + SharedNetwork4Ptr network1; + subnet->getSharedNetwork(network1); + ASSERT_TRUE(network1); + EXPECT_TRUE(network1 == network); + + // Create another subnet with the same ID. Adding a network with the + // same ID should cause an error. + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(15))); + ASSERT_THROW(network->add(subnet2), DuplicateSubnetID); + + // Create another subnet with the same prefix. Adding a network with the + // same prefix should cause an error. + subnet2.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1234))); + ASSERT_THROW(network->add(subnet2), DuplicateSubnetID); + + // Create another network and try to add a subnet to it. It should fail + // because the subnet is already associated with the first network. + SharedNetwork4Ptr network2(new SharedNetwork4("dog")); + ASSERT_THROW(network2->add(subnet), InvalidOperation); +} + +// This test verifies that an IPv4 subnet can be replaced in a shared network. +// It does the same tests than for addSubnet4 (at the exception of conflicts) +// and check the random order is kept. +TEST(SharedNetwork4Test, replaceSubnet4) { + // First, create a network. + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + + // Try to replace null pointer. It should throw. + Subnet4Ptr subnet; + ASSERT_THROW(network->replace(subnet), BadValue); + + // Create some valid subnets. they should now be added successfully. + subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(15))); + ASSERT_NO_THROW(network->add(subnet)); + subnet.reset(new Subnet4(IOAddress("192.168.0.0"), 24, 10, 20, 30, + SubnetID(1))); + ASSERT_NO_THROW(network->add(subnet)); + subnet.reset(new Subnet4(IOAddress("192.168.1.0"), 24, 10, 20, 30, + SubnetID(10))); + ASSERT_NO_THROW(network->add(subnet)); + ASSERT_EQ(3, network->getAllSubnets()->size()); + + // Create another subnet with another ID. Replace should return false. + subnet.reset(new Subnet4(IOAddress("192.168.2.0"), 24, 10, 20, 30, + SubnetID(2))); + EXPECT_FALSE(network->replace(subnet)); + + // Subnets did not changed. + ASSERT_EQ(3, network->getAllSubnets()->size()); + auto returned_it = network->getAllSubnets()->begin(); + Subnet4Ptr returned_subnet = *returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(1, returned_subnet->getID()); + ++returned_it; + returned_subnet = *returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(10, returned_subnet->getID()); + ++returned_it; + returned_subnet = *returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(15, returned_subnet->getID()); + + // Reset the returned subnet to the subnet with subnet id 1. + returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getID()); + + // Create another subnet with the same ID than the second subnet. + subnet.reset(new Subnet4(IOAddress("192.168.0.0"), 24, 100, 200, 300, + SubnetID(1))); + EXPECT_TRUE(network->replace(subnet)); + + // Second subnet was updated. + EXPECT_EQ(10, returned_subnet->getT1()); + EXPECT_EQ(20, returned_subnet->getT2()); + EXPECT_EQ(30, returned_subnet->getValid()); + SharedNetwork4Ptr network1; + returned_subnet->getSharedNetwork(network1); + EXPECT_FALSE(network1); + + ASSERT_EQ(3, network->getAllSubnets()->size()); + returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getID()); + EXPECT_EQ(100, returned_subnet->getT1()); + EXPECT_EQ(200, returned_subnet->getT2()); + EXPECT_EQ(300, returned_subnet->getValid()); + returned_subnet->getSharedNetwork(network1); + EXPECT_TRUE(network1); + EXPECT_TRUE(network == network1); + + // Other subnets did not changed. + returned_it = network->getAllSubnets()->begin(); + returned_subnet = *++returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(10, returned_subnet->getID()); + returned_subnet = *++returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(15, returned_subnet->getID()); + + // Create another network and try to replace a subnet to it. It should fail + // because the subnet is already associated with the first network. + SharedNetwork4Ptr network2(new SharedNetwork4("dog")); + ASSERT_THROW(network2->replace(subnet), InvalidOperation); + + // Try to change the prefix. Not recommended but should work. + subnet.reset(new Subnet4(IOAddress("192.168.10.0"), 24, 100, 200, 300, + SubnetID(1))); + EXPECT_TRUE(network->replace(subnet)); + ASSERT_EQ(3, network->getAllSubnets()->size()); + returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getID()); + EXPECT_EQ("192.168.10.0/24", returned_subnet->toText()); + + // but not if the prefix already exists for another subnet. + subnet.reset(new Subnet4(IOAddress("192.168.1.0"), 24, 100, 200, 300, + SubnetID(1))); + EXPECT_FALSE(network->replace(subnet)); + ASSERT_EQ(3, network->getAllSubnets()->size()); + returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getID()); + EXPECT_EQ("192.168.10.0/24", returned_subnet->toText()); +} + +// This test verifies that it is possible to remove a specified subnet. +TEST(SharedNetwork4Test, delSubnet4) { + // Create two subnets and add them to the shared network. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + ASSERT_NO_THROW(network->add(subnet1)); + ASSERT_NO_THROW(network->add(subnet2)); + + // Make sure they have been added successfully. + ASSERT_EQ(2, network->getAllSubnets()->size()); + + // Try to remove a subnet that doesn't exist in this shared network. + // It should cause an error. + ASSERT_THROW(network->del(SubnetID(5)), BadValue); + + // Now delete the subnet that exists. + ASSERT_NO_THROW(network->del(subnet1->getID())); + // We should be left with only one subnet. + ASSERT_EQ(1, network->getAllSubnets()->size()); + Subnet4Ptr subnet_returned = *network->getAllSubnets()->begin(); + ASSERT_TRUE(subnet_returned); + EXPECT_EQ(subnet2->getID(), subnet_returned->getID()); + + // Check that shared network has been cleared for the removed subnet. + SharedNetwork4Ptr network1; + subnet1->getSharedNetwork(network1); + EXPECT_FALSE(network1); + + // Remove another subnet and make sure there are no subnets left. + ASSERT_NO_THROW(network->del(subnet2->getID())); + EXPECT_EQ(0, network->getAllSubnets()->size()); + + // The network pointer should be cleared for this second subnet too. + SharedNetwork4Ptr network2; + subnet1->getSharedNetwork(network2); + EXPECT_FALSE(network2); +} + +// This test verifies that it is possible to iterate over the subnets +// associated with a particular shared network. +TEST(SharedNetwork4Test, getNextSubnet) { + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + + // Create three subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + Subnet4Ptr subnet3(new Subnet4(IOAddress("172.16.25.0"), 24, 10, 20, 30, + SubnetID(3))); + std::vector<Subnet4Ptr> subnets; + subnets.push_back(subnet1); + subnets.push_back(subnet2); + subnets.push_back(subnet3); + + // Subnets have unique IDs so they should successfully be added to the + // network. + for (auto i = 0; i < subnets.size(); ++i) { + ASSERT_NO_THROW(network->add(subnets[i])) + << "failed to add subnet with id " << subnets[i]->getID() + << " to shared network"; + } + + // Collect networks associated with our subnets in the vector. + std::vector<SharedNetwork4Ptr> networks; + for (auto i = 0; i < subnets.size(); ++i) { + SharedNetwork4Ptr network; + subnets[i]->getSharedNetwork(network); + ASSERT_TRUE(network) << "failed to retrieve shared network for a" + << " subnet id " << subnets[i]->getID(); + networks.push_back(network); + } + + // All subnets should be associated with the same network. + for (auto i = 1; i < networks.size(); ++i) { + EXPECT_TRUE(networks[0] == networks[i]); + } + + // Perform the test 3 times where each subnet belonging to the shared + // network is treated as a "first" subnet in the call to getNextSubnet. + for (auto i = 0; i < subnets.size(); ++i) { + Subnet4Ptr s = subnets[i]; + + // Iterate over the subnets starting from the subnet with index i. + for (auto j = 0; j < subnets.size(); ++j) { + // Get next subnet (following the one currently in s). + s = networks[0]->getNextSubnet(subnets[i], s->getID()); + // The last iteration should return empty pointer to indicate end of + // the subnets within shared network. If we're not at last iteration + // check that the subnet identifier of the returned subnet is valid. + if (j < subnets.size() - 1) { + ASSERT_TRUE(s) << "retrieving next subnet failed for pair of" + " indexes (i, j) = (" << i << ", " << j << ")"; + const auto expected_subnet_id = (i + j + 1) % subnets.size() + 1; + EXPECT_EQ(expected_subnet_id, s->getID()); + } else { + // Null subnet returned for a last iteration. + ASSERT_FALSE(s) << "expected null pointer to be returned as" + " next subnet for pair of indexes (i, j) = (" + << i << ", " << j << ")"; + } + } + } +} + +// This test verifies that preferred subnet is returned based on the timestamp +// when the subnet was last used and allowed client classes. +TEST(SharedNetwork4Test, getPreferredSubnet) { + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + + // Create four subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + Subnet4Ptr subnet3(new Subnet4(IOAddress("172.16.25.0"), 24, 10, 20, 30, + SubnetID(3))); + Subnet4Ptr subnet4(new Subnet4(IOAddress("172.16.28.0"), 24, 10, 20, 30, + SubnetID(4))); + + // Associate first two subnets with classes. + subnet1->allowClientClass("class1"); + subnet2->allowClientClass("class1"); + + std::vector<Subnet4Ptr> subnets; + subnets.push_back(subnet1); + subnets.push_back(subnet2); + subnets.push_back(subnet3); + subnets.push_back(subnet4); + + // Subnets have unique IDs so they should successfully be added to the + // network. + for (auto i = 0; i < subnets.size(); ++i) { + ASSERT_NO_THROW(network->add(subnets[i])) + << "failed to add subnet with id " << subnets[i]->getID() + << " to shared network"; + } + + Subnet4Ptr preferred; + + // Initially, for every subnet we should get the same subnet as the preferred + // one, because none of them have been used. + for (auto i = 0; i < subnets.size(); ++i) { + preferred = network->getPreferredSubnet(subnets[i]); + EXPECT_EQ(subnets[i]->getID(), preferred->getID()); + } + + // Allocating an address from subnet2 updates the last allocated timestamp + // for this subnet, which makes this subnet preferred over subnet1. + subnet2->setLastAllocated(Lease::TYPE_V4, IOAddress("192.0.2.25")); + preferred = network->getPreferredSubnet(subnet1); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // If selected is subnet2, the same is returned. + preferred = network->getPreferredSubnet(subnet2); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // Even though the subnet1 has been most recently used, the preferred + // subnet is subnet3 in this case, because of the client class + // mismatch. + preferred = network->getPreferredSubnet(subnet3); + EXPECT_EQ(subnet3->getID(), preferred->getID()); + + // Same for subnet4. + preferred = network->getPreferredSubnet(subnet4); + EXPECT_EQ(subnet4->getID(), preferred->getID()); + + // Allocate an address from the subnet3. This makes it preferred to + // subnet4. + subnet3->setLastAllocated(Lease::TYPE_V4, IOAddress("172.16.25.23")); + + // If the selected is subnet1, the preferred subnet is subnet2, because + // it has the same set of classes as subnet1. The subnet3 can't be + // preferred here because of the client class mismatch. + preferred = network->getPreferredSubnet(subnet1); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // If we select subnet4, the preferred subnet is subnet3 because + // it was used more recently. + preferred = network->getPreferredSubnet(subnet4); + EXPECT_EQ(subnet3->getID(), preferred->getID()); + + // Repeat the test for subnet3 being a selected subnet. + preferred = network->getPreferredSubnet(subnet3); + EXPECT_EQ(subnet3->getID(), preferred->getID()); +} + +// This test verifies that preferred subnet is returned based on the timestamp +// when the subnet was last used and allowed client classes. +TEST(SharedNetwork4Test, getPreferredSubnetMultiThreading) { + MultiThreadingTest mt(true); + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + + // Create four subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + Subnet4Ptr subnet3(new Subnet4(IOAddress("172.16.25.0"), 24, 10, 20, 30, + SubnetID(3))); + Subnet4Ptr subnet4(new Subnet4(IOAddress("172.16.28.0"), 24, 10, 20, 30, + SubnetID(4))); + + // Associate first two subnets with classes. + subnet1->allowClientClass("class1"); + subnet2->allowClientClass("class1"); + + std::vector<Subnet4Ptr> subnets; + subnets.push_back(subnet1); + subnets.push_back(subnet2); + subnets.push_back(subnet3); + subnets.push_back(subnet4); + + // Subnets have unique IDs so they should successfully be added to the + // network. + for (auto i = 0; i < subnets.size(); ++i) { + ASSERT_NO_THROW(network->add(subnets[i])) + << "failed to add subnet with id " << subnets[i]->getID() + << " to shared network"; + } + + Subnet4Ptr preferred; + + // Initially, for every subnet we should get the same subnet as the preferred + // one, because none of them have been used. + for (auto i = 0; i < subnets.size(); ++i) { + preferred = network->getPreferredSubnet(subnets[i]); + EXPECT_EQ(subnets[i]->getID(), preferred->getID()); + } + + // Allocating an address from subnet2 updates the last allocated timestamp + // for this subnet, which makes this subnet preferred over subnet1. + subnet2->setLastAllocated(Lease::TYPE_V4, IOAddress("192.0.2.25")); + preferred = network->getPreferredSubnet(subnet1); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // If selected is subnet2, the same is returned. + preferred = network->getPreferredSubnet(subnet2); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // Even though the subnet1 has been most recently used, the preferred + // subnet is subnet3 in this case, because of the client class + // mismatch. + preferred = network->getPreferredSubnet(subnet3); + EXPECT_EQ(subnet3->getID(), preferred->getID()); + + // Same for subnet4. + preferred = network->getPreferredSubnet(subnet4); + EXPECT_EQ(subnet4->getID(), preferred->getID()); + + // Allocate an address from the subnet3. This makes it preferred to + // subnet4. + subnet3->setLastAllocated(Lease::TYPE_V4, IOAddress("172.16.25.23")); + + // If the selected is subnet1, the preferred subnet is subnet2, because + // it has the same set of classes as subnet1. The subnet3 can't be + // preferred here because of the client class mismatch. + preferred = network->getPreferredSubnet(subnet1); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // If we select subnet4, the preferred subnet is subnet3 because + // it was used more recently. + preferred = network->getPreferredSubnet(subnet4); + EXPECT_EQ(subnet3->getID(), preferred->getID()); + + // Repeat the test for subnet3 being a selected subnet. + preferred = network->getPreferredSubnet(subnet3); + EXPECT_EQ(subnet3->getID(), preferred->getID()); +} + +// This test verifies that subnetsIncludeMatchClientId() works as expected. +TEST(SharedNetwork4Test, subnetsIncludeMatchClientId) { + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + ClientClasses classes; + + // Create a subnet and add it to the shared network. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + subnet1->setMatchClientId(false); + ASSERT_NO_THROW(network->add(subnet1)); + + // The subnet does not match client id. + EXPECT_FALSE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes)); + + // Create a second subnet and add it. + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + ASSERT_NO_THROW(network->add(subnet2)); + + // Default is to match client id. + EXPECT_TRUE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes)); + + // Add a class. + classes.insert("class1"); + + //The second subnet is not guarded so matches. + EXPECT_TRUE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes)); + + // Put the second subnet in another class + subnet2->allowClientClass("class2"); + EXPECT_FALSE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes)); + + // Put the second subnet in the class. + subnet2->allowClientClass("class1"); + EXPECT_TRUE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes)); +} + +// This test verifies operations on the network's relay list +TEST(SharedNetwork4Test, relayInfoList) { + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + + EXPECT_FALSE(network->hasRelays()); + EXPECT_FALSE(network->hasRelayAddress(IOAddress("192.168.2.1"))); + + // Add relay addresses to the network. + network->addRelayAddress(IOAddress("192.168.2.1")); + network->addRelayAddress(IOAddress("192.168.2.2")); + network->addRelayAddress(IOAddress("192.168.2.3")); + + // Verify we believe we have relays and we can match them accordingly. + EXPECT_TRUE(network->hasRelays()); + EXPECT_TRUE(network->hasRelayAddress(IOAddress("192.168.2.1"))); + EXPECT_TRUE(network->hasRelayAddress(IOAddress("192.168.2.2"))); + EXPECT_TRUE(network->hasRelayAddress(IOAddress("192.168.2.3"))); + EXPECT_FALSE(network->hasRelayAddress(IOAddress("192.168.2.4"))); +} + +// This test verifies that unparsing shared network returns valid structure. +TEST(SharedNetwork4Test, unparse) { + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + + // Set interface name. + network->setIface("eth1"); + + network->setT1(100); + network->setT2(150); + network->setValid(200); + network->setMatchClientId(false); + + std::string uc = "{ \"comment\": \"bar\", \"foo\": 1}"; + data::ElementPtr ctx = data::Element::fromJSON(uc); + network->setContext(ctx); + network->requireClientClass("foo"); + network->addRelayAddress(IOAddress("192.168.2.1")); + network->setAuthoritative(false); + network->setMatchClientId(false); + network->setReservationsGlobal(false); + network->setReservationsInSubnet(true); + network->setReservationsOutOfPool(false); + + // Add several subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + subnet1->addRelayAddress(IOAddress("10.0.0.1")); + subnet1->addRelayAddress(IOAddress("10.0.0.2")); + + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + network->add(subnet1); + network->add(subnet2); + + std::string expected = "{\n" + " \"authoritative\": false,\n" + " \"interface\": \"eth1\",\n" + " \"match-client-id\": false,\n" + " \"name\": \"frog\",\n" + " \"option-data\": [ ],\n" + " \"rebind-timer\": 150,\n" + " \"relay\": {\n" + " \"ip-addresses\": [ \"192.168.2.1\" ]\n" + " },\n" + " \"renew-timer\": 100,\n" + " \"require-client-classes\": [ \"foo\" ],\n" + " \"reservations-global\": false,\n" + " \"reservations-in-subnet\": true,\n" + " \"reservations-out-of-pool\": false,\n" + " \"subnet4\": [\n" + " {\n" + " \"4o6-interface\": \"\",\n" + " \"4o6-interface-id\": \"\",\n" + " \"4o6-subnet\": \"\",\n" + " \"id\": 1,\n" + " \"option-data\": [ ],\n" + " \"pools\": [ ],\n" + " \"rebind-timer\": 20,\n" + " \"relay\": {\n" + " \"ip-addresses\": [ \"10.0.0.1\", \"10.0.0.2\" ]\n" + " },\n" + " \"renew-timer\": 10,\n" + " \"subnet\": \"10.0.0.0/8\",\n" + " \"valid-lifetime\": 30,\n" + " \"min-valid-lifetime\": 30,\n" + " \"max-valid-lifetime\": 30\n" + " },\n" + " {\n" + " \"4o6-interface\": \"\",\n" + " \"4o6-interface-id\": \"\",\n" + " \"4o6-subnet\": \"\",\n" + " \"id\": 2,\n" + " \"option-data\": [ ],\n" + " \"pools\": [ ],\n" + " \"rebind-timer\": 20,\n" + " \"relay\": {\n" + " \"ip-addresses\": [ ]\n" + " },\n" + " \"renew-timer\": 10,\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"valid-lifetime\": 30,\n" + " \"min-valid-lifetime\": 30,\n" + " \"max-valid-lifetime\": 30\n" + " }\n" + " ],\n" + " \"user-context\": { \"comment\": \"bar\", \"foo\": 1 },\n" + " \"valid-lifetime\": 200,\n" + " \"min-valid-lifetime\": 200,\n" + " \"max-valid-lifetime\": 200\n" + "}\n"; + + test::runToElementTest<SharedNetwork4>(expected, *network); +} + +// This test verifies that when the shared network object is destroyed, +// the subnets belonging to this shared network will not hold the pointer +// to the destroyed network. +TEST(SharedNetwork4Test, destructSharedNetwork) { + // Create a network and add a subnet to it. + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + Subnet4Ptr subnet(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + ASSERT_NO_THROW(network->add(subnet)); + + // Get the pointer to the network from subnet. + SharedNetwork4Ptr subnet_to_network; + subnet->getSharedNetwork(subnet_to_network); + ASSERT_TRUE(subnet_to_network); + + // Reset the pointer to not hold the reference to the shared network. + subnet_to_network.reset(); + + // Destroy the network object. + network.reset(); + + // The reference to the network from the subnet should be lost. + subnet->getSharedNetwork(subnet_to_network); + ASSERT_FALSE(subnet_to_network); +} + +// This test verifies that it is possible to remove all subnets. +TEST(SharedNetwork4Test, delAll) { + // Create two subnets and add them to the shared network. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30, + SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30, + SubnetID(2))); + + SharedNetwork4Ptr network(new SharedNetwork4("frog")); + ASSERT_NO_THROW(network->add(subnet1)); + ASSERT_NO_THROW(network->add(subnet2)); + + // Make sure they have been added successfully. + ASSERT_EQ(2, network->getAllSubnets()->size()); + + ASSERT_NO_THROW(network->delAll()); + + // Now check that there are no subnets. + ASSERT_EQ(0, network->getAllSubnets()->size()); +} + +// This test verifies that the SharedNetwork6 factory function creates a +// valid shared network instance. +TEST(SharedNetwork6Test, create) { + auto network = SharedNetwork6::create("frog"); + ASSERT_TRUE(network); + EXPECT_EQ("frog", network->getName()); +} + +// This test verifies the default values set for the shared +// networks and verifies that the optional values are unspecified. +TEST(SharedNetwork6Test, defaults) { + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + EXPECT_TRUE(network->getIface().unspecified()); + EXPECT_TRUE(network->getIface().empty()); + + EXPECT_TRUE(network->getClientClass().unspecified()); + EXPECT_TRUE(network->getClientClass().empty()); + + EXPECT_TRUE(network->getValid().unspecified()); + EXPECT_EQ(0, network->getValid().get()); + + EXPECT_TRUE(network->getT1().unspecified()); + EXPECT_EQ(0, network->getT1().get()); + + EXPECT_TRUE(network->getT2().unspecified()); + EXPECT_EQ(0, network->getT2().get()); + + EXPECT_TRUE(network->getReservationsGlobal().unspecified()); + EXPECT_FALSE(network->getReservationsGlobal().get()); + + EXPECT_TRUE(network->getReservationsInSubnet().unspecified()); + EXPECT_TRUE(network->getReservationsInSubnet().get()); + + EXPECT_TRUE(network->getReservationsOutOfPool().unspecified()); + EXPECT_FALSE(network->getReservationsOutOfPool().get()); + + EXPECT_TRUE(network->getCalculateTeeTimes().unspecified()); + EXPECT_FALSE(network->getCalculateTeeTimes().get()); + + EXPECT_TRUE(network->getT1Percent().unspecified()); + EXPECT_EQ(0.0, network->getT1Percent().get()); + + EXPECT_TRUE(network->getT2Percent().unspecified()); + EXPECT_EQ(0.0, network->getT2Percent().get()); + + EXPECT_TRUE(network->getPreferred().unspecified()); + EXPECT_EQ(0, network->getPreferred().get()); + + EXPECT_TRUE(network->getRapidCommit().unspecified()); + EXPECT_FALSE(network->getRapidCommit().get()); + + EXPECT_TRUE(network->getDdnsSendUpdates().unspecified()); + EXPECT_FALSE(network->getDdnsSendUpdates().get()); + + EXPECT_TRUE(network->getDdnsOverrideNoUpdate().unspecified()); + EXPECT_FALSE(network->getDdnsOverrideNoUpdate().get()); + + EXPECT_TRUE(network->getDdnsOverrideClientUpdate().unspecified()); + EXPECT_FALSE(network->getDdnsOverrideClientUpdate().get()); + + EXPECT_TRUE(network->getDdnsReplaceClientNameMode().unspecified()); + EXPECT_EQ(D2ClientConfig::RCM_NEVER, network->getDdnsReplaceClientNameMode().get()); + + EXPECT_TRUE(network->getDdnsGeneratedPrefix().unspecified()); + EXPECT_TRUE(network->getDdnsGeneratedPrefix().empty()); + + EXPECT_TRUE(network->getDdnsQualifyingSuffix().unspecified()); + EXPECT_TRUE(network->getDdnsQualifyingSuffix().empty()); + + EXPECT_TRUE(network->getHostnameCharSet().unspecified()); + EXPECT_TRUE(network->getHostnameCharSet().empty()); + + EXPECT_TRUE(network->getHostnameCharReplacement().unspecified()); + EXPECT_TRUE(network->getHostnameCharReplacement().empty()); +} + +// This test verifies that shared network can be given a name and that +// this name can be retrieved. +TEST(SharedNetwork6Test, getName) { + // Create shared network with an initial name "frog". + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + EXPECT_EQ("frog", network->getName()); + + // Override the name. + network->setName("dog"); + EXPECT_EQ("dog", network->getName()); +} + +// This test verifies that an IPv6 subnet can be added to a shared network. +// It also verifies that two subnets with the same ID can't be added to +// a shared network and that a single subnet can't be added to two different +// shared subnets. +TEST(SharedNetwork6Test, addSubnet6) { + // First, create a network. + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + + // Try to add null pointer. It should throw. + Subnet6Ptr subnet; + ASSERT_THROW(network->add(subnet), BadValue); + + // Create a valid subnet. It should now be added successfully. + subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, 40, + SubnetID(15))); + ASSERT_NO_THROW(network->add(subnet)); + ASSERT_EQ(1, network->getAllSubnets()->size()); + + // Retrieve the subnet from the network and make sure it is returned + // as expected. + Subnet6Ptr returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(subnet->getID(), returned_subnet->getID()); + SharedNetwork6Ptr network1; + subnet->getSharedNetwork(network1); + ASSERT_TRUE(network1); + EXPECT_TRUE(network1 == network); + + // Create another subnet with the same ID. Adding a network with the + // same ID should cause an error. + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40, + SubnetID(15))); + ASSERT_THROW(network->add(subnet2), DuplicateSubnetID); + + // Create another subnet with the same prefix. Adding a network with the + // same prefix should cause an error. + subnet2.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, 40, + SubnetID(1234))); + ASSERT_THROW(network->add(subnet2), DuplicateSubnetID); + + // Create another network and try to add a subnet to it. It should fail + // because the subnet is already associated with the first network. + SharedNetwork6Ptr network2(new SharedNetwork6("dog")); + ASSERT_THROW(network2->add(subnet), InvalidOperation); +} + +// This test verifies that an IPv6 subnet can be replaced in a shared network. +// It does the same tests than for addSubnet6 (at the exception of conflicts) +// and check the random order is kept. +TEST(SharedNetwork6Test, replaceSubnet6) { + // First, create a network. + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + + // Try to replace null pointer. It should throw. + Subnet6Ptr subnet; + ASSERT_THROW(network->replace(subnet), BadValue); + + // Create some valid subnets. they should now be added successfully. + subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 48, 10, 20, 30, 40, + SubnetID(15))); + ASSERT_NO_THROW(network->add(subnet)); + subnet.reset(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30, 40, + SubnetID(1))); + ASSERT_NO_THROW(network->add(subnet)); + subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), 64, 10, 20, 30, 40, + SubnetID(10))); + ASSERT_NO_THROW(network->add(subnet)); + ASSERT_EQ(3, network->getAllSubnets()->size()); + + // Create another subnet with another ID. Replace should return false. + subnet.reset(new Subnet6(IOAddress("2001:db8:4::1"), 64, 10, 20, 30, 40, + SubnetID(2))); + EXPECT_FALSE(network->replace(subnet)); + + // Subnets did not changed. + ASSERT_EQ(3, network->getAllSubnets()->size()); + auto returned_it = network->getAllSubnets()->begin(); + Subnet6Ptr returned_subnet = *returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(1, returned_subnet->getID()); + ++returned_it; + returned_subnet = *returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(10, returned_subnet->getID()); + ++returned_it; + returned_subnet = *returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(15, returned_subnet->getID()); + + // Reset the returned subnet to the subnet with subnet id 1. + returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getID()); + + // Create another subnet with the same ID than the second subnet. + subnet.reset(new Subnet6(IOAddress("2001:db8:2::"), 64, 100, 200, 300, 400, + SubnetID(1))); + EXPECT_TRUE(network->replace(subnet)); + + // Second subnet was updated. + EXPECT_EQ(10, returned_subnet->getT1()); + EXPECT_EQ(20, returned_subnet->getT2()); + EXPECT_EQ(30, returned_subnet->getPreferred()); + EXPECT_EQ(40, returned_subnet->getValid()); + SharedNetwork6Ptr network1; + returned_subnet->getSharedNetwork(network1); + EXPECT_FALSE(network1); + + ASSERT_EQ(3, network->getAllSubnets()->size()); + returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getID()); + EXPECT_EQ(100, returned_subnet->getT1()); + EXPECT_EQ(200, returned_subnet->getT2()); + EXPECT_EQ(300, returned_subnet->getPreferred()); + EXPECT_EQ(400, returned_subnet->getValid()); + returned_subnet->getSharedNetwork(network1); + EXPECT_TRUE(network1); + EXPECT_TRUE(network == network1); + + // Other subnets did not changed. + returned_it = network->getAllSubnets()->begin(); + returned_subnet = *++returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(10, returned_subnet->getID()); + returned_subnet = *++returned_it; + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(15, returned_subnet->getID()); + + // Create another network and try to replace a subnet to it. It should fail + // because the subnet is already associated with the first network. + SharedNetwork6Ptr network2(new SharedNetwork6("dog")); + ASSERT_THROW(network2->replace(subnet), InvalidOperation); + + // Try to change the prefix. Not recommended but should work. + subnet.reset(new Subnet6(IOAddress("2001:db8:10::"), 64, 100, 200, 300, + 400, SubnetID(1))); + EXPECT_TRUE(network->replace(subnet)); + ASSERT_EQ(3, network->getAllSubnets()->size()); + returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getID()); + EXPECT_EQ("2001:db8:10::/64", returned_subnet->toText()); + + // but not if the prefix already exists for another subnet. + subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), 64, 100, 200, 300, 400, + SubnetID(1))); + EXPECT_FALSE(network->replace(subnet)); + ASSERT_EQ(3, network->getAllSubnets()->size()); + returned_subnet = *network->getAllSubnets()->begin(); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getID()); + EXPECT_EQ("2001:db8:10::/64", returned_subnet->toText()); +} + +// This test verifies that it is possible to remove a specified subnet. +TEST(SharedNetwork6Test, delSubnet6) { + // Create two subnets and add them to the shared network. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, + 40, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40, + SubnetID(2))); + + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + ASSERT_NO_THROW(network->add(subnet1)); + ASSERT_NO_THROW(network->add(subnet2)); + + // Make sure they have been added successfully. + ASSERT_EQ(2, network->getAllSubnets()->size()); + + // Try to remove a subnet that doesn't exist in this shared network. + // It should cause an error. + ASSERT_THROW(network->del(SubnetID(5)), BadValue); + + // Now delete the subnet that exists. + ASSERT_NO_THROW(network->del(subnet1->getID())); + // We should be left with only one subnet. + ASSERT_EQ(1, network->getAllSubnets()->size()); + Subnet6Ptr subnet_returned = *network->getAllSubnets()->begin(); + ASSERT_TRUE(subnet_returned); + EXPECT_EQ(subnet2->getID(), subnet_returned->getID()); + + // Check that shared network has been cleared for the removed subnet. + SharedNetwork6Ptr network1; + subnet1->getSharedNetwork(network1); + EXPECT_FALSE(network1); + + // Remove another subnet and make sure there are no subnets left. + ASSERT_NO_THROW(network->del(subnet2->getID())); + EXPECT_EQ(0, network->getAllSubnets()->size()); + + // The network pointer should be cleared for this second subnet too. + SharedNetwork6Ptr network2; + subnet1->getSharedNetwork(network2); + EXPECT_FALSE(network2); +} + +// This test verifies that it is possible to iterate over the subnets +// associated with a particular shared network. +TEST(SharedNetwork6Test, getNextSubnet) { + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + + // Create three subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, + 40, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40, + SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30, + 40, SubnetID(3))); + std::vector<Subnet6Ptr> subnets; + subnets.push_back(subnet1); + subnets.push_back(subnet2); + subnets.push_back(subnet3); + + // Subnets have unique IDs so they should successfully be added to the + // network. + for (auto i = 0; i < subnets.size(); ++i) { + ASSERT_NO_THROW(network->add(subnets[i])) + << "failed to add subnet with id " << subnets[i]->getID() + << " to shared network"; + } + + // Collect networks associated with our subnets in the vector. + std::vector<SharedNetwork6Ptr> networks; + for (auto i = 0; i < subnets.size(); ++i) { + SharedNetwork6Ptr network; + subnets[i]->getSharedNetwork(network); + ASSERT_TRUE(network) << "failed to retrieve shared network for a" + << " subnet id " << subnets[i]->getID(); + networks.push_back(network); + } + + // All subnets should be associated with the same network. + for (auto i = 1; i < networks.size(); ++i) { + EXPECT_TRUE(networks[0] == networks[i]); + } + + // Perform the test 3 times where each subnet belonging to the shared + // network is treated as a "first" subnet in the call to getNextSubnet. + for (auto i = 0; i < subnets.size(); ++i) { + Subnet6Ptr s = subnets[i]; + + // Iterate over the subnets starting from the subnet with index i. + for (auto j = 0; j < subnets.size(); ++j) { + // Get next subnet (following the one currently in s). + s = networks[0]->getNextSubnet(subnets[i], s->getID()); + // The last iteration should return empty pointer to indicate end of + // the subnets within shared network. If we're not at last iteration + // check that the subnet identifier of the returned subnet is valid. + if (j < subnets.size() - 1) { + ASSERT_TRUE(s) << "retrieving next subnet failed for pair of" + " indexes (i, j) = (" << i << ", " << j << ")"; + const auto expected_subnet_id = (i + j + 1) % subnets.size() + 1; + EXPECT_EQ(expected_subnet_id, s->getID()); + } else { + // Null subnet returned for a last iteration. + ASSERT_FALSE(s) << "expected null pointer to be returned as" + " next subnet for pair of indexes (i, j) = (" + << i << ", " << j << ")"; + } + } + } +} + +// This test verifies that preferred subnet is returned based on the timestamp +// when the subnet was last used and allowed client classes. +TEST(SharedNetwork6Test, getPreferredSubnet) { + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + + // Create four subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, + 40, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40, + SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30, + 40, SubnetID(3))); + Subnet6Ptr subnet4(new Subnet6(IOAddress("3000:1::"), 64, 10, 20, 30, + 40, SubnetID(4))); + + // Associate first two subnets with classes. + subnet1->allowClientClass("class1"); + subnet2->allowClientClass("class1"); + + std::vector<Subnet6Ptr> subnets; + subnets.push_back(subnet1); + subnets.push_back(subnet2); + subnets.push_back(subnet3); + subnets.push_back(subnet4); + + // Subnets have unique IDs so they should successfully be added to the + // network. + for (auto i = 0; i < subnets.size(); ++i) { + ASSERT_NO_THROW(network->add(subnets[i])) + << "failed to add subnet with id " << subnets[i]->getID() + << " to shared network"; + } + + Subnet6Ptr preferred; + + // Initially, for every subnet we should get the same subnet as the preferred + // one, because none of them have been used. + for (auto i = 0; i < subnets.size(); ++i) { + preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_NA); + EXPECT_EQ(subnets[i]->getID(), preferred->getID()); + preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_TA); + EXPECT_EQ(subnets[i]->getID(), preferred->getID()); + preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_PD); + EXPECT_EQ(subnets[i]->getID(), preferred->getID()); + } + + // Allocating an address from subnet2 updates the last allocated timestamp + // for this subnet, which makes this subnet preferred over subnet1. + subnet2->setLastAllocated(Lease::TYPE_NA, IOAddress("2001:db8:1:2::")); + preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // If selected is subnet2, the same is returned. + preferred = network->getPreferredSubnet(subnet2, Lease::TYPE_NA); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // The preferred subnet is dependent on the lease type. For the PD + // we should get the same subnet as selected. + preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD); + EXPECT_EQ(subnet1->getID(), preferred->getID()); + + // Although, if we pick a prefix from the subnet2, we should get the + // subnet2 as preferred instead. + subnet2->setLastAllocated(Lease::TYPE_PD, IOAddress("3000:1234::")); + preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // Even though the subnet1 has been most recently used, the preferred + // subnet is subnet3 in this case, because of the client class + // mismatch. + preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA); + EXPECT_EQ(subnet3->getID(), preferred->getID()); + + // Same for subnet4. + preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA); + EXPECT_EQ(subnet4->getID(), preferred->getID()); + + // Allocate an address from the subnet3. This makes it preferred to + // subnet4. + subnet3->setLastAllocated(Lease::TYPE_NA, IOAddress("2001:db8:2:1234::")); + + // If the selected is subnet1, the preferred subnet is subnet2, because + // it has the same set of classes as subnet1. The subnet3 can't be + // preferred here because of the client class mismatch. + preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // If we select subnet4, the preferred subnet is subnet3 because + // it was used more recently. + preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA); + EXPECT_EQ(subnet3->getID(), preferred->getID()); + + // Repeat the test for subnet3 being a selected subnet. + preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA); + EXPECT_EQ(subnet3->getID(), preferred->getID()); +} + +// This test verifies that preferred subnet is returned based on the timestamp +// when the subnet was last used and allowed client classes. +TEST(SharedNetwork6Test, getPreferredSubnetMultiThreading) { + MultiThreadingTest mt(true); + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + + // Create four subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, + 40, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40, + SubnetID(2))); + Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30, + 40, SubnetID(3))); + Subnet6Ptr subnet4(new Subnet6(IOAddress("3000:1::"), 64, 10, 20, 30, + 40, SubnetID(4))); + + // Associate first two subnets with classes. + subnet1->allowClientClass("class1"); + subnet2->allowClientClass("class1"); + + std::vector<Subnet6Ptr> subnets; + subnets.push_back(subnet1); + subnets.push_back(subnet2); + subnets.push_back(subnet3); + subnets.push_back(subnet4); + + // Subnets have unique IDs so they should successfully be added to the + // network. + for (auto i = 0; i < subnets.size(); ++i) { + ASSERT_NO_THROW(network->add(subnets[i])) + << "failed to add subnet with id " << subnets[i]->getID() + << " to shared network"; + } + + Subnet6Ptr preferred; + + // Initially, for every subnet we should get the same subnet as the preferred + // one, because none of them have been used. + for (auto i = 0; i < subnets.size(); ++i) { + preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_NA); + EXPECT_EQ(subnets[i]->getID(), preferred->getID()); + preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_TA); + EXPECT_EQ(subnets[i]->getID(), preferred->getID()); + preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_PD); + EXPECT_EQ(subnets[i]->getID(), preferred->getID()); + } + + // Allocating an address from subnet2 updates the last allocated timestamp + // for this subnet, which makes this subnet preferred over subnet1. + subnet2->setLastAllocated(Lease::TYPE_NA, IOAddress("2001:db8:1:2::")); + preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // If selected is subnet2, the same is returned. + preferred = network->getPreferredSubnet(subnet2, Lease::TYPE_NA); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // The preferred subnet is dependent on the lease type. For the PD + // we should get the same subnet as selected. + preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD); + EXPECT_EQ(subnet1->getID(), preferred->getID()); + + // Although, if we pick a prefix from the subnet2, we should get the + // subnet2 as preferred instead. + subnet2->setLastAllocated(Lease::TYPE_PD, IOAddress("3000:1234::")); + preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // Even though the subnet1 has been most recently used, the preferred + // subnet is subnet3 in this case, because of the client class + // mismatch. + preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA); + EXPECT_EQ(subnet3->getID(), preferred->getID()); + + // Same for subnet4. + preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA); + EXPECT_EQ(subnet4->getID(), preferred->getID()); + + // Allocate an address from the subnet3. This makes it preferred to + // subnet4. + subnet3->setLastAllocated(Lease::TYPE_NA, IOAddress("2001:db8:2:1234::")); + + // If the selected is subnet1, the preferred subnet is subnet2, because + // it has the same set of classes as subnet1. The subnet3 can't be + // preferred here because of the client class mismatch. + preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA); + EXPECT_EQ(subnet2->getID(), preferred->getID()); + + // If we select subnet4, the preferred subnet is subnet3 because + // it was used more recently. + preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA); + EXPECT_EQ(subnet3->getID(), preferred->getID()); + + // Repeat the test for subnet3 being a selected subnet. + preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA); + EXPECT_EQ(subnet3->getID(), preferred->getID()); +} + +// This test verifies operations on the network's relay list +TEST(SharedNetwork6Test, relayInfoList) { + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + + EXPECT_FALSE(network->hasRelays()); + EXPECT_FALSE(network->hasRelayAddress(IOAddress("2001:db8:2::1"))); + + // Add relay addresses to the network. + network->addRelayAddress(IOAddress("2001:db8:2::1")); + network->addRelayAddress(IOAddress("2001:db8:2::2")); + network->addRelayAddress(IOAddress("2001:db8:2::3")); + + // Verify we believe we have relays and we can match them accordingly. + EXPECT_TRUE(network->hasRelays()); + EXPECT_TRUE(network->hasRelayAddress(IOAddress("2001:db8:2::1"))); + EXPECT_TRUE(network->hasRelayAddress(IOAddress("2001:db8:2::2"))); + EXPECT_TRUE(network->hasRelayAddress(IOAddress("2001:db8:2::3"))); + EXPECT_FALSE(network->hasRelayAddress(IOAddress("2001:db8:2::4"))); +} + +// This test verifies that unparsing shared network returns valid structure. +TEST(SharedNetwork6Test, unparse) { + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + network->setIface("eth1"); + network->setT1(100); + network->setT2(150); + network->setPreferred(200); + network->setValid(300); + network->setRapidCommit(true); + network->requireClientClass("foo"); + + data::ElementPtr ctx = data::Element::fromJSON("{ \"foo\": \"bar\" }"); + network->setContext(ctx); + network->requireClientClass("foo"); + + network->addRelayAddress(IOAddress("2001:db8:1::7")); + network->addRelayAddress(IOAddress("2001:db8:1::8")); + + network->setRapidCommit(true); + network->setReservationsGlobal(false); + network->setReservationsInSubnet(true); + network->setReservationsOutOfPool(false); + + // Include interface-id at shared network level. After unparsing the + // network we should only see it at shared network level and not at + // the subnet level. + std::string iface_id_value = "vlan102"; + OptionBuffer iface_id_buffer(iface_id_value.begin(), iface_id_value.end()); + OptionPtr iface_id_opt(new Option(Option::V6, D6O_INTERFACE_ID, iface_id_buffer)); + network->setInterfaceId(iface_id_opt); + + // Add several subnets. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, + 40, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40, + SubnetID(2))); + subnet2->addRelayAddress(IOAddress("2001:db8:1::8")); + + // Set subnet specific interface-id for subnet2. This is to ensure that + // the subnet specific value is not overridden by shared network specific + // value. + std::string subnet_interface_id_value = "vlan222"; + OptionBuffer subnet_iface_id_buffer(subnet_interface_id_value.begin(), + subnet_interface_id_value.end()); + OptionPtr subnet_iface_id_opt(new Option(Option::V6, D6O_INTERFACE_ID, subnet_iface_id_buffer)); + subnet2->setInterfaceId(subnet_iface_id_opt); + + network->add(subnet1); + network->add(subnet2); + + std::string expected = "{\n" + " \"interface\": \"eth1\",\n" + " \"interface-id\": \"vlan102\",\n" + " \"name\": \"frog\",\n" + " \"option-data\": [ ],\n" + " \"preferred-lifetime\": 200,\n" + " \"min-preferred-lifetime\": 200,\n" + " \"max-preferred-lifetime\": 200,\n" + " \"rapid-commit\": true,\n" + " \"rebind-timer\": 150,\n" + " \"relay\": {\n" + " \"ip-addresses\": [ \"2001:db8:1::7\", \"2001:db8:1::8\" ]\n" + " },\n" + " \"renew-timer\": 100,\n" + " \"require-client-classes\": [ \"foo\" ],\n" + " \"reservations-global\": false,\n" + " \"reservations-in-subnet\": true,\n" + " \"reservations-out-of-pool\": false,\n" + " \"subnet6\": [\n" + " {\n" + " \"id\": 1,\n" + " \"option-data\": [ ],\n" + " \"pd-pools\": [ ],\n" + " \"pools\": [ ],\n" + " \"preferred-lifetime\": 30,\n" + " \"min-preferred-lifetime\": 30,\n" + " \"max-preferred-lifetime\": 30,\n" + " \"rebind-timer\": 20,\n" + " \"relay\": {\n" + " \"ip-addresses\": [ ]\n" + " },\n" + " \"renew-timer\": 10,\n" + " \"subnet\": \"2001:db8:1::/64\",\n" + " \"valid-lifetime\": 40,\n" + " \"min-valid-lifetime\": 40,\n" + " \"max-valid-lifetime\": 40\n" + " },\n" + " {\n" + " \"id\": 2,\n" + " \"interface-id\": \"vlan222\",\n" + " \"option-data\": [ ],\n" + " \"pd-pools\": [ ],\n" + " \"pools\": [ ],\n" + " \"preferred-lifetime\": 30,\n" + " \"min-preferred-lifetime\": 30,\n" + " \"max-preferred-lifetime\": 30,\n" + " \"rebind-timer\": 20,\n" + " \"relay\": {\n" + " \"ip-addresses\": [ \"2001:db8:1::8\" ]\n" + " },\n" + " \"renew-timer\": 10,\n" + " \"subnet\": \"3000::/16\",\n" + " \"valid-lifetime\": 40,\n" + " \"min-valid-lifetime\": 40,\n" + " \"max-valid-lifetime\": 40\n" + " }\n" + " ],\n" + " \"user-context\": { \"foo\": \"bar\" },\n" + " \"valid-lifetime\": 300,\n" + " \"min-valid-lifetime\": 300,\n" + " \"max-valid-lifetime\": 300\n" + "}\n"; + + test::runToElementTest<SharedNetwork6>(expected, *network); +} + +// This test verifies that when the shared network object is destroyed, +// the subnets belonging to this shared network will not hold the pointer +// to the destroyed network. +TEST(SharedNetwork6Test, destructSharedNetwork) { + // Create a network and add a subnet to it. + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, + 40, SubnetID(1))); + ASSERT_NO_THROW(network->add(subnet)); + + // Get the pointer to the network from subnet. + SharedNetwork6Ptr subnet_to_network; + subnet->getSharedNetwork(subnet_to_network); + ASSERT_TRUE(subnet_to_network); + + // Reset the pointer to not hold the reference to the shared network. + subnet_to_network.reset(); + + // Destroy the network object. + network.reset(); + + // The reference to the network from the subnet should be lost. + subnet->getSharedNetwork(subnet_to_network); + ASSERT_FALSE(subnet_to_network); +} + +// This test verifies that it is possible to remove all subnets. +TEST(SharedNetwork6Test, delAll) { + // Create two subnets and add them to the shared network. + Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, + 40, SubnetID(1))); + Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40, + SubnetID(2))); + + SharedNetwork6Ptr network(new SharedNetwork6("frog")); + ASSERT_NO_THROW(network->add(subnet1)); + ASSERT_NO_THROW(network->add(subnet2)); + + // Make sure they have been added successfully. + ASSERT_EQ(2, network->getAllSubnets()->size()); + + ASSERT_NO_THROW(network->delAll()); + + // Now check that there are no subnets. + ASSERT_EQ(0, network->getAllSubnets()->size()); +} + +// This test verifies that the IPv4 shared network can be fetched by name. +TEST(SharedNetworkFetcherTest, getSharedNetwork4ByName) { + SharedNetwork4Collection collection; + + // Shared network hasn't been added to the collection. A null pointer should + // be returned. + auto network = SharedNetworkFetcher4::get(collection, "network1"); + EXPECT_FALSE(network); + + network.reset(new SharedNetwork4("network1")); + EXPECT_NO_THROW(collection.push_back(network)); + + network.reset(new SharedNetwork4("network2")); + EXPECT_NO_THROW(collection.push_back(network)); + + network = SharedNetworkFetcher4::get(collection, "network1"); + ASSERT_TRUE(network); + EXPECT_EQ("network1", network->getName()); + + network = SharedNetworkFetcher4::get(collection, "network2"); + ASSERT_TRUE(network); + EXPECT_EQ("network2", network->getName()); +} + +// This test verifies that the IPv6 shared network can be fetched by name. +TEST(SharedNetworkFetcherTest, getSharedNetwork6ByName) { + SharedNetwork6Collection collection; + + // Shared network hasn't been added to the collection. A null pointer should + // be returned. + auto network = SharedNetworkFetcher6::get(collection, "network1"); + EXPECT_FALSE(network); + + network.reset(new SharedNetwork6("network1")); + EXPECT_NO_THROW(collection.push_back(network)); + + network.reset(new SharedNetwork6("network2")); + EXPECT_NO_THROW(collection.push_back(network)); + + network = SharedNetworkFetcher6::get(collection, "network1"); + ASSERT_TRUE(network); + EXPECT_EQ("network1", network->getName()); + + network = SharedNetworkFetcher6::get(collection, "network2"); + ASSERT_TRUE(network); + EXPECT_EQ("network2", network->getName()); +} + +} // end of anonymous namespace |