]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1428] Delete multiple hosts by subnet/addr
authorMarcin Siodelski <marcin@isc.org>
Wed, 30 Sep 2020 16:50:56 +0000 (18:50 +0200)
committerMarcin Siodelski <marcin@isc.org>
Mon, 5 Oct 2020 13:14:57 +0000 (13:14 +0000)
Adjusted behavior of the del(subnet_id, addr) function in MySQL and
Postgres to delete multiple hosts having the same IPv4 or IPv6 address.
The MySQL schema had to be updated to use CASCADE action rather than
trigger to remove dependent options and IPv6 reservations.

src/lib/dhcpsrv/base_host_data_source.h
src/lib/dhcpsrv/cfg_hosts.h
src/lib/dhcpsrv/cql_host_data_source.h
src/lib/dhcpsrv/host_mgr.h
src/lib/dhcpsrv/mysql_host_data_source.cc
src/lib/dhcpsrv/mysql_host_data_source.h
src/lib/dhcpsrv/pgsql_host_data_source.cc
src/lib/dhcpsrv/pgsql_host_data_source.h
src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc
src/share/database/scripts/mysql/dhcpdb_create.mysql
src/share/database/scripts/mysql/upgrade_9.3_to_9.4.sh.in

index 7b37fde2c7f930cc5ca3c9e0d3b38022d8bef631..f0cec9028148b0b0cddd3d80130d0dc9da730731 100644 (file)
@@ -412,7 +412,7 @@ public:
     /// @param host Pointer to the new @c Host object being added.
     virtual void add(const HostPtr& host) = 0;
 
-    /// @brief Attempts to delete a host by (subnet-id, address)
+    /// @brief Attempts to delete hosts by (subnet-id, address)
     ///
     /// This method supports both v4 and v6.
     ///
index d3f136d339e4236ecc0d94cde588cc5b3bfba22f..75926e36e9b8a8446a611b89a1f679c4daec31e2 100644 (file)
@@ -536,7 +536,7 @@ public:
     /// has already been added to the IPv4 or IPv6 subnet.
     virtual void add(const HostPtr& host);
 
-    /// @brief Attempts to delete a host by address.
+    /// @brief Attempts to delete a hosts by address.
     ///
     /// This method supports both v4 and v6.
     /// @todo: Not implemented.
index 0b583008980644fb5e058a61fc63bec700f1ac85..7d47eb3f2d3c8c4188ddf59f83dbad632ad8ce6f 100644 (file)
@@ -105,7 +105,7 @@ public:
     /// @param host pointer to the new @ref Host being added.
     virtual void add(const HostPtr& host) override;
 
-    /// @brief Attempts to delete a host by (subnet-id, address)
+    /// @brief Attempts to delete hosts by (subnet-id, address)
     ///
     /// This method supports both v4 and v6.
     ///
index 3c61cb228d4afebff856cc75891176e2a11a77d6..602016778ccdb54a7af418e2475297671c3fbc2c 100644 (file)
@@ -498,7 +498,7 @@ public:
     /// @param host Pointer to the new @c Host object being added.
     virtual void add(const HostPtr& host);
 
-    /// @brief Attempts to delete a host by address.
+    /// @brief Attempts to delete hosts by address.
     ///
     /// This method supports both v4 and v6.
     ///
index 0eb73c8ea426f8ca15ec783d45772072bf3f7057..efa9ada55fd8411c3deee2921e83a71aac1d7893 100644 (file)
@@ -2037,6 +2037,7 @@ public:
         INSERT_V4_HOST_OPTION,     // Insert DHCPv4 option
         INSERT_V6_HOST_OPTION,     // Insert DHCPv6 option
         DEL_HOST_ADDR4,            // Delete v4 host (subnet-id, addr4)
+        DEL_HOST_ADDR6,            // Delete v6 host (subnet-id, addr6)
         DEL_HOST_SUBID4_ID,        // Delete v4 host (subnet-id, ident.type, identifier)
         DEL_HOST_SUBID6_ID,        // Delete v6 host (subnet-id, ident.type, identifier)
         NUM_STATEMENTS             // Number of statements
