From: Marcin Siodelski Date: Mon, 22 Jul 2019 11:35:58 +0000 (+0200) Subject: [#680,!426] Deleted embedded DHCPv6 options when subnet is deleted. X-Git-Tag: Kea-1.6.1~10^2~120 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e88273f6deb9fdbf6adf6215066e0c14bf6b88a5;p=thirdparty%2Fkea.git [#680,!426] Deleted embedded DHCPv6 options when subnet is deleted. --- diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc index 6b8fdfde18..b62a974976 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc @@ -2966,7 +2966,8 @@ MySqlConfigBackendDHCPv6Impl::MySqlConfigBackendDHCPv6Impl(const DatabaseConnect } MySqlConfigBackendDHCPv6::MySqlConfigBackendDHCPv6(const DatabaseConnection::ParameterMap& parameters) - : impl_(new MySqlConfigBackendDHCPv6Impl(parameters)) { + : base_impl_(new MySqlConfigBackendDHCPv6Impl(parameters)), impl_() { + impl_ = boost::dynamic_pointer_cast(base_impl_); } Subnet6Ptr diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h index 42d340e099..1c97647f61 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h @@ -7,6 +7,7 @@ #ifndef MYSQL_CONFIG_BACKEND_DHCP6_H #define MYSQL_CONFIG_BACKEND_DHCP6_H +#include #include #include #include @@ -571,7 +572,11 @@ public: /// This should be called by the hook lib unload() function. static void unregisterBackendType(); -private: +protected: + + /// @brief Pointer to the base implementation of the backend shared by + /// DHCPv4 and DHCPv6 servers. + boost::shared_ptr base_impl_; /// @brief Pointer to the implementation of the @c MySqlConfigBackendDHCPv6 /// class. 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 531252174e..bd4de446da 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 @@ -18,11 +18,11 @@ #include #include #include -#include +#include #include #include -#include #include +#include #include #include @@ -63,7 +63,7 @@ public: /// server tags. We will have to expand existing tests when /// the API is extended allowing for inserting servers to the /// database. -class MySqlConfigBackendDHCPv4Test : public GenericBackendTest { +class MySqlConfigBackendDHCPv4Test : public MySqlGenericBackendTest { public: /// @brief Constructor. @@ -126,20 +126,7 @@ public: auto impl = boost::dynamic_pointer_cast(p->base_impl_); auto& conn = impl->conn_; - // Execute a simple select query on all rows. - std::string query = "SELECT * FROM " + table; - auto status = mysql_query(conn.mysql_, query.c_str()); - if (status != 0) { - ADD_FAILURE() << "Query failed: " << mysql_error(conn.mysql_); - return (0); - } - - // Get the number of rows returned and free the result. - MYSQL_RES * res = mysql_store_result(conn.mysql_); - unsigned numrows = static_cast(mysql_num_rows(res)); - mysql_free_result(res); - - return (numrows); + return (MySqlGenericBackendTest::countRows(conn, table)); } /// @brief Creates several servers used in tests. @@ -1974,11 +1961,15 @@ TEST_F(MySqlConfigBackendDHCPv4Test, getSharedNetworkSubnets4) { EXPECT_TRUE(isEquivalent(returned_list, test_list)); } +// Test that deleting a subnet triggers deletion of the options associated +// with the subnet and pools. TEST_F(MySqlConfigBackendDHCPv4Test, subnetOptions) { EXPECT_NO_THROW(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0])); + EXPECT_EQ(2, countRows("dhcp4_pool")); EXPECT_EQ(3, countRows("dhcp4_options")); EXPECT_NO_THROW(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[1])); + EXPECT_EQ(2, countRows("dhcp4_pool")); EXPECT_EQ(2, countRows("dhcp4_options")); EXPECT_NO_THROW(cbptr_->deleteSubnet4(ServerSelector::ALL(), test_subnets_[1]->getID())); @@ -1988,6 +1979,7 @@ TEST_F(MySqlConfigBackendDHCPv4Test, subnetOptions) { EXPECT_NO_THROW(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0])); EXPECT_EQ(3, countRows("dhcp4_options")); + EXPECT_EQ(2, countRows("dhcp4_pool")); EXPECT_NO_THROW(cbptr_->deleteSubnet4(ServerSelector::ALL(), test_subnets_[0]->getID())); EXPECT_EQ(0, countRows("dhcp4_subnet")); 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 6575297a9e..9a0e26c246 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 @@ -17,10 +17,11 @@ #include #include #include -#include +#include #include #include #include +#include #include #include @@ -33,6 +34,24 @@ using namespace isc::dhcp::test; namespace { +/// @brief Test implementation of the MySQL configuration backend. +/// +/// It exposes protected members of the @c MySqlConfigBackendDHCPv6. +class TestMySqlConfigBackendDHCPv6 : public MySqlConfigBackendDHCPv6 { +public: + + /// @brief Constructor. + /// + /// @param parameters A data structure relating keywords and values + /// concerned with the database. + explicit TestMySqlConfigBackendDHCPv6(const DatabaseConnection::ParameterMap& parameters) + : MySqlConfigBackendDHCPv6(parameters) { + } + + using MySqlConfigBackendDHCPv6::base_impl_; + +}; + /// @brief Test fixture class for @c MySqlConfigBackendDHCPv6. /// /// @todo The tests we're providing here only test cases when the @@ -43,7 +62,7 @@ namespace { /// server tags. We will have to expand existing tests when /// the API is extended allowing for inserting servers to the /// database. -class MySqlConfigBackendDHCPv6Test : public GenericBackendTest { +class MySqlConfigBackendDHCPv6Test : public MySqlGenericBackendTest { public: /// @brief Constructor. @@ -58,7 +77,7 @@ public: // Create MySQL connection and use it to start the backend. DatabaseConnection::ParameterMap params = DatabaseConnection::parse(validMySQLConnectionString()); - cbptr_.reset(new MySqlConfigBackendDHCPv6(params)); + cbptr_.reset(new TestMySqlConfigBackendDHCPv6(params)); } catch (...) { std::cerr << "*** ERROR: unable to open database. The test\n" @@ -85,6 +104,30 @@ public: destroyMySQLSchema(); } + /// @brief Counts rows in a selected table in MySQL 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. + size_t countRows(const std::string& table) const { + auto p = boost::dynamic_pointer_cast(cbptr_); + if (!p) { + ADD_FAILURE() << "cbptr_ does not cast to TestMySqlConfigBackendDHCPv6"; + return (0); + } + + // Reuse the existing connection of the backend. + auto impl = boost::dynamic_pointer_cast(p->base_impl_); + auto& conn = impl->conn_; + + 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")); @@ -1930,6 +1973,36 @@ TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetworkSubnets6) { EXPECT_TRUE(isEquivalent(returned_list, test_list)); } +// Test that deleting a subnet triggers deletion of the options associated +// with the subnet and pools. +TEST_F(MySqlConfigBackendDHCPv6Test, subnetOptions) { + 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")); + + 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")); + + EXPECT_NO_THROW(cbptr_->deleteSubnet6(ServerSelector::ALL(), test_subnets_[1]->getID())); + EXPECT_EQ(0, countRows("dhcp6_subnet")); + EXPECT_EQ(0, countRows("dhcp6_pool")); + EXPECT_EQ(0, countRows("dhcp6_pd_pool")); + EXPECT_EQ(0, countRows("dhcp6_options")); + + EXPECT_NO_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0])); + EXPECT_EQ(2, countRows("dhcp6_pool")); + EXPECT_EQ(3, countRows("dhcp6_options")); + + EXPECT_NO_THROW(cbptr_->deleteSubnet6(ServerSelector::ALL(), test_subnets_[0]->getID())); + EXPECT_EQ(0, countRows("dhcp6_subnet")); + EXPECT_EQ(0, countRows("dhcp6_pool")); + EXPECT_EQ(0, countRows("dhcp6_pd_pool")); + EXPECT_EQ(0, countRows("dhcp6_options")); +} + // Test that shared network can be inserted, fetched, updated and then // fetched again. TEST_F(MySqlConfigBackendDHCPv6Test, getSharedNetwork6) { diff --git a/src/lib/dhcpsrv/testutils/Makefile.am b/src/lib/dhcpsrv/testutils/Makefile.am index 56ebaa2779..aab2f38e6e 100644 --- a/src/lib/dhcpsrv/testutils/Makefile.am +++ b/src/lib/dhcpsrv/testutils/Makefile.am @@ -19,6 +19,7 @@ libdhcpsrvtest_la_SOURCES += memory_host_data_source.cc memory_host_data_source. 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 += lease_file_io.cc lease_file_io.h +libdhcpsrvtest_la_SOURCES += mysql_generic_backend_unittest.cc mysql_generic_backend_unittest.h libdhcpsrvtest_la_SOURCES += test_config_backend.h libdhcpsrvtest_la_SOURCES += test_config_backend_dhcp4.cc test_config_backend_dhcp4.h libdhcpsrvtest_la_SOURCES += test_config_backend_dhcp6.cc test_config_backend_dhcp6.h diff --git a/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc new file mode 100644 index 0000000000..b45aa7e1ea --- /dev/null +++ b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc @@ -0,0 +1,41 @@ +// Copyright (C) 2019 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 + +using namespace isc::db; + +namespace isc { +namespace dhcp { +namespace test { + +MySqlGenericBackendTest::MySqlGenericBackendTest() + : GenericBackendTest() { +} + +size_t +MySqlGenericBackendTest::countRows(MySqlConnection& conn, const std::string& table) const { + // Execute a simple select query on all rows. + std::string query = "SELECT * FROM " + table; + auto status = mysql_query(conn.mysql_, query.c_str()); + if (status != 0) { + ADD_FAILURE() << "Query failed: " << mysql_error(conn.mysql_); + return (0); + } + + // Get the number of rows returned and free the result. + MYSQL_RES * res = mysql_store_result(conn.mysql_); + unsigned numrows = static_cast(mysql_num_rows(res)); + mysql_free_result(res); + + return (numrows); +} + + +} // end of namespace isc::dhcp::test +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h new file mode 100644 index 0000000000..5e4d7a4d71 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h @@ -0,0 +1,45 @@ +// Copyright (C) 2018-2019 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 MYSQL_GENERIC_BACKEND_UNITTEST_H +#define MYSQL_GENERIC_BACKEND_UNITTEST_H + +#include +#include + + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Generic test fixture class with utility functions for testing +/// MySQL database backends. +class MySqlGenericBackendTest : public GenericBackendTest { +public: + + /// @brief Constructor. + MySqlGenericBackendTest(); + + /// @brief Counts rows in a selected table in MySQL database. + /// + /// One of the common applications of this method is to check whether the + /// expected number of rows were deleted from the specified table. In + /// relational databases, a deletion of a raw in one table causes deletion of + /// rows in other tables, e.g. via cascaded delete or triggers. This method + /// can be used to verify that the deletion took place in the dependent + /// tables. + /// + /// @param conn MySql connection to be used for the query. + /// @param table Table name. + /// @return Number of rows in the specified table. + size_t countRows(db::MySqlConnection& conn, const std::string& table) const; +}; + +} +} +} + +#endif diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql index 253f2f45c7..599fdbd378 100644 --- a/src/share/database/scripts/mysql/dhcpdb_create.mysql +++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql @@ -2404,6 +2404,51 @@ CREATE TRIGGER dhcp4_subnet_BDEL BEFORE DELETE ON dhcp4_subnet END $$ DELIMITER ; +# Do not perform cascade deletion of the data in the dhcp6_pool and dhcp6_pd_pool +# because the cascaded deletion does not execute triggers associated with the table. +# Instead we are going to use triggers on the dhcp6_subnet table. +ALTER TABLE dhcp6_pool + DROP FOREIGN KEY fk_dhcp6_pool_subnet_id; + +ALTER TABLE dhcp6_pd_pool + DROP FOREIGN KEY fk_dhcp6_pd_pool_subnet_id; + +ALTER TABLE dhcp6_pool + ADD CONSTRAINT fk_dhcp6_pool_subnet_id FOREIGN KEY (subnet_id) + REFERENCES dhcp6_subnet (subnet_id) + ON DELETE NO ACTION ON UPDATE CASCADE; + +ALTER TABLE dhcp6_pd_pool + ADD CONSTRAINT fk_dhcp6_pd_pool_subnet_id FOREIGN KEY (subnet_id) + REFERENCES dhcp6_subnet (subnet_id) + ON DELETE NO ACTION ON UPDATE CASCADE; + +# Create trigger which removes pool specific options upon removal of +# the pool. +DELIMITER $$ +CREATE TRIGGER dhcp6_pd_pool_BDEL BEFORE DELETE ON dhcp6_pd_pool FOR EACH ROW +BEGIN +DELETE FROM dhcp6_options WHERE scope_id = 6 AND pd_pool_id = OLD.id; +END +$$ +DELIMITER ; + +# Drop existing trigger on the dhcp6_subnet table. +DROP TRIGGER dhcp6_subnet_ADEL; + +# Create new trigger which will delete pools associated with the subnet and +# the options associated with the subnet. +DELIMITER $$ +CREATE TRIGGER dhcp6_subnet_BDEL BEFORE DELETE ON dhcp6_subnet + FOR EACH ROW + BEGIN + CALL createAuditEntryDHCP6('dhcp6_subnet', OLD.subnet_id, "delete"); + DELETE FROM dhcp6_pool WHERE subnet_id = OLD.subnet_id; + DELETE FROM dhcp6_pd_pool WHERE subnet_id = OLD.subnet_id; + DELETE FROM dhcp6_options WHERE dhcp6_subnet_id = OLD.subnet_id; + END $$ +DELIMITER ; + # Update the schema version number UPDATE schema_version