Skip to content

Commit 826ca4b

Browse files
[Enabler] [zos_job_submit] Add support for GDG/GDS (#1497)
* modified DatasetCreatedError message * Added gdg functions * Created unit test for validating gds relative name * Updated to fail when future gen * Update arg parser * Adding gdg support for zos_data_set * Add escaping function for data set names * Add unit tests for name escaping * Remove calls to escape_data_set_name * renamed tests * Added MVSDataset class * Updated escaped symbols * Updated tests * Added utils * Add changelog * Uncommented test * Updated exception * Updated mvsdataset class * Updated class * Added type * Added gds tests * Testing for special symbols * Made data set name escaping optional * Use new class for GDS handling * Update special chars data set name * Escape dollar sign in test * Add positive test for GDG/GDS * Add negative GDG tests * Update docs * Fix data set existence check * Update docs again * Add changelog fragment * Fix merge with dev * Fix source validation * Fix validate-modules issue --------- Co-authored-by: Fernando Flores <[email protected]>
1 parent 6499a81 commit 826ca4b

File tree

5 files changed

+145
-33
lines changed

5 files changed

+145
-33
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 - add support for generation data groups and generation
3+
data sets as sources for jobs.
4+
(https://github.com/ansible-collections/ibm_zos_core/pull/1497)

docs/source/modules/zos_job_submit.rst

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ Parameters
3131
src
3232
The source file or data set containing the JCL to submit.
3333

34-
It could be a physical sequential data set, a partitioned data set qualified by a member or a path. (e.g "USER.TEST","USER.JCL(TEST)")
34+
It could be a physical sequential data set, a partitioned data set qualified by a member or a path (e.g. \ :literal:`USER.TEST`\ , \ :literal:`USER.JCL(TEST)`\ ), or a generation data set from a generation data group (for example, \ :literal:`USER.TEST.GDG(-2)`\ ).
3535

36-
Or a USS file. (e.g "/u/tester/demo/sample.jcl")
36+
Or a USS file. (e.g \ :literal:`/u/tester/demo/sample.jcl`\ )
3737

38-
Or a LOCAL file in ansible control node. (e.g "/User/tester/ansible-playbook/sample.jcl")
38+
Or a LOCAL file in ansible control node. (e.g \ :literal:`/User/tester/ansible-playbook/sample.jcl`\ )
39+
40+
When using a generation data set, only already created generations are valid. If either the relative name is positive, or negative but not found, the module will fail.
3941

4042
| **required**: True
4143
| **type**: str
@@ -44,11 +46,11 @@ src
4446
location
4547
The JCL location. Supported choices are \ :literal:`data\_set`\ , \ :literal:`uss`\ or \ :literal:`local`\ .
4648

47-
\ :literal:`data\_set`\ can be a PDS, PDSE, or sequential data set.
49+
\ :literal:`data\_set`\ can be a PDS, PDSE, sequential data set, or a generation data set.
4850

4951
\ :literal:`uss`\ means the JCL location is located in UNIX System Services (USS).
5052

51-
\ :literal:`local`\ means locally to the ansible control node.
53+
\ :literal:`local`\ means locally to the Ansible control node.
5254

5355
| **required**: False
5456
| **type**: str
@@ -311,6 +313,16 @@ Examples
311313
location: data_set
312314
max_rc: 16
313315

316+
- name: Submit JCL from the latest generation data set in a generation data group.
317+
zos_job_submit:
318+
src: HLQ.DATA.GDG(0)
319+
location: data_set
320+
321+
- name: Submit JCL from a previous generation data set in a generation data group.
322+
zos_job_submit:
323+
src: HLQ.DATA.GDG(-2)
324+
location: data_set
325+
314326

315327

316328

plugins/module_utils/data_set.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,6 +1809,7 @@ class MVSDataSet():
18091809
def __init__(
18101810
self,
18111811
name,
1812+
escape_name=False,
18121813
data_set_type=None,
18131814
state=None,
18141815
organization=None,
@@ -1858,15 +1859,16 @@ def __init__(
18581859
self.is_cataloged = False
18591860

18601861
# If name has escaped chars or is GDS relative name we clean it.
1861-
# self.name = DataSet.escape_data_set_name(self.name)
1862+
if escape_name:
1863+
self.name = DataSet.escape_data_set_name(self.name)
18621864
if DataSet.is_gds_relative_name(self.name):
18631865
try:
18641866
self.name = DataSet.resolve_gds_absolute_name(self.name)
18651867
self.is_gds_active = True
18661868
except Exception:
18671869
# This means the generation is a positive version so is only used for creation.
18681870
self.is_gds_active = False
1869-
if self.data_set_type.upper() in DataSet.MVS_VSAM or self.data_set_type == "zfs":
1871+
if self.data_set_type and (self.data_set_type.upper() in DataSet.MVS_VSAM or self.data_set_type == "zfs"):
18701872
# When trying to create a new VSAM with a specified record format will fail
18711873
# with ZOAU
18721874
self.record_format = None

plugins/modules/zos_job_submit.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@
3636
description:
3737
- The source file or data set containing the JCL to submit.
3838
- It could be a physical sequential data set, a partitioned data set
39-
qualified by a member or a path. (e.g "USER.TEST","USER.JCL(TEST)")
40-
- Or a USS file. (e.g "/u/tester/demo/sample.jcl")
39+
qualified by a member or a path (e.g. C(USER.TEST), V(USER.JCL(TEST\))),
40+
or a generation data set from a generation data group
41+
(for example, V(USER.TEST.GDG(-2\))).
42+
- Or a USS file. (e.g C(/u/tester/demo/sample.jcl))
4143
- Or a LOCAL file in ansible control node.
42-
(e.g "/User/tester/ansible-playbook/sample.jcl")
44+
(e.g C(/User/tester/ansible-playbook/sample.jcl))
45+
- When using a generation data set, only already created generations
46+
are valid. If either the relative name is positive, or negative but
47+
not found, the module will fail.
4348
location:
4449
required: false
4550
default: data_set
@@ -50,9 +55,9 @@
5055
- local
5156
description:
5257
- The JCL location. Supported choices are C(data_set), C(uss) or C(local).
53-
- C(data_set) can be a PDS, PDSE, or sequential data set.
58+
- C(data_set) can be a PDS, PDSE, sequential data set, or a generation data set.
5459
- C(uss) means the JCL location is located in UNIX System Services (USS).
55-
- C(local) means locally to the ansible control node.
60+
- C(local) means locally to the Ansible control node.
5661
wait_time_s:
5762
required: false
5863
default: 10
@@ -601,6 +606,16 @@
601606
src: HLQ.DATA.LLQ
602607
location: data_set
603608
max_rc: 16
609+
610+
- name: Submit JCL from the latest generation data set in a generation data group.
611+
zos_job_submit:
612+
src: HLQ.DATA.GDG(0)
613+
location: data_set
614+
615+
- name: Submit JCL from a previous generation data set in a generation data group.
616+
zos_job_submit:
617+
src: HLQ.DATA.GDG(-2)
618+
location: data_set
604619
"""
605620

606621
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.encode import (
@@ -647,7 +662,7 @@
647662
MAX_WAIT_TIME_S = 86400
648663

649664

650-
def submit_src_jcl(module, src, src_name=None, timeout=0, is_unix=True, volume=None, start_time=timer()):
665+
def submit_src_jcl(module, src, src_name=None, timeout=0, is_unix=True, start_time=timer()):
651666
"""Submit src JCL whether JCL is local (Ansible Controller), USS or in a data set.
652667
653668
Parameters
@@ -666,9 +681,6 @@ def submit_src_jcl(module, src, src_name=None, timeout=0, is_unix=True, volume=N
666681
True if JCL is a file in USS, otherwise False; Note that all
667682
JCL local to a controller is transfered to USS thus would be
668683
True.
669-
volume : str
670-
volume the data set JCL is located on that will be cataloged before
671-
being submitted.
672684
start_time : int
673685
time the JCL started its submission.
674686
@@ -704,20 +716,6 @@ def submit_src_jcl(module, src, src_name=None, timeout=0, is_unix=True, volume=N
704716
result = {}
705717

706718
try:
707-
if volume is not None:
708-
volumes = [volume]
709-
# Get the PDS name to catalog it
710-
src_ds_name = data_set.extract_dsname(src)
711-
present, changed = DataSet.attempt_catalog_if_necessary(
712-
src_ds_name, volumes)
713-
714-
if not present:
715-
result["changed"] = False
716-
result["failed"] = True
717-
result["msg"] = ("Unable to submit job {0} because the data set could "
718-
"not be cataloged on the volume {1}.".format(src, volume))
719-
module.fail_json(**result)
720-
721719
job_submitted = jobs.submit(src, is_unix=is_unix, **kwargs)
722720

723721
# Introducing a sleep to ensure we have the result of job submit carrying the job id.
@@ -952,9 +950,32 @@ def run_module():
952950
job_submitted_id = None
953951
duration = 0
954952
start_time = timer()
953+
955954
if location == "data_set":
955+
# Resolving a relative GDS name and escaping special symbols if needed.
956+
src_data = data_set.MVSDataSet(src)
957+
958+
# Checking that the source is actually present on the system.
959+
if volume is not None:
960+
volumes = [volume]
961+
# Get the data set name to catalog it.
962+
src_ds_name = data_set.extract_dsname(src_data.name)
963+
present, changed = DataSet.attempt_catalog_if_necessary(src_ds_name, volumes)
964+
965+
if not present:
966+
module.fail_json(
967+
msg=(f"Unable to submit job {src_data.name} because the data set could "
968+
f"not be cataloged on the volume {volume}.")
969+
)
970+
elif data_set.is_member(src_data.name):
971+
if not DataSet.data_set_member_exists(src_data.name):
972+
module.fail_json(msg=f"Cannot submit job, the data set member {src_data.raw_name} was not found.")
973+
else:
974+
if not DataSet.data_set_exists(src_data.name):
975+
module.fail_json(msg=f"Cannot submit job, the data set {src_data.raw_name} was not found.")
976+
956977
job_submitted_id, duration = submit_src_jcl(
957-
module, src, src_name=src, timeout=wait_time_s, is_unix=False, volume=volume, start_time=start_time)
978+
module, src_data.name, src_name=src_data.raw_name, timeout=wait_time_s, is_unix=False, start_time=start_time)
958979
elif location == "uss":
959980
job_submitted_id, duration = submit_src_jcl(
960981
module, src, src_name=src, timeout=wait_time_s, is_unix=True)

tests/functional/modules/test_zos_job_submit_func.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@
399399
"""
400400

401401
TEMP_PATH = "/tmp/jcl"
402-
DATA_SET_NAME_SPECIAL_CHARS = "imstestl.im@1.xxx05"
402+
DATA_SET_NAME_SPECIAL_CHARS = "imstestl.im@1.x#$xx05"
403403

404404
@pytest.mark.parametrize(
405405
"location", [
@@ -460,7 +460,7 @@ def test_job_submit_PDS_special_characters(ansible_zos_module):
460460
)
461461
hosts.all.shell(
462462
cmd="cp {0}/SAMPLE \"//'{1}(SAMPLE)'\"".format(
463-
TEMP_PATH, DATA_SET_NAME_SPECIAL_CHARS
463+
TEMP_PATH, DATA_SET_NAME_SPECIAL_CHARS.replace('$', '\$')
464464
)
465465
)
466466
results = hosts.all.zos_job_submit(
@@ -922,6 +922,79 @@ def test_job_submit_local_jcl_typrun_jclhold(ansible_zos_module):
922922
assert result.get("jobs")[0].get("ret_code").get("msg_code") is None
923923

924924

925+
@pytest.mark.parametrize("generation", ["0", "-1"])
926+
def test_job_from_gdg_source(ansible_zos_module, generation):
927+
hosts = ansible_zos_module
928+
929+
try:
930+
# Creating a GDG for the test.
931+
source = get_tmp_ds_name()
932+
gds_name = f"{source}({generation})"
933+
hosts.all.zos_data_set(name=source, state="present", type="gdg", limit=3)
934+
hosts.all.zos_data_set(name=f"{source}(+1)", state="present", type="seq")
935+
hosts.all.zos_data_set(name=f"{source}(+1)", state="present", type="seq")
936+
937+
# Copying the JCL to the GDS.
938+
hosts.all.file(path=TEMP_PATH, state="directory")
939+
hosts.all.shell(
940+
cmd="echo {0} > {1}/SAMPLE".format(quote(JCL_FILE_CONTENTS), TEMP_PATH)
941+
)
942+
hosts.all.shell(
943+
cmd="dcp '{0}/SAMPLE' '{1}'".format(TEMP_PATH, gds_name)
944+
)
945+
946+
results = hosts.all.zos_job_submit(src=gds_name, location="data_set")
947+
for result in results.contacted.values():
948+
assert result.get("jobs")[0].get("ret_code").get("msg_code") == "0000"
949+
assert result.get("jobs")[0].get("ret_code").get("code") == 0
950+
assert result.get("changed") is True
951+
finally:
952+
hosts.all.file(path=TEMP_PATH, state="absent")
953+
hosts.all.zos_data_set(name=f"{source}(0)", state="absent")
954+
hosts.all.zos_data_set(name=f"{source}(-1)", state="absent")
955+
hosts.all.zos_data_set(name=source, state="absent")
956+
957+
958+
def test_inexistent_negative_gds(ansible_zos_module):
959+
hosts = ansible_zos_module
960+
961+
try:
962+
# Creating a GDG for the test.
963+
source = get_tmp_ds_name()
964+
gds_name = f"{source}(-1)"
965+
hosts.all.zos_data_set(name=source, state="present", type="gdg", limit=3)
966+
# Only creating generation 0.
967+
hosts.all.zos_data_set(name=f"{source}(+1)", state="present", type="seq")
968+
969+
results = hosts.all.zos_job_submit(src=gds_name, location="data_set")
970+
for result in results.contacted.values():
971+
assert result.get("changed") is False
972+
assert "was not found" in result.get("msg")
973+
finally:
974+
hosts.all.zos_data_set(name=f"{source}(0)", state="absent")
975+
hosts.all.zos_data_set(name=source, state="absent")
976+
977+
978+
def test_inexistent_positive_gds(ansible_zos_module):
979+
hosts = ansible_zos_module
980+
981+
try:
982+
# Creating a GDG for the test.
983+
source = get_tmp_ds_name()
984+
gds_name = f"{source}(+1)"
985+
hosts.all.zos_data_set(name=source, state="present", type="gdg", limit=3)
986+
# Only creating generation 0.
987+
hosts.all.zos_data_set(name=gds_name, state="present", type="seq")
988+
989+
results = hosts.all.zos_job_submit(src=gds_name, location="data_set")
990+
for result in results.contacted.values():
991+
assert result.get("changed") is False
992+
assert "was not found" in result.get("msg")
993+
finally:
994+
hosts.all.zos_data_set(name=f"{source}(0)", state="absent")
995+
hosts.all.zos_data_set(name=source, state="absent")
996+
997+
925998
# This test case is related to the following GitHub issues:
926999
# - https://github.com/ansible-collections/ibm_zos_core/issues/677
9271000
# - https://github.com/ansible-collections/ibm_zos_core/issues/972

0 commit comments

Comments
 (0)