From: Thomas Markwalder Date: Thu, 24 Feb 2022 13:27:26 +0000 (-0500) Subject: [#2330] Refactor CB V6 unit tests X-Git-Tag: Kea-2.1.4~67 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=133d99d3e91949574b0bacec0102bfba66bdb853;p=thirdparty%2Fkea.git [#2330] Refactor CB V6 unit tests src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h new files src/lib/dhcpsrv/testutils/Makefile.am Added generic_cb_dhcp6_unittest.h generic_cb_dhcp6_unittest.cc src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc Moved common setup and UT test code to generic classes --- diff --git a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc index f14afc6bf7..e7b5afa57f 100644 --- a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc +++ b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc @@ -1,27 +1,19 @@ -// Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2019-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 -#include -#include -#include #include #include -#include -#include -#include -#include -#include -#include #include #include -#include -#include +#include +#include #include #include +#include #include #include #include @@ -65,58 +57,43 @@ public: }; /// @brief Test fixture class for @c MySqlConfigBackendDHCPv6. -/// -/// @todo The tests we're providing here only test cases when the -/// server selector is set to 'ALL' (configuration elements belong to -/// all servers). Currently we have no API to insert servers into -/// the database, and therefore we can't test the case when -/// configuration elements are assigned to particular servers by -/// server tags. We will have to expand existing tests when -/// the API is extended allowing for inserting servers to the -/// database. -class MySqlConfigBackendDHCPv6Test : public MySqlGenericBackendTest { +class MySqlConfigBackendDHCPv6Test : public GenericConfigBackendDHCPv6Test { public: /// @brief Constructor. - MySqlConfigBackendDHCPv6Test() - : test_subnets_(), test_networks_(), test_option_defs_(), - test_options_(), test_client_classes_(), test_servers_(), timestamps_(), - cbptr_(), audit_entries_() { - // Ensure we have the proper schema with no transient data. - createMySQLSchema(); + MySqlConfigBackendDHCPv6Test() {} - try { - // Create MySQL connection and use it to start the backend. - DatabaseConnection::ParameterMap params = - DatabaseConnection::parse(validMySQLConnectionString()); - cbptr_.reset(new TestMySqlConfigBackendDHCPv6(params)); - - } catch (...) { - std::cerr << "*** ERROR: unable to open database. The test\n" - "*** environment is broken and must be fixed before\n" - "*** the MySQL tests will run correctly.\n" - "*** The reason for the problem is described in the\n" - "*** accompanying exception output.\n"; - throw; - } + /// @brief Destructor. + virtual ~MySqlConfigBackendDHCPv6Test() {} - // Create test data. - initTestServers(); - initTestOptions(); - initTestSubnets(); - initTestSharedNetworks(); - initTestOptionDefs(); - initTestClientClasses(); - initTimestamps(); + /// @brief Creates the MySQL back end schema + virtual void createSchema() { + createMySQLSchema(); } - /// @brief Destructor. - virtual ~MySqlConfigBackendDHCPv6Test() { - cbptr_.reset(); - // If data wipe enabled, delete transient data otherwise destroy the schema. + /// @brief Destroys the MySQL back end schema + virtual void destroySchema() { destroyMySQLSchema(); } + /// @brief Returns a valid MySQL back end specific connection + /// string + std::string validConnectionString() { + return (validMySQLConnectionString()); + } + + /// @brief Instantiates an instance of a MySQL DHCPv4 configuration + /// back end. + /// + /// @params Connection parameters describing the back end to create. + /// + /// @return Pointer to the newly created back end instance. + ConfigBackendDHCPv6Ptr backendFactory(db::DatabaseConnection::ParameterMap& + params) { + + return (ConfigBackendDHCPv6Ptr(new TestMySqlConfigBackendDHCPv6(params))); + } + /// @brief Counts rows in a selected table in MySQL database. /// /// This method can be used to verify that some configuration elements were @@ -140,5326 +117,380 @@ public: return (MySqlGenericBackendTest::countRows(conn, table)); } - - /// @brief Creates several servers used in tests. - void 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")); - } - - /// @brief Creates several subnets used in tests. - void 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 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(), Triplet(), - Triplet(), Triplet(), - 4096)); - test_subnets_.push_back(subnet); - } - - /// @brief Creates several subnets used in tests. - void 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 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); - } - - /// @brief Creates several option definitions used in tests. - void 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); - } - - /// @brief Creates several DHCP options used in tests. - void initTestOptions() { - ElementPtr user_context = Element::createMap(); - user_context->set("foo", Element::create("bar")); - - OptionDefSpaceContainer defs; - - OptionDescriptor desc = - createOption(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(Option::V6, D6O_PREFERENCE, - false, true, 64); - desc.space_name_ = DHCP6_OPTION_SPACE; - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createOption(Option::V6, 1, false, false, 312131), - desc.space_name_ = "vendor-encapsulated-options"; - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createAddressOption(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(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(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(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); - } - - /// @brief Creates several client classes used in tests. - void initTestClientClasses() { - ExpressionPtr match_expr = boost::make_shared(); - CfgOptionPtr cfg_option = boost::make_shared(); - auto class1 = boost::make_shared("foo", match_expr, cfg_option); - class1->setRequired(true); - class1->setValid(Triplet(30, 60, 90)); - test_client_classes_.push_back(class1); - - auto class2 = boost::make_shared("bar", match_expr, cfg_option); - class2->setTest("member('foo')"); - test_client_classes_.push_back(class2); - - auto class3 = boost::make_shared("foobar", match_expr, cfg_option); - class3->setTest("member('foo') and member('bar')"); - test_client_classes_.push_back(class3); - } - - /// @brief Initialize posix time values used in tests. - void initTimestamps() { - // Current time minus 1 hour to make sure it is in the past. - timestamps_["today"] = boost::posix_time::second_clock::local_time() - - boost::posix_time::hours(1); - // One second after today. - timestamps_["after today"] = timestamps_["today"] + boost::posix_time::seconds(1); - // Yesterday. - timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24); - // One second after yesterday. - timestamps_["after yesterday"] = timestamps_["yesterday"] + boost::posix_time::seconds(1); - // Two days ago. - timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48); - // Tomorrow. - timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24); - // One second after tomorrow. - timestamps_["after tomorrow"] = timestamps_["tomorrow"] + boost::posix_time::seconds(1); - } - - /// @brief Logs audit entries in the @c audit_entries_ member. - /// - /// This function is called in case of an error. - /// - /// @param server_tag Server tag for which the audit entries should be logged. - std::string logExistingAuditEntries(const std::string& server_tag) { - std::ostringstream s; - - auto& mod_time_idx = audit_entries_[server_tag].get(); - - for (auto audit_entry_it = mod_time_idx.begin(); - audit_entry_it != mod_time_idx.end(); - ++audit_entry_it) { - auto audit_entry = *audit_entry_it; - s << audit_entry->getObjectType() << ", " - << audit_entry->getObjectId() << ", " - << static_cast(audit_entry->getModificationType()) << ", " - << audit_entry->getModificationTime() << ", " - << audit_entry->getRevisionId() << ", " - << audit_entry->getLogMessage() - << std::endl; - } - - return (s.str()); - } - - /// @brief Tests that the new audit entry is added. - /// - /// This method retrieves a collection of the existing audit entries and - /// checks that the new one has been added at the end of this collection. - /// It then verifies the values of the audit entry against the values - /// specified by the caller. - /// - /// @param exp_object_type Expected object type. - /// @param exp_modification_type Expected modification type. - /// @param exp_log_message Expected log message. - /// @param server_selector Server selector to be used for next query. - /// @param new_entries_num Number of the new entries expected to be inserted. - /// @param max_tested_entries Maximum number of entries tested. - void testNewAuditEntry(const std::string& exp_object_type, - const AuditEntry::ModificationType& exp_modification_type, - const std::string& exp_log_message, - const ServerSelector& server_selector = ServerSelector::ALL(), - const size_t new_entries_num = 1, - const size_t max_tested_entries = 65535) { - // Get the server tag for which the entries are fetched. - std::string tag; - if (server_selector.getType() == ServerSelector::Type::ALL) { - // Server tag is 'all'. - tag = "all"; - - } else { - auto tags = server_selector.getTags(); - // This test is not meant to handle multiple server tags all at once. - if (tags.size() > 1) { - ADD_FAILURE() << "Test error: do not use multiple server tags"; - - } else if (tags.size() == 1) { - // Get the server tag for which we run the current test. - tag = tags.begin()->get(); - } - } - - auto audit_entries_size_save = audit_entries_[tag].size(); - - // Audit entries for different server tags are stored in separate - // containers. - audit_entries_[tag] = cbptr_->getRecentAuditEntries(server_selector, - timestamps_["two days ago"], 0); - ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size()) - << logExistingAuditEntries(tag); - - auto& mod_time_idx = audit_entries_[tag].get(); - - // Iterate over specified number of entries starting from the most recent - // one and check they have correct values. - for (auto audit_entry_it = mod_time_idx.rbegin(); - ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num) && - (std::distance(mod_time_idx.rbegin(), audit_entry_it) < max_tested_entries)); - ++audit_entry_it) { - auto audit_entry = *audit_entry_it; - EXPECT_EQ(exp_object_type, audit_entry->getObjectType()) - << logExistingAuditEntries(tag); - EXPECT_EQ(exp_modification_type, audit_entry->getModificationType()) - << logExistingAuditEntries(tag); - EXPECT_EQ(exp_log_message, audit_entry->getLogMessage()) - << logExistingAuditEntries(tag); - } - } - - /// @brief Holds pointers to subnets used in tests. - std::vector test_subnets_; - - /// @brief Holds pointers to shared networks used in tests. - std::vector test_networks_; - - /// @brief Holds pointers to option definitions used in tests. - std::vector test_option_defs_; - - /// @brief Holds pointers to options used in tests. - std::vector test_options_; - - /// @brief Holds pointers to classes used in tests. - std::vector test_client_classes_; - - /// @brief Holds pointers to the servers used in tests. - std::vector test_servers_; - - /// @brief Holds timestamp values used in tests. - std::map timestamps_; - - /// @brief Holds pointer to the backend. - boost::shared_ptr cbptr_; - - /// @brief Holds the most recent audit entries. - std::map audit_entries_; }; -// This test verifies that the expected backend type is returned. -TEST_F(MySqlConfigBackendDHCPv6Test, getType) { - DatabaseConnection::ParameterMap params; - params["name"] = "keatest"; - params["password"] = "keatest"; - params["user"] = "keatest"; - ASSERT_NO_THROW(cbptr_.reset(new MySqlConfigBackendDHCPv6(params))); - ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); - EXPECT_EQ("mysql", cbptr_->getType()); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteGlobalParameter6Test) { + createUpdateDeleteGlobalParameter6Test(); } -// This test verifies that by default localhost is returned as MySQL connection -// host. -TEST_F(MySqlConfigBackendDHCPv6Test, getHost) { - DatabaseConnection::ParameterMap params; - params["name"] = "keatest"; - params["password"] = "keatest"; - params["user"] = "keatest"; - ASSERT_NO_THROW(cbptr_.reset(new MySqlConfigBackendDHCPv6(params))); - ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); - EXPECT_EQ("localhost", cbptr_->getHost()); +TEST_F(MySqlConfigBackendDHCPv6Test, globalParameters6WithServerTagsTest) { + globalParameters6WithServerTagsTest(); } -// This test verifies that by default port of 0 is returned as MySQL connection -// port. -TEST_F(MySqlConfigBackendDHCPv6Test, getPort) { - DatabaseConnection::ParameterMap params; - params["name"] = "keatest"; - params["password"] = "keatest"; - params["user"] = "keatest"; - ASSERT_NO_THROW(cbptr_.reset(new MySqlConfigBackendDHCPv6(params))); - ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); - EXPECT_EQ(0, cbptr_->getPort()); +TEST_F(MySqlConfigBackendDHCPv6Test, getAllGlobalParameters6Test) { + getAllGlobalParameters6Test(); } -// This test verifies that the server can be added, updated and deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteServer) { - // 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. - EXPECT_NO_THROW(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"); - EXPECT_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. - EXPECT_NO_THROW(returned_server = cbptr_->getServer6(ServerTag("server2"))); - EXPECT_FALSE(returned_server); - - // Try to fetch the server which we expect to exist. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(servers_deleted = cbptr_->deleteServer6(ServerTag("server2"))); - EXPECT_EQ(0, servers_deleted); - - // Make sure that the server1 wasn't deleted. - EXPECT_NO_THROW(returned_server = cbptr_->getServer6(ServerTag("server1"))); - EXPECT_TRUE(returned_server); - - // Deleting logical server 'all' is not allowed. - EXPECT_THROW(cbptr_->deleteServer6(ServerTag()), isc::InvalidOperation); - - // Delete the existing server. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(returned_server = cbptr_->getServer6(ServerTag("server1"))); - EXPECT_FALSE(returned_server); +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedGlobalParameters6Test) { + getModifiedGlobalParameters6Test(); } -// This test verifies that it is possible to retrieve all servers from the -// database and then delete all of them. -TEST_F(MySqlConfigBackendDHCPv6Test, getAndDeleteAllServers) { - for (auto i = 1; i < test_servers_.size(); ++i) { - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[i])); - } - - ServerCollection servers; - EXPECT_NO_THROW(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; - EXPECT_NO_THROW(deleted_servers = cbptr_->deleteAllServers6()); - - EXPECT_NO_THROW(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); +TEST_F(MySqlConfigBackendDHCPv6Test, nullKeyErrorTest) { + nullKeyErrorTest(); } -// This test verifies that the global parameter can be added, updated and -// deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteGlobalParameter6) { - StampedValuePtr global_parameter = StampedValue::create("global", "whale"); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateSubnet6SelectorsTest) { + createUpdateSubnet6SelectorsTest(); +} - // 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); +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6Test) { + getSubnet6Test(); +} - { - SCOPED_TRACE("CREATE audit entry for global parameter"); - testNewAuditEntry("dhcp6_global_parameter", - AuditEntry::ModificationType::CREATE, - "global parameter set"); - } +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6byIdSelectorsTest) { + getSubnet6byIdSelectorsTest(); +} - // 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6WithOptionalUnspecifiedTest) { + getSubnet6WithOptionalUnspecifiedTest(); +} - // 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6SharedNetworkTest) { + getSubnet6SharedNetworkTest(); +} - // 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6ByPrefixTest) { + getSubnet6ByPrefixTest(); +} - { - SCOPED_TRACE("UPDATE audit entry for the global parameter"); - testNewAuditEntry("dhcp6_global_parameter", - AuditEntry::ModificationType::UPDATE, - "global parameter set"); - } +TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6byPrefixSelectorsTest) { + getSubnet6byPrefixSelectorsTest(); +} - // Should not delete parameter specified for all servers if explicit - // server name is provided. - EXPECT_EQ(0, cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server1"), - "global")); +TEST_F(MySqlConfigBackendDHCPv6Test, getAllSubnets6Test) { + getAllSubnets6Test(); +} - // 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); +TEST_F(MySqlConfigBackendDHCPv6Test, getAllSubnets6SelectorsTest) { + getAllSubnets6SelectorsTest(); +} - { - SCOPED_TRACE("DELETE audit entry for the global parameter"); - testNewAuditEntry("dhcp6_global_parameter", - AuditEntry::ModificationType::DELETE, - "global parameter deleted"); - } +TEST_F(MySqlConfigBackendDHCPv6Test, getAllSubnets6WithServerTagsTest) { + getAllSubnets6WithServerTagsTest(); } -// This test verifies that it is possible to differentiate between the -// global parameters by server tag and that the value specified for the -// particular server overrides the value specified for all servers. -TEST_F(MySqlConfigBackendDHCPv6Test, globalParameters6WithServerTags) { - // 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"); +TEST_F(MySqlConfigBackendDHCPv6Test, deleteSubnet6Test) { + deleteSubnet6Test(); +} - // Try to insert one of them and associate with non-existing server. - // This should fail because the server must be inserted first. - EXPECT_THROW(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"), - global_parameter1), - NullKeyError); +TEST_F(MySqlConfigBackendDHCPv6Test, deleteSubnet6ByIdSelectorsTest) { + deleteSubnet6ByIdSelectorsTest(); +} - // Create two servers. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[1])); - { - SCOPED_TRACE("server1 is created"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } +TEST_F(MySqlConfigBackendDHCPv6Test, deleteSubnet6ByPrefixSelectorsTest) { + deleteSubnet6ByPrefixSelectorsTest(); +} - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); - { - SCOPED_TRACE("server2 is created"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } +TEST_F(MySqlConfigBackendDHCPv6Test, deleteAllSubnets6SelectorsTest) { + deleteAllSubnets6SelectorsTest(); +} - // This time inserting the global parameters for the server1 and server2 should - // be successful. - EXPECT_NO_THROW(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); - } +TEST_F(MySqlConfigBackendDHCPv6Test, unassignedSubnet6Test) { + unassignedSubnet6Test(); +} +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSubnets6Test) { + getModifiedSubnets6Test(); +} - EXPECT_NO_THROW(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); - } +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSubnets6SelectorsTest) { + getModifiedSubnets6SelectorsTest(); +} - // The last parameter is associated with all servers. - EXPECT_NO_THROW(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); - } +TEST_F(MySqlConfigBackendDHCPv6Test, subnetLifetimeTest) { + subnetLifetimeTest(); +} - StampedValuePtr returned_global; +TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetworkSubnets6Test) { + getSharedNetworkSubnets6Test(); +} - // Try to fetch the value specified for all servers. - EXPECT_NO_THROW( - 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, subnetUpdatePoolsTest) { + subnetUpdatePoolsTest(); +} - // Try to fetch the value specified for the server1. This should override the - // value specified for all servers. - EXPECT_NO_THROW( - returned_global = cbptr_->getGlobalParameter6(ServerSelector::ONE("server1"), - "global") - ); - ASSERT_TRUE(returned_global); - EXPECT_EQ(global_parameter1->getValue(), returned_global->getValue()); +TEST_F(MySqlConfigBackendDHCPv6Test, subnetOptionsTest) { + subnetOptionsTest(); +} - ASSERT_EQ(1, returned_global->getServerTags().size()); - EXPECT_EQ("server1", returned_global->getServerTags().begin()->get()); +TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetwork6Test) { + getSharedNetwork6Test(); +} - // The same in case of the server2. - EXPECT_NO_THROW( - 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetwork6SelectorsTest) { + getSharedNetwork6SelectorsTest(); +} - StampedValueCollection returned_globals; +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateSharedNetwork6Test) { + createUpdateSharedNetwork6Test(); +} - // 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'. - EXPECT_NO_THROW( - returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector:: - MULTIPLE({ "server1", "server2", - "server3" })); - ); - ASSERT_EQ(3, returned_globals.size()); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateSharedNetwork6SelectorsTest) { + createUpdateSharedNetwork6SelectorsTest(); +} - // Capture the returned values into the map so as we can check the - // values against the servers. - std::map 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()); - } +TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetwork6WithOptionalUnspecifiedTest) { + getSharedNetwork6WithOptionalUnspecifiedTest(); +} - 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"]); +TEST_F(MySqlConfigBackendDHCPv6Test, deleteSharedNetworkSubnets6Test) { + deleteSharedNetworkSubnets6Test(); +} - // 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. - EXPECT_NO_THROW( - 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, getAllSharedNetworks6Test) { + getAllSharedNetworks6Test(); +} - // Delete the server1. It should remove associations of this server with the - // global parameter and the global parameter itself. - EXPECT_NO_THROW(cbptr_->deleteServer6(ServerTag("server1"))); - EXPECT_NO_THROW( - 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, getAllSharedNetworks6SelectorsTest) { + getAllSharedNetworks6SelectorsTest(); +} - { - 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); - } +TEST_F(MySqlConfigBackendDHCPv6Test, getAllSharedNetworks6WithServerTagsTest) { + getAllSharedNetworks6WithServerTagsTest(); +} - // Attempt to delete global parameter for server1. - uint64_t deleted_num = 0; - EXPECT_NO_THROW(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); +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSharedNetworks6Test) { + getModifiedSharedNetworks6Test(); +} - // Deleting the existing value for server2 should succeed. - EXPECT_NO_THROW(deleted_num = cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server2"), - "global")); - EXPECT_EQ(1, deleted_num); +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSharedNetworks6SelectorsTest) { + getModifiedSharedNetworks6SelectorsTest(); +} - // Create it again to test that deletion of all server removes this too. - EXPECT_NO_THROW(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server2"), - global_parameter2)); +TEST_F(MySqlConfigBackendDHCPv6Test, deleteSharedNetwork6Test) { + deleteSharedNetwork6Test(); +} - // Delete all servers, except 'all'. - EXPECT_NO_THROW(deleted_num = cbptr_->deleteAllServers6()); - EXPECT_NO_THROW( - 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, deleteSharedNetwork6SelectorsTest) { + deleteSharedNetwork6SelectorsTest(); +} - { - 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); - } +TEST_F(MySqlConfigBackendDHCPv6Test, deleteAllSharedNetworks6SelectorsTest) { + deleteAllSharedNetworks6SelectorsTest(); } -// This test verifies that all global parameters can be retrieved and deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllGlobalParameters6) { - // 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(65)))); - cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), - StampedValue::create("name3", "value3")); - cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), - StampedValue::create("name4", Element::create(static_cast(true)))); - cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), - StampedValue::create("name5", Element::create(static_cast(1.65)))); +TEST_F(MySqlConfigBackendDHCPv6Test, unassignedSharedNetworkTest) { + unassignedSharedNetworkTest(); +} - // Fetch all parameters. - auto parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL()); - ASSERT_EQ(5, parameters.size()); +TEST_F(MySqlConfigBackendDHCPv6Test, sharedNetworkLifetimeTest) { + sharedNetworkLifetimeTest(); +} - const auto& parameters_index = parameters.get(); +TEST_F(MySqlConfigBackendDHCPv6Test, sharedNetworkOptionsTest) { + sharedNetworkOptionsTest(); +} - for (auto param = parameters_index.begin(); param != parameters_index.end(); - ++param) { - ASSERT_EQ(1, (*param)->getServerTags().size()); - EXPECT_EQ("all", (*param)->getServerTags().begin()->get()); - } +TEST_F(MySqlConfigBackendDHCPv6Test, getOptionDef6Test) { + getOptionDef6Test(); +} - // 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, optionDefs6WithServerTagsTest) { + optionDefs6WithServerTagsTest(); +} - // Should be able to fetch these parameters when explicitly providing - // the server tag. - parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ONE("server1")); - EXPECT_EQ(5, parameters.size()); +TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptionDefs6Test) { + getAllOptionDefs6Test(); +} - // Deleting global parameters with non-matching server selector - // should fail. - EXPECT_EQ(0, cbptr_->deleteAllGlobalParameters6(ServerSelector::ONE("server1"))); +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedOptionDefs6Test) { + getModifiedOptionDefs6Test(); +} - // 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteOption6Test) { + createUpdateDeleteOption6Test(); } -// This test verifies that modified global parameters can be retrieved. -TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedGlobalParameters6) { - // 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); +TEST_F(MySqlConfigBackendDHCPv6Test, globalOptions6WithServerTagsTest) { + globalOptions6WithServerTagsTest(); +} - value = StampedValue::create("name2", Element::create(static_cast(65))); - value->setModificationTime(timestamps_["today"]); - cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), - value); +TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptions6Test) { + getAllOptions6Test(); +} - value = StampedValue::create("name3", "value3"); - value->setModificationTime(timestamps_["tomorrow"]); - cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), - value); +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedOptions6Test) { + getModifiedOptions6Test(); +} - // Get parameters modified after "today". - auto parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ALL(), - timestamps_["after today"]); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteSubnetOption6Test) { + createUpdateDeleteSubnetOption6Test(); +} - const auto& parameters_index = parameters.get(); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeletePoolOption6Test) { + createUpdateDeletePoolOption6Test(); +} - // It should be the one modified "tomorrow". - ASSERT_EQ(1, parameters_index.size()); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeletePdPoolOption6Test) { + createUpdateDeletePdPoolOption6Test(); +} - auto parameter = parameters_index.find("name3"); - ASSERT_FALSE(parameter == parameters_index.end()); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteSharedNetworkOption6Test) { + createUpdateDeleteSharedNetworkOption6Test(); +} - ASSERT_TRUE(*parameter); - EXPECT_EQ("value3", (*parameter)->getValue()); +TEST_F(MySqlConfigBackendDHCPv6Test, subnetOptionIdOrderTest) { + subnetOptionIdOrderTest(); +} - // 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()); +TEST_F(MySqlConfigBackendDHCPv6Test, sharedNetworkOptionIdOrderTest) { + sharedNetworkOptionIdOrderTest(); } -// Test that the NullKeyError message is correctly updated. -TEST_F(MySqlConfigBackendDHCPv6Test, nullKeyError) { - // Create a global parameter (it should work with any object type). - StampedValuePtr global_parameter = StampedValue::create("global", "value"); +TEST_F(MySqlConfigBackendDHCPv6Test, setAndGetAllClientClasses6Test) { + setAndGetAllClientClasses6Test(); +} - // 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); +TEST_F(MySqlConfigBackendDHCPv6Test, getClientClass6Test) { + getClientClass6Test(); } -// Test that ceateUpdateSubnet6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateSubnet6Selectors) { - ASSERT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - ASSERT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); +TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateClientClass6OptionsTest) { + createUpdateClientClass6OptionsTest(); +} - // Supported selectors. - Subnet6Ptr subnet = test_subnets_[0]; - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), - subnet)); - subnet = test_subnets_[2]; - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), - subnet)); - subnet = test_subnets_[3]; - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), - subnet)); +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedClientClasses6Test) { + getModifiedClientClasses6Test(); +} - // Not supported server selectors. - EXPECT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ANY(), subnet), - isc::InvalidOperation); +TEST_F(MySqlConfigBackendDHCPv6Test, deleteClientClass6Test) { + deleteClientClass6Test(); +} - // Not implemented server selectors. - EXPECT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::UNASSIGNED(), - subnet), - isc::NotImplemented); +TEST_F(MySqlConfigBackendDHCPv6Test, deleteAllClientClasses6Test) { + deleteAllClientClasses6Test(); } -// Test that subnet can be inserted, fetched, updated and then fetched again. -TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6) { - // Insert the server2 into the database. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); - { - SCOPED_TRACE("CREATE audit entry for server"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } +TEST_F(MySqlConfigBackendDHCPv6Test, clientClassDependencies6Test) { + clientClassDependencies6Test(); +} - auto subnet = test_subnets_[0]; - auto subnet2 = test_subnets_[2]; +TEST_F(MySqlConfigBackendDHCPv6Test, multipleAuditEntriesTest) { + multipleAuditEntriesTest(); +} - // An attempt to add a subnet to a non-existing server (server1) should fail. - EXPECT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), - subnet2), - NullKeyError); +/// @brief Test fixture for verifying database connection loss-recovery +/// behavior. +class MySqlConfigBackendDHCPv6DbLostCallbackTest : public GenericConfigBackendDbLostCallbackTest { +public: + /// @brief Constructor + MySqlConfigBackendDHCPv6DbLostCallbackTest() {}; - // The subnet shouldn't have been added, even though one of the servers exists. - Subnet6Ptr returned_subnet; - ASSERT_NO_THROW(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server2"), - subnet2->getID())); - EXPECT_FALSE(returned_subnet); + /// @brief Destructor + virtual ~MySqlConfigBackendDHCPv6DbLostCallbackTest() {}; - // Insert two subnets, one for all servers and one for server2. - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); - { - SCOPED_TRACE("CREATE audit entry for the subnet"); - testNewAuditEntry("dhcp6_subnet", - AuditEntry::ModificationType::CREATE, - "subnet set"); + /// @brief Creates the MySQL CB schema. + virtual void createSchema() { + createMySQLSchema(); } - - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2)); - { - SCOPED_TRACE("CREATE audit entry for the subnet"); - testNewAuditEntry("dhcp6_subnet", - AuditEntry::ModificationType::CREATE, - "subnet set", ServerSelector::ONE("subnet2"), - 2, 1); + /// @brief Destroys the MySQL CB schema. + virtual void destroySchema() { + destroyMySQLSchema(); } - // We are not going to support selection of a single entry for multiple servers. - EXPECT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), - subnet->getID()), - isc::InvalidOperation); - - EXPECT_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(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))); - - EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); - - // Test fetching subnet by prefix. - ASSERT_NO_THROW(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()); + /// @brief Method which returns a valid back end specific connection + /// string + virtual std::string validConnectionString() { + return (validMySQLConnectionString()); } - 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"); + /// @brief Method which returns an invalid back end specific connection + /// string. + virtual std::string invalidConnectionString() { + return (connectionString(MYSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, + VALID_USER, VALID_PASSWORD)); } - // Update the subnet in the database (both use the same ID). - subnet = test_subnets_[1]; - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet)); - { - SCOPED_TRACE("CREATE audit entry for the subnet"); - testNewAuditEntry("dhcp6_subnet", - AuditEntry::ModificationType::UPDATE, - "subnet set"); + /// @brief Registers MySQL as a CB backend type. + virtual void registerBackendType() { + isc::dhcp::MySqlConfigBackendDHCPv6::registerBackendType(); } - { - 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()); + /// @brief Unregisters MySQL as a CB backend type. + virtual void unregisterBackendType() { + isc::dhcp::MySqlConfigBackendDHCPv6::unregisterBackendType(); } - // 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)); - EXPECT_NO_THROW(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)); - EXPECT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2), - DuplicateEntry); -} - -// Test that getSubnet6 by ID throws appropriate exceptions for various server -// selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6byIdSelectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->getSubnet6(ServerSelector::ANY(), SubnetID(1))); - EXPECT_NO_THROW(cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), SubnetID(1))); - EXPECT_NO_THROW(cbptr_->getSubnet6(ServerSelector::ALL(), SubnetID(1))); - EXPECT_NO_THROW(cbptr_->getSubnet6(ServerSelector::ONE("server1"), SubnetID(1))); - - // Not supported selectors. - EXPECT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), - SubnetID(1)), - isc::InvalidOperation); -} - -// Test that the information about unspecified optional parameters gets -// propagated to the database. -TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6WithOptionalUnspecified) { - // 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. - cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network); - cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); - - // Fetch this subnet by subnet identifier. - Subnet6Ptr 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()); + /// @brief Sets the IOService instance in the CB implementation object. + /// + /// @param io_service pointer to the IOService instance to use. It may be + /// an empty pointer. + virtual void setConfigBackendImplIOService(isc::asiolink::IOServicePtr io_service) { + isc::dhcp::MySqlConfigBackendImpl::setIOService(io_service); + } - EXPECT_TRUE(returned_subnet->getRapidCommit().unspecified()); - EXPECT_FALSE(returned_subnet->getRapidCommit().get()); + /// @brief Attempts to add a backend instance to the CB manager. + /// + /// @param access Connection access string containing the database + /// connetion parameters. + virtual void addBackend(const std::string& access) { + ConfigBackendDHCPv6Mgr::instance().addBackend(access); + } - 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()); - -} - -// Test that subnet can be associated with a shared network. -TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6SharedNetwork) { - 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. - cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), - shared_network); - - // Store subnet associated with the shared network in the database. - 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()); -} - -// Test that subnet can be fetched by prefix. -TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6ByPrefix) { - // Insert subnet to the database. - Subnet6Ptr subnet = test_subnets_[0]; - 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()); -} - -// Test that getSubnet6 by prefix throws appropriate exceptions for various server -// selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, getSubnet6byPrefixSelectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->getSubnet6(ServerSelector::ANY(), "192.0.2.0/26")); - EXPECT_NO_THROW(cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), "192.0.2.0/26")); - EXPECT_NO_THROW(cbptr_->getSubnet6(ServerSelector::ALL(), "192.0.2.0/26")); - EXPECT_NO_THROW(cbptr_->getSubnet6(ServerSelector::ONE("server1"), "192.0.2.0/26")); - - // Not supported selectors. - EXPECT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), - "192.0.2.0/26"), - isc::InvalidOperation); -} - -// Test that all subnets can be fetched and then deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllSubnets6) { - // 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) { - EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(), - (*subnet_it)->toElement()->str()); - ASSERT_EQ(1, (*subnet_it)->getServerTags().size()); - EXPECT_EQ("all", (*subnet_it)->getServerTags().begin()->get()); - } - - // 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"); - } -} - -// Test that getAllSubnets6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllSubnets6Selectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->getAllSubnets6(ServerSelector::UNASSIGNED())); - EXPECT_NO_THROW(cbptr_->getAllSubnets6(ServerSelector::ALL())); - EXPECT_NO_THROW(cbptr_->getAllSubnets6(ServerSelector::ONE("server1"))); - EXPECT_NO_THROW(cbptr_->getAllSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }))); - - // Not supported selectors. - EXPECT_THROW(cbptr_->getAllSubnets6(ServerSelector::ANY()), isc::InvalidOperation); -} - -// Test that subnets with different server associations are returned. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllSubnets6WithServerTags) { - auto subnet1 = test_subnets_[0]; - auto subnet2 = test_subnets_[2]; - auto subnet3 = test_subnets_[3]; - - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); - - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), - subnet1)); - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), - subnet2)); - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), - subnet3)); - - Subnet6Collection subnets; - - // All three subnets are associated with the server1. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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"))); -} - -// Test that selected subnet can be deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteSubnet6) { - // Create two servers in the database. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - { - SCOPED_TRACE("CREATE audit entry for server"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } - - EXPECT_NO_THROW(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. - EXPECT_NO_THROW( - cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet1) - ); - EXPECT_NO_THROW( - cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2) - ); - EXPECT_NO_THROW( - 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; - EXPECT_NO_THROW( - deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->getID()) - ); - EXPECT_EQ(0, deleted_count); - - deleted_count = 0; - EXPECT_NO_THROW( - 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; - EXPECT_NO_THROW( - 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; - EXPECT_NO_THROW( - 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); -} - -// Test that deleteSubnet6 by ID throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteSubnet6ByIdSelectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->deleteSubnet6(ServerSelector::ANY(), SubnetID(1))); - EXPECT_NO_THROW(cbptr_->deleteSubnet6(ServerSelector::ALL(), SubnetID(1))); - EXPECT_NO_THROW(cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), SubnetID(1))); - - // Not supported selectors. - EXPECT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), - SubnetID(1)), - isc::InvalidOperation); - - // Not implemented selectors. - EXPECT_THROW(cbptr_->deleteSubnet6(ServerSelector::UNASSIGNED(), SubnetID(1)), - isc::NotImplemented); -} - -// Test that deleteSubnet6 by prefix throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteSubnet6ByPrefixSelectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->deleteSubnet6(ServerSelector::ANY(), "192.0.2.0/26")); - EXPECT_NO_THROW(cbptr_->deleteSubnet6(ServerSelector::ALL(), "192.0.2.0/26")); - EXPECT_NO_THROW(cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), "192.0.2.0/26")); - - // Not supported selectors. - EXPECT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), - "192.0.2.0/26"), - isc::InvalidOperation); - - // Not implemented selectors. - EXPECT_THROW(cbptr_->deleteSubnet6(ServerSelector::UNASSIGNED(), "192.0.2.0/26"), - isc::NotImplemented); -} - -// Test that deleteAllSubnets6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteAllSubnets6Selectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->deleteAllSubnets6(ServerSelector::UNASSIGNED())); - EXPECT_NO_THROW(cbptr_->deleteAllSubnets6(ServerSelector::ALL())); - EXPECT_NO_THROW(cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1"))); - - // Not supported selectors. - EXPECT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::ANY()), - isc::InvalidOperation); - EXPECT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" })), - isc::InvalidOperation); -} - -// Test that it is possible to retrieve and delete orphaned subnet. -TEST_F(MySqlConfigBackendDHCPv6Test, unassignedSubnet6) { - // Create the server. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - - // Create the subnets and associate them with the server1. - auto subnet = test_subnets_[0]; - auto subnet2 = test_subnets_[2]; - EXPECT_NO_THROW( - cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), subnet) - ); - EXPECT_NO_THROW( - 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; - EXPECT_NO_THROW(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; - EXPECT_NO_THROW(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"), - subnet->getID())); - EXPECT_FALSE(returned_subnet); - - // The same if we use other calls. - EXPECT_NO_THROW(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"), - subnet->toText())); - EXPECT_FALSE(returned_subnet); - - Subnet6Collection returned_subnets; - EXPECT_NO_THROW(returned_subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1"))); - EXPECT_TRUE(returned_subnets.empty()); - - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW(returned_subnet = cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), - subnet->getID())); - ASSERT_TRUE(returned_subnet); - - EXPECT_NO_THROW(returned_subnet = cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), - subnet->toText())); - ASSERT_TRUE(returned_subnet); - - // Also if we ask for all unassigned subnets it should be returned. - EXPECT_NO_THROW(returned_subnets = cbptr_->getAllSubnets6(ServerSelector::UNASSIGNED())); - ASSERT_EQ(2, returned_subnets.size()); - - // Same for modified subnets. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW(returned_subnet = cbptr_->getSubnet6(ServerSelector::ANY(), - subnet->getID())); - ASSERT_TRUE(returned_subnet); - - EXPECT_NO_THROW(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. - EXPECT_NO_THROW( - deleted_count = cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), - subnet->getID()) - ); - EXPECT_EQ(0, deleted_count); - - // Also, if we delete all subnets for server1. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - deleted_count = cbptr_->deleteSubnet6(ServerSelector::ANY(), subnet->getID()) - ); - EXPECT_EQ(1, deleted_count); - - // We can delete all subnets using UNASSIGNED selector. - EXPECT_NO_THROW( - deleted_count = cbptr_->deleteAllSubnets6(ServerSelector::UNASSIGNED()); - ); - EXPECT_EQ(1, deleted_count); -} - -// Test that subnets modified after given time can be fetched. -TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSubnets6) { - // 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()); -} - -// Test that getModifiedSubnets6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSubnets6Selectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->getModifiedSubnets6(ServerSelector::UNASSIGNED(), - timestamps_["yesterday"])); - EXPECT_NO_THROW(cbptr_->getModifiedSubnets6(ServerSelector::ALL(), - timestamps_["yesterday"])); - EXPECT_NO_THROW(cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"), - timestamps_["yesterday"])); - EXPECT_NO_THROW(cbptr_->getModifiedSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }), - timestamps_["yesterday"])); - - // Not supported selectors. - EXPECT_THROW(cbptr_->getModifiedSubnets6(ServerSelector::ANY(), - timestamps_["yesterday"]), - isc::InvalidOperation); -} - -// Test that lifetimes in subnets are handled as expected. -TEST_F(MySqlConfigBackendDHCPv6Test, subnetLifetime) { - // Insert new subnet with unspecified valid lifetime - Triplet 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(100, 200, 300)); - subnet->setValid( Triplet(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()); -} - -// Test that subnets belonging to a shared network can be retrieved. -TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetworkSubnets6) { - // 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())); - - // 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)); -} - -// Test that pools are properly updated as a result a subnet update. -TEST_F(MySqlConfigBackendDHCPv6Test, subnetUpdatePools) { - - 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. - EXPECT_NO_THROW(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)); - EXPECT_NO_THROW(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); - } -} - -// Test that deleting a subnet triggers deletion of the options associated -// with the subnet and pools. -TEST_F(MySqlConfigBackendDHCPv6Test, subnetOptions) { - // Add the subnet with two pools and three options. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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")); -} - -// Test that shared network can be inserted, fetched, updated and then -// fetched again. -TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetwork6) { - // Insert the server2 into the database. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), - shared_network)); - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server2"), - shared_network2)); - - // We are not going to support selection of a single entry for multiple servers. - EXPECT_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(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]; - EXPECT_NO_THROW(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"); - } -} - -// Test that getSharedNetwork6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetwork6Selectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->getSharedNetwork6(ServerSelector::ANY(), "level1")); - EXPECT_NO_THROW(cbptr_->getSharedNetwork6(ServerSelector::UNASSIGNED(), "level1")); - EXPECT_NO_THROW(cbptr_->getSharedNetwork6(ServerSelector::ALL(), "level1")); - EXPECT_NO_THROW(cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"), "level1")); - - // Not supported selectors. - EXPECT_THROW(cbptr_->getSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), - "level1"), - isc::InvalidOperation); -} - -// Test that shared network may be created and updated and the server tags -// are properly assigned to it. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateSharedNetwork6) { - auto shared_network = test_networks_[0]; - - // An attempt to insert the shared network for non-existing server should fail. - EXPECT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), - shared_network), - NullKeyError); - - // Insert the server1 into the database. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); - { - SCOPED_TRACE("CREATE audit entry for server"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } - - EXPECT_NO_THROW(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"); - } - - EXPECT_NO_THROW(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(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())); -} - -// Test that createUpdateSharedNetwork6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateSharedNetwork6Selectors) { - ASSERT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - ASSERT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); - - // Supported selectors. - SharedNetwork6Ptr shared_network(new SharedNetwork6("all")); - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), - shared_network)); - shared_network.reset(new SharedNetwork6("one")); - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), - shared_network)); - shared_network.reset(new SharedNetwork6("multiple")); - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), - shared_network)); - - // Not supported server selectors. - EXPECT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ANY(), shared_network), - isc::InvalidOperation); - - // Not implemented server selectors. - EXPECT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::UNASSIGNED(), - shared_network), - isc::NotImplemented); -} - -// Test that the information about unspecified optional parameters gets -// propagated to the database. -TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetwork6WithOptionalUnspecified) { - // 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()); -} - -// Test that deleteSharedNetworkSubnets6 with not ANY selector throw. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteSharedNetworkSubnets6) { - EXPECT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::UNASSIGNED(), - test_networks_[1]->getName()), - isc::InvalidOperation); - EXPECT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ALL(), - test_networks_[1]->getName()), - isc::InvalidOperation); - EXPECT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ONE("server1"), - test_networks_[1]->getName()), - isc::InvalidOperation); - EXPECT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }), - test_networks_[1]->getName()), - isc::InvalidOperation); -} - -// Test that all shared networks can be fetched. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllSharedNetworks6) { - // 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 two new audit entries. - testNewAuditEntry("dhcp6_shared_network", - AuditEntry::ModificationType::DELETE, - "deleted all shared networks", - ServerSelector::ALL(), 2); - } - - // 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()); -} - -// Test that getAllSharedNetworks6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllSharedNetworks6Selectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->getAllSharedNetworks6(ServerSelector::UNASSIGNED())); - EXPECT_NO_THROW(cbptr_->getAllSharedNetworks6(ServerSelector::ALL())); - EXPECT_NO_THROW(cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1"))); - EXPECT_NO_THROW(cbptr_->getAllSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" }))); - - // Not supported selectors. - EXPECT_THROW(cbptr_->getAllSharedNetworks6(ServerSelector::ANY()), - isc::InvalidOperation); -} - -// Test that shared networks with different server associations are returned. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllSharedNetworks6WithServerTags) { - auto shared_network1 = test_networks_[0]; - auto shared_network2 = test_networks_[2]; - auto shared_network3 = test_networks_[3]; - - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); - - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), - shared_network1)); - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), - shared_network2)); - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), - shared_network3)); - - SharedNetwork6Collection networks; - - // All three networks are associated with the server1. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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"))); -} - -// Test that shared networks modified after given time can be fetched. -TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSharedNetworks6) { - // 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()); -} - -// Test that getModifiedSharedNetworks6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedSharedNetworks6Selectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->getModifiedSharedNetworks6(ServerSelector::UNASSIGNED(), - timestamps_["yesterday"])); - EXPECT_NO_THROW(cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(), - timestamps_["yesterday"])); - EXPECT_NO_THROW(cbptr_->getModifiedSharedNetworks6(ServerSelector::ONE("server1"), - timestamps_["yesterday"])); - EXPECT_NO_THROW(cbptr_->getModifiedSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" }), - timestamps_["yesterday"])); - - // Not supported selectors. - EXPECT_THROW(cbptr_->getModifiedSharedNetworks6(ServerSelector::ANY(), - timestamps_["yesterday"]), - isc::InvalidOperation); -} - -// Test that selected shared network can be deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteSharedNetwork6) { - // Create two servers in the database. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - { - SCOPED_TRACE("CREATE audit entry for server"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } - - EXPECT_NO_THROW(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 two shared networks, one for all servers, and one for server2. - EXPECT_NO_THROW( - cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network1) - ); - EXPECT_NO_THROW( - cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server2"), shared_network2) - ); - EXPECT_NO_THROW( - 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; - EXPECT_NO_THROW( - 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. - EXPECT_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. - EXPECT_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; - EXPECT_NO_THROW( - 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); -} - -// Test that deleteSharedNetwork6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteSharedNetwork6Selectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::ANY(), "level1")); - EXPECT_NO_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), "level1")); - EXPECT_NO_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"), "level1")); - - // Not supported selectors. - EXPECT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), - "level1"), - isc::InvalidOperation); - - // Not implemented selectors. - EXPECT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::UNASSIGNED(), "level1"), - isc::NotImplemented); -} - -// Test that deleteAllSharedNetworks6 throws appropriate exceptions for various -// server selectors. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteAllSharedNetworks6Selectors) { - // Supported selectors. - EXPECT_NO_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::UNASSIGNED())); - EXPECT_NO_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::ALL())); - EXPECT_NO_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1"))); - - // Not supported selectors. - EXPECT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::ANY()), - isc::InvalidOperation); - EXPECT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" })), - isc::InvalidOperation); -} - -// Test that it is possible to retrieve and delete orphaned shared network. -TEST_F(MySqlConfigBackendDHCPv6Test, unassignedSharedNetwork) { - // Create the server. - EXPECT_NO_THROW(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]; - EXPECT_NO_THROW( - cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), shared_network) - ); - EXPECT_NO_THROW( - 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; - EXPECT_NO_THROW(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; - EXPECT_NO_THROW(returned_network = cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"), - "level1")); - EXPECT_FALSE(returned_network); - - // The same if we use other calls. - SharedNetwork6Collection returned_networks; - EXPECT_NO_THROW( - returned_networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1")) - ); - EXPECT_TRUE(returned_networks.empty()); - - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW(returned_network = cbptr_->getSharedNetwork6(ServerSelector::UNASSIGNED(), - "level1")); - ASSERT_TRUE(returned_network); - - // Also if we ask for all unassigned networks it should be returned. - EXPECT_NO_THROW(returned_networks = cbptr_->getAllSharedNetworks6(ServerSelector::UNASSIGNED())); - ASSERT_EQ(2, returned_networks.size()); - - // And all modified. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW( - deleted_count = cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"), - "level1") - ); - EXPECT_EQ(0, deleted_count); - - // Also, if we delete all shared networks for server1. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - deleted_count = cbptr_->deleteSharedNetwork6(ServerSelector::ANY(), "level1") - ); - EXPECT_EQ(1, deleted_count); - - // We can delete all second networks using UNASSIGNED selector. - EXPECT_NO_THROW( - deleted_count = cbptr_->deleteAllSharedNetworks6(ServerSelector::UNASSIGNED()); - ); - EXPECT_EQ(1, deleted_count); -} - -// Test that lifetimes in shared networks are handled as expected. -TEST_F(MySqlConfigBackendDHCPv6Test, sharedNetworkLifetime) { - // Insert new shared network with unspecified valid lifetime - SharedNetwork6Ptr network(new SharedNetwork6("foo")); - Triplet 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(100, 200, 300)); - network->setValid( Triplet(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()); -} - -// Test that deleting a shared network triggers deletion of the options -// associated with the shared network. -TEST_F(MySqlConfigBackendDHCPv6Test, sharedNetworkOptions) { - // Add shared network with three options. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), - test_networks_[0]->getName())); - EXPECT_EQ(1, countRows("dhcp6_shared_network")); - EXPECT_EQ(1, countRows("dhcp6_options")); -} - -// Test that option definition can be inserted, fetched, updated and then -// fetched again. -TEST_F(MySqlConfigBackendDHCPv6Test, getOptionDef6) { - // 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"); - } -} - -// This test verifies that it is possible to differentiate between the -// option definitions by server tag and that the option definition -// specified for the particular server overrides the definition for -// all servers. -TEST_F(MySqlConfigBackendDHCPv6Test, optionDefs6WithServerTags) { - 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. - EXPECT_THROW(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server1"), - option1), - NullKeyError); - - // Create two servers. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[1])); - { - SCOPED_TRACE("server1 is created"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } - - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW(cbptr_->deleteServer6(ServerTag("server1"))); - EXPECT_NO_THROW( - 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; - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server2"), - option2)); - - // Delete all servers, except 'all'. - EXPECT_NO_THROW(deleted_num = cbptr_->deleteAllServers6()); - EXPECT_NO_THROW( - 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); - } -} - -// Test that all option definitions can be fetched. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptionDefs6) { - // 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. - if (option_def->getName() == "bar") { - SCOPED_TRACE("UPDATE audit entry for the option definition " + - option_def->getName()); - testNewAuditEntry("dhcp6_option_def", - AuditEntry::ModificationType::UPDATE, - "option definition set"); - ++updates_num; - - } else { - SCOPED_TRACE("CREATE audit entry for the option definition " + - option_def->getName()); - 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); - } -} - -// Test that option definitions modified after given time can be fetched. -TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedOptionDefs6) { - // 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()); -} - -// This test verifies that global option can be added, updated and deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteOption6) { - // 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"); - } -} - -// This test verifies that it is possible to differentiate between the -// global options by server tag and that the option specified for the -// particular server overrides the value specified for all servers. -TEST_F(MySqlConfigBackendDHCPv6Test, globalOptions6WithServerTags) { - OptionDescriptorPtr opt_timezone1 = test_options_[0]; - OptionDescriptorPtr opt_timezone2 = test_options_[6]; - OptionDescriptorPtr opt_timezone3 = test_options_[7]; - - EXPECT_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"), - opt_timezone1), - NullKeyError); - - // Create two servers. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[1])); - { - SCOPED_TRACE("server1 is created"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } - - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); - { - SCOPED_TRACE("server2 is created"); - testNewAuditEntry("dhcp6_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } - - EXPECT_NO_THROW(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); - - } - - EXPECT_NO_THROW(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); - - } - - EXPECT_NO_THROW(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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW( - 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. - EXPECT_NO_THROW(cbptr_->deleteServer6(ServerTag("server1"))); - EXPECT_NO_THROW( - 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; - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"), - opt_timezone2)); - - // Delete all servers, except 'all'. - EXPECT_NO_THROW(deleted_num = cbptr_->deleteAllServers6()); - EXPECT_NO_THROW( - 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); - } -} - -// This test verifies that all global options can be retrieved. -TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptions6) { - // 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()); - } -} - -// This test verifies that modified global options can be retrieved. -TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedOptions6) { - // 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); - } -} - -// This test verifies that subnet level option can be added, updated and -// deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteSubnetOption6) { - // 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")); -} - -// This test verifies that option can be inserted, updated and deleted -// from the pool. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeletePoolOption6) { - // Insert new subnet. - Subnet6Ptr subnet = test_subnets_[1]; - 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]; - 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")); -} - -// This test verifies that option can be inserted, updated and deleted -// from the pd pool. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeletePdPoolOption6) { - // Insert new subnet. - Subnet6Ptr subnet = test_subnets_[1]; - 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()); - cbptr_->createUpdateOption6(ServerSelector::ANY(), - pd_pool->getFirstAddress(), - static_cast(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(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(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")); -} - -// This test verifies that shared network level option can be added, -// updated and deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteSharedNetworkOption6) { - // 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")); -} - -// This test verifies that option id values in one subnet do -// not impact options returned in subsequent subnets when -// fetching subnets from the backend. -TEST_F(MySqlConfigBackendDHCPv6Test, subnetOptionIdOrder) { - - // Add a network with two pools with two options each. - EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[1])); - EXPECT_EQ(2, countRows("dhcp6_pool")); - EXPECT_EQ(4, countRows("dhcp6_options")); - - // Add second subnet with a single option. The number of options in the database - // should now be 3. - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(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; - EXPECT_NO_THROW(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(); - } - } -} - -// This test verifies that option id values in one shared network do -// not impact options returned in subsequent shared networks when -// fetching shared networks from the backend. -TEST_F(MySqlConfigBackendDHCPv6Test, sharedNetworkOptionIdOrder) { - 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. - EXPECT_NO_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), - level1_no_options)); - - EXPECT_NO_THROW(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()); - } - } - - EXPECT_NO_THROW(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()); - } - } -} - -// This test verifies that it is possible to create client classes, update them -// and retrieve all classes for a given server. -TEST_F(MySqlConfigBackendDHCPv6Test, setAndGetAllClientClasses6) { - // Create a server. - EXPECT_NO_THROW(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()); - EXPECT_EQ("foo", (*classes_list->begin())->getName()); - EXPECT_EQ("bar", (*(classes_list->begin() + 1))->getName()); - EXPECT_EQ("foobar", (*(classes_list->begin() + 2))->getName()); - - // Move the third class between the first and second class. - ASSERT_NO_THROW(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_EQ("foobar", (*(classes_list->begin() + 1))->getName()); - EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName()); - - // Update the foobar class without specifying its position. It should not - // be moved. - ASSERT_NO_THROW(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_EQ("foobar", (*(classes_list->begin() + 1))->getName()); - EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName()); -} - -// This test verifies that a single class can be retrieved from the database. -TEST_F(MySqlConfigBackendDHCPv6Test, getClientClass6) { - // Create a server. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - - // Add classes. - auto class1 = test_client_classes_[0]; - EXPECT_NO_THROW(class1->getCfgOption()->add(test_options_[0]->option_, - test_options_[0]->persistent_, - test_options_[0]->space_name_)); - EXPECT_NO_THROW(class1->getCfgOption()->add(test_options_[1]->option_, - test_options_[1]->persistent_, - test_options_[1]->space_name_)); - - EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); - - auto class2 = test_client_classes_[1]; - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ANY(), - class1->getName())); - EXPECT_TRUE(client_class); - - EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"), - class1->getName())); - EXPECT_TRUE(client_class); - - EXPECT_NO_THROW(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. - EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), - class2->getName())); - EXPECT_FALSE(client_class); - - EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ANY(), - class2->getName())); - EXPECT_TRUE(client_class); - - EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"), - class2->getName())); - EXPECT_TRUE(client_class); - - EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::UNASSIGNED(), - class2->getName())); - EXPECT_FALSE(client_class); -} - -// This test verifies that client class specific DHCP options can be -// modified during the class update. -TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateClientClass6Options) { - // Add class with two options and two option definitions. - auto class1 = test_client_classes_[0]; - EXPECT_NO_THROW(class1->getCfgOption()->add(test_options_[0]->option_, - test_options_[0]->persistent_, - test_options_[0]->space_name_)); - EXPECT_NO_THROW(class1->getCfgOption()->add(test_options_[1]->option_, - test_options_[1]->persistent_, - test_options_[1]->space_name_)); - auto cfg_option_def = boost::make_shared(); - class1->setCfgOptionDef(cfg_option_def); - EXPECT_NO_THROW(class1->getCfgOptionDef()->add(test_option_defs_[0])); - EXPECT_NO_THROW(class1->getCfgOptionDef()->add(test_option_defs_[2])); - EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); - - // Fetch the class and the options from the database. - ClientClassDefPtr client_class; - EXPECT_NO_THROW(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(); - class1->setCfgOptionDef(cfg_option_def); - EXPECT_NO_THROW(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()); - EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); - EXPECT_NO_THROW(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_); -} - -// This test verifies that modified client classes can be retrieved from the database. -TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedClientClasses6) { - // Create server1. - EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); - - // Add three classes to the database with different timestamps. - auto class1 = test_client_classes_[0]; - class1->setModificationTime(timestamps_["yesterday"]); - EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); - - auto class2 = test_client_classes_[1]; - class2->setModificationTime(timestamps_["today"]); - EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, "")); - - auto class3 = test_client_classes_[2]; - class3->setModificationTime(timestamps_["tomorrow"]); - EXPECT_NO_THROW(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. - EXPECT_THROW(cbptr_->getModifiedClientClasses6(ServerSelector::ANY(), - timestamps_["two days ago"]), - InvalidOperation); -} - -// This test verifies that a specified client class can be deleted. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteClientClass6) { - EXPECT_NO_THROW(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")); - } - - EXPECT_NO_THROW(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]; - EXPECT_NO_THROW(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]; - EXPECT_NO_THROW(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')"); - EXPECT_NO_THROW(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; - EXPECT_NO_THROW(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")); - } - - EXPECT_NO_THROW(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")); - } - - EXPECT_NO_THROW(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")); - } -} - -// This test verifies that all client classes can be deleted using -// a specified server selector. -TEST_F(MySqlConfigBackendDHCPv6Test, deleteAllClientClasses6) { - EXPECT_NO_THROW(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")); - } - - EXPECT_NO_THROW(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]; - EXPECT_NO_THROW(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]; - EXPECT_NO_THROW(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')"); - EXPECT_NO_THROW(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; - - EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::UNASSIGNED())); - EXPECT_EQ(0, result); - - EXPECT_NO_THROW(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")); - } - - EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2"))); - EXPECT_EQ(0, result); - - EXPECT_NO_THROW(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")); - } - - EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1"))); - EXPECT_EQ(0, result); - - EXPECT_NO_THROW(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")); - } - - EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL())); - EXPECT_EQ(0, result); - - // Deleting multiple objects using ANY server tag is unsupported. - EXPECT_THROW(cbptr_->deleteAllClientClasses6(ServerSelector::ANY()), InvalidOperation); -} - -// This test verifies that client class dependencies are tracked when the -// classes are added to the database. It verifies that an attempt to update -// a class violating the dependencies results in an error. -TEST_F(MySqlConfigBackendDHCPv6Test, clientClassDependencies6) { - // Create a server. - EXPECT_NO_THROW(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')"); - EXPECT_NO_THROW(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')"); - EXPECT_NO_THROW(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')"); - EXPECT_NO_THROW(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. - EXPECT_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')"); - EXPECT_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')"); - EXPECT_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')"); - EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class3, "")); -} - -/// This test verifies that audit entries can be retrieved from a given -/// timestamp and id including when two entries can get the same timestamp. -/// (either it is a common even and this should catch it, or it is a rare -/// event and it does not matter). -TEST_F(MySqlConfigBackendDHCPv6Test, multipleAuditEntries) { - // Get current time. - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - - // Create a server. - EXPECT_NO_THROW(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(); - 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())); - } -} - -class MySqlConfigBackendDHCPv6DbLostCallbackTest : public ::testing::Test { -public: - MySqlConfigBackendDHCPv6DbLostCallbackTest() - : db_lost_callback_called_(0), db_recovered_callback_called_(0), - db_failed_callback_called_(0), - io_service_(boost::make_shared()) { - isc::db::DatabaseConnection::db_lost_callback_ = 0; - isc::db::DatabaseConnection::db_recovered_callback_ = 0; - isc::db::DatabaseConnection::db_failed_callback_ = 0; - isc::dhcp::MySqlConfigBackendImpl::setIOService(io_service_); - isc::dhcp::TimerMgr::instance()->setIOService(io_service_); - isc::dhcp::CfgMgr::instance().clear(); - } - - virtual ~MySqlConfigBackendDHCPv6DbLostCallbackTest() { - isc::db::DatabaseConnection::db_lost_callback_ = 0; - isc::db::DatabaseConnection::db_recovered_callback_ = 0; - isc::db::DatabaseConnection::db_failed_callback_ = 0; - isc::dhcp::MySqlConfigBackendImpl::setIOService(isc::asiolink::IOServicePtr()); - isc::dhcp::TimerMgr::instance()->unregisterTimers(); - isc::dhcp::CfgMgr::instance().clear(); - } - - /// @brief Prepares the class for a test. - /// - /// Invoked by gtest prior test entry, we create the - /// appropriate schema and create a basic DB manager to - /// wipe out any prior instance - virtual void SetUp() { - // Ensure we have the proper schema with no transient data. - createMySQLSchema(); - isc::dhcp::CfgMgr::instance().clear(); - isc::dhcp::MySqlConfigBackendDHCPv6::registerBackendType(); - } - - /// @brief Pre-text exit clean up - /// - /// Invoked by gtest upon test exit, we destroy the schema - /// we created. - virtual void TearDown() { - // If data wipe enabled, delete transient data otherwise destroy the schema - destroyMySQLSchema(); - isc::dhcp::CfgMgr::instance().clear(); - isc::dhcp::MySqlConfigBackendDHCPv6::unregisterBackendType(); - } - - /// @brief Method which returns the back end specific connection - /// string - virtual std::string validConnectString() { - return (validMySQLConnectionString()); - } - - /// @brief Method which returns invalid back end specific connection - /// string - virtual std::string invalidConnectString() { - return (connectionString(MYSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, - VALID_USER, VALID_PASSWORD)); - } - - /// @brief Verifies open failures do NOT invoke db lost callback - /// - /// The db lost callback should only be invoked after successfully - /// opening the DB and then subsequently losing it. Failing to - /// open should be handled directly by the application layer. - void testNoCallbackOnOpenFailure(); - - /// @brief Verifies the CB manager's behavior if DB connection is lost - /// - /// This function creates a CB manager with a back end that supports - /// connectivity lost callback. It verifies connectivity by issuing a known - /// valid query. Next it simulates connectivity lost by identifying and - /// closing the socket connection to the CB backend. It then reissues the - /// query and verifies that: - /// -# The Query throws DbOperationError (rather than exiting) - /// -# The registered DbLostCallback was invoked - /// -# The registered DbRecoveredCallback was invoked - void testDbLostAndRecoveredCallback(); - - /// @brief Verifies the CB manager's behavior if DB connection is lost - /// - /// This function creates a CB manager with a back end that supports - /// connectivity lost callback. It verifies connectivity by issuing a known - /// valid query. Next it simulates connectivity lost by identifying and - /// closing the socket connection to the CB backend. It then reissues the - /// query and verifies that: - /// -# The Query throws DbOperationError (rather than exiting) - /// -# The registered DbLostCallback was invoked - /// -# The registered DbFailedCallback was invoked - void testDbLostAndFailedCallback(); - - /// @brief Verifies the CB manager's behavior if DB connection is lost - /// - /// This function creates a CB manager with a back end that supports - /// connectivity lost callback. It verifies connectivity by issuing a known - /// valid query. Next it simulates connectivity lost by identifying and - /// closing the socket connection to the CB backend. It then reissues the - /// query and verifies that: - /// -# The Query throws DbOperationError (rather than exiting) - /// -# The registered DbLostCallback was invoked - /// -# The registered DbRecoveredCallback was invoked after two reconnect - /// attempts (once failing and second triggered by timer) - void testDbLostAndRecoveredAfterTimeoutCallback(); - - /// @brief Verifies the CB manager's behavior if DB connection is lost - /// - /// This function creates a CB manager with a back end that supports - /// connectivity lost callback. It verifies connectivity by issuing a known - /// valid query. Next it simulates connectivity lost by identifying and - /// closing the socket connection to the CB backend. It then reissues the - /// query and verifies that: - /// -# The Query throws DbOperationError (rather than exiting) - /// -# The registered DbLostCallback was invoked - /// -# The registered DbFailedCallback was invoked after two reconnect - /// attempts (once failing and second triggered by timer) - void testDbLostAndFailedAfterTimeoutCallback(); - - /// @brief Callback function registered with the CB manager - bool db_lost_callback(db::ReconnectCtlPtr /* not_used */) { - return (++db_lost_callback_called_); - } - - /// @brief Flag used to detect calls to db_lost_callback function - uint32_t db_lost_callback_called_; - - /// @brief Callback function registered with the CB manager - bool db_recovered_callback(db::ReconnectCtlPtr /* not_used */) { - return (++db_recovered_callback_called_); - } - - /// @brief Flag used to detect calls to db_recovered_callback function - uint32_t db_recovered_callback_called_; - - /// @brief Callback function registered with the CB manager - bool db_failed_callback(db::ReconnectCtlPtr /* not_used */) { - return (++db_failed_callback_called_); + /// @brief Fetches a collection of all the servers currently in + /// the CB database. This function is used to check the operability + /// of the CB backend. + ServerCollection getAllServers() { + return (ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector())); } - - /// @brief Flag used to detect calls to db_failed_callback function - uint32_t db_failed_callback_called_; - - /// The IOService object, used for all ASIO operations. - isc::asiolink::IOServicePtr io_service_; }; -void -MySqlConfigBackendDHCPv6DbLostCallbackTest::testNoCallbackOnOpenFailure() { - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = invalidConnectString(); - - // Connect to the CB backend. - ASSERT_THROW(ConfigBackendDHCPv6Mgr::instance().addBackend(access), DbOpenError); - - io_service_->poll(); - - EXPECT_EQ(0, db_lost_callback_called_); - EXPECT_EQ(0, db_recovered_callback_called_); - EXPECT_EQ(0, db_failed_callback_called_); -} - -void -MySqlConfigBackendDHCPv6DbLostCallbackTest::testDbLostAndRecoveredCallback() { - // Set the connectivity lost callback. - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = validConnectString(); - - ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); - config_ctl_info->addConfigDatabase(access); - CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); - - // Find the most recently opened socket. Our SQL client's socket should - // be the next one. - int last_open_socket = findLastSocketFd(); - - // Fill holes. - FillFdHoles holes(last_open_socket); - - // Connect to the CB backend. - ASSERT_NO_THROW(ConfigBackendDHCPv6Mgr::instance().addBackend(access)); - - // Find the SQL client socket. - int sql_socket = findLastSocketFd(); - ASSERT_TRUE(sql_socket > last_open_socket); - - // Verify we can execute a query. We don't care about the answer. - ServerCollection servers; - ASSERT_NO_THROW(servers = ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector())); - - // Now close the sql socket out from under backend client - ASSERT_EQ(0, close(sql_socket)); - - // A query should fail with DbConnectionUnusable. - ASSERT_THROW(servers = ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector()), - DbConnectionUnusable); - - io_service_->poll(); - - // Our lost and recovered connectivity callback should have been invoked. - EXPECT_EQ(1, db_lost_callback_called_); - EXPECT_EQ(1, db_recovered_callback_called_); - EXPECT_EQ(0, db_failed_callback_called_); -} - -void -MySqlConfigBackendDHCPv6DbLostCallbackTest::testDbLostAndFailedCallback() { - // Set the connectivity lost callback. - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = validConnectString(); - ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); - config_ctl_info->addConfigDatabase(access); - CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); - - // Find the most recently opened socket. Our SQL client's socket should - // be the next one. - int last_open_socket = findLastSocketFd(); - - // Fill holes. - FillFdHoles holes(last_open_socket); - - // Connect to the CB backend. - ASSERT_NO_THROW(ConfigBackendDHCPv6Mgr::instance().addBackend(access)); - - // Find the SQL client socket. - int sql_socket = findLastSocketFd(); - ASSERT_TRUE(sql_socket > last_open_socket); - - // Verify we can execute a query. We don't care about the answer. - ServerCollection servers; - ASSERT_NO_THROW(servers = ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector())); - - access = invalidConnectString(); - CfgMgr::instance().clear(); - // by adding an invalid access will cause the manager factory to throw - // resulting in failure to recreate the manager - config_ctl_info.reset(new ConfigControlInfo()); - config_ctl_info->addConfigDatabase(access); - CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); - const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases(); - (const_cast(cfg))[0].setAccessString(access, true); - - // Now close the sql socket out from under backend client - ASSERT_EQ(0, close(sql_socket)); - - // A query should fail with DbConnectionUnusable. - ASSERT_THROW(servers = ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector()), - DbConnectionUnusable); - - io_service_->poll(); - - // Our lost and failed connectivity callback should have been invoked. - EXPECT_EQ(1, db_lost_callback_called_); - EXPECT_EQ(0, db_recovered_callback_called_); - EXPECT_EQ(1, db_failed_callback_called_); -} - -void -MySqlConfigBackendDHCPv6DbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() { - // Set the connectivity lost callback. - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = validConnectString(); - std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1"; - access += extra; - ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); - config_ctl_info->addConfigDatabase(access); - CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); - - // Find the most recently opened socket. Our SQL client's socket should - // be the next one. - int last_open_socket = findLastSocketFd(); - - // Fill holes. - FillFdHoles holes(last_open_socket); - - // Connect to the CB backend. - ASSERT_NO_THROW(ConfigBackendDHCPv6Mgr::instance().addBackend(access)); - - // Find the SQL client socket. - int sql_socket = findLastSocketFd(); - ASSERT_TRUE(sql_socket > last_open_socket); - - // Verify we can execute a query. We don't care about the answer. - ServerCollection servers; - ASSERT_NO_THROW(servers = ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector())); - - access = invalidConnectString(); - access += extra; - CfgMgr::instance().clear(); - // by adding an invalid access will cause the manager factory to throw - // resulting in failure to recreate the manager - config_ctl_info.reset(new ConfigControlInfo()); - config_ctl_info->addConfigDatabase(access); - CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); - const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases(); - (const_cast(cfg))[0].setAccessString(access, true); - - // Now close the sql socket out from under backend client - ASSERT_EQ(0, close(sql_socket)); - - // A query should fail with DbConnectionUnusable. - ASSERT_THROW(servers = ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector()), - DbConnectionUnusable); - - io_service_->poll(); - - // Our lost connectivity callback should have been invoked. - EXPECT_EQ(1, db_lost_callback_called_); - EXPECT_EQ(0, db_recovered_callback_called_); - EXPECT_EQ(0, db_failed_callback_called_); - - access = validConnectString(); - access += extra; - CfgMgr::instance().clear(); - config_ctl_info.reset(new ConfigControlInfo()); - config_ctl_info->addConfigDatabase(access); - CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); - - sleep(1); - - io_service_->poll(); - - // Our lost and recovered connectivity callback should have been invoked. - EXPECT_EQ(2, db_lost_callback_called_); - EXPECT_EQ(1, db_recovered_callback_called_); - EXPECT_EQ(0, db_failed_callback_called_); - - sleep(1); - - io_service_->poll(); - - // No callback should have been invoked. - EXPECT_EQ(2, db_lost_callback_called_); - EXPECT_EQ(1, db_recovered_callback_called_); - EXPECT_EQ(0, db_failed_callback_called_); -} - -void -MySqlConfigBackendDHCPv6DbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() { - // Set the connectivity lost callback. - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&MySqlConfigBackendDHCPv6DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = validConnectString(); - std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1"; - access += extra; - ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); - config_ctl_info->addConfigDatabase(access); - CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); - - // Find the most recently opened socket. Our SQL client's socket should - // be the next one. - int last_open_socket = findLastSocketFd(); - - // Fill holes. - FillFdHoles holes(last_open_socket); - - // Connect to the CB backend. - ASSERT_NO_THROW(ConfigBackendDHCPv6Mgr::instance().addBackend(access)); - - // Find the SQL client socket. - int sql_socket = findLastSocketFd(); - ASSERT_TRUE(sql_socket > last_open_socket); - - // Verify we can execute a query. We don't care about the answer. - ServerCollection servers; - ASSERT_NO_THROW(servers = ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector())); - - access = invalidConnectString(); - access += extra; - CfgMgr::instance().clear(); - // by adding an invalid access will cause the manager factory to throw - // resulting in failure to recreate the manager - config_ctl_info.reset(new ConfigControlInfo()); - config_ctl_info->addConfigDatabase(access); - CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); - const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases(); - (const_cast(cfg))[0].setAccessString(access, true); - - // Now close the sql socket out from under backend client - ASSERT_EQ(0, close(sql_socket)); - - // A query should fail with DbConnectionUnusable. - ASSERT_THROW(servers = ConfigBackendDHCPv6Mgr::instance().getPool()->getAllServers6(BackendSelector()), - DbConnectionUnusable); - - io_service_->poll(); - - // Our lost connectivity callback should have been invoked. - EXPECT_EQ(1, db_lost_callback_called_); - EXPECT_EQ(0, db_recovered_callback_called_); - EXPECT_EQ(0, db_failed_callback_called_); - - sleep(1); - - io_service_->poll(); - - // Our lost connectivity callback should have been invoked. - EXPECT_EQ(2, db_lost_callback_called_); - EXPECT_EQ(0, db_recovered_callback_called_); - EXPECT_EQ(0, db_failed_callback_called_); - - sleep(1); - - io_service_->poll(); - - // Our lost and failed connectivity callback should have been invoked. - EXPECT_EQ(3, db_lost_callback_called_); - EXPECT_EQ(0, db_recovered_callback_called_); - EXPECT_EQ(1, db_failed_callback_called_); -} - -/// @brief Verifies that db lost callback is not invoked on an open failure TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testNoCallbackOnOpenFailure) { MultiThreadingTest mt(false); testNoCallbackOnOpenFailure(); } -/// @brief Verifies that db lost callback is not invoked on an open failure TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testNoCallbackOnOpenFailureMultiThreading) { MultiThreadingTest mt(true); testNoCallbackOnOpenFailure(); } -/// @brief Verifies that loss of connectivity to MySQL is handled correctly. TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testDbLostAndRecoveredCallback) { MultiThreadingTest mt(false); testDbLostAndRecoveredCallback(); } -/// @brief Verifies that loss of connectivity to MySQL is handled correctly. TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testDbLostAndRecoveredCallbackMultiThreading) { MultiThreadingTest mt(true); testDbLostAndRecoveredCallback(); } -/// @brief Verifies that loss of connectivity to MySQL is handled correctly. TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testDbLostAndFailedCallback) { MultiThreadingTest mt(false); testDbLostAndFailedCallback(); } -/// @brief Verifies that loss of connectivity to MySQL is handled correctly. TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testDbLostAndFailedCallbackMultiThreading) { MultiThreadingTest mt(true); testDbLostAndFailedCallback(); } -/// @brief Verifies that loss of connectivity to MySQL is handled correctly. TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallback) { MultiThreadingTest mt(false); testDbLostAndRecoveredAfterTimeoutCallback(); } -/// @brief Verifies that loss of connectivity to MySQL is handled correctly. TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallbackMultiThreading) { MultiThreadingTest mt(true); testDbLostAndRecoveredAfterTimeoutCallback(); } -/// @brief Verifies that loss of connectivity to MySQL is handled correctly. TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallback) { MultiThreadingTest mt(false); testDbLostAndFailedAfterTimeoutCallback(); } -/// @brief Verifies that loss of connectivity to MySQL is handled correctly. TEST_F(MySqlConfigBackendDHCPv6DbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallbackMultiThreading) { MultiThreadingTest mt(true); testDbLostAndFailedAfterTimeoutCallback(); diff --git a/src/lib/dhcpsrv/testutils/Makefile.am b/src/lib/dhcpsrv/testutils/Makefile.am index 9c7e17fb3d..a1757533ab 100644 --- a/src/lib/dhcpsrv/testutils/Makefile.am +++ b/src/lib/dhcpsrv/testutils/Makefile.am @@ -20,6 +20,7 @@ libdhcpsrvtest_la_SOURCES += test_utils.cc test_utils.h libdhcpsrvtest_la_SOURCES += generic_backend_unittest.cc generic_backend_unittest.h libdhcpsrvtest_la_SOURCES += generic_host_data_source_unittest.cc generic_host_data_source_unittest.h libdhcpsrvtest_la_SOURCES += generic_cb_dhcp4_unittest.h generic_cb_dhcp4_unittest.cc +libdhcpsrvtest_la_SOURCES += generic_cb_dhcp6_unittest.h generic_cb_dhcp6_unittest.cc libdhcpsrvtest_la_SOURCES += generic_cb_recovery_unittest.h generic_cb_recovery_unittest.cc libdhcpsrvtest_la_SOURCES += lease_file_io.cc lease_file_io.h libdhcpsrvtest_la_SOURCES += test_config_backend.h 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 0000000000..2c5243c56c --- /dev/null +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc @@ -0,0 +1,4796 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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() { + // 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(); + initTimestamps(); +} + +void +GenericConfigBackendDHCPv6Test::TearDown() { + cbptr_.reset(); + // If data wipe enabled, delete transient data otherwise destroy the schema. + destroySchema(); +} + +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 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(), Triplet(), + Triplet(), Triplet(), + 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 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(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(Option::V6, D6O_PREFERENCE, false, true, 64); + desc.space_name_ = DHCP6_OPTION_SPACE; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption(Option::V6, 1, false, false, 312131), + desc.space_name_ = "vendor-encapsulated-options"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createAddressOption(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(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(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(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(); + CfgOptionPtr cfg_option = boost::make_shared(); + auto class1 = boost::make_shared("foo", match_expr, cfg_option); + class1->setRequired(true); + class1->setValid(Triplet(30, 60, 90)); + test_client_classes_.push_back(class1); + + auto class2 = boost::make_shared("bar", match_expr, cfg_option); + class2->setTest("member('foo')"); + test_client_classes_.push_back(class2); + + auto class3 = boost::make_shared("foobar", match_expr, cfg_option); + class3->setTest("member('foo') and member('bar')"); + test_client_classes_.push_back(class3); +} + +void +GenericConfigBackendDHCPv6Test::initTimestamps() { + // Current time minus 1 hour to make sure it is in the past. + timestamps_["today"] = boost::posix_time::second_clock::local_time() + - boost::posix_time::hours(1); + // One second after today. + timestamps_["after today"] = timestamps_["today"] + boost::posix_time::seconds(1); + + // Yesterday. + timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24); + + // One second after yesterday. + timestamps_["after yesterday"] = timestamps_["yesterday"] + boost::posix_time::seconds(1); + + // Two days ago. + timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48); + + // Tomorrow. + timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24); + + // One second after tomorrow. + timestamps_["after tomorrow"] = timestamps_["tomorrow"] + boost::posix_time::seconds(1); +} + +std::string +GenericConfigBackendDHCPv6Test::logExistingAuditEntries(const std::string& server_tag) { + std::ostringstream s; + + auto& mod_time_idx = audit_entries_[server_tag].get(); + + for (auto audit_entry_it = mod_time_idx.begin(); + audit_entry_it != mod_time_idx.end(); + ++audit_entry_it) { + auto audit_entry = *audit_entry_it; + s << audit_entry->getObjectType() << ", " + << audit_entry->getObjectId() << ", " + << static_cast(audit_entry->getModificationType()) << ", " + << audit_entry->getModificationTime() << ", " + << audit_entry->getRevisionId() << ", " + << audit_entry->getLogMessage() + << std::endl; + } + + return (s.str()); +} + +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::testNewAuditEntry(const std::string& exp_object_type, + const AuditEntry::ModificationType& + exp_modification_type, + const std::string& exp_log_message, + const ServerSelector& server_selector, + const size_t new_entries_num, + const size_t max_tested_entries) { + + // Get the server tag for which the entries are fetched. + std::string tag; + if (server_selector.getType() == ServerSelector::Type::ALL) { + // Server tag is 'all'. + tag = "all"; + } else { + auto tags = server_selector.getTags(); + // This test is not meant to handle multiple server tags all at once. + if (tags.size() > 1) { + ADD_FAILURE() << "Test error: do not use multiple server tags"; + } else if (tags.size() == 1) { + // Get the server tag for which we run the current test. + tag = tags.begin()->get(); + } + } + + auto audit_entries_size_save = audit_entries_[tag].size(); + + // Audit entries for different server tags are stored in separate + // containers. + ASSERT_NO_THROW_LOG(audit_entries_[tag] + = cbptr_->getRecentAuditEntries(server_selector, + timestamps_["two days ago"], 0)); + ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size()) + << logExistingAuditEntries(tag); + + auto& mod_time_idx = audit_entries_[tag].get(); + + // Iterate over specified number of entries starting from the most recent + // one and check they have correct values. + for (auto audit_entry_it = mod_time_idx.rbegin(); + ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num) && + (std::distance(mod_time_idx.rbegin(), audit_entry_it) < max_tested_entries)); + ++audit_entry_it) { + auto audit_entry = *audit_entry_it; + EXPECT_EQ(exp_object_type, audit_entry->getObjectType()) + << logExistingAuditEntries(tag); + EXPECT_EQ(exp_modification_type, audit_entry->getModificationType()) + << logExistingAuditEntries(tag); + EXPECT_EQ(exp_log_message, audit_entry->getLogMessage()) + << logExistingAuditEntries(tag); + } +} + +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"); + EXPECT_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. + EXPECT_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); +} + +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()); + + { + 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. + EXPECT_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 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(65)))); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name3", "value3")); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name4", Element::create(static_cast(true)))); + cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(), + StampedValue::create("name5", Element::create(static_cast(1.65)))); + + // Fetch all parameters. + auto parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL()); + ASSERT_EQ(5, parameters.size()); + + const auto& parameters_index = parameters.get(); + + for (auto param = parameters_index.begin(); param != parameters_index.end(); + ++param) { + ASSERT_EQ(1, (*param)->getServerTags().size()); + EXPECT_EQ("all", (*param)->getServerTags().begin()->get()); + } + + // 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()); + + // 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(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(); + + // 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(cbptr_->createUpdateServer6(test_servers_[0])); + ASSERT_NO_THROW(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. + EXPECT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ANY(), subnet), + isc::InvalidOperation); + + // Not implemented server selectors. + EXPECT_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. + EXPECT_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(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("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("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. + EXPECT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + subnet->getID()), + isc::InvalidOperation); + + EXPECT_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(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))); + + EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str()); + + // Test fetching subnet by prefix. + ASSERT_NO_THROW(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("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)); + EXPECT_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. + EXPECT_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. + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network); + cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet); + + // Fetch this subnet by subnet identifier. + Subnet6Ptr 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. + cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), + shared_network); + + // Store subnet associated with the shared network in the database. + 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]; + 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. + EXPECT_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) { + EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(), + (*subnet_it)->toElement()->str()); + ASSERT_EQ(1, (*subnet_it)->getServerTags().size()); + EXPECT_EQ("all", (*subnet_it)->getServerTags().begin()->get()); + } + + // 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. + EXPECT_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::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. + EXPECT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + SubnetID(1)), + isc::InvalidOperation); + + // Not implemented selectors. + EXPECT_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. + EXPECT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }), + "192.0.2.0/26"), + isc::InvalidOperation); + + // Not implemented selectors. + EXPECT_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. + EXPECT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::ANY()), + isc::InvalidOperation); + EXPECT_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::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::subnetLifetimeTest() { + // Insert new subnet with unspecified valid lifetime + Triplet 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(100, 200, 300)); + subnet->setValid( Triplet(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())); + + // 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. + EXPECT_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(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. + EXPECT_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. + EXPECT_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(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(cbptr_->createUpdateServer6(test_servers_[0])); + ASSERT_NO_THROW(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. + EXPECT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ANY(), shared_network), + isc::InvalidOperation); + + // Not implemented server selectors. + EXPECT_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() { + EXPECT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::UNASSIGNED(), + test_networks_[1]->getName()), + isc::InvalidOperation); + EXPECT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ALL(), + test_networks_[1]->getName()), + isc::InvalidOperation); + EXPECT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ONE("server1"), + test_networks_[1]->getName()), + isc::InvalidOperation); + EXPECT_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 two new audit entries. + testNewAuditEntry("dhcp6_shared_network", + AuditEntry::ModificationType::DELETE, + "deleted all shared networks", + ServerSelector::ALL(), 2); + } + + // 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. + EXPECT_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. + EXPECT_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 two shared networks, one for all servers, and one for 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. + EXPECT_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. + EXPECT_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. + EXPECT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }), + "level1"), + isc::InvalidOperation); + + // Not implemented selectors. + EXPECT_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. + EXPECT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::ANY()), + isc::InvalidOperation); + EXPECT_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 second 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 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(100, 200, 300)); + network->setValid( Triplet(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. + EXPECT_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. + if (option_def->getName() == "bar") { + SCOPED_TRACE("UPDATE audit entry for the option definition " + + option_def->getName()); + testNewAuditEntry("dhcp6_option_def", + AuditEntry::ModificationType::UPDATE, + "option definition set"); + ++updates_num; + + } else { + SCOPED_TRACE("CREATE audit entry for the option definition " + + option_def->getName()); + 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]; + + EXPECT_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]; + 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]; + 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]; + 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()); + cbptr_->createUpdateOption6(ServerSelector::ANY(), + pd_pool->getFirstAddress(), + static_cast(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(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(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 network 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 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()); + EXPECT_EQ("foo", (*classes_list->begin())->getName()); + EXPECT_EQ("bar", (*(classes_list->begin() + 1))->getName()); + EXPECT_EQ("foobar", (*(classes_list->begin() + 2))->getName()); + + // Move the third class between the first and second class. + ASSERT_NO_THROW(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_EQ("foobar", (*(classes_list->begin() + 1))->getName()); + EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName()); + + // Update the foobar class without specifying its position. It should not + // be moved. + ASSERT_NO_THROW(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_EQ("foobar", (*(classes_list->begin() + 1))->getName()); + EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName()); +} + +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(); + 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(); + 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. + EXPECT_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. + EXPECT_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. + EXPECT_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')"); + EXPECT_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')"); + EXPECT_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(); + 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())); + } +} diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h new file mode 100644 index 0000000000..46368069c4 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h @@ -0,0 +1,414 @@ +// 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/. + +#ifndef GENERIC_CONFIG_BACKEND_DHCP6_H +#define GENERIC_CONFIG_BACKEND_DHCP6_H + +#include +#include +#include + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Generic test fixture class for testing DHCPv6 +/// config backend operations. +class GenericConfigBackendDHCPv6Test : public GenericBackendTest { +public: + /// @brief Constructor. + GenericConfigBackendDHCPv6Test() + : test_subnets_(), test_networks_(), test_option_defs_(), + test_options_(), test_client_classes_(), test_servers_(), timestamps_(), + cbptr_(), audit_entries_() { + } + + /// @brief Destructor. + virtual ~GenericConfigBackendDHCPv6Test() {}; + + /// @brief Prepares the class for a test. + /// + /// Invoked by gtest prior test entry, we create the + /// appropriate schema and create a basic host manager to + /// wipe out any prior instance + virtual void SetUp(); + + /// @brief Pre-test exit clean up + /// + /// Invoked by gtest upon test exit, we destroy the schema + /// we created. + virtual void TearDown(); + + /// @brief Abstract method for destroying the back end specific schema + virtual void destroySchema() = 0; + + /// @brief Abstract method for creating the back end specific schema + virtual void createSchema() = 0; + + /// @brief Abstract method which returns the back end specific connection + /// string + virtual std::string validConnectionString() = 0; + + /// @brief Abstract method which instantiates an instance of a + /// DHCPv6 configuration back end. + /// + /// @params Connection parameters describing the back end to create. + /// @return Pointer to the newly created back end instance. + virtual ConfigBackendDHCPv6Ptr backendFactory(db::DatabaseConnection::ParameterMap& + params) = 0; + + /// @brief Counts rows in a selected table in the back end database. + /// + /// This method can be used to verify that some configuration elements were + /// deleted from a selected table as a result of cascade delete or a trigger. + /// For example, deleting a subnet should trigger deletion of its address + /// pools and options. By counting the rows on each table we can determine + /// whether the deletion took place on all tables for which it was expected. + /// + /// @param table Table name. + /// @return Number of rows in the specified table. + virtual size_t countRows(const std::string& table) const = 0; + + /// @brief Creates several servers used in tests. + void initTestServers(); + + /// @brief Creates several subnets used in tests. + void initTestSubnets(); + + /// @brief Creates several subnets used in tests. + void initTestSharedNetworks(); + + /// @brief Creates several option definitions used in tests. + void initTestOptionDefs(); + + /// @brief Creates several DHCP options used in tests. + void initTestOptions(); + + /// @brief Creates several client classes used in tests. + void initTestClientClasses(); + + /// @brief Initialize posix time values used in tests. + void initTimestamps(); + + /// @brief Logs audit entries in the @c audit_entries_ member. + /// + /// This function is called in case of an error. + /// + /// @param server_tag Server tag for which the audit entries should be logged. + std::string logExistingAuditEntries(const std::string& server_tag); + + /// @brief Tests that the new audit entry is added. + /// + /// This method retrieves a collection of the existing audit entries and + /// checks that the new one has been added at the end of this collection. + /// It then verifies the values of the audit entry against the values + /// specified by the caller. + /// + /// @param exp_object_type Expected object type. + /// @param exp_modification_type Expected modification type. + /// @param exp_log_message Expected log message. + /// @param server_selector Server selector to be used for next query. + /// @param new_entries_num Number of the new entries expected to be inserted. + /// @param max_tested_entries Maximum number of entries tested. + void testNewAuditEntry(const std::string& exp_object_type, + const db::AuditEntry::ModificationType& exp_modification_type, + const std::string& exp_log_message, + const db::ServerSelector& server_selector = db::ServerSelector::ALL(), + const size_t new_entries_num = 1, + const size_t max_tested_entries = 65535); + + /// @brief Tests that a backend of the given type can be instantiated. + /// + /// @param expected_type type of the back end created (i.e. "mysql", + /// "postgresql"). + void getTypeTest(const std::string& expected_type); + + /// @brief Verifies that a backend on the localhost can be instantiated. + void getHostTest(); + + /// @brief Verifies that a backend on the localhost port 0 can be instantiated. + void getPortTest(); + + /// @brief This test verifies that the server can be added, updated and deleted. + void createUpdateDeleteServerTest(); + + /// @brief This test verifies that it is possible to retrieve all servers from the + /// database and then delete all of them. + void getAndDeleteAllServersTest(); + + /// @brief Thist test verifies that the global parameter can be added, updated and + /// deleted. + void createUpdateDeleteGlobalParameter6Test(); + + /// @brief Thist test verifies that it is possible to differentiate between the + /// global parameters by server tag and that the value specified for the + /// particular server overrides the value specified for all servers. + void globalParameters6WithServerTagsTest(); + + /// @brief Thist test verifies that all global parameters can be retrieved and deleted. + void getAllGlobalParameters6Test(); + + /// @brief Thist test verifies that modified global parameters can be retrieved. + void getModifiedGlobalParameters6Test(); + + /// Test that the NullKeyError message is correctly updated. + void nullKeyErrorTest(); + + /// Test that ceateUpdateSubnet6 throws appropriate exceptions for various + /// server selectors. + void createUpdateSubnet6SelectorsTest(); + + /// Test that subnet can be inserted, fetched, updated and then fetched again. + void getSubnet6Test(); + + /// Test that getSubnet6 by ID throws appropriate exceptions for various server + /// selectors. + void getSubnet6byIdSelectorsTest(); + + /// Test that the information about unspecified optional parameters gets + /// propagated to the database. + void getSubnet6WithOptionalUnspecifiedTest(); + + /// Test that subnet can be associated with a shared network. + void getSubnet6SharedNetworkTest(); + + /// Test that subnet can be fetched by prefix. + void getSubnet6ByPrefixTest(); + + /// Test that getSubnet6 by prefix throws appropriate exceptions for various server + /// selectors. + void getSubnet6byPrefixSelectorsTest(); + + /// Test that all subnets can be fetched and then deleted. + void getAllSubnets6Test(); + + /// Test that getAllSubnets6 throws appropriate exceptions for various + /// server selectors. + void getAllSubnets6SelectorsTest(); + + /// Test that subnets with different server associations are returned. + void getAllSubnets6WithServerTagsTest(); + + /// Test that selected subnet can be deleted. + void deleteSubnet6Test(); + + /// Test that deleteSubnet6 by ID throws appropriate exceptions for various + /// server selectors. + void deleteSubnet6ByIdSelectorsTest(); + + /// Test that deleteSubnet6 by prefix throws appropriate exceptions for various + /// server selectors. + void deleteSubnet6ByPrefixSelectorsTest(); + + /// Test that deleteAllSubnets6 throws appropriate exceptions for various + /// server selectors. + void deleteAllSubnets6SelectorsTest(); + + /// Test that it is possible to retrieve and delete orphaned subnet. + void unassignedSubnet6Test(); + + /// Test that subnets modified after given time can be fetched. + void getModifiedSubnets6Test(); + + /// Test that getModifiedSubnets6 throws appropriate exceptions for various + /// server selectors. + void getModifiedSubnets6SelectorsTest(); + + /// Test that lifetimes in subnets are handled as expected. + void subnetLifetimeTest(); + + /// Test that subnets belonging to a shared network can be retrieved. + void getSharedNetworkSubnets6Test(); + + /// Test that pools are properly updated as a result a subnet update. + void subnetUpdatePoolsTest(); + + /// Test that deleting a subnet triggers deletion of the options associated + /// with the subnet and pools. + void subnetOptionsTest(); + + /// Test that shared network can be inserted, fetched, updated and then + /// fetched again. + void getSharedNetwork6Test(); + + /// Test that getSharedNetwork6 throws appropriate exceptions for various + /// server selectors. + void getSharedNetwork6SelectorsTest(); + + /// Test that shared network may be created and updated and the server tags + /// are properly assigned to it. + void createUpdateSharedNetwork6Test(); + + /// Test that createUpdateSharedNetwork6 throws appropriate exceptions for various + /// server selectors. + void createUpdateSharedNetwork6SelectorsTest(); + + /// Test that the information about unspecified optional parameters gets + /// propagated to the database. + void getSharedNetwork6WithOptionalUnspecifiedTest(); + + /// Test that deleteSharedNetworkSubnets6 with not ANY selector throw. + void deleteSharedNetworkSubnets6Test(); + + /// Test that all shared networks can be fetched. + void getAllSharedNetworks6Test(); + + /// Test that getAllSharedNetworks6 throws appropriate exceptions for various + /// server selectors. + void getAllSharedNetworks6SelectorsTest(); + + /// Test that shared networks with different server associations are returned. + void getAllSharedNetworks6WithServerTagsTest(); + + /// Test that shared networks modified after given time can be fetched. + void getModifiedSharedNetworks6Test(); + + /// Test that getModifiedSharedNetworks6 throws appropriate exceptions for various + /// server selectors. + void getModifiedSharedNetworks6SelectorsTest(); + + /// Test that selected shared network can be deleted. + void deleteSharedNetwork6Test(); + + /// Test that deleteSharedNetwork6 throws appropriate exceptions for various + /// server selectors. + void deleteSharedNetwork6SelectorsTest(); + + /// Test that deleteAllSharedNetworks6 throws appropriate exceptions for various + /// server selectors. + void deleteAllSharedNetworks6SelectorsTest(); + + /// Test that it is possible to retrieve and delete orphaned shared network. + void unassignedSharedNetworkTest(); + + /// Test that lifetimes in shared networks are handled as expected. + void sharedNetworkLifetimeTest(); + + /// Test that deleting a shared network triggers deletion of the options + /// associated with the shared network. + void sharedNetworkOptionsTest(); + + /// Test that option definition can be inserted, fetched, updated and then + /// fetched again. + void getOptionDef6Test(); + + /// @brief Thist test verifies that it is possible to differentiate between the + /// option definitions by server tag and that the option definition + /// specified for the particular server overrides the definition for + /// all servers. + void optionDefs6WithServerTagsTest(); + + /// Test that all option definitions can be fetched. + void getAllOptionDefs6Test(); + + /// Test that option definitions modified after given time can be fetched. + void getModifiedOptionDefs6Test(); + + /// @brief Thist test verifies that global option can be added, updated and deleted. + void createUpdateDeleteOption6Test(); + + /// @brief Thist test verifies that it is possible to differentiate between the + /// global options by server tag and that the option specified for the + /// particular server overrides the value specified for all servers. + void globalOptions6WithServerTagsTest(); + + /// @brief Thist test verifies that all global options can be retrieved. + void getAllOptions6Test(); + + /// @brief Thist test verifies that modified global options can be retrieved. + void getModifiedOptions6Test(); + + /// @brief Thist test verifies that subnet level option can be added, updated and + /// deleted. + void createUpdateDeleteSubnetOption6Test(); + + /// @brief Thist test verifies that option can be inserted, updated and deleted + /// from the pool. + void createUpdateDeletePoolOption6Test(); + + /// @brief Thist test verifies that option can be inserted, updated and deleted + /// from the pd pool. + void createUpdateDeletePdPoolOption6Test(); + + /// @brief Thist test verifies that shared network level option can be added, + /// updated and deleted. + void createUpdateDeleteSharedNetworkOption6Test(); + + /// @brief Thist test verifies that option id values in one subnet do + /// not impact options returned in subsequent subnets when + /// fetching subnets from the backend. + void subnetOptionIdOrderTest(); + + /// @brief Thist test verifies that option id values in one shared network do + /// not impact options returned in subsequent shared networks when + /// fetching shared networks from the backend. + void sharedNetworkOptionIdOrderTest(); + + /// @brief Thist test verifies that it is possible to create client classes, update them + /// and retrieve all classes for a given server. + void setAndGetAllClientClasses6Test(); + + /// @brief Thist test verifies that a single class can be retrieved from the database. + void getClientClass6Test(); + + /// @brief Thist test verifies that client class specific DHCP options can be + /// modified during the class update. + void createUpdateClientClass6OptionsTest(); + + /// @brief Thist test verifies that modified client classes can be retrieved from the database. + void getModifiedClientClasses6Test(); + + /// @brief Thist test verifies that a specified client class can be deleted. + void deleteClientClass6Test(); + + /// @brief Thist test verifies that all client classes can be deleted using + /// a specified server selector. + void deleteAllClientClasses6Test(); + + /// @brief Thist test verifies that client class dependencies are tracked when the + /// classes are added to the database. It verifies that an attempt to update + /// a class violating the dependencies results in an error. + void clientClassDependencies6Test(); + + /// @brief Thist test verifies that audit entries can be retrieved from a given + /// timestamp and id including when two entries can get the same timestamp. + /// (either it is a common even and this should catch it, or it is a rare + /// event and it does not matter). + void multipleAuditEntriesTest(); + + /// @brief Holds pointers to subnets used in tests. + std::vector test_subnets_; + + /// @brief Holds pointers to shared networks used in tests. + std::vector test_networks_; + + /// @brief Holds pointers to option definitions used in tests. + std::vector test_option_defs_; + + /// @brief Holds pointers to options used in tests. + std::vector test_options_; + + /// @brief Holds pointers to classes used in tests. + std::vector test_client_classes_; + + /// @brief Holds pointers to the servers used in tests. + std::vector test_servers_; + + /// @brief Holds timestamp values used in tests. + std::map timestamps_; + + /// @brief Holds pointer to the backend. + boost::shared_ptr cbptr_; + + /// @brief Holds the most recent audit entries. + std::map audit_entries_; +}; + +} // namespace test +} // namespace dhcp +} // namespace isc + +#endif // GENERIC_CONFIG_BACKEND_DHCP6_H