Skip to content

Commit 9aa2411

Browse files
Correct zos_copy handling of a zoauresponse during opercmd usage for locked data sets. (#1744)
* Test case updates to test opercmd authentication Signed-off-by: ddimatos <[email protected]> * Mock test to use a enum class to selected a managed user Signed-off-by: ddimatos <[email protected]> * add a users.py helper class with racf commands and users Signed-off-by: ddimatos <[email protected]> * RACF updates Signed-off-by: ddimatos <[email protected]> * Updates to create user Signed-off-by: ddimatos <[email protected]> * Updates to fix creating racf user Signed-off-by: ddimatos <[email protected]> * Added support for other user types Signed-off-by: ddimatos <[email protected]> * Update user.py to controll user access Signed-off-by: ddimatos <[email protected]> * Updated user.py with delete function Signed-off-by: ddimatos <[email protected]> * Upudates to change the original get new user design Signed-off-by: ddimatos <[email protected]> * Updated doc and exceptions Signed-off-by: ddimatos <[email protected]> * Update logic Signed-off-by: ddimatos <[email protected]> * Update logic Signed-off-by: ddimatos <[email protected]> * Updates to support managed users Signed-off-by: ddimatos <[email protected]> * debug stmts Signed-off-by: ddimatos <[email protected]> * fix test case Signed-off-by: ddimatos <[email protected]> * fix test case Signed-off-by: ddimatos <[email protected]> * Debug stmt Signed-off-by: ddimatos <[email protected]> * Debug stmt Signed-off-by: ddimatos <[email protected]> * Debug stmt Signed-off-by: ddimatos <[email protected]> * Debug stmt Signed-off-by: ddimatos <[email protected]> * Debug stmt Signed-off-by: ddimatos <[email protected]> * debug Signed-off-by: ddimatos <[email protected]> * debug Signed-off-by: ddimatos <[email protected]> * debug Signed-off-by: ddimatos <[email protected]> * debug Signed-off-by: ddimatos <[email protected]> * debug Signed-off-by: ddimatos <[email protected]> * bug Signed-off-by: ddimatos <[email protected]> * debug Signed-off-by: ddimatos <[email protected]> * debug Signed-off-by: ddimatos <[email protected]> * Added ssh config append Signed-off-by: ddimatos <[email protected]> * Update zos_copy and pipeline framework to support dynaic users Signed-off-by: ddimatos <[email protected]> * Fixed bug that mixed up dir and files Signed-off-by: ddimatos <[email protected]> * Added a todo comment for AC Signed-off-by: ddimatos <[email protected]> * Update users py to add new execute function Signed-off-by: ddimatos <[email protected]> * updates to complete the ability to use a managed user Signed-off-by: ddimatos <[email protected]> * Fixes E123: closing bracket does not match indentation Signed-off-by: ddimatos <[email protected]> * Correct name of ManagedUseeType to ManagedUserType Co-authored-by: Fernando Flores <[email protected]> * Update tests/functional/modules/test_zos_copy_func.py Co-authored-by: Fernando Flores <[email protected]> * PR review comments addressed Signed-off-by: ddimatos <[email protected]> --------- Signed-off-by: ddimatos <[email protected]> Co-authored-by: Fernando Flores <[email protected]>
1 parent b74b96e commit 9aa2411

File tree

5 files changed

+1107
-30
lines changed

5 files changed

+1107
-30
lines changed

ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ ac_test(){
684684
message_error "Unable to find test configration in ${VENV}/config.yml."
685685
fi
686686

687+
# TODO: Consider adding the -vvvv like so `$CURR_DIR/${file} -vvvv --ignore="${skip}"` so that you can access the verbosity feature of pytest.
687688
if [ "$file" ]; then
688689
. ${VENV_BIN}/activate && export ANSIBLE_LIBRARY=$VENV/ansible_collections/ibm/ibm_zos_core/plugins/modules;export ANSIBLE_CONFIG=$VENV/ansible.cfg;${VENV_BIN}/pytest $CURR_DIR/${file} --ignore="${skip}" --host-pattern=all --zinventory=${VENV}/config.yml ${debug} >&2 ; echo $? >&1
689690
else
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
bugfixes:
2+
- zos_copy - Improve module zos_copy error handling when the user does not have
3+
universal access authority set to UACC(READ) for SAF Profile
4+
'MVS.MCSOPER.ZOAU' and SAF Class OPERCMDS. The module now handles the exception
5+
and returns an informative message.
6+
(https://github.com/ansible-collections/ibm_zos_core/pull/1744).
7+
trivial:
8+
- pipeline - Deliver a new users.py framework that allows functional test cases to
9+
request a managed user type where this user can have limited access to some SAF
10+
profile, or saf class as well as user id's with specific patterns such as including
11+
supported special characters such as '@', '#', etc.
12+
(https://github.com/ansible-collections/ibm_zos_core/pull/1744).

plugins/modules/zos_copy.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3165,32 +3165,46 @@ def data_set_locked(dataset_name):
31653165
31663166
Parameters
31673167
----------
3168-
dataset_name : str
3168+
dataset_name (str):
31693169
The data set name used to check if there is a lock.
31703170
31713171
Returns
31723172
-------
31733173
bool
31743174
True if the data set is locked, or False if the data set is not locked.
3175+
3176+
Raises
3177+
------
3178+
CopyOperationError
3179+
When the user does not have Universal Access Authority to
3180+
ZOAU SAF Profile 'MVS.MCSOPER.ZOAU' and SAF Class OPERCMDS.
31753181
"""
31763182
# Using operator command "D GRS,RES=(*,{dataset_name})" to detect if a data set
31773183
# is in use, when a data set is in use it will have "EXC/SHR and SHARE"
31783184
# in the result with a length greater than 4.
31793185
result = dict()
31803186
result["stdout"] = []
31813187
command_dgrs = "D GRS,RES=(*,{0})".format(dataset_name)
3182-
response = opercmd.execute(command=command_dgrs)
3183-
stdout = response.stdout_response
3184-
if stdout is not None:
3185-
for out in stdout.split("\n"):
3186-
if out:
3187-
result["stdout"].append(out)
3188-
if len(result["stdout"]) > 4 and "EXC/SHR" in stdout and "SHARE" in stdout:
3188+
3189+
try:
3190+
response = opercmd.execute(command=command_dgrs)
3191+
stdout = response.stdout_response
3192+
3193+
if stdout is not None:
3194+
for out in stdout.split("\n"):
3195+
if out:
3196+
result["stdout"].append(out)
3197+
if len(result["stdout"]) <= 4 and "NO REQUESTORS FOR RESOURCE" in stdout:
3198+
return False
3199+
31893200
return True
3190-
elif len(result["stdout"]) <= 4 and "NO REQUESTORS FOR RESOURCE" in stdout:
3191-
return False
3192-
else:
3193-
return False
3201+
except zoau_exceptions.ZOAUException as copy_exception:
3202+
raise CopyOperationError(
3203+
msg="Unable to determine if the dest {0} is in use.".format(dataset_name),
3204+
rc=copy_exception.response.rc,
3205+
stdout=copy_exception.response.stdout_response,
3206+
stderr=copy_exception.response.stderr_response
3207+
)
31943208

31953209

31963210
def run_module(module, arg_def):

tests/functional/modules/test_zos_copy_func.py

Lines changed: 138 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from __future__ import absolute_import, division, print_function
1515

16+
from ibm_zos_core.tests.helpers.users import ManagedUserType, ManagedUser
1617
import pytest
1718
import os
1819
import shutil
@@ -365,6 +366,7 @@ def link_loadlib_from_cobol(hosts, cobol_src_pds, cobol_src_mem, loadlib_pds, lo
365366
wait_time_s=60
366367
)
367368
for result in job_result.contacted.values():
369+
print(result)
368370
rc = result.get("jobs")[0].get("ret_code").get("code")
369371
finally:
370372
hosts.all.file(path=temp_jcl_uss_path, state="absent")
@@ -1960,8 +1962,15 @@ def test_ensure_copy_file_does_not_change_permission_on_dest(ansible_zos_module,
19601962

19611963

19621964
@pytest.mark.seq
1963-
@pytest.mark.parametrize("ds_type", [ "pds", "pdse", "seq"])
1964-
def test_copy_dest_lock(ansible_zos_module, ds_type):
1965+
@pytest.mark.parametrize("ds_type, f_lock",[
1966+
( "pds", True), # Success path, pds locked, force_lock enabled and user authorized
1967+
( "pdse", True), # Success path, pdse locked, force_lock enabled and user authorized
1968+
( "seq", True), # Success path, seq locked, force_lock enabled and user authorized
1969+
( "pds", False), # Module exits with: Unable to write to dest '{0}' because a task is accessing the data set."
1970+
( "pdse", False), # Module exits with: Unable to write to dest '{0}' because a task is accessing the data set."
1971+
( "seq", False), # Module exits with: Unable to write to dest '{0}' because a task is accessing the data set."
1972+
])
1973+
def test_copy_dest_lock(ansible_zos_module, ds_type, f_lock ):
19651974
hosts = ansible_zos_module
19661975
data_set_1 = get_tmp_ds_name()
19671976
data_set_2 = get_tmp_ds_name()
@@ -1973,7 +1982,6 @@ def test_copy_dest_lock(ansible_zos_module, ds_type):
19731982
src_data_set = data_set_1
19741983
dest_data_set = data_set_2
19751984
try:
1976-
hosts = ansible_zos_module
19771985
hosts.all.zos_data_set(name=data_set_1, state="present", type=ds_type, replace=True)
19781986
hosts.all.zos_data_set(name=data_set_2, state="present", type=ds_type, replace=True)
19791987
if ds_type == "pds" or ds_type == "pdse":
@@ -1999,27 +2007,139 @@ def test_copy_dest_lock(ansible_zos_module, ds_type):
19992007
dest = dest_data_set,
20002008
remote_src = True,
20012009
force=True,
2002-
force_lock=True,
2010+
force_lock=f_lock,
20032011
)
20042012
for result in results.contacted.values():
20052013
print(result)
2006-
assert result.get("changed") == True
2007-
assert result.get("msg") is None
2008-
# verify that the content is the same
2009-
verify_copy = hosts.all.shell(
2010-
cmd="dcat \"{0}\"".format(dest_data_set),
2011-
executable=SHELL_EXECUTABLE,
2012-
)
2013-
for vp_result in verify_copy.contacted.values():
2014-
print(vp_result)
2015-
verify_copy_2 = hosts.all.shell(
2016-
cmd="dcat \"{0}\"".format(src_data_set),
2014+
if f_lock: #and apf_auth_user:
2015+
assert result.get("changed") == True
2016+
assert result.get("msg") is None
2017+
# verify that the content is the same
2018+
verify_copy = hosts.all.shell(
2019+
cmd="dcat \"{0}\"".format(dest_data_set),
20172020
executable=SHELL_EXECUTABLE,
20182021
)
2019-
for vp_result_2 in verify_copy_2.contacted.values():
2020-
print(vp_result_2)
2021-
assert vp_result_2.get("stdout") == vp_result.get("stdout")
2022+
for vp_result in verify_copy.contacted.values():
2023+
print(vp_result)
2024+
verify_copy_2 = hosts.all.shell(
2025+
cmd="dcat \"{0}\"".format(src_data_set),
2026+
executable=SHELL_EXECUTABLE,
2027+
)
2028+
for vp_result_2 in verify_copy_2.contacted.values():
2029+
print(vp_result_2)
2030+
assert vp_result_2.get("stdout") == vp_result.get("stdout")
2031+
elif not f_lock:
2032+
assert result.get("failed") is True
2033+
assert result.get("changed") == False
2034+
assert "because a task is accessing the data set" in result.get("msg")
2035+
assert result.get("rc") is None
2036+
finally:
2037+
# extract pid
2038+
ps_list_res = hosts.all.shell(cmd="ps -e | grep -i 'pdse-lock'")
2039+
# kill process - release lock - this also seems to end the job
2040+
pid = list(ps_list_res.contacted.values())[0].get('stdout').strip().split(' ')[0]
2041+
hosts.all.shell(cmd="kill 9 {0}".format(pid.strip()))
2042+
# clean up c code/object/executable files, jcl
2043+
hosts.all.shell(cmd=f'rm -r {temp_dir}')
2044+
# remove pdse
2045+
hosts.all.zos_data_set(name=data_set_1, state="absent")
2046+
hosts.all.zos_data_set(name=data_set_2, state="absent")
2047+
2048+
2049+
def test_copy_dest_lock_test_with_no_opercmd_access_pds_without_force_lock(ansible_zos_module):
2050+
"""
2051+
This tests the module exeception raised 'msg="Unable to determine if the source {0} is in use.".format(dataset_name)'.
2052+
This this a wrapper for the actual test case `managed_user_copy_dest_lock_test_with_no_opercmd_access`.
2053+
"""
2054+
managed_user = None
2055+
managed_user_test_case_name = "managed_user_copy_dest_lock_test_with_no_opercmd_access"
2056+
try:
2057+
# Initialize the Managed user API from the pytest fixture.
2058+
managed_user = ManagedUser.from_fixture(ansible_zos_module)
2059+
2060+
# Important: Execute the test case with the managed users execution utility.
2061+
managed_user.execute_managed_user_test(
2062+
managed_user_test_case = managed_user_test_case_name,debug = True,
2063+
verbose = False, managed_user_type=ManagedUserType.ZOAU_LIMITED_ACCESS_OPERCMD)
2064+
2065+
finally:
2066+
# Delete the managed user on the remote host to avoid proliferation of users.
2067+
managed_user.delete_managed_user()
2068+
2069+
@pytest.mark.parametrize("ds_type, f_lock",[
2070+
( "pds", False), # Module exception raised msg="Unable to determine if the source {0} is in use.".format(dataset_name)
2071+
( "pdse", False), # Module exception raised msg="Unable to determine if the source {0} is in use.".format(dataset_name)
2072+
( "seq", False), # Module exception raised msg="Unable to determine if the source {0} is in use.".format(dataset_name)
2073+
( "seq", True), # Opercmd is not called so a user with limited UACC will not matter and will succeed
2074+
])
2075+
def managed_user_copy_dest_lock_test_with_no_opercmd_access(ansible_zos_module, ds_type, f_lock ):
2076+
"""
2077+
When force_lock option is false, it exercises the opercmd call which requires RACF universal access.
2078+
This negative test will ensure that if the user does not have RACF universal access that the module
2079+
not halt execution and instead bubble up the ZOAU exception.
2080+
"""
2081+
hosts = ansible_zos_module
20222082

2083+
data_set_1 = get_tmp_ds_name()
2084+
data_set_2 = get_tmp_ds_name()
2085+
member_1 = "MEM1"
2086+
2087+
if ds_type == "pds" or ds_type == "pdse":
2088+
src_data_set = data_set_1 + "({0})".format(member_1)
2089+
dest_data_set = data_set_2 + "({0})".format(member_1)
2090+
else:
2091+
src_data_set = data_set_1
2092+
dest_data_set = data_set_2
2093+
try:
2094+
hosts.all.zos_data_set(name=data_set_1, state="present", type=ds_type, replace=True)
2095+
hosts.all.zos_data_set(name=data_set_2, state="present", type=ds_type, replace=True)
2096+
if ds_type == "pds" or ds_type == "pdse":
2097+
hosts.all.zos_data_set(name=src_data_set, state="present", type="member", replace=True)
2098+
hosts.all.zos_data_set(name=dest_data_set, state="present", type="member", replace=True)
2099+
# copy text_in source
2100+
hosts.all.shell(cmd="decho \"{0}\" \"{1}\"".format(DUMMY_DATA, src_data_set))
2101+
# copy/compile c program and copy jcl to hold data set lock for n seconds in background(&)
2102+
temp_dir = get_random_file_name(dir=TMP_DIRECTORY)
2103+
hosts.all.zos_copy(content=c_pgm, dest=f'{temp_dir}/pdse-lock.c', force=True)
2104+
hosts.all.zos_copy(
2105+
content=call_c_jcl.format(temp_dir, dest_data_set),
2106+
dest=f'{temp_dir}/call_c_pgm.jcl',
2107+
force=True
2108+
)
2109+
hosts.all.shell(cmd="xlc -o pdse-lock pdse-lock.c", chdir=f"{temp_dir}/")
2110+
# submit jcl
2111+
hosts.all.shell(cmd="submit call_c_pgm.jcl", chdir=f"{temp_dir}/")
2112+
# pause to ensure c code acquires lock
2113+
time.sleep(10)
2114+
results = hosts.all.zos_copy(
2115+
src = src_data_set,
2116+
dest = dest_data_set,
2117+
remote_src = True,
2118+
force=True,
2119+
force_lock=f_lock,
2120+
)
2121+
for result in results.contacted.values():
2122+
if f_lock:
2123+
assert result.get("changed") == True
2124+
assert result.get("msg") is None
2125+
# verify that the content is the same
2126+
verify_copy = hosts.all.shell(
2127+
cmd="dcat \"{0}\"".format(dest_data_set),
2128+
executable=SHELL_EXECUTABLE,
2129+
)
2130+
for vp_result in verify_copy.contacted.values():
2131+
verify_copy_2 = hosts.all.shell(
2132+
cmd="dcat \"{0}\"".format(src_data_set),
2133+
executable=SHELL_EXECUTABLE,
2134+
)
2135+
for vp_result_2 in verify_copy_2.contacted.values():
2136+
assert vp_result_2.get("stdout") == vp_result.get("stdout")
2137+
elif not f_lock:
2138+
assert result.get("failed") is True
2139+
assert result.get("changed") == False
2140+
assert "Unable to determine if the dest" in result.get("msg")
2141+
assert "BGYSC0819E Insufficient security authorization for resource MVS.MCSOPER.ZOAU in class OPERCMDS" in result.get("stderr")
2142+
assert result.get("rc") == 6
20232143
finally:
20242144
# extract pid
20252145
ps_list_res = hosts.all.shell(cmd="ps -e | grep -i 'pdse-lock'")

0 commit comments

Comments
 (0)