Skip to content

Commit 45eb69f

Browse files
authored
[Enhancement] [zos_job_submit] Async support for zos_job_submit (#1786)
* Add async support for zos_copy * Add use of remote_tmp in action plugin * Remove use of ansible's tempfile * Fix temp file path * Add async support to zos_job_submit * Update removal of temp files * Update authors list * Add test for async support * Remove async support from zos_copy * Remove unnecessary blank line * Add changelog fragment * Fix playbook used in async test * Fix inventory used in async test * Remove commented code * Update test
1 parent 7b58f33 commit 45eb69f

File tree

4 files changed

+139
-34
lines changed

4 files changed

+139
-34
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
minor_changes:
2+
- zos_job_submit - Added support to run zos_job_submit tasks in async mode inside
3+
playbooks.
4+
(https://github.com/ansible-collections/ibm_zos_core/pull/1786).

plugins/action/zos_job_submit.py

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@
2323

2424
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import template
2525

26+
from datetime import datetime
27+
from os import path
2628

2729
display = Display()
2830

2931

3032
class ActionModule(ActionBase):
3133
def run(self, tmp=None, task_vars=None):
3234
""" handler for file transfer operations """
35+
self._supports_async = True
36+
3337
if task_vars is None:
3438
task_vars = {}
3539

@@ -54,22 +58,9 @@ def run(self, tmp=None, task_vars=None):
5458

5559
source = self._task.args.get("src", None)
5660

57-
# Get a temporary file on the managed node
58-
tempfile = self._execute_module(
59-
module_name="tempfile", module_args={}, task_vars=task_vars,
60-
)
61-
dest_path = tempfile.get("path")
62-
# Calling execute_module from this step with tempfile leaves behind a tmpdir.
63-
# This is called to ensure the proper removal.
64-
tmpdir = self._connection._shell.tmpdir
65-
if tmpdir:
66-
self._remove_tmp_path(tmpdir)
67-
6861
result["failed"] = True
6962
if source is None:
7063
result["msg"] = "Source is required."
71-
elif dest_path is None:
72-
result["msg"] = "Failed copying to remote, destination file was not created. {0}".format(tempfile.get("msg"))
7364
elif source is not None and os.path.isdir(to_bytes(source, errors="surrogate_or_strict")):
7465
result["msg"] = "Source must be a file."
7566
else:
@@ -85,26 +76,27 @@ def run(self, tmp=None, task_vars=None):
8576
result["msg"] = to_text(e)
8677
return result
8778

88-
if tmp is None or "-tmp-" not in tmp:
89-
tmp = self._make_tmp_path()
79+
tmp_dir = self._connection._shell._options.get("remote_tmp")
80+
rc, stdout, stderr = self._connection.exec_command("cd {0} && pwd".format(tmp_dir))
81+
if rc > 0:
82+
msg = f"Failed to resolve remote temporary directory {tmp_dir}. Ensure that the directory exists and user has proper access."
83+
return self._exit_action({}, msg, failed=True)
84+
85+
tmp_dir = stdout.decode("utf-8").replace("\r", "").replace("\n", "")
86+
temp_file_dir = f'zos_job_submit_{datetime.now().strftime("%Y%m%d%S%f")}'
87+
dest_path = path.join(tmp_dir, temp_file_dir, path.basename(source))
88+
# Creating the name for the temp file needed.
89+
self._connection.exec_command("mkdir -p {0}".format(path.dirname(dest_path)))
9090

9191
source_full = None
9292
try:
9393
source_full = self._loader.get_real_file(source)
94-
# source_rel = os.path.basename(source)
9594
except AnsibleFileNotFound as e:
9695
result["failed"] = True
9796
result["msg"] = "Source {0} not found. {1}".format(source_full, e)
9897
self._remove_tmp_path(tmp)
9998
return result
10099

101-
# if self._connection._shell.path_has_trailing_slash(dest):
102-
# dest_file = self._connection._shell.join_path(dest, source_rel)
103-
# else:
104-
self._connection._shell.join_path(dest_path)
105-
106-
tmp_src = self._connection._shell.join_path(tmp, "source")
107-
108100
rendered_file = None
109101
if use_template:
110102
template_parameters = module_args.get("template_parameters", dict())
@@ -129,36 +121,35 @@ def run(self, tmp=None, task_vars=None):
129121

130122
source_full = rendered_file
131123

132-
remote_path = None
133-
remote_path = self._transfer_file(source_full, tmp_src)
134-
135-
if remote_path:
136-
self._fixup_perms2((tmp, remote_path))
137-
138124
result = {}
139125
copy_module_args = {}
140126
module_args = self._task.args.copy()
141127

142128
copy_module_args.update(
143129
dict(
144-
src=tmp_src,
130+
src=source_full,
145131
dest=dest_path,
146132
mode="0600",
147133
force=True,
148134
encoding=module_args.get('encoding'),
149-
remote_src=True,
135+
remote_src=False,
150136
)
151137
)
152138
copy_task = self._task.copy()
153139
copy_task.args = copy_module_args
140+
# Making the zos_copy task run synchronously every time.
141+
copy_task.async_val = 0
142+
154143
copy_action = self._shared_loader_obj.action_loader.get(
155144
'ibm.ibm_zos_core.zos_copy',
156145
task=copy_task,
157146
connection=self._connection,
158147
play_context=self._play_context,
159148
loader=self._loader,
160149
templar=self._templar,
161-
shared_loader_obj=self._shared_loader_obj)
150+
shared_loader_obj=self._shared_loader_obj
151+
)
152+
162153
result.update(copy_action.run(task_vars=task_vars))
163154
if result.get("msg") is None:
164155
module_args["src"] = dest_path
@@ -167,6 +158,7 @@ def run(self, tmp=None, task_vars=None):
167158
module_name="ibm.ibm_zos_core.zos_job_submit",
168159
module_args=module_args,
169160
task_vars=task_vars,
161+
wrap_async=self._task.async_val
170162
)
171163
)
172164
else:
@@ -178,6 +170,7 @@ def run(self, tmp=None, task_vars=None):
178170
module_name="ibm.ibm_zos_core.zos_job_submit",
179171
module_args=module_args,
180172
task_vars=task_vars,
173+
wrap_async=self._task.async_val
181174
)
182175
)
183176

