diff options
Diffstat (limited to 'src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc')
-rw-r--r-- | src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc | 4747 |
1 files changed, 4747 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc new file mode 100644 index 0000000..7322d52 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc @@ -0,0 +1,4747 @@ +// Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/addr_utilities.h> +#include <database/database_connection.h> +#include <database/db_exceptions.h> +#include <database/server.h> +#include <database/testutils/schema.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option6_addrlst.h> +#include <dhcp/option_int.h> +#include <dhcp/option_space.h> +#include <dhcp/option_string.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/client_class_def.h> +#include <dhcpsrv/config_backend_dhcp6_mgr.h> +#include <dhcpsrv/pool.h> +#include <dhcpsrv/subnet.h> +#include <dhcpsrv/testutils/generic_cb_dhcp6_unittest.h> +#include <dhcpsrv/testutils/test_utils.h> +#include <testutils/gtest_utils.h> + +#include <boost/make_shared.hpp> +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::util; +using namespace isc::asiolink; +using namespace isc::db; +using namespace isc::db::test; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using namespace isc::process; +using namespace isc::test; +namespace ph = std::placeholders; + +void +GenericConfigBackendDHCPv6Test::SetUp() { + CfgMgr::instance().setFamily(AF_INET6); + + // Ensure we have the proper schema with no transient data. + createSchema(); + + try { + // Create a connection parameter map and use it to start the backend. + DatabaseConnection::ParameterMap params = + DatabaseConnection::parse(validConnectionString()); + cbptr_ = backendFactory(params); + } catch (...) { + std::cerr << "*** ERROR: unable to open database. The test\n" + "*** environment is broken and must be fixed before\n" + "*** the tests will run correctly.\n" + "*** The reason for the problem is described in the\n" + "*** accompanying exception output.\n"; + throw; + } + + // Create test data. + initTestServers(); + initTestOptions(); + initTestSubnets(); + initTestSharedNetworks(); + initTestOptionDefs(); + initTestClientClasses(); +} + +void +GenericConfigBackendDHCPv6Test::TearDown() { + cbptr_.reset(); + // If data wipe enabled, delete transient data otherwise destroy the schema. + destroySchema(); +} + +db::AuditEntryCollection +GenericConfigBackendDHCPv6Test::getRecentAuditEntries(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time, + const uint64_t& modification_id) const { + return (cbptr_->getRecentAuditEntries(server_selector, modification_time, modification_id)); +} + +void +GenericConfigBackendDHCPv6Test::initTestServers() { + test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1")); + test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1 bis")); + test_servers_.push_back(Server::create(ServerTag("server2"), "this is server 2")); + test_servers_.push_back(Server::create(ServerTag("server3"), "this is server 3")); +} + +void +GenericConfigBackendDHCPv6Test::initTestSubnets() { + // First subnet includes all parameters. + std::string interface_id_text = "whale"; + OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end()); + OptionPtr opt_interface_id(new Option(Option::V6, D6O_INTERFACE_ID, + interface_id)); + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 64, + 30, 40, 50, 60, 1024)); + subnet->allowClientClass("home"); + subnet->setIface("eth1"); + subnet->setInterfaceId(opt_interface_id); + subnet->setT2(323212); + subnet->addRelayAddress(IOAddress("2001:db8:1::2")); + subnet->addRelayAddress(IOAddress("2001:db8:3::4")); + subnet->setT1(1234); + subnet->requireClientClass("required-class1"); + subnet->requireClientClass("required-class2"); + subnet->setReservationsGlobal(false); + subnet->setReservationsInSubnet(false); + subnet->setContext(user_context); + subnet->setValid(555555); + subnet->setPreferred(4444444); + subnet->setCalculateTeeTimes(true); + subnet->setT1Percent(0.345); + subnet->setT2Percent(0.444); + subnet->setDdnsSendUpdates(false); + + Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8::10"), + IOAddress("2001:db8::20"))); + subnet->addPool(pool1); + + Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8::50"), + IOAddress("2001:db8::60"))); + subnet->addPool(pool2); + + Pool6Ptr pdpool1(new Pool6(Lease::TYPE_PD, + IOAddress("2001:db8:a::"), 48, 64)); + subnet->addPool(pdpool1); + + Pool6Ptr pdpool2(new Pool6(Lease::TYPE_PD, + IOAddress("2001:db8:b::"), 48, 64)); + subnet->addPool(pdpool2); + // Add several options to the subnet. + subnet->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_); + + subnet->getCfgOption()->add(test_options_[1]->option_, + test_options_[1]->persistent_, + test_options_[1]->space_name_); + + subnet->getCfgOption()->add(test_options_[2]->option_, + test_options_[2]->persistent_, + test_options_[2]->space_name_); + + test_subnets_.push_back(subnet); + + // Adding another subnet with the same subnet id to test + // cases that this second instance can override existing + // subnet instance. + subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), + 48, 20, 30, 40, 50, 1024)); + + pool1.reset(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8:1::10"), + IOAddress("2001:db8:1::20"))); + subnet->addPool(pool1); + + pool1->getCfgOption()->add(test_options_[3]->option_, + test_options_[3]->persistent_, + test_options_[3]->space_name_); + + pool1->getCfgOption()->add(test_options_[4]->option_, + test_options_[4]->persistent_, + test_options_[4]->space_name_); + + pool2.reset(new Pool6(Lease::TYPE_NA, + IOAddress("2001:db8:1::50"), + IOAddress("2001:db8:1::60"))); + subnet->addPool(pool2); + + pool2->allowClientClass("work"); + pool2->requireClientClass("required-class3"); + pool2->requireClientClass("required-class4"); + user_context = Element::createMap(); + user_context->set("bar", Element::create("foo")); + pool2->setContext(user_context); + pdpool1.reset(new Pool6(IOAddress("2001:db8:c::"), 48, 64, + IOAddress("2001:db8:c::1"), 96)); + subnet->addPool(pdpool1); + + pdpool1->getCfgOption()->add(test_options_[3]->option_, + test_options_[3]->persistent_, + test_options_[3]->space_name_); + + pdpool1->getCfgOption()->add(test_options_[4]->option_, + test_options_[4]->persistent_, + test_options_[4]->space_name_); + + // Create the prefix delegation pool with an excluded prefix. + pdpool2.reset(new Pool6(IOAddress("2001:db8:d::"), 48, 64, + IOAddress("2001:db8:d::2000"), 120)); + + subnet->addPool(pdpool2); + + pdpool2->allowClientClass("work"); + pdpool2->requireClientClass("required-class3"); + pdpool2->requireClientClass("required-class4"); + user_context = Element::createMap(); + user_context->set("bar", Element::create("foo")); + pdpool2->setContext(user_context); + + test_subnets_.push_back(subnet); + + subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), 64, 20, 30, 40, 50, 2048)); + Triplet<uint32_t> null_timer; + subnet->setPreferred(null_timer); + subnet->setT1(null_timer); + subnet->setT2(null_timer); + subnet->setValid(null_timer); + subnet->setPreferred(null_timer); + subnet->setDdnsSendUpdates(true); + subnet->setDdnsOverrideNoUpdate(true); + subnet->setDdnsOverrideClientUpdate(false); + subnet->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT); + subnet->setDdnsGeneratedPrefix("myhost"); + subnet->setDdnsQualifyingSuffix("example.org"); + + subnet->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_); + + test_subnets_.push_back(subnet); + + // Add a subnet with all defaults. + subnet.reset(new Subnet6(IOAddress("2001:db8:4::"), 64, + Triplet<uint32_t>(), Triplet<uint32_t>(), + Triplet<uint32_t>(), Triplet<uint32_t>(), + 4096)); + test_subnets_.push_back(subnet); +} + +void +GenericConfigBackendDHCPv6Test::initTestSharedNetworks() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + std::string interface_id_text = "fish"; + OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end()); + OptionPtr opt_interface_id(new Option(Option::V6, D6O_INTERFACE_ID, interface_id)); + + SharedNetwork6Ptr shared_network(new SharedNetwork6("level1")); + shared_network->allowClientClass("foo"); + shared_network->setIface("eth1"); + shared_network->setInterfaceId(opt_interface_id); + shared_network->setT2(323212); + shared_network->addRelayAddress(IOAddress("2001:db8:1::2")); + shared_network->addRelayAddress(IOAddress("2001:db8:3::4")); + shared_network->setT1(1234); + shared_network->requireClientClass("required-class1"); + shared_network->requireClientClass("required-class2"); + shared_network->setReservationsGlobal(false); + shared_network->setReservationsInSubnet(false); + shared_network->setContext(user_context); + shared_network->setValid(5555); + shared_network->setPreferred(4444); + shared_network->setCalculateTeeTimes(true); + shared_network->setT1Percent(0.345); + shared_network->setT2Percent(0.444); + shared_network->setDdnsSendUpdates(false); + + // Add several options to the shared network. + shared_network->getCfgOption()->add(test_options_[2]->option_, + test_options_[2]->persistent_, + test_options_[2]->space_name_); + + shared_network->getCfgOption()->add(test_options_[3]->option_, + test_options_[3]->persistent_, + test_options_[3]->space_name_); + + shared_network->getCfgOption()->add(test_options_[4]->option_, + test_options_[4]->persistent_, + test_options_[4]->space_name_); + + test_networks_.push_back(shared_network); + + // Adding another shared network called "level1" to test + // cases that this second instance can override existing + // "level1" instance. + shared_network.reset(new SharedNetwork6("level1")); + test_networks_.push_back(shared_network); + + // Add more shared networks. + shared_network.reset(new SharedNetwork6("level2")); + Triplet<uint32_t> null_timer; + shared_network->setPreferred(null_timer); + shared_network->setT1(null_timer); + shared_network->setT2(null_timer); + shared_network->setValid(null_timer); + shared_network->setPreferred(null_timer); + shared_network->setDdnsSendUpdates(true); + shared_network->setDdnsOverrideNoUpdate(true); + shared_network->setDdnsOverrideClientUpdate(false); + shared_network->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT); + shared_network->setDdnsGeneratedPrefix("myhost"); + shared_network->setDdnsQualifyingSuffix("example.org"); + + shared_network->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_); + test_networks_.push_back(shared_network); + + shared_network.reset(new SharedNetwork6("level3")); + test_networks_.push_back(shared_network); +} + +void +GenericConfigBackendDHCPv6Test::initTestOptionDefs() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + OptionDefinitionPtr option_def(new OptionDefinition("foo", 1234, + DHCP6_OPTION_SPACE, + "string", + "espace")); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("bar", 1234, DHCP6_OPTION_SPACE, + "uint32", true)); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("fish", 5235, DHCP6_OPTION_SPACE, + "record", true)); + option_def->addRecordField("uint32"); + option_def->addRecordField("string"); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("whale", 20236, "xyz", "string")); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("bar", 1234, DHCP6_OPTION_SPACE, + "uint64", true)); + test_option_defs_.push_back(option_def); +} + +void +GenericConfigBackendDHCPv6Test::initTestOptions() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + OptionDefSpaceContainer defs; + + OptionDescriptor desc = + createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE, + true, false, "my-timezone"); + desc.space_name_ = DHCP6_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption<OptionUint8>(Option::V6, D6O_PREFERENCE, + false, true, 64); + desc.space_name_ = DHCP6_OPTION_SPACE; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption<OptionUint32>(Option::V6, 1, false, false, 312131), + desc.space_name_ = "vendor-encapsulated-options"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createAddressOption<Option6AddrLst>(1254, true, true, + "2001:db8::3"); + desc.space_name_ = DHCP6_OPTION_SPACE; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createEmptyOption(Option::V6, 1, true); + desc.space_name_ = "isc"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createAddressOption<Option6AddrLst>(2, false, true, + "2001:db8:1::5", + "2001:db8:1::3", + "2001:db8:3::4"); + desc.space_name_ = "isc"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE, + true, false, "my-timezone-2"); + desc.space_name_ = DHCP6_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE, + true, false, "my-timezone-3"); + desc.space_name_ = DHCP6_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + // Add definitions for DHCPv6 non-standard options in case we need to + // compare subnets, networks and pools in JSON format. In that case, + // the @c toElement functions require option definitions to generate the + // proper output. + defs.addItem(OptionDefinitionPtr(new OptionDefinition("vendor-encapsulated-1", 1, + "vendor-encapsulated-options", + "uint32"))); + defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-1254", 1254, + DHCP6_OPTION_SPACE, + "ipv6-address", true))); + defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty"))); + defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc", + "ipv6-address", true))); + + // Register option definitions. + LibDHCP::setRuntimeOptionDefs(defs); +} + +void +GenericConfigBackendDHCPv6Test::initTestClientClasses() { + ExpressionPtr match_expr = boost::make_shared<Expression>(); + CfgOptionPtr cfg_option = boost::make_shared<CfgOption>(); + auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option); + class1->setCfgOptionDef(boost::make_shared<CfgOptionDef>()); + class1->setRequired(true); + class1->setValid(Triplet<uint32_t>(30, 60, 90)); + class1->setPreferred(Triplet<uint32_t>(25, 55, 85)); + test_client_classes_.push_back(class1); + ElementPtr user_context = Element::createMap(); + user_context->set("melon", Element::create("water")); + class1->setContext(user_context); + + auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option); + class2->setCfgOptionDef(boost::make_shared<CfgOptionDef>()); + class2->setTest("member('foo')"); + test_client_classes_.push_back(class2); + + auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option); + class3->setCfgOptionDef(boost::make_shared<CfgOptionDef>()); + class3->setTest("member('foo') and member('bar')"); + test_client_classes_.push_back(class3); +} + +void +GenericConfigBackendDHCPv6Test::getTypeTest(const std::string& expected_type) { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params)); + ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); + EXPECT_EQ(expected_type, cbptr_->getType()); +} + +void +GenericConfigBackendDHCPv6Test::getHostTest() { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params)); + ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); + EXPECT_EQ("localhost", cbptr_->getHost()); +} + +void +GenericConfigBackendDHCPv6Test::getPortTest() { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params)); + ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); + EXPECT_EQ(0, cbptr_->getPort()); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateDeleteServerTest() { + // Explicitly set modification time to make sure that the time + // returned from the database is correct. + test_servers_[0]->setModificationTime(timestamps_["yesterday"]); + test_servers_[1]->setModificationTime(timestamps_["today"]); + + // Insert the server1 into the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + // It should not be possible to create a duplicate of the logical + // server 'all'. + auto all_server = Server::create(ServerTag("all"), "this is logical server all"); + ASSERT_THROW(cbptr_->createUpdateServer6(all_server), isc::InvalidOperation); + + ServerPtr returned_server; + + // An attempt to fetch the server that hasn't been inserted should return + // a null pointer. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server2"))); + EXPECT_FALSE(returned_server); + + // Try to fetch the server which we expect to exist. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1"))); + ASSERT_TRUE(returned_server); + EXPECT_EQ("server1", returned_server->getServerTagAsText()); + EXPECT_EQ("this is server 1", returned_server->getDescription()); + EXPECT_EQ(timestamps_["yesterday"], returned_server->getModificationTime()); + + // This call is expected to update the existing server. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1])); + + { + SCOPED_TRACE("UPDATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::UPDATE, + "server set"); + } + + // Verify that the server has been updated. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1"))); + ASSERT_TRUE(returned_server); + EXPECT_EQ("server1", returned_server->getServerTag().get()); + EXPECT_EQ("this is server 1 bis", returned_server->getDescription()); + EXPECT_EQ(timestamps_["today"], returned_server->getModificationTime()); + + uint64_t servers_deleted = 0; + + // Try to delete non-existing server. + ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer6(ServerTag("server2"))); + EXPECT_EQ(0, servers_deleted); + + // Make sure that the server1 wasn't deleted. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1"))); + EXPECT_TRUE(returned_server); + + // Deleting logical server 'all' is not allowed. + ASSERT_THROW(cbptr_->deleteServer6(ServerTag()), isc::InvalidOperation); + + // Delete the existing server. + ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer6(ServerTag("server1"))); + EXPECT_EQ(1, servers_deleted); + + { + SCOPED_TRACE("DELETE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::DELETE, + "deleting a server"); + } + + // Make sure that the server is gone. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1"))); + EXPECT_FALSE(returned_server); +} + +void +GenericConfigBackendDHCPv6Test::getAndDeleteAllServersTest() { + for (auto i = 1; i < test_servers_.size(); ++i) { + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[i])); + } + + ServerCollection servers; + ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers6()); + ASSERT_EQ(test_servers_.size() - 1, servers.size()); + + // All servers should have been returned. + EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server1"))); + EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server2"))); + EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server3"))); + + // The logical server all should not be returned. We merely return the + // user configured servers. + EXPECT_FALSE(ServerFetcher::get(servers, ServerTag())); + + // Delete all servers and make sure they are gone. + uint64_t deleted_servers = 0; + ASSERT_NO_THROW_LOG(deleted_servers = cbptr_->deleteAllServers6()); + + ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers6()); + EXPECT_TRUE(servers.empty()); + + // All servers should be gone. + EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server1"))); + EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server2"))); + EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server3"))); + + // The number of deleted server should be equal to the number of + // inserted servers. The logical 'all' server should be excluded. + EXPECT_EQ(test_servers_.size() - 1, deleted_servers); + + EXPECT_EQ(1, countRows("dhcp6_server")); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateDeleteGlobalParameter6Test() { + StampedValuePtr global_parameter = StampedValue::create("global", "whale"); + + // Explicitly set modification time to make sure that the time + // returned from the database is correct. + global_parameter->setModificationTime(timestamps_["yesterday"]); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + global_parameter); + + { + SCOPED_TRACE("CREATE audit entry for global parameter"); + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::CREATE, + "global parameter set"); + } + + // Verify returned parameter and the modification time. + StampedValuePtr returned_global_parameter = + cbptr_->getGlobalParameter6(ServerSelector::ALL(), "global"); + ASSERT_TRUE(returned_global_parameter); + EXPECT_EQ("global", returned_global_parameter->getName()); + EXPECT_EQ("whale", returned_global_parameter->getValue()); + EXPECT_TRUE(returned_global_parameter->getModificationTime() == + global_parameter->getModificationTime()); + ASSERT_EQ(1, returned_global_parameter->getServerTags().size()); + EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get()); + + // Because we have added the global parameter for all servers, it + // should be also returned for the explicitly specified server. + returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ONE("server1"), + "global"); + ASSERT_TRUE(returned_global_parameter); + EXPECT_EQ("global", returned_global_parameter->getName()); + EXPECT_EQ("whale", returned_global_parameter->getValue()); + EXPECT_TRUE(returned_global_parameter->getModificationTime() == + global_parameter->getModificationTime()); + ASSERT_EQ(1, returned_global_parameter->getServerTags().size()); + EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get()); + + // Check that the parameter is updated when selector is specified correctly. + global_parameter = StampedValue::create("global", "fish"); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + global_parameter); + returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ALL(), + "global"); + ASSERT_TRUE(returned_global_parameter); + EXPECT_EQ("global", returned_global_parameter->getName()); + EXPECT_EQ("fish", returned_global_parameter->getValue()); + EXPECT_TRUE(returned_global_parameter->getModificationTime() == + global_parameter->getModificationTime()); + ASSERT_EQ(1, returned_global_parameter->getServerTags().size()); + EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get()); + + { + SCOPED_TRACE("UPDATE audit entry for the global parameter"); + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::UPDATE, + "global parameter set"); + } + + // Should not delete parameter specified for all servers if explicit + // server name is provided. + EXPECT_EQ(0, cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server1"), + "global")); + + // Delete parameter and make sure it is gone. + cbptr_->deleteGlobalParameter6(ServerSelector::ALL(), "global"); + returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ALL(), + "global"); + EXPECT_FALSE(returned_global_parameter); + + { + SCOPED_TRACE("DELETE audit entry for the global parameter"); + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::DELETE, + "global parameter deleted"); + } +} + +void +GenericConfigBackendDHCPv6Test::globalParameters6WithServerTagsTest() { + // Create three global parameters having the same name. + StampedValuePtr global_parameter1 = StampedValue::create("global", "value1"); + StampedValuePtr global_parameter2 = StampedValue::create("global", "value2"); + StampedValuePtr global_parameter3 = StampedValue::create("global", "value3"); + + // Try to insert one of them and associate with non-existing server. + // This should fail because the server must be inserted first. + ASSERT_THROW(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"), + global_parameter1), + NullKeyError); + + // Create two servers. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("server2 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + // This time inserting the global parameters for the server1 and server2 should + // be successful. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"), + global_parameter1)); + { + SCOPED_TRACE("Global parameter for server1 is set"); + // The value of 3 means there should be 3 audit entries available for the + // server1, two that indicate creation of the servers and one that we + // validate, which sets the global value. + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::CREATE, + "global parameter set", + ServerSelector::ONE("server1"), + 3, 1); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server2"), + global_parameter2)); + { + SCOPED_TRACE("Global parameter for server2 is set"); + // Same as in case of the server2, there should be 3 audit entries of + // which one we validate. + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::CREATE, + "global parameter set", + ServerSelector::ONE("server2"), + 3, 1); + } + + // The last parameter is associated with all servers. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + global_parameter3)); + { + SCOPED_TRACE("Global parameter for all servers is set"); + // There should be one new audit entry for all servers. It indicates + // the insertion of the global value. + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::CREATE, + "global parameter set", + ServerSelector::ALL(), + 1, 1); + } + + StampedValuePtr returned_global; + + // Try to fetch the value specified for all servers. + ASSERT_NO_THROW_LOG( + returned_global = cbptr_->getGlobalParameter6(ServerSelector::ALL(), + "global") + ); + ASSERT_TRUE(returned_global); + EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue()); + ASSERT_EQ(1, returned_global->getServerTags().size()); + EXPECT_EQ("all", returned_global->getServerTags().begin()->get()); + + // Try to fetch the value specified for the server1. This should override the + // value specified for all servers. + ASSERT_NO_THROW_LOG( + returned_global = cbptr_->getGlobalParameter6(ServerSelector::ONE("server1"), + "global") + ); + ASSERT_TRUE(returned_global); + EXPECT_EQ(global_parameter1->getValue(), returned_global->getValue()); + + ASSERT_EQ(1, returned_global->getServerTags().size()); + EXPECT_EQ("server1", returned_global->getServerTags().begin()->get()); + + // The same in case of the server2. + ASSERT_NO_THROW_LOG( + returned_global = cbptr_->getGlobalParameter6(ServerSelector::ONE("server2"), + "global") + ); + ASSERT_TRUE(returned_global); + EXPECT_EQ(global_parameter2->getValue(), returned_global->getValue()); + ASSERT_EQ(1, returned_global->getServerTags().size()); + EXPECT_EQ("server2", returned_global->getServerTags().begin()->get()); + + StampedValueCollection returned_globals; + + // Try to fetch the collection of globals for the server1, server2 and server3. + // The server3 does not have an explicit value so for this server we should get + /// the value for 'all'. + ASSERT_NO_THROW_LOG( + returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector:: + MULTIPLE({ "server1", "server2", + "server3" })); + ); + ASSERT_EQ(3, returned_globals.size()); + + // Capture the returned values into the map so as we can check the + // values against the servers. + std::map<std::string, std::string> values; + for (auto g = returned_globals.begin(); g != returned_globals.end(); ++g) { + ASSERT_EQ(1, (*g)->getServerTags().size()); + values[(*g)->getServerTags().begin()->get()] = ((*g)->getValue()); + } + + ASSERT_EQ(3, values.size()); + EXPECT_EQ(global_parameter1->getValue(), values["server1"]); + EXPECT_EQ(global_parameter2->getValue(), values["server2"]); + EXPECT_EQ(global_parameter3->getValue(), values["all"]); + + // Try to fetch the collection of global parameters specified for all servers. + // This excludes the values specific to server1 and server2. It returns only the + // common ones. + ASSERT_NO_THROW_LOG( + returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ALL()) + ); + ASSERT_EQ(1, returned_globals.size()); + returned_global = *returned_globals.begin(); + EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue()); + ASSERT_EQ(1, returned_global->getServerTags().size()); + EXPECT_EQ("all", returned_global->getServerTags().begin()->get()); + + // Delete the server1. It should remove associations of this server with the + // global parameter and the global parameter itself. + ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1"))); + ASSERT_NO_THROW_LOG( + returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ONE("server1")) + ); + ASSERT_EQ(1, returned_globals.size()); + returned_global = *returned_globals.begin(); + // As a result, the value fetched for the server1 should be the one available for + // all servers, rather than the one dedicated for server1. The association of + // the server1 specific value with the server1 should be gone. + EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue()); + ASSERT_EQ(1, returned_global->getServerTags().size()); + EXPECT_EQ("all", returned_global->getServerTags().begin()->get()); + + { + SCOPED_TRACE("DELETE audit entry for the global parameter after server deletion"); + // We expect two new audit entries for the server1, one indicating that the + // server has been deleted and another one indicating that the corresponding + // global value has been deleted. We check the latter entry. + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::DELETE, + "deleting a server", ServerSelector::ONE("server1"), + 2, 1); + } + + // Attempt to delete global parameter for server1. + uint64_t deleted_num = 0; + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server1"), + "global")); + // No parameters should be deleted. In particular, the parameter for the logical + // server 'all' should not be deleted. + EXPECT_EQ(0, deleted_num); + + // Deleting the existing value for server2 should succeed. + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server2"), + "global")); + EXPECT_EQ(1, deleted_num); + + // Create it again to test that deletion of all server removes this too. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server2"), + global_parameter2)); + + // Delete all servers, except 'all'. + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6()); + ASSERT_NO_THROW_LOG( + returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ALL()) + ); + EXPECT_EQ(1, deleted_num); + ASSERT_EQ(1, returned_globals.size()); + returned_global = *returned_globals.begin(); + // The common value for all servers should still be available because 'all' + // logical server should not be deleted. + EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue()); + ASSERT_EQ(1, returned_global->getServerTags().size()); + EXPECT_EQ("all", returned_global->getServerTags().begin()->get()); + + { + SCOPED_TRACE("DELETE audit entry for the global parameter after deletion of" + " all servers"); + // There should be 4 new audit entries. One for deleting the global, one for + // re-creating it, one for deleting the server2 and one for deleting the + // global again as a result of deleting the server2. + testNewAuditEntry("dhcp6_global_parameter", + AuditEntry::ModificationType::DELETE, + "deleting all servers", ServerSelector::ONE("server2"), + 4, 1); + } +} + +void +GenericConfigBackendDHCPv6Test::getAllGlobalParameters6Test() { + // Create 3 parameters and put them into the database. + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name1", "value1")); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name2", Element::create(static_cast<int64_t>(65)))); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name3", "value3")); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name4", Element::create(static_cast<bool>(true)))); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name5", Element::create(static_cast<double>(1.65)))); + + // Fetch all parameters. + auto parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL()); + ASSERT_EQ(5, parameters.size()); + + const auto& parameters_index = parameters.get<StampedValueNameIndexTag>(); + + // Verify their values. + EXPECT_EQ("value1", (*parameters_index.find("name1"))->getValue()); + EXPECT_EQ(65, (*parameters_index.find("name2"))->getIntegerValue()); + EXPECT_EQ("value3", (*parameters_index.find("name3"))->getValue()); + EXPECT_TRUE((*parameters_index.find("name4"))->getBoolValue()); + EXPECT_EQ(1.65, (*parameters_index.find("name5"))->getDoubleValue()); + + for (auto param = parameters_index.begin(); param != parameters_index.end(); + ++param) { + ASSERT_EQ(1, (*param)->getServerTags().size()); + EXPECT_EQ("all", (*param)->getServerTags().begin()->get()); + } + + // Should be able to fetch these parameters when explicitly providing + // the server tag. + parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ONE("server1")); + EXPECT_EQ(5, parameters.size()); + + // Deleting global parameters with non-matching server selector + // should fail. + EXPECT_EQ(0, cbptr_->deleteAllGlobalParameters6(ServerSelector::ONE("server1"))); + + // Delete all parameters and make sure they are gone. + EXPECT_EQ(5, cbptr_->deleteAllGlobalParameters6(ServerSelector::ALL())); + parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL()); + EXPECT_TRUE(parameters.empty()); +} + +void +GenericConfigBackendDHCPv6Test::getModifiedGlobalParameters6Test() { + // Create 3 global parameters and assign modification times: + // "yesterday", "today" and "tomorrow" respectively. + StampedValuePtr value = StampedValue::create("name1", "value1"); + value->setModificationTime(timestamps_["yesterday"]); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + value); + + value = StampedValue::create("name2", Element::create(static_cast<int64_t>(65))); + value->setModificationTime(timestamps_["today"]); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + value); + + value = StampedValue::create("name3", "value3"); + value->setModificationTime(timestamps_["tomorrow"]); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + value); + + // Get parameters modified after "today". + auto parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ALL(), + timestamps_["after today"]); + + const auto& parameters_index = parameters.get<StampedValueNameIndexTag>(); + + // It should be the one modified "tomorrow". + ASSERT_EQ(1, parameters_index.size()); + + auto parameter = parameters_index.find("name3"); + ASSERT_FALSE(parameter == parameters_index.end()); + + ASSERT_TRUE(*parameter); + EXPECT_EQ("value3", (*parameter)->getValue()); + + // Should be able to fetct these parameters when explicitly providing + // the server tag. + parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ONE("server1"), + timestamps_["after today"]); + EXPECT_EQ(1, parameters.size()); +} + +void +GenericConfigBackendDHCPv6Test::nullKeyErrorTest() { + // Create a global parameter (it should work with any object type). + StampedValuePtr global_parameter = StampedValue::create("global", "value"); + + // Try to insert it and associate with non-existing server. + std::string msg; + try { + cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"), + global_parameter); + msg = "got no exception"; + } catch (const NullKeyError& ex) { + msg = ex.what(); + } catch (const std::exception&) { + msg = "got another exception"; + } + EXPECT_EQ("server 'server1' does not exist", msg); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateSubnet6SelectorsTest() { + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + + // Supported selectors. + Subnet6Ptr subnet = test_subnets_[0]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), + subnet)); + subnet = test_subnets_[2]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), + subnet)); + subnet = test_subnets_[3]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + subnet)); + + // Not supported server selectors. + ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ANY(), subnet), + isc::InvalidOperation); + + // Not implemented server selectors. + ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::UNASSIGNED(), + subnet), + isc::NotImplemented); +} + +void +GenericConfigBackendDHCPv6Test::getSubnet6Test() { + // Insert the server2 into the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + auto subnet = test_subnets_[0]; + auto subnet2 = test_subnets_[2]; + + // An attempt to add a subnet to a non-existing server (server1) should fail. + ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + subnet2), + NullKeyError); + + // The subnet shouldn't have been added, even though one of the servers exists. + Subnet6Ptr returned_subnet; + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server2"), + subnet2->getID())); + EXPECT_FALSE(returned_subnet); + + // Insert two subnets, one for all servers and one for server2. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); + { + SCOPED_TRACE("A. CREATE audit entry for the subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2)); + { + SCOPED_TRACE("B. CREATE audit entry for the subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set", ServerSelector::ONE("subnet2"), + 2, 1); + } + + // We are not going to support selection of a single entry for multiple servers. + ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + subnet->getID()), + isc::InvalidOperation); + + ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + subnet->toText()), + isc::InvalidOperation); + + // Test that this subnet will be fetched for various server selectors. + auto test_get_subnet = [this, &subnet] (const std::string& test_case_name, + const ServerSelector& server_selector, + const std::string& expected_tag = ServerTag::ALL) { + SCOPED_TRACE(test_case_name); + + // Test fetching subnet by id. + Subnet6Ptr returned_subnet; + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(server_selector, subnet->getID())); + ASSERT_TRUE(returned_subnet); + + ASSERT_EQ(1, returned_subnet->getServerTags().size()); + EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag))); + + ASSERT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + + // Test fetching subnet by prefix. + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(server_selector, + subnet->toText())); + ASSERT_TRUE(returned_subnet); + + ASSERT_EQ(1, returned_subnet->getServerTags().size()); + EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag))); + + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + }; + + { + SCOPED_TRACE("testing various server selectors before update"); + test_get_subnet("all servers", ServerSelector::ALL()); + test_get_subnet("one server", ServerSelector::ONE("server1")); + test_get_subnet("any server", ServerSelector::ANY()); + } + + subnet = subnet2; + { + SCOPED_TRACE("testing server selectors for another server"); + test_get_subnet("one server", ServerSelector::ONE("server2"), "server2"); + test_get_subnet("any server", ServerSelector::ANY(), "server2"); + } + + // Update the subnet in the database (both use the same ID). + subnet = test_subnets_[1]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); + { + SCOPED_TRACE("C. CREATE audit entry for the subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet set"); + } + + { + SCOPED_TRACE("testing various server selectors after update"); + test_get_subnet("all servers", ServerSelector::ALL()); + test_get_subnet("one server", ServerSelector::ONE("server1")); + test_get_subnet("any server", ServerSelector::ANY()); + } + + // The server2 specific subnet should not be returned if the server selector + // is not matching. + EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ALL(), subnet2->getID())); + EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ALL(), subnet2->toText())); + EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ONE("server1"), subnet2->getID())); + EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ONE("server1"), subnet2->toText())); + + // Update the subnet in the database (both use the same prefix). + subnet2.reset(new Subnet6(IOAddress("2001:db8:3::"), + 64, 30, 40, 50, 80, 8192)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2)); + + // Fetch again and verify. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server2"), subnet2->toText()); + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(subnet2->toElement()->str(), returned_subnet->toElement()->str()); + + // Update the subnet when it conflicts same id and same prefix both + // with different subnets. This should throw. + // Subnets are 2001:db8:1::/48 id 1024 and 2001:db8:3::/64 id 8192 + subnet2.reset(new Subnet6(IOAddress("2001:db8:1::"), + 48, 30, 40, 50, 80, 8192)); + ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2), + DuplicateEntry); +} + +void +GenericConfigBackendDHCPv6Test::getSubnet6byIdSelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ANY(), SubnetID(1))); + ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), SubnetID(1))); + ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ALL(), SubnetID(1))); + ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ONE("server1"), SubnetID(1))); + + // Not supported selectors. + ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + SubnetID(1)), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::getSubnet6WithOptionalUnspecifiedTest() { + // Create a subnet and wrap it within a shared network. It is important + // to have the shared network to verify that the subnet doesn't inherit + // the values of the shared network but stores the NULL values in the + // for those parameters that are unspecified on the subnet level. + Subnet6Ptr subnet = test_subnets_[2]; + SharedNetwork6Ptr shared_network = test_networks_[0]; + shared_network->add(subnet); + + // Need to add the shared network to the database because otherwise + // the subnet foreign key would fail. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); + + // Fetch this subnet by subnet identifier. + Subnet6Ptr returned_subnet; + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID())); + ASSERT_TRUE(returned_subnet); + + EXPECT_TRUE(returned_subnet->getIface().unspecified()); + EXPECT_TRUE(returned_subnet->getIface().empty()); + + EXPECT_TRUE(returned_subnet->getClientClass().unspecified()); + EXPECT_TRUE(returned_subnet->getClientClass().empty()); + + EXPECT_TRUE(returned_subnet->getValid().unspecified()); + EXPECT_EQ(0, returned_subnet->getValid().get()); + + EXPECT_TRUE(returned_subnet->getPreferred().unspecified()); + EXPECT_EQ(0, returned_subnet->getPreferred().get()); + + EXPECT_TRUE(returned_subnet->getT1().unspecified()); + EXPECT_EQ(0, returned_subnet->getT1().get()); + + EXPECT_TRUE(returned_subnet->getT2().unspecified()); + EXPECT_EQ(0, returned_subnet->getT2().get()); + + EXPECT_TRUE(returned_subnet->getReservationsGlobal().unspecified()); + EXPECT_FALSE(returned_subnet->getReservationsGlobal().get()); + + EXPECT_TRUE(returned_subnet->getReservationsInSubnet().unspecified()); + EXPECT_TRUE(returned_subnet->getReservationsInSubnet().get()); + + EXPECT_TRUE(returned_subnet->getReservationsOutOfPool().unspecified()); + EXPECT_FALSE(returned_subnet->getReservationsOutOfPool().get()); + + EXPECT_TRUE(returned_subnet->getCalculateTeeTimes().unspecified()); + EXPECT_FALSE(returned_subnet->getCalculateTeeTimes().get()); + + EXPECT_TRUE(returned_subnet->getT1Percent().unspecified()); + EXPECT_EQ(0.0, returned_subnet->getT1Percent().get()); + + EXPECT_TRUE(returned_subnet->getT2Percent().unspecified()); + EXPECT_EQ(0.0, returned_subnet->getT2Percent().get()); + + EXPECT_TRUE(returned_subnet->getRapidCommit().unspecified()); + EXPECT_FALSE(returned_subnet->getRapidCommit().get()); + + EXPECT_FALSE(returned_subnet->getDdnsSendUpdates().unspecified()); + EXPECT_TRUE(returned_subnet->getDdnsSendUpdates().get()); + + EXPECT_FALSE(returned_subnet->getDdnsOverrideNoUpdate().unspecified()); + EXPECT_TRUE(returned_subnet->getDdnsOverrideNoUpdate().get()); + + EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().unspecified()); + EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().get()); + + EXPECT_FALSE(returned_subnet->getDdnsReplaceClientNameMode().unspecified()); + EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT, + returned_subnet->getDdnsReplaceClientNameMode().get()); + + EXPECT_FALSE(returned_subnet->getDdnsGeneratedPrefix().unspecified()); + EXPECT_EQ("myhost", returned_subnet->getDdnsGeneratedPrefix().get()); + + EXPECT_FALSE(returned_subnet->getDdnsQualifyingSuffix().unspecified()); + EXPECT_EQ("example.org", returned_subnet->getDdnsQualifyingSuffix().get()); + + // The easiest way to verify whether the returned subnet matches the inserted + // subnet is to convert both to text. + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); +} + +void +GenericConfigBackendDHCPv6Test::getSubnet6SharedNetworkTest() { + Subnet6Ptr subnet = test_subnets_[0]; + SharedNetwork6Ptr shared_network = test_networks_[0]; + + // Add subnet to a shared network. + shared_network->add(subnet); + + // Store shared network in the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network)); + + // Store subnet associated with the shared network in the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); + + // Fetch this subnet by subnet identifier. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + test_subnets_[0]->getID()); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getServerTags().size()); + EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get()); + + // The easiest way to verify whether the returned subnet matches the inserted + // subnet is to convert both to text. + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + + // However, the check above doesn't verify whether shared network name was + // correctly returned from the database. + EXPECT_EQ(shared_network->getName(), returned_subnet->getSharedNetworkName()); +} + +void +GenericConfigBackendDHCPv6Test::getSubnet6ByPrefixTest() { + // Insert subnet to the database. + Subnet6Ptr subnet = test_subnets_[0]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); + + // Fetch the subnet by prefix. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + "2001:db8::/64"); + ASSERT_TRUE(returned_subnet); + ASSERT_EQ(1, returned_subnet->getServerTags().size()); + EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get()); + + // Verify subnet contents. + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + + // Fetching the subnet for an explicitly specified server tag should + // succeed too. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"), + "2001:db8::/64"); + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); +} + +void +GenericConfigBackendDHCPv6Test::getSubnet6byPrefixSelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ANY(), "192.0.2.0/26")); + ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), "192.0.2.0/26")); + ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ALL(), "192.0.2.0/26")); + ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ONE("server1"), "192.0.2.0/26")); + + // Not supported selectors. + ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + "192.0.2.0/26"), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::getAllSubnets6Test() { + // Insert test subnets into the database. Note that the second subnet will + // overwrite the first subnet as they use the same ID. + for (auto subnet : test_subnets_) { + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // That subnet overrides the first subnet so the audit entry should + // indicate an update. + if (subnet->toText() == "2001:db8:1::/48") { + SCOPED_TRACE("UPDATE audit entry for the subnet " + subnet->toText()); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet set"); + + } else { + SCOPED_TRACE("CREATE audit entry for the subnet " + subnet->toText()); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + } + + // Fetch all subnets. + Subnet6Collection subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()); + ASSERT_EQ(test_subnets_.size() - 1, subnets.size()); + + // All subnets should also be returned for explicitly specified server tag. + subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1")); + ASSERT_EQ(test_subnets_.size() - 1, subnets.size()); + + // See if the subnets are returned ok. + auto subnet_it = subnets.begin(); + for (auto i = 0; i < subnets.size(); ++i, ++subnet_it) { + ASSERT_EQ(1, (*subnet_it)->getServerTags().size()); + EXPECT_EQ("all", (*subnet_it)->getServerTags().begin()->get()); + EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(), + (*subnet_it)->toElement()->str()); + } + + // Attempt to remove the non existing subnet should return 0. + EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ALL(), 22)); + EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ALL(), + "2001:db8:555::/64")); + // All subnets should be still there. + ASSERT_EQ(test_subnets_.size() - 1, subnets.size()); + + // Should not delete the subnet for explicit server tag because + // our subnet is for all servers. + EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), + test_subnets_[1]->getID())); + + // Also, verify that behavior when deleting by prefix. + EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), + test_subnets_[2]->toText())); + + // Same for all subnets. + EXPECT_EQ(0, cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1"))); + + // Delete first subnet by id and verify that it is gone. + EXPECT_EQ(1, cbptr_->deleteSubnet6(ServerSelector::ALL(), + test_subnets_[1]->getID())); + + { + SCOPED_TRACE("DELETE first subnet audit entry"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::DELETE, + "subnet deleted"); + } + + subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()); + ASSERT_EQ(test_subnets_.size() - 2, subnets.size()); + + // Delete second subnet by prefix and verify it is gone. + EXPECT_EQ(1, cbptr_->deleteSubnet6(ServerSelector::ALL(), + test_subnets_[2]->toText())); + subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()); + ASSERT_EQ(test_subnets_.size() - 3, subnets.size()); + + { + SCOPED_TRACE("DELETE second subnet audit entry"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::DELETE, + "subnet deleted"); + } + + // Delete all. + EXPECT_EQ(1, cbptr_->deleteAllSubnets6(ServerSelector::ALL())); + subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()); + ASSERT_TRUE(subnets.empty()); + + { + SCOPED_TRACE("DELETE all subnets audit entry"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::DELETE, + "deleted all subnets"); + } +} + +void +GenericConfigBackendDHCPv6Test::getAllSubnets6SelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::UNASSIGNED())); + ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::ALL())); + ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::ONE("server1"))); + ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }))); + + // Not supported selectors. + ASSERT_THROW(cbptr_->getAllSubnets6(ServerSelector::ANY()), isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::getAllSubnets6WithServerTagsTest() { + auto subnet1 = test_subnets_[0]; + auto subnet2 = test_subnets_[2]; + auto subnet3 = test_subnets_[3]; + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), + subnet1)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), + subnet2)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + subnet3)); + + Subnet6Collection subnets; + + // All three subnets are associated with the server1. + ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1"))); + EXPECT_EQ(3, subnets.size()); + + // First subnet is associated with all servers. + auto returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024)); + ASSERT_TRUE(returned_subnet); + EXPECT_TRUE(returned_subnet->hasAllServerTag()); + EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1"))); + EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2"))); + + // Second subnet is only associated with the server1. + returned_subnet = SubnetFetcher6::get(subnets, SubnetID(2048)); + ASSERT_TRUE(returned_subnet); + EXPECT_FALSE(returned_subnet->hasAllServerTag()); + EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1"))); + EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2"))); + + // Third subnet is associated with both server1 and server2. + returned_subnet = SubnetFetcher6::get(subnets, SubnetID(4096)); + ASSERT_TRUE(returned_subnet); + EXPECT_FALSE(returned_subnet->hasAllServerTag()); + EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1"))); + EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2"))); + + // For server2 we should only get two subnets, i.e. first and last. + ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server2"))); + EXPECT_EQ(2, subnets.size()); + + // First subnet is associated with all servers. + returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024)); + ASSERT_TRUE(returned_subnet); + EXPECT_TRUE(returned_subnet->hasAllServerTag()); + EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1"))); + EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2"))); + + // Last subnet is associated with server1 and server2. + returned_subnet = SubnetFetcher6::get(subnets, SubnetID(4096)); + ASSERT_TRUE(returned_subnet); + EXPECT_FALSE(returned_subnet->hasAllServerTag()); + EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1"))); + EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2"))); + + // Only the first subnet is associated with all servers. + ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ALL())); + EXPECT_EQ(1, subnets.size()); + + returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024)); + ASSERT_TRUE(returned_subnet); + EXPECT_TRUE(returned_subnet->hasAllServerTag()); + EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1"))); + EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2"))); +} + +void +GenericConfigBackendDHCPv6Test::getModifiedSubnets6SelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::UNASSIGNED(), + timestamps_["yesterday"])); + ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::ALL(), + timestamps_["yesterday"])); + ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"), + timestamps_["yesterday"])); + ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }), + timestamps_["yesterday"])); + + // Not supported selectors. + EXPECT_THROW(cbptr_->getModifiedSubnets6(ServerSelector::ANY(), + timestamps_["yesterday"]), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::deleteSubnet6Test() { + // Create two servers in the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + auto subnet1 = test_subnets_[0]; + auto subnet2 = test_subnets_[2]; + auto subnet3 = test_subnets_[3]; + + auto create_test_subnets = [&] () { + // Insert three subnets, one for all servers, one for server2 and one for two + // servers: server1 and server2. + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet1) + ); + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2) + ); + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + subnet3) + ); + }; + + create_test_subnets(); + + // Test that subnet is not deleted for a specified server selector. + auto test_no_delete = [this] (const std::string& test_case_name, + const ServerSelector& server_selector, + const Subnet6Ptr& subnet) { + SCOPED_TRACE(test_case_name); + uint64_t deleted_count = 0; + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->getID()) + ); + EXPECT_EQ(0, deleted_count); + + deleted_count = 0; + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->toText()) + ); + EXPECT_EQ(0, deleted_count); + }; + + { + SCOPED_TRACE("Test valid but non matching server selectors"); + test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"), + subnet1); + test_no_delete("selector: all, actual: one", ServerSelector::ALL(), + subnet2); + test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(), + subnet3); + } + + // Test successful deletion of a subnet by ID. + auto test_delete_by_id = [this] (const std::string& test_case_name, + const ServerSelector& server_selector, + const Subnet6Ptr& subnet) { + SCOPED_TRACE(test_case_name); + uint64_t deleted_count = 0; + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->getID()) + ); + EXPECT_EQ(1, deleted_count); + + EXPECT_FALSE(cbptr_->getSubnet6(server_selector, subnet->getID())); + }; + + test_delete_by_id("all servers", ServerSelector::ALL(), subnet1); + test_delete_by_id("any server", ServerSelector::ANY(), subnet2); + test_delete_by_id("one server", ServerSelector::ONE("server1"), subnet3); + + // Re-create deleted subnets. + create_test_subnets(); + + // Test successful deletion of a subnet by prefix. + auto test_delete_by_prefix = [this] (const std::string& test_case_name, + const ServerSelector& server_selector, + const Subnet6Ptr& subnet) { + SCOPED_TRACE(test_case_name); + uint64_t deleted_count = 0; + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->toText()) + ); + EXPECT_EQ(1, deleted_count); + + EXPECT_FALSE(cbptr_->getSubnet6(server_selector, subnet->toText())); + }; + + test_delete_by_prefix("all servers", ServerSelector::ALL(), subnet1); + test_delete_by_prefix("any server", ServerSelector::ANY(), subnet2); + test_delete_by_prefix("one server", ServerSelector::ONE("server1"), subnet3); +} + +void +GenericConfigBackendDHCPv6Test::deleteSubnet6ByIdSelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ANY(), SubnetID(1))); + ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), SubnetID(1))); + ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), SubnetID(1))); + + // Not supported selectors. + ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + SubnetID(1)), + isc::InvalidOperation); + + // Not implemented selectors. + ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::UNASSIGNED(), SubnetID(1)), + isc::NotImplemented); +} + +void +GenericConfigBackendDHCPv6Test::deleteSubnet6ByPrefixSelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ANY(), "192.0.2.0/26")); + ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), "192.0.2.0/26")); + ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), "192.0.2.0/26")); + + // Not supported selectors. + ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + "192.0.2.0/26"), + isc::InvalidOperation); + + // Not implemented selectors. + ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::UNASSIGNED(), "192.0.2.0/26"), + isc::NotImplemented); +} + +void +GenericConfigBackendDHCPv6Test::deleteAllSubnets6SelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::UNASSIGNED())); + ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::ALL())); + ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1"))); + + // Not supported selectors. + ASSERT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::ANY()), + isc::InvalidOperation); + ASSERT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" })), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::unassignedSubnet6Test() { + // Create the server. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + + // Create the subnets and associate them with the server1. + auto subnet = test_subnets_[0]; + auto subnet2 = test_subnets_[2]; + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), subnet) + ); + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), subnet2) + ); + + // Delete the server. The subnets should be preserved but are considered orphaned, + // i.e. do not belong to any server. + uint64_t deleted_count = 0; + ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer6(ServerTag("server1"))); + EXPECT_EQ(1, deleted_count); + + // Trying to fetch the subnet by server tag should return no result. + Subnet6Ptr returned_subnet; + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"), + subnet->getID())); + EXPECT_FALSE(returned_subnet); + + // The same if we use other calls. + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"), + subnet->toText())); + EXPECT_FALSE(returned_subnet); + + Subnet6Collection returned_subnets; + ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1"))); + EXPECT_TRUE(returned_subnets.empty()); + + ASSERT_NO_THROW_LOG( + returned_subnets = cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"), + timestamps_["two days ago"]) + ); + EXPECT_TRUE(returned_subnets.empty()); + + // We should get the subnet if we ask for unassigned. + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), + subnet->getID())); + ASSERT_TRUE(returned_subnet); + + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), + subnet->toText())); + ASSERT_TRUE(returned_subnet); + + // Also if we ask for all unassigned subnets it should be returned. + ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets6(ServerSelector::UNASSIGNED())); + ASSERT_EQ(2, returned_subnets.size()); + + // Same for modified subnets. + ASSERT_NO_THROW_LOG( + returned_subnets = cbptr_->getModifiedSubnets6(ServerSelector::UNASSIGNED(), + timestamps_["two days ago"]) + ); + ASSERT_EQ(2, returned_subnets.size()); + + // If we ask for any subnet by subnet id, it should be returned too. + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ANY(), + subnet->getID())); + ASSERT_TRUE(returned_subnet); + + ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ANY(), + subnet->toText())); + ASSERT_TRUE(returned_subnet); + + // Deleting the subnet with the mismatched server tag should not affect our + // subnet. + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), + subnet->getID()) + ); + EXPECT_EQ(0, deleted_count); + + // Also, if we delete all subnets for server1. + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1")) + ); + EXPECT_EQ(0, deleted_count); + + // We can delete this subnet when we specify ANY and the matching id. + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSubnet6(ServerSelector::ANY(), subnet->getID()) + ); + EXPECT_EQ(1, deleted_count); + + // We can delete all subnets using UNASSIGNED selector. + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteAllSubnets6(ServerSelector::UNASSIGNED()); + ); + EXPECT_EQ(1, deleted_count); +} + +void +GenericConfigBackendDHCPv6Test::getModifiedSubnets6Test() { + // Explicitly set timestamps of subnets. First subnet has a timestamp + // pointing to the future. Second subnet has timestamp pointing to the + // past (yesterday). Third subnet has a timestamp pointing to the + // past (an hour ago). + test_subnets_[1]->setModificationTime(timestamps_["tomorrow"]); + test_subnets_[2]->setModificationTime(timestamps_["yesterday"]); + test_subnets_[3]->setModificationTime(timestamps_["today"]); + + // Insert subnets into the database. + for (int i = 1; i < test_subnets_.size(); ++i) { + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), + test_subnets_[i]); + } + + // Fetch subnets with timestamp later than today. Only one subnet + // should be returned. + Subnet6Collection + subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(), + timestamps_["after today"]); + ASSERT_EQ(1, subnets.size()); + + // All subnets should also be returned for explicitly specified server tag. + subnets = cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"), + timestamps_["after today"]); + ASSERT_EQ(1, subnets.size()); + + // Fetch subnets with timestamp later than yesterday. We should get + // two subnets. + subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(), + timestamps_["after yesterday"]); + ASSERT_EQ(2, subnets.size()); + + // Fetch subnets with timestamp later than tomorrow. Nothing should + // be returned. + subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(), + timestamps_["after tomorrow"]); + ASSERT_TRUE(subnets.empty()); +} + +void +GenericConfigBackendDHCPv6Test::subnetLifetimeTest() { + // Insert new subnet with unspecified valid lifetime + Triplet<uint32_t> unspecified; + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 64, 30, 40, + unspecified, unspecified, 1111)); + subnet->setIface("eth1"); + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // Fetch this subnet by subnet identifier + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + // Verified returned and original subnets match. + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + + // Update the preferred and valid lifetime. + subnet->setPreferred( Triplet<uint32_t>(100, 200, 300)); + subnet->setValid( Triplet<uint32_t>(200, 300, 400)); + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // Fetch and verify again. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), subnet->getID()); + ASSERT_TRUE(returned_subnet); + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); +} + +void +GenericConfigBackendDHCPv6Test::getSharedNetworkSubnets6Test() { + // Assign test subnets to shared networks level1 and level2. + test_subnets_[1]->setSharedNetworkName("level1"); + test_subnets_[2]->setSharedNetworkName("level2"); + test_subnets_[3]->setSharedNetworkName("level2"); + + // Store shared networks in the database. + for (auto network : test_networks_) { + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network); + } + + // Store subnets in the database. + for (auto subnet : test_subnets_) { + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + } + + // Fetch all subnets belonging to shared network level1. + Subnet6Collection subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ALL(), + "level1"); + ASSERT_EQ(1, subnets.size()); + + // Returned subnet should match test subnet #1. + EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(), + (*subnets.begin())->toElement())); + + // All subnets should also be returned for ANY server. + subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ANY(), "level1"); + ASSERT_EQ(1, subnets.size()); + + // Returned subnet should match test subnet #1. + EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(), + (*subnets.begin())->toElement())); + + // Check server tag + ASSERT_EQ(1, (*subnets.begin())->getServerTags().size()); + EXPECT_EQ("all", (*subnets.begin())->getServerTags().begin()->get()); + + // Fetch all subnets belonging to shared network level2. + subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ALL(), "level2"); + ASSERT_EQ(2, subnets.size()); + + ElementPtr test_list = Element::createList(); + test_list->add(test_subnets_[2]->toElement()); + test_list->add(test_subnets_[3]->toElement()); + + ElementPtr returned_list = Element::createList(); + auto subnet = subnets.begin(); + returned_list->add((*subnet)->toElement()); + returned_list->add((*++subnet)->toElement()); + + EXPECT_TRUE(isEquivalent(returned_list, test_list)); + + // All subnets should also be returned for explicitly specified server tag. + subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ONE("server1"), "level2"); + ASSERT_EQ(2, subnets.size()); + + returned_list = Element::createList(); + subnet = subnets.begin(); + returned_list->add((*subnet)->toElement()); + returned_list->add((*++subnet)->toElement()); + + EXPECT_TRUE(isEquivalent(returned_list, test_list)); +} + +void +GenericConfigBackendDHCPv6Test::subnetUpdatePoolsTest() { + + auto test_subnet_update = [this](const std::string& subnet_prefix, + const SubnetID& subnet_id) { + // Add the subnet with two address pools and two prefix delegation + // pools. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), + test_subnets_[0])); + // Make sure that the pools have been added to the database. + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(2, countRows("dhcp6_pd_pool")); + + // Create the subnet without options which updates the existing + // subnet. + Subnet6Ptr subnet(new Subnet6(IOAddress(subnet_prefix), 64, 30, 60, 50, 60, + subnet_id)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); + // Check that options are gone. + EXPECT_EQ(0, countRows("dhcp6_pool")); + EXPECT_EQ(0, countRows("dhcp6_pd_pool")); + }; + + { + SCOPED_TRACE("update subnet, modify subnet id"); + // Create another subnet with the same prefix as the original subnet but + // different id. This is legal to update the subnet id if the prefix is + // stable. However, the new subnet has no address pools, so we need to + // check of the pools associated with the existing subnet instance are + // gone after the update. + test_subnet_update("2001:db8::", 2048); + } + + { + SCOPED_TRACE("update subnet, modify prefix"); + // Create a subnet with the same subnet id but different prefix. + // The prefix should be updated. + test_subnet_update("2001:db9::", 1024); + } +} + +void +GenericConfigBackendDHCPv6Test::subnetOptionsTest() { + // Add the subnet with two pools and three options. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0])); + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(2, countRows("dhcp6_pd_pool")); + EXPECT_EQ(3, countRows("dhcp6_options")); + + // The second subnet uses the same subnet id, so this operation should replace + // the existing subnet and its options. The new instance has four pools, each + // including one option, so we should end up with four options. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[1])); + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(2, countRows("dhcp6_pd_pool")); + EXPECT_EQ(4, countRows("dhcp6_options")); + + // Add third subnet with a single option. The number of options in the database + // should now be 5. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2])); + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(2, countRows("dhcp6_pd_pool")); + EXPECT_EQ(5, countRows("dhcp6_options")); + + // Delete the subnet. All options and pools it contains should also be removed, leaving + // the last added subnet and its sole option. + ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), test_subnets_[1]->getID())); + EXPECT_EQ(1, countRows("dhcp6_subnet")); + EXPECT_EQ(0, countRows("dhcp6_pool")); + EXPECT_EQ(0, countRows("dhcp6_pd_pool")); + EXPECT_EQ(1, countRows("dhcp6_options")); + + // Add the first subnet again. We should now have 4 options: 3 options from the + // newly added subnet and one option from the existing subnet. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0])); + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(2, countRows("dhcp6_pd_pool")); + EXPECT_EQ(4, countRows("dhcp6_options")); + + // Delete the subnet including 3 options. The option from the other subnet should not + // be affected. + ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), test_subnets_[0]->getID())); + EXPECT_EQ(1, countRows("dhcp6_subnet")); + EXPECT_EQ(0, countRows("dhcp6_pool")); + EXPECT_EQ(0, countRows("dhcp6_pd_pool")); + EXPECT_EQ(1, countRows("dhcp6_options")); +} + +void +GenericConfigBackendDHCPv6Test::getSharedNetwork6Test() { + // Insert the server2 into the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + auto shared_network = test_networks_[0]; + auto shared_network2 = test_networks_[2]; + + // Insert two shared networks, one for all servers, and one for server2. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server2"), + shared_network2)); + + // We are not going to support selection of a single entry for multiple servers. + ASSERT_THROW(cbptr_->getSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + test_networks_[0]->getName()), + isc::InvalidOperation); + + // Test that this shared network will be fetched for various server selectors. + auto test_get_network = [this, &shared_network] (const std::string& test_case_name, + const ServerSelector& server_selector, + const std::string& expected_tag = ServerTag::ALL) { + SCOPED_TRACE(test_case_name); + SharedNetwork6Ptr network; + ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork6(server_selector, + shared_network->getName())); + ASSERT_TRUE(network); + + EXPECT_GT(network->getId(), 0); + ASSERT_EQ(1, network->getServerTags().size()); + EXPECT_EQ(expected_tag, network->getServerTags().begin()->get()); + + // The easiest way to verify whether the returned shared network matches the + // inserted shared network is to convert both to text. + EXPECT_EQ(shared_network->toElement()->str(), network->toElement()->str()); + }; + + { + SCOPED_TRACE("testing various server selectors before update"); + test_get_network("all servers", ServerSelector::ALL()); + test_get_network("one server", ServerSelector::ONE("server1")); + test_get_network("any server", ServerSelector::ANY()); + } + + { + SCOPED_TRACE("CREATE audit entry for a shared network"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::CREATE, + "shared network set"); + } + + // Update shared network in the database. + shared_network = test_networks_[1]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network)); + + { + SCOPED_TRACE("testing various server selectors after update"); + test_get_network("all servers after update", ServerSelector::ALL()); + test_get_network("one server after update", ServerSelector::ONE("server1")); + test_get_network("any server after update", ServerSelector::ANY()); + } + + { + SCOPED_TRACE("UPDATE audit entry for a shared network"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network set"); + } + + // The server2 specific shared network should not be returned if the + // server selector is not matching. + EXPECT_FALSE(cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network2->getName())); + EXPECT_FALSE(cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"), + shared_network2->getName())); + + { + SCOPED_TRACE("testing selectors for server2 specific shared network"); + shared_network = shared_network2; + test_get_network("one server", ServerSelector::ONE("server2"), "server2"); + test_get_network("any server", ServerSelector::ANY(), "server2"); + } +} + +void +GenericConfigBackendDHCPv6Test::getSharedNetwork6SelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ANY(), "level1")); + ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::UNASSIGNED(), "level1")); + ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ALL(), "level1")); + ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"), "level1")); + + // Not supported selectors. + ASSERT_THROW(cbptr_->getSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + "level1"), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateSharedNetwork6Test() { + auto shared_network = test_networks_[0]; + + // An attempt to insert the shared network for non-existing server should fail. + ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), + shared_network), + NullKeyError); + + // Insert the server1 into the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + // Insert the server2 into the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network)); + { + SCOPED_TRACE("CREATE audit entry for shared network and ALL servers"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::CREATE, + "shared network set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + shared_network)); + { + SCOPED_TRACE("UPDATE audit entry for shared network and MULTIPLE servers"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network set"); + } + + SharedNetwork6Ptr network; + ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork6(ServerSelector::ANY(), + shared_network->getName())); + ASSERT_TRUE(network); + EXPECT_TRUE(network->hasServerTag(ServerTag("server1"))); + EXPECT_TRUE(network->hasServerTag(ServerTag("server2"))); + EXPECT_FALSE(network->hasServerTag(ServerTag())); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateSharedNetwork6SelectorsTest() { + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + + // Supported selectors. + SharedNetwork6Ptr shared_network(new SharedNetwork6("all")); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network)); + shared_network.reset(new SharedNetwork6("one")); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), + shared_network)); + shared_network.reset(new SharedNetwork6("multiple")); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + shared_network)); + + // Not supported server selectors. + ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ANY(), shared_network), + isc::InvalidOperation); + + // Not implemented server selectors. + ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::UNASSIGNED(), + shared_network), + isc::NotImplemented); +} + +void +GenericConfigBackendDHCPv6Test::getSharedNetwork6WithOptionalUnspecifiedTest() { + // Insert new shared network. + SharedNetwork6Ptr shared_network = test_networks_[2]; + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network); + + // Fetch this shared network by name. + SharedNetwork6Ptr + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + test_networks_[2]->getName()); + ASSERT_TRUE(returned_network); + + EXPECT_TRUE(returned_network->getIface().unspecified()); + EXPECT_TRUE(returned_network->getIface().empty()); + + EXPECT_TRUE(returned_network->getClientClass().unspecified()); + EXPECT_TRUE(returned_network->getClientClass().empty()); + + EXPECT_TRUE(returned_network->getValid().unspecified()); + EXPECT_EQ(0, returned_network->getValid().get()); + + EXPECT_TRUE(returned_network->getPreferred().unspecified()); + EXPECT_EQ(0, returned_network->getPreferred().get()); + + EXPECT_TRUE(returned_network->getT1().unspecified()); + EXPECT_EQ(0, returned_network->getT1().get()); + + EXPECT_TRUE(returned_network->getT2().unspecified()); + EXPECT_EQ(0, returned_network->getT2().get()); + + EXPECT_TRUE(returned_network->getReservationsGlobal().unspecified()); + EXPECT_FALSE(returned_network->getReservationsGlobal().get()); + + EXPECT_TRUE(returned_network->getReservationsInSubnet().unspecified()); + EXPECT_TRUE(returned_network->getReservationsInSubnet().get()); + + EXPECT_TRUE(returned_network->getReservationsOutOfPool().unspecified()); + EXPECT_FALSE(returned_network->getReservationsOutOfPool().get()); + + EXPECT_TRUE(returned_network->getCalculateTeeTimes().unspecified()); + EXPECT_FALSE(returned_network->getCalculateTeeTimes().get()); + + EXPECT_TRUE(returned_network->getT1Percent().unspecified()); + EXPECT_EQ(0.0, returned_network->getT1Percent().get()); + + EXPECT_TRUE(returned_network->getT2Percent().unspecified()); + EXPECT_EQ(0.0, returned_network->getT2Percent().get()); + + EXPECT_TRUE(returned_network->getRapidCommit().unspecified()); + EXPECT_FALSE(returned_network->getRapidCommit().get()); + + EXPECT_FALSE(returned_network->getDdnsSendUpdates().unspecified()); + EXPECT_TRUE(returned_network->getDdnsSendUpdates().get()); + + EXPECT_FALSE(returned_network->getDdnsOverrideNoUpdate().unspecified()); + EXPECT_TRUE(returned_network->getDdnsOverrideNoUpdate().get()); + + EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().unspecified()); + EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().get()); + + EXPECT_FALSE(returned_network->getDdnsReplaceClientNameMode().unspecified()); + EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT, + returned_network->getDdnsReplaceClientNameMode().get()); + + EXPECT_FALSE(returned_network->getDdnsGeneratedPrefix().unspecified()); + EXPECT_EQ("myhost", returned_network->getDdnsGeneratedPrefix().get()); + + EXPECT_FALSE(returned_network->getDdnsQualifyingSuffix().unspecified()); + EXPECT_EQ("example.org", returned_network->getDdnsQualifyingSuffix().get()); +} + +void +GenericConfigBackendDHCPv6Test::deleteSharedNetworkSubnets6Test() { + ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::UNASSIGNED(), + test_networks_[1]->getName()), + isc::InvalidOperation); + ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ALL(), + test_networks_[1]->getName()), + isc::InvalidOperation); + ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ONE("server1"), + test_networks_[1]->getName()), + isc::InvalidOperation); + ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }), + test_networks_[1]->getName()), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::getAllSharedNetworks6Test() { + // Insert test shared networks into the database. Note that the second shared + // network will overwrite the first shared network as they use the same name. + for (auto network : test_networks_) { + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network); + + // That shared network overrides the first one so the audit entry should + // indicate an update. + if ((network->getName() == "level1") && (!audit_entries_["all"].empty())) { + SCOPED_TRACE("UPDATE audit entry for the shared network " + + network->getName()); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network set"); + + } else { + SCOPED_TRACE("CREATE audit entry for the shared network " + + network->getName()); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::CREATE, + "shared network set"); + } + } + + // Fetch all shared networks. + SharedNetwork6Collection networks = + cbptr_->getAllSharedNetworks6(ServerSelector::ALL()); + ASSERT_EQ(test_networks_.size() - 1, networks.size()); + + // All shared networks should also be returned for explicitly specified + // server tag. + networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1")); + ASSERT_EQ(test_networks_.size() - 1, networks.size()); + + // See if shared networks are returned ok. + for (auto i = 0; i < networks.size(); ++i) { + EXPECT_EQ(test_networks_[i + 1]->toElement()->str(), + networks[i]->toElement()->str()); + ASSERT_EQ(1, networks[i]->getServerTags().size()); + EXPECT_EQ("all", networks[i]->getServerTags().begin()->get()); + } + + // Add some subnets. + test_networks_[1]->add(test_subnets_[0]); + test_subnets_[2]->setSharedNetworkName("level2"); + test_networks_[2]->add(test_subnets_[3]); + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]); + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2]); + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[3]); + + // Both ways to attach a subnet are equivalent. + Subnet6Ptr subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + test_subnets_[0]->getID()); + ASSERT_TRUE(subnet); + EXPECT_EQ("level1", subnet->getSharedNetworkName()); + + { + SCOPED_TRACE("CREATE audit entry for subnets"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set", ServerSelector::ALL(), 3); + } + + // Deleting non-existing shared network should return 0. + EXPECT_EQ(0, cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), + "big-fish")); + // All shared networks should be still there. + ASSERT_EQ(test_networks_.size() - 1, networks.size()); + + // Should not delete the shared network for explicit server tag + // because our shared network is for all servers. + EXPECT_EQ(0, cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"), + test_networks_[1]->getName())); + + // Same for all shared networks. + EXPECT_EQ(0, cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1"))); + + // Delete first shared network with it subnets and verify it is gone. + // Begin by its subnet. + EXPECT_EQ(1, cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ANY(), + test_networks_[1]->getName())); + + { + SCOPED_TRACE("DELETE audit entry for subnets of the first shared network"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::DELETE, + "deleted all subnets for a shared network"); + } + + // Check that the subnet is gone.. + subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + test_subnets_[0]->getID()); + EXPECT_FALSE(subnet); + + // And after the shared network itself. + EXPECT_EQ(1, cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), + test_networks_[1]->getName())); + + networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL()); + ASSERT_EQ(test_networks_.size() - 2, networks.size()); + + { + SCOPED_TRACE("DELETE audit entry for the first shared network"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::DELETE, + "shared network deleted"); + } + + // Delete all. + EXPECT_EQ(2, cbptr_->deleteAllSharedNetworks6(ServerSelector::ALL())); + networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL()); + ASSERT_TRUE(networks.empty()); + + { + SCOPED_TRACE("DELETE audit entry for the remaining two shared networks"); + // The last parameter indicates that we expect four new audit entries, + // two for deleted shared networks and two for updated subnets + std::vector<ExpAuditEntry> exp_entries({ + { + "dhcp6_shared_network", + AuditEntry::ModificationType::DELETE, "deleted all shared networks" + }, + { + "dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, "deleted all shared networks" + }, + { + "dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, "deleted all shared networks" + }, + { + "dhcp6_shared_network", + AuditEntry::ModificationType::DELETE, "deleted all shared networks" + } + }); + + testNewAuditEntry(exp_entries, ServerSelector::ALL()); + } + + // Check that subnets are still there but detached. + subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + test_subnets_[2]->getID()); + ASSERT_TRUE(subnet); + EXPECT_TRUE(subnet->getSharedNetworkName().empty()); + subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + test_subnets_[3]->getID()); + ASSERT_TRUE(subnet); + EXPECT_TRUE(subnet->getSharedNetworkName().empty()); +} + +void +GenericConfigBackendDHCPv6Test::getAllSharedNetworks6SelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::UNASSIGNED())); + ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::ALL())); + ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1"))); + ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" }))); + + // Not supported selectors. + ASSERT_THROW(cbptr_->getAllSharedNetworks6(ServerSelector::ANY()), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::getAllSharedNetworks6WithServerTagsTest() { + auto shared_network1 = test_networks_[0]; + auto shared_network2 = test_networks_[2]; + auto shared_network3 = test_networks_[3]; + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network1)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), + shared_network2)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + shared_network3)); + + SharedNetwork6Collection networks; + + // All three networks are associated with the server1. + ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1"))); + EXPECT_EQ(3, networks.size()); + + // First network is associated with all servers. + auto returned_network = SharedNetworkFetcher6::get(networks, "level1"); + ASSERT_TRUE(returned_network); + EXPECT_TRUE(returned_network->hasAllServerTag()); + EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1"))); + EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2"))); + + // Second network is only associated with the server1. + returned_network = SharedNetworkFetcher6::get(networks, "level2"); + ASSERT_TRUE(returned_network); + EXPECT_FALSE(returned_network->hasAllServerTag()); + EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1"))); + EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2"))); + + // Third network is associated with both server1 and server2. + returned_network = SharedNetworkFetcher6::get(networks, "level3"); + ASSERT_TRUE(returned_network); + EXPECT_FALSE(returned_network->hasAllServerTag()); + EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1"))); + EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2"))); + + // For server2 we should only get two shared networks, i.e. first and last. + ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server2"))); + EXPECT_EQ(2, networks.size()); + + // First shared network is associated with all servers. + returned_network = SharedNetworkFetcher6::get(networks, "level1"); + ASSERT_TRUE(returned_network); + EXPECT_TRUE(returned_network->hasAllServerTag()); + EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1"))); + EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2"))); + + // Last shared network is associated with server1 and server2. + returned_network = SharedNetworkFetcher6::get(networks, "level3"); + ASSERT_TRUE(returned_network); + EXPECT_FALSE(returned_network->hasAllServerTag()); + EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1"))); + EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2"))); + + // Only the first shared network is associated with all servers. + ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL())); + EXPECT_EQ(1, networks.size()); + + returned_network = SharedNetworkFetcher6::get(networks, "level1"); + ASSERT_TRUE(returned_network); + EXPECT_TRUE(returned_network->hasAllServerTag()); + EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1"))); + EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2"))); +} + +void +GenericConfigBackendDHCPv6Test::getModifiedSharedNetworks6Test() { + // Explicitly set timestamps of shared networks. First shared + // network has a timestamp pointing to the future. Second shared + // network has timestamp pointing to the past (yesterday). + // Third shared network has a timestamp pointing to the + // past (an hour ago). + test_networks_[1]->setModificationTime(timestamps_["tomorrow"]); + test_networks_[2]->setModificationTime(timestamps_["yesterday"]); + test_networks_[3]->setModificationTime(timestamps_["today"]); + + // Insert shared networks into the database. + for (int i = 1; i < test_networks_.size(); ++i) { + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + test_networks_[i]); + } + + // Fetch shared networks with timestamp later than today. Only one + // shared network should be returned. + SharedNetwork6Collection + networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(), + timestamps_["after today"]); + ASSERT_EQ(1, networks.size()); + + // Fetch shared networks with timestamp later than yesterday. We + // should get two shared networks. + networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(), + timestamps_["after yesterday"]); + ASSERT_EQ(2, networks.size()); + + // Fetch shared networks with timestamp later than tomorrow. Nothing + // should be returned. + networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(), + timestamps_["after tomorrow"]); + ASSERT_TRUE(networks.empty()); +} + +void +GenericConfigBackendDHCPv6Test::getModifiedSharedNetworks6SelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::UNASSIGNED(), + timestamps_["yesterday"])); + ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(), + timestamps_["yesterday"])); + ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::ONE("server1"), + timestamps_["yesterday"])); + ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" }), + timestamps_["yesterday"])); + + // Not supported selectors. + ASSERT_THROW(cbptr_->getModifiedSharedNetworks6(ServerSelector::ANY(), + timestamps_["yesterday"]), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::deleteSharedNetwork6Test() { + // Create two servers in the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("CREATE audit entry for server"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + auto shared_network1 = test_networks_[0]; + auto shared_network2 = test_networks_[2]; + auto shared_network3 = test_networks_[3]; + + // Insert three shared networks, one for all servers, one for server2 and + // one for two servers: server1 and server2. + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network1) + ); + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server2"), shared_network2) + ); + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + shared_network3) + ); + + auto test_no_delete = [this] (const std::string& test_case_name, + const ServerSelector& server_selector, + const SharedNetwork6Ptr& shared_network) { + SCOPED_TRACE(test_case_name); + uint64_t deleted_count = 0; + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSharedNetwork6(server_selector, + shared_network->getName()) + ); + EXPECT_EQ(0, deleted_count); + }; + + { + SCOPED_TRACE("Test valid but non matching server selectors"); + test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"), + shared_network1); + test_no_delete("selector: all, actual: one", ServerSelector::ALL(), + shared_network2); + test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(), + shared_network3); + } + + // We are not going to support deletion of a single entry for multiple servers. + ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + shared_network3->getName()), + isc::InvalidOperation); + + // We currently don't support deleting a shared network with specifying + // an unassigned server tag. Use ANY to delete any subnet instead. + ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::UNASSIGNED(), + shared_network1->getName()), + isc::NotImplemented); + + // Test successful deletion of a shared network. + auto test_delete = [this] (const std::string& test_case_name, + const ServerSelector& server_selector, + const SharedNetwork6Ptr& shared_network) { + SCOPED_TRACE(test_case_name); + uint64_t deleted_count = 0; + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSharedNetwork6(server_selector, + shared_network->getName()) + ); + EXPECT_EQ(1, deleted_count); + + EXPECT_FALSE(cbptr_->getSharedNetwork6(server_selector, + shared_network->getName())); + }; + + test_delete("all servers", ServerSelector::ALL(), shared_network1); + test_delete("any server", ServerSelector::ANY(), shared_network2); + test_delete("one server", ServerSelector::ONE("server1"), shared_network3); +} + +void +GenericConfigBackendDHCPv6Test::deleteSharedNetwork6SelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ANY(), "level1")); + ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), "level1")); + ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"), "level1")); + + // Not supported selectors. + ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + "level1"), + isc::InvalidOperation); + + // Not implemented selectors. + ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::UNASSIGNED(), "level1"), + isc::NotImplemented); +} + +void +GenericConfigBackendDHCPv6Test::deleteAllSharedNetworks6SelectorsTest() { + // Supported selectors. + ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::UNASSIGNED())); + ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::ALL())); + ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1"))); + + // Not supported selectors. + ASSERT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::ANY()), + isc::InvalidOperation); + ASSERT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" })), + isc::InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::unassignedSharedNetworkTest() { + // Create the server. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + + // Create the shared networks and associate them with the server1. + auto shared_network = test_networks_[0]; + auto shared_network2 = test_networks_[2]; + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), shared_network) + ); + ASSERT_NO_THROW_LOG( + cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), shared_network2) + ); + + // Delete the server. The shared networks should be preserved but are + // considered orphaned, i.e. do not belong to any server. + uint64_t deleted_count = 0; + ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer6(ServerTag("server1"))); + EXPECT_EQ(1, deleted_count); + + // Trying to fetch this shared network by server tag should return no result. + SharedNetwork6Ptr returned_network; + ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"), + "level1")); + EXPECT_FALSE(returned_network); + + // The same if we use other calls. + SharedNetwork6Collection returned_networks; + ASSERT_NO_THROW_LOG( + returned_networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1")) + ); + EXPECT_TRUE(returned_networks.empty()); + + ASSERT_NO_THROW_LOG( + returned_networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ONE("server1"), + timestamps_["two days ago"]) + ); + EXPECT_TRUE(returned_networks.empty()); + + // We should get the shared network if we ask for unassigned. + ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::UNASSIGNED(), + "level1")); + ASSERT_TRUE(returned_network); + + // Also if we ask for all unassigned networks it should be returned. + ASSERT_NO_THROW_LOG(returned_networks = cbptr_->getAllSharedNetworks6(ServerSelector::UNASSIGNED())); + ASSERT_EQ(2, returned_networks.size()); + + // And all modified. + ASSERT_NO_THROW_LOG( + returned_networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::UNASSIGNED(), + timestamps_["two days ago"]) + ); + ASSERT_EQ(2, returned_networks.size()); + + // If we ask for any network by name, it should be returned too. + ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::ANY(), + "level1")); + ASSERT_TRUE(returned_network); + + // Deleting a shared network with the mismatched server tag should not affect + // our shared network. + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"), + "level1") + ); + EXPECT_EQ(0, deleted_count); + + // Also, if we delete all shared networks for server1. + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1")) + ); + EXPECT_EQ(0, deleted_count); + + // We can delete this shared network when we specify ANY and the matching name. + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteSharedNetwork6(ServerSelector::ANY(), "level1") + ); + EXPECT_EQ(1, deleted_count); + + // We can delete all networks using UNASSIGNED selector. + ASSERT_NO_THROW_LOG( + deleted_count = cbptr_->deleteAllSharedNetworks6(ServerSelector::UNASSIGNED()); + ); + EXPECT_EQ(1, deleted_count); +} + +void +GenericConfigBackendDHCPv6Test::sharedNetworkLifetimeTest() { + // Insert new shared network with unspecified valid lifetime + SharedNetwork6Ptr network(new SharedNetwork6("foo")); + Triplet<uint32_t> unspecified; + network->setPreferred(unspecified); + network->setValid(unspecified); + network->setIface("eth1"); + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network); + + // Fetch this shared network. + SharedNetwork6Ptr returned_network = + cbptr_->getSharedNetwork6(ServerSelector::ALL(), "foo"); + ASSERT_TRUE(returned_network); + + // Verified returned and original shared networks match. + EXPECT_EQ(network->toElement()->str(), + returned_network->toElement()->str()); + + // Update the preferred and valid lifetime. + network->setPreferred( Triplet<uint32_t>(100, 200, 300)); + network->setValid( Triplet<uint32_t>(200, 300, 400)); + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network); + + // Fetch and verify again. + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), "foo"); + ASSERT_TRUE(returned_network); + EXPECT_EQ(network->toElement()->str(), + returned_network->toElement()->str()); +} + +void +GenericConfigBackendDHCPv6Test::sharedNetworkOptionsTest() { + // Add shared network with three options. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[0])); + EXPECT_EQ(3, countRows("dhcp6_options")); + + // Add another shared network with a single option. The numnber of options in the + // database should now be 4. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[2])); + EXPECT_EQ(4, countRows("dhcp6_options")); + + // The second shared network uses the same name as the first shared network, so + // this operation should replace the existing shared network and its options. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[1])); + EXPECT_EQ(1, countRows("dhcp6_options")); + + // Remove the shared network. This should not affect options assigned to the + // other shared network. + ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), + test_networks_[1]->getName())); + EXPECT_EQ(1, countRows("dhcp6_shared_network")); + EXPECT_EQ(1, countRows("dhcp6_options")); + + // Create the first option again. The number of options should be equal to the + // sum of options associated with both shared networks. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[0])); + EXPECT_EQ(4, countRows("dhcp6_options")); + + // Delete this shared network. This should not affect the option associated + // with the remaining shared network. + ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), + test_networks_[0]->getName())); + EXPECT_EQ(1, countRows("dhcp6_shared_network")); + EXPECT_EQ(1, countRows("dhcp6_options")); +} + +void +GenericConfigBackendDHCPv6Test::getOptionDef6Test() { + // Insert new option definition. + OptionDefinitionPtr option_def = test_option_defs_[0]; + cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def); + + // Fetch this option_definition by subnet identifier. + OptionDefinitionPtr returned_option_def = + cbptr_->getOptionDef6(ServerSelector::ALL(), + test_option_defs_[0]->getCode(), + test_option_defs_[0]->getOptionSpaceName()); + ASSERT_TRUE(returned_option_def); + EXPECT_GT(returned_option_def->getId(), 0); + ASSERT_EQ(1, returned_option_def->getServerTags().size()); + EXPECT_EQ("all", returned_option_def->getServerTags().begin()->get()); + + EXPECT_TRUE(returned_option_def->equals(*option_def)); + + { + SCOPED_TRACE("CREATE audit entry for an option definition"); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::CREATE, + "option definition set"); + } + + // Update the option definition in the database. + OptionDefinitionPtr option_def2 = test_option_defs_[1]; + cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def2); + + // Fetch updated option definition and see if it matches. + returned_option_def = cbptr_->getOptionDef6(ServerSelector::ALL(), + test_option_defs_[1]->getCode(), + test_option_defs_[1]->getOptionSpaceName()); + EXPECT_TRUE(returned_option_def->equals(*option_def2)); + + // Fetching option definition for an explicitly specified server tag + // should succeed too. + returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server1"), + test_option_defs_[1]->getCode(), + test_option_defs_[1]->getOptionSpaceName()); + EXPECT_TRUE(returned_option_def->equals(*option_def2)); + + { + SCOPED_TRACE("UPDATE audit entry for an option definition"); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::UPDATE, + "option definition set"); + } +} + +void +GenericConfigBackendDHCPv6Test::optionDefs6WithServerTagsTest() { + OptionDefinitionPtr option1 = test_option_defs_[0]; + OptionDefinitionPtr option2 = test_option_defs_[1]; + OptionDefinitionPtr option3 = test_option_defs_[4]; + + // An attempt to create option definition for non-existing server should + // fail. + ASSERT_THROW(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server1"), + option1), + NullKeyError); + + // Create two servers. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("server2 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + // This time creation of the option definition for the server1 should pass. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server1"), + option1)); + { + SCOPED_TRACE("option definition for server1 is set"); + // The value of 3 means there should be 3 audit entries available for the + // server1, two that indicate creation of the servers and one that we + // validate, which sets the option definition. + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::CREATE, + "option definition set", + ServerSelector::ONE("server1"), + 3, 1); + } + + // Creation of the option definition for the server2 should also pass. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server2"), + option2)); + { + SCOPED_TRACE("option definition for server2 is set"); + // Same as in case of the server1, there should be 3 audit entries and + // we validate one of them. + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::CREATE, + "option definition set", + ServerSelector::ONE("server2"), + 3, 1); + } + + // Finally, creation of the option definition for all servers should + // also pass. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), + option3)); + { + SCOPED_TRACE("option definition for server2 is set"); + // There should be one new audit entry for all servers. It logs + // the insertion of the option definition. + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::CREATE, + "option definition set", + ServerSelector::ALL(), + 1, 1); + } + + OptionDefinitionPtr returned_option_def; + + // Try to fetch the option definition specified for all servers. It should + // return the third one. + ASSERT_NO_THROW_LOG( + returned_option_def = cbptr_->getOptionDef6(ServerSelector::ALL(), + option3->getCode(), + option3->getOptionSpaceName()) + ); + ASSERT_TRUE(returned_option_def); + EXPECT_TRUE(returned_option_def->equals(*option3)); + + // Try to fetch the option definition specified for server1. It should + // override the definition for all servers. + ASSERT_NO_THROW_LOG( + returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server1"), + option1->getCode(), + option1->getOptionSpaceName()) + ); + ASSERT_TRUE(returned_option_def); + EXPECT_TRUE(returned_option_def->equals(*option1)); + + // The same in case of the server2. + ASSERT_NO_THROW_LOG( + returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server2"), + option2->getCode(), + option2->getOptionSpaceName()) + ); + ASSERT_TRUE(returned_option_def); + EXPECT_TRUE(returned_option_def->equals(*option2)); + + OptionDefContainer returned_option_defs; + + // Try to fetch the collection of the option definitions for server1, server2 + // and server3. The server3 does not have an explicit option definition, so + // for this server we should get the definition associated with "all" servers. + ASSERT_NO_THROW_LOG( + returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector:: + MULTIPLE({ "server1", "server2", + "server3" })); + ); + ASSERT_EQ(3, returned_option_defs.size()); + + // Check that expected option definitions have been returned. + auto current_option = returned_option_defs.begin(); + EXPECT_TRUE((*current_option)->equals(*option1)); + EXPECT_TRUE((*(++current_option))->equals(*option2)); + EXPECT_TRUE((*(++current_option))->equals(*option3)); + + // Try to fetch the collection of options specified for all servers. + // This excludes the options specific to server1 and server2. It returns + // only the common ones. + ASSERT_NO_THROW_LOG( + returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL()); + + ); + ASSERT_EQ(1, returned_option_defs.size()); + EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3)); + + // Delete the server1. It should remove associations of this server with the + // option definitions and the option definition itself. + ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1"))); + ASSERT_NO_THROW_LOG( + returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ONE("server1")); + + ); + ASSERT_EQ(1, returned_option_defs.size()); + EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3)); + + { + SCOPED_TRACE("DELETE audit entry for the option definition after server deletion"); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::DELETE, + "deleting a server", ServerSelector::ONE("server1"), + 2, 1); + } + + // Attempt to delete option definition for server1. + uint64_t deleted_num = 0; + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef6(ServerSelector::ONE("server1"), + option1->getCode(), + option1->getOptionSpaceName())); + EXPECT_EQ(0, deleted_num); + + // Deleting the existing option definition for server2 should succeed. + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef6(ServerSelector::ONE("server2"), + option2->getCode(), + option2->getOptionSpaceName())); + EXPECT_EQ(1, deleted_num); + + // Create this option definition again to test that deletion of all servers + // removes it too. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server2"), + option2)); + + // Delete all servers, except 'all'. + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6()); + ASSERT_NO_THROW_LOG( + returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL()); + ); + EXPECT_EQ(1, deleted_num); + EXPECT_EQ(1, returned_option_defs.size()); + EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3)); + + { + SCOPED_TRACE("DELETE audit entry for the option definition after deletion of" + " all servers"); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::DELETE, + "deleting all servers", ServerSelector::ONE("server2"), + 4, 1); + } +} + +void +GenericConfigBackendDHCPv6Test::getAllOptionDefs6Test() { + // Insert test option definitions into the database. Note that the second + // option definition will overwrite the first option definition as they use + // the same code and space. + size_t updates_num = 0; + for (auto option_def : test_option_defs_) { + cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def); + + // That option definition overrides the first one so the audit entry should + // indicate an update. + auto name = option_def->getName(); + if (name.find("bar") != std::string::npos) { + SCOPED_TRACE("UPDATE audit entry for the option definition " + name); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::UPDATE, + "option definition set"); + ++updates_num; + + } else { + SCOPED_TRACE("CREATE audit entry for the option definition " + name); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::CREATE, + "option definition set"); + } + } + + // Fetch all option_definitions. + OptionDefContainer option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL()); + ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size()); + + // All option definitions should also be returned for explicitly specified + // server tag. + option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ONE("server1")); + ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size()); + + // See if option definitions are returned ok. + for (auto def = option_defs.begin(); def != option_defs.end(); ++def) { + ASSERT_EQ(1, (*def)->getServerTags().size()); + EXPECT_EQ("all", (*def)->getServerTags().begin()->get()); + bool success = false; + for (auto i = 1; i < test_option_defs_.size(); ++i) { + if ((*def)->equals(*test_option_defs_[i])) { + success = true; + } + } + ASSERT_TRUE(success) << "failed for option definition " << (*def)->getCode() + << ", option space " << (*def)->getOptionSpaceName(); + } + + // Deleting non-existing option definition should return 0. + EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ALL(), + 99, "non-exiting-space")); + // All option definitions should be still there. + ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size()); + + // Should not delete option definition for explicit server tag + // because our option definition is for all servers. + EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ONE("server1"), + test_option_defs_[1]->getCode(), + test_option_defs_[1]->getOptionSpaceName())); + + // Same for all option definitions. + EXPECT_EQ(0, cbptr_->deleteAllOptionDefs6(ServerSelector::ONE("server1"))); + + // Delete one of the option definitions and see if it is gone. + EXPECT_EQ(1, cbptr_->deleteOptionDef6(ServerSelector::ALL(), + test_option_defs_[2]->getCode(), + test_option_defs_[2]->getOptionSpaceName())); + ASSERT_FALSE(cbptr_->getOptionDef6(ServerSelector::ALL(), + test_option_defs_[2]->getCode(), + test_option_defs_[2]->getOptionSpaceName())); + + { + SCOPED_TRACE("DELETE audit entry for the first option definition"); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::DELETE, + "option definition deleted"); + } + + // Delete all remaining option definitions. + EXPECT_EQ(2, cbptr_->deleteAllOptionDefs6(ServerSelector::ALL())); + option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL()); + ASSERT_TRUE(option_defs.empty()); + + { + SCOPED_TRACE("DELETE audit entries for the remaining option definitions"); + // The last parameter indicates that we expect two new audit entries. + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::DELETE, + "deleted all option definitions", + ServerSelector::ALL(), 2); + } +} + +void +GenericConfigBackendDHCPv6Test::getModifiedOptionDefs6Test() { + // Explicitly set timestamps of option definitions. First option + // definition has a timestamp pointing to the future. Second option + // definition has timestamp pointing to the past (yesterday). + // Third option definitions has a timestamp pointing to the + // past (an hour ago). + test_option_defs_[1]->setModificationTime(timestamps_["tomorrow"]); + test_option_defs_[2]->setModificationTime(timestamps_["yesterday"]); + test_option_defs_[3]->setModificationTime(timestamps_["today"]); + + // Insert option definitions into the database. + for (int i = 1; i < test_networks_.size(); ++i) { + cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), + test_option_defs_[i]); + } + + // Fetch option definitions with timestamp later than today. Only one + // option definition should be returned. + OptionDefContainer + option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(), + timestamps_["after today"]); + ASSERT_EQ(1, option_defs.size()); + + // Fetch option definitions with timestamp later than yesterday. We + // should get two option definitions. + option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(), + timestamps_["after yesterday"]); + ASSERT_EQ(2, option_defs.size()); + + // Fetch option definitions with timestamp later than tomorrow. Nothing + // should be returned. + option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(), + timestamps_["after tomorrow"]); + ASSERT_TRUE(option_defs.empty()); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateDeleteOption6Test() { + // Add option to the database. + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + opt_posix_timezone); + + // Make sure we can retrieve this option and that it is equal to the + // option we have inserted into the database. + OptionDescriptorPtr returned_opt_posix_timezone = + cbptr_->getOption6(ServerSelector::ALL(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_); + ASSERT_TRUE(returned_opt_posix_timezone); + + { + SCOPED_TRACE("verify created option"); + testOptionsEquivalent(*opt_posix_timezone, + *returned_opt_posix_timezone); + } + + { + SCOPED_TRACE("CREATE audit entry for an option"); + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::CREATE, + "global option set"); + } + + // Modify option and update it in the database. + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ALL(), + opt_posix_timezone); + + // Retrieve the option again and make sure that updates were + // properly propagated to the database. Use explicit server selector + // which should also return this option. + returned_opt_posix_timezone = cbptr_->getOption6(ServerSelector::ONE("server1"), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_); + ASSERT_TRUE(returned_opt_posix_timezone); + + { + SCOPED_TRACE("verify updated option"); + testOptionsEquivalent(*opt_posix_timezone, + *returned_opt_posix_timezone); + } + + { + SCOPED_TRACE("UPDATE audit entry for an option"); + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::UPDATE, + "global option set"); + } + + // Deleting an option with explicitly specified server tag should fail. + EXPECT_EQ(0, cbptr_->deleteOption6(ServerSelector::ONE("server1"), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Deleting option for all servers should succeed. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ALL(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + EXPECT_FALSE(cbptr_->getOption6(ServerSelector::ALL(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + { + SCOPED_TRACE("DELETE audit entry for an option"); + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::DELETE, + "global option deleted"); + } +} + +void +GenericConfigBackendDHCPv6Test::globalOptions6WithServerTagsTest() { + OptionDescriptorPtr opt_timezone1 = test_options_[0]; + OptionDescriptorPtr opt_timezone2 = test_options_[6]; + OptionDescriptorPtr opt_timezone3 = test_options_[7]; + + ASSERT_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"), + opt_timezone1), + NullKeyError); + + // Create two servers. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("server2 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"), + opt_timezone1)); + { + SCOPED_TRACE("global option for server1 is set"); + // The value of 3 means there should be 3 audit entries available for the + // server1, two that indicate creation of the servers and one that we + // validate, which sets the global option. + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::CREATE, + "global option set", + ServerSelector::ONE("server1"), + 3, 1); + + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"), + opt_timezone2)); + { + SCOPED_TRACE("global option for server2 is set"); + // Same as in case of the server1, there should be 3 audit entries and + // we validate one of them. + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::CREATE, + "global option set", + ServerSelector::ONE("server2"), + 3, 1); + + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ALL(), + opt_timezone3)); + { + SCOPED_TRACE("global option for all servers is set"); + // There should be one new audit entry for all servers. It logs + // the insertion of the global option. + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::CREATE, + "global option set", + ServerSelector::ALL(), + 1, 1); + + } + + OptionDescriptorPtr returned_option; + + // Try to fetch the option specified for all servers. It should return + // the third option. + ASSERT_NO_THROW_LOG( + returned_option = cbptr_->getOption6(ServerSelector::ALL(), + opt_timezone3->option_->getType(), + opt_timezone3->space_name_); + ); + ASSERT_TRUE(returned_option); + testOptionsEquivalent(*opt_timezone3, *returned_option); + + // Try to fetch the option specified for the server1. It should override the + // option specified for all servers. + ASSERT_NO_THROW_LOG( + returned_option = cbptr_->getOption6(ServerSelector::ONE("server1"), + opt_timezone1->option_->getType(), + opt_timezone1->space_name_); + ); + ASSERT_TRUE(returned_option); + testOptionsEquivalent(*opt_timezone1, *returned_option); + + // The same in case of the server2. + ASSERT_NO_THROW_LOG( + returned_option = cbptr_->getOption6(ServerSelector::ONE("server2"), + opt_timezone2->option_->getType(), + opt_timezone2->space_name_); + ); + ASSERT_TRUE(returned_option); + testOptionsEquivalent(*opt_timezone2, *returned_option); + + OptionContainer returned_options; + + // Try to fetch the collection of global options for the server1, server2 + // and server3. The server3 does not have an explicit value so for this server + // we should get the option associated with "all" servers. + ASSERT_NO_THROW_LOG( + returned_options = cbptr_->getAllOptions6(ServerSelector:: + MULTIPLE({ "server1", "server2", + "server3" })); + ); + ASSERT_EQ(3, returned_options.size()); + + // Check that expected options have been returned. + auto current_option = returned_options.begin(); + testOptionsEquivalent(*opt_timezone1, *current_option); + testOptionsEquivalent(*opt_timezone2, *(++current_option)); + testOptionsEquivalent(*opt_timezone3, *(++current_option)); + + // Try to fetch the collection of options specified for all servers. + // This excludes the options specific to server1 and server2. It returns + // only the common ones. + ASSERT_NO_THROW_LOG( + returned_options = cbptr_->getAllOptions6(ServerSelector::ALL()); + ); + ASSERT_EQ(1, returned_options.size()); + testOptionsEquivalent(*opt_timezone3, *returned_options.begin()); + + // Delete the server1. It should remove associations of this server with the + // option and the option itself. + ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1"))); + ASSERT_NO_THROW_LOG( + returned_options = cbptr_->getAllOptions6(ServerSelector::ONE("server1")); + ); + ASSERT_EQ(1, returned_options.size()); + testOptionsEquivalent(*opt_timezone3, *returned_options.begin()); + + { + SCOPED_TRACE("DELETE audit entry for the global option after server deletion"); + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::DELETE, + "deleting a server", ServerSelector::ONE("server1"), + 2, 1); + } + + // Attempt to delete global option for server1. + uint64_t deleted_num = 0; + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption6(ServerSelector::ONE("server1"), + opt_timezone1->option_->getType(), + opt_timezone1->space_name_)); + EXPECT_EQ(0, deleted_num); + + // Deleting the existing option for server2 should succeed. + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption6(ServerSelector::ONE("server2"), + opt_timezone2->option_->getType(), + opt_timezone2->space_name_)); + EXPECT_EQ(1, deleted_num); + + // Create this option again to test that deletion of all servers removes it too. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"), + opt_timezone2)); + + // Delete all servers, except 'all'. + ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6()); + ASSERT_NO_THROW_LOG( + returned_options = cbptr_->getAllOptions6(ServerSelector::ALL()); + ); + EXPECT_EQ(1, deleted_num); + ASSERT_EQ(1, returned_options.size()); + testOptionsEquivalent(*opt_timezone3, *returned_options.begin()); + + { + SCOPED_TRACE("DELETE audit entry for the global option after deletion of" + " all servers"); + testNewAuditEntry("dhcp6_options", + AuditEntry::ModificationType::DELETE, + "deleting all servers", ServerSelector::ONE("server2"), + 4, 1); + } +} + +void +GenericConfigBackendDHCPv6Test::getAllOptions6Test() { + // Add three global options to the database. + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[0]); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[1]); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[5]); + + // Retrieve all these options. + OptionContainer returned_options = cbptr_->getAllOptions6(ServerSelector::ALL()); + ASSERT_EQ(3, returned_options.size()); + + // Fetching global options with explicitly specified server tag should return + // the same result. + returned_options = cbptr_->getAllOptions6(ServerSelector::ONE("server1")); + ASSERT_EQ(3, returned_options.size()); + + // Get the container index used to search options by option code. + const OptionContainerTypeIndex& index = returned_options.get<1>(); + + // Verify that all options we put into the database were + // returned. + { + SCOPED_TRACE("verify test_options_[0]"); + auto option0 = index.find(test_options_[0]->option_->getType()); + ASSERT_FALSE(option0 == index.end()); + testOptionsEquivalent(*test_options_[0], *option0); + EXPECT_GT(option0->getId(), 0); + ASSERT_EQ(1, option0->getServerTags().size()); + EXPECT_EQ("all", option0->getServerTags().begin()->get()); + } + + { + SCOPED_TRACE("verify test_options_[1]"); + auto option1 = index.find(test_options_[1]->option_->getType()); + ASSERT_FALSE(option1 == index.end()); + testOptionsEquivalent(*test_options_[1], *option1); + EXPECT_GT(option1->getId(), 0); + ASSERT_EQ(1, option1->getServerTags().size()); + EXPECT_EQ("all", option1->getServerTags().begin()->get()); + } + + { + SCOPED_TRACE("verify test_options_[5]"); + auto option5 = index.find(test_options_[5]->option_->getType()); + ASSERT_FALSE(option5 == index.end()); + testOptionsEquivalent(*test_options_[5], *option5); + EXPECT_GT(option5->getId(), 0); + ASSERT_EQ(1, option5->getServerTags().size()); + EXPECT_EQ("all", option5->getServerTags().begin()->get()); + } +} + +void +GenericConfigBackendDHCPv6Test::getModifiedOptions6Test() { + // Assign timestamps to the options we're going to store in the + // database. + test_options_[0]->setModificationTime(timestamps_["tomorrow"]); + test_options_[1]->setModificationTime(timestamps_["yesterday"]); + test_options_[5]->setModificationTime(timestamps_["today"]); + + // Put options into the database. + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[0]); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[1]); + cbptr_->createUpdateOption6(ServerSelector::ALL(), + test_options_[5]); + + // Get options with the timestamp later than today. Only + // one option should be returned. + OptionContainer returned_options = + cbptr_->getModifiedOptions6(ServerSelector::ALL(), + timestamps_["after today"]); + ASSERT_EQ(1, returned_options.size()); + + // Fetching modified options with explicitly specified server selector + // should return the same result. + returned_options = cbptr_->getModifiedOptions6(ServerSelector::ONE("server1"), + timestamps_["after today"]); + ASSERT_EQ(1, returned_options.size()); + + // The returned option should be the one with the timestamp + // set to tomorrow. + const OptionContainerTypeIndex& index = returned_options.get<1>(); + auto option0 = index.find(test_options_[0]->option_->getType()); + ASSERT_FALSE(option0 == index.end()); + { + SCOPED_TRACE("verify returned option"); + testOptionsEquivalent(*test_options_[0], *option0); + } +} + +void +GenericConfigBackendDHCPv6Test::createUpdateDeleteSubnetOption6Test() { + // Insert new subnet. + Subnet6Ptr subnet = test_subnets_[1]; + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // Fetch this subnet by subnet identifier. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + { + SCOPED_TRACE("CREATE audit entry for a new subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + // The inserted subnet contains four options. + ASSERT_EQ(4, countRows("dhcp6_options")); + + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + cbptr_->createUpdateOption6(ServerSelector::ANY(), subnet->getID(), + opt_posix_timezone); + + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + OptionDescriptor returned_opt_posix_timezone = + returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + + { + SCOPED_TRACE("verify returned option"); + testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone); + EXPECT_GT(returned_opt_posix_timezone.getId(), 0); + } + + { + SCOPED_TRACE("UPDATE audit entry for an added subnet option"); + // Instead of adding an audit entry for an option we add an audit + // entry for the entire subnet so as the server refreshes the + // subnet with the new option. Note that the server doesn't + // have means to retrieve only the newly added option. + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option set"); + } + + // We have added one option to the existing subnet. We should now have + // five options. + ASSERT_EQ(5, countRows("dhcp6_options")); + + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ANY(), subnet->getID(), + opt_posix_timezone); + + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + returned_opt_posix_timezone = + returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + + { + SCOPED_TRACE("verify returned option with modified persistence"); + testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone); + } + + { + SCOPED_TRACE("UPDATE audit entry for an updated subnet option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option set"); + } + + // Updating the option should replace the existing instance with the new + // instance. Therefore, we should still have five options. + ASSERT_EQ(5, countRows("dhcp6_options")); + + // It should succeed for any server. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(), subnet->getID(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE).option_); + + { + SCOPED_TRACE("UPDATE audit entry for a deleted subnet option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "subnet specific option deleted"); + } + + // We should have only four options after deleting one of them. + ASSERT_EQ(4, countRows("dhcp6_options")); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateDeletePoolOption6Test() { + // Insert new subnet. + Subnet6Ptr subnet = test_subnets_[1]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); + + { + SCOPED_TRACE("CREATE audit entry for a subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + // Inserted subnet has four options. + ASSERT_EQ(4, countRows("dhcp6_options")); + + // Add an option into the pool. + const PoolPtr pool = subnet->getPool(Lease::TYPE_NA, + IOAddress("2001:db8::10")); + ASSERT_TRUE(pool); + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ANY(), + pool->getFirstAddress(), + pool->getLastAddress(), + opt_posix_timezone)); + + // Query for a subnet. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + // The returned subnet should include our pool. + const PoolPtr returned_pool = returned_subnet->getPool(Lease::TYPE_NA, + IOAddress("2001:db8::10")); + ASSERT_TRUE(returned_pool); + + // The pool should contain option we added earlier. + OptionDescriptor returned_opt_posix_timezone = + returned_pool->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + + { + SCOPED_TRACE("verify returned pool option"); + testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone); + EXPECT_GT(returned_opt_posix_timezone.getId(), 0); + } + + { + SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option " + "to the address pool"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "address pool specific option set"); + } + + // With the newly inserted option we should now have five options. + ASSERT_EQ(5, countRows("dhcp6_options")); + + // Modify the option and update it in the database. + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ANY(), + pool->getFirstAddress(), + pool->getLastAddress(), + opt_posix_timezone); + + // Fetch the subnet and the corresponding pool. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + const PoolPtr returned_pool1 = returned_subnet->getPool(Lease::TYPE_NA, + IOAddress("2001:db8::10")); + ASSERT_TRUE(returned_pool1); + + // Test that the option has been correctly updated in the database. + returned_opt_posix_timezone = + returned_pool1->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + + { + SCOPED_TRACE("verify updated option with modified persistence"); + testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone); + } + + { + SCOPED_TRACE("UPDATE audit entry for a subnet when updating " + "address pool specific option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "address pool specific option set"); + } + + // The new option instance should replace the existing one, so we should + // still have five options. + ASSERT_EQ(5, countRows("dhcp6_options")); + + // Delete option for any server should succeed. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(), + pool->getFirstAddress(), + pool->getLastAddress(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Fetch the subnet and the pool from the database again to make sure + // that the option is really gone. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + const PoolPtr returned_pool2 = returned_subnet->getPool(Lease::TYPE_NA, + IOAddress("2001:db8::10")); + ASSERT_TRUE(returned_pool2); + + // Option should be gone. + EXPECT_FALSE(returned_pool2->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_NEW_POSIX_TIMEZONE).option_); + + { + SCOPED_TRACE("UPDATE audit entry for a subnet when deleting " + "address pool specific option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "address pool specific option deleted"); + } + + // The option has been deleted so the number of options should now + // be down to 4. + EXPECT_EQ(4, countRows("dhcp6_options")); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateDeletePdPoolOption6Test() { + // Insert new subnet. + Subnet6Ptr subnet = test_subnets_[1]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); + + { + SCOPED_TRACE("CREATE audit entry for a subnet"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::CREATE, + "subnet set"); + } + + // Inserted subnet has four options. + ASSERT_EQ(4, countRows("dhcp6_options")); + + // Add an option into the pd pool. + const PoolPtr pd_pool = subnet->getPool(Lease::TYPE_PD, + IOAddress("2001:db8:a:10::")); + ASSERT_TRUE(pd_pool); + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + int pd_pool_len = prefixLengthFromRange(pd_pool->getFirstAddress(), + pd_pool->getLastAddress()); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ANY(), + pd_pool->getFirstAddress(), + static_cast<uint8_t>(pd_pool_len), + opt_posix_timezone)); + + // Query for a subnet. + Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + + // The returned subnet should include our pool. + const PoolPtr returned_pd_pool = returned_subnet->getPool(Lease::TYPE_PD, + IOAddress("2001:db8:a:10::")); + ASSERT_TRUE(returned_pd_pool); + + // The pd pool should contain option we added earlier. + OptionDescriptor returned_opt_posix_timezone = + returned_pd_pool->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + + { + SCOPED_TRACE("verify returned pool option"); + testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone); + EXPECT_GT(returned_opt_posix_timezone.getId(), 0); + } + + { + SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option " + "to the prefix delegation pool"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "prefix delegation pool specific option set"); + } + + // With the newly inserted option we should now have five options. + ASSERT_EQ(5, countRows("dhcp6_options")); + + // Modify the option and update it in the database. + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ANY(), + pd_pool->getFirstAddress(), + static_cast<uint8_t>(pd_pool_len), + opt_posix_timezone); + + // Fetch the subnet and the corresponding pd pool. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + const PoolPtr returned_pd_pool1 = returned_subnet->getPool(Lease::TYPE_PD, + IOAddress("2001:db8:a:10::")); + ASSERT_TRUE(returned_pd_pool1); + + // Test that the option has been correctly updated in the database. + returned_opt_posix_timezone = + returned_pd_pool1->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + + { + SCOPED_TRACE("verify updated option with modified persistence"); + testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone); + } + + { + SCOPED_TRACE("UPDATE audit entry for a subnet when updating " + "prefix delegation pool specific option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "prefix delegation pool specific option set"); + } + + // The new option instance should replace the existing one, so we should + // still have five options. + ASSERT_EQ(5, countRows("dhcp6_options")); + + // Delete option for any server should succeed. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(), + pd_pool->getFirstAddress(), + static_cast<uint8_t>(pd_pool_len), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + + // Fetch the subnet and the pool from the database again to make sure + // that the option is really gone. + returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), + subnet->getID()); + ASSERT_TRUE(returned_subnet); + const PoolPtr returned_pd_pool2 = returned_subnet->getPool(Lease::TYPE_PD, + IOAddress("2001:db8:a:10::")); + ASSERT_TRUE(returned_pd_pool2); + + // Option should be gone. + EXPECT_FALSE(returned_pd_pool2->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_NEW_POSIX_TIMEZONE).option_); + + { + SCOPED_TRACE("UPDATE audit entry for a subnet when deleting " + "prefix delegation pool specific option"); + testNewAuditEntry("dhcp6_subnet", + AuditEntry::ModificationType::UPDATE, + "prefix delegation pool specific option deleted"); + } + + // The option has been deleted so the number of options should now + // be down to 4. + EXPECT_EQ(4, countRows("dhcp6_options")); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateDeleteSharedNetworkOption6Test() { + // Insert new shared network. + SharedNetwork6Ptr shared_network = test_networks_[1]; + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network); + + // Fetch this shared network by name. + SharedNetwork6Ptr returned_network = + cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network->getName()); + ASSERT_TRUE(returned_network); + + { + SCOPED_TRACE("CREATE audit entry for the new shared network"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::CREATE, + "shared network set"); + } + + // The inserted shared network has no options. + ASSERT_EQ(0, countRows("dhcp6_options")); + + OptionDescriptorPtr opt_posix_timezone = test_options_[0]; + cbptr_->createUpdateOption6(ServerSelector::ANY(), + shared_network->getName(), + opt_posix_timezone); + + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network->getName()); + ASSERT_TRUE(returned_network); + + OptionDescriptor returned_opt_posix_timezone = + returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + + { + SCOPED_TRACE("verify returned option"); + testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone); + EXPECT_GT(returned_opt_posix_timezone.getId(), 0); + } + + { + SCOPED_TRACE("UPDATE audit entry for the added shared network option"); + // Instead of adding an audit entry for an option we add an audit + // entry for the entire shared network so as the server refreshes the + // shared network with the new option. Note that the server doesn't + // have means to retrieve only the newly added option. + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network specific option set"); + } + + // One option should now be stored in the database. + ASSERT_EQ(1, countRows("dhcp6_options")); + + opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_; + cbptr_->createUpdateOption6(ServerSelector::ANY(), + shared_network->getName(), + opt_posix_timezone); + + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network->getName()); + ASSERT_TRUE(returned_network); + returned_opt_posix_timezone = + returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_posix_timezone.option_); + + { + SCOPED_TRACE("verify updated option with modified persistence"); + testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone); + } + + { + SCOPED_TRACE("UPDATE audit entry for the updated shared network option"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network specific option set"); + } + + // The new option instance should replace the existing option instance, + // so we should still have one option. + ASSERT_EQ(1, countRows("dhcp6_options")); + + // Deleting an option for any server should succeed. + EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(), + shared_network->getName(), + opt_posix_timezone->option_->getType(), + opt_posix_timezone->space_name_)); + returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), + shared_network->getName()); + ASSERT_TRUE(returned_network); + EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE).option_); + + { + SCOPED_TRACE("UPDATE audit entry for the deleted shared network option"); + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::UPDATE, + "shared network specific option deleted"); + } + + // After deleting the option we should be back to 0. + EXPECT_EQ(0, countRows("dhcp6_options")); +} + +void +GenericConfigBackendDHCPv6Test::subnetOptionIdOrderTest() { + + // Add a subnet with two pools with two options each. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[1])); + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(4, countRows("dhcp6_options")); + + // Add a second subnet with a single option. The number of options in the database + // should now be 3. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2])); + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(5, countRows("dhcp6_options")); + + // Now replace the first subnet with three options and two pools. This will cause + // the option id values for this subnet to be larger than those in the second + // subnet. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0])); + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(4, countRows("dhcp6_options")); + + // Now fetch all subnets. + Subnet6Collection subnets; + ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ALL())); + ASSERT_EQ(2, subnets.size()); + + // Verify that the subnets returned are as expected. + for (auto subnet : subnets) { + ASSERT_EQ(1, subnet->getServerTags().size()); + EXPECT_EQ("all", subnet->getServerTags().begin()->get()); + if (subnet->getID() == 1024) { + EXPECT_EQ(test_subnets_[0]->toElement()->str(), subnet->toElement()->str()); + } else if (subnet->getID() == 2048) { + EXPECT_EQ(test_subnets_[2]->toElement()->str(), subnet->toElement()->str()); + } else { + ADD_FAILURE() << "unexpected subnet id:" << subnet->getID(); + } + } +} + +void +GenericConfigBackendDHCPv6Test::sharedNetworkOptionIdOrderTest() { + auto level1_options = test_networks_[0]; + auto level1_no_options = test_networks_[1]; + auto level2 = test_networks_[2]; + + // Insert two shared networks. We insert level1 without options first, + // then level2. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + level1_no_options)); + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + level2)); + // Fetch all shared networks. + SharedNetwork6Collection networks = + cbptr_->getAllSharedNetworks6(ServerSelector::ALL()); + + ASSERT_EQ(2, networks.size()); + + // See if shared networks are returned ok. + for (auto i = 0; i < networks.size(); ++i) { + if (i == 0) { + // level1_no_options + EXPECT_EQ(level1_no_options->toElement()->str(), + networks[i]->toElement()->str()); + } else { + // bar + EXPECT_EQ(level2->toElement()->str(), + networks[i]->toElement()->str()); + } + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + level1_options)); + + // Fetch all shared networks. + networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL()); + ASSERT_EQ(2, networks.size()); + + // See if shared networks are returned ok. + for (auto i = 0; i < networks.size(); ++i) { + if (i == 0) { + // level1_no_options + EXPECT_EQ(level1_options->toElement()->str(), + networks[i]->toElement()->str()); + } else { + // bar + EXPECT_EQ(level2->toElement()->str(), + networks[i]->toElement()->str()); + } + } +} + +void +GenericConfigBackendDHCPv6Test::setAndGetAllClientClasses6Test() { + // Create a server. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + // Create first class. + auto class1 = test_client_classes_[0]; + class1->setTest("pkt6.msgtype == 1"); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + { + SCOPED_TRACE("client class foo is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + // Create second class. + auto class2 = test_client_classes_[1]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, "")); + { + SCOPED_TRACE("client class bar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + // Create third class. + auto class3 = test_client_classes_[2]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "")); + { + SCOPED_TRACE("client class foobar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + // Update the third class to depend on the second class. + class3->setTest("member('foo')"); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "")); + { + SCOPED_TRACE("client class bar is updated"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::UPDATE, + "client class set", + ServerSelector::ONE("server1")); + } + // Only the first class should be returned for the server selector ALL. + auto client_classes = cbptr_->getAllClientClasses6(ServerSelector::ALL()); + ASSERT_EQ(1, client_classes.getClasses()->size()); + // All three classes should be returned for the server1. + client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1")); + auto classes_list = client_classes.getClasses(); + ASSERT_EQ(3, classes_list->size()); + + auto fetched_class = classes_list->begin(); + ASSERT_EQ("foo", (*fetched_class)->getName()); + EXPECT_FALSE((*fetched_class)->getMatchExpr()); + EXPECT_EQ(class1->toElement()->str(), (*fetched_class)->toElement()->str()); + + ++fetched_class; + ASSERT_EQ("bar", (*fetched_class)->getName()); + EXPECT_FALSE((*fetched_class)->getMatchExpr()); + EXPECT_EQ(class2->toElement()->str(), (*fetched_class)->toElement()->str()); + + ++fetched_class; + ASSERT_EQ("foobar", (*fetched_class)->getName()); + EXPECT_FALSE((*fetched_class)->getMatchExpr()); + EXPECT_EQ(class3->toElement()->str(), (*fetched_class)->toElement()->str()); + + // Move the third class between the first and second class. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "foo")); + + // Ensure that the classes order has changed. + client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1")); + classes_list = client_classes.getClasses(); + ASSERT_EQ(3, classes_list->size()); + EXPECT_EQ("foo", (*classes_list->begin())->getName()); + EXPECT_FALSE((*classes_list->begin())->getMatchExpr()); + EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName()); + EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr()); + EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName()); + EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr()); + + // Update the foobar class without specifying its position. It should not + // be moved. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "")); + + client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1")); + classes_list = client_classes.getClasses(); + ASSERT_EQ(3, classes_list->size()); + EXPECT_EQ("foo", (*classes_list->begin())->getName()); + EXPECT_FALSE((*classes_list->begin())->getMatchExpr()); + EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName()); + EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr()); + EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName()); + EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr()); +} + +void +GenericConfigBackendDHCPv6Test::getClientClass6Test() { + // Create a server. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + + // Add classes. + auto class1 = test_client_classes_[0]; + ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_)); + ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_, + test_options_[1]->persistent_, + test_options_[1]->space_name_)); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + + auto class2 = test_client_classes_[1]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, "")); + + // Get the first client class and validate its contents. + ClientClassDefPtr client_class; + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName())); + ASSERT_TRUE(client_class); + EXPECT_EQ("foo", client_class->getName()); + EXPECT_TRUE(client_class->getRequired()); + EXPECT_EQ(30, client_class->getValid().getMin()); + EXPECT_EQ(60, client_class->getValid().get()); + EXPECT_EQ(90, client_class->getValid().getMax()); + + // Validate options belonging to this class. + ASSERT_TRUE(client_class->getCfgOption()); + OptionDescriptor returned_opt_new_posix_timezone = + client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_new_posix_timezone.option_); + + OptionDescriptor returned_opt_preference = + client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_PREFERENCE); + ASSERT_TRUE(returned_opt_preference.option_); + + // Fetch the same class using different server selectors. + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ANY(), + class1->getName())); + EXPECT_TRUE(client_class); + + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"), + class1->getName())); + EXPECT_TRUE(client_class); + + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::UNASSIGNED(), + class1->getName())); + EXPECT_FALSE(client_class); + + // Fetch the second client class using different selectors. This time the + // class should not be returned for the ALL server selector because it is + // associated with the server1. + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), + class2->getName())); + EXPECT_FALSE(client_class); + + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ANY(), + class2->getName())); + EXPECT_TRUE(client_class); + + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"), + class2->getName())); + EXPECT_TRUE(client_class); + + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::UNASSIGNED(), + class2->getName())); + EXPECT_FALSE(client_class); +} + +void +GenericConfigBackendDHCPv6Test::createUpdateClientClass6OptionsTest() { + // Add class with two options and two option definitions. + auto class1 = test_client_classes_[0]; + ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_)); + ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_, + test_options_[1]->persistent_, + test_options_[1]->space_name_)); + auto cfg_option_def = boost::make_shared<CfgOptionDef>(); + class1->setCfgOptionDef(cfg_option_def); + ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[0])); + ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2])); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + + // Fetch the class and the options from the database. + ClientClassDefPtr client_class; + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName())); + ASSERT_TRUE(client_class); + + // Validate options belonging to the class. + ASSERT_TRUE(client_class->getCfgOption()); + OptionDescriptor returned_opt_new_posix_timezone = + client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_new_posix_timezone.option_); + + OptionDescriptor returned_opt_preference = + client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_PREFERENCE); + ASSERT_TRUE(returned_opt_preference.option_); + + // Validate option definitions belonging to the class. + ASSERT_TRUE(client_class->getCfgOptionDef()); + auto returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(), + test_option_defs_[0]->getCode()); + ASSERT_TRUE(returned_def_foo); + EXPECT_EQ(1234, returned_def_foo->getCode()); + EXPECT_EQ("foo", returned_def_foo->getName()); + EXPECT_EQ(DHCP6_OPTION_SPACE, returned_def_foo->getOptionSpaceName()); + EXPECT_EQ("espace", returned_def_foo->getEncapsulatedSpace()); + EXPECT_EQ(OPT_STRING_TYPE, returned_def_foo->getType()); + EXPECT_FALSE(returned_def_foo->getArrayType()); + + auto returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(), + test_option_defs_[2]->getCode()); + ASSERT_TRUE(returned_def_fish); + EXPECT_EQ(5235, returned_def_fish->getCode()); + EXPECT_EQ("fish", returned_def_fish->getName()); + EXPECT_EQ(DHCP6_OPTION_SPACE, returned_def_fish->getOptionSpaceName()); + EXPECT_TRUE(returned_def_fish->getEncapsulatedSpace().empty()); + EXPECT_EQ(OPT_RECORD_TYPE, returned_def_fish->getType()); + EXPECT_TRUE(returned_def_fish->getArrayType()); + + // Replace client class specific option definitions. Leave only one option + // definition. + cfg_option_def = boost::make_shared<CfgOptionDef>(); + class1->setCfgOptionDef(cfg_option_def); + ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2])); + + // Delete one of the options and update the class. + class1->getCfgOption()->del(test_options_[0]->space_name_, + test_options_[0]->option_->getType()); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName())); + ASSERT_TRUE(client_class); + + // Ensure that the first option definition is gone. + ASSERT_TRUE(client_class->getCfgOptionDef()); + returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(), + test_option_defs_[0]->getCode()); + EXPECT_FALSE(returned_def_foo); + + // The second option definition should be present. + returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(), + test_option_defs_[2]->getCode()); + EXPECT_TRUE(returned_def_fish); + + // Make sure that the first option is gone. + ASSERT_TRUE(client_class->getCfgOption()); + returned_opt_new_posix_timezone = client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_NEW_POSIX_TIMEZONE); + EXPECT_FALSE(returned_opt_new_posix_timezone.option_); + + // The second option should be there. + returned_opt_preference = client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_PREFERENCE); + ASSERT_TRUE(returned_opt_preference.option_); +} + +void +GenericConfigBackendDHCPv6Test::getModifiedClientClasses6Test() { + // Create server1. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + + // Add three classes to the database with different timestamps. + auto class1 = test_client_classes_[0]; + class1->setModificationTime(timestamps_["yesterday"]); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + + auto class2 = test_client_classes_[1]; + class2->setModificationTime(timestamps_["today"]); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, "")); + + auto class3 = test_client_classes_[2]; + class3->setModificationTime(timestamps_["tomorrow"]); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "")); + + // Get modified client classes configured for all servers. + auto client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ALL(), + timestamps_["two days ago"]); + EXPECT_EQ(2, client_classes.getClasses()->size()); + + // Get modified client classes appropriate for server1. It includes classes + // for all servers and for the server1. + client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"), + timestamps_["two days ago"]); + EXPECT_EQ(3, client_classes.getClasses()->size()); + + // Get the classes again but use the timestamp equal to the modification + // time of the first class. + client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"), + timestamps_["yesterday"]); + EXPECT_EQ(3, client_classes.getClasses()->size()); + + // Get modified classes starting from today. It should return only two. + client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"), + timestamps_["today"]); + EXPECT_EQ(2, client_classes.getClasses()->size()); + + // Get client classes modified in the future. It should return none. + client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"), + timestamps_["after tomorrow"]); + EXPECT_EQ(0, client_classes.getClasses()->size()); + + // Getting modified client classes for any server is unsupported. + ASSERT_THROW(cbptr_->getModifiedClientClasses6(ServerSelector::ANY(), + timestamps_["two days ago"]), + InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::deleteClientClass6Test() { + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server2")); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("server1 is created and available for server1"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("server1 is created and available for server2"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server2")); + } + + auto class1 = test_client_classes_[0]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + { + SCOPED_TRACE("client class foo is created and available for server1"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("client class foo is created and available for server 2"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server2")); + } + + auto class2 = test_client_classes_[1]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, "")); + { + SCOPED_TRACE("client class bar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + + auto class3 = test_client_classes_[2]; + class3->setTest("member('foo')"); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server2"), class3, "")); + { + SCOPED_TRACE("client class foobar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server2")); + } + + uint64_t result; + ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ONE("server1"), + class2->getName())); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client class bar is deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "client class deleted", + ServerSelector::ONE("server1")); + } + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ONE("server2"), + class3->getName())); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client class foobar is deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "client class deleted", + ServerSelector::ONE("server2")); + } + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ANY(), + class1->getName())); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client class foo is deleted and no longer available for the server1"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "client class deleted", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("client class foo is deleted and no longer available for the server2"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "client class deleted", + ServerSelector::ONE("server2")); + } +} + +void +GenericConfigBackendDHCPv6Test::deleteAllClientClasses6Test() { + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server2")); + } + + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("server1 is created and available for server1"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("server1 is created and available for server2"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server2")); + } + + auto class1 = test_client_classes_[0]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + { + SCOPED_TRACE("client class foo is created and available for server1"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("client class foo is created and available for server 2"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server2")); + } + + auto class2 = test_client_classes_[1]; + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, "")); + { + SCOPED_TRACE("client class bar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + + auto class3 = test_client_classes_[2]; + class3->setTest("member('foo')"); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server2"), class3, "")); + { + SCOPED_TRACE("client class foobar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server2")); + } + + uint64_t result; + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::UNASSIGNED())); + EXPECT_EQ(0, result); + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2"))); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client classes for server2 deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "deleted all client classes", + ServerSelector::ONE("server2")); + } + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2"))); + EXPECT_EQ(0, result); + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1"))); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client classes for server1 deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "deleted all client classes", + ServerSelector::ONE("server1")); + } + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1"))); + EXPECT_EQ(0, result); + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL())); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client classes for all deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "deleted all client classes", + ServerSelector::ONE("server1")); + } + + ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL())); + EXPECT_EQ(0, result); + + // Deleting multiple objects using ANY server tag is unsupported. + ASSERT_THROW(cbptr_->deleteAllClientClasses6(ServerSelector::ANY()), InvalidOperation); +} + +void +GenericConfigBackendDHCPv6Test::clientClassDependencies6Test() { + // Create a server. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0])); + + // Create first class. It depends on KNOWN built-in class. + auto class1 = test_client_classes_[0]; + class1->setTest("member('KNOWN')"); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + + // Create second class which depends on the first class. This yelds indirect + // dependency on KNOWN class. + auto class2 = test_client_classes_[1]; + class2->setTest("member('foo')"); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, "")); + + // Create third class depending on the second class. This also yelds indirect + // dependency on KNOWN class. + auto class3 = test_client_classes_[2]; + class3->setTest("member('bar')"); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class3, "")); + + // An attempt to move the first class to the end of the class hierarchy should + // fail because other classes depend on it. + ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "bar"), + DbOperationError); + + // Try to change the dependency of the first class. There are other classes + // having indirect dependency on KNOWN class via this class. Therefore, the + // update should be unsuccessful. + class1->setTest("member('HA_server1')"); + ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""), + DbOperationError); + + // Try to change the dependency of the second class. This should result in + // an error because the third class depends on it. + class2->setTest("member('HA_server1')"); + ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, ""), + DbOperationError); + + // Changing the indirect dependency of the third class should succeed, because + // no other classes depend on this class. + class3->setTest("member('HA_server1')"); + ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class3, "")); +} + +void +GenericConfigBackendDHCPv6Test::multipleAuditEntriesTest() { + // Get current time. + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + + // Create a server. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1])); + + // Create a global parameter and update it many times. + const ServerSelector& server_selector = ServerSelector::ALL(); + StampedValuePtr param; + ElementPtr value; + for (int i = 0; i < 100; ++i) { + value = Element::create(i); + param = StampedValue::create("my-parameter", value); + cbptr_->createUpdateGlobalParameter6(server_selector, param); + } + + // Get all audit entries from now. + AuditEntryCollection audit_entries = + cbptr_->getRecentAuditEntries(server_selector, now, 0); + + // Check that partial retrieves return the right count. + auto& mod_time_idx = audit_entries.get<AuditEntryModificationTimeIdTag>(); + for (auto it = mod_time_idx.begin(); it != mod_time_idx.end(); ++it) { + size_t partial_size = + cbptr_->getRecentAuditEntries(server_selector, + (*it)->getModificationTime(), + (*it)->getRevisionId()).size(); + EXPECT_EQ(partial_size + 1, + std::distance(it, mod_time_idx.end())); + } +} |