Skip to content

Commit 248ba87

Browse files
authored
[module_utils/data_set] surface errors in data_set_cataloged function (#1535)
* raise exception when mvscmd fails; add supporting unit tests Signed-off-by: Ketan Kelkar <[email protected]> * add changelog fragment Signed-off-by: Ketan Kelkar <[email protected]> * re-categorize changelog fragment Signed-off-by: Ketan Kelkar <[email protected]> * add full message text for rc=4 stdout Signed-off-by: Ketan Kelkar <[email protected]> * update data_set_cataloged function to resolve gdg/gds to absolute names before passing them into listcat Signed-off-by: Ketan Kelkar <[email protected]> * update data_set_cataloged function return false if GDSNameResolveError is caught Signed-off-by: Ketan Kelkar <[email protected]> * rename changelog fragment file Signed-off-by: Ketan Kelkar <[email protected]> * Update test_zos_blockinfile_func.py upper case 'FILE*' in c pgm * update data set func test case to use shell commands instead of zos_copy Signed-off-by: Ketan Kelkar <[email protected]> * add escape chars to c_pgm Signed-off-by: Ketan Kelkar <[email protected]> * re-add cleanup steps, remove commented out call to zos_copy Signed-off-by: Ketan Kelkar <[email protected]> --------- Signed-off-by: Ketan Kelkar <[email protected]>
1 parent 0a5ef16 commit 248ba87

File tree

5 files changed

+158
-9
lines changed

5 files changed

+158
-9
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
bugfixes:
2+
- module_util/data_set.py - DataSet.data_set_cataloged function previously only returned
3+
True or False, but failed to account for exceptions which occurred during the LISTCAT.
4+
The fix now raises an MVSCmdExecError if the return code from LISTCAT is too high.
5+
(https://github.com/ansible-collections/ibm_zos_core/pull/1535).

plugins/module_utils/data_set.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,19 @@ def data_set_cataloged(name, volumes=None):
352352
353353
Returns:
354354
bool -- If data is is cataloged.
355+
356+
Raise:
357+
MVSCmdExecError: When the call to IDCAMS fails with rc greater than 4.
355358
"""
359+
360+
# Resolve GDS names before passing it into listcat
361+
if DataSet.is_gds_relative_name(name):
362+
try:
363+
name = DataSet.resolve_gds_absolute_name(name)
364+
except GDSNameResolveError:
365+
# if GDS name cannot be resolved, it's not in the catalog.
366+
return False
367+
356368
# We need to unescape because this calls to system can handle
357369
# special characters just fine.
358370
name = name.upper().replace("\\", '')
@@ -363,6 +375,13 @@ def data_set_cataloged(name, volumes=None):
363375
"mvscmdauth --pgm=idcams --sysprint=* --sysin=stdin", data=stdin
364376
)
365377

378+
# The above 'listcat entries' command to idcams returns:
379+
# rc=0 if data set found in catalog
380+
# rc=4 if data set NOT found in catalog
381+
# rc>4 for other errors
382+
if rc > 4:
383+
raise MVSCmdExecError(rc, stdout, stderr)
384+
366385
if volumes:
367386
cataloged_volume_list = DataSet.data_set_cataloged_volume_list(name) or []
368387
if bool(set(volumes) & set(cataloged_volume_list)):
@@ -380,13 +399,22 @@ def data_set_cataloged_volume_list(name):
380399
name (str) -- The data set name to check if cataloged.
381400
Returns:
382401
list{str} -- A list of volumes where the dataset is cataloged.
402+
Raise:
403+
MVSCmdExecError: When the call to IDCAMS fails with rc greater than 4.
383404
"""
384405
name = name.upper()
385406
module = AnsibleModuleHelper(argument_spec={})
386407
stdin = " LISTCAT ENTRIES('{0}') ALL".format(name)
387408
rc, stdout, stderr = module.run_command(
388409
"mvscmdauth --pgm=idcams --sysprint=* --sysin=stdin", data=stdin
389410
)
411+
# The above 'listcat entries all' command to idcams returns:
412+
# rc=0 if data set found in catalog
413+
# rc=4 if data set NOT found in catalog
414+
# rc>4 for other errors
415+
if rc > 4:
416+
raise MVSCmdExecError(rc, stdout, stderr)
417+
390418
delimiter = 'VOLSER------------'
391419
arr = stdout.split(delimiter)[1:] # throw away header
392420

tests/functional/modules/test_zos_blockinfile_func.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
{
3131
char dsname[ strlen(argv[1]) + 4];
3232
sprintf(dsname, \\\"//'%s'\\\", argv[1]);
33-
file* member;
33+
FILE* member;
3434
member = fopen(dsname, \\\"rb,type=record\\\");
3535
sleep(300);
3636
fclose(member);

tests/functional/modules/test_zos_data_set_func.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,9 @@ def test_batch_data_set_and_member_creation(ansible_zos_module):
514514
int main(int argc, char** argv)
515515
{
516516
char dsname[ strlen(argv[1]) + 4];
517-
sprintf(dsname, "//'%s'", argv[1]);
517+
sprintf(dsname, \\\"//'%s'\\\", argv[1]);
518518
FILE* member;
519-
member = fopen(dsname, "rb,type=record");
519+
member = fopen(dsname, \\\"rb,type=record\\\");
520520
sleep(300);
521521
fclose(member);
522522
return 0;
@@ -581,12 +581,11 @@ def test_data_member_force_delete(ansible_zos_module):
581581
for result in results.contacted.values():
582582
assert result.get("changed") is True
583583

584-
# copy/compile c program and copy jcl to hold data set lock for n seconds in background(&)
585-
hosts.all.zos_copy(content=c_pgm, dest='/tmp/disp_shr/pdse-lock.c', force=True)
586-
hosts.all.zos_copy(
587-
content=call_c_jcl.format(default_data_set_name, member_1),
588-
dest='/tmp/disp_shr/call_c_pgm.jcl',
589-
force=True
584+
hosts.all.file(path="/tmp/disp_shr/", state="directory")
585+
hosts.all.shell(cmd=f"echo \"{c_pgm}\" > /tmp/disp_shr/pdse-lock.c")
586+
hosts.all.shell(
587+
cmd=f"echo \"{call_c_jcl.format(default_data_set_name, member_1)}\""+
588+
" > /tmp/disp_shr/call_c_pgm.jcl"
590589
)
591590
hosts.all.shell(cmd="xlc -o pdse-lock pdse-lock.c", chdir="/tmp/disp_shr/")
592591

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright (c) IBM Corporation 2024
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
15+
from __future__ import absolute_import, division, print_function
16+
17+
__metaclass__ = type
18+
19+
import pytest
20+
21+
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.data_set import MVSCmdExecError
22+
23+
IMPORT_NAME = "ansible_collections.ibm.ibm_zos_core.plugins.module_utils.data_set"
24+
25+
26+
class DummyModule(object):
27+
"""Used in place of Ansible's module
28+
so we can easily mock the desired behavior."""
29+
30+
def __init__(self, rc=0, stdout="", stderr=""):
31+
self.rc = rc
32+
self.stdout = stdout
33+
self.stderr = stderr
34+
35+
def run_command(self, *args, **kwargs):
36+
return (self.rc, self.stdout, self.stderr)
37+
38+
39+
# Unit tests are intended to exercise code paths (not test for functionality).
40+
41+
# These unit tests are NOT run on any z/OS system, so hard-coded data set names will not matter.
42+
data_set_name = "USER.PRIVATE.TESTDS"
43+
44+
stdout_ds_in_catatlog = """0
45+
LISTCAT ENTRIES('{0}')
46+
0NONVSAM ------- {0}
47+
IN-CAT --- CATALOG.SVPLEX9.MASTER
48+
1IDCAMS SYSTEM SERVICES """.format(data_set_name)
49+
50+
stdout_ds_not_in_catalog="""
51+
1IDCAMS SYSTEM SERVICES TIME: 13:34:18 06/06/24 PAGE 1
52+
0
53+
LISTCAT ENTRIES('{0}')
54+
0IDC3012I ENTRY {0} NOT FOUND
55+
IDC3009I ** VSAM CATALOG RETURN CODE IS 8 - REASON CODE IS IGG0CLEG-42
56+
IDC1566I ** {0} NOT LISTED
57+
1IDCAMS SYSTEM SERVICES TIME: 13:34:18 06/06/24 PAGE 2
58+
0 THE NUMBER OF ENTRIES PROCESSED WAS:
59+
0 AIX -------------------0
60+
ALIAS -----------------0
61+
CLUSTER ---------------0
62+
DATA ------------------0
63+
GDG -------------------0
64+
INDEX -----------------0
65+
NONVSAM ---------------0
66+
PAGESPACE -------------0
67+
PATH ------------------0
68+
SPACE -----------------0
69+
USERCATALOG -----------0
70+
TAPELIBRARY -----------0
71+
TAPEVOLUME ------------0
72+
TOTAL -----------------0
73+
0 THE NUMBER OF PROTECTED ENTRIES SUPPRESSED WAS 0
74+
0IDC0001I FUNCTION COMPLETED, HIGHEST CONDITION CODE WAS 4
75+
0
76+
0IDC0002I IDCAMS PROCESSING COMPLETE. MAXIMUM CONDITION CODE WAS 4
77+
""".format(data_set_name)
78+
79+
# passing in a lowercase data set causes idcams to fail.
80+
# this behavior isn't possible via ansible because we upper-case the input.
81+
stdout_mvscmd_failed="""0
82+
LISTCAT ENTRIES('...................')
83+
0IDC3203I ITEM '...................' DOES NOT ADHERE TO RESTRICTIONS
84+
0IDC3202I ABOVE TEXT BYPASSED UNTIL NEXT COMMAND. CONDITION CODE IS 12
85+
0
86+
0IDC0002I IDCAMS PROCESSING COMPLETE. MAXIMUM CONDITION CODE WAS 12"""
87+
88+
89+
@pytest.mark.parametrize(
90+
("rc, stdout, expected_return, expected_exception_type"),
91+
[
92+
(0, stdout_ds_in_catatlog, True, None),
93+
(4, stdout_ds_not_in_catalog, False, None),
94+
(12, stdout_mvscmd_failed, None, MVSCmdExecError)
95+
],
96+
)
97+
def test_dataset_cataloged_unit(zos_import_mocker, rc, stdout, expected_return, expected_exception_type):
98+
mocker, importer = zos_import_mocker
99+
zos_module_util_data_set = importer(IMPORT_NAME)
100+
mocker.patch(
101+
"{0}.AnsibleModuleHelper".format(IMPORT_NAME),
102+
create=True,
103+
return_value=DummyModule(rc=rc, stdout=stdout),
104+
)
105+
106+
107+
results = None
108+
error_raised = False
109+
try:
110+
results = zos_module_util_data_set.DataSet.data_set_cataloged(data_set_name)
111+
except Exception as e:
112+
error_raised = True
113+
assert type(e) == expected_exception_type
114+
finally:
115+
if not expected_exception_type:
116+
assert not error_raised
117+
assert results == expected_return

0 commit comments

Comments
 (0)