Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions scripts/irods/test/test_istream.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,3 +570,91 @@ def test_istream_write_with_various_options_on_empty_replica_with_effects_on_mti
finally:
self.user.run_icommand(["irm", "-f", logical_path])
self.admin.run_icommand(["iadmin", "rmresc", test_resource])

def test_istream_read_R_is_a_directive_not_a_preference__issue_8627(self):
filename = "test_istream_read_R_is_a_directive_not_a_preference__issue_8627"
logical_path = os.path.join(self.user.session_collection, filename)
resource_1 = "resc1"
resource_2 = "resc2"
root_resource = "pt1"
contents = "test_istream_read_R_is_a_directive_not_a_preference__issue_8627"

try:
# Create a resource hierarchy for testing.
lib.create_passthru_resource(self.admin, root_resource)
lib.create_ufs_resource(self.admin, resource_1, test.settings.HOSTNAME_2)
lib.create_ufs_resource(self.admin, resource_2, test.settings.HOSTNAME_3)
lib.add_child_resource(self.admin, root_resource, resource_1)

# Create a data object for testing.
self.user.assert_icommand(["istream", "write", "-R", root_resource, logical_path], input=contents)

# Target resource which does not exist - failure.
self.user.assert_icommand(
["istream", "read", "-R", "nope", logical_path], "STDERR", "Error: Cannot open data object.")

# Target leaf resource in a hierarchy - failure
self.user.assert_icommand(
["istream", "read", "-R", resource_1, logical_path], "STDERR", "Error: Cannot open data object.")

# Target resource which does not host any replicas - failure.
self.user.assert_icommand(
["istream", "read", "-R", resource_2, logical_path], "STDERR", "Error: Cannot open data object.")

# Target the resource on which the replica actually resides - success.
self.user.assert_icommand(["istream", "read", "-R", root_resource, logical_path], "STDOUT", contents)

finally:
self.user.run_icommand(['irm', '-f', logical_path])
lib.remove_child_resource(self.admin, root_resource, resource_1)
lib.remove_resource(self.admin, root_resource)
lib.remove_resource(self.admin, resource_1)
lib.remove_resource(self.admin, resource_2)

def test_istream_write_R_is_a_directive_not_a_preference__issue_8627(self):
filename = "test_istream_write_R_is_a_directive_not_a_preference__issue_8627"
logical_path = os.path.join(self.user.session_collection, filename)
resource_1 = "resc1"
resource_2 = "resc2"
root_resource = "pt1"
contents = "test_istreamwrite_R_is_a_directive_not_a_preference__issue_8627"

try:
# Create a resource hierarchy for testing.
lib.create_passthru_resource(self.admin, root_resource)
lib.create_ufs_resource(self.admin, resource_1, test.settings.HOSTNAME_2)
lib.create_ufs_resource(self.admin, resource_2, test.settings.HOSTNAME_3)
lib.add_child_resource(self.admin, root_resource, resource_1)

# Create a data object for testing.
self.user.assert_icommand(["istream", "write", "-R", root_resource, logical_path], input=contents)

# Target resource which does not exist - failure. Specify --append to avoid create.
self.user.assert_icommand(
["istream", "write", "--append", "-R", "nope", logical_path],
"STDERR", "Error: Cannot open data object.", input=contents)
self.user.assert_icommand(["istream", "read", logical_path], "STDOUT", contents)

# Target leaf resource in a hierarchy - failure. Specify --append to avoid create.
self.user.assert_icommand(
["istream", "write", "--append", "-R", resource_1, logical_path],
"STDERR", "Error: Cannot open data object.", input=contents)
self.user.assert_icommand(["istream", "read", logical_path], "STDOUT", contents)

# Target the resource on which the replica actually resides - success. Specify --append to avoid create.
self.user.assert_icommand(
["istream", "write", "--append", "-R", root_resource, logical_path], input=contents)
self.user.assert_icommand(["istream", "read", logical_path], "STDOUT", contents * 2)

# Target resource which does not host any replicas. Should create a new replica because --append not used.
other_contents = "somethingelse"
self.user.assert_icommand(["istream", "write", "-R", resource_2, logical_path], input=other_contents)
self.user.assert_icommand(["istream", "read", "-R", resource_2, logical_path], "STDOUT", other_contents)

finally:
self.user.assert_icommand(['ils', '-L'], "STDOUT") # debugging
self.user.run_icommand(['irm', '-f', logical_path])
lib.remove_child_resource(self.admin, root_resource, resource_1)
lib.remove_resource(self.admin, root_resource)
lib.remove_resource(self.admin, resource_1)
lib.remove_resource(self.admin, resource_2)
18 changes: 18 additions & 0 deletions server/api/src/rsDataObjOpen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,24 @@ namespace
return SYS_REPLICA_DOES_NOT_EXIST;
}

// Only accept the resolved hierarchy if it descends from the resource specified by the user.
if (const auto* resc_name = getValByKey(&dataObjInp->condInput, RESC_NAME_KW);
nullptr != resc_name && !irods::hierarchy_parser{hierarchy}.contains(resc_name))
{
constexpr auto error_code = SYS_REPLICA_INACCESSIBLE;
const auto msg = fmt::format(
"{}: hierarchy descending from requested resource name does not have a replica or the replica is "
"inaccessible at this time. [path=[{}], requested resource name=[{}], resolved hierarchy=[{}]]",
__func__,
dataObjInp->objPath,
resc_name,
hierarchy);

addRErrorMsg(&rsComm->rError, error_code, msg.data());
log_api::warn(msg);
return error_code;
}

auto replica = *maybe_replica;

irods::log(LOG_DEBUG, fmt::format(
Expand Down
6 changes: 6 additions & 0 deletions server/api/src/rsDataObjPhymv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ namespace

cond_input[RESC_HIER_STR_KW] = resc_mgr.get_hier_to_root_for_resc(parser.last_resc());

// Erase RESC_NAME_KW because the target hierarchy has already been worked out.
cond_input.erase(RESC_NAME_KW);

return source_data_obj_inp;
} // init_source_replica_input

Expand Down Expand Up @@ -329,6 +332,9 @@ namespace
cond_input[DEST_RESC_HIER_STR_KW] = full_hierarchy;
cond_input[RESC_HIER_STR_KW] = full_hierarchy;

// Erase DEST_RESC_NAME_KW because the target hierarchy has already been worked out.
cond_input.erase(DEST_RESC_NAME_KW);

return destination_data_obj_inp;
} // init_destination_replica_input

Expand Down
Loading