Skip to content

Commit

Permalink
[113] Add support for modifying replica properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
korydraughn committed Oct 27, 2023
1 parent 6ba3edf commit 80887b1
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
134 changes: 134 additions & 0 deletions src/endpoints/data_objects/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <irods/filesystem.hpp>
#include <irods/irods_at_scope_exit.hpp>
#include <irods/irods_exception.hpp>
#include <irods/modDataObjMeta.h>
#include <irods/phyPathReg.h>
#include <irods/rcMisc.h>
#include <irods/rodsErrorTable.h>
Expand All @@ -33,6 +34,7 @@

#include <nlohmann/json.hpp>

#include <array>
#include <atomic>
#include <mutex>
#include <span>
Expand Down Expand Up @@ -187,6 +189,8 @@ namespace

IRODS_HTTP_API_ENDPOINT_OPERATION_SIGNATURE(op_modify_metadata);

IRODS_HTTP_API_ENDPOINT_OPERATION_SIGNATURE(op_modify_replica);

//
// Operation to Handler mappings
//
Expand Down Expand Up @@ -219,6 +223,8 @@ namespace
{"calculate_checksum", op_calculate_checksum},

{"modify_metadata", op_modify_metadata},

{"modify_replica", op_modify_replica},
};
} // anonymous namespace

Expand Down Expand Up @@ -1845,4 +1851,132 @@ namespace
using namespace irods::http::shared_api_operations;
return op_atomic_apply_metadata_operations(_sess_ptr, _req, _args, entity_type::data_object);
} // op_modify_metadata

IRODS_HTTP_API_ENDPOINT_OPERATION_SIGNATURE(op_modify_replica)
{
auto result = irods::http::resolve_client_identity(_req);
if (result.response) {
return _sess_ptr->send(std::move(*result.response));
}

const auto* client_info = result.client_info;

irods::http::globals::background_task([fn = __func__, client_info, _sess_ptr, _req = std::move(_req), _args = std::move(_args)] {
log::info("{}: client_info->username = [{}]", fn, client_info->username);

http::response<http::string_body> res{http::status::ok, _req.version()};
res.set(http::field::server, irods::http::version::server_name);
res.set(http::field::content_type, "application/json");
res.keep_alive(_req.keep_alive());

try {
DataObjInfo info{};
irods::at_scope_exit free_memory{[&info] { clearKeyVal(&info.condInput); }};

if (const auto iter = _args.find("lpath"); iter != std::end(_args)) {
std::strncpy(info.objPath, iter->second.c_str(), sizeof(DataObjInfo::objPath));
}
else {
log::error("{}: Missing [lpath] parameter.", fn);
return _sess_ptr->send(irods::http::fail(res, http::status::bad_request));
}

if (auto iter = _args.find("resource-hierarchy"); iter != std::end(_args)) {
std::strncpy(info.rescHier, iter->second.c_str(), sizeof(DataObjInfo::rescHier));
}
else if (iter = _args.find("replica-number"); iter != std::end(_args)) {
info.replNum = std::stoi(iter->second.c_str());
}
else {
log::error("{}: Missing [resource-hierarchy] or [replica-number] parameter.", fn);
return _sess_ptr->send(irods::http::fail(res, http::status::bad_request));
}

KeyValPair kvp{};
irods::at_scope_exit clear_kvp{[&kvp] { clearKeyVal(&kvp); }};

if (const auto name_iter = _args.find("property-name"); name_iter != std::end(_args)) {
static constexpr auto properties = std::to_array<std::pair<const char*, const char*>>({
{"COLL_ID", COLL_ID_KW},
{"DATA_CREATE_TIME", DATA_CREATE_KW},
{"DATA_CHECKSUM", CHKSUM_KW},
{"DATA_EXPIRY", DATA_EXPIRY_KW},
{"DATA_ID", DATA_ID_KW},
{"DATA_REPL_STATUS", REPL_STATUS_KW},
{"DATA_MAP_ID", DATA_MAP_ID_KW},
{"DATA_MODE", DATA_MODE_KW},
{"DATA_NAME", DATA_NAME_KW},
{"DATA_OWNER_NAME", DATA_OWNER_KW},
{"DATA_OWNER_ZONE", DATA_OWNER_ZONE_KW},
{"DATA_PATH", FILE_PATH_KW},
{"DATA_REPL_NUM", REPL_NUM_KW},
{"DATA_SIZE", DATA_SIZE_KW},
{"DATA_STATUS", STATUS_STRING_KW},
{"DATA_TYPE_NAME", DATA_TYPE_KW},
{"DATA_VERSION", VERSION_KW},
{"DATA_MODIFY_TIME", DATA_MODIFY_KW},
{"DATA_COMMENTS", DATA_COMMENTS_KW},
//{"DATA_RESC_GROUP_NAME", DATA_RESC_GROUP_NAME_KW}, // missing from genquery since 4.2
{"DATA_RESC_HIER", RESC_HIER_STR_KW},
{"DATA_RESC_ID", RESC_ID_KW},
{"DATA_RESC_NAME", RESC_NAME_KW}
});

const auto end = std::end(properties);
const auto iter = std::find_if(std::begin(properties), end, [&name_iter](auto&& _pn) {
return name_iter->second == _pn.first;
});

if (iter == end) {
log::error("{}: Invalid value [{}] for [property-name] parameter.", fn, name_iter->second);
return _sess_ptr->send(irods::http::fail(res, http::status::bad_request));
}

if (const auto value_iter = _args.find("property-value"); value_iter != std::end(_args)) {
addKeyVal(&kvp, iter->second, value_iter->second.c_str());
addKeyVal(&kvp, ADMIN_KW, "");
}
else {
log::error("{}: Missing [property-value] parameter.", fn);
return _sess_ptr->send(irods::http::fail(res, http::status::bad_request));
}
}
else {
log::error("{}: Missing [property-name] parameter.", fn);
return _sess_ptr->send(irods::http::fail(res, http::status::bad_request));
}

ModDataObjMetaInp input{};
input.dataObjInfo = &info;
input.regParam = &kvp;

auto conn = irods::get_connection(client_info->username);
const auto ec = rcModDataObjMeta(static_cast<RcComm*>(conn), &input);

json response{
{"irods_response", {
{"error_code", ec}
}}
};

res.body() = response.dump();
}
catch (const irods::exception& e) {
res.result(http::status::bad_request);
res.body() = json{
{"irods_response", {
{"error_code", e.code()},
{"error_message", e.client_display_what()}
}}
}.dump();
}
catch (const std::exception& e) {
res.result(http::status::internal_server_error);
}

res.prepare_payload();

_sess_ptr->send(std::move(res));
});
} // op_modify_replica
} // anonymous namespace
59 changes: 59 additions & 0 deletions test/test_irods_http_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,65 @@ def test_modifying_permissions_atomically(self):
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['irods_response']['error_code'], 0)

