From: Marcin Siodelski Date: Wed, 30 Sep 2020 16:50:56 +0000 (+0200) Subject: [#1428] Delete multiple hosts by subnet/addr X-Git-Tag: Kea-1.9.1~142 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1181298f2c71eed352ff2f7b1aa994445e4c0bc3;p=thirdparty%2Fkea.git [#1428] Delete multiple hosts by subnet/addr 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. --- diff --git a/src/lib/dhcpsrv/base_host_data_source.h b/src/lib/dhcpsrv/base_host_data_source.h index 7b37fde2c7..f0cec90281 100644 --- a/src/lib/dhcpsrv/base_host_data_source.h +++ b/src/lib/dhcpsrv/base_host_data_source.h @@ -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. /// diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h index d3f136d339..75926e36e9 100644 --- a/src/lib/dhcpsrv/cfg_hosts.h +++ b/src/lib/dhcpsrv/cfg_hosts.h @@ -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. diff --git a/src/lib/dhcpsrv/cql_host_data_source.h b/src/lib/dhcpsrv/cql_host_data_source.h index 0b58300898..7d47eb3f2d 100644 --- a/src/lib/dhcpsrv/cql_host_data_source.h +++ b/src/lib/dhcpsrv/cql_host_data_source.h @@ -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. /// diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index 3c61cb228d..602016778c 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -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. /// diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index 0eb73c8ea4..efa9ada55f 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -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(&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(&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(&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 + (const_cast(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 diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index 6823e5a939..bbb5584e3f 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -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. /// diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc index 135d882cfd..e6d825257b 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.cc +++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc @@ -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 diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h index d9d8414c4f..51b65ad002 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.h +++ b/src/lib/dhcpsrv/pgsql_host_data_source.h @@ -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. /// diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc index 9df193692a..404e742ea6 100644 --- a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc @@ -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 diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql index b49c659a49..e443fee952 100644 --- a/src/share/database/scripts/mysql/dhcpdb_create.mysql +++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql @@ -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'; diff --git a/src/share/database/scripts/mysql/upgrade_9.3_to_9.4.sh.in b/src/share/database/scripts/mysql/upgrade_9.3_to_9.4.sh.in index 26b133ed23..0e492496c7 100644 --- a/src/share/database/scripts/mysql/upgrade_9.3_to_9.4.sh.in +++ b/src/share/database/scripts/mysql/upgrade_9.3_to_9.4.sh.in @@ -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';