Skip to content

Commit dad619d

Browse files
authored
refactor and patch job output checking (#6)
* refactor and patch job output checking * updated documentation, output formatting, and added RC validation * updated response information * updated parsing of abend codes * updated return example formatting
1 parent bcba3e6 commit dad619d

File tree

3 files changed

+553
-407
lines changed

3 files changed

+553
-407
lines changed

plugins/module_utils/job.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Copyright (c) IBM Corporation 2019, 2020
2+
# Apache License, Version 2.0 (see https://opensource.org/licenses/Apache-2.0)
3+
4+
from tempfile import NamedTemporaryFile
5+
from os import chmod, path, remove
6+
import json
7+
import re
8+
9+
def job_output(module, job_id='', owner='', job_name='', dd_name=''):
10+
"""Get the output from a z/OS job based on various search criteria.
11+
12+
Arguments:
13+
module {AnsibleModule} -- The AnsibleModule object from the running module.
14+
15+
Keyword Arguments:
16+
job_id {str} -- The job ID to search for (default: {''})
17+
owner {str} -- The owner of the job (default: {''})
18+
job_name {str} -- The job name search for (default: {''})
19+
dd_name {str} -- The data definition to retrieve (default: {''})
20+
21+
Raises:
22+
RuntimeError: When job output cannot be retrieved successfully but job exists.
23+
RuntimeError: When no job output is found
24+
25+
Returns:
26+
dict[str, list[dict]] -- The output information for a given job.
27+
"""
28+
job_detail_json = {}
29+
rc, out, err = _get_job_json_str(module, job_id, owner, job_name, dd_name)
30+
if rc != 0:
31+
raise RuntimeError(
32+
'Failed to retrieve job output. RC: {} Error: {}'.format(str(rc), str(err)))
33+
if not out:
34+
raise RuntimeError(
35+
'Failed to retrieve job output. No job output found.')
36+
job_detail_json = json.loads(out, strict=False)
37+
for job in job_detail_json.get('jobs'):
38+
job['ret_code'] = {} if job.get('ret_code') == None else job.get('ret_code')
39+
job['ret_code']['code'] = _get_return_code_num(job.get('ret_code', {}).get('msg', ''))
40+
job['ret_code']['msg_code'] = _get_return_code_str(job.get('ret_code', {}).get('msg', ''))
41+
job['ret_code']['msg_txt'] = ''
42+
return job_detail_json
43+
44+
45+
def _get_job_json_str(module, job_id='', owner='', job_name='', dd_name=''):
46+
"""Generate JSON output string containing Job info from SDSF.
47+
Writes a temporary REXX script to the USS filesystem to gather output.
48+
49+
Arguments:
50+
module {AnsibleModule} -- The AnsibleModule object from the running module.
51+
52+
Keyword Arguments:
53+
job_id {str} -- The job ID to search for (default: {''})
54+
owner {str} -- The owner of the job (default: {''})
55+
job_name {str} -- The job name search for (default: {''})
56+
dd_name {str} -- The data definition to retrieve (default: {''})
57+
58+
Returns:
59+
tuple[int, str, str] -- RC, STDOUT, and STDERR from the REXX script.
60+
"""
61+
get_job_detail_json_rexx = """/* REXX */
62+
arg options
63+
parse var options param
64+
upper param
65+
parse var param 'JOBID=' jobid ' OWNER=' owner,
66+
' JOBNAME=' jobname ' DDNAME=' ddname
67+
68+
rc=isfcalls('ON')
69+
70+
jobid = strip(jobid,'L')
71+
if (jobid <> '') then do
72+
ISFFILTER='JobID EQ '||jobid
73+
end
74+
owner = strip(owner,'L')
75+
if (owner <> '') then do
76+
ISFOWNER=owner
77+
end
78+
jobname = strip(jobname,'L')
79+
if (jobname <> '') then do
80+
ISFPREFIX=jobname
81+
end
82+
ddname = strip(ddname,'L')
83+
if (ddname == '?') then do
84+
ddname = ''
85+
end
86+
87+
Address SDSF "ISFEXEC ST (ALTERNATE DELAYED)"
88+
if rc<>0 then do
89+
Say '{"jobs":[]}'
90+
Exit 0
91+
end
92+
if isfrows == 0 then do
93+
Say '{"jobs":[]}'
94+
end
95+
else do
96+
Say '{"jobs":['
97+
do ix=1 to isfrows
98+
linecount = 0
99+
if ix<>1 then do
100+
Say ','
101+
end
102+
Say '{'
103+
Say '"'||'job_id'||'":"'||value('JOBID'||"."||ix)||'",'
104+
Say '"'||'job_name'||'":"'||value('JNAME'||"."||ix)||'",'
105+
Say '"'||'subsystem'||'":"'||value('ESYSID'||"."||ix)||'",'
106+
Say '"'||'owner'||'":"'||value('OWNERID'||"."||ix)||'",'
107+
Say '"'||'ret_code'||'":{"'||'msg'||'":"'||value('RETCODE'||"."||ix)||'"},'
108+
Say '"'||'class'||'":"'||value('JCLASS'||"."||ix)||'",'
109+
Say '"'||'content_type'||'":"'||value('JTYPE'||"."||ix)||'",'
110+
Address SDSF "ISFACT ST TOKEN('"TOKEN.ix"') PARM(NP ?)",
111+
"("prefix JDS_
112+
lrc=rc
113+
if lrc<>0 | JDS_DDNAME.0 == 0 then do
114+
Say '"ddnames":[]'
115+
end
116+
else do
117+
Say '"ddnames":['
118+
do jx=1 to JDS_DDNAME.0
119+
if jx<>1 & ddname == '' then do
120+
Say ','
121+
end
122+
if ddname == '' | ddname == value('JDS_DDNAME'||"."||jx) then do
123+
Say '{'
124+
Say '"'||'ddname'||'":"'||value('JDS_DDNAME'||"."||jx)||'",'
125+
Say '"'||'record_count'||'":"'||value('JDS_RECCNT'||"."||jx)||'",'
126+
Say '"'||'id'||'":"'||value('JDS_DSID'||"."||jx)||'",'
127+
Say '"'||'stepname'||'":"'||value('JDS_STEPN'||"."||jx)||'",'
128+
Say '"'||'procstep'||'":"'||value('JDS_PROCS'||"."||jx)||'",'
129+
Say '"'||'byte_count'||'":"'||value('JDS_BYTECNT'||"."||jx)||'",'
130+
Say '"'||'content'||'":['
131+
Address SDSF "ISFBROWSE ST TOKEN('"token.ix"')"
132+
untilline = linecount + JDS_RECCNT.jx
133+
startingcount = linecount + 1
134+
do kx=linecount+1 to untilline
135+
if kx<>startingcount then do
136+
Say ','
137+
end
138+
linecount = linecount + 1
139+
Say '"'||escapeNewLine(escapeDoubleQuote(isfline.kx))||'"'
140+
end
141+
Say ']'
142+
Say '}'
143+
end
144+
end
145+
Say ']'
146+
end
147+
Say '}'
148+
end
149+
Say ']}'
150+
end
151+
152+
rc=isfcalls('OFF')
153+
154+
return 0
155+
156+
escapeDoubleQuote: Procedure
157+
Parse Arg string
158+
out=''
159+
Do While Pos('"',string)<>0
160+
Parse Var string prefix '"' string
161+
out=out||prefix||'\\"'
162+
End
163+
Return out||string
164+
165+
escapeNewLine: Procedure
166+
Parse Arg string
167+
Return translate(string, '4040'x, '1525'x)
168+
"""
169+
try:
170+
dirname, scriptname = _write_script(get_job_detail_json_rexx)
171+
if dd_name is None or dd_name == '?':
172+
dd_name = ''
173+
jobid_param = 'jobid=' + job_id
174+
owner_param = 'owner=' + owner
175+
jobname_param = 'jobname=' + job_name
176+
ddname_param = 'ddname=' + dd_name
177+
cmd = [dirname + '/' + scriptname, jobid_param, owner_param,
178+
jobname_param, ddname_param]
179+
180+
rc, out, err = module.run_command(args=" ".join(cmd),
181+
cwd=dirname,
182+
use_unsafe_shell=True)
183+
except Exception:
184+
raise
185+
finally:
186+
remove(dirname + "/" + scriptname)
187+
return rc, out, err
188+
189+
190+
def _write_script(content):
191+
"""Write a script to the filesystem.
192+
This includes writing and setting the execute bit.
193+
194+
Arguments:
195+
content {str} -- The contents of the script
196+
197+
Returns:
198+
tuple[str, str] -- The directory and script names
199+
"""
200+
delete_on_close = False
201+
try:
202+
tmp_file = NamedTemporaryFile(delete=delete_on_close)
203+
with open(tmp_file.name, 'w') as f:
204+
f.write(content)
205+
chmod(tmp_file.name, 0o755)
206+
dirname = path.dirname(tmp_file.name)
207+
scriptname = path.basename(tmp_file.name)
208+
except Exception:
209+
remove(tmp_file)
210+
raise
211+
return dirname, scriptname
212+
213+
def _get_return_code_num(rc_str):
214+
"""Parse an integer return code from
215+
z/OS job output return code string.
216+
217+
Arguments:
218+
rc_str {str} -- The return code message from z/OS job log (eg. "CC 0000")
219+
220+
Returns:
221+
Union[int, NoneType] -- Returns integer RC if possible, if not returns NoneType
222+
"""
223+
rc = None
224+
match = re.search(r'\s*CC\s*([0-9]+)', rc_str)
225+
if match:
226+
rc = int(match.group(1))
227+
return rc
228+
229+
def _get_return_code_str(rc_str):
230+
"""Parse an intestrger return code from
231+
z/OS job output return code string.
232+
233+
Arguments:
234+
rc_str {str} -- The return code message from z/OS job log (eg. "CC 0000" or "ABEND")
235+
236+
Returns:
237+
Union[str, NoneType] -- Returns string RC or ABEND code if possible, if not returns NoneType
238+
"""
239+
rc = None
240+
match = re.search(r'(?:\s*CC\s*([0-9]+))|(?:ABEND\s*((?:S|U)[0-9]+))', rc_str)
241+
if match:
242+
rc = match.group(1) or match.group(2)
243+
return rc

0 commit comments

Comments
 (0)