From: Marcin Siodelski Date: Thu, 4 Jul 2019 19:18:16 +0000 (+0200) Subject: [#715] Added support for associating option defs with server tags. X-Git-Tag: Kea-1.6.0-beta2~117 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=007bbb7ee67b97d3b5055f1a7215a4455f6b211a;p=thirdparty%2Fkea.git [#715] Added support for associating option defs with 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 3a57105e5c..201ab51fc0 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc @@ -114,6 +114,7 @@ public: DELETE_ALL_SHARED_NETWORKS4, DELETE_OPTION_DEF4_CODE_NAME, DELETE_ALL_OPTION_DEFS4, + DELETE_ALL_OPTION_DEFS4_UNASSIGNED, DELETE_OPTION4, DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED, DELETE_OPTION4_SUBNET_ID, @@ -1872,6 +1873,18 @@ public: in_bindings)); } + /// @brief Removes unassigned global parameters, global options and + /// option definitions. + /// + /// This function is called when one or more servers are deleted and + /// it is likely that there are some orhpaned configuration elements + /// left in the database. This method removes those elements. + void purgeUnassignedConfig() { + multipleUpdateDeleteQueries(DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED, + DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED, + DELETE_ALL_OPTION_DEFS4_UNASSIGNED); + } + /// @brief Attempts to delete a server having a given tag. /// /// @param server_tag Tag of the server to be deleted. @@ -1904,16 +1917,9 @@ public: in_bindings); // If we have deleted any servers we have to remove any dangling global - // parameters. + // parameters, options and option definitions. if (count > 0) { - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: - DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED, - MySqlBindingCollection()); - - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: - DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED, - MySqlBindingCollection()); - /// @todo delete option definitions. + purgeUnassignedConfig(); } transaction.commit(); @@ -1944,17 +1950,9 @@ public: in_bindings); // If we have deleted any servers we have to remove any dangling global - // parameters. + // parameters, options and option definitions. if (count > 0) { - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: - DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED, - MySqlBindingCollection()); - - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl:: - DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED, - MySqlBindingCollection()); - - /// @todo delete dangling option definitions. + purgeUnassignedConfig(); } transaction.commit(); @@ -2378,6 +2376,11 @@ TaggedStatementArray tagged_statements = { { MYSQL_DELETE_OPTION_DEF(dhcp4) }, + // Delete all option definitions which are assigned to no servers. + { MySqlConfigBackendDHCPv4Impl::DELETE_ALL_OPTION_DEFS4_UNASSIGNED, + MYSQL_DELETE_OPTION_DEF_UNASSIGNED(dhcp4) + }, + // Delete single global option. { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4, MYSQL_DELETE_OPTION(dhcp4, AND o.scope_id = 0 AND o.code = ? AND o.space = ?) diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc index 94a4537d4f..0f1ce65424 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc @@ -120,6 +120,7 @@ public: DELETE_ALL_SHARED_NETWORKS6, DELETE_OPTION_DEF6_CODE_NAME, DELETE_ALL_OPTION_DEFS6, + DELETE_ALL_OPTION_DEFS6_UNASSIGNED, DELETE_OPTION6, DELETE_ALL_GLOBAL_OPTIONS6_UNASSIGNED, DELETE_OPTION6_SUBNET_ID, @@ -2187,6 +2188,18 @@ public: in_bindings)); } + /// @brief Removes unassigned global parameters, global options and + /// option definitions. + /// + /// This function is called when one or more servers are deleted and + /// it is likely that there are some orhpaned configuration elements + /// left in the database. This method removes those elements. + void purgeUnassignedConfig() { + multipleUpdateDeleteQueries(DELETE_ALL_GLOBAL_PARAMETERS6_UNASSIGNED, + DELETE_ALL_GLOBAL_OPTIONS6_UNASSIGNED, + DELETE_ALL_OPTION_DEFS6_UNASSIGNED); + } + /// @brief Attempts to delete a server having a given tag. /// /// @param server_tag Tag of the server to be deleted. @@ -2219,16 +2232,9 @@ public: in_bindings); // If we have deleted any servers we have to remove any dangling global - // parameters. + // parameters, options and option definitions. if (count > 0) { - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: - DELETE_ALL_GLOBAL_PARAMETERS6_UNASSIGNED, - MySqlBindingCollection()); - - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: - DELETE_ALL_GLOBAL_OPTIONS6_UNASSIGNED, - MySqlBindingCollection()); - /// @todo delete dangling option definitions. + purgeUnassignedConfig(); } transaction.commit(); @@ -2259,16 +2265,9 @@ public: in_bindings); // If we have deleted any servers we have to remove any dangling global - // parameters. + // parameters, options and option definitions. if (count > 0) { - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: - DELETE_ALL_GLOBAL_PARAMETERS6_UNASSIGNED, - MySqlBindingCollection()); - - conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: - DELETE_ALL_GLOBAL_OPTIONS6_UNASSIGNED, - MySqlBindingCollection()); - /// @todo delete dangling option definitions. + purgeUnassignedConfig(); } transaction.commit(); @@ -2735,6 +2734,11 @@ TaggedStatementArray tagged_statements = { { MYSQL_DELETE_OPTION_DEF(dhcp6) }, + // Delete all option definitions which are assigned to no servers. + { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_OPTION_DEFS6_UNASSIGNED, + MYSQL_DELETE_OPTION_DEF_UNASSIGNED(dhcp6) + }, + // Delete single global option. { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6, MYSQL_DELETE_OPTION(dhcp6, AND o.scope_id = 0 AND o.code = ? AND o.space = ?) diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc index dfa24124b8..00c18aef04 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc @@ -387,14 +387,16 @@ MySqlConfigBackendImpl::getOptionDefs(const int index, uint64_t last_def_id = 0; + OptionDefContainer local_option_defs; + // Run select query. conn_.selectQuery(index, in_bindings, out_bindings, - [&option_defs, &last_def_id] + [&local_option_defs, &last_def_id] (MySqlBindingCollection& out_bindings) { // Get pointer to last fetched option definition. OptionDefinitionPtr last_def; - if (!option_defs.empty()) { - last_def = *option_defs.rbegin(); + if (!local_option_defs.empty()) { + last_def = *local_option_defs.rbegin(); } // See if the last fetched definition is the one for which we now got @@ -453,14 +455,47 @@ MySqlConfigBackendImpl::getOptionDefs(const int index, last_def->setModificationTime(out_bindings[5]->getTimestamp()); // server_tag - last_def->setServerTag(out_bindings[10]->getString()); + ServerTag last_def_server_tag(out_bindings[10]->getString()); + last_def->setServerTag(last_def_server_tag.get()); + + // If we're fetching option definitions for a given server + // (explicit server tag is provided), it takes precedence over + // the same option definition specified for all servers. + // Therefore, we check if the given option already exists and + // belongs to 'all'. + auto& index = local_option_defs.get<1>(); + auto existing_it_pair = index.equal_range(last_def->getCode()); + auto existing_it = existing_it_pair.first; + bool found = false; + for ( ; existing_it != existing_it_pair.second; ++existing_it) { + if ((*existing_it)->getOptionSpaceName() == last_def->getOptionSpaceName()) { + found = true; + // This option definition was already fetched. Let's check + // if we should replace it or not. + if (!last_def_server_tag.amAll() && (*existing_it)->hasAllServerTag()) { + index.replace(existing_it, last_def); + return; + } + break; + } + } - // Store created option definition. - // (option_defs is a multi-index container with no unique - // indexes so push_back can't fail). - static_cast(option_defs.push_back(last_def)); + // If there is no such option definition yet or the existing option + // definition belongs to a different server and the inserted option + // definition is not for all servers. + if (!found || + (!(*existing_it)->hasServerTag(last_def_server_tag) && + !last_def_server_tag.amAll())) { + static_cast(local_option_defs.push_back(last_def)); + } } }); + + // Append the option definition fetched by this function into the container + // supplied by the caller. The container supplied by the caller may already + // hold some option definitions fetched for other server tags. + option_defs.insert(option_defs.end(), local_option_defs.begin(), + local_option_defs.end()); } void @@ -497,7 +532,10 @@ MySqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_s MySqlBinding::createBool(option_def->getArrayType()), MySqlBinding::createString(option_def->getEncapsulatedSpace()), record_types_binding, - createInputContextBinding(option_def) + createInputContextBinding(option_def), + MySqlBinding::createString(tag), + MySqlBinding::createInteger(option_def->getCode()), + MySqlBinding::createString(option_def->getOptionSpaceName()) }; MySqlTransaction transaction(conn_); @@ -525,15 +563,9 @@ MySqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_s "option definition set", true); - if (existing_definition) { - // Need to add three more bindings for WHERE clause. - in_bindings.push_back(MySqlBinding::createString(tag)); - in_bindings.push_back(MySqlBinding::createInteger(existing_definition->getCode())); - in_bindings.push_back(MySqlBinding::createString(existing_definition->getOptionSpaceName())); - conn_.updateDeleteQuery(update_option_def, in_bindings); - - } else { - // If the option definition doesn't exist, let's insert it. + if (conn_.updateDeleteQuery(update_option_def, in_bindings) == 0) { + // Remove the bindings used only during the update. + in_bindings.resize(in_bindings.size() - 3); conn_.insertQuery(insert_option_def, in_bindings); // Fetch unique identifier of the inserted option definition and use it diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.h b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.h index f37d9de104..e858ce7078 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.h +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.h @@ -25,6 +25,7 @@ #include #include #include +#include namespace isc { namespace dhcp { @@ -631,6 +632,28 @@ public: const int& update_index, const db::ServerPtr& server); + /// @brief Executes multiple update and/or delete queries with no input + /// bindings. + /// + /// This is a convenience function which takes multiple query indexes as + /// arguments and for each index executes an update or delete query. + /// One of the applications of this function is to remove dangling + /// configuration elements after the server associated with these elements + /// have been deleted. + /// + /// @tparam T type of the indexes, e.g. @c MySqlConfigBackendDHCPv4Impl::StatementIndex. + /// @tparam R parameter pack holding indexes of type @c T. + /// @param first_index first index. + /// @param other_indexes remaining indexes. + template + void multipleUpdateDeleteQueries(T first_index, R... other_indexes) { + std::vector indexes({ first_index, other_indexes... }); + db::MySqlBindingCollection empty_bindings; + for (auto index : indexes) { + conn_.updateDeleteQuery(index, empty_bindings); + } + } + /// @brief Returns backend type in the textual format. /// /// @return "mysql". 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 d257f08212..96f24cd74b 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h +++ b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h @@ -657,6 +657,14 @@ namespace { "WHERE s.tag = ? " #__VA_ARGS__ #endif +#ifndef MYSQL_DELETE_OPTION_DEF_UNASSIGNED +#define MYSQL_DELETE_OPTION_DEF_UNASSIGNED(table_prefix, ...) \ + "DELETE d FROM " #table_prefix "_option_def AS d " \ + "LEFT JOIN " #table_prefix "_option_def_server AS a " \ + " ON d.id = a.option_def_id " \ + "WHERE a.option_def_id IS NULL " #__VA_ARGS__ +#endif + #ifndef MYSQL_DELETE_OPTION #define MYSQL_DELETE_OPTION(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 b8060499b1..513279f77c 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 @@ -263,6 +263,10 @@ public: option_def.reset(new OptionDefinition("whale", 236, "string")); option_def->setOptionSpaceName("xyz"); test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("foobar", 234, "uint64", true)); + option_def->setOptionSpaceName("dhcp4"); + test_option_defs_.push_back(option_def); } /// @brief Creates several DHCP options used in tests. @@ -1780,26 +1784,217 @@ TEST_F(MySqlConfigBackendDHCPv4Test, getOptionDef4) { } } +// 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(MySqlConfigBackendDHCPv4Test, optionDefs4WithServerTags) { + 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_->createUpdateOptionDef4(ServerSelector::ONE("server1"), + option1), + 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"); + } + + // This time creation of the option definition for the server1 should pass. + EXPECT_NO_THROW(cbptr_->createUpdateOptionDef4(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("dhcp4_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_->createUpdateOptionDef4(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("dhcp4_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_->createUpdateOptionDef4(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("dhcp4_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_->getOptionDef4(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_->getOptionDef4(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_->getOptionDef4(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_->getAllOptionDefs4(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_->getAllOptionDefs4(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_->deleteServer4(ServerTag("server1"))); + EXPECT_NO_THROW( + returned_option_defs = cbptr_->getAllOptionDefs4(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("dhcp4_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_->deleteOptionDef4(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_->deleteOptionDef4(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_->createUpdateOptionDef4(ServerSelector::ONE("server2"), + option2)); + + // Delete all servers, except 'all'. + EXPECT_NO_THROW(deleted_num = cbptr_->deleteAllServers4()); + EXPECT_NO_THROW( + returned_option_defs = cbptr_->getAllOptionDefs4(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("dhcp4_option_def", + AuditEntry::ModificationType::DELETE, + "deleting all servers", ServerSelector::ONE("server2"), + 4, 1); + } +} + // Test that all option definitions can be fetched. TEST_F(MySqlConfigBackendDHCPv4Test, getAllOptionDefs4) { // 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_->createUpdateOptionDef4(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()); + auto name = option_def->getName(); + if (name.find("bar") != std::string::npos) { + SCOPED_TRACE("UPDATE audit entry for the option definition " + name); testNewAuditEntry("dhcp4_option_def", AuditEntry::ModificationType::UPDATE, "option definition set"); + ++updates_num; } else { - SCOPED_TRACE("CREATE audit entry for the option defnition " + - option_def->getName()); + SCOPED_TRACE("CREATE audit entry for the option defnition " + name); testNewAuditEntry("dhcp4_option_def", AuditEntry::ModificationType::CREATE, "option definition set"); @@ -1808,12 +2003,12 @@ TEST_F(MySqlConfigBackendDHCPv4Test, getAllOptionDefs4) { // Fetch all option_definitions. OptionDefContainer option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL()); - ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size()); + 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_->getAllOptionDefs4(ServerSelector::ONE("server1")); - ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size()); + 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) { @@ -1833,7 +2028,7 @@ TEST_F(MySqlConfigBackendDHCPv4Test, getAllOptionDefs4) { EXPECT_EQ(0, cbptr_->deleteOptionDef4(ServerSelector::ALL(), 99, "non-exiting-space")); // All option definitions should be still there. - ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size()); + 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. 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 f92f3d9ddd..95789f2145 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 @@ -302,6 +302,10 @@ public: option_def.reset(new OptionDefinition("whale", 20236, "string")); option_def->setOptionSpaceName("xyz"); test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("bar", 1234, "uint64", true)); + option_def->setOptionSpaceName("dhcp6"); + test_option_defs_.push_back(option_def); } /// @brief Creates several DHCP options used in tests. @@ -1800,11 +1804,202 @@ TEST_F(MySqlConfigBackendDHCPv6Test, getOptionDef6) { } } +// 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), + 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"); + } + + // 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); @@ -1816,6 +2011,7 @@ TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptionDefs6) { testNewAuditEntry("dhcp6_option_def", AuditEntry::ModificationType::UPDATE, "option definition set"); + ++updates_num; } else { SCOPED_TRACE("CREATE audit entry for the option definition " + @@ -1828,12 +2024,12 @@ TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptionDefs6) { // Fetch all option_definitions. OptionDefContainer option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL()); - ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size()); + 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() - 1, option_defs.size()); + 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) { @@ -1853,7 +2049,7 @@ TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptionDefs6) { EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ALL(), 99, "non-exiting-space")); // All option definitions should be still there. - ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size()); + 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.