From 8bf8637b33a27f42029b58a56dc507b1ffc0309d Mon Sep 17 00:00:00 2001 From: Alan King Date: Wed, 6 Mar 2024 14:38:11 -0500 Subject: [PATCH] [#7104] Add replica_truncate API The replica_truncate API is meant to act as a replacement for rcDataObjTruncate. The interface is very similar to rcDataObjTruncate, but the result is different. The API only targets one replica to be truncated, and will trigger fileModified as if the file had been modified via an open/write/close. --- lib/api/CMakeLists.txt | 2 + lib/api/include/irods/apiHeaderAll.h | 2 + lib/api/include/irods/apiNumberData.h | 1 + lib/api/include/irods/apiTable.hpp | 8 + lib/api/include/irods/replica_truncate.h | 89 +++++ lib/api/src/rc_replica_truncate.cpp | 14 + lib/core/include/irods/library_features.h | 5 + server/api/CMakeLists.txt | 4 +- .../api/include/irods/rs_replica_truncate.hpp | 90 +++++ server/api/src/rs_get_library_features.cpp | 1 + server/api/src/rs_replica_truncate.cpp | 327 ++++++++++++++++++ .../irods/irods_api_calling_functions.hpp | 7 + server/core/src/client_api_allowlist.cpp | 3 +- .../core/src/irods_api_calling_functions.cpp | 5 + 14 files changed, 556 insertions(+), 2 deletions(-) create mode 100644 lib/api/include/irods/replica_truncate.h create mode 100644 lib/api/src/rc_replica_truncate.cpp create mode 100644 server/api/include/irods/rs_replica_truncate.hpp create mode 100644 server/api/src/rs_replica_truncate.cpp diff --git a/lib/api/CMakeLists.txt b/lib/api/CMakeLists.txt index 39f7f4d5d3..ee52b343d8 100644 --- a/lib/api/CMakeLists.txt +++ b/lib/api/CMakeLists.txt @@ -147,6 +147,7 @@ foreach(variant IN ITEMS client server) "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_register_physical_path.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_replica_close.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_replica_open.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_replica_truncate.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_set_delay_server_migration_info.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_set_grid_configuration_value.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_switch_user.cpp" @@ -314,6 +315,7 @@ install( "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/register_physical_path.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/replica_close.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/replica_open.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/replica_truncate.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rmColl.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/ruleExecDel.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/ruleExecMod.h" diff --git a/lib/api/include/irods/apiHeaderAll.h b/lib/api/include/irods/apiHeaderAll.h index ee49dff71a..77691d814b 100644 --- a/lib/api/include/irods/apiHeaderAll.h +++ b/lib/api/include/irods/apiHeaderAll.h @@ -148,4 +148,6 @@ #include "irods/check_auth_credentials.h" +#include "irods/replica_truncate.h" + #endif // API_HEADER_ALL_H__ diff --git a/lib/api/include/irods/apiNumberData.h b/lib/api/include/irods/apiNumberData.h index 409e53a8a7..0a9d771df7 100644 --- a/lib/api/include/irods/apiNumberData.h +++ b/lib/api/include/irods/apiNumberData.h @@ -146,6 +146,7 @@ API_NUMBER(GET_LIMITED_PASSWORD_AN, 726) API_NUMBER(CHECK_AUTH_CREDENTIALS_AN, 800) API_NUMBER(GET_LIBRARY_FEATURES_AN, 801) +API_NUMBER(REPLICA_TRUNCATE_AN, 802) /* 1100 - 1200 - SSL API calls */ API_NUMBER(SSL_START_AN, 1100) diff --git a/lib/api/include/irods/apiTable.hpp b/lib/api/include/irods/apiTable.hpp index 8b7816d6d1..2a25b70a4a 100644 --- a/lib/api/include/irods/apiTable.hpp +++ b/lib/api/include/irods/apiTable.hpp @@ -153,6 +153,7 @@ # include "irods/rs_check_auth_credentials.hpp" # include "irods/rs_get_library_features.hpp" # include "irods/rs_get_resource_info_for_operation.hpp" +# include "irods/rs_replica_truncate.hpp" # define NULLPTR_FOR_CLIENT_TABLE(x) x #elif !defined(CREATE_API_TABLE_FOR_SERVER) && defined(CREATE_API_TABLE_FOR_CLIENT) # define NULLPTR_FOR_CLIENT_TABLE(x) nullptr @@ -264,6 +265,7 @@ #define RS_REG_COLL NULLPTR_FOR_CLIENT_TABLE(rsRegColl) #define RS_REG_DATA_OBJ NULLPTR_FOR_CLIENT_TABLE(rsRegDataObj) #define RS_REG_REPLICA NULLPTR_FOR_CLIENT_TABLE(rsRegReplica) +#define RS_REPLICA_TRUNCATE NULLPTR_FOR_CLIENT_TABLE(rs_replica_truncate) #define RS_RM_COLL NULLPTR_FOR_CLIENT_TABLE(rsRmColl) #define RS_RULE_EXEC_DEL NULLPTR_FOR_CLIENT_TABLE(rsRuleExecDel) #define RS_RULE_EXEC_MOD NULLPTR_FOR_CLIENT_TABLE(rsRuleExecMod) @@ -1279,6 +1281,12 @@ static irods::apidef_t client_api_table_inp[] = { boost::any(std::function(RS_GET_RESOURCE_INFO_FOR_OPERATION)), "api_get_resource_info_for_operation", clearDataObjInp, irods::clearOutStruct_noop, (funcPtr)CALL_GET_RESOURCE_INFO_FOR_OPERATION + }, + { REPLICA_TRUNCATE_AN, RODS_API_VERSION, REMOTE_USER_AUTH, REMOTE_USER_AUTH, + "DataObjInp_PI", 0, "STR_PI", 0, + boost::any(std::function(RS_REPLICA_TRUNCATE)), + "api_replica_truncate", clearDataObjInp, irods::clearOutStruct_noop, + (funcPtr)CALL_REPLICA_TRUNCATE } // clang-format on }; // _api_table_inp diff --git a/lib/api/include/irods/replica_truncate.h b/lib/api/include/irods/replica_truncate.h new file mode 100644 index 0000000000..7223037d0f --- /dev/null +++ b/lib/api/include/irods/replica_truncate.h @@ -0,0 +1,89 @@ +#ifndef IRODS_REPLICA_TRUNCATE_H +#define IRODS_REPLICA_TRUNCATE_H + +/// \file + +#include "irods/dataObjInpOut.h" + +struct RcComm; + +#ifdef __cplusplus +extern "C" { +#endif + +/// Truncate a replica for the specified data object to the specified size. +/// +/// \parblock +/// This API selects a replica to truncate according to the rules of POSIX truncate(2). The caller may provide keywords +/// via condInput in order to influence the hierarchy resolution for selecting a replica to truncate. +/// \endparblock +/// +/// \param[in] _comm A pointer to a RcComm. +/// \param[in] _inp \parblock +/// DataObjInp structure which requires the following inputs: +/// objPath - The full logical path to the target data object. +/// dataSize - The desired size of the replica after truncating. +/// +/// The condInput supports the following keywords: +/// REPL_NUM_KW - The replica number of the replica to truncate. +/// RESC_NAME_KW - The name of the resource with the replica to truncate. Must be a root resource. +/// DEF_RESC_NAME_KW - The default resource to target in the absence of any other inputs or policy. +/// RESC_HIER_STR_KW - Full resource hierarchy to the replica to truncate. Use with caution. +/// ADMIN_KW - Flag indicating that the operation is to be performed with elevated privileges. No value required. +/// \endparblock +/// \param[out] _out \parblock +/// Character string representing a JSON structure with the following form: +/// \code{.js} +/// { +/// // Resource hierarchy of the selected replica for truncate. If an error occurs before hierarchy resolution is +/// // completed, a null value will be here instead of a string. +/// "resource_hierarchy": , +/// // Replica number of the selected replica for truncate. If an error occurs before hierarchy resolution is +/// // completed, a null value will be here instead of an integer. +/// "replica_number": , +/// // A string containing any relevant message the server may wish to send to the user (including error messages). +/// // This value will always be a string, even if it is empty. +/// "message": +/// } +/// \endcode +/// \endparblock +/// +/// \usage \parblock +/// \code{c} +/// RcComm* comm = // Our iRODS connection. +/// +/// // Don't forget to call clearKeyVal on truncate_doi.condInput before exiting to prevent leaks. +/// DataObjInp truncate_doi; +/// memset(&truncate_doi, 0, sizeof(DataObjInp)); +/// +/// // Set the path and the desired size. +/// strncpy(truncate_doi.objPath, "/tempZone/home/alice/foo", MAX_NAME_LEN); +/// truncate_doi.size = 0; +/// +/// // Target a specific replica, if desired. If no replica is targeted via some key-value pair, the policy for +/// // selecting a preferred resource configured on the server will be used for resolving the hierarchy for a +/// // "write" operation. +/// addKeyVal(&truncate_doi.condInput, REPL_NUM_KW, "3"); +/// +/// // Need a character string to hold the output. Don't forget to free this before exiting to prevent leaks. +/// char* output_string = NULL; +/// +/// const int ec = rc_replica_truncate(comm, &truncate_doi, &output_string); +/// if (ec < 0) { +/// // Error handling. Perhaps use the "message" field inside the output_string. +/// } +/// \endcode +/// \endparblock +/// +/// \return An integer representing an iRODS error code. +/// \retval 0 on success. +/// \retval <0 on failure. +/// +/// \since 4.3.2 +int rc_replica_truncate(struct RcComm* _comm, struct DataObjInp* _inp, char** _out); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // IRODS_REPLICA_TRUNCATE_H diff --git a/lib/api/src/rc_replica_truncate.cpp b/lib/api/src/rc_replica_truncate.cpp new file mode 100644 index 0000000000..19875025fd --- /dev/null +++ b/lib/api/src/rc_replica_truncate.cpp @@ -0,0 +1,14 @@ +#include "irods/replica_truncate.h" + +#include "irods/apiNumber.h" +#include "irods/procApiRequest.h" +#include "irods/rodsErrorTable.h" + +auto rc_replica_truncate(RcComm* _comm, DataObjInp* _inp, char** _out) -> int +{ + if (!_comm || !_inp || !_out) { + return SYS_INVALID_INPUT_PARAM; + } + + return procApiRequest(_comm, REPLICA_TRUNCATE_AN, _inp, nullptr, reinterpret_cast(_out), nullptr); +} // rc_replica_truncate diff --git a/lib/core/include/irods/library_features.h b/lib/core/include/irods/library_features.h index 5f1c603c45..bbf2f7f488 100644 --- a/lib/core/include/irods/library_features.h +++ b/lib/core/include/irods/library_features.h @@ -49,4 +49,9 @@ /// \since 4.3.1 #define IRODS_HAS_API_ENDPOINT_CHECK_AUTH_CREDENTIALS 202307L +/// Defined if the development library supports #rc_replica_truncate. +/// +/// \since 4.3.2 +#define IRODS_HAS_API_ENDPOINT_REPLICA_TRUNCATE 202403L + #endif // IRODS_LIBRARY_FEATURES_H diff --git a/server/api/CMakeLists.txt b/server/api/CMakeLists.txt index 5fe01a27ff..8afd065f77 100644 --- a/server/api/CMakeLists.txt +++ b/server/api/CMakeLists.txt @@ -13,6 +13,7 @@ add_library( "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_register_physical_path.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_replica_close.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_replica_open.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_replica_truncate.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_set_delay_server_migration_info.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_set_grid_configuration_value.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_touch.cpp" @@ -211,8 +212,9 @@ install( "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_get_library_features.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_get_resource_info_for_operation.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_register_physical_path.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_replica_open.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_replica_close.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_replica_open.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_replica_truncate.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_set_delay_server_migration_info.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_set_grid_configuration_value.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_touch.hpp" diff --git a/server/api/include/irods/rs_replica_truncate.hpp b/server/api/include/irods/rs_replica_truncate.hpp new file mode 100644 index 0000000000..0a425bcfb2 --- /dev/null +++ b/server/api/include/irods/rs_replica_truncate.hpp @@ -0,0 +1,90 @@ +#ifndef IRODS_RS_REPLICA_TRUNCATE_HPP +#define IRODS_RS_REPLICA_TRUNCATE_HPP + +/// \file + +#include "irods/dataObjInpOut.h" + +struct RsComm; + +#ifdef __cplusplus +extern "C" { +#endif + +/// Truncate a replica for the specified data object to the specified size. +/// +/// \parblock +/// This API selects a replica to truncate according to the rules of POSIX truncate(2). The caller may provide keywords +/// via condInput in order to influence the hierarchy resolution for selecting a replica to truncate. +/// \endparblock +/// +/// \param[in] _comm A pointer to a RsComm. +/// \param[in] _inp \parblock +/// DataObjInp structure which requires the following inputs: +/// objPath - The full logical path to the target data object. +/// dataSize - The desired size of the replica after truncating. +/// +/// The condInput supports the following keywords: +/// REPL_NUM_KW - The replica number of the replica to truncate. +/// RESC_NAME_KW - The name of the resource with the replica to truncate. Must be a root resource. +/// DEF_RESC_NAME_KW - The default resource to target in the absence of any other inputs or policy. +/// RESC_HIER_STR_KW - Full resource hierarchy to the replica to truncate. Use with caution. +/// ADMIN_KW - Flag indicating that the operation is to be performed with elevated privileges. No value required. +/// \endparblock +/// \param[out] _out \parblock +/// Character string representing a JSON structure with the following form: +/// \code{.js} +/// { +/// // Resource hierarchy of the selected replica for truncate. If an error occurs before hierarchy resolution is +/// // completed, a null value will be here instead of a string. +/// "resource_hierarchy": , +/// // Replica number of the selected replica for truncate. If an error occurs before hierarchy resolution is +/// // completed, a null value will be here instead of an integer. +/// "replica_number": , +/// // A string containing any relevant message the server may wish to send to the user (including error messages). +/// // This value will always be a string, even if it is empty. +/// "message": +/// } +/// \endcode +/// \endparblock +/// +/// \usage \parblock +/// \code{c} +/// // Because this is a server-side function, we will assume that there is access to an RsComm; +/// RsComm* comm; +/// +/// // Don't forget to call clearKeyVal on truncate_doi.condInput before exiting to prevent leaks. +/// DataObjInp truncate_doi; +/// memset(&truncate_doi, 0, sizeof(DataObjInp)); +/// +/// // Set the path and the desired size. +/// strncpy(truncate_doi.objPath, "/tempZone/home/alice/foo", MAX_NAME_LEN); +/// truncate_doi.size = 0; +/// +/// // Target a specific replica, if desired. If no replica is targeted via some key-value pair, the policy for +/// // selecting a preferred resource configured on the server will be used for resolving the hierarchy for a +/// // "write" operation. +/// addKeyVal(&truncate_doi.condInput, REPL_NUM_KW, "3"); +/// +/// // Need a character string to hold the output. Don't forget to free this before exiting to prevent leaks. +/// char* output_string = NULL; +/// +/// const int ec = rs_replica_truncate(comm, &truncate_doi, &output_string); +/// if (ec < 0) { +/// // Error handling. Perhaps use the "message" field inside the output_string. +/// } +/// \endcode +/// \endparblock +/// +/// \return An integer representing an iRODS error code, or 0. +/// \retval 0 on success. +/// \retval <0 on failure; an iRODS error code. +/// +/// \since 4.3.2 +int rs_replica_truncate(RsComm* _comm, DataObjInp* _inp, char** _out); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // IRODS_RS_REPLICA_TRUNCATE_HPP diff --git a/server/api/src/rs_get_library_features.cpp b/server/api/src/rs_get_library_features.cpp index ee65d069e3..e32a963f3b 100644 --- a/server/api/src/rs_get_library_features.cpp +++ b/server/api/src/rs_get_library_features.cpp @@ -27,6 +27,7 @@ auto rs_get_library_features(RsComm* _comm, char** _features) -> int IRODS_FEATURE(IRODS_HAS_LIBRARY_SYSTEM_ERROR) IRODS_FEATURE(IRODS_HAS_FEATURE_PROXY_USER_SUPPORT_FOR_CLIENT_CONNECTION_LIBRARIES) IRODS_FEATURE(IRODS_HAS_API_ENDPOINT_CHECK_AUTH_CREDENTIALS) + IRODS_FEATURE(IRODS_HAS_API_ENDPOINT_REPLICA_TRUNCATE) }.dump().c_str()); // clang-format on diff --git a/server/api/src/rs_replica_truncate.cpp b/server/api/src/rs_replica_truncate.cpp new file mode 100644 index 0000000000..e70e24c3db --- /dev/null +++ b/server/api/src/rs_replica_truncate.cpp @@ -0,0 +1,327 @@ +#include "irods/rs_replica_truncate.hpp" + +#include "irods/getRemoteZoneResc.h" +#include "irods/irods_at_scope_exit.hpp" +#include "irods/irods_error.hpp" +#include "irods/irods_exception.hpp" +#include "irods/irods_file_object.hpp" +#include "irods/irods_logger.hpp" +#include "irods/irods_resource_backport.hpp" +#include "irods/irods_resource_redirect.hpp" +#include "irods/irods_rs_comm_query.hpp" +#include "irods/logical_locking.hpp" +#include "irods/replica_truncate.h" +#include "irods/rodsConnect.h" +#include "irods/rsFileTruncate.hpp" +#include "irods/rsModDataObjMeta.hpp" + +#define IRODS_REPLICA_ENABLE_SERVER_SIDE_API +#include "irods/data_object_proxy.hpp" + +#include +#include + +#include +#include +#include // For std::tie. + +#include // For strdup. + +namespace +{ + namespace ill = irods::logical_locking; + using log_api = irods::experimental::log::api; + + auto make_json_output(const std::string& _message, const DataObjInfo* _replica = nullptr) -> nlohmann::json + { + constexpr auto json_null = nlohmann::json::value_t::null; + + nlohmann::json output; + + output["message"] = _message; + + if (_replica) { + output["resource_hierarchy"] = _replica->rescHier; + } + else { + output["resource_hierarchy"] = json_null; + } + + if (_replica) { + output["replica_number"] = _replica->replNum; + } + else { + output["replica_number"] = json_null; + } + + return output; + } // make_json_output + + auto get_data_object_info_and_resolve_hierarchy(RsComm* _comm, + DataObjInp* _inp, + DataObjInfo** _doi_out, + std::string& _hierarchy_out) -> int + { + if (!_comm || !_inp || !_doi_out) { + return SYS_INTERNAL_NULL_INPUT_ERR; + } + + *_doi_out = nullptr; + + if (const auto* provided_resc_hier = getValByKey(&_inp->condInput, RESC_HIER_STR_KW); provided_resc_hier) { + _hierarchy_out = provided_resc_hier; + } + + DataObjInfo* info_head = nullptr; + + if (_hierarchy_out.empty()) { + try { + irods::file_object_ptr file_obj; + std::tie(file_obj, _hierarchy_out) = + irods::resolve_resource_hierarchy(irods::WRITE_OPERATION, _comm, *_inp, &info_head); + } + catch (const irods::exception& e) { + if (info_head) { + freeAllDataObjInfo(info_head); + } + + // If the data object does not exist, then the exception will contain error code of CAT_NO_ROWS_FOUND. + if (e.code() == CAT_NO_ROWS_FOUND) { + return OBJ_PATH_DOES_NOT_EXIST; + } + + return static_cast(e.code()); + } + + addKeyVal(&_inp->condInput, RESC_HIER_STR_KW, _hierarchy_out.c_str()); + } + else { + // If the resource hierarchy has already been resolved, we still need the data object information. The + // file_object_factory is used here in order to ensure that the same information is used for both cases. + irods::file_object_ptr file_obj{new irods::file_object{}}; + if (const auto err = irods::file_object_factory(_comm, _inp, file_obj, &info_head); !err.ok()) { + if (info_head) { + freeAllDataObjInfo(info_head); + } + + return static_cast(err.code()); + } + } + + // If for whatever reason the function calls above did not produce an error but did not return a pointer to the + // data object information, throw an error here. + if (!info_head) { + return SYS_INTERNAL_NULL_INPUT_ERR; + } + + // An error should be returned if the resolved hierarchy had a locked or intermediate replica. However, not all + // code paths resolve a resource hierarchy and it is inexpensive to check, regardless. + if (const auto ec = ill::try_lock(*info_head, ill::lock_type::write, _hierarchy_out); ec < 0) { + return ec; + } + + *_doi_out = info_head; + + return 0; + } // get_data_object_info_and_resolve_hierarchy + + auto truncate_replica(RsComm* _comm, const DataObjInp* _inp, const DataObjInfo* _replica) -> int + { + if (!_comm || !_inp || !_replica) { + return SYS_INTERNAL_NULL_INPUT_ERR; + } + + std::string location; + if (const auto err = irods::get_loc_for_hier_string(_replica->rescHier, location); !err.ok()) { + return static_cast(err.code()); + } + + fileOpenInp_t file_open_inp{}; + std::strncpy(file_open_inp.fileName, _replica->filePath, MAX_NAME_LEN); + std::strncpy(file_open_inp.resc_hier_, _replica->rescHier, MAX_NAME_LEN); + std::strncpy(file_open_inp.addr.hostAddr, location.c_str(), NAME_LEN); + file_open_inp.dataSize = _inp->dataSize; + return rsFileTruncate(_comm, &file_open_inp); + } // truncate_replica + + auto update_system_metadata_for_data_object(RsComm* _comm, const DataObjInp* _inp, const DataObjInfo* _info) -> int + { + ModDataObjMetaInp mod_data_obj_meta_inp{}; + + auto kvp = KeyValPair{}; + const auto clear_kvp = irods::at_scope_exit{[&kvp] { clearKeyVal(&kvp); }}; + auto reg_param = irods::experimental::make_key_value_proxy(kvp); + + reg_param[DATA_SIZE_KW] = std::to_string(_inp->dataSize); + reg_param[CHKSUM_KW] = ""; + reg_param[OPEN_TYPE_KW] = std::to_string(OPEN_FOR_WRITE_TYPE); + reg_param[ALL_REPL_STATUS_KW] = ""; + reg_param[DATA_MODIFY_KW] = std::to_string(time(nullptr)); + if (getValByKey(&_inp->condInput, ADMIN_KW)) { + reg_param[ADMIN_KW] = ""; + } + + // We must const_cast here because rsModDataObjMeta does not accept a constant input. Therefore, the input must + // be non-const, so the members inside the input structure must also be non-const. + mod_data_obj_meta_inp.dataObjInfo = const_cast(_info); + mod_data_obj_meta_inp.regParam = reg_param.get(); + return rsModDataObjMeta(_comm, &mod_data_obj_meta_inp); + } // update_system_metadata_for_data_object +} // anonymous namespace + +auto rs_replica_truncate(RsComm* _comm, DataObjInp* _inp, char** _out) -> int +{ + if (!_comm || !_inp || !_out) { + static const auto msg = fmt::format("{}: Null input parameter.", __func__); + log_api::info(msg); + return USER__NULL_INPUT_ERR; + } + + *_out = nullptr; + + // Do not allow DEST_RESC_NAME_KW to be used. It is confusing when RESC_NAME_KW is also supported and the API is + // only dealing with one replica. + if (const auto* destination_resource = getValByKey(&_inp->condInput, DEST_RESC_NAME_KW); destination_resource) { + static const auto msg = fmt::format("{}: [{}] keyword not supported.", __func__, DEST_RESC_NAME_KW); + log_api::info(msg); + *_out = strdup(make_json_output(msg).dump().c_str()); + return SYS_INVALID_INPUT_PARAM; + } + + // Do not allow DEST_RESC_HIER_STR_KW to be used. It is confusing when RESC_HIER_STR_KW is also supported and the + // API is only dealing with one replica. + if (const auto* destination_hierarchy = getValByKey(&_inp->condInput, DEST_RESC_HIER_STR_KW); destination_hierarchy) + { + const auto msg = fmt::format("{}: [{}] keyword not supported.", __func__, DEST_RESC_HIER_STR_KW); + log_api::info(msg); + *_out = strdup(make_json_output(msg).dump().c_str()); + return SYS_INVALID_INPUT_PARAM; + } + + const auto* resource_name = getValByKey(&_inp->condInput, RESC_NAME_KW); + const auto* replica_number = getValByKey(&_inp->condInput, REPL_NUM_KW); + + // Do not allow RESC_NAME_KW and REPL_NUM_KW to be used at the same time. + if (resource_name && replica_number) { + static const auto msg = + fmt::format("{}: [{}] and [{}] keywords cannot be used together.", __func__, RESC_NAME_KW, REPL_NUM_KW); + log_api::info(msg); + *_out = strdup(make_json_output(msg).dump().c_str()); + return SYS_INVALID_INPUT_PARAM; + } + + // Check for admin keyword and only allow its usage if the connected client user is privileged. + if (getValByKey(&_inp->condInput, ADMIN_KW) && !irods::is_privileged_client(*_comm)) { + const auto msg = fmt::format("{}: [{}] keyword used by non-admin user [{}#{}].", + __func__, + ADMIN_KW, + _comm->clientUser.userName, + _comm->clientUser.rodsZone); + log_api::error(msg); + *_out = strdup(make_json_output(msg).dump().c_str()); + return CAT_INSUFFICIENT_PRIVILEGE_LEVEL; + } + + // Do not free this structure because it is a pointer to a global list of rodsServerHost structs which must + // persist for the life of the agent. + rodsServerHost* host_info = nullptr; + const auto remoteFlag = getAndConnRemoteZone(_comm, _inp, &host_info, REMOTE_OPEN); + if (remoteFlag < 0) { + const auto msg = + fmt::format("{}: getAndConnRemoteZone failed for [{}]: {}", __func__, _inp->objPath, remoteFlag); + log_api::error(msg); + *_out = strdup(make_json_output(msg).dump().c_str()); + return remoteFlag; + } + + // Let the remote zone handle it. + if (REMOTE_HOST == remoteFlag) { + return rc_replica_truncate(host_info->conn, _inp, _out); + } + + try { + // Get the data object information and resolve the resource hierarchy. If an error occurs, this function + // should throw. Cannot use structured bindings with these types. + DataObjInfo* doi = nullptr; + std::string resolved_hierarchy; + const auto free_data_object_info = irods::at_scope_exit{[&doi] { freeAllDataObjInfo(doi); }}; + if (const auto ec = get_data_object_info_and_resolve_hierarchy(_comm, _inp, &doi, resolved_hierarchy); ec < 0) { + const auto msg = fmt::format("{}: Error occurred resolving hierarchy or getting information for [{}]: {}", + __func__, + _inp->objPath, + ec); + log_api::error(msg); + *_out = strdup(make_json_output(msg).dump().c_str()); + return ec; + } + + // Find the replica in the list which resides on the resolved resource hierarchy. If it is not found, something + // has gone horribly wrong and we should bail. + const auto* replica = irods::experimental::data_object::find_replica(*doi, resolved_hierarchy); + if (!replica) { + const auto msg = fmt::format( + "{}: [{}] has no replica on resolved hierarchy [{}].", __func__, _inp->objPath, resolved_hierarchy); + log_api::error(msg); + *_out = strdup(make_json_output(msg).dump().c_str()); + return SYS_REPLICA_DOES_NOT_EXIST; + } + + // If the user specified a resource and it is not in the resolved hierarchy, this is considered an error. + if (resource_name && !irods::hierarchy_parser{replica->rescHier}.resc_in_hier(resource_name)) { + const auto msg = fmt::format( + "{}: Hierarchy descending from specified resource name [{}] does not have a replica of [{}] " + "or the replica is inaccessible at this time.", + __func__, + resource_name, + _inp->objPath); + log_api::error(msg); + *_out = strdup(make_json_output(msg, replica).dump().c_str()); + return SYS_REPLICA_INACCESSIBLE; + } + + // If the target replica is already of the specified size, there is nothing to do. + if (replica->dataSize == _inp->dataSize) { + const auto msg = fmt::format("{}: Replica of [{}] on [{}] already has size [{}].", + __func__, + _inp->objPath, + resolved_hierarchy, + _inp->dataSize); + log_api::debug(msg); + *_out = strdup(make_json_output(msg, replica).dump().c_str()); + return 0; + } + + if (const auto ec = truncate_replica(_comm, _inp, replica); ec < 0) { + const auto msg = fmt::format("{}: Error occurred while truncating replica of [{}] on [{}]: {}", + __func__, + replica->objPath, + replica->rescHier, + ec); + log_api::debug(msg); + *_out = strdup(make_json_output(msg, replica).dump().c_str()); + return ec; + } + + if (const auto ec = update_system_metadata_for_data_object(_comm, _inp, replica); ec < 0) { + const auto msg = + fmt::format("{}: Error occurred while updating system metadata for replica of [{}] on [{}]: {}", + __func__, + replica->objPath, + replica->rescHier, + ec); + log_api::debug(msg); + *_out = strdup(make_json_output(msg, replica).dump().c_str()); + return ec; + } + } + catch (const irods::exception& e) { + log_api::error("{}: Failed to truncate [{}]: {}", __func__, _inp->objPath, e.client_display_what()); + return static_cast(e.code()); + } + catch (const std::exception& e) { + log_api::error("{}: Exception occurred truncating [{}]: {}", __func__, _inp->objPath, e.what()); + return SYS_INTERNAL_ERR; + } + + return 0; +} // rs_replica_truncate diff --git a/server/core/include/irods/irods_api_calling_functions.hpp b/server/core/include/irods/irods_api_calling_functions.hpp index f320774db2..a87c5b23d8 100644 --- a/server/core/include/irods/irods_api_calling_functions.hpp +++ b/server/core/include/irods/irods_api_calling_functions.hpp @@ -1045,4 +1045,11 @@ int call_get_resource_info_for_operation(irods::api_entry*, rsComm_t*, dataObjIn # define CALL_GET_RESOURCE_INFO_FOR_OPERATION nullptr // NOLINT(cppcoreguidelines-macro-usage) #endif +#ifdef CREATE_API_TABLE_FOR_SERVER +int call_replica_truncate(irods::api_entry*, rsComm_t*, dataObjInp_t*, char**); +# define CALL_REPLICA_TRUNCATE call_replica_truncate +#else +# define CALL_REPLICA_TRUNCATE nullptr // NOLINT(cppcoreguidelines-macro-usage) +#endif + #endif // IRODS_API_CALLING_FUNCTIONS_HPP diff --git a/server/core/src/client_api_allowlist.cpp b/server/core/src/client_api_allowlist.cpp index 188c112d0d..f8b9eaa905 100644 --- a/server/core/src/client_api_allowlist.cpp +++ b/server/core/src/client_api_allowlist.cpp @@ -183,7 +183,8 @@ namespace irods::client_api_allowlist GET_LIBRARY_FEATURES_AN, - GET_RESOURCE_INFO_FOR_OPERATION_AN + GET_RESOURCE_INFO_FOR_OPERATION_AN, + REPLICA_TRUNCATE_AN }; // clang-format on } // init diff --git a/server/core/src/irods_api_calling_functions.cpp b/server/core/src/irods_api_calling_functions.cpp index 78f878b8e9..95a4eb3a5b 100644 --- a/server/core/src/irods_api_calling_functions.cpp +++ b/server/core/src/irods_api_calling_functions.cpp @@ -1136,3 +1136,8 @@ int call_get_resource_info_for_operation(irods::api_entry* _api, rsComm_t* _comm { return _api->call_handler(_comm, _inp, _out); } // call_get_resource_info_for_operation + +auto call_replica_truncate(irods::api_entry* _api, rsComm_t* _comm, dataObjInp_t* _inp, char** _out) -> int +{ + return _api->call_handler(_comm, _inp, _out); +} // call_replica_truncate