summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc')
-rw-r--r--src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc1512
1 files changed, 1512 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
new file mode 100644
index 0000000..58c0d03
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
@@ -0,0 +1,1512 @@
+// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <set>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test fixture class for testing @c CfgHost object holding
+/// host reservations specified in the configuration file.
+class CfgHostsTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor allocates a collection of @c HWAddr and @c DuidPtr
+ /// objects used by the unit tests.
+ ///
+ /// The allocated HW addresses use the following pattern: 01:02:0A:BB:03:XX
+ /// where XX is a number between 0 and 0x32. All of them are of the
+ /// HTYPE_ETHER type.
+ ///
+ /// The allocated DUID LLTs use the following pattern:
+ /// 01:02:03:04:05:06:07:08:09:0A:XX where the XX is a number between
+ /// 0 and 0x32.
+ CfgHostsTest();
+
+ /// @brief Destructor.
+ ///
+ /// This destructor resets global state after tests are run.
+ ~CfgHostsTest();
+
+ /// @brief Increases last byte of an address.
+ ///
+ /// @param address Address to be increased.
+ IOAddress increase(const IOAddress& address, const uint8_t num) const;
+
+ /// @brief Collection of HW address objects allocated for unit tests.
+ std::vector<HWAddrPtr> hwaddrs_;
+ /// @brief Collection of DUIDs allocated for unit tests.
+ std::vector<DuidPtr> duids_;
+ /// @brief Collection of IPv4 address objects allocated for unit tests.
+ std::vector<IOAddress> addressesa_;
+ std::vector<IOAddress> addressesb_;
+};
+
+CfgHostsTest::CfgHostsTest() {
+ const uint8_t mac_template[] = {
+ 0x01, 0x02, 0x0A, 0xBB, 0x03, 0x00
+ };
+ for (unsigned i = 0; i < 50; ++i) {
+ std::vector<uint8_t> vec(mac_template,
+ mac_template + sizeof(mac_template));
+ vec[vec.size() - 1] = i;
+ HWAddrPtr hwaddr(new HWAddr(vec, HTYPE_ETHER));
+ hwaddrs_.push_back(hwaddr);
+ }
+
+ const uint8_t duid_template[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00
+ };
+ for (unsigned i = 0; i < 50; ++i) {
+ std::vector<uint8_t> vec(duid_template,
+ duid_template + sizeof(mac_template));
+ vec[vec.size() - 1] = i;
+ DuidPtr duid(new DUID(vec));
+ duids_.push_back(duid);
+ }
+
+ const uint32_t addra_template = 0xc0000205; // 192.0.2.5
+ const uint32_t addrb_template = 0xc00a020a; // 192.10.2.10
+ for (unsigned i = 0; i < 50; ++i) {
+ IOAddress addra(addra_template + i);
+ addressesa_.push_back(addra);
+ IOAddress addrb(addrb_template + i);
+ addressesb_.push_back(addrb);
+ }
+}
+
+CfgHostsTest::~CfgHostsTest() {
+ CfgMgr::instance().setFamily(AF_INET);
+}
+
+IOAddress
+CfgHostsTest::increase(const IOAddress& address, const uint8_t num) const {
+ std::vector<uint8_t> vec = address.toBytes();
+ if (!vec.empty()) {
+ vec[vec.size() - 1] += num;
+ return (IOAddress::fromBytes(address.getFamily(), &vec[0]));
+ }
+ return (address);
+}
+
+// This test checks that hosts with unique HW addresses and DUIDs can be
+// retrieved from the host configuration.
+TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by HW address and 25 hosts identified by
+ // DUID. They are added to different subnets.
+ for (unsigned i = 0; i < 25; ++i) {
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(i % 10 + 1), SubnetID(i % 5 + 1),
+ addressesa_[i])));
+
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(i % 5 + 1), SubnetID(i % 10 + 1),
+ addressesb_[i])));
+
+ }
+
+ // Try to retrieve each added reservation using HW address and DUID. Do it
+ // in the reverse order to make sure that the order doesn't matter.
+ for (int i = 24; i >= 0; --i) {
+ // Get host identified by HW address.
+ HostCollection hosts = cfg.getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(i % 10 + 1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(addressesa_[i].toText(),
+ hosts[0]->getIPv4Reservation().toText());
+
+ // Get host identified by DUID.
+ hosts = cfg.getAll(Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0],
+ duids_[i]->getDuid().size());
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(i % 5 + 1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(addressesb_[i].toText(),
+ hosts[0]->getIPv4Reservation().toText());
+ }
+
+ // Make sure that the reservations do not exist for the hardware addresses
+ // and DUIDs from the range of 25 to 49.
+ for (int i = 49; i >= 25; --i) {
+ EXPECT_TRUE(cfg.getAll(Host::IDENT_HWADDR, &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size()).empty());
+ EXPECT_TRUE(cfg.getAll(Host::IDENT_DUID, &duids_[i]->getDuid()[0],
+ duids_[i]->getDuid().size()).empty());
+ }
+}
+
+// This test verifies that the host can be added to multiple subnets and
+// that the getAll message retrieves all instances of the host.
+TEST_F(CfgHostsTest, getAllRepeatingHosts) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add two hosts, using the same HW address to two distinct subnets.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(2),
+ addressesa_[i])));
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(2), SubnetID(3),
+ addressesb_[i])));
+
+ // Add two hosts, using the same DUID to two distinct subnets.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1), SubnetID(2),
+ addressesb_[i])));
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(2), SubnetID(3),
+ addressesa_[i])));
+ }
+
+ // Verify that hosts can be retrieved.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Get host by HW address.
+ HostCollection hosts = cfg.getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ ASSERT_EQ(2, hosts.size());
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(addressesa_[i], hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ(2, hosts[1]->getIPv4SubnetID());
+ EXPECT_EQ(addressesb_[i], hosts[1]->getIPv4Reservation().toText());
+
+ // The HW address is non-null but there are no reservations
+ // for the HW addresses from the range of 25 to 49.
+ hosts = cfg.getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[i + 25]->hwaddr_[0],
+ hwaddrs_[i + 25]->hwaddr_.size());
+ EXPECT_TRUE(hosts.empty());
+
+ // Get host by DUID.
+ hosts = cfg.getAll(Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0],
+ duids_[i]->getDuid().size());
+
+ // The DUID is non-null but there are no reservations
+ // for the DUIDs from the range of 25 to 49.
+ hosts = cfg.getAll(Host::IDENT_DUID,
+ &duids_[i + 25]->getDuid()[0],
+ duids_[i + 25]->getDuid().size());
+ EXPECT_TRUE(hosts.empty());
+ }
+}
+
+// This test checks that hosts in the same subnet can be retrieved from
+// the host configuration.
+TEST_F(CfgHostsTest, getAll4BySubnet) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by HW address in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(1),
+ addressesa_[i])));
+ }
+
+ // Check that other subnets are empty.
+ HostCollection hosts = cfg.getAll4(SubnetID(100));
+ EXPECT_EQ(0, hosts.size());
+
+ // Try to retrieve all added reservations.
+ hosts = cfg.getAll4(SubnetID(1));
+ ASSERT_EQ(25, hosts.size());
+ for (unsigned i = 0; i < 25; ++i) {
+ EXPECT_EQ(1, hosts[i]->getIPv4SubnetID());
+ EXPECT_EQ(addressesa_[i].toText(),
+ hosts[i]->getIPv4Reservation().toText());
+ }
+}
+
+// This test checks that hosts in the same subnet can be retrieved from
+// the host configuration.
+TEST_F(CfgHostsTest, getAll6BySubnet) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by DUID in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1), SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ // Check that other subnets are empty.
+ HostCollection hosts = cfg.getAll6(SubnetID(100));
+ EXPECT_EQ(0, hosts.size());
+
+ // Try to retrieve all added reservations.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(25, hosts.size());
+ for (unsigned i = 0; i < 25; ++i) {
+ EXPECT_EQ(1, hosts[i]->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ hosts[i]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ reservations.first->second.getPrefix());
+ }
+}
+
+// This test checks that hosts with the same reserved address can be retrieved
+// from the host configuration.
+TEST_F(CfgHostsTest, getAll6ByAddress) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by DUID in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(i+1),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i % 5)));
+ cfg.add(host);
+ }
+
+ // Try to retrieve all added reservations with IP equals 2001:db8:1::1.
+ auto hosts = cfg.getAll6(IOAddress("2001:db8:1::1"));
+ EXPECT_EQ(5, hosts.size());
+ for (unsigned i = 0; i < 5; ++i) {
+ EXPECT_EQ(1 + 5 * i, hosts[i]->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ hosts[i]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(IOAddress("2001:db8:1::1"),
+ reservations.first->second.getPrefix());
+ }
+}
+
+// This test checks that hosts in the same subnet can be retrieved from
+// the host configuration by pages.
+TEST_F(CfgHostsTest, getPage4) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by DUID in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1), SubnetID(1),
+ addressesa_[i])));
+ }
+ size_t idx(0);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+
+ // Check that other subnets are empty.
+ HostCollection page = cfg.getPage4(SubnetID(100), idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+
+ // Try to retrieve all added reservations.
+ // Get first page.
+ page = cfg.getPage4(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+
+ // Get second and last pages.
+ page = cfg.getPage4(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ page = cfg.getPage4(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ page = cfg.getPage4(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+}
+
+// This test checks that hosts in the same subnet can be retrieved from
+// the host configuration by pages.
+TEST_F(CfgHostsTest, getPage6) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by HW address in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+ }
+ size_t idx(0);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+
+ // Check that other subnets are empty.
+ HostCollection page = cfg.getPage6(SubnetID(100), idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+
+ // Try to retrieve all added reservations.
+ // Get first page.
+ page = cfg.getPage6(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+
+ // Get second and last pages.
+ page = cfg.getPage6(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ page = cfg.getPage6(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ page = cfg.getPage6(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+}
+
+// This test checks that all hosts can be retrieved from the host
+// configuration by pages.
+TEST_F(CfgHostsTest, getPage4All) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by DUID.
+ for (unsigned i = 0; i < 25; ++i) {
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(i), SubnetID(i),
+ addressesa_[i])));
+ }
+ size_t idx(0);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+
+ // Try to retrieve all added reservations.
+ // Get first page.
+ HostCollection page = cfg.getPage4(idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+
+ // Get second and last pages.
+ page = cfg.getPage4(idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ page = cfg.getPage4(idx, host_id, page_size);
+ EXPECT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ page = cfg.getPage4(idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+}
+
+// This test checks that all hosts can be retrieved from the host
+// configuration by pages.
+TEST_F(CfgHostsTest, getPage6All) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by HW address.
+ for (unsigned i = 0; i < 25; ++i) {
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(i), SubnetID(i),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+ }
+ size_t idx(0);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+
+ // Try to retrieve all added reservations.
+ // Get first page.
+ HostCollection page = cfg.getPage6(idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+
+ // Get second and last pages.
+ page = cfg.getPage6(idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ page = cfg.getPage6(idx, host_id, page_size);
+ EXPECT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ page = cfg.getPage6(idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+}
+
+// This test checks that all reservations for the specified IPv4 address can
+// be retrieved.
+TEST_F(CfgHostsTest, getAll4ByAddress) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by the HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.5"))));
+ // Add host identified by the DUID.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(),
+ "duid",
+ SubnetID(1 + i), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.10"))));
+ }
+
+ HostCollection hosts = cfg.getAll4(IOAddress("192.0.2.10"));
+ std::set<uint32_t> subnet_ids;
+ for (HostCollection::const_iterator host = hosts.begin(); host != hosts.end();
+ ++host) {
+ subnet_ids.insert((*host)->getIPv4SubnetID());
+ }
+ ASSERT_EQ(25, subnet_ids.size());
+ EXPECT_EQ(1, *subnet_ids.begin());
+ EXPECT_EQ(25, *subnet_ids.rbegin());
+}
+
+// This test checks that the IPv4 reservation for the specified IPv4 address can
+// be deleted.
+TEST_F(CfgHostsTest, deleteForIPv4) {
+ CfgHosts cfg;
+ // Add hosts.
+ IOAddress address("10.0.0.42");
+ SubnetID subnet_id(42);
+ size_t host_count = 10;
+
+ for (size_t i = 0; i < host_count; i++)
+ {
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ subnet_id, SUBNET_ID_UNUSED,
+ increase(address, i))));
+ }
+
+ // Get all inserted hosts.
+ HostCollection hosts_by_subnet = cfg.getAll4(subnet_id);
+ HostCollection hosts_by_address = cfg.getAll4(address);
+ // Make sure the hosts and IP reservations were added.
+ ASSERT_EQ(host_count, hosts_by_subnet.size());
+ ASSERT_EQ(1, hosts_by_address.size());
+
+ // Delete one host.
+ EXPECT_TRUE(cfg.del(subnet_id, address));
+
+ // Check if the host is actually deleted.
+ hosts_by_subnet = cfg.getAll4(subnet_id);
+ hosts_by_address = cfg.getAll4(address);
+ EXPECT_EQ(host_count-1, hosts_by_subnet.size());
+ EXPECT_EQ(0, hosts_by_address.size());
+}
+
+// This test checks that the IPv6 reservation for the specified subnet ID and
+// IPv6 address can be deleted.
+TEST_F(CfgHostsTest, deleteForIPv6) {
+ CfgHosts cfg;
+ // Add hosts.
+ IOAddress address("2001:db8:1::1");
+ size_t host_count = 10;
+ SubnetID subnet_id(42);
+
+ for (size_t i = 0; i < host_count; i++)
+ {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, subnet_id,
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress(address), i)));
+ cfg.add(host);
+ }
+
+
+ // Get all inserted hosts.
+ auto hosts_by_subnet_and_address = cfg.getAll6(subnet_id, address);
+ auto hosts_by_subnet = cfg.getAll6(subnet_id);
+ // Make sure the hosts and IP reservations were added.
+ ASSERT_EQ(1, hosts_by_subnet_and_address.size());
+ ASSERT_EQ(host_count, hosts_by_subnet.size());
+
+ // Delete one host.
+ EXPECT_TRUE(cfg.del(subnet_id, address));
+
+ // Check if the host is actually deleted.
+ hosts_by_subnet_and_address = cfg.getAll6(subnet_id, address);
+ hosts_by_subnet = cfg.getAll6(subnet_id);
+ EXPECT_EQ(0, hosts_by_subnet_and_address.size());
+ EXPECT_EQ(host_count-1, hosts_by_subnet.size());
+}
+
+// This test checks that false is returned for deleting the IPv4 reservation
+// that doesn't exist.
+TEST_F(CfgHostsTest, deleteForMissingIPv4) {
+ CfgHosts cfg;
+
+ // Delete non-existent host.
+ EXPECT_FALSE(cfg.del(SubnetID(42), IOAddress(("10.0.0.42"))));
+}
+
+// This test checks that false is returned for deleting the IPv6 reservation
+// that doesn't exist.
+TEST_F(CfgHostsTest, deleteForMissingIPv6) {
+ CfgHosts cfg;
+
+ // Delete non-existent host.
+ EXPECT_FALSE(cfg.del(SubnetID(42), IOAddress(("2001:db8:1::1"))));
+}
+
+// This test checks that the reservation for the specified IPv4 subnet and
+// identifier can be deleted.
+TEST_F(CfgHostsTest, del4) {
+ CfgHosts cfg;
+
+ // Add hosts.
+ size_t host_count = 20;
+ size_t host_id = 5;
+ SubnetID subnet_id(42);
+ IOAddress address("10.0.0.1");
+
+ // Add half of the hosts with the same subnet ID but differ with DUID and
+ // address.
+ for (size_t i = 0; i < host_count / 2; i++) {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ subnet_id, SUBNET_ID_UNUSED,
+ increase(address, i)));
+ cfg.add(host);
+ }
+ // Add half of the hosts with the same subnet DUID and address but
+ // differ with address.
+ for (size_t i = 0; i < host_count / 2; i++) {
+ HostPtr host = HostPtr(new Host(duids_[host_id]->toText(), "duid",
+ SubnetID(subnet_id + i + 1), SUBNET_ID_UNUSED,
+ increase(address, host_id)));
+ cfg.add(host);
+ }
+
+
+ // Get all inserted hosts.
+ HostCollection hosts_by_subnet = cfg.getAll4(subnet_id);
+ HostCollection hosts_by_address = cfg.getAll4(increase(address, host_id));
+ HostPtr host = cfg.get4(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0],
+ duids_[host_id]->getDuid().size());
+ // Make sure the hosts and IP reservations were added.
+ ASSERT_EQ(host_count / 2, hosts_by_subnet.size());
+ ASSERT_EQ(host_count / 2 + 1, hosts_by_address.size());
+ ASSERT_TRUE(host);
+
+ // Delete one host.
+ EXPECT_TRUE(cfg.del4(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0], duids_[host_id]->getDuid().size()));
+
+ // Check if the host is actually deleted.
+ hosts_by_subnet = cfg.getAll4(subnet_id);
+ hosts_by_address = cfg.getAll4(increase(address, host_id));
+ host = cfg.get4(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0],
+ duids_[host_id]->getDuid().size());
+ EXPECT_EQ((host_count / 2)-1, hosts_by_subnet.size());
+ EXPECT_EQ(host_count / 2, hosts_by_address.size());
+ EXPECT_FALSE(host);
+}
+
+// This test checks that the host and its reservations for the specified IPv6
+// subnet and identifier can be deleted.
+TEST_F(CfgHostsTest, del6) {
+ CfgHosts cfg;
+
+ // Add hosts.
+ size_t host_count = 20;
+ size_t host_id = 5;
+ SubnetID subnet_id(42);
+ IOAddress address("2001:db8:1::1");
+
+ // Add half of the hosts with the same subnet ID but differ with DUID and
+ // address.
+ for (size_t i = 0; i < host_count / 2; i++) {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, subnet_id,
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress(address), i)));
+ cfg.add(host);
+ }
+ // Add half of the hosts with the same subnet DUID and address but
+ // differ with address.
+ for (size_t i = 0; i < host_count / 2; i++) {
+ HostPtr host = HostPtr(new Host(duids_[host_id]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(subnet_id + i + 1),
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(address, host_id)));
+ cfg.add(host);
+ }
+
+
+ // Get all inserted hosts.
+ HostCollection hosts_by_subnet = cfg.getAll6(subnet_id);
+ HostCollection hosts_by_address = cfg.getAll6(increase(address, host_id));
+ HostPtr host = cfg.get6(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0],
+ duids_[host_id]->getDuid().size());
+ // Make sure the hosts and IP reservations were added.
+ ASSERT_EQ(host_count / 2, hosts_by_subnet.size());
+ ASSERT_EQ(host_count / 2 + 1, hosts_by_address.size());
+ ASSERT_TRUE(host);
+
+ // Delete one host.
+ EXPECT_TRUE(cfg.del6(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0], duids_[host_id]->getDuid().size()));
+
+ // Check if the host is actually deleted.
+ hosts_by_subnet = cfg.getAll6(subnet_id);
+ hosts_by_address = cfg.getAll6(increase(address, host_id));
+ host = cfg.get6(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0],
+ duids_[host_id]->getDuid().size());
+ EXPECT_EQ((host_count / 2)-1, hosts_by_subnet.size());
+ EXPECT_EQ(host_count / 2, hosts_by_address.size());
+ EXPECT_FALSE(host);
+}
+
+// This test checks that false is returned for deleting the IPv4 host that
+// doesn't exist.
+TEST_F(CfgHostsTest, del4MissingHost) {
+ CfgHosts cfg;
+ EXPECT_FALSE(cfg.del4(SubnetID(42), Host::IdentifierType::IDENT_DUID,
+ &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()));
+}
+
+// This test checks that false is returned for deleting the IPv6 host that
+// doesn't exist.
+TEST_F(CfgHostsTest, del6MissingHost) {
+ CfgHosts cfg;
+ EXPECT_FALSE(cfg.del6(SubnetID(42), Host::IdentifierType::IDENT_DUID,
+ &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()));
+}
+
+// This test checks that all reservations for the specified IPv4 subnet can
+// be deleted.
+TEST_F(CfgHostsTest, deleteAll4) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Hosts will differ by hostname. It is easier than differentiating by
+ // IPv4 address because if they all have zero IPv4 address it is
+ // easier to retrieve all of them to check the host counts.
+ std::ostringstream s;
+ s << "hostname" << i;
+
+ // Add host identified by the HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i % 2), SUBNET_ID_UNUSED,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ s.str())));
+ }
+
+ // Get all inserted hosts.
+ HostCollection hosts = cfg.getAll4(IOAddress::IPV4_ZERO_ADDRESS());
+ std::set<uint32_t> subnet_ids;
+ for (HostCollection::const_iterator host = hosts.begin(); host != hosts.end();
+ ++host) {
+ subnet_ids.insert((*host)->getIPv4SubnetID());
+ }
+ // Make sure there are two unique subnets: 1 and 2.
+ ASSERT_EQ(2, subnet_ids.size());
+ EXPECT_EQ(1, *subnet_ids.begin());
+ EXPECT_EQ(2, *subnet_ids.rbegin());
+
+ // Delete all hosts for subnet id 2. There should be 12 of them.
+ EXPECT_EQ(12, cfg.delAll4(SubnetID(2)));
+
+ // Gather the host counts again.
+ subnet_ids.clear();
+ hosts = cfg.getAll4(IOAddress::IPV4_ZERO_ADDRESS());
+ for (HostCollection::const_iterator host = hosts.begin(); host != hosts.end();
+ ++host) {
+ subnet_ids.insert((*host)->getIPv4SubnetID());
+ }
+ // We should only have hosts for one subnet and it should be the subnet
+ // with ID of 1.
+ ASSERT_EQ(1, subnet_ids.size());
+ EXPECT_EQ(1, *subnet_ids.begin());
+}
+
+// This test checks that the reservations can be retrieved for the particular
+// host connected to the specific IPv4 subnet (by subnet id).
+TEST_F(CfgHostsTest, get4) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i % 2), SubnetID(13),
+ increase(IOAddress("192.0.2.5"), i))));
+
+ // Add host identified by DUID.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1 + i % 2), SubnetID(13),
+ increase(IOAddress("192.0.2.100"), i))));
+ }
+
+ for (unsigned i = 0; i < 25; ++i) {
+ // Retrieve host by HW address.
+ HostPtr host = cfg.get4(SubnetID(1 + i % 2), Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
+ EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
+ host->getIPv4Reservation());
+
+ // Retrieve host by DUID.
+ host = cfg.get4(SubnetID(1 + i % 2), Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0], duids_[i]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
+ EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
+ host->getIPv4Reservation());
+
+ }
+}
+
+// This test checks that the DHCPv4 reservations can be unparsed
+TEST_F(CfgHostsTest, unparsed4) {
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHosts cfg;
+ CfgHostsList list;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i), SubnetID(13),
+ increase(IOAddress("192.0.2.5"), i))));
+
+ // Add host identified by DUID.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1 + i), SubnetID(13),
+ increase(IOAddress("192.0.2.100"), i))));
+ }
+
+ using namespace isc::data;
+ ConstElementPtr cfg_unparsed;
+ ASSERT_NO_THROW(cfg_unparsed = cfg.toElement());
+ ASSERT_NO_THROW(list.internalize(cfg_unparsed));
+ for (unsigned i = 0; i < 25; ++i) {
+ ConstElementPtr unparsed = list.get(SubnetID(1 + i));
+ ASSERT_TRUE(unparsed);
+ ASSERT_EQ(Element::list, unparsed->getType());
+ EXPECT_EQ(2, unparsed->size());
+ ASSERT_NE(0, unparsed->size());
+
+ // Check by HW address entries
+ bool checked_hw = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("hw-address")) {
+ continue;
+ }
+ checked_hw = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("duid"));
+ // Check the HW address
+ ConstElementPtr hw = host->get("hw-address");
+ ASSERT_TRUE(hw);
+ ASSERT_EQ(Element::string, hw->getType());
+ EXPECT_EQ(hwaddrs_[i]->toText(false), hw->stringValue());
+ // Check the reservation
+ ConstElementPtr resv = host->get("ip-address");
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_hw);
+
+ // Check by DUID entries
+ bool checked_duid = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("duid")) {
+ continue;
+ }
+ checked_duid = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("hw-address"));
+ // Check the DUID
+ ConstElementPtr duid = host->get("duid");
+ ASSERT_TRUE(duid);
+ ASSERT_EQ(Element::string, duid->getType());
+ EXPECT_EQ(duids_[i]->toText(), duid->stringValue());
+ // Check the reservation
+ ConstElementPtr resv = host->get("ip-address");
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_duid);
+ }
+}
+
+// This test checks that the reservations can be retrieved for the particular
+// host connected to the specific IPv6 subnet (by subnet id).
+TEST_F(CfgHostsTest, get6) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(10), SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+
+ // Add host identified by DUID.
+ host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(10), SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:2::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ for (unsigned i = 0; i < 25; ++i) {
+ // Retrieve host by HW address.
+ HostPtr host = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ reservations.first->second.getPrefix());
+
+ // Retrieve host by DUID.
+ host = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0], duids_[i]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+ reservations = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+ reservations.first->second.getPrefix());
+ }
+}
+
+// This test checks that all reservations for the specified IPv6 subnet can
+// be deleted.
+TEST_F(CfgHostsTest, deleteAll6) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address. The subnet for which we're
+ // adding the host has id of 1 for even values of i and 2 for
+ // odd values of i.
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(10), SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ // Delete all hosts for subnet id. There should be 13 of them.
+ EXPECT_EQ(13, cfg.delAll6(SubnetID(1)));
+
+ for (unsigned i = 0; i < 25; ++i) {
+ // Calculate subnet id for the given i.
+ SubnetID subnet_id = 1 + i % 2;
+
+ // Try to retrieve host by HW address.
+ HostPtr host = cfg.get6(subnet_id, Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ // The host should exist for subnet id of 2.
+ if (subnet_id == 2) {
+ ASSERT_TRUE(host);
+ EXPECT_EQ(subnet_id, host->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ reservations.first->second.getPrefix());
+
+ } else {
+ // All hosts for subnet id 1 should be gone.
+ EXPECT_FALSE(host);
+ }
+ }
+}
+
+// This test checks that the DHCPv6 reservations can be unparsed
+TEST_F(CfgHostsTest, unparse6) {
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHosts cfg;
+ CfgHostsList list;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(10), SubnetID(1 + i),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+
+ // Add host identified by DUID.
+ host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(10), SubnetID(1 + i),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:2::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ using namespace isc::data;
+ ConstElementPtr cfg_unparsed;
+ ASSERT_NO_THROW(cfg_unparsed = cfg.toElement());
+ ASSERT_NO_THROW(list.internalize(cfg_unparsed));
+ for (unsigned i = 0; i < 25; ++i) {
+ ConstElementPtr unparsed = list.get(SubnetID(1 + i));
+ ASSERT_TRUE(unparsed);
+ ASSERT_EQ(Element::list, unparsed->getType());
+ EXPECT_EQ(2, unparsed->size());
+ ASSERT_NE(0, unparsed->size());
+
+ // Check by HW address entries
+ bool checked_hw = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("hw-address")) {
+ continue;
+ }
+ checked_hw = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("duid"));
+ // Check the HW address
+ ConstElementPtr hw = host->get("hw-address");
+ ASSERT_TRUE(hw);
+ ASSERT_EQ(Element::string, hw->getType());
+ EXPECT_EQ(hwaddrs_[i]->toText(false), hw->stringValue());
+ // Check the reservation
+ ConstElementPtr resvs = host->get("ip-addresses");
+ ASSERT_TRUE(resvs);
+ ASSERT_EQ(Element::list, resvs->getType());
+ EXPECT_EQ(1, resvs->size());
+ ASSERT_GE(1, resvs->size());
+ ConstElementPtr resv = resvs->get(0);
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_hw);
+
+ // Check by DUID entries
+ bool checked_duid = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("duid")) {
+ continue;
+ }
+ checked_duid = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("hw-address"));
+ // Check the DUID
+ ConstElementPtr duid = host->get("duid");
+ ASSERT_TRUE(duid);
+ ASSERT_EQ(Element::string, duid->getType());
+ EXPECT_EQ(duids_[i]->toText(), duid->stringValue());
+ // Check the reservation
+ ConstElementPtr resvs = host->get("ip-addresses");
+ ASSERT_TRUE(resvs);
+ ASSERT_EQ(Element::list, resvs->getType());
+ EXPECT_EQ(1, resvs->size());
+ ASSERT_GE(1, resvs->size());
+ ConstElementPtr resv = resvs->get(0);
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_duid);
+ }
+}
+
+// This test checks that the IPv6 reservations can be retrieved for a particular
+// (subnet-id, address) tuple.
+TEST_F(CfgHostsTest, get6ByAddr) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+
+ // Add host identified by DUID.
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:2::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ for (unsigned i = 0; i < 25; ++i) {
+ // Retrieve host by (subnet-id,address).
+ HostPtr host = cfg.get6(SubnetID(1 + i % 2),
+ increase(IOAddress("2001:db8:2::1"), i));
+ ASSERT_TRUE(host);
+
+ EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+ reservations.first->second.getPrefix());
+ }
+}
+
+// This test checks that the IPv6 reservations can be retrieved for a particular
+// (subnet-id, address) tuple.
+TEST_F(CfgHostsTest, get6MultipleAddrs) {
+ CfgHosts cfg;
+
+ // Add 25 hosts. Each host has reservations for 5 addresses.
+ for (unsigned i = 0; i < 25; ++i) {
+
+ // Add host identified by DUID.
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+
+ // Generate 5 unique addresses for this host.
+ for (unsigned j = 0; j < 5; ++j) {
+ std::stringstream address_stream;
+ address_stream << "2001:db8:" << i << "::" << j;
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ address_stream.str()));
+ }
+ cfg.add(host);
+ }
+
+ // Now check if we can retrieve each of those 25 hosts by using each
+ // of their addresses.
+ for (unsigned i = 0; i < 25; ++i) {
+
+ // Check that the host is there.
+ HostPtr by_duid = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0],
+ duids_[i]->getDuid().size());
+ ASSERT_TRUE(by_duid);
+
+ for (unsigned j = 0; j < 5; ++j) {
+ std::stringstream address_stream;
+ address_stream << "2001:db8:" << i << "::" << j;
+
+ // Retrieve host by (subnet-id,address).
+ HostPtr by_addr = cfg.get6(SubnetID(1 + i % 2),
+ address_stream.str());
+ ASSERT_TRUE(by_addr);
+
+ // The pointers should match. Maybe we should compare contents
+ // rather than just pointers? I think there's no reason why
+ // the code would make any copies of the Host object, so
+ // the pointers should always point to the same object.
+ EXPECT_EQ(by_duid, by_addr);
+ }
+ }
+}
+
+
+// Checks that it's not possible for a second host to reserve an address
+// which is already reserved.
+TEST_F(CfgHostsTest, add4AlreadyReserved) {
+ CfgHosts cfg;
+
+ // First host has a reservation for address 192.0.2.1
+ HostPtr host1 = HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(SUBNET_ID_UNUSED),
+ IOAddress("192.0.2.1")));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(hwaddrs_[1]->toText(false),
+ "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.1")));
+
+ // This second host has a reservation for an address that is already
+ // reserved for the first host, so it should be rejected.
+ EXPECT_THROW(cfg.add(host2), isc::dhcp::ReservedAddress);
+}
+
+// Test that it is possible to allow inserting multiple reservations for
+// the same IP address.
+TEST_F(CfgHostsTest, allow4AlreadyReserved) {
+ CfgHosts cfg;
+ // Allow creating multiple reservations for the same IP address.
+ ASSERT_TRUE(cfg.setIPReservationsUnique(false));
+
+ // First host has a reservation for address 192.0.2.1
+ HostPtr host1 = HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(SUBNET_ID_UNUSED),
+ IOAddress("192.0.2.1")));
+ ASSERT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(hwaddrs_[1]->toText(false),
+ "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.1")));
+ // Adding this should work because the HW address is different.
+ ASSERT_NO_THROW(cfg.add(host2));
+
+ // Get both hosts.
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = cfg.getAll4(host1->getIPv4SubnetID(), IOAddress("192.0.2.1")));
+ EXPECT_EQ(2, returned.size());
+
+ // Make sure the address is the same but the identifiers are different.
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+ EXPECT_EQ(returned[0]->getIPv4Reservation().toText(),
+ returned[1]->getIPv4Reservation().toText());
+}
+
+// Checks that it's not possible for two hosts to have the same address
+// reserved at the same time.
+TEST_F(CfgHostsTest, add6Invalid2Hosts) {
+ CfgHosts cfg;
+
+ // First host has a reservation for address 2001:db8::1
+ HostPtr host1 = HostPtr(new Host(duids_[0]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(duids_[1]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+
+ // This second host has a reservation for an address that is already
+ // reserved for the first host, so it should be rejected.
+ EXPECT_THROW(cfg.add(host2), isc::dhcp::DuplicateHost);
+}
+
+// Test that it is possible to allow inserting multiple reservations for
+// the same IPv6 address.
+TEST_F(CfgHostsTest, allowAddress6AlreadyReserved) {
+ CfgHosts cfg;
+ // Allow creating multiple reservations for the same IP address.
+ ASSERT_TRUE(cfg.setIPReservationsUnique(false));
+
+ // First host has a reservation for address 2001:db8::1
+ HostPtr host1 = HostPtr(new Host(duids_[0]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(duids_[1]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+
+ // Adding this should work because the DUID is different.
+ ASSERT_NO_THROW(cfg.add(host2));
+
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = cfg.getAll6(host1->getIPv6SubnetID(), IOAddress("2001:db8::1")));
+ EXPECT_EQ(2, returned.size());
+
+ // Make sure the address is the same but the identifiers are different.
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+ auto range0 = returned[0]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ EXPECT_EQ(1, std::distance(range0.first, range0.second));
+ auto range1 = returned[1]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ EXPECT_EQ(1, std::distance(range1.first, range1.second));
+ EXPECT_EQ(range0.first->second.getPrefix().toText(),
+ range1.first->second.getPrefix().toText());
+}
+
+// Test that it is possible to allow inserting multiple reservations for
+// the same IPv6 delegated prefix.
+TEST_F(CfgHostsTest, allowPrefix6AlreadyReserved) {
+ CfgHosts cfg;
+ // Allow creating multiple reservations for the same delegated prefix.
+ ASSERT_TRUE(cfg.setIPReservationsUnique(false));
+
+ // First host has a reservation for prefix 3000::/64.
+ HostPtr host1 = HostPtr(new Host(duids_[0]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("3000::"), 64));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same prefix.
+ HostPtr host2 = HostPtr(new Host(duids_[1]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("3000::"), 64));
+
+ // Adding this should work because the DUID is different.
+ ASSERT_NO_THROW(cfg.add(host2));
+
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = cfg.getAll6(host1->getIPv6SubnetID(), IOAddress("3000::")));
+ EXPECT_EQ(2, returned.size());
+
+ // Make sure the prefix is the same but the identifiers are different.
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+ auto range0 = returned[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ EXPECT_EQ(1, std::distance(range0.first, range0.second));
+ auto range1 = returned[1]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ EXPECT_EQ(1, std::distance(range1.first, range1.second));
+ EXPECT_EQ(range0.first->second.getPrefix().toText(),
+ range1.first->second.getPrefix().toText());
+}
+
+// Check that no error is reported when adding a host with subnet
+// ids equal to global.
+TEST_F(CfgHostsTest, globalSubnetIDs) {
+ CfgHosts cfg;
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_GLOBAL, SUBNET_ID_GLOBAL,
+ IOAddress("10.0.0.1")))));
+}
+
+
+// Check that error is reported when trying to add a host with subnet
+// ids equal to unused.
+TEST_F(CfgHostsTest, unusedSubnetIDs) {
+ CfgHosts cfg;
+ ASSERT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_UNUSED, SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.1")))),
+ isc::BadValue);
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
+ CfgHosts cfg;
+ // Add a host.
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.1")))));
+
+ // Try to add the host with the same HW address to the same subnet. The fact
+ // that the IP address is different here shouldn't really matter.
+ EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.10")))),
+ isc::dhcp::DuplicateHost);
+
+ // Now try to add it to a different subnet. It should go through.
+ EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(11), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.10")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
+ CfgHosts cfg;
+ // Add a host.
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.1")))));
+
+ // Try to add the host with the same DUID to the same subnet. The fact
+ // that the IP address is different here shouldn't really matter.
+ EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.10")))),
+ isc::dhcp::DuplicateHost);
+
+ // Now try to add it to a different subnet. It should go through.
+ EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SubnetID(11), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.10")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv6 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
+ CfgHosts cfg;
+ // Add a host.
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))));
+
+ // Try to add the host with the same HW address to the same subnet. The fact
+ // that the IP address is different here shouldn't really matter.
+ EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))),
+ isc::dhcp::DuplicateHost);
+
+ // Now try to add it to a different subnet. It should go through.
+ EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(2),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv6 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
+ CfgHosts cfg;
+ // Add a host.
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))));
+
+ // Try to add the host with the same DUID to the same subnet. The fact
+ // that the IP address is different here shouldn't really matter.
+ EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))),
+ isc::dhcp::DuplicateHost);
+
+ // Now try to add it to a different subnet. It should go through.
+ EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SUBNET_ID_UNUSED, SubnetID(2),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))));
+}
+
+// Checks that updates work correctly.
+TEST_F(CfgHostsTest, update) {
+ CfgHosts cfg;
+
+ HostPtr const host(boost::make_shared<Host>(duids_[0]->toText(), "duid", SUBNET_ID_UNUSED,
+ SubnetID(1), IOAddress("0.0.0.0"),
+ "foo.example.com"));
+
+ // Updating a host that doesn't exist should throw.
+ EXPECT_THROW_MSG(cfg.update(host), HostNotFound, "Host not updated (not found).");
+
+ // There should be no hosts.
+ HostCollection hosts(cfg.getAll6(SubnetID(1)));
+ EXPECT_EQ(0, hosts.size());
+
+ // Add a host.
+ EXPECT_NO_THROW(cfg.add(host));
+
+ // The host should be in the config.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("duid=010203040500 ipv6_subnet_id=1 hostname=foo.example.com "
+ "ipv4_reservation=(no) siaddr=(no) sname=(empty) file=(empty) "
+ "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
+
+ // Update the host. Change nothing.
+ EXPECT_NO_THROW(cfg.update(host));
+
+ // The same host should be in the config.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("duid=010203040500 ipv6_subnet_id=1 hostname=foo.example.com "
+ "ipv4_reservation=(no) siaddr=(no) sname=(empty) file=(empty) "
+ "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
+
+ // Update the host with new hostname.
+ host->setHostname("bar.example.com");
+ EXPECT_NO_THROW(cfg.update(host));
+
+ // The change should be reflected in the config.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("duid=010203040500 ipv6_subnet_id=1 hostname=bar.example.com "
+ "ipv4_reservation=(no) siaddr=(no) sname=(empty) file=(empty) "
+ "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
+
+ // Remove hostname from host.
+ host->setHostname("");
+ EXPECT_NO_THROW(cfg.update(host));
+
+ // The change should be reflected in the config.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("duid=010203040500 ipv6_subnet_id=1 hostname=(empty) "
+ "ipv4_reservation=(no) siaddr=(no) sname=(empty) file=(empty) "
+ "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
+}
+
+} // namespace