def test_modifying_replica_properties(self):
headers = {'Authorization': 'Bearer ' + self.rodsadmin_bearer_token}

# Create a data object.
data_object = os.path.join('/', self.zone_name, 'home', self.rodsadmin_username, 'modrepl.txt')
r = requests.post(self.url_endpoint, headers=headers, data={
'op': 'touch',
'lpath': data_object
})
#print(r.content) # Debug
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['irods_response']['error_code'], 0)

# Show the replica is currently marked as good.
r = requests.get(f'{self.url_base}/query', headers=headers, params={
'op': 'execute_genquery',
'query': f"select DATA_REPL_STATUS where COLL_NAME = '{os.path.dirname(data_object)}' and DATA_NAME = '{os.path.basename(data_object)}'"
})
#print(r.content) # Debug
self.assertEqual(r.status_code, 200)

result = r.json()
self.assertEqual(result['irods_response']['error_code'], 0)
self.assertEqual(result['rows'][0][0], '1')

# Change the replica's status to stale using the modify_replica operation.
r = requests.post(self.url_endpoint, headers=headers, data={
'op': 'modify_replica',
'lpath': data_object,
'replica-number': 0,
'property-name': 'DATA_REPL_STATUS',
'property-value': 0
})
#print(r.content) # Debug
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['irods_response']['error_code'], 0)

# Show the replica is now marked stale.
r = requests.get(f'{self.url_base}/query', headers=headers, params={
'op': 'execute_genquery',
'query': f"select DATA_REPL_STATUS where COLL_NAME = '{os.path.dirname(data_object)}' and DATA_NAME = '{os.path.basename(data_object)}'"
})
#print(r.content) # Debug
self.assertEqual(r.status_code, 200)

result = r.json()
self.assertEqual(result['irods_response']['error_code'], 0)
self.assertEqual(result['rows'][0][0], '0')

# Remove the data object.
r = requests.post(self.url_endpoint, headers=headers, data={
'op': 'remove',
'lpath': data_object,
'no-trash': 1
})
#print(r.content) # Debug
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json()['irods_response']['error_code'], 0)

@unittest.skip('Test needs to be implemented.')
def test_return_error_on_missing_parameters(self):
pass
Expand Down

0 comments on commit 80887b1

Please sign in to comment.