@@ -2659,10 +2660,17 @@ TaggedStatementArray tagged_statements = { {
                 "persistent, user_context, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
             "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
 
-    // Delete a single IPv4 reservation by subnet id and reserved address.
+    // Delete IPv4 reservations by subnet id and reserved address.
     {MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
             "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
 
+    // Delete IPv6 reservations by subnet id and reserved address/prefix.
+    {MySqlHostDataSourceImpl::DEL_HOST_ADDR6,
+            "DELETE h FROM hosts AS h "
+            "INNER JOIN ipv6_reservations AS r "
+                "ON h.host_id = r.host_id "
+            "WHERE h.dhcp6_subnet_id = ? AND r.address = ?"},
+
     // Delete a single IPv4 reservation by subnet id and identifier.
     {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
             "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? "
@@ -3088,34 +3096,35 @@ MySqlHostDataSource::del(const SubnetID& subnet_id,
     // If operating in read-only mode, throw exception.
     impl_->checkReadOnly(ctx);
 
-    if (addr.isV4()) {
-        // Set up the WHERE clause value
-        MYSQL_BIND inbind[2];
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[2];
 
-        uint32_t subnet = subnet_id;
-        memset(inbind, 0, sizeof(inbind));
-        inbind[0].buffer_type = MYSQL_TYPE_LONG;
-        inbind[0].buffer = reinterpret_cast<char*>(&subnet);
-        inbind[0].is_unsigned = MLM_TRUE;
+    uint32_t subnet = subnet_id;
+    memset(inbind, 0, sizeof(inbind));
+    inbind[0].buffer_type = MYSQL_TYPE_LONG;
+    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+    inbind[0].is_unsigned = MLM_TRUE;
 
+    // v4
+    if (addr.isV4()) {
         uint32_t addr4 = addr.toUint32();
         inbind[1].buffer_type = MYSQL_TYPE_LONG;
         inbind[1].buffer = reinterpret_cast<char*>(&addr4);
         inbind[1].is_unsigned = MLM_TRUE;
 
-        ConstHostCollection collection;
         return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind));
     }
 
     // v6
-    ConstHostPtr host = get6(subnet_id, addr);
-    if (!host) {
-        return (false);
-    }
+    std::string addr_str = addr.toText();
+    unsigned long addr_len = addr_str.size();
+    inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+    inbind[1].buffer = reinterpret_cast<char*>
+                        (const_cast<char*>(addr_str.c_str()));
+    inbind[1].length = &addr_len;
+    inbind[1].buffer_length = addr_len;
 
-    // Ok, there is a host. Let's delete it.
-    return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
-                host->getIdentifier().size());
+    return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR6, inbind));
 }
 
 bool
index 6823e5a939bc5d9015b8adddc15b4af666534a22..bbb5584e3f1bcfd4e6542f66d33ef39f19714d4e 100644 (file)
@@ -81,7 +81,7 @@ public:
     /// @param host Pointer to the new @c Host object being added.
     virtual void add(const HostPtr& host);
 
-    /// @brief Attempts to delete a host by (subnet-id, address)
+    /// @brief Attempts to delete hosts by (subnet-id, address)
     ///
     /// This method supports both v4 and v6.
     ///
index 135d882cfdfad4c91d58fbb253d2f94255b99240..e6d825257b0ccb722690b6870181665cc84e83e6 100644 (file)
@@ -1399,6 +1399,7 @@ public:
         INSERT_V4_HOST_OPTION,     // Insert DHCPv4 option
         INSERT_V6_HOST_OPTION,     // Insert DHCPv6 option
         DEL_HOST_ADDR4,            // Delete v4 host (subnet-id, addr4)
+        DEL_HOST_ADDR6,            // Delete v6 host (subnet-id, addr6)
         DEL_HOST_SUBID4_ID,        // Delete v4 host (subnet-id, ident.type, identifier)
         DEL_HOST_SUBID6_ID,        // Delete v6 host (subnet-id, ident.type, identifier)
         NUM_STATEMENTS             // Number of statements
@@ -2090,6 +2091,15 @@ TaggedStatementArray tagged_statements = { {
      "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2"
     },
 
+    // PgSqlHostDataSourceImpl::DEL_HOST_ADDR6
+    // Deletes a v6 host that matches (subnet-id, addr6)
+    {2,
+     { OID_INT8, OID_VARCHAR },
+     "del_host_addr6",
+     "DELETE FROM hosts USING ipv6_reservations "
+     "  WHERE dhcp6_subnet_id = $1 AND ipv6_reservations.address = $2"
+    },
+
     // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID
     // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier)
     {3,
@@ -2490,21 +2500,21 @@ PgSqlHostDataSource::del(const SubnetID& subnet_id,
     // If operating in read-only mode, throw exception.
     impl_->checkReadOnly(ctx);
 
+    PsqlBindArrayPtr bind_array(new PsqlBindArray());
+    bind_array->add(subnet_id);
+
+    // v4
     if (addr.isV4()) {
-        PsqlBindArrayPtr bind_array(new PsqlBindArray());
-        bind_array->add(subnet_id);
         bind_array->add(addr);
         return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR4,
                                     bind_array));
     }
 
-    ConstHostPtr host = get6(subnet_id, addr);
-    if (!host) {
-        return (false);
-    }
+    // v6
+    bind_array->add(addr.toText());
 
-    return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
-                host->getIdentifier().size());
+    return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR6,
+                                bind_array));
 }
 
 bool
index d9d8414c4f7227bf6a972b08a85223e38d779745..51b65ad0024c3c4861308399a152ce7ede20514e 100644 (file)
@@ -108,7 +108,7 @@ public:
     /// violation
     virtual void add(const HostPtr& host);
 
-    /// @brief Attempts to delete a host by (subnet-id, address)
+    /// @brief Attempts to delete hosts by (subnet-id, address)
     ///
     /// This method supports both v4 and v6.
     ///
index 9df193692ae631f303254fdc83249dfd0accce98..404e742ea654d10b9239bb4cb421d30483e8c484 100644 (file)
@@ -1859,6 +1859,13 @@ GenericHostDataSourceTest::testAllowDuplicateIPv6() {
     ASSERT_NO_THROW(returned = hdsptr_->getAll6(host->getIPv6SubnetID(), IOAddress("2001:db8::1")));
     EXPECT_EQ(2, returned.size());
     EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+    // Let's now try to delete the hosts by subnet_id and address.
+    bool deleted = false;
+    ASSERT_NO_THROW(deleted = hdsptr_->del(subnet_id, IOAddress("2001:db8::1")));
+    ASSERT_TRUE(deleted);
+    ASSERT_NO_THROW(returned = hdsptr_->getAll6(host->getIPv6SubnetID(), IOAddress("2001:db8::1")));
+    EXPECT_TRUE(returned.empty());
 }
 
 void
@@ -1933,6 +1940,13 @@ GenericHostDataSourceTest::testAllowDuplicateIPv4() {
     ASSERT_NO_THROW(returned = hdsptr_->getAll4(host->getIPv4SubnetID(), IOAddress("192.0.2.1")));
     EXPECT_EQ(2, returned.size());
     EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+    // Let's now try to delete the hosts by subnet_id and address.
+    bool deleted = false;
+    ASSERT_NO_THROW(deleted = hdsptr_->del(subnet_id, IOAddress("192.0.2.1")));
+    ASSERT_TRUE(deleted);
+    ASSERT_NO_THROW(returned = hdsptr_->getAll4(host->getIPv4SubnetID(), IOAddress("192.0.2.1")));
+    EXPECT_TRUE(returned.empty());
 }
 
 void
index b49c659a492f0632ffe96544414c79cdc81ee408..e443fee952c9e3c3eedc8805875820279fc1649c 100644 (file)
@@ -2999,6 +2999,28 @@ DROP INDEX key_dhcp6_address_prefix_len ON ipv6_reservations;
 CREATE INDEX key_dhcp6_address_prefix_len
     ON ipv6_reservations (address ASC, prefix_len ASC);
 
+# Stop using a trigger to delete entries dependent on hosts table.
+# Use cascade action instead. This works better with complex delete
+# statements.
+DROP TRIGGER IF EXISTS host_BDEL;
+
+# Replace existing constraint to set cascade actions.
+ALTER TABLE ipv6_reservations DROP FOREIGN KEY fk_ipv6_reservations_Host;
+ALTER TABLE ipv6_reservations ADD CONSTRAINT fk_ipv6_reservations_Host
+    FOREIGN KEY (host_id)
+        REFERENCES hosts(host_id)
+        ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_Host
+    FOREIGN KEY (host_id)
+        REFERENCES hosts(host_id)
+        ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_Host
+    FOREIGN KEY (host_id)
+        REFERENCES hosts(host_id)
+        ON DELETE CASCADE ON UPDATE CASCADE;
+
 # Update the schema version number
 UPDATE schema_version
 SET version = '9', minor = '4';
index 26b133ed23ecac059acfdf49d28dc04fcafb1a6e..0e492496c7ad3df5435297a3ffa2ce7135b55331 100644 (file)
@@ -36,6 +36,28 @@ DROP INDEX key_dhcp6_address_prefix_len ON ipv6_reservations;
 CREATE INDEX key_dhcp6_address_prefix_len
     ON ipv6_reservations (address ASC, prefix_len ASC);
 
+# Stop using a trigger to delete entries dependent on hosts table.
+# Use cascade action instead. This works better with complex delete
+# statements.
+DROP TRIGGER IF EXISTS host_BDEL;
+
+# Replace existing constraint to set cascade actions.
+ALTER TABLE ipv6_reservations DROP FOREIGN KEY fk_ipv6_reservations_Host;
+ALTER TABLE ipv6_reservations ADD CONSTRAINT fk_ipv6_reservations_Host
+    FOREIGN KEY (host_id)
+        REFERENCES hosts(host_id)
+        ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_Host
+    FOREIGN KEY (host_id)
+        REFERENCES hosts(host_id)
+        ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_Host
+    FOREIGN KEY (host_id)
+        REFERENCES hosts(host_id)
+        ON DELETE CASCADE ON UPDATE CASCADE;
+
 # Update the schema version number
 UPDATE schema_version
 SET version = '9', minor = '4';