From f9a24c2f604a3a5c554c2c73150902bf060697fc Mon Sep 17 00:00:00 2001 From: Andrei Pavel Date: Wed, 5 Apr 2023 14:48:26 +0300 Subject: [PATCH] [#549] implement reservation-update --- src/lib/database/db_exceptions.h | 10 +++++- src/lib/dhcpsrv/base_host_data_source.h | 18 +++++++++-- src/lib/dhcpsrv/cfg_hosts.cc | 19 +++++++++++ src/lib/dhcpsrv/cfg_hosts.h | 3 ++ src/lib/dhcpsrv/host_mgr.cc | 15 +++++++++ src/lib/dhcpsrv/host_mgr.h | 3 ++ src/lib/dhcpsrv/mysql_host_data_source.cc | 39 +++++++++++++++++++++++ src/lib/dhcpsrv/mysql_host_data_source.h | 3 ++ src/lib/dhcpsrv/pgsql_host_data_source.cc | 39 +++++++++++++++++++++++ src/lib/dhcpsrv/pgsql_host_data_source.h | 3 ++ 10 files changed, 149 insertions(+), 3 deletions(-) diff --git a/src/lib/database/db_exceptions.h b/src/lib/database/db_exceptions.h index ac0e5b0857..9656ab0331 100644 --- a/src/lib/database/db_exceptions.h +++ b/src/lib/database/db_exceptions.h @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2023 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 @@ -85,6 +85,14 @@ public: isc::Exception(file, line, what) {} }; +/// @brief Thrown when it is expected that some rows are affected, +/// usually during a DELETE or an UPDATE, but none are. +class NoRowsAffected : public Exception { +public: + NoRowsAffected(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + } // namespace isc } // namespace db diff --git a/src/lib/dhcpsrv/base_host_data_source.h b/src/lib/dhcpsrv/base_host_data_source.h index 5228497397..03ab6f87a2 100644 --- a/src/lib/dhcpsrv/base_host_data_source.h +++ b/src/lib/dhcpsrv/base_host_data_source.h @@ -25,6 +25,13 @@ public: isc::Exception(file, line, what) { }; }; +/// @brief Exception thrown when a @c Host object is expected, but none are found. +class HostNotFound : public Exception { +public: + HostNotFound(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + /// @brief Exception thrown when an address is already reserved by a @c Host /// object (DuplicateHost is same identity, ReservedAddress same address). class ReservedAddress : public Exception { @@ -452,6 +459,13 @@ public: const Host::IdentifierType& identifier_type, const uint8_t* identifier_begin, const size_t identifier_len) = 0; + /// @brief Attempts to update an existing host entry. + /// + /// @param host the host up to date with the requested changes + /// + /// @return true if deletion was successful, false if the host was not there. + virtual void update(HostPtr const& host) = 0; + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) @@ -516,7 +530,7 @@ typedef boost::shared_ptr HostDataSourcePtr; /// @brief HostDataSource list typedef std::vector HostDataSourceList; -} -} +} // namespace dhcp +} // namespace isc #endif // BASE_HOST_DATA_SOURCE_H diff --git a/src/lib/dhcpsrv/cfg_hosts.cc b/src/lib/dhcpsrv/cfg_hosts.cc index d317e11146..7d20f721a2 100644 --- a/src/lib/dhcpsrv/cfg_hosts.cc +++ b/src/lib/dhcpsrv/cfg_hosts.cc @@ -19,6 +19,7 @@ using namespace isc::asiolink; using namespace isc::data; +using namespace std; namespace isc { namespace dhcp { @@ -1141,6 +1142,24 @@ CfgHosts::del6(const SubnetID& /*subnet_id*/, return (false); } +void +CfgHosts::update(HostPtr const& host) { + bool deleted(false); + if (CfgMgr::instance().getFamily() == AF_INET) { + vector const& identifier(host->getIdentifier()); + deleted = del4(host->getIPv4SubnetID(), host->getIdentifierType(), identifier.data(), + identifier.size()); + } else { + vector const& identifier(host->getIdentifier()); + deleted = del6(host->getIPv6SubnetID(), host->getIdentifierType(), identifier.data(), + identifier.size()); + } + if (!deleted) { + isc_throw(HostNotFound, "Host not updated (not found)."); + } + add(host); +} + bool CfgHosts::setIPReservationsUnique(const bool unique) { ip_reservations_unique_ = unique; diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h index 495cc81265..aea5b711fd 100644 --- a/src/lib/dhcpsrv/cfg_hosts.h +++ b/src/lib/dhcpsrv/cfg_hosts.h @@ -589,6 +589,9 @@ public: const Host::IdentifierType& identifier_type, const uint8_t* identifier_begin, const size_t identifier_len); + /// @brief Implements @ref BaseHostDataSource::update() for config hosts. + void update(HostPtr const& host); + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc index 1397c48443..606248662c 100644 --- a/src/lib/dhcpsrv/host_mgr.cc +++ b/src/lib/dhcpsrv/host_mgr.cc @@ -617,6 +617,21 @@ HostMgr::del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_ return (false); } +void +HostMgr::update(HostPtr const& host) { + if (alternate_sources_.empty()) { + isc_throw(NoHostDataSourceManager, + "Unable to update existing host because there is no hosts-database configured."); + } + for (HostDataSourcePtr const& source : alternate_sources_) { + source->update(host); + } + // If no backend throws the host should be cached. + if (cache_ptr_) { + cache(host); + } +} + void HostMgr::cache(ConstHostPtr host) const { if (cache_ptr_) { diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index 36e6c14200..82d039adb1 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -555,6 +555,9 @@ public: del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type, const uint8_t* identifier_begin, const size_t identifier_len); + /// @brief Implements @ref BaseHostDataSource::update() for alternate sources. + void update(HostPtr const& host); + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index 66b1e474d7..310421e3a7 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -3921,6 +3921,45 @@ MySqlHostDataSource::getAll6(const SubnetID& subnet_id, return (collection); } +void +MySqlHostDataSource::update(HostPtr const& host) { + // Get a context. + MySqlHostContextAlloc const context(*impl_); + MySqlHostContextPtr ctx(context.ctx_); + + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(ctx); + + // Initiate MySQL transaction as we will have to make multiple queries + // to update host information into multiple tables. If that fails on + // any stage, the transaction will be rolled back by the destructor of + // the MySqlTransaction class. + MySqlTransaction transaction(ctx->conn_); + + // As much as having dedicated prepared statements for updating tables would be consistent with + // the implementation of other commands, it's difficult if not impossible to cover all cases for + // updating the host to exactly as is described in the command, which may involve inserts and + // deletes alongside updates. So let's delete and add. The delete cascades into all tables. The + // add explicitly adds into all tables. + bool deleted(false); + if (CfgMgr::instance().getFamily() == AF_INET) { + vector const& identifier(host->getIdentifier()); + deleted = del4(host->getIPv4SubnetID(), host->getIdentifierType(), identifier.data(), + identifier.size()); + } else { + vector const& identifier(host->getIdentifier()); + deleted = del6(host->getIPv6SubnetID(), host->getIdentifierType(), identifier.data(), + identifier.size()); + } + if (!deleted) { + isc_throw(NoRowsAffected, "Host not updated (not found)."); + } + add(host); + + // Everything went fine, so explicitly commit the transaction. + transaction.commit(); +} + // Miscellaneous database methods. std::string diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index 08e6b84bd1..8284dd2a92 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -403,6 +403,9 @@ public: getAll6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Implements @ref BaseHostDataSource::update() for MySQL. + void update(HostPtr const& host); + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc index a2e50e833d..5ab5cebf25 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.cc +++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc @@ -3191,6 +3191,45 @@ PgSqlHostDataSource::getAll6(const SubnetID& subnet_id, return (collection); } +void +PgSqlHostDataSource::update(HostPtr const& host) { + // Get a context. + PgSqlHostContextAlloc const context(*impl_); + PgSqlHostContextPtr ctx(context.ctx_); + + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(ctx); + + // Initiate PostgreSQL transaction as we will have to make multiple queries + // to update host information into multiple tables. If that fails on + // any stage, the transaction will be rolled back by the destructor of + // the PgSqlTransaction class. + PgSqlTransaction transaction(ctx->conn_); + + // As much as having dedicated prepared statements for updating tables would be consistent with + // the implementation of other commands, it's difficult if not impossible to cover all cases for + // updating the host to exactly as is described in the command, which may involve inserts and + // deletes alongside updates. So let's delete and add. The delete cascades into all tables. The + // add explicitly adds into all tables. + bool deleted(false); + if (CfgMgr::instance().getFamily() == AF_INET) { + vector const& identifier(host->getIdentifier()); + deleted = del4(host->getIPv4SubnetID(), host->getIdentifierType(), identifier.data(), + identifier.size()); + } else { + vector const& identifier(host->getIdentifier()); + deleted = del6(host->getIPv6SubnetID(), host->getIdentifierType(), identifier.data(), + identifier.size()); + } + if (!deleted) { + isc_throw(NoRowsAffected, "Host not updated (not found)."); + } + add(host); + + // Everything went fine, so explicitly commit the transaction. + transaction.commit(); +} + // Miscellaneous database methods. std::string diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h index 1abada5757..23004ecbde 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.h +++ b/src/lib/dhcpsrv/pgsql_host_data_source.h @@ -451,6 +451,9 @@ public: getAll6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const; + /// @brief Implements @ref BaseHostDataSource::update() for PostgreSQL. + void update(HostPtr const& host); + /// @brief Return backend type /// /// Returns the type of database as the string "postgresql". This is -- 2.47.2