// Copyright (C) 2017-2024 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc; using namespace isc::asiolink; using namespace isc::dhcp; using namespace isc::test; namespace { class TestSubnetIterativeAllocationState; /// @brief Shared pointer to the @c TestSubnetIterativeAllocationState typedef boost::shared_ptr TestSubnetIterativeAllocationStatePtr; /// @brief A derivation exposing the @c last_allocated_time_ member. class TestSubnetIterativeAllocationState : public SubnetIterativeAllocationState { public: /// @brief Creates the state instance. /// /// @param subnet subnet instance for which the state is created. /// @return state instance. static TestSubnetIterativeAllocationStatePtr create(const SubnetPtr& subnet) { auto subnet_prefix = subnet->get(); return (boost::make_shared (subnet_prefix.first, subnet_prefix.second)); } /// @brief Constructor. /// /// @param prefix subnet prefix. /// @param prefix_length subnet prefix length. TestSubnetIterativeAllocationState(const IOAddress& prefix, const uint8_t prefix_length) : SubnetIterativeAllocationState(prefix, prefix_length) { } using SubnetIterativeAllocationState::last_allocated_time_; }; // 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().get()); EXPECT_EQ(20, returned_subnet->getT2().get()); EXPECT_EQ(30, returned_subnet->getValid().get()); 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().get()); EXPECT_EQ(200, returned_subnet->getT2().get()); EXPECT_EQ(300, returned_subnet->getValid().get()); 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 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 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 << ")"; auto const 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 five subnets. auto subnet1 = Subnet4::create(IOAddress("10.0.0.0"), 8, 10, 20, 30, SubnetID(1)); auto subnet2 = Subnet4::create(IOAddress("192.0.2.0"), 24, 10, 20, 30, SubnetID(2)); auto subnet3 = Subnet4::create(IOAddress("172.16.25.0"), 24, 10, 20, 30, SubnetID(3)); auto subnet4 = Subnet4::create(IOAddress("172.16.28.0"), 24, 10, 20, 30, SubnetID(4)); auto subnet5 = Subnet4::create(IOAddress("172.16.30.0"), 24, 10, 20, 30, SubnetID(5)); auto state1 = TestSubnetIterativeAllocationState::create(subnet1); auto state2 = TestSubnetIterativeAllocationState::create(subnet1); auto state3 = TestSubnetIterativeAllocationState::create(subnet1); auto state4 = TestSubnetIterativeAllocationState::create(subnet1); auto state5 = TestSubnetIterativeAllocationState::create(subnet1); subnet1->setAllocationState(Lease::TYPE_V4, state1); subnet2->setAllocationState(Lease::TYPE_V4, state2); subnet3->setAllocationState(Lease::TYPE_V4, state3); subnet4->setAllocationState(Lease::TYPE_V4, state4); subnet5->setAllocationState(Lease::TYPE_V4, state5); // Associate first two subnets with classes. subnet1->allowClientClass("class1"); subnet2->allowClientClass("class1"); std::vector subnets; subnets.push_back(subnet1); subnets.push_back(subnet2); subnets.push_back(subnet3); subnets.push_back(subnet4); subnets.push_back(subnet5); // 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. state2->setLastAllocated(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()); // Same for subnet5. preferred = network->getPreferredSubnet(subnet5); EXPECT_EQ(subnet5->getID(), preferred->getID()); // Allocate an address from the subnet3. This makes it preferred to // subnet4 and subnet5. state3->setLastAllocated(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()); // Allocate an address from remaining subnets and make sure that the // allocation from the subnet4 is slightly more recent. Both are // more recent than subnet3. state4->setLastAllocated(IOAddress("172.16.28.24")); state5->setLastAllocated(IOAddress("172.16.30.1")); state4->last_allocated_time_ += boost::posix_time::seconds(2); state5->last_allocated_time_ += boost::posix_time::seconds(1); // The subnet4 should now be preferred. preferred = network->getPreferredSubnet(subnet3); EXPECT_EQ(subnet4->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. auto subnet1 = Subnet4::create(IOAddress("10.0.0.0"), 8, 10, 20, 30, SubnetID(1)); auto subnet2 = Subnet4::create(IOAddress("192.0.2.0"), 24, 10, 20, 30, SubnetID(2)); auto subnet3 = Subnet4::create(IOAddress("172.16.25.0"), 24, 10, 20, 30, SubnetID(3)); auto subnet4 = Subnet4::create(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 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 auto state2 = boost::dynamic_pointer_cast (subnet2->getAllocationState(Lease::TYPE_V4)); state2->setLastAllocated(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. auto state3 = boost::dynamic_pointer_cast (subnet3->getAllocationState(Lease::TYPE_V4)); state3->setLastAllocated(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(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().get()); EXPECT_EQ(20, returned_subnet->getT2().get()); EXPECT_EQ(30, returned_subnet->getPreferred().get()); EXPECT_EQ(40, returned_subnet->getValid().get()); 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().get()); EXPECT_EQ(200, returned_subnet->getT2().get()); EXPECT_EQ(300, returned_subnet->getPreferred().get()); EXPECT_EQ(400, returned_subnet->getValid().get()); 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 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 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 << ")"; auto const 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. auto subnet1 = Subnet6::create(IOAddress("2001:db8:1::"), 64, 10, 20, 30, 40, SubnetID(1)); auto subnet2 = Subnet6::create(IOAddress("3000::"), 16, 10, 20, 30, 40, SubnetID(2)); auto subnet3 = Subnet6::create(IOAddress("2001:db8:2::"), 64, 10, 20, 30, 40, SubnetID(3)); auto subnet4 = Subnet6::create(IOAddress("3000:1::"), 64, 10, 20, 30, 40, SubnetID(4)); // Associate first two subnets with classes. subnet1->allowClientClass("class1"); subnet2->allowClientClass("class1"); std::vector 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 auto state = boost::dynamic_pointer_cast (subnet2->getAllocationState(Lease::TYPE_NA)); state->setLastAllocated(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. state = boost::dynamic_pointer_cast (subnet2->getAllocationState(Lease::TYPE_PD)); state->setLastAllocated(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. state = boost::dynamic_pointer_cast (subnet3->getAllocationState(Lease::TYPE_NA)); state->setLastAllocated(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. auto subnet1 = Subnet6::create(IOAddress("2001:db8:1::"), 64, 10, 20, 30, 40, SubnetID(1)); auto subnet2 = Subnet6::create(IOAddress("3000::"), 16, 10, 20, 30, 40, SubnetID(2)); auto subnet3 = Subnet6::create(IOAddress("2001:db8:2::"), 64, 10, 20, 30, 40, SubnetID(3)); auto subnet4 = Subnet6::create(IOAddress("3000:1::"), 64, 10, 20, 30, 40, SubnetID(4)); // Associate first two subnets with classes. subnet1->allowClientClass("class1"); subnet2->allowClientClass("class1"); std::vector 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. auto state = boost::dynamic_pointer_cast (subnet2->getAllocationState(Lease::TYPE_NA)); state->setLastAllocated(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. state = boost::dynamic_pointer_cast (subnet2->getAllocationState(Lease::TYPE_PD)); state->setLastAllocated(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. state = boost::dynamic_pointer_cast (subnet3->getAllocationState(Lease::TYPE_NA)); state->setLastAllocated(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(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