Skip to content

Commit c480c9e

Browse files
[enabler][1252][zos_copy]Opercmd_usage_for_disposition (#1917)
* First iteration * Debug * Solutino using iefbr14 * Add fragment * Fix lint * Add fragment * Add debug * Fix test * Return value * Fix required shr * Remove test cases that accest to TSO command * Change calls of datasets * Rechange logic and delete test cases for opercomand * Update changelogs/fragments/1917-Opercmd_usage_for_disposition.yml Co-authored-by: Fernando Flores <[email protected]> * Fix comments * Change validation as new stderr is out --------- Co-authored-by: Fernando Flores <[email protected]>
1 parent d6c5221 commit c480c9e

File tree

6 files changed

+48
-164
lines changed

6 files changed

+48
-164
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
minor_changes:
2+
- zos_copy - Removes the need to allow READ access to MVS.MCSOPER.ZOAU to execute the module by changing how the module checks if a data set is locked.
3+
(https://github.com/ansible-collections/ibm_zos_core/pull/1917).

plugins/module_utils/data_set.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,25 @@ def _process_listcat_output(self, output):
19901990
).replace("-", "")
19911991
return result
19921992

1993+
@staticmethod
1994+
def verify_dataset_disposition(data_set, disposition):
1995+
"""Function to call iefbr14 to verify the dsp of data_set
1996+
1997+
Args:
1998+
data_set {str}: Name of dataset to verify the dsp=shr
1999+
2000+
Returns:
2001+
bool: If the data_set is in dsp=shr
2002+
"""
2003+
data_set_disp = f"{data_set},{disposition}"
2004+
dd = {"dd" : data_set_disp}
2005+
rc, stdput, stderr = mvs_cmd.iefbr14(dds=dd)
2006+
2007+
if rc != 0:
2008+
return True
2009+
else:
2010+
return False
2011+
19932012

19942013
class MVSDataSet():
19952014
"""

plugins/module_utils/mvs_cmd.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,24 @@ def amaterse(cmd="", dds=None, authorized=False):
249249
return _run_mvs_command("AMATERSE", "", dds, authorized)
250250

251251

252+
def iefbr14(dds=None):
253+
"""IEFBR14 performs no action other than return a completion code of 0;
254+
however, "running" this utility invokes other system components that
255+
perform useful tasks.
256+
257+
Parameters
258+
----------
259+
dds : dict
260+
Any DD statements to pass to MVS command.
261+
262+
Returns
263+
-------
264+
tuple(int, str, str)
265+
A tuple of return code, stdout and stderr.
266+
"""
267+
return _run_mvs_command("IEFBR14", "", dds, False)
268+
269+
252270
def adrdssu(cmd, dds=None, authorized=False):
253271
"""The ADRDSSU program enables you to copy SMS-compressed data without
254272
having to decompress the data and also provides support for copying
@@ -302,7 +320,10 @@ def _run_mvs_command(pgm, cmd, dd=None, authorized=False, tmphlq=None):
302320
mvscmd += "auth"
303321
if tmphlq:
304322
mvscmd += " -Q={0}".format(tmphlq)
305-
mvscmd += " --pgm={0} --{1}=* --{2}=stdin".format(pgm, sysprint, sysin)
323+
if pgm == "IEFBR14":
324+
mvscmd += " --pgm={0}".format(pgm)
325+
else:
326+
mvscmd += " --pgm={0} --{1}=* --{2}=stdin".format(pgm, sysprint, sysin)
306327
if dd:
307328
for k, v in dd.items():
308329
mvscmd += " --{0}={1}".format(k, v)

plugins/modules/zos_copy.py

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -946,10 +946,9 @@
946946
from re import match as fullmatch
947947

948948
try:
949-
from zoautil_py import datasets, opercmd, gdgs
949+
from zoautil_py import datasets, gdgs
950950
except Exception:
951951
datasets = ZOAUImportError(traceback.format_exc())
952-
opercmd = ZOAUImportError(traceback.format_exc())
953952
gdgs = ZOAUImportError(traceback.format_exc())
954953

955954
try:
@@ -3158,56 +3157,6 @@ def normalize_line_endings(src, encoding=None):
31583157
return src
31593158

31603159

3161-
def data_set_locked(dataset_name):
3162-
"""
3163-
Checks if a data set is in use and therefore locked (DISP=SHR), which
3164-
is often caused by a long running task. Returns a boolean value to indicate the data set status.
3165-
3166-
Parameters
3167-
----------
3168-
dataset_name (str):
3169-
The data set name used to check if there is a lock.
3170-
3171-
Returns
3172-
-------
3173-
bool
3174-
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.
3181-
"""
3182-
# Using operator command "D GRS,RES=(*,{dataset_name})" to detect if a data set
3183-
# is in use, when a data set is in use it will have "EXC/SHR and SHARE"
3184-
# in the result with a length greater than 4.
3185-
result = dict()
3186-
result["stdout"] = []
3187-
dataset_name = dataset_name.replace("$", "\\$")
3188-
command_dgrs = "D GRS,RES=(*,{0})".format(dataset_name)
3189-
3190-
try:
3191-
response = opercmd.execute(command=command_dgrs)
3192-
stdout = response.stdout_response
3193-
3194-
if stdout is not None:
3195-
for out in stdout.split("\n"):
3196-
if out:
3197-
result["stdout"].append(out)
3198-
if len(result["stdout"]) <= 4 and "NO REQUESTORS FOR RESOURCE" in stdout:
3199-
return False
3200-
3201-
return True
3202-
except zoau_exceptions.ZOAUException as copy_exception:
3203-
raise CopyOperationError(
3204-
msg="Unable to determine if the dest {0} is in use.".format(dataset_name),
3205-
rc=copy_exception.response.rc,
3206-
stdout=copy_exception.response.stdout_response,
3207-
stderr=copy_exception.response.stderr_response
3208-
)
3209-
3210-
32113160
def run_module(module, arg_def):
32123161
"""Initialize module
32133162
@@ -3548,7 +3497,7 @@ def run_module(module, arg_def):
35483497
# ********************************************************************
35493498
if dest_exists and dest_ds_type != "USS":
35503499
if not force_lock:
3551-
is_dest_lock = data_set_locked(dataset_name=dest_name)
3500+
is_dest_lock = data_set.DataSetUtils.verify_dataset_disposition(data_set=data_set.extract_dsname(dest_name), disposition="old")
35523501
if is_dest_lock:
35533502
module.fail_json(
35543503
msg="Unable to write to dest '{0}' because a task is accessing the data set.".format(

tests/functional/modules/test_zos_copy_func.py

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

1414
from __future__ import absolute_import, division, print_function
1515

16-
from ibm_zos_core.tests.helpers.users import ManagedUserType, ManagedUser
1716
import pytest
1817
import os
1918
import shutil
@@ -2089,113 +2088,6 @@ def test_copy_dest_lock(ansible_zos_module, ds_type, f_lock ):
20892088
hosts.all.zos_data_set(name=data_set_2, state="absent")
20902089

20912090

2092-
def test_copy_dest_lock_test_with_no_opercmd_access_pds_without_force_lock(ansible_zos_module, z_python_interpreter):
2093-
"""
2094-
This tests the module exeception raised 'msg="Unable to determine if the source {0} is in use.".format(dataset_name)'.
2095-
This this a wrapper for the actual test case `managed_user_copy_dest_lock_test_with_no_opercmd_access`.
2096-
"""
2097-
managed_user = None
2098-
managed_user_test_case_name = "managed_user_copy_dest_lock_test_with_no_opercmd_access"
2099-
try:
2100-
# Initialize the Managed user API from the pytest fixture.
2101-
managed_user = ManagedUser.from_fixture(ansible_zos_module, z_python_interpreter)
2102-
2103-
# Important: Execute the test case with the managed users execution utility.
2104-
managed_user.execute_managed_user_test(
2105-
managed_user_test_case = managed_user_test_case_name,debug = True,
2106-
verbose = False, managed_user_type=ManagedUserType.ZOAU_LIMITED_ACCESS_OPERCMD)
2107-
2108-
finally:
2109-
# Delete the managed user on the remote host to avoid proliferation of users.
2110-
managed_user.delete_managed_user()
2111-
2112-
@pytest.mark.parametrize("ds_type, f_lock",[
2113-
( "pds", False), # Module exception raised msg="Unable to determine if the source {0} is in use.".format(dataset_name)
2114-
( "pdse", False), # Module exception raised msg="Unable to determine if the source {0} is in use.".format(dataset_name)
2115-
( "seq", False), # Module exception raised msg="Unable to determine if the source {0} is in use.".format(dataset_name)
2116-
( "seq", True), # Opercmd is not called so a user with limited UACC will not matter and will succeed
2117-
])
2118-
def managed_user_copy_dest_lock_test_with_no_opercmd_access(ansible_zos_module, ds_type, f_lock ):
2119-
"""
2120-
When force_lock option is false, it exercises the opercmd call which requires RACF universal access.
2121-
This negative test will ensure that if the user does not have RACF universal access that the module
2122-
not halt execution and instead bubble up the ZOAU exception.
2123-
"""
2124-
hosts = ansible_zos_module
2125-
2126-
data_set_1 = get_tmp_ds_name()
2127-
data_set_2 = get_tmp_ds_name()
2128-
member_1 = "MEM1"
2129-
2130-
if ds_type == "pds" or ds_type == "pdse":
2131-
src_data_set = data_set_1 + "({0})".format(member_1)
2132-
dest_data_set = data_set_2 + "({0})".format(member_1)
2133-
else:
2134-
src_data_set = data_set_1
2135-
dest_data_set = data_set_2
2136-
try:
2137-
hosts.all.zos_data_set(name=data_set_1, state="present", type=ds_type, replace=True)
2138-
hosts.all.zos_data_set(name=data_set_2, state="present", type=ds_type, replace=True)
2139-
if ds_type == "pds" or ds_type == "pdse":
2140-
hosts.all.zos_data_set(name=src_data_set, state="present", type="member", replace=True)
2141-
hosts.all.zos_data_set(name=dest_data_set, state="present", type="member", replace=True)
2142-
# copy text_in source
2143-
hosts.all.shell(cmd="decho \"{0}\" \"{1}\"".format(DUMMY_DATA, src_data_set))
2144-
# copy/compile c program and copy jcl to hold data set lock for n seconds in background(&)
2145-
temp_dir = get_random_file_name(dir=TMP_DIRECTORY)
2146-
hosts.all.zos_copy(content=c_pgm, dest=f'{temp_dir}/pdse-lock.c', force=True)
2147-
hosts.all.zos_copy(
2148-
content=call_c_jcl.format(temp_dir, dest_data_set),
2149-
dest=f'{temp_dir}/call_c_pgm.jcl',
2150-
force=True
2151-
)
2152-
hosts.all.shell(cmd="xlc -o pdse-lock pdse-lock.c", chdir=f"{temp_dir}/")
2153-
# submit jcl
2154-
hosts.all.shell(cmd="submit call_c_pgm.jcl", chdir=f"{temp_dir}/")
2155-
# pause to ensure c code acquires lock
2156-
time.sleep(10)
2157-
results = hosts.all.zos_copy(
2158-
src = src_data_set,
2159-
dest = dest_data_set,
2160-
remote_src = True,
2161-
force=True,
2162-
force_lock=f_lock,
2163-
)
2164-
for result in results.contacted.values():
2165-
if f_lock:
2166-
assert result.get("changed") == True
2167-
assert result.get("msg") is None
2168-
# verify that the content is the same
2169-
verify_copy = hosts.all.shell(
2170-
cmd="dcat \"{0}\"".format(dest_data_set),
2171-
executable=SHELL_EXECUTABLE,
2172-
)
2173-
for vp_result in verify_copy.contacted.values():
2174-
verify_copy_2 = hosts.all.shell(
2175-
cmd="dcat \"{0}\"".format(src_data_set),
2176-
executable=SHELL_EXECUTABLE,
2177-
)
2178-
for vp_result_2 in verify_copy_2.contacted.values():
2179-
assert vp_result_2.get("stdout") == vp_result.get("stdout")
2180-
elif not f_lock:
2181-
assert result.get("failed") is True
2182-
assert result.get("changed") == False
2183-
assert "Unable to determine if the dest" in result.get("msg")
2184-
assert "BGYSC0819E Insufficient security authorization for resource MVS.MCSOPER.ZOAU in class OPERCMDS" in result.get("stderr")
2185-
assert result.get("rc") == 6
2186-
finally:
2187-
# extract pid
2188-
ps_list_res = hosts.all.shell(cmd="ps -e | grep -i 'pdse-lock'")
2189-
# kill process - release lock - this also seems to end the job
2190-
pid = list(ps_list_res.contacted.values())[0].get('stdout').strip().split(' ')[0]
2191-
hosts.all.shell(cmd="kill 9 {0}".format(pid.strip()))
2192-
# clean up c code/object/executable files, jcl
2193-
hosts.all.shell(cmd=f'rm -r {temp_dir}')
2194-
# remove pdse
2195-
hosts.all.zos_data_set(name=data_set_1, state="absent")
2196-
hosts.all.zos_data_set(name=data_set_2, state="absent")
2197-
2198-
21992091
@pytest.mark.uss
22002092
@pytest.mark.seq
22012093
def test_copy_file_record_length_to_sequential_data_set(ansible_zos_module):

tests/functional/modules/test_zos_job_output_func.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ def test_zos_job_output_invalid_owner(ansible_zos_module):
7777
hosts = ansible_zos_module
7878
results = hosts.all.zos_job_output(owner="INVALID")
7979
for result in results.contacted.values():
80-
assert result.get("changed") is False
81-
assert result.get("jobs")[0].get("ret_code").get("msg_txt") is not None
80+
assert result.get("failed") is True
81+
assert result.get("stderr") is not None
8282

8383

8484
def test_zos_job_output_reject(ansible_zos_module):

0 commit comments

Comments
 (0)