Skip to content

Commit 0d9faa6

Browse files
authored
[Enabler] [zos_copy] Add GDG/GDS support to zos_copy (#1564)
* Add use of new data set class * Add tests for GDS source * Add support for a GDS destination * Add more GDS tests * Fix name resolution when allocating a new GDS * Fix GDS validations * Add more type validations * Remove unnecessary name resolution * Add support to copy a GDG to USS * Add copy of complete GDGs * Add support for a GDS as backup * Add special symbols test and fix backup ones * Add changelog fragment * Update docs * Add GDG attributes for dest_data_set * Update module RST * Fix pep8 issue * Fix backups without backup names * Fix non-GDG dest dataset allocation * Fix GDS allocation
1 parent 248ba87 commit 0d9faa6

File tree

6 files changed

+1165
-36
lines changed

6 files changed

+1165
-36
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_copy - add support for copying generation data sets (GDS) and
3+
generation data groups (GDG), as well as using a GDS for backup.
4+
(https://github.com/ansible-collections/ibm_zos_core/pull/1564).

docs/source/modules/zos_copy.rst

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ backup_name
6767

6868
If \ :literal:`dest`\ is a data set member and \ :literal:`backup\_name`\ is not provided, the data set member will be backed up to the same partitioned data set with a randomly generated member name.
6969

70+
If \ :emphasis:`backup\_name`\ is a generation data set (GDS), it must be a relative positive name (for example, \ :literal:`HLQ.USER.GDG(+1)`\ ).
71+
7072
| **required**: False
7173
| **type**: str
7274
@@ -105,6 +107,10 @@ dest
105107

106108
When \ :literal:`dest`\ is and existing VSAM (LDS), then source must be an LDS. The VSAM (LDS) will be deleted and recreated following the process outlined in the \ :literal:`volume`\ option.
107109

110+
\ :literal:`dest`\ can be a previously allocated generation data set (GDS) or a new GDS.
111+
112+
When \ :literal:`dest`\ is a generation data group (GDG), \ :literal:`src`\ must be a GDG too. The copy will allocate successive new generations in \ :literal:`dest`\ , the module will verify it has enough available generations before starting the copy operations.
113+
108114
When \ :literal:`dest`\ is a data set, you can override storage management rules by specifying \ :literal:`volume`\ if the storage class being used has GUARANTEED\_SPACE=YES specified, otherwise, the allocation will fail. See \ :literal:`volume`\ for more volume related processes.
109115

110116
| **required**: True
@@ -298,6 +304,10 @@ src
298304

299305
If \ :literal:`src`\ is a VSAM data set, \ :literal:`dest`\ must also be a VSAM.
300306

307+
If \ :literal:`src`\ is a generation data set (GDS), it must be a previously allocated one.
308+
309+
If \ :literal:`src`\ is a generation data group (GDG), \ :literal:`dest`\ can be another GDG or a USS directory.
310+
301311
Wildcards can be used to copy multiple PDS/PDSE members to another PDS/PDSE.
302312

303313
Required unless using \ :literal:`content`\ .
@@ -334,6 +344,8 @@ volume
334344
dest_data_set
335345
Data set attributes to customize a \ :literal:`dest`\ data set to be copied into.
336346

347+
Some attributes only apply when \ :literal:`dest`\ is a generation data group (GDG).
348+
337349
| **required**: False
338350
| **type**: dict
339351
@@ -343,7 +355,7 @@ dest_data_set
343355

344356
| **required**: True
345357
| **type**: str
346-
| **choices**: ksds, esds, rrds, lds, seq, pds, pdse, member, basic, library
358+
| **choices**: ksds, esds, rrds, lds, seq, pds, pdse, member, basic, library, gdg
347359
348360

349361
space_primary
@@ -470,6 +482,68 @@ dest_data_set
470482
| **type**: str
471483
472484

485+
limit
486+
Sets the \ :emphasis:`limit`\ attribute for a GDG.
487+
488+
Specifies the maximum number, from 1 to 255(up to 999 if extended), of generations that can be associated with the GDG being defined.
489+
490+
\ :emphasis:`limit`\ is required when \ :emphasis:`type=gdg`\ .
491+
492+
| **required**: False
493+
| **type**: int
494+
495+
496+
empty
497+
Sets the \ :emphasis:`empty`\ attribute for a GDG.
498+
499+
If false, removes only the oldest GDS entry when a new GDS is created that causes GDG limit to be exceeded.
500+
501+
If true, removes all GDS entries from a GDG base when a new GDS is created that causes the GDG limit to be exceeded.
502+
503+
| **required**: False
504+
| **type**: bool
505+
506+
507+
scratch
508+
Sets the \ :emphasis:`scratch`\ attribute for a GDG.
509+
510+
Specifies what action is to be taken for a generation data set located on disk volumes when the data set is uncataloged from the GDG base as a result of EMPTY/NOEMPTY processing.
511+
512+
| **required**: False
513+
| **type**: bool
514+
515+
516+
purge
517+
Sets the \ :emphasis:`purge`\ attribute for a GDG.
518+
519+
Specifies whether to override expiration dates when a generation data set (GDS) is rolled off and the \ :literal:`scratch`\ option is set.
520+
521+
| **required**: False
522+
| **type**: bool
523+
524+
525+
extended
526+
Sets the \ :emphasis:`extended`\ attribute for a GDG.
527+
528+
If false, allow up to 255 generation data sets (GDSs) to be associated with the GDG.
529+
530+
If true, allow up to 999 generation data sets (GDS) to be associated with the GDG.
531+
532+
| **required**: False
533+
| **type**: bool
534+
535+
536+
fifo
537+
Sets the \ :emphasis:`fifo`\ attribute for a GDG.
538+
539+
If false, the order is the newest GDS defined to the oldest GDS. This is the default value.
540+
541+
If true, the order is the oldest GDS defined to the newest GDS.
542+
543+
| **required**: False
544+
| **type**: bool
545+
546+
473547

474548
use_template
475549
Whether the module should treat \ :literal:`src`\ as a Jinja2 template and render it before continuing with the rest of the module.
@@ -794,6 +868,19 @@ Examples
794868
dest: HLQ.PRINT.NEW
795869
asa_text: true
796870

871+
- name: Copy a file to a new generation data set.
872+
zos_copy:
873+
src: /path/to/uss/src
874+
dest: HLQ.TEST.GDG(+1)
875+
remote_src: true
876+
877+
- name: Copy a local file and take a backup of the existing file with a GDS.
878+
zos_copy:
879+
src: /path/to/local/file
880+
dest: /path/to/dest
881+
backup: true
882+
backup_name: HLQ.BACKUP.GDG(+1)
883+
797884

798885

799886

plugins/module_utils/copy.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
__metaclass__ = type
1616

1717

18+
import traceback
19+
from os import path
1820
from ansible.module_utils.six import PY3
1921
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.ansible_module import (
2022
AnsibleModuleHelper,
@@ -25,12 +27,19 @@
2527
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.mvs_cmd import (
2628
ikjeft01
2729
)
30+
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.import_handler import \
31+
ZOAUImportError
2832

2933
if PY3:
3034
from shlex import quote
3135
else:
3236
from pipes import quote
3337

38+
try:
39+
from zoautil_py import datasets, gdgs
40+
except Exception:
41+
datasets = ZOAUImportError(traceback.format_exc())
42+
gdgs = ZOAUImportError(traceback.format_exc())
3443

3544
REPRO = """ REPRO INDATASET({}) -
3645
OUTDATASET({}) REPLACE """
@@ -216,6 +225,49 @@ def copy_pds2uss(src, dest, is_binary=False, asa_text=False):
216225
return rc, out, err
217226

218227

228+
def copy_gdg2uss(src, dest, is_binary=False, asa_text=False):
229+
"""Copy a whole GDG to a USS path.
230+
231+
Parameters
232+
----------
233+
src : str
234+
The MVS data set to be copied, it must be a generation data group.
235+
dest : str
236+
The destination USS path.
237+
238+
Keyword Parameters
239+
------------------
240+
is_binary : bool
241+
Whether the file to be copied contains binary data.
242+
asa_text : bool
243+
Whether the file to be copied contains ASA control
244+
characters.
245+
246+
Returns
247+
-------
248+
bool
249+
True if all copies were successful, False otherwise.
250+
"""
251+
src_view = gdgs.GenerationDataGroupView(src)
252+
generations = src_view.generations()
253+
254+
copy_args = {
255+
"options": ""
256+
}
257+
258+
if is_binary or asa_text:
259+
copy_args["options"] = "-B"
260+
261+
for gds in generations:
262+
dest_file = path.join(dest, gds.name)
263+
rc = datasets.copy(gds.name, dest_file, **copy_args)
264+
265+
if rc != 0:
266+
return False
267+
268+
return True
269+
270+
219271
def copy_uss2uss_binary(src, dest):
220272
"""Copy a USS file to a USS location in binary mode.
221273

plugins/module_utils/data_set.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,73 @@ def allocate_model_data_set(ds_name, model, executable=False, asa_text=False, vo
343343
if rc != 0:
344344
raise MVSCmdExecError(rc, out, err)
345345

346+
@staticmethod
347+
def allocate_gds_model_data_set(ds_name, model, executable=False, asa_text=False, vol=None):
348+
"""
349+
Allocates a new current generation of a generation data group using a model
350+
data set to set its attributes.
351+
352+
Parameters
353+
----------
354+
ds_name : str
355+
Name of the data set that will be allocated. It must be a GDS
356+
relative name.
357+
model : str
358+
The name of the data set whose allocation parameters
359+
should be used to allocate the new data set.
360+
executable : bool, optional
361+
Whether the new data set should support executables.
362+
asa_text : bool, optional
363+
Whether the new data set should support ASA control
364+
characters (have record format FBA).
365+
vol : str, optional
366+
The volume where the new data set should be allocated.
367+
368+
Returns
369+
-------
370+
str
371+
Absolute name of the newly allocated generation data set.
372+
373+
Raises
374+
------
375+
DatasetCreateError
376+
When the allocation fails.
377+
"""
378+
model_attributes = datasets.list_datasets(model)[0]
379+
dataset_type = model_attributes.organization
380+
record_format = model_attributes.record_format
381+
382+
if executable:
383+
dataset_type = "library"
384+
elif dataset_type in DataSet.MVS_SEQ:
385+
dataset_type = "seq"
386+
elif dataset_type in DataSet.MVS_PARTITIONED:
387+
dataset_type = "pdse"
388+
389+
if asa_text:
390+
record_format = "fba"
391+
elif executable:
392+
record_format = "u"
393+
394+
data_set_object = MVSDataSet(
395+
name=ds_name,
396+
data_set_type=dataset_type,
397+
state="absent",
398+
record_format=record_format,
399+
volumes=vol,
400+
block_size=model_attributes.block_size,
401+
record_length=model_attributes.record_length,
402+
space_primary=model_attributes.total_space,
403+
space_type=''
404+
)
405+
406+
success = data_set_object.ensure_present()
407+
if not success:
408+
raise DatasetCreateError(
409+
data_set=ds_name,
410+
msg=f"Error while trying to allocate {ds_name}."
411+
)
412+
346413
@staticmethod
347414
def data_set_cataloged(name, volumes=None):
348415
"""Determine if a data set is in catalog.

0 commit comments

Comments
 (0)