From: Marcin Siodelski Date: Wed, 3 Jul 2019 11:42:39 +0000 (+0200) Subject: [#714,!409] Associate global options with the server tags. X-Git-Tag: Kea-1.6.0-beta2~123 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ed7befb61639f9f736cbc5140597f81e28e0850c;p=thirdparty%2Fkea.git [#714,!409] Associate global options with the server tags. --- diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc index 69dfe47c59..8e31d674c9 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc @@ -115,6 +115,7 @@ public: DELETE_OPTION_DEF4_CODE_NAME, DELETE_ALL_OPTION_DEFS4, DELETE_OPTION4, + DELETE_ALL_OPTIONS4_UNASSIGNED, DELETE_OPTION4_SUBNET_ID, DELETE_OPTION4_POOL_RANGE, DELETE_OPTION4_SHARED_NETWORK, @@ -1441,7 +1442,10 @@ public: createInputContextBinding(option), MySqlBinding::createNull(), MySqlBinding::createNull(), - MySqlBinding::createTimestamp(option->getModificationTime()) + MySqlBinding::createTimestamp(option->getModificationTime()), + MySqlBinding::createString(tag), + MySqlBinding::createInteger(option->option_->getType()), + MySqlBinding::condCreateString(option->space_name_) }; MySqlTransaction transaction(conn_); @@ -1456,16 +1460,11 @@ public: MySqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION, server_selector, "global option set", false); - if (existing_option) { - in_bindings.push_back(MySqlBinding::createString(tag)); - in_bindings.push_back(MySqlBinding::createInteger(option->option_->getType())); - in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_)); - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4, - in_bindings); - - } else { + if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4, + in_bindings) == 0) { + // Remove the 3 bindings used only in case of update. + in_bindings.resize(in_bindings.size() - 3); insertOption4(server_selector, in_bindings); - } transaction.commit(); @@ -1931,7 +1930,11 @@ public: conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED, MySqlBindingCollection()); - /// @todo delete dangling options and option definitions. + + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: + DELETE_ALL_OPTIONS4_UNASSIGNED, + MySqlBindingCollection()); + /// @todo delete option definitions. } transaction.commit(); @@ -1967,7 +1970,12 @@ public: conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED, MySqlBindingCollection()); - /// @todo delete dangling options and option definitions. + + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: + DELETE_ALL_OPTIONS4_UNASSIGNED, + MySqlBindingCollection()); + + /// @todo delete dangling option definitions. } transaction.commit(); @@ -2396,6 +2404,11 @@ TaggedStatementArray tagged_statements = { { MYSQL_DELETE_OPTION(dhcp4, AND o.scope_id = 0 AND o.code = ? AND o.space = ?) }, + // Delete all options which are unassigned to any servers. + { MySqlConfigBackendDHCPv4Impl::DELETE_ALL_OPTIONS4_UNASSIGNED, + MYSQL_DELETE_OPTION_UNASSIGNED(dhcp4) + }, + // Delete single option from a subnet. { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SUBNET_ID, MYSQL_DELETE_OPTION(dhcp4, diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc index aeb1fdbce8..8ee6917853 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc @@ -121,6 +121,7 @@ public: DELETE_OPTION_DEF6_CODE_NAME, DELETE_ALL_OPTION_DEFS6, DELETE_OPTION6, + DELETE_ALL_OPTIONS6_UNASSIGNED, DELETE_OPTION6_SUBNET_ID, DELETE_OPTION6_POOL_RANGE, DELETE_OPTION6_PD_POOL, @@ -1653,7 +1654,10 @@ public: MySqlBinding::createNull(), MySqlBinding::createNull(), MySqlBinding::createTimestamp(option->getModificationTime()), - MySqlBinding::createNull() + MySqlBinding::createNull(), + MySqlBinding::createString(tag), + MySqlBinding::createInteger(option->option_->getType()), + MySqlBinding::condCreateString(option->space_name_) }; MySqlTransaction transaction(conn_); @@ -1668,16 +1672,11 @@ public: MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, server_selector, "global option set", false); - if (existing_option) { - in_bindings.push_back(MySqlBinding::createString(tag)); - in_bindings.push_back(MySqlBinding::createInteger(option->option_->getType())); - in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_)); - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6, - in_bindings); - - } else { + if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6, + in_bindings) == 0) { + // Remove the 3 bindings used only in case of update. + in_bindings.resize(in_bindings.size() - 3); insertOption6(server_selector, in_bindings); - } transaction.commit(); @@ -2245,7 +2244,11 @@ public: conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: DELETE_ALL_GLOBAL_PARAMETERS6_UNASSIGNED, MySqlBindingCollection()); - /// @todo delete dangling options and option definitions. + + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: + DELETE_ALL_OPTIONS6_UNASSIGNED, + MySqlBindingCollection()); + /// @todo delete dangling option definitions. } transaction.commit(); @@ -2281,7 +2284,11 @@ public: conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: DELETE_ALL_GLOBAL_PARAMETERS6_UNASSIGNED, MySqlBindingCollection()); - /// @todo delete dangling options and option definitions. + + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: + DELETE_ALL_OPTIONS6_UNASSIGNED, + MySqlBindingCollection()); + /// @todo delete dangling option definitions. } transaction.commit(); @@ -2753,6 +2760,11 @@ TaggedStatementArray tagged_statements = { { MYSQL_DELETE_OPTION(dhcp6, AND o.scope_id = 0 AND o.code = ? AND o.space = ?) }, + // Delete all options which are unassigned to any servers. + { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_OPTIONS6_UNASSIGNED, + MYSQL_DELETE_OPTION_UNASSIGNED(dhcp6) + }, + // Delete single option from a subnet. { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6_SUBNET_ID, MYSQL_DELETE_OPTION(dhcp6, diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc index dd6a4421f1..dfa24124b8 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc @@ -762,8 +762,10 @@ MySqlConfigBackendImpl::getOptions(const int index, uint64_t last_option_id = 0; + OptionContainer local_options; + conn_.selectQuery(index, in_bindings, out_bindings, - [this, universe, &options, &last_option_id] + [this, universe, &local_options, &last_option_id] (MySqlBindingCollection& out_bindings) { // Parse option. if (!out_bindings[0]->amNull() && @@ -774,11 +776,46 @@ MySqlConfigBackendImpl::getOptions(const int index, OptionDescriptorPtr desc = processOptionRow(universe, out_bindings.begin()); if (desc) { // server_tag for the global option - desc->setServerTag(out_bindings[12]->getString()); - static_cast(options.push_back(*desc)); + ServerTag last_option_server_tag(out_bindings[12]->getString()); + desc->setServerTag(last_option_server_tag.get()); + + // If we're fetching options for a given server (explicit server + // tag is provided), it takes precedence over the same option + // specified for all servers. Therefore, we check if the given + // option already exists and belongs to 'all'. + auto& index = local_options.get<1>(); + auto existing_it_pair = index.equal_range(desc->option_->getType()); + auto existing_it = existing_it_pair.first; + bool found = false; + for ( ; existing_it != existing_it_pair.second; ++existing_it) { + if (existing_it->space_name_ == desc->space_name_) { + found = true; + // This option was already fetched. Let's check if we should + // replace it or not. + if (!last_option_server_tag.amAll() && existing_it->hasAllServerTag()) { + index.replace(existing_it, *desc); + return; + } + break; + } + } + + // If there is no such global option yet or the existing option + // belongs to a different server and the inserted option is not + // for all servers. + if (!found || + (!existing_it->hasServerTag(last_option_server_tag) && + !last_option_server_tag.amAll())) { + static_cast(local_options.push_back(*desc)); + } } } }); + + // Append the options fetched by this function into the container supplied + // by the caller. The container supplied by the caller may already hold + // some options fetched for other server tags. + options.insert(options.end(), local_options.begin(), local_options.end()); } OptionDescriptorPtr diff --git a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h index 1f7c9efb23..d257f08212 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h +++ b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h @@ -353,7 +353,7 @@ namespace { "INNER JOIN " #table_prefix "_server AS s" \ " ON a.server_id = s.id " \ "WHERE (s.tag = ? OR s.id = 1) " #__VA_ARGS__ \ - " ORDER BY o.option_id" + " ORDER BY o.option_id, s.id" #define MYSQL_GET_OPTION4(...) \ MYSQL_GET_OPTION_COMMON(dhcp4, "", __VA_ARGS__) @@ -667,6 +667,14 @@ namespace { "WHERE s.tag = ? " #__VA_ARGS__ #endif +#ifndef MYSQL_DELETE_OPTION_UNASSIGNED +#define MYSQL_DELETE_OPTION_UNASSIGNED(table_prefix, ...) \ + "DELETE o FROM " #table_prefix "_options AS o " \ + "LEFT JOIN " #table_prefix "_options_server AS a " \ + " ON o.option_id = a.option_id " \ + "WHERE a.option_id IS NULL " #__VA_ARGS__ +#endif + #ifndef MYSQL_DELETE_OPTION_POOL_RANGE #define MYSQL_DELETE_OPTION_POOL_RANGE(table_prefix, ...) \ "DELETE o FROM " #table_prefix "_options AS o " \ diff --git a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc index 8dca6fe890..b8060499b1 100644 --- a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc +++ b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc @@ -302,6 +302,18 @@ public: desc.space_name_ = "isc"; test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + desc = createOption(Option::V4, DHO_BOOT_FILE_NAME, + true, false, "my-boot-file-2"); + desc.space_name_ = DHCP4_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption(Option::V4, DHO_BOOT_FILE_NAME, + true, false, "my-boot-file-3"); + desc.space_name_ = DHCP4_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + // Add definitions for DHCPv4 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 @@ -1931,8 +1943,9 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteOption4) { opt_boot_file_name); // Retrieve the option again and make sure that updates were - // properly propagated to the database. - returned_opt_boot_file_name = cbptr_->getOption4(ServerSelector::ALL(), + // properly propagated to the database. Use explicit server selector + // which should also return this option. + returned_opt_boot_file_name = cbptr_->getOption4(ServerSelector::ONE("server1"), opt_boot_file_name->option_->getType(), opt_boot_file_name->space_name_); ASSERT_TRUE(returned_opt_boot_file_name); @@ -1971,6 +1984,189 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteOption4) { } } +// 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(MySqlConfigBackendDHCPv4Test, globalOptions4WithServerTags) { + OptionDescriptorPtr opt_boot_file_name1 = test_options_[0]; + OptionDescriptorPtr opt_boot_file_name2 = test_options_[6]; + OptionDescriptorPtr opt_boot_file_name3 = test_options_[7]; + + EXPECT_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"), + opt_boot_file_name1), + DbOperationError); + + // Create two servers. + EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[1])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp4_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[2])); + { + SCOPED_TRACE("server2 is created"); + testNewAuditEntry("dhcp4_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + EXPECT_NO_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"), + opt_boot_file_name1)); + { + 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("dhcp4_options", + AuditEntry::ModificationType::CREATE, + "global option set", + ServerSelector::ONE("server1"), + 3, 1); + + } + + EXPECT_NO_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server2"), + opt_boot_file_name2)); + { + 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("dhcp4_options", + AuditEntry::ModificationType::CREATE, + "global option set", + ServerSelector::ONE("server2"), + 3, 1); + + } + + EXPECT_NO_THROW(cbptr_->createUpdateOption4(ServerSelector::ALL(), + opt_boot_file_name3)); + { + 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("dhcp4_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_->getOption4(ServerSelector::ALL(), + opt_boot_file_name3->option_->getType(), + opt_boot_file_name3->space_name_); + ); + ASSERT_TRUE(returned_option); + testOptionsEquivalent(*opt_boot_file_name3, *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_->getOption4(ServerSelector::ONE("server1"), + opt_boot_file_name1->option_->getType(), + opt_boot_file_name1->space_name_); + ); + ASSERT_TRUE(returned_option); + testOptionsEquivalent(*opt_boot_file_name1, *returned_option); + + // The same in case of the server2. + EXPECT_NO_THROW( + returned_option = cbptr_->getOption4(ServerSelector::ONE("server2"), + opt_boot_file_name2->option_->getType(), + opt_boot_file_name2->space_name_); + ); + ASSERT_TRUE(returned_option); + testOptionsEquivalent(*opt_boot_file_name2, *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_->getAllOptions4(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_boot_file_name1, *current_option); + testOptionsEquivalent(*opt_boot_file_name2, *(++current_option)); + testOptionsEquivalent(*opt_boot_file_name3, *(++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_->getAllOptions4(ServerSelector::ALL()); + ); + ASSERT_EQ(1, returned_options.size()); + testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin()); + + // Delete the server1. It should remove associations of this server with the + // option and the option itself. + EXPECT_NO_THROW(cbptr_->deleteServer4(ServerTag("server1"))); + EXPECT_NO_THROW( + returned_options = cbptr_->getAllOptions4(ServerSelector::ONE("server1")); + ); + ASSERT_EQ(1, returned_options.size()); + testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin()); + + { + SCOPED_TRACE("DELETE audit entry for the global option after server deletion"); + testNewAuditEntry("dhcp4_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_->deleteOption4(ServerSelector::ONE("server1"), + opt_boot_file_name1->option_->getType(), + opt_boot_file_name1->space_name_)); + EXPECT_EQ(0, deleted_num); + + // Deleting the existing option for server2 should succeed. + EXPECT_NO_THROW(deleted_num = cbptr_->deleteOption4(ServerSelector::ONE("server2"), + opt_boot_file_name2->option_->getType(), + opt_boot_file_name2->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_->createUpdateOption4(ServerSelector::ONE("server2"), + opt_boot_file_name2)); + + // Delete all servers, except 'all'. + EXPECT_NO_THROW(deleted_num = cbptr_->deleteAllServers4()); + EXPECT_NO_THROW( + returned_options = cbptr_->getAllOptions4(ServerSelector::ALL()); + ); + EXPECT_EQ(1, deleted_num); + ASSERT_EQ(1, returned_options.size()); + testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin()); + + { + SCOPED_TRACE("DELETE audit entry for the global option after deletion of" + " all servers"); + testNewAuditEntry("dhcp4_options", + AuditEntry::ModificationType::DELETE, + "deleting all servers", ServerSelector::ONE("server2"), + 4, 1); + } +} + // This test verifies that all global options can be retrieved. TEST_F(MySqlConfigBackendDHCPv4Test, getAllOptions4) { // Add three global options to the database. 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 b788ae8990..f92f3d9ddd 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 @@ -343,6 +343,18 @@ public: 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 @@ -1953,8 +1965,9 @@ TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteOption6) { opt_posix_timezone); // Retrieve the option again and make sure that updates were - // properly propagated to the database. - returned_opt_posix_timezone = cbptr_->getOption6(ServerSelector::ALL(), + // 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); @@ -1994,6 +2007,189 @@ TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteOption6) { } } +// 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), + DbOperationError); + + // 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.