Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v1.10.0] [zos_copy] zos_copy migration to ZOAU v1.3.0 #1222

Merged
merged 9 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelogs/fragments/1222-zoau-migration-zos_copy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trivial:
- zos_copy - Migrated the module to use ZOAU v1.3.0.
(https://github.com/ansible-collections/ibm_zos_core/pull/1222).
12 changes: 10 additions & 2 deletions plugins/module_utils/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,21 @@ def _get_job_status(job_id="*", owner="*", job_name="*", dd_name=None, dd_scan=T
job["owner"] = entry.owner

job["ret_code"] = dict()
job["ret_code"]["msg"] = "{0} {1}".format(entry.status, entry.return_code)

# From v1.3.0, ZOAU sets unavailable job fields as None, instead of '?'.
# This new way of constructing msg allows for a better empty message.
# "" instead of "None None".
job["ret_code"]["msg"] = "{0} {1}".format(
entry.status if entry.status else "",
entry.return_code if entry.return_code else ""
).strip()

job["ret_code"]["msg_code"] = entry.return_code
job["ret_code"]["code"] = None
if entry.return_code and len(entry.return_code) > 0:
if entry.return_code.isdigit():
job["ret_code"]["code"] = int(entry.return_code)
job["ret_code"]["msg_text"] = entry.status
job["ret_code"]["msg_text"] = entry.status if entry.status else "?"

# Beginning in ZOAU v1.3.0, the Job class changes svc_class to
# service_class.
Expand Down
123 changes: 80 additions & 43 deletions plugins/modules/zos_copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@


from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.import_handler import (
MissingZOAUImport,
ZOAUImportError,
)
from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.mvs_cmd import (
idcams
Expand All @@ -829,6 +829,7 @@
import math
import tempfile
import os
import traceback

if PY3:
from re import fullmatch
Expand All @@ -839,7 +840,13 @@
try:
from zoautil_py import datasets, opercmd
except Exception:
datasets = MissingZOAUImport()
datasets = ZOAUImportError(traceback.format_exc())
opercmd = ZOAUImportError(traceback.format_exc())

try:
from zoautil_py import exceptions as zoau_exceptions
except ImportError:
zoau_exceptions = ZOAUImportError(traceback.format_exc())


class CopyHandler(object):
Expand Down Expand Up @@ -909,6 +916,14 @@ def copy_to_seq(

if src_type == 'USS' and self.asa_text:
response = copy.copy_asa_uss2mvs(new_src, dest)

if response.rc != 0:
raise CopyOperationError(
msg="Unable to copy source {0} to {1}".format(new_src, dest),
rc=response.rc,
stdout=response.stdout_response,
stderr=response.stderr_response
)
else:
# While ASA files are just text files, we do a binary copy
# so dcp doesn't introduce any additional blanks or newlines.
Expand All @@ -918,14 +933,15 @@ def copy_to_seq(
if self.force_lock:
copy_args["options"] += " -f"

response = datasets._copy(new_src, dest, None, **copy_args)
if response.rc != 0:
raise CopyOperationError(
msg="Unable to copy source {0} to {1}".format(new_src, dest),
rc=response.rc,
stdout=response.stdout_response,
stderr=response.stderr_response
)
try:
datasets.copy(new_src, dest, **copy_args)
except zoau_exceptions.ZOAUException as copy_exception:
raise CopyOperationError(
msg="Unable to copy source {0} to {1}".format(new_src, dest),
rc=copy_exception.response.rc,
stdout=copy_exception.response.stdout_response,
stderr=copy_exception.response.stderr_response
)

def copy_to_vsam(self, src, dest):
"""Copy source VSAM to destination VSAM.
Expand Down Expand Up @@ -988,9 +1004,11 @@ def _copy_tree(self, entries, src, dest, dirs_exist_ok=False):
else:
opts = dict()
opts["options"] = ""
response = datasets._copy(src_name, dest_name, None, **opts)
if response.rc > 0:
raise Exception(response.stderr_response)

try:
datasets.copy(src_name, dest_name, **opts)
except zoau_exceptions.ZOAUException as copy_exception:
raise Exception(copy_exception.response.stderr_response)
shutil.copystat(src_name, dest_name, follow_symlinks=True)
except Exception as err:
raise err
Expand Down Expand Up @@ -1356,14 +1374,17 @@ def _copy_to_file(self, src, dest, conv_path, temp_path):
else:
opts = dict()
opts["options"] = ""
response = datasets._copy(new_src, dest, None, **opts)
if response.rc > 0:
raise Exception(response.stderr_response)
datasets.copy(new_src, dest, **opts)
shutil.copystat(new_src, dest, follow_symlinks=True)
# shutil.copy(new_src, dest)
if self.executable:
status = os.stat(dest)
os.chmod(dest, status.st_mode | stat.S_IEXEC)
except zoau_exceptions.ZOAUException as err:
raise CopyOperationError(
msg="Unable to copy file {0} to {1}".format(new_src, dest),
stderr=err.response.stderr_response,
)
except OSError as err:
raise CopyOperationError(
msg="Destination {0} is not writable".format(dest),
Expand Down Expand Up @@ -1549,12 +1570,21 @@ def _mvs_copy_to_uss(
if src_member or src_ds_type in data_set.DataSet.MVS_SEQ:
if self.asa_text:
response = copy.copy_asa_mvs2uss(src, dest)
rc = response.rc
elif self.executable:
response = datasets._copy(src, dest, alias=True, executable=True)
try:
rc = datasets.copy(src, dest, alias=True, executable=True)
except zoau_exceptions.ZOAUException as copy_exception:
response = copy_exception.response
rc = response.rc
else:
response = datasets._copy(src, dest)
try:
rc = datasets.copy(src, dest)
except zoau_exceptions.ZOAUException as copy_exception:
response = copy_exception.response
rc = response.rc

if response.rc != 0:
if rc != 0:
raise CopyOperationError(
msg="Error while copying source {0} to {1}".format(src, dest),
rc=response.rc,
Expand All @@ -1563,14 +1593,14 @@ def _mvs_copy_to_uss(
)
else:
if self.executable:
response = datasets._copy(src, dest, None, alias=True, executable=True)

if response.rc != 0:
try:
datasets.copy(src, dest, alias=True, executable=True)
except zoau_exceptions.ZOAUException as copy_exception:
raise CopyOperationError(
msg="Error while copying source {0} to {1}".format(src, dest),
rc=response.rc,
stdout=response.stdout_response,
stderr=response.stderr_response
rc=copy_exception.response.rc,
stdout=copy_exception.response.stdout_response,
stderr=copy_exception.response.stderr_response
)
elif self.asa_text:
response = copy.copy_asa_pds2uss(src, dest)
Expand Down Expand Up @@ -1785,6 +1815,7 @@ def copy_to_member(

if src_type == 'USS' and self.asa_text:
response = copy.copy_asa_uss2mvs(src, dest)
rc, out, err = response.rc, response.stdout_response, response.stderr_response
else:
# While ASA files are just text files, we do a binary copy
# so dcp doesn't introduce any additional blanks or newlines.
Expand All @@ -1794,8 +1825,14 @@ def copy_to_member(
if self.force_lock:
opts["options"] += " -f"

response = datasets._copy(src, dest, alias=self.aliases, executable=self.executable, **opts)
rc, out, err = response.rc, response.stdout_response, response.stderr_response
try:
rc = datasets.copy(src, dest, alias=self.aliases, executable=self.executable, **opts)
out = ""
err = ""
except zoau_exceptions.ZOAUException as copy_exception:
rc = copy_exception.response.rc
out = copy_exception.response.stdout_response
err = copy_exception.response.stderr_response

return dict(
rc=rc,
Expand Down Expand Up @@ -1852,8 +1889,8 @@ def dump_data_set_member_to_file(data_set_member, is_binary):
if is_binary:
copy_args["options"] = "-B"

response = datasets._copy(data_set_member, temp_path, None, **copy_args)
if response.rc != 0 or response.stderr_response:
response = datasets.copy(data_set_member, temp_path, **copy_args)
if response != 0:
raise DataSetMemberAttributeError(data_set_member)

return temp_path
Expand Down Expand Up @@ -2315,7 +2352,7 @@ def get_attributes_of_any_dataset_created(
volume=volume
)
else:
src_attributes = datasets.listing(src_name)[0]
src_attributes = datasets.list_datasets(src_name)[0]
size = int(src_attributes.total_space)
params = get_data_set_attributes(
dest,
Expand Down Expand Up @@ -2397,8 +2434,8 @@ def allocate_destination_data_set(
try:
# Dumping the member into a file in USS to compute the record length and
# size for the new data set.
src_attributes = datasets.listing(src_name)[0]
record_length = int(src_attributes.lrecl)
src_attributes = datasets.list_datasets(src_name)[0]
record_length = int(src_attributes.record_length)
temp_dump = dump_data_set_member_to_file(src, is_binary)
create_seq_dataset_from_file(
temp_dump,
Expand All @@ -2417,11 +2454,11 @@ def allocate_destination_data_set(
if src_ds_type in data_set.DataSet.MVS_PARTITIONED:
data_set.DataSet.allocate_model_data_set(ds_name=dest, model=src_name, executable=executable, asa_text=asa_text, vol=volume)
elif src_ds_type in data_set.DataSet.MVS_SEQ:
src_attributes = datasets.listing(src_name)[0]
src_attributes = datasets.list_datasets(src_name)[0]
# The size returned by listing is in bytes.
size = int(src_attributes.total_space)
record_format = src_attributes.recfm
record_length = int(src_attributes.lrecl)
record_format = src_attributes.record_format
record_length = int(src_attributes.record_length)
dest_params = get_data_set_attributes(
dest,
size,
Expand Down Expand Up @@ -2507,8 +2544,8 @@ def allocate_destination_data_set(
asa_text,
volume
)
dest_attributes = datasets.listing(dest)[0]
record_format = dest_attributes.recfm
dest_attributes = datasets.list_datasets(dest)[0]
record_format = dest_attributes.record_format
dest_params["type"] = dest_ds_type
dest_params["record_format"] = record_format
return True, dest_params
Expand Down Expand Up @@ -2730,8 +2767,8 @@ def run_module(module, arg_def):
src_ds_type = data_set.DataSet.data_set_type(src_name)

if src_ds_type not in data_set.DataSet.MVS_VSAM:
src_attributes = datasets.listing(src_name)[0]
if src_attributes.recfm == 'FBA' or src_attributes.recfm == 'VBA':
src_attributes = datasets.list_datasets(src_name)[0]
if src_attributes.record_format == 'FBA' or src_attributes.record_format == 'VBA':
src_has_asa_chars = True
else:
raise NonExistentSourceError(src)
Expand Down Expand Up @@ -2785,17 +2822,17 @@ def run_module(module, arg_def):
elif not dest_exists and asa_text:
dest_has_asa_chars = True
elif dest_exists and dest_ds_type not in data_set.DataSet.MVS_VSAM:
dest_attributes = datasets.listing(dest_name)[0]
if dest_attributes.recfm == 'FBA' or dest_attributes.recfm == 'VBA':
dest_attributes = datasets.list_datasets(dest_name)[0]
if dest_attributes.record_format == 'FBA' or dest_attributes.record_format == 'VBA':
dest_has_asa_chars = True

if dest_data_set and (dest_data_set.get('record_format', '') == 'FBA' or dest_data_set.get('record_format', '') == 'VBA'):
dest_has_asa_chars = True
elif not dest_exists and asa_text:
dest_has_asa_chars = True
elif dest_exists and dest_ds_type not in data_set.DataSet.MVS_VSAM:
dest_attributes = datasets.listing(dest_name)[0]
if dest_attributes.recfm == 'FBA' or dest_attributes.recfm == 'VBA':
dest_attributes = datasets.list_datasets(dest_name)[0]
if dest_attributes.record_format == 'FBA' or dest_attributes.record_format == 'VBA':
dest_has_asa_chars = True

if dest_ds_type in data_set.DataSet.MVS_PARTITIONED:
Expand Down
8 changes: 6 additions & 2 deletions tests/functional/modules/test_zos_copy_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -4347,16 +4347,21 @@ def test_backup_pds(ansible_zos_module, args):
def test_copy_data_set_to_volume(ansible_zos_module, volumes_on_systems, src_type):
hosts = ansible_zos_module
source = get_tmp_ds_name()
source_member = f"{source}(MEM)"
dest = get_tmp_ds_name()
volumes = Volume_Handler(volumes_on_systems)
volume_1 = volumes.get_available_vol()

if volume_1 == "SCR03":
volume = volumes.get_available_vol()
volumes.free_vol(volume_1)
volume_1 = volume

try:
hosts.all.zos_data_set(name=source, type=src_type, state='present')
hosts.all.zos_data_set(name=source_member, type="member", state='present')
if src_type != "seq":
hosts.all.zos_data_set(name=source_member, type="member", state='present')

copy_res = hosts.all.zos_copy(
src=source,
dest=dest,
Expand Down Expand Up @@ -4406,7 +4411,6 @@ def test_copy_ksds_to_non_existing_ksds(ansible_zos_module):
finally:
hosts.all.zos_data_set(name=dest_ds, state="absent")


@pytest.mark.vsam
@pytest.mark.parametrize("force", [False, True])
def test_copy_ksds_to_existing_ksds(ansible_zos_module, force):
Expand Down
Loading