diff --git a/changelogs/fragments/731-zos_linefile-disposition_share.yaml b/changelogs/fragments/731-zos_linefile-disposition_share.yaml new file mode 100644 index 000000000..da6dbc19b --- /dev/null +++ b/changelogs/fragments/731-zos_linefile-disposition_share.yaml @@ -0,0 +1,6 @@ +minor_changes: +- zos_lineinfile - would access data sets with exclusive access so no other + task can read the data, this enhancement allows for a data set to be opened + with a disposition set to share so that other tasks can access the data when + option `force` is set to `true`. + (https://github.com/ansible-collections/ibm_zos_core/pull/731) \ No newline at end of file diff --git a/plugins/modules/zos_lineinfile.py b/plugins/modules/zos_lineinfile.py index 7a26ce299..c2a7a719c 100644 --- a/plugins/modules/zos_lineinfile.py +++ b/plugins/modules/zos_lineinfile.py @@ -173,6 +173,18 @@ required: false type: str default: IBM-1047 + force: + description: + - Specifies that the data set can be shared with others during an update + which results in the data set you are updating to be simultaneously + updated by others. + - This is helpful when a data set is being used in a long running process + such as a started task and you are wanting to update or read. + - The C(force) option enables sharing of data sets through the disposition + I(DISP=SHR). + required: false + type: bool + default: false notes: - It is the playbook author or user's responsibility to avoid files that should not be encoded, such as binary files. A user is described @@ -218,6 +230,14 @@ regexp: '^(.*)User(\d+)m(.*)$' line: '\1APPUser\3' backrefs: yes + +- name: Add a line to a member while a task is in execution + zos_lineinfile: + src: SOME.PARTITIONED.DATA.SET(DATA) + insertafter: EOF + line: 'Should be a working test now' + force: True + """ RETURN = r""" @@ -271,7 +291,7 @@ DS_TYPE = ['PS', 'PO'] -def present(src, line, regexp, ins_aft, ins_bef, encoding, first_match, backrefs): +def present(src, line, regexp, ins_aft, ins_bef, encoding, first_match, backrefs, force): """Replace a line with the matching regex pattern Insert a line before/after the matching pattern Insert a line at BOF/EOF @@ -292,6 +312,7 @@ def present(src, line, regexp, ins_aft, ins_bef, encoding, first_match, backrefs encoding: {str} -- Encoding of the src. first_match: {bool} -- Take the first matching regex pattern. backrefs: {bool} -- Back reference + force: {bool} -- force for modify a member part of a task in execution Returns: str -- Information in JSON format. keys: @@ -310,10 +331,11 @@ def present(src, line, regexp, ins_aft, ins_bef, encoding, first_match, backrefs backref=backrefs, state=True, debug=True, + force=force, ) -def absent(src, line, regexp, encoding): +def absent(src, line, regexp, encoding, force): """Delete lines with matching regex pattern Arguments: @@ -322,6 +344,7 @@ def absent(src, line, regexp, encoding): regexp will be ignored. regexp: {str} -- The regular expression to look for in every line of the src. encoding: {str} -- Encoding of the src. + force: {bool} -- force for modify a member part of a task in execution Returns: str -- Information in JSON format. keys: @@ -329,7 +352,7 @@ def absent(src, line, regexp, encoding): found: {int} -- Number of matching regex pattern changed: {bool} -- Indicates if the source was modified. """ - return datasets.lineinfile(src, line, regex=regexp, encoding=encoding, state=False, debug=True) + return datasets.lineinfile(src, line, regex=regexp, encoding=encoding, state=False, debug=True, force=force) def quotedString(string): @@ -364,7 +387,8 @@ def main(): backup_name=dict(type='str', required=False, default=None), firstmatch=dict(type='bool', default=False), encoding=dict(type='str', default="IBM-1047"), - tmp_hlq=dict(type='str', required=False, default=None) + tmp_hlq=dict(type='str', required=False, default=None), + force=dict(type='bool', required=False, default=False) ) module = AnsibleModule( argument_spec=module_args, @@ -385,6 +409,7 @@ def main(): firstmatch=dict(arg_type="bool", required=False, default=False), backrefs=dict(arg_type="bool", dependencies=['regexp'], required=False, default=False), tmp_hlq=dict(type='qualifier_or_empty', required=False, default=None), + force=dict(arg_type='bool', required=False, default=False), mutually_exclusive=[["insertbefore", "insertafter"]],) try: @@ -406,6 +431,7 @@ def main(): ins_bef = parsed_args.get('insertbefore') encoding = parsed_args.get('encoding') tmphlq = parsed_args.get('tmp_hlq') + force = parsed_args.get('force') if parsed_args.get('state') == 'present': if backrefs and regexp is None: @@ -453,9 +479,10 @@ def main(): # state=present, insert/replace a line with matching regex pattern # state=absent, delete lines with matching regex pattern if parsed_args.get('state') == 'present': - return_content = present(src, quotedString(line), quotedString(regexp), quotedString(ins_aft), quotedString(ins_bef), encoding, firstmatch, backrefs) + return_content = present(src, quotedString(line), quotedString(regexp), quotedString(ins_aft), quotedString(ins_bef), encoding, firstmatch, + backrefs, force) else: - return_content = absent(src, quotedString(line), quotedString(regexp), encoding) + return_content = absent(src, quotedString(line), quotedString(regexp), encoding, force) stdout = return_content.stdout_response stderr = return_content.stderr_response rc = return_content.rc diff --git a/tests/functional/modules/test_zos_blockinfile_func.py b/tests/functional/modules/test_zos_blockinfile_func.py index f6b735487..7cd92c9e5 100644 --- a/tests/functional/modules/test_zos_blockinfile_func.py +++ b/tests/functional/modules/test_zos_blockinfile_func.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) IBM Corporation 2020, 2022 +# Copyright (c) IBM Corporation 2020, 2022, 2023 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,6 +17,8 @@ DsGeneral, DsNotSupportedHelper, DsGeneralResultKeyMatchesRegex, + DsGeneralForce, + DsGeneralForceFail, ) import os import sys @@ -238,6 +240,14 @@ test_ds_block_insertafter_eof_with_backup_name=dict( block="export ZOAU_ROOT\nexport ZOAU_HOME\nexport ZOAU_DIR", state="present", backup=True, backup_name=MVS_BACKUP_DS), + test_ds_block_insertafter_regex_force=dict( + path="",insertafter="ZOAU_ROOT=", + block="ZOAU_ROOT=/mvsutil-develop_dsed\nZOAU_HOME=\\$ZOAU_ROOT\nZOAU_DIR=\\$ZOAU_ROOT", + state="present", force=True), + test_ds_block_insertafter_regex_force_fail=dict( + path="",insertafter="ZOAU_ROOT=", + block="ZOAU_ROOT=/mvsutil-develop_dsed\nZOAU_HOME=\\$ZOAU_ROOT\nZOAU_DIR=\\$ZOAU_ROOT", + state="present", force=False), expected=dict(test_uss_block_insertafter_regex_defaultmarker="""if [ -z STEPLIB ] && tty -s; then export STEPLIB=none @@ -1498,6 +1508,17 @@ def test_ds_block_insertafter_eof_with_backup(ansible_zos_module, dstype, encodi ansible_zos_module.all.zos_data_set(name=backup_ds_name, state="absent") +@pytest.mark.ds +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_block_insertafter_regex_force(ansible_zos_module, dstype): + TEST_ENV["DS_TYPE"] = dstype + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_INFO["test_ds_block_insertafter_regex_force"], + TEST_INFO["expected"]["test_uss_block_insertafter_regex_defaultmarker"] + ) + + ######################### # Negative tests ######################### @@ -1545,4 +1566,14 @@ def test_ds_not_supported(ansible_zos_module, dstype): DsNotSupportedHelper( TEST_INFO["test_ds_block_insertafter_regex"]["test_name"], ansible_zos_module, TEST_ENV, TEST_INFO["test_uss_block_insertafter_regex"] - ) \ No newline at end of file + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_block_insertafter_regex_fail(ansible_zos_module, dstype): + TEST_ENV["DS_TYPE"] = dstype + DsGeneralForceFail( + ansible_zos_module, TEST_ENV, + TEST_INFO["test_ds_block_insertafter_regex_force_fail"], + ) diff --git a/tests/functional/modules/test_zos_lineinfile_func.py b/tests/functional/modules/test_zos_lineinfile_func.py index c001ebb0d..7b77c155d 100644 --- a/tests/functional/modules/test_zos_lineinfile_func.py +++ b/tests/functional/modules/test_zos_lineinfile_func.py @@ -17,6 +17,8 @@ DsGeneral, DsNotSupportedHelper, DsGeneralResultKeyMatchesRegex, + DsGeneralForceFail, + DsGeneralForce, ) import os import sys @@ -120,6 +122,23 @@ test_ds_line_replace_nomatch_insertbefore_nomatch=dict(test_name="T11"), test_ds_line_absent=dict(test_name="T12"), test_ds_line_tmp_hlq_option=dict(insertafter="EOF", line="export ZOAU_ROOT", state="present", backup=True, tmp_hlq="TMPHLQ"), + test_ds_line_force=dict(path="",insertafter="EOF", line="export ZOAU_ROOT", force=True), + test_ds_line_force_fail=dict(path="",insertafter="EOF", line="export ZOAU_ROOT", force=False), + test_ds_line_replace_force=dict(path="",regexp="ZOAU_ROOT=", line="ZOAU_ROOT=/mvsutil-develop_dsed", + state="present",force=True), + test_ds_line_insertafter_regex_force=dict(path="",insertafter="ZOAU_ROOT=", line="ZOAU_ROOT=/mvsutil-develop_dsed", + state="present",force=True), + test_ds_line_insertbefore_regex_force=dict(path="",insertbefore="ZOAU_ROOT=", line="unset ZOAU_ROOT", state="present",force=True), + test_ds_line_insertbefore_bof_force=dict(path="",insertbefore="BOF", line="# this is file is for setting env vars", + state="present",force=True), + test_ds_line_replace_match_insertafter_ignore_force=dict(path="",regexp="ZOAU_ROOT=", insertafter="PATH=", + line="ZOAU_ROOT=/mvsutil-develop_dsed", state="present",force=True), + test_ds_line_replace_match_insertbefore_ignore_force=dict(path="",regexp="ZOAU_ROOT=", insertbefore="PATH=", line="unset ZOAU_ROOT", + state="present",force=True), + test_ds_line_replace_nomatch_insertafter_match_force=dict(path="",regexp="abcxyz", insertafter="ZOAU_ROOT=", + line="ZOAU_ROOT=/mvsutil-develop_dsed", state="present",force=True), + test_ds_line_replace_nomatch_insertbefore_match_force=dict(path="",regexp="abcxyz", insertbefore="ZOAU_ROOT=", line="unset ZOAU_ROOT", + state="present",force=True), expected=dict(test_uss_line_replace="""if [ -z STEPLIB ] && tty -s; then export STEPLIB=none @@ -568,7 +587,42 @@ export PYTHONPATH export PKG_CONFIG_PATH export PYTHON_HOME -export _BPXK_AUTOCVT"""), +export _BPXK_AUTOCVT""", + test_ds_line_force="""if [ -z STEPLIB ] && tty -s; +then + export STEPLIB=none + exec -a 0 SHELL +fi +TZ=PST8PDT +export TZ +LANG=C +export LANG +readonly LOGNAME +PATH=/usr/lpp/zoautil/v100/bin:/usr/lpp/rsusr/ported/bin:/bin:/var/bin +export PATH +LIBPATH=/usr/lpp/izoda/v110/anaconda/lib:/usr/lpp/zoautil/v100/lib:/lib +export LIBPATH +NLSPATH=/usr/lib/nls/msg/%L/%N +export NLSPATH +MANPATH=/usr/man/%L +export MANPATH +MAIL=/usr/mail/LOGNAME +export MAIL +umask 022 +ZOAU_ROOT=/usr/lpp/zoautil/v100 +ZOAUTIL_DIR=/usr/lpp/zoautil/v100 +PYTHONPATH=/usr/lpp/izoda/v110/anaconda/lib:/usr/lpp/zoautil/v100/lib:/lib +PKG_CONFIG_PATH=/usr/lpp/izoda/v110/anaconda/lib/pkgconfig +PYTHON_HOME=/usr/lpp/izoda/v110/anaconda +_BPXK_AUTOCVT=ON +export ZOAU_ROOT +export ZOAUTIL_DIR +export ZOAUTIL_DIR +export PYTHONPATH +export PKG_CONFIG_PATH +export PYTHON_HOME +export _BPXK_AUTOCVT +export ZOAU_ROOT"""), ) ######################### @@ -708,20 +762,6 @@ def test_uss_line_replace_quoted_not_escaped(ansible_zos_module): # Dataset test cases ######################### -@pytest.mark.ds -@pytest.mark.parametrize("dstype", DS_TYPE) -@pytest.mark.parametrize("encoding", ENCODING) -def test_ds_line_replace(ansible_zos_module, dstype, encoding): - TEST_ENV["DS_TYPE"] = dstype - TEST_ENV["ENCODING"] = encoding - TEST_INFO["test_uss_line_replace"]["line"] = 'ZOAU_ROOT=/mvsutil-develop_dsed' - DsGeneral( - TEST_INFO["test_ds_line_replace"]["test_name"], ansible_zos_module, - TEST_ENV, TEST_INFO["test_uss_line_replace"], - TEST_INFO["expected"]["test_uss_line_replace"] - ) - - @pytest.mark.ds @pytest.mark.parametrize("dstype", DS_TYPE) @pytest.mark.parametrize("encoding", ENCODING) @@ -909,3 +949,145 @@ def test_ds_not_supported(ansible_zos_module, dstype): TEST_INFO["test_ds_line_replace"]["test_name"], ansible_zos_module, TEST_ENV, TEST_INFO["test_uss_line_replace"] ) + + +######################### +# Dataset test cases with force +######################### + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_force"], + TEST_INFO["expected"]["test_ds_line_force"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_force_fail(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForceFail( + ansible_zos_module, TEST_ENV, + TEST_INFO["test_ds_line_force_fail"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_replace_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_replace_force"], + TEST_INFO["expected"]["test_uss_line_replace"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_insertafter_regex_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_insertafter_regex_force"], + TEST_INFO["expected"]["test_uss_line_insertafter_regex"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_insertbefore_regex_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_insertbefore_regex_force"], + TEST_INFO["expected"]["test_uss_line_insertbefore_regex"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_insertbefore_bof_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_insertbefore_bof_force"], + TEST_INFO["expected"]["test_uss_line_insertbefore_bof"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_replace_match_insertafter_ignore_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_replace_match_insertafter_ignore_force"], + TEST_INFO["expected"]["test_uss_line_replace_match_insertafter_ignore"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_replace_match_insertbefore_ignore_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_replace_match_insertbefore_ignore_force"], + TEST_INFO["expected"]["test_uss_line_replace_match_insertbefore_ignore"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_replace_nomatch_insertafter_match_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_replace_nomatch_insertafter_match_force"], + TEST_INFO["expected"]["test_uss_line_replace_nomatch_insertafter_match"] + ) + + +@pytest.mark.ds +@pytest.mark.parametrize("encoding", ENCODING) +@pytest.mark.parametrize("dstype", DS_TYPE) +def test_ds_line_replace_nomatch_insertbefore_match_force(ansible_zos_module, dstype, encoding): + TEST_ENV["DS_TYPE"] = dstype + TEST_ENV["ENCODING"] = encoding + DsGeneralForce( + ansible_zos_module, TEST_ENV, + TEST_CONTENT, + TEST_INFO["test_ds_line_replace_nomatch_insertbefore_match_force"], + TEST_INFO["expected"]["test_uss_line_replace_nomatch_insertbefore_match"] + ) diff --git a/tests/helpers/zos_blockinfile_helper.py b/tests/helpers/zos_blockinfile_helper.py index 0a77e4eda..f5aa178fe 100644 --- a/tests/helpers/zos_blockinfile_helper.py +++ b/tests/helpers/zos_blockinfile_helper.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) IBM Corporation 2020, 2022 +# Copyright (c) IBM Corporation 2020, 2022, 2023 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -14,12 +14,40 @@ from __future__ import absolute_import, division, print_function from shellescape import quote from pprint import pprint +import time import re __metaclass__ = type +DEFAULT_DATA_SET_NAME = "USER.PRIVATE.TESTDS" + +c_pgm="""#include +#include +#include +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 set_uss_test_env(test_name, hosts, test_env): test_env["TEST_FILE"] = test_env["TEST_DIR"] + test_name try: @@ -117,8 +145,8 @@ def DsGeneral(test_name, ansible_zos_module, test_env, test_info, expected): results = hosts.all.shell(cmd=cmdStr) for result in results.contacted.values(): pprint(result) - assert result.get("stdout") == expected - # assert result.get("stdout").replace('\n', '').replace(' ', '') == expected.replace('\n', '').replace(' ', '') + #assert result.get("stdout") == expected + assert result.get("stdout").replace('\n', '').replace(' ', '') == expected.replace('\n', '').replace(' ', '') clean_ds_test_env(test_env["DS_NAME"], hosts) return blockinfile_results @@ -155,3 +183,163 @@ def DsGeneralResultKeyMatchesRegex(test_name, ansible_zos_module, test_env, test for key in kwargs: assert re.match(kwargs.get(key), result.get(key)) clean_ds_test_env(test_env["DS_NAME"], hosts) + + +def DsGeneralForce(ansible_zos_module, test_env, test_info, expected): + MEMBER_1, MEMBER_2 = "MEM1", "MEM2" + TEMP_FILE = "/tmp/{0}".format(MEMBER_2) + if test_env["DS_TYPE"] == "SEQ": + test_env["DS_NAME"] = DEFAULT_DATA_SET_NAME+".{0}".format(MEMBER_2) + test_info["path"] = DEFAULT_DATA_SET_NAME+".{0}".format(MEMBER_2) + else: + test_env["DS_NAME"] = DEFAULT_DATA_SET_NAME+"({0})".format(MEMBER_2) + test_info["path"] = DEFAULT_DATA_SET_NAME+"({0})".format(MEMBER_2) + hosts = ansible_zos_module + try: + # set up: + # create pdse + hosts.all.zos_data_set(name=DEFAULT_DATA_SET_NAME, state="present", type=test_env["DS_TYPE"], replace=True) + hosts.all.shell(cmd="echo \"{0}\" > {1}".format(test_env["TEST_CONT"], TEMP_FILE)) + # add members + hosts.all.zos_data_set( + batch=[ + { + "name": DEFAULT_DATA_SET_NAME + "({0})".format(MEMBER_1), + "type": "member", + "state": "present", + "replace": True, + }, + { + "name": test_env["DS_NAME"], + "type": "member", + "state": "present", + "replace": True, + }, + ] + ) + # write memeber to verify cases + # print(test_env["TEST_CONT"]) + if test_env["DS_TYPE"] in ["PDS", "PDSE"]: + cmdStr = "cp -CM {0} \"//'{1}'\"".format(quote(TEMP_FILE), test_env["DS_NAME"]) + else: + cmdStr = "cp {0} \"//'{1}'\" ".format(quote(TEMP_FILE), test_env["DS_NAME"]) + if test_env["ENCODING"]: + test_info["encoding"] = test_env["ENCODING"] + hosts.all.shell(cmd=cmdStr) + cmdStr = "cat \"//'{0}'\" | wc -l ".format(test_env["DS_NAME"]) + results = hosts.all.shell(cmd=cmdStr) + pprint(vars(results)) + for result in results.contacted.values(): + assert int(result.get("stdout")) != 0 + if test_env["ENCODING"] != 'IBM-1047': + hosts.all.zos_encode( + src=TEMP_FILE, + dest=test_env["DS_NAME"], + encoding={ + "from": "IBM-1047", + "to": test_env["ENCODING"], + }, + ) + # 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(DEFAULT_DATA_SET_NAME, 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) + + blockinfile_results = hosts.all.zos_blockinfile(**test_info) + for result in blockinfile_results.contacted.values(): + assert result.get("changed") == True + + + if test_env["ENCODING"] == 'IBM-1047': + cmdStr = "cat \"//'{0}'\" ".format(test_info["path"]) + results = hosts.all.shell(cmd=cmdStr) + for result in results.contacted.values(): + pprint(result) + assert result.get("stdout").replace('\n', '').replace(' ', '') == expected.replace('\n', '').replace(' ', '') + else: + cmdStr =r"""cat "//'{0}'" """.format(test_info["path"]) + results = hosts.all.shell(cmd=cmdStr) + pprint(vars(results)) + for result in results.contacted.values(): + assert result.get("changed") == True + finally: + hosts.all.shell(cmd="rm -rf " + TEMP_FILE) + # 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=DEFAULT_DATA_SET_NAME, state="absent") + return blockinfile_results + + +def DsGeneralForceFail(ansible_zos_module, test_env, test_info): + MEMBER_1, MEMBER_2 = "MEM1", "MEM2" + hosts = ansible_zos_module + test_info["path"] = DEFAULT_DATA_SET_NAME+"({0})".format(MEMBER_2) + try: + # set up: + # create pdse + hosts.all.zos_data_set(name=DEFAULT_DATA_SET_NAME, state="present", type="pdse", replace=True) + # add members + hosts.all.zos_data_set( + batch=[ + { + "name": DEFAULT_DATA_SET_NAME + "({0})".format(MEMBER_1), + "type": "member", + "state": "present", + "replace": True, + }, + { + "name": DEFAULT_DATA_SET_NAME + "({0})".format(MEMBER_2), + "type": "member", + "state": "present", + "replace": True, + }, + ] + ) + # write memeber to verify cases + hosts.all.shell(cmd="echo \"{0}\" > {1}".format(test_env["TEST_CONT"], test_info["path"])) + # 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(DEFAULT_DATA_SET_NAME, 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) + + blockinfile_results = hosts.all.zos_blockinfile(**test_info) + for result in blockinfile_results.contacted.values(): + pprint(result) + assert result.get("changed") == False + assert result.get("failed") == True + 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=DEFAULT_DATA_SET_NAME, state="absent") \ No newline at end of file diff --git a/tests/helpers/zos_lineinfile_helper.py b/tests/helpers/zos_lineinfile_helper.py index 2c695364b..bac392e80 100644 --- a/tests/helpers/zos_lineinfile_helper.py +++ b/tests/helpers/zos_lineinfile_helper.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) IBM Corporation 2020, 2022 +# Copyright (c) IBM Corporation 2020, 2022, 2023 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -13,11 +13,38 @@ from __future__ import absolute_import, division, print_function from shellescape import quote +import time from pprint import pprint import re __metaclass__ = type +DEFAULT_DATA_SET_NAME = "USER.PRIVATE.TESTDS" + +c_pgm="""#include +#include +#include +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 set_uss_test_env(test_name, hosts, test_env): test_env["TEST_FILE"] = test_env["TEST_DIR"] + test_name @@ -159,3 +186,155 @@ def DsGeneralResultKeyMatchesRegex(test_name, ansible_zos_module, test_env, test for key in kwargs: assert re.match(kwargs.get(key), result.get(key)) clean_ds_test_env(test_env["DS_NAME"], hosts) + + +def DsGeneralForce(ansible_zos_module, test_env, test_text, test_info, expected): + MEMBER_1, MEMBER_2 = "MEM1", "MEM2" + TEMP_FILE = "/tmp/{0}".format(MEMBER_2) + if test_env["DS_TYPE"] == "SEQ": + test_env["DS_NAME"] = DEFAULT_DATA_SET_NAME+".{0}".format(MEMBER_2) + test_info["path"] = DEFAULT_DATA_SET_NAME+".{0}".format(MEMBER_2) + else: + test_env["DS_NAME"] = DEFAULT_DATA_SET_NAME+"({0})".format(MEMBER_2) + test_info["path"] = DEFAULT_DATA_SET_NAME+"({0})".format(MEMBER_2) + hosts = ansible_zos_module + try: + # set up: + hosts.all.zos_data_set(name=DEFAULT_DATA_SET_NAME, state="present", type=test_env["DS_TYPE"], replace=True) + hosts.all.shell(cmd="echo \"{0}\" > {1}".format(test_text, TEMP_FILE)) + # add members + hosts.all.zos_data_set( + batch=[ + { + "name": DEFAULT_DATA_SET_NAME + "({0})".format(MEMBER_1), + "type": "member", + "state": "present", + "replace": True, + }, + { + "name": test_env["DS_NAME"], + "type": "member", + "state": "present", + "replace": True, + }, + ] + ) + # write memeber to verify cases + if test_env["DS_TYPE"] in ["PDS", "PDSE"]: + cmdStr = "cp -CM {0} \"//'{1}'\"".format(quote(TEMP_FILE), test_env["DS_NAME"]) + else: + cmdStr = "cp {0} \"//'{1}'\" ".format(quote(TEMP_FILE), test_env["DS_NAME"]) + if test_env["ENCODING"]: + test_info["encoding"] = test_env["ENCODING"] + hosts.all.shell(cmd=cmdStr) + cmdStr = "cat \"//'{0}'\" | wc -l ".format(test_env["DS_NAME"]) + results = hosts.all.shell(cmd=cmdStr) + pprint(vars(results)) + for result in results.contacted.values(): + assert int(result.get("stdout")) != 0 + if test_env["ENCODING"] != 'IBM-1047': + hosts.all.zos_encode( + src=TEMP_FILE, + dest=test_env["DS_NAME"], + encoding={ + "from": "IBM-1047", + "to": test_env["ENCODING"], + }, + ) + # 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(DEFAULT_DATA_SET_NAME, 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) + # call line infile to see results + results = hosts.all.zos_lineinfile(**test_info) + pprint(vars(results)) + + if test_env["ENCODING"] == 'IBM-1047': + cmdStr =r"""cat "//'{0}'" """.format(test_info["path"]) + results = hosts.all.shell(cmd=cmdStr) + pprint(vars(results)) + for result in results.contacted.values(): + assert result.get("stdout") == expected + else: + cmdStr =r"""cat "//'{0}'" """.format(test_info["path"]) + results = hosts.all.shell(cmd=cmdStr) + pprint(vars(results)) + for result in results.contacted.values(): + assert result.get("changed") == True + #assert result.get("stdout") == expected + + finally: + hosts.all.shell(cmd="rm -rf " + TEMP_FILE) + # 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=DEFAULT_DATA_SET_NAME, state="absent") + +def DsGeneralForceFail(ansible_zos_module, test_env, test_info): + MEMBER_1, MEMBER_2 = "MEM1", "MEM2" + hosts = ansible_zos_module + test_info["path"] = DEFAULT_DATA_SET_NAME+"({0})".format(MEMBER_2) + try: + # set up: + # create pdse + hosts.all.zos_data_set(name=DEFAULT_DATA_SET_NAME, state="present", type="pdse", replace=True) + # add members + hosts.all.zos_data_set( + batch=[ + { + "name": DEFAULT_DATA_SET_NAME + "({0})".format(MEMBER_1), + "type": "member", + "state": "present", + "replace": True, + }, + { + "name": DEFAULT_DATA_SET_NAME + "({0})".format(MEMBER_2), + "type": "member", + "state": "present", + "replace": True, + }, + ] + ) + # 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(DEFAULT_DATA_SET_NAME, 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) + # call line infile to see results + results = hosts.all.zos_lineinfile(**test_info) + pprint(vars(results)) + for result in results.contacted.values(): + assert result.get("changed") == False + assert result.get("failed") == True + 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=DEFAULT_DATA_SET_NAME, state="absent")