]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#549] implement reservation-update
authorAndrei Pavel <andrei@isc.org>
Wed, 5 Apr 2023 11:48:26 +0000 (14:48 +0300)
committerAndrei Pavel <andrei@isc.org>
Wed, 19 Apr 2023 20:56:00 +0000 (23:56 +0300)
src/lib/database/db_exceptions.h
src/lib/dhcpsrv/base_host_data_source.h
src/lib/dhcpsrv/cfg_hosts.cc
src/lib/dhcpsrv/cfg_hosts.h
src/lib/dhcpsrv/host_mgr.cc
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

index ac0e5b08571a6af28e314a76760b9c8300f4b047..9656ab0331564890a8e5b82ffa7fa1346507d069 100644 (file)
@@ -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
 
index 522849739747c16df9459161e85efe368b2c7032..03ab6f87a21eb0a2faf5ed1f0379c4f33053926a 100644 (file)
@@ -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<BaseHostDataSource> HostDataSourcePtr;
 /// @brief HostDataSource list
 typedef std::vector<HostDataSourcePtr> HostDataSourceList;
 
-}
-}
+}  // namespace dhcp
+}  // namespace isc
 
 #endif // BASE_HOST_DATA_SOURCE_H
index d317e111468c2174f313c25e0c677115391a12d8..7d20f721a2714398ab06fae439dff0d10a032f80 100644 (file)
@@ -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<uint8_t> const& identifier(host->getIdentifier());
+        deleted = del4(host->getIPv4SubnetID(), host->getIdentifierType(), identifier.data(),
+             identifier.size());
+    } else {
+        vector<uint8_t> 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;
index 495cc81265ba22912c5fc014c499b51546acaccb..aea5b711fd6f27c66ea8ec0c1f1e84c278377c91 100644 (file)
@@ -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.)
index 1397c48443302a138f31f04073989d6a2f897617..606248662c088d6dd84c6bb589399687e4c331a7 100644 (file)
@@ -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_) {
index 36e6c142001fc24c3ddffcbc414018354bc8a1ec..82d039adb1b5efe5364ced93834c3df71dbb7dbb 100644 (file)
@@ -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.)
index 66b1e474d72451725e6e99860d3e149d914cb19f..310421e3a74168b7202b46bc11bb0d54e76ff49f 100644 (file)
@@ -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<uint8_t> const& identifier(host->getIdentifier());
+        deleted = del4(host->getIPv4SubnetID(), host->getIdentifierType(), identifier.data(),
+             identifier.size());
+    } else {
+        vector<uint8_t> 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
index 08e6b84bd1d5f383fc999c80322484cf238e3299..8284dd2a92c446ca803d21218dedf3b0b2a49397 100644 (file)
@@ -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.)
index a2e50e833d620947f87a83b352d7de95b97452c3..5ab5cebf2560e02bd585a393f24616e88c6b240c 100644 (file)
@@ -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<uint8_t> const& identifier(host->getIdentifier());
+        deleted = del4(host->getIPv4SubnetID(), host->getIdentifierType(), identifier.data(),
+             identifier.size());
+    } else {
+        vector<uint8_t> 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
index 1abada5757e3236e5bf48cd49d2dd70fa403f24b..23004ecbde8b1d883eb2ad255722aac457f66746 100644 (file)
@@ -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