plugins/modules/zos_job_submit.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- "Xiao Yuan Ma (@bjmaxy)"
2424
- "Rich Parker (@richp405)"
2525
- "Demetrios Dimatos (@ddimatos)"
26+
- "Ivan Moreno (@rexemin)"
2627
short_description: Submit JCL
2728
description:
2829
- Submit JCL in a data set, USS file, or file on the controller.
@@ -657,7 +658,8 @@
657658
from ansible.module_utils.basic import AnsibleModule
658659
from ansible.module_utils._text import to_text
659660
from timeit import default_timer as timer
660-
from os import remove
661+
from os import path
662+
import shutil
661663
import traceback
662664
from time import sleep
663665
import re
@@ -1202,7 +1204,7 @@ def run_module():
12021204

12031205
finally:
12041206
if temp_file is not None:
1205-
remove(temp_file)
1207+
shutil.rmtree(path.dirname(temp_file))
12061208

12071209
# If max_rc is set, we don't want to default to changed=True, rely on 'is_changed'
12081210
result["changed"] = True if is_changed else False

tests/functional/modules/test_zos_job_submit_func.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
import tempfile
1919
import re
2020
import os
21+
import yaml
2122
from shellescape import quote
2223
import pytest
2324
from datetime import datetime
25+
import subprocess
2426

2527
from ibm_zos_core.tests.helpers.volumes import Volume_Handler
2628
from ibm_zos_core.tests.helpers.dataset import get_tmp_ds_name
@@ -381,6 +383,48 @@
381383
//
382384
"""
383385

386+
PLAYBOOK_ASYNC_TEST = """- hosts: zvm
387+
collections:
388+
- ibm.ibm_zos_core
389+
gather_facts: False
390+
environment:
391+
_BPXK_AUTOCVT: "ON"
392+
ZOAU_HOME: "{0}"
393+
PYTHONPATH: "{0}/lib/{2}"
394+
LIBPATH: "{0}/lib:{1}/lib:/lib:/usr/lib:."
395+
PATH: "{0}/bin:/bin:/usr/lpp/rsusr/ported/bin:/var/bin:/usr/lpp/rsusr/ported/bin:/usr/lpp/java/java180/J8.0_64/bin:{1}/bin:"
396+
_CEE_RUNOPTS: "FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)"
397+
_TAG_REDIR_ERR: "txt"
398+
_TAG_REDIR_IN: "txt"
399+
_TAG_REDIR_OUT: "txt"
400+
LANG: "C"
401+
402+
tasks:
403+
- name: Submit async job.
404+
ibm.ibm_zos_core.zos_job_submit:
405+
src: {3}
406+
location: local
407+
async: 45
408+
poll: 0
409+
register: job_task
410+
411+
- name: Query async task.
412+
async_status:
413+
jid: "{{{{ job_task.ansible_job_id }}}}"
414+
register: job_result
415+
until: job_result.finished
416+
retries: 20
417+
delay: 5
418+
"""
419+
420+
INVENTORY_ASYNC_TEST = """all:
421+
hosts:
422+
zvm:
423+
ansible_host: {0}
424+
ansible_ssh_private_key_file: {1}
425+
ansible_user: {2}
426+
ansible_python_interpreter: {3}"""
427+
384428

385429
@pytest.mark.parametrize(
386430
"location", [
@@ -1113,3 +1157,65 @@ def test_zoau_bugfix_invalid_utf8_chars(ansible_zos_module):
11131157
assert result.get("changed") is True
11141158
finally:
11151159
hosts.all.file(path=temp_path, state="absent")
1160+
1161+
1162+
def test_job_submit_async(get_config):
1163+
# Creating temp JCL file used by the playbook.
1164+
tmp_file = tempfile.NamedTemporaryFile(delete=True)
1165+
with open(tmp_file.name, "w",encoding="utf-8") as f:
1166+
f.write(JCL_FILE_CONTENTS)
1167+
1168+
# Getting all the info required to run the playbook.
1169+
path = get_config
1170+
with open(path, 'r') as file:
1171+
enviroment = yaml.safe_load(file)
1172+
1173+
ssh_key = enviroment["ssh_key"]
1174+
hosts = enviroment["host"].upper()
1175+
user = enviroment["user"].upper()
1176+
python_path = enviroment["python_path"]
1177+
cut_python_path = python_path[:python_path.find('/bin')].strip()
1178+
zoau = enviroment["environment"]["ZOAU_ROOT"]
1179+
python_version = cut_python_path.split('/')[2]
1180+
1181+
playbook = tempfile.NamedTemporaryFile(delete=True)
1182+
inventory = tempfile.NamedTemporaryFile(delete=True)
1183+
1184+
os.system("echo {0} > {1}".format(
1185+
quote(PLAYBOOK_ASYNC_TEST.format(
1186+
zoau,
1187+
cut_python_path,
1188+
python_version,
1189+
tmp_file.name
1190+
)),
1191+
playbook.name
1192+
))
1193+
1194+
os.system("echo {0} > {1}".format(
1195+
quote(INVENTORY_ASYNC_TEST.format(
1196+
hosts,
1197+
ssh_key,
1198+
user,
1199+
python_path
1200+
)),
1201+
inventory.name
1202+
))
1203+
1204+
command = "ansible-playbook -i {0} {1}".format(
1205+
inventory.name,
1206+
playbook.name
1207+
)
1208+
1209+
result = subprocess.run(
1210+
command,
1211+
capture_output=True,
1212+
shell=True,
1213+
timeout=120,
1214+
encoding='utf-8'
1215+
)
1216+
1217+
assert result.returncode == 0
1218+
assert "ok=2" in result.stdout
1219+
assert "changed=2" in result.stdout
1220+
assert result.stderr == ""
1221+

0 commit comments

Comments
 (0)