From cd7a29e5cd4a1153017afb0fbf0781add5c2b2b5 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Wed, 27 Jan 2021 17:00:55 -0500 Subject: [PATCH 01/18] 3687: looking for 'JCL ERROR' in return code message, since that will not contain a "CC" value to return. Temporarily assigning that a CC=16 --- plugins/module_utils/job.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/job.py b/plugins/module_utils/job.py index c86a702be..262da2e88 100644 --- a/plugins/module_utils/job.py +++ b/plugins/module_utils/job.py @@ -445,10 +445,14 @@ def _get_return_code_num(rc_str): Returns: Union[int, NoneType] -- Returns integer RC if possible, if not returns NoneType """ + rc = None - match = re.search(r"\s*CC\s*([0-9]+)", rc_str) - if match: - rc = int(match.group(1)) + if "JCL ERROR" in rc_str: + rc = 16 + else: + match = re.search(r"\s*CC\s*([0-9]+)", rc_str) + if match: + rc = int(match.group(1)) return rc From 672173f1cb65c3d85b1083f9efed48bddb2d2a32 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Thu, 28 Jan 2021 12:07:44 -0500 Subject: [PATCH 02/18] Expanded changes for JCL error to 'fail fast' + added "JCL ERROR" to job_completion_messages list + corrected grammar in error message@615 (was 'slow to response') --- plugins/module_utils/job.py | 4 +++- plugins/modules/zos_job_submit.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/job.py b/plugins/module_utils/job.py index 262da2e88..68cfdcd92 100644 --- a/plugins/module_utils/job.py +++ b/plugins/module_utils/job.py @@ -467,7 +467,9 @@ def _get_return_code_str(rc_str): Union[str, NoneType] -- Returns string RC or ABEND code if possible, if not returns NoneType """ rc = None - match = re.search(r"(?:\s*CC\s*([0-9]+))|(?:ABEND\s*((?:S|U)[0-9]+))", rc_str) + match = re.search( + r"(?:\s*CC\s*([0-9]+))|(?:ABEND\s*((?:S|U)[0-9]+)|(?:JCL ERROR)))", rc_str + ) if match: rc = match.group(1) or match.group(2) return rc diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 430aa17ce..7a05dae8d 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -513,7 +513,7 @@ POLLING_INTERVAL = 1 POLLING_COUNT = 60 -JOB_COMPLETION_MESSAGES = ["CC", "ABEND", "SEC"] +JOB_COMPLETION_MESSAGES = ["CC", "ABEND", "SEC", "JCL ERROR"] def submit_pds_jcl(src, module): @@ -612,7 +612,7 @@ def query_jobs_status(module, jobId): ) if not output and timeout == 0: raise SubmitJCLError( - "THE JOB CAN NOT BE QUERIED FROM JES (TIMEOUT=10s). PLEASE CHECK THE ZOS SYSTEM. IT IS SLOW TO RESPONSE." + "THE JOB CAN NOT BE QUERIED FROM JES (TIMEOUT=10s). PLEASE CHECK THE ZOS SYSTEM. IT IS SLOW TO RESPOND." ) return output From d5d6c169e8f641c6352f1e764f34e0ec94200d51 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Thu, 18 Feb 2021 14:38:44 -0500 Subject: [PATCH 03/18] rebuilt timing delay loops for zos_job_submit --- plugins/module_utils/job.py | 2 +- plugins/modules/zos_job_submit.py | 80 ++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/plugins/module_utils/job.py b/plugins/module_utils/job.py index 68cfdcd92..5fcc754fc 100644 --- a/plugins/module_utils/job.py +++ b/plugins/module_utils/job.py @@ -468,7 +468,7 @@ def _get_return_code_str(rc_str): """ rc = None match = re.search( - r"(?:\s*CC\s*([0-9]+))|(?:ABEND\s*((?:S|U)[0-9]+)|(?:JCL ERROR)))", rc_str + r"(?:\s*CC\s*([0-9]+))|(?:ABEND\s*((?:S|U)[0-9]+)|(?:JCL ERROR))", rc_str ) if match: rc = match.group(1) or match.group(2) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 7a05dae8d..396d98c6e 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -494,6 +494,8 @@ from os import chmod, path, remove from tempfile import NamedTemporaryFile import re +from timeit import default_timer as timer + from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.job import job_output from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.better_arg_parser import ( BetterArgParser, @@ -697,7 +699,7 @@ def run_module(): if wait_time_s <= 0: module.fail_json( - msg="The option wait_time_s is not valid it just be greater than 0.", + msg="The option wait_time_s is not valid. It must be greater than 0.", **result ) @@ -751,41 +753,62 @@ def run_module(): result["job_id"] = jobId duration = 0 - if wait is True: - # calculate the job elapse time + if not wait: + wait_time_s = 10 + + # real time loop - will be used regardless of 'wait' to capture data + starttime = timer() + loopdone = False + while not loopdone: try: - waitJob = query_jobs_status(module, jobId) - job_msg = waitJob[0].get("ret_code").get("msg") - except SubmitJCLError as e: + job_output_txt = job_output(job_id=jobId) + except IndexError: + pass + except Exception as e: + result["err_detail"] = "{1} {2}.\n".format( + "Error during job submission. The output is:", job_output_txt or " " + ) module.fail_json(msg=repr(e), **result) - # while (job_msg.startswith("CC") or job_msg.startswith("ABEND")) is False: - while not re.search( - "^(?:{0})".format("|".join(JOB_COMPLETION_MESSAGES)), job_msg - ): - sleep(1) - duration = duration + 1 - waitJob = job_output(job_id=jobId) - job_msg = waitJob[0].get("ret_code").get("msg") - if re.search("^(?:{0})".format("|".join(JOB_COMPLETION_MESSAGES)), job_msg): - break - if duration == wait_time_s: # Long running task. timeout return - break - try: - result = get_job_info(module, jobId, return_output) + if bool(job_output_txt): + jot_retcode = job_output_txt[0].get("ret_code") + if bool(jot_retcode): + job_msg = jot_retcode.get("msg") + if re.search( + "^(?:{0})".format("|".join(JOB_COMPLETION_MESSAGES)), job_msg + ): + loopdone = True + + if not loopdone: + checktime = timer() + duration = round(checktime - starttime) + if duration >= wait_time_s: + loopdone = True + result["message"] = { + "stdout": "Submit JCL operation succeeded but it is a long running job. Timeout is " + + str(wait_time_s) + + " seconds." + } + else: + sleep(0.5) + + # End real time loop + if bool(job_output_txt): + result["jobs"] = job_output_txt if wait is True and return_output is True and max_rc is not None: assert_valid_return_code( max_rc, result.get("jobs")[0].get("ret_code").get("code") ) - except SubmitJCLError as e: - module.fail_json(msg=repr(e), **result) - except Exception as e: - module.fail_json(msg=repr(e), **result) - finally: - if temp_file: - remove(temp_file) + + if temp_file: + remove(temp_file) + if temp_file_2: + remove(temp_file_2) + + checktime = timer() + duration = round(checktime - starttime) result["duration"] = duration - if duration == wait_time_s: + if duration >= wait_time_s: result["message"] = { "stdout": "Submit JCL operation succeeded but it is a long running job. Timeout is " + str(wait_time_s) @@ -793,6 +816,7 @@ def run_module(): } else: result["message"] = {"stdout": "Submit JCL operation succeeded."} + result["changed"] = True module.exit_json(**result) From 4c4193287c1e25fadd84b77fd34271cdfa7d6f29 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Mon, 8 Mar 2021 16:44:32 -0500 Subject: [PATCH 04/18] fixed variable usage, added more meaningful error for JCL ERROR and ABEND detectors --- plugins/modules/zos_job_submit.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index b0041cb26..7df27a775 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -515,7 +515,7 @@ POLLING_INTERVAL = 1 POLLING_COUNT = 60 -JOB_COMPLETION_MESSAGES = ["CC", "ABEND", "SEC", "JCL ERROR"] +JOB_COMPLETION_MESSAGES = ["CC", "ABEND", "SEC", "JCL\sERROR"] def submit_pds_jcl(src, module): @@ -694,6 +694,7 @@ def run_module(): max_rc = parsed_args.get("max_rc") # get temporary file names for copied files temp_file = parsed_args.get("temp_file") + temp_file_2 = None if temp_file: temp_file_2 = NamedTemporaryFile(delete=True) @@ -761,6 +762,7 @@ def run_module(): # real time loop - will be used regardless of 'wait' to capture data starttime = timer() loopdone = False + foundissue = None while not loopdone: try: job_output_txt = job_output(job_id=jobId) @@ -780,6 +782,9 @@ def run_module(): "^(?:{0})".format("|".join(JOB_COMPLETION_MESSAGES)), job_msg ): loopdone = True + # if the message doesn't have a CC, it is an improper completion (error/abend) + if re.search("^(?:CC)", job_msg) is None: + foundissue = job_msg if not loopdone: checktime = timer() @@ -794,7 +799,8 @@ def run_module(): else: sleep(0.5) - # End real time loop + # End real time loop ^^^ + if bool(job_output_txt): result["jobs"] = job_output_txt if wait is True and return_output is True and max_rc is not None: @@ -810,6 +816,8 @@ def run_module(): checktime = timer() duration = round(checktime - starttime) result["duration"] = duration + result["changed"] = True + if duration >= wait_time_s: result["message"] = { "stdout": "Submit JCL operation succeeded but it is a long running job. Timeout is " @@ -817,9 +825,15 @@ def run_module(): + " seconds." } else: - result["message"] = {"stdout": "Submit JCL operation succeeded."} + if foundissue is not None: + result["changed"] = False + result["message"] = { + "stderr": "Submit succeeded, but job failed: " + foundissue + } + result["failed"] = True + else: + result["message"] = {"stdout": "Submit JCL operation succeeded."} - result["changed"] = True module.exit_json(**result) From a6543715507f9f175b878dbedb9d67fc540a6ba4 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Mon, 8 Mar 2021 17:18:11 -0500 Subject: [PATCH 05/18] simplified jcl error search string corrected logic in test (testing for failure) --- plugins/modules/zos_job_submit.py | 2 +- .../modules/test_zos_job_submit_func.py | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 7df27a775..ccc96d069 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -515,7 +515,7 @@ POLLING_INTERVAL = 1 POLLING_COUNT = 60 -JOB_COMPLETION_MESSAGES = ["CC", "ABEND", "SEC", "JCL\sERROR"] +JOB_COMPLETION_MESSAGES = ["CC", "ABEND", "SEC", "JCL ERROR"] def submit_pds_jcl(src, module): diff --git a/tests/functional/modules/test_zos_job_submit_func.py b/tests/functional/modules/test_zos_job_submit_func.py index 516d738c7..2d2db9292 100644 --- a/tests/functional/modules/test_zos_job_submit_func.py +++ b/tests/functional/modules/test_zos_job_submit_func.py @@ -49,6 +49,18 @@ //SYSUT2 DD SYSOUT=* // """ +JCL_FILE_CONTENTS_BAD = """//HELLO JOB (T043JM,JM00,1,0,0,0),'HELLO WORLD - JRM',CLASS=R, +// MSGCLASS=X,MSGLEVEL=1,NOTIFY=S0JM +//STEP0001 EXEC PGM=IEBGENER +//SYSIN DD DUMMY +//SYSPRINT DD SYSOUT=*!! +//SYSUT1 DD * +HELLO, WORLD +/* +//SYSUT2 DD SYSOUT=* +// +""" + TEMP_PATH = "/tmp/ansible/jcl" DATA_SET_NAME = "imstestl.ims1.test05" @@ -149,6 +161,20 @@ def test_job_submit_LOCAL_extraR(ansible_zos_module): assert result.get("changed") is True +def test_job_submit_LOCAL_BADJCL(ansible_zos_module): + tmp_file = tempfile.NamedTemporaryFile(delete=True) + with open(tmp_file.name, "w") as f: + f.write(JCL_FILE_CONTENTS_BAD) + hosts = ansible_zos_module + results = hosts.all.zos_job_submit(src=tmp_file.name, location="LOCAL", wait=True) + + for result in results.contacted.values(): + print(result) + + assert result.get("changed") is False + assert result.get("failed", False) is True + + # * currently don't have volume support from ZOAU python API, so this will not be reproduceable # * in CI/CD testing environment (for now) # def test_job_submit_PDS_volume(ansible_zos_module): From bed3b34888c0e7eab887a95cfc715700fbf0ea0c Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 12:55:50 -0500 Subject: [PATCH 06/18] added debug call/conv strings to return from job_submit, to understand blank returns --- plugins/modules/zos_job_submit.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index ccc96d069..3ca6c510d 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -704,6 +704,13 @@ def run_module(): **result ) + callstr = "loc: " + location + "..wait: " + str(wait) + "..src: " + src + "..wts: " = str(wait_time_s) + if temp_file: + callstr = callstr + "f1: " + str(temp_file.name) + if temp_file_2: + callstr = callstr + "f2: " + str(temp_file_2.name) + result["call_set"] = callstr + DSN_REGEX = r"^(?:(?:[A-Z$#@]{1}[A-Z0-9$#@-]{0,7})(?:[.]{1})){1,21}[A-Z$#@]{1}[A-Z0-9$#@-]{0,7}(?:\([A-Z$#@]{1}[A-Z0-9$#@]{0,7}\)){0,1}$" try: if location == "DATA_SET": @@ -727,13 +734,15 @@ def run_module(): # added -c to iconv to try and prevent \r from mis-mapping as invalid char to EBCDIC to_encoding = encoding.get("to") - (conv_rc, stdout, stderr) = module.run_command( - "iconv -c -f {0} -t {1} {2} > {3}".format( + conv_str = "iconv -c -f {0} -t {1} {2} > {3}".format( from_encoding, to_encoding, quote(temp_file), quote(temp_file_2.name), - ), + ) + result["conv_str"] = conv_str + (conv_rc, stdout, stderr) = module.run_command( + conv_str, use_unsafe_shell=True, ) if conv_rc == 0: @@ -748,7 +757,7 @@ def run_module(): except SubmitJCLError as e: module.fail_json(msg=repr(e), **result) if jobId is None or jobId == "": - result["job_id"] = jobId + result["job_id"] = "-blank-" module.fail_json( msg="JOB ID RETURNED IS None. PLEASE CHECK WHETHER THE JCL IS CORRECT.", **result From 72d4963c1ab625d52d62cfee2a3579f45038d6c1 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 13:02:07 -0500 Subject: [PATCH 07/18] argh...typo in the tracking string --- plugins/modules/zos_job_submit.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 3ca6c510d..190a06ea2 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -704,12 +704,21 @@ def run_module(): **result ) - callstr = "loc: " + location + "..wait: " + str(wait) + "..src: " + src + "..wts: " = str(wait_time_s) + callstr = ( + "loc: " + + str(location) + + "..wait: " + + str(wait) + + "..src: " + + src + + "..wts: " + + str(wait_time_s) + ) if temp_file: callstr = callstr + "f1: " + str(temp_file.name) if temp_file_2: callstr = callstr + "f2: " + str(temp_file_2.name) - result["call_set"] = callstr + result["call_set"] = callstr DSN_REGEX = r"^(?:(?:[A-Z$#@]{1}[A-Z0-9$#@-]{0,7})(?:[.]{1})){1,21}[A-Z$#@]{1}[A-Z0-9$#@-]{0,7}(?:\([A-Z$#@]{1}[A-Z0-9$#@]{0,7}\)){0,1}$" try: @@ -735,11 +744,11 @@ def run_module(): # added -c to iconv to try and prevent \r from mis-mapping as invalid char to EBCDIC to_encoding = encoding.get("to") conv_str = "iconv -c -f {0} -t {1} {2} > {3}".format( - from_encoding, - to_encoding, - quote(temp_file), - quote(temp_file_2.name), - ) + from_encoding, + to_encoding, + quote(temp_file), + quote(temp_file_2.name), + ) result["conv_str"] = conv_str (conv_rc, stdout, stderr) = module.run_command( conv_str, From 91ac19c20940032b25d0f8585a009e42d4eae46d Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 13:21:30 -0500 Subject: [PATCH 08/18] continuing to track non-response issue --- plugins/modules/zos_job_submit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 190a06ea2..627cd68ff 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -849,6 +849,7 @@ def run_module(): "stderr": "Submit succeeded, but job failed: " + foundissue } result["failed"] = True + module.fail_json("JCL error", **result) else: result["message"] = {"stdout": "Submit JCL operation succeeded."} From 3381ec86eb21e12cfb5ca8ef00aaf93b6a39a674 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 13:58:32 -0500 Subject: [PATCH 09/18] corrected issue with temp_file.name (may need to add comment that it is a string, not an object) --- plugins/modules/zos_job_submit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 627cd68ff..a8a1b3380 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -715,7 +715,7 @@ def run_module(): + str(wait_time_s) ) if temp_file: - callstr = callstr + "f1: " + str(temp_file.name) + callstr = callstr + "f1: " + str(temp_file) if temp_file_2: callstr = callstr + "f2: " + str(temp_file_2.name) result["call_set"] = callstr From 2371d8790cbf6d2eca618273a064eca958b6676d Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 14:56:08 -0500 Subject: [PATCH 10/18] eliminated duplicate temp file 2 removal --- plugins/modules/zos_job_submit.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index a8a1b3380..908fb6703 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -828,8 +828,6 @@ def run_module(): if temp_file: remove(temp_file) - if temp_file_2: - remove(temp_file_2) checktime = timer() duration = round(checktime - starttime) From 9f8e5076c27fc508be71c09a249bca4ef8b81de9 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 15:22:58 -0500 Subject: [PATCH 11/18] adding badjcl indicator, since the glitch moved --- tests/functional/modules/test_zos_job_submit_func.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/modules/test_zos_job_submit_func.py b/tests/functional/modules/test_zos_job_submit_func.py index 2d2db9292..ba1ccaa16 100644 --- a/tests/functional/modules/test_zos_job_submit_func.py +++ b/tests/functional/modules/test_zos_job_submit_func.py @@ -169,7 +169,7 @@ def test_job_submit_LOCAL_BADJCL(ansible_zos_module): results = hosts.all.zos_job_submit(src=tmp_file.name, location="LOCAL", wait=True) for result in results.contacted.values(): - print(result) + print("badjcl:{0}".format(result)) assert result.get("changed") is False assert result.get("failed", False) is True From f78ed0ad00c6a64a7d0b512fc7b355c3056d2bf6 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 16:11:38 -0500 Subject: [PATCH 12/18] added another fail_json to job_submit --- plugins/modules/zos_job_submit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 908fb6703..05f23a74a 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -847,7 +847,7 @@ def run_module(): "stderr": "Submit succeeded, but job failed: " + foundissue } result["failed"] = True - module.fail_json("JCL error", **result) + module.fail_json(msg=result["message"], **result) else: result["message"] = {"stdout": "Submit JCL operation succeeded."} From d424b0116aa59b22d95fa77ff8aeee4f5399754d Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 16:29:10 -0500 Subject: [PATCH 13/18] removed failed=true as an assertion --- tests/functional/modules/test_zos_job_submit_func.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/functional/modules/test_zos_job_submit_func.py b/tests/functional/modules/test_zos_job_submit_func.py index ba1ccaa16..7caf7bc69 100644 --- a/tests/functional/modules/test_zos_job_submit_func.py +++ b/tests/functional/modules/test_zos_job_submit_func.py @@ -139,7 +139,7 @@ def test_job_submit_LOCAL(ansible_zos_module): results = hosts.all.zos_job_submit(src=tmp_file.name, location="LOCAL", wait=True) for result in results.contacted.values(): - print(result) + print("localgood:{0}".format(result)) assert result.get("jobs")[0].get("ret_code").get("msg_code") == "0000" assert result.get("jobs")[0].get("ret_code").get("code") == 0 @@ -154,7 +154,7 @@ def test_job_submit_LOCAL_extraR(ansible_zos_module): results = hosts.all.zos_job_submit(src=tmp_file.name, location="LOCAL", wait=True) for result in results.contacted.values(): - print(result) + print("localexr:{0}".format(result)) assert result.get("jobs")[0].get("ret_code").get("msg_code") == "0000" assert result.get("jobs")[0].get("ret_code").get("code") == 0 @@ -172,7 +172,6 @@ def test_job_submit_LOCAL_BADJCL(ansible_zos_module): print("badjcl:{0}".format(result)) assert result.get("changed") is False - assert result.get("failed", False) is True # * currently don't have volume support from ZOAU python API, so this will not be reproduceable From 3f00c8a6afab04584251c212cf4c57ff12db8ac3 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Tue, 9 Mar 2021 17:00:47 -0500 Subject: [PATCH 14/18] removed tracking statements. --- plugins/modules/zos_job_submit.py | 17 ----------------- .../modules/test_zos_job_submit_func.py | 3 --- 2 files changed, 20 deletions(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 05f23a74a..ae3828c0e 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -704,22 +704,6 @@ def run_module(): **result ) - callstr = ( - "loc: " - + str(location) - + "..wait: " - + str(wait) - + "..src: " - + src - + "..wts: " - + str(wait_time_s) - ) - if temp_file: - callstr = callstr + "f1: " + str(temp_file) - if temp_file_2: - callstr = callstr + "f2: " + str(temp_file_2.name) - result["call_set"] = callstr - DSN_REGEX = r"^(?:(?:[A-Z$#@]{1}[A-Z0-9$#@-]{0,7})(?:[.]{1})){1,21}[A-Z$#@]{1}[A-Z0-9$#@-]{0,7}(?:\([A-Z$#@]{1}[A-Z0-9$#@]{0,7}\)){0,1}$" try: if location == "DATA_SET": @@ -749,7 +733,6 @@ def run_module(): quote(temp_file), quote(temp_file_2.name), ) - result["conv_str"] = conv_str (conv_rc, stdout, stderr) = module.run_command( conv_str, use_unsafe_shell=True, diff --git a/tests/functional/modules/test_zos_job_submit_func.py b/tests/functional/modules/test_zos_job_submit_func.py index 7caf7bc69..7f3bef5b1 100644 --- a/tests/functional/modules/test_zos_job_submit_func.py +++ b/tests/functional/modules/test_zos_job_submit_func.py @@ -139,7 +139,6 @@ def test_job_submit_LOCAL(ansible_zos_module): results = hosts.all.zos_job_submit(src=tmp_file.name, location="LOCAL", wait=True) for result in results.contacted.values(): - print("localgood:{0}".format(result)) assert result.get("jobs")[0].get("ret_code").get("msg_code") == "0000" assert result.get("jobs")[0].get("ret_code").get("code") == 0 @@ -154,7 +153,6 @@ def test_job_submit_LOCAL_extraR(ansible_zos_module): results = hosts.all.zos_job_submit(src=tmp_file.name, location="LOCAL", wait=True) for result in results.contacted.values(): - print("localexr:{0}".format(result)) assert result.get("jobs")[0].get("ret_code").get("msg_code") == "0000" assert result.get("jobs")[0].get("ret_code").get("code") == 0 @@ -169,7 +167,6 @@ def test_job_submit_LOCAL_BADJCL(ansible_zos_module): results = hosts.all.zos_job_submit(src=tmp_file.name, location="LOCAL", wait=True) for result in results.contacted.values(): - print("badjcl:{0}".format(result)) assert result.get("changed") is False From b2344d20059f2e7e21a39e95b41db147f38a2e6b Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Mon, 15 Mar 2021 14:01:43 -0400 Subject: [PATCH 15/18] minor corrections on comments... still need to discuss return code and no-wait action changes --- plugins/modules/zos_job_submit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index ae3828c0e..4760e5a06 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -515,7 +515,7 @@ POLLING_INTERVAL = 1 POLLING_COUNT = 60 -JOB_COMPLETION_MESSAGES = ["CC", "ABEND", "SEC", "JCL ERROR"] +JOB_COMPLETION_MESSAGES = ["CC", "ABEND", "SEC ERROR", "JCL ERROR"] def submit_pds_jcl(src, module): @@ -749,7 +749,7 @@ def run_module(): except SubmitJCLError as e: module.fail_json(msg=repr(e), **result) if jobId is None or jobId == "": - result["job_id"] = "-blank-" + result["job_id"] = "" module.fail_json( msg="JOB ID RETURNED IS None. PLEASE CHECK WHETHER THE JCL IS CORRECT.", **result From 85ded1b6c45e333f8209a12629817fadc008ba1b Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Thu, 18 Mar 2021 15:47:59 -0400 Subject: [PATCH 16/18] changed job to return msg_code=None on JCL error updated documentation of return to note this --- plugins/module_utils/job.py | 14 ++++++++------ plugins/modules/zos_job_submit.py | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/module_utils/job.py b/plugins/module_utils/job.py index fc50bee49..633220e34 100644 --- a/plugins/module_utils/job.py +++ b/plugins/module_utils/job.py @@ -125,6 +125,11 @@ def _parse_jobs(output_str): job["ret_code"]["code"] = _get_return_code_num(ret_code_msg) job["ret_code"]["msg_code"] = _get_return_code_str(ret_code_msg) job["ret_code"]["msg_txt"] = "" + if "JCL ERROR" in ret_code_msg: + job["ret_code"][ + "msg_txt" + ] = "JCL Error detected. Check the data dumps for more information." + if ret_code_msg == "": job["ret_code"]["msg"] = "AC" @@ -454,12 +459,9 @@ def _get_return_code_num(rc_str): """ rc = None - if "JCL ERROR" in rc_str: - rc = 16 - else: - match = re.search(r"\s*CC\s*([0-9]+)", rc_str) - if match: - rc = int(match.group(1)) + match = re.search(r"\s*CC\s*([0-9]+)", rc_str) + if match: + rc = int(match.group(1)) return rc diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index 4760e5a06..c9909a1d1 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -221,10 +221,11 @@ description: Returns additional information related to the job. type: str - sample: "No job can be located with this job name: HELLO" + sample: "JCL Error detected. Check the data dumps for more information." code: description: Return code converted to integer value (when possible). + For JCL ERRORs, this will be None. type: int sample: 00 sample: From e67abd8a7e3998bf3ffe20bfb9aee3344b515c32 Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Thu, 6 May 2021 13:51:05 -0400 Subject: [PATCH 17/18] Changed documentation to help clarify the wait/wait_time_s issue Fixed another all-caps error message Added JobId info to long-running meessage note. --- plugins/modules/zos_job_submit.py | 71 +++++++++++++++++-------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index c9909a1d1..d544f0f9a 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -13,6 +13,21 @@ # limitations under the License. from __future__ import absolute_import, division, print_function +from ansible.module_utils.six import PY3 +from stat import S_IEXEC, S_IREAD, S_IWRITE +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.encode import ( + Defaults, +) +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.better_arg_parser import ( + BetterArgParser, +) +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.job import job_output +from timeit import default_timer as timer +import re +from tempfile import NamedTemporaryFile +from os import chmod, path, remove, stat +from time import sleep +from ansible.module_utils.basic import AnsibleModule __metaclass__ = type @@ -57,15 +72,16 @@ type: bool description: - Wait for the Job to finish and capture the output. Default is false. - - User can specify the wait time, see option ``wait_time_s``. + - When I(wait) is false or absent, the module will wait up to 10 seconds for the job to start, + but will not wait for the job to complete. + - If I(wait) is true, User can specify the wait time, see option ``wait_time_s``. wait_time_s: required: false default: 60 type: int description: - - When wait is true, the module will wait for a maximum of 60 seconds by - default. - - User can set the wait time manually in this option. + - When I(wait) is true, the module will wait for the number of seconds for Job completion. + - User can set the wait time manually with this option. max_rc: required: false type: int @@ -490,22 +506,6 @@ wait_time_s: 30 """ -from ansible.module_utils.basic import AnsibleModule -from time import sleep -from os import chmod, path, remove, stat -from tempfile import NamedTemporaryFile -import re -from timeit import default_timer as timer - -from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.job import job_output -from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.better_arg_parser import ( - BetterArgParser, -) -from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.encode import ( - Defaults, -) -from stat import S_IEXEC, S_IREAD, S_IWRITE -from ansible.module_utils.six import PY3 if PY3: from shlex import quote @@ -566,7 +566,8 @@ def submit_jcl_in_volume(src, vol, module): if "Error" in stdout: raise SubmitJCLError("SUBMIT JOB FAILED: " + stdout) elif "" == stdout: - raise SubmitJCLError("SUBMIT JOB FAILED, NO JOB ID IS RETURNED : " + stdout) + raise SubmitJCLError( + "SUBMIT JOB FAILED, NO JOB ID IS RETURNED : " + stdout) jobId = stdout.replace("\n", "").strip() return jobId @@ -579,7 +580,8 @@ def copy_rexx_and_run(script, src, vol, module): chmod(tmp_file.name, S_IEXEC | S_IREAD | S_IWRITE) pathName = path.dirname(tmp_file.name) scriptName = path.basename(tmp_file.name) - rc, stdout, stderr = module.run_command(["./" + scriptName, src, vol], cwd=pathName) + rc, stdout, stderr = module.run_command( + ["./" + scriptName, src, vol], cwd=pathName) return rc, stdout, stderr @@ -662,7 +664,8 @@ def run_module(): default="DATA_SET", choices=["DATA_SET", "USS", "LOCAL"], ), - from_encoding=dict(arg_type="encoding", default=Defaults.DEFAULT_ASCII_CHARSET), + from_encoding=dict(arg_type="encoding", + default=Defaults.DEFAULT_ASCII_CHARSET), to_encoding=dict( arg_type="encoding", default=Defaults.DEFAULT_EBCDIC_USS_CHARSET ), @@ -752,7 +755,7 @@ def run_module(): if jobId is None or jobId == "": result["job_id"] = "" module.fail_json( - msg="JOB ID RETURNED IS None. PLEASE CHECK WHETHER THE JCL IS CORRECT.", + msg="JOB ID Returned is None. Please check whether the JCL is valid.", **result ) @@ -781,7 +784,8 @@ def run_module(): if bool(jot_retcode): job_msg = jot_retcode.get("msg") if re.search( - "^(?:{0})".format("|".join(JOB_COMPLETION_MESSAGES)), job_msg + "^(?:{0})".format( + "|".join(JOB_COMPLETION_MESSAGES)), job_msg ): loopdone = True # if the message doesn't have a CC, it is an improper completion (error/abend) @@ -794,9 +798,10 @@ def run_module(): if duration >= wait_time_s: loopdone = True result["message"] = { - "stdout": "Submit JCL operation succeeded but it is a long running job. Timeout is " + "stdout": "Submit JCL operation succeeded but it is a long running job, exceeding the timeout of " + str(wait_time_s) - + " seconds." + + " seconds. JobID is " + + str(jobId) + ". Consider using module zos_job_query to poll for long running jobs." } else: sleep(0.5) @@ -819,10 +824,12 @@ def run_module(): result["changed"] = True if duration >= wait_time_s: + # This is a duplicate message, to handle the edge-case where the timeout was crossed after the check result["message"] = { - "stdout": "Submit JCL operation succeeded but it is a long running job. Timeout is " + "stdout": "Submit JCL operation succeeded but it is a long running job, exceeding the timeout of " + str(wait_time_s) - + " seconds." + + " seconds. JobID is " + + str(jobId) + ". Consider using module zos_job_query to poll for long running jobs." } else: if foundissue is not None: @@ -833,7 +840,8 @@ def run_module(): result["failed"] = True module.fail_json(msg=result["message"], **result) else: - result["message"] = {"stdout": "Submit JCL operation succeeded."} + result["message"] = { + "stdout": "Submit JCL operation succeeded with id of " + str(jobId) + "."} module.exit_json(**result) @@ -844,7 +852,8 @@ class Error(Exception): class SubmitJCLError(Error): def __init__(self, jobs): - self.msg = 'An error occurred during submission of jobs "{0}"'.format(jobs) + self.msg = 'An error occurred during submission of jobs "{0}"'.format( + jobs) def main(): From 2f1ad77d0f201fb06dd3caad70d8ec6f610679ea Mon Sep 17 00:00:00 2001 From: Rich Parker Date: Thu, 6 May 2021 13:59:35 -0400 Subject: [PATCH 18/18] forgot to switch back to black editor...autopep8 moved the import statements argh! --- plugins/modules/zos_job_submit.py | 57 ++++++++++++++++--------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/plugins/modules/zos_job_submit.py b/plugins/modules/zos_job_submit.py index d544f0f9a..1c9159986 100644 --- a/plugins/modules/zos_job_submit.py +++ b/plugins/modules/zos_job_submit.py @@ -13,21 +13,6 @@ # limitations under the License. from __future__ import absolute_import, division, print_function -from ansible.module_utils.six import PY3 -from stat import S_IEXEC, S_IREAD, S_IWRITE -from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.encode import ( - Defaults, -) -from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.better_arg_parser import ( - BetterArgParser, -) -from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.job import job_output -from timeit import default_timer as timer -import re -from tempfile import NamedTemporaryFile -from os import chmod, path, remove, stat -from time import sleep -from ansible.module_utils.basic import AnsibleModule __metaclass__ = type @@ -506,6 +491,22 @@ wait_time_s: 30 """ +from ansible.module_utils.six import PY3 +from stat import S_IEXEC, S_IREAD, S_IWRITE +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.encode import ( + Defaults, +) +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.better_arg_parser import ( + BetterArgParser, +) +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.job import job_output +from timeit import default_timer as timer +import re +from tempfile import NamedTemporaryFile +from os import chmod, path, remove, stat +from time import sleep +from ansible.module_utils.basic import AnsibleModule + if PY3: from shlex import quote @@ -566,8 +567,7 @@ def submit_jcl_in_volume(src, vol, module): if "Error" in stdout: raise SubmitJCLError("SUBMIT JOB FAILED: " + stdout) elif "" == stdout: - raise SubmitJCLError( - "SUBMIT JOB FAILED, NO JOB ID IS RETURNED : " + stdout) + raise SubmitJCLError("SUBMIT JOB FAILED, NO JOB ID IS RETURNED : " + stdout) jobId = stdout.replace("\n", "").strip() return jobId @@ -580,8 +580,7 @@ def copy_rexx_and_run(script, src, vol, module): chmod(tmp_file.name, S_IEXEC | S_IREAD | S_IWRITE) pathName = path.dirname(tmp_file.name) scriptName = path.basename(tmp_file.name) - rc, stdout, stderr = module.run_command( - ["./" + scriptName, src, vol], cwd=pathName) + rc, stdout, stderr = module.run_command(["./" + scriptName, src, vol], cwd=pathName) return rc, stdout, stderr @@ -664,8 +663,7 @@ def run_module(): default="DATA_SET", choices=["DATA_SET", "USS", "LOCAL"], ), - from_encoding=dict(arg_type="encoding", - default=Defaults.DEFAULT_ASCII_CHARSET), + from_encoding=dict(arg_type="encoding", default=Defaults.DEFAULT_ASCII_CHARSET), to_encoding=dict( arg_type="encoding", default=Defaults.DEFAULT_EBCDIC_USS_CHARSET ), @@ -784,8 +782,7 @@ def run_module(): if bool(jot_retcode): job_msg = jot_retcode.get("msg") if re.search( - "^(?:{0})".format( - "|".join(JOB_COMPLETION_MESSAGES)), job_msg + "^(?:{0})".format("|".join(JOB_COMPLETION_MESSAGES)), job_msg ): loopdone = True # if the message doesn't have a CC, it is an improper completion (error/abend) @@ -801,7 +798,8 @@ def run_module(): "stdout": "Submit JCL operation succeeded but it is a long running job, exceeding the timeout of " + str(wait_time_s) + " seconds. JobID is " - + str(jobId) + ". Consider using module zos_job_query to poll for long running jobs." + + str(jobId) + + ". Consider using module zos_job_query to poll for long running jobs." } else: sleep(0.5) @@ -829,7 +827,8 @@ def run_module(): "stdout": "Submit JCL operation succeeded but it is a long running job, exceeding the timeout of " + str(wait_time_s) + " seconds. JobID is " - + str(jobId) + ". Consider using module zos_job_query to poll for long running jobs." + + str(jobId) + + ". Consider using module zos_job_query to poll for long running jobs." } else: if foundissue is not None: @@ -841,7 +840,10 @@ def run_module(): module.fail_json(msg=result["message"], **result) else: result["message"] = { - "stdout": "Submit JCL operation succeeded with id of " + str(jobId) + "."} + "stdout": "Submit JCL operation succeeded with id of " + + str(jobId) + + "." + } module.exit_json(**result) @@ -852,8 +854,7 @@ class Error(Exception): class SubmitJCLError(Error): def __init__(self, jobs): - self.msg = 'An error occurred during submission of jobs "{0}"'.format( - jobs) + self.msg = 'An error occurred during submission of jobs "{0}"'.format(jobs) def main():