Skip to content

Commit 102a25c

Browse files
[zos_copy]Ensure generations are copied correctly when identical_gdg_copy is True (#2100)
* Enhancement * Added Test Case * added fragments * fixed * sanity fixing * fixing * fixed sanity * SANITY FIXED * changed fragments * fixed error in statements * changed fragments * Update plugins/modules/zos_copy.py Co-authored-by: Fernando Flores <[email protected]> * Update plugins/modules/zos_copy.py Co-authored-by: Fernando Flores <[email protected]> * Removed Try and success = True * fixed sanity * fixed * Update zos_copy.py * fixed syntax error * Update zos_copy.py * removed condition block * Update zos_copy.py * fixed extra spaces * added qualifier to delete base and gds of src and dest * added code to validate that the absolute names are correct * sanity fixed * removed try and except --------- Co-authored-by: Fernando Flores <[email protected]>
1 parent e80e5b0 commit 102a25c

File tree

3 files changed

+98
-10
lines changed

3 files changed

+98
-10
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
minor_changes:
2+
- zos_copy - Adds new option `identical_gdg_copy` in the module.
3+
This allows copying GDG generations from a source base to a destination base
4+
while preserving generation data set absolute names when the destination base does not exist prior to the copy.
5+
(https://github.com/ansible-collections/ibm_zos_core/pull/2100).
6+
trivial:
7+
- test_zos_copy_func - Added a test case to ensure GDG generation names are preserved
8+
when copying with identical_gdg_copy is true and the destination base is non-existent.
9+
(https://github.com/ansible-collections/ibm_zos_core/pull/2100).
10+

plugins/modules/zos_copy.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@
4949
type: bool
5050
default: false
5151
required: false
52+
identical_gdg_copy:
53+
description:
54+
- If set to C(true), and the destination GDG does not exist, the module
55+
will copy the source GDG to the destination GDG with identical GDS absolute names.
56+
- If set to C(false), the copy will be done as a normal copy, without
57+
preserving the source GDG absolute names.
58+
type: bool
59+
default: false
60+
required: false
5261
backup:
5362
description:
5463
- Specifies whether a backup of the destination should be created before
@@ -983,6 +992,7 @@ def __init__(
983992
asa_text=False,
984993
backup_name=None,
985994
force_lock=False,
995+
identical_gdg_copy=False,
986996
tmphlq=None
987997
):
988998
"""Utility class to handle copying data between two targets.
@@ -1047,6 +1057,7 @@ def __init__(
10471057
self.aliases = aliases
10481058
self.backup_name = backup_name
10491059
self.force_lock = force_lock
1060+
self.identical_gdg_copy = identical_gdg_copy
10501061
self.tmphlq = tmphlq
10511062

10521063
def run_command(self, cmd, **kwargs):
@@ -1175,22 +1186,29 @@ def copy_to_gdg(self, src, dest):
11751186
"""
11761187
src_view = gdgs.GenerationDataGroupView(src)
11771188
generations = src_view.generations()
1178-
dest_generation = f"{dest}(+1)"
1179-
11801189
copy_args = {
11811190
"options": ""
11821191
}
1183-
11841192
if self.is_binary or self.asa_text:
11851193
copy_args["options"] = "-B"
11861194

1195+
success = True
11871196
for gds in generations:
1188-
rc = datasets.copy(gds.name, dest_generation, **copy_args)
1189-
1197+
# If identical_gdg_copy is True, use exact source generation name in destination
1198+
if self.identical_gdg_copy:
1199+
src_gen_absolute = gds.name
1200+
parts = src_gen_absolute.split('.')
1201+
# Extract generation number
1202+
generation_part = parts[-1]
1203+
dest_gen_name = f"{dest}.{generation_part}"
1204+
else:
1205+
# If identical_gdg_copy is False, use the default next generation
1206+
dest_gen_name = f"{dest}(+1)"
1207+
# Perform the copy operation
1208+
rc = datasets.copy(gds.name, dest_gen_name, **copy_args)
11901209
if rc != 0:
1191-
return False
1192-
1193-
return True
1210+
success = False
1211+
return success
11941212

11951213
def _copy_tree(self, entries, src, dest, dirs_exist_ok=False):
11961214
"""Recursively copy USS directory to another USS directory.
@@ -3358,6 +3376,7 @@ def run_module(module, arg_def):
33583376
force = module.params.get('force')
33593377
force_lock = module.params.get('force_lock')
33603378
content = module.params.get('content')
3379+
identical_gdg_copy = module.params.get('identical_gdg_copy', False)
33613380

33623381
# Set temporary directory at os environment level
33633382
os.environ['TMPDIR'] = f"{os.path.realpath(module.tmpdir)}/"
@@ -3585,10 +3604,19 @@ def run_module(module, arg_def):
35853604
dest_member_exists = dest_exists and data_set.DataSet.files_in_data_set_members(root_dir, dest)
35863605
elif src_ds_type in data_set.DataSet.MVS_PARTITIONED:
35873606
dest_member_exists = dest_exists and data_set.DataSet.data_set_shared_members(src, dest)
3588-
35893607
except Exception as err:
35903608
module.fail_json(msg=str(err))
3591-
3609+
identical_gdg_copy = module.params.get('identical_gdg_copy', False)
3610+
if identical_gdg_copy:
3611+
# Validate destination GDG doesn't exist
3612+
if dest_exists:
3613+
module.fail_json(
3614+
msg=(
3615+
f"Identical GDG copy failed: {raw_dest} already exists."
3616+
"When using option identical_gdg_copy the destination GDG should not exist."
3617+
),
3618+
changed=False
3619+
)
35923620
# Checking that we're dealing with a positive generation when dest does not
35933621
# exist.
35943622
if is_dest_gds and not is_dest_gds_active:
@@ -3793,6 +3821,7 @@ def run_module(module, arg_def):
37933821
asa_text=asa_text,
37943822
backup_name=backup_name,
37953823
force_lock=force_lock,
3824+
identical_gdg_copy=module.params.get('identical_gdg_copy', False),
37963825
tmphlq=tmphlq
37973826
)
37983827

@@ -3950,6 +3979,7 @@ def main():
39503979
executable=dict(type='bool', default=False),
39513980
asa_text=dict(type='bool', default=False),
39523981
aliases=dict(type='bool', default=False, required=False),
3982+
identical_gdg_copy=dict(type='bool', default=False),
39533983
encoding=dict(
39543984
type='dict',
39553985
required=False,

tests/functional/modules/test_zos_copy_func.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5605,6 +5605,54 @@ def test_copy_gdg_to_gdg(ansible_zos_module, new_gdg):
56055605
hosts.all.shell(cmd=f"""drm "{dest_data_set}(0)" """)
56065606
hosts.all.shell(cmd=f"drm {dest_data_set}")
56075607

5608+
def test_identical_gdg_copy(ansible_zos_module):
5609+
hosts = ansible_zos_module
5610+
try:
5611+
src_data_set = get_tmp_ds_name()
5612+
dest_data_set = get_tmp_ds_name()
5613+
# Create source GDG base
5614+
hosts.all.shell(cmd=f"dtouch -tGDG -L5 {src_data_set}")
5615+
# Create 5 generations in source GDG
5616+
hosts.all.shell(cmd=f"""dtouch -tSEQ "{src_data_set}(+1)" """)
5617+
hosts.all.shell(cmd=f"""dtouch -tSEQ "{src_data_set}(+1)" """)
5618+
hosts.all.shell(cmd=f"""dtouch -tSEQ "{src_data_set}(+1)" """)
5619+
hosts.all.shell(cmd=f"""dtouch -tSEQ "{src_data_set}(+1)" """)
5620+
hosts.all.shell(cmd=f"""dtouch -tSEQ "{src_data_set}(+1)" """)
5621+
5622+
# Delete first two generations: (-4) and (-3)
5623+
hosts.all.shell(cmd=f"""drm "{src_data_set}(-4)" """)
5624+
hosts.all.shell(cmd=f"""drm "{src_data_set}(-3)" """)
5625+
# Copy with identical_gdg_copy: true
5626+
copy_results = hosts.all.zos_copy(
5627+
src=src_data_set,
5628+
dest=dest_data_set,
5629+
remote_src=True,
5630+
identical_gdg_copy=True
5631+
)
5632+
for result in copy_results.contacted.values():
5633+
assert result.get("msg") is None
5634+
assert result.get("changed") is True
5635+
finally:
5636+
src_gdg_result = hosts.all.shell(cmd=f"dls {src_data_set}.*")
5637+
src_gdgs = []
5638+
for result in src_gdg_result.contacted.values():
5639+
src_gdgs.extend(result.get("stdout_lines", []))
5640+
# List destination generations
5641+
dest_gdg_result = hosts.all.shell(cmd=f"dls {dest_data_set}.*")
5642+
dest_gdgs = []
5643+
for result in dest_gdg_result.contacted.values():
5644+
dest_gdgs.extend(result.get("stdout_lines", []))
5645+
expected_dest_gdgs = [
5646+
ds_name.replace(src_data_set,dest_data_set) for ds_name in src_gdgs
5647+
]
5648+
assert sorted(dest_gdgs) == sorted(expected_dest_gdgs), f"Absolute names mismatch.\nExpected: {expected_dest_gdgs}\nFound: {dest_gdgs}"
5649+
print("Abssolute GDG names copied correctly.")
5650+
for name in dest_gdgs:
5651+
print(name)
5652+
# Clean up both source and destination
5653+
hosts.all.shell(cmd=f"drm {src_data_set}*")
5654+
hosts.all.shell(cmd=f"drm {dest_data_set}*")
5655+
56085656

56095657
def test_copy_gdg_to_gdg_dest_attributes(ansible_zos_module):
56105658
hosts = ansible_zos_module

0 commit comments

Comments
 (0)