]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#680,!426] Deleted embedded DHCPv6 options when subnet is deleted.
authorMarcin Siodelski <marcin@isc.org>
Mon, 22 Jul 2019 11:35:58 +0000 (13:35 +0200)
committerMarcin Siodelski <marcin@isc.org>
Thu, 25 Jul 2019 07:58:11 +0000 (03:58 -0400)
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc
src/lib/dhcpsrv/testutils/Makefile.am
src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h [new file with mode: 0644]
src/share/database/scripts/mysql/dhcpdb_create.mysql

index 6b8fdfde183422eef9d61e00b53e7ae4d2741ad7..b62a9749761b145e521a8b1d71c706d67050d410 100644 (file)
@@ -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<MySqlConfigBackendDHCPv6Impl>(base_impl_);
 }
 
 Subnet6Ptr
index 42d340e0991e381e0bb57a50818ce4265c1664bb..1c97647f61a24a0382fc171344ad028580440e94 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef MYSQL_CONFIG_BACKEND_DHCP6_H
 #define MYSQL_CONFIG_BACKEND_DHCP6_H
 
+#include <mysql_cb_impl.h>
 #include <database/database_connection.h>
 #include <dhcpsrv/config_backend_dhcp6.h>
 #include <mysql_cb_log.h>
@@ -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<MySqlConfigBackendImpl> base_impl_;
 
     /// @brief Pointer to the implementation of the @c MySqlConfigBackendDHCPv6
     /// class.
index 531252174e8bbafade39fef0f918676cdc010e19..bd4de446da36bab6ae1bf6af7ca1f5ada9129de8 100644 (file)
 #include <dhcp/option_string.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
-#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <dhcpsrv/testutils/mysql_generic_backend_unittest.h>
 #include <mysql/testutils/mysql_schema.h>
 #include <boost/shared_ptr.hpp>
-#include <mysql.h>
 #include <gtest/gtest.h>
+#include <mysql.h>
 #include <map>
 #include <sstream>
 
@@ -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<MySqlConfigBackendImpl>(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<unsigned>(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"));
index 6575297a9e0ffcbd9556123764bce0f9f317e6d0..9a0e26c246f531e7cca15aaf8466ca8fac56da15 100644 (file)
 #include <dhcp/option_string.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
-#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <dhcpsrv/testutils/mysql_generic_backend_unittest.h>
 #include <mysql/testutils/mysql_schema.h>
 #include <boost/shared_ptr.hpp>
 #include <gtest/gtest.h>
+#include <mysql.h>
 #include <map>
 #include <sstream>
 
@@ -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<TestMySqlConfigBackendDHCPv6>(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<MySqlConfigBackendImpl>(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) {
index 56ebaa2779e95939c3947ea4c2115c0488dc3270..aab2f38e6e266c06061b75730a109f0b6b68d22c 100644 (file)
@@ -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 (file)
index 0000000..b45aa7e
--- /dev/null
@@ -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 <config.h>
+#include <dhcpsrv/testutils/mysql_generic_backend_unittest.h>
+
+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<unsigned>(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 (file)
index 0000000..5e4d7a4
--- /dev/null
@@ -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 <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <mysql/mysql_connection.h>
+
+
+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
index 253f2f45c704739d35eadc22c40667c93839871c..599fdbd3785305282bd49da0a52bcf3f98c356fc 100644 (file)
@@ -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