Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fix, zos_copy returns an error message when a concurrent copy fails #794

Merged
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6ea0505
Test case to validate bug does not happen
AndreMarcel99 May 24, 2023
44be9e1
First iteration for solutions
AndreMarcel99 May 30, 2023
2fbc9c2
First proposal to validate_disposition
AndreMarcel99 May 31, 2023
d400a39
Remove unecesary test
AndreMarcel99 May 31, 2023
5c82056
Solvin unecesary code
AndreMarcel99 May 31, 2023
9c37938
Cover all cases with bug or false positive
AndreMarcel99 May 31, 2023
3341f24
Add test case to ensure behaviour
AndreMarcel99 Jun 2, 2023
47a4384
Get the better version of test case
AndreMarcel99 Jun 5, 2023
a2cc1b7
Add fragment
AndreMarcel99 Jun 5, 2023
a548776
Solve identation
AndreMarcel99 Jun 5, 2023
0b22761
Solve identation
AndreMarcel99 Jun 7, 2023
c86386c
Solve identation
AndreMarcel99 Jun 7, 2023
16e6e09
Merge branch 'dev' into bugfix/586/Return_error_message_when_concurre…
AndreMarcel99 Jun 8, 2023
496eaa2
Solve error in cleanup folders
AndreMarcel99 Jun 9, 2023
3c7c94d
Merge branch 'dev' into bugfix/586/Return_error_message_when_concurre…
AndreMarcel99 Jun 12, 2023
6276493
Merge branch 'dev' into bugfix/586/Return_error_message_when_concurre…
AndreMarcel99 Jun 16, 2023
6205f64
Change function name
AndreMarcel99 Jun 20, 2023
13f1002
Merge branch 'dev' into bugfix/586/Return_error_message_when_concurre…
AndreMarcel99 Jun 20, 2023
3bac701
Merge branch 'bugfix/586/Return_error_message_when_concurrent_copy_fa…
AndreMarcel99 Jun 22, 2023
53db8f2
Change variables names
AndreMarcel99 Jun 22, 2023
53ae23f
Solve wrote and write
AndreMarcel99 Jun 27, 2023
57ca20c
Merge branch 'dev' into bugfix/586/Return_error_message_when_concurre…
AndreMarcel99 Jun 28, 2023
2648b99
Update changelog entry
ddimatos Jul 5, 2023
ee82445
Merge branch 'dev' into bugfix/586/Return_error_message_when_concurre…
AndreMarcel99 Jul 6, 2023
5173cf6
Better verbose and function name
AndreMarcel99 Jul 6, 2023
1c1fe64
Better message
AndreMarcel99 Jul 6, 2023
0f7e2e4
Solve certification tests
AndreMarcel99 Jul 6, 2023
1a0f74b
Clearer and eficient version
AndreMarcel99 Jul 7, 2023
928ce51
continuation line over-indented solve
AndreMarcel99 Jul 7, 2023
db714e1
continuation line over-indented solve
AndreMarcel99 Jul 7, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
bugfixes:
- zos_copy - Reported a false positive such that the response would have
`changed=true` when copying from a source (src) or destination (dest)
data set that was in use (DISP=SHR). This change now displays an appropriate
error message and returns `changed=false`.
(https://github.com/ansible-collections/ibm_zos_core/pull/794).
61 changes: 60 additions & 1 deletion plugins/modules/zos_copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@
from re import match as fullmatch

try:
from zoautil_py import datasets
from zoautil_py import datasets, opercmd
except Exception:
datasets = MissingZOAUImport()

Expand Down Expand Up @@ -2245,6 +2245,37 @@ def normalize_line_endings(src, encoding=None):
return src


def data_set_locked(dataset_name):
"""
Checks if a data set has a lock on it meaning the data set is opened
with DISP=SHR, often by a long running task.

Arguments:
dataset_name (str) - the data set name used to check if there is a lock.

Returns:
bool -- True if the dataset has not lock false if has lock.
ddimatos marked this conversation as resolved.
Show resolved Hide resolved
"""
# Using operator command "D GRS,RES=(*,{dataset_name})" to detect if a data set
# is in use, when a data set is in use it will have "EXC/SHR and SHARE"
# in the result with a length greater than 4.
result = dict()
result["stdout"] = []
command_dgrs = "D GRS,RES=(*,{0})".format(dataset_name)
response = opercmd.execute(command=command_dgrs)
stdout = response.stdout_response
if stdout is not None:
for out in stdout.split("\n"):
if out:
result["stdout"].append(out)
if len(result["stdout"]) > 4 and "EXC/SHR" in stdout and "SHARE" in stdout:
ddimatos marked this conversation as resolved.
Show resolved Hide resolved
return True
elif len(result["stdout"]) <= 4 and "NO REQUESTORS FOR RESOURCE" in stdout:
return False
else:
return False


def normalize_line_endings(src, encoding=None):
"""
Normalizes src's encoding to IBM-037 (a dataset's default) and then normalizes
Expand Down Expand Up @@ -2501,6 +2532,34 @@ def run_module(module, arg_def):
)

# ********************************************************************
# To validate the source and dest are not lock in a batch process by
# the machine and not generate a false positive check the disposition
# for try to write in dest and if both src and dest are in lock.
# ********************************************************************
if src_ds_type != "USS" and dest_ds_type != "USS":
is_source_lock = data_set_locked(src_name)
is_dest_lock = data_set_locked(dest_name)
if is_source_lock and is_dest_lock:
module.fail_json(
msg="DATASETS in lock, unable to access'{0}' and unable to write in'{1}'".format(
ddimatos marked this conversation as resolved.
Show resolved Hide resolved
src_name, dest_name
)
)
elif is_dest_lock:
module.fail_json(
msg="DATASET in lock, unable to write in '{0}'".format(
dest_name
ddimatos marked this conversation as resolved.
Show resolved Hide resolved
)
)
elif dest_ds_type != "USS":
ddimatos marked this conversation as resolved.
Show resolved Hide resolved
is_dest_lock = data_set_locked(dest_name)
if is_dest_lock:
module.fail_json(
msg="DATASET in lock, unable to write in '{0}'".format(
dest_name
ddimatos marked this conversation as resolved.
Show resolved Hide resolved
)
)
# ********************************************************************
# Backup should only be performed if dest is an existing file or
# data set. Otherwise ignored.
# ********************************************************************
Expand Down
75 changes: 75 additions & 0 deletions tests/functional/modules/test_zos_copy_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from tempfile import mkstemp
import subprocess


__metaclass__ = type


Expand Down Expand Up @@ -117,6 +118,30 @@

"""

c_pgm="""#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv)
{
char dsname[ strlen(argv[1]) + 4];
sprintf(dsname, "//'%s'", argv[1]);
FILE* member;
member = fopen(dsname, "rb,type=record");
sleep(300);
fclose(member);
return 0;
}
"""

call_c_jcl="""//PDSELOCK JOB MSGCLASS=A,MSGLEVEL=(1,1),NOTIFY=&SYSUID,REGION=0M
//LOCKMEM EXEC PGM=BPXBATCH
//STDPARM DD *
SH /tmp/disp_shr/pdse-lock '{0}({1})'
//STDIN DD DUMMY
//STDOUT DD SYSOUT=*
//STDERR DD SYSOUT=*
//"""

def populate_dir(dir_path):
for i in range(5):
with open(dir_path + "/" + "file" + str(i + 1), "w") as infile:
Expand Down Expand Up @@ -1198,6 +1223,54 @@ def test_ensure_copy_file_does_not_change_permission_on_dest(ansible_zos_module,
hosts.all.file(path=dest_path, state="absent")


@pytest.mark.seq
def test_copy_dest_lock(ansible_zos_module):
DATASET_1 = "USER.PRIVATE.TESTDS"
DATASET_2 = "ADMI.PRIVATE.TESTDS"
MEMBER_1 = "MEM1"
try:
hosts = ansible_zos_module
hosts.all.zos_data_set(name=DATASET_1, state="present", type="pdse", replace=True)
hosts.all.zos_data_set(name=DATASET_2, state="present", type="pdse", replace=True)
hosts.all.zos_data_set(name=DATASET_1 + "({0})".format(MEMBER_1), state="present", type="member", replace=True)
hosts.all.zos_data_set(name=DATASET_2 + "({0})".format(MEMBER_1), state="present", type="member", replace=True)
# copy text_in source
hosts.all.shell(cmd="echo \"{0}\" > {1}".format(DUMMY_DATA, DATASET_2+"({0})".format(MEMBER_1)))
# copy/compile c program and copy jcl to hold data set lock for n seconds in background(&)
hosts.all.zos_copy(content=c_pgm, dest='/tmp/disp_shr/pdse-lock.c', force=True)
hosts.all.zos_copy(
content=call_c_jcl.format(DATASET_1, MEMBER_1),
dest='/tmp/disp_shr/call_c_pgm.jcl',
force=True
)
hosts.all.shell(cmd="xlc -o pdse-lock pdse-lock.c", chdir="/tmp/disp_shr/")
# submit jcl
hosts.all.shell(cmd="submit call_c_pgm.jcl", chdir="/tmp/disp_shr/")
# pause to ensure c code acquires lock
time.sleep(5)
results = hosts.all.zos_copy(
src = DATASET_2 + "({0})".format(MEMBER_1),
dest = DATASET_1 + "({0})".format(MEMBER_1),
remote_src = True,
force = True
)
for result in results.contacted.values():
print(result)
assert result.get("changed") == False
assert result.get("msg") is not None
finally:
# extract pid
ps_list_res = hosts.all.shell(cmd="ps -e | grep -i 'pdse-lock'")
# kill process - release lock - this also seems to end the job
pid = list(ps_list_res.contacted.values())[0].get('stdout').strip().split(' ')[0]
hosts.all.shell(cmd="kill 9 {0}".format(pid.strip()))
# clean up c code/object/executable files, jcl
hosts.all.shell(cmd='rm -r /tmp/disp_shr')
# remove pdse
hosts.all.zos_data_set(name=DATASET_1, state="absent")
hosts.all.zos_data_set(name=DATASET_2, state="absent")


@pytest.mark.uss
@pytest.mark.seq
def test_copy_file_record_length_to_sequential_data_set(ansible_zos_module):
Expand Down Expand Up @@ -2984,6 +3057,7 @@ def test_copy_uss_file_to_existing_sequential_data_set_twice_with_tmphlq_option(
hosts.all.zos_data_set(name=dest, state="absent")



@pytest.mark.parametrize("options", [
dict(src="/etc/profile", dest="/tmp/zos_copy_test_profile",
force=True, is_remote=False, verbosity="-vvvvv", verbosity_level=5),
Expand Down Expand Up @@ -3019,3 +3093,4 @@ def test_display_verbosity_in_zos_copy_plugin(ansible_zos_module, options):

finally:
hosts.all.file(path=options["dest"], state="absent")