From 532f744ee3b24594249385e8a433fc2711cfc953 Mon Sep 17 00:00:00 2001 From: Mayank Mani Date: Wed, 9 Jul 2025 21:33:23 +0530 Subject: [PATCH 01/36] [Enabler] [2053] [zos_data_set] Add_support_for_noscratch_option (#2202) * add nonscratch parameter in main module * added support for nonscratch in module_utils * added test case * added fragments * FIXED * changed in fragments * Updated changelogs * reviewed comments * fixed * removed print statement * removed extra validation --------- Co-authored-by: Fernando Flores --- ...zos_data_set-Support-noscratch-options.yml | 10 ++ plugins/module_utils/data_set.py | 26 +-- plugins/modules/zos_data_set.py | 51 +++++- .../modules/test_zos_data_set_func.py | 148 ++++++++++++++++++ 4 files changed, 222 insertions(+), 13 deletions(-) create mode 100644 changelogs/fragments/2202-zos_data_set-Support-noscratch-options.yml diff --git a/changelogs/fragments/2202-zos_data_set-Support-noscratch-options.yml b/changelogs/fragments/2202-zos_data_set-Support-noscratch-options.yml new file mode 100644 index 0000000000..d1bf0fdad0 --- /dev/null +++ b/changelogs/fragments/2202-zos_data_set-Support-noscratch-options.yml @@ -0,0 +1,10 @@ +minor_changes: + - zos_data_set - Adds `noscratch` option to allow uncataloging + a data set without deleting it from the volume's VTOC. + (https://github.com/ansible-collections/ibm_zos_core/pull/2202) +trivial: + - data_set - Internal updates to support the noscratch option. + https://github.com/ansible-collections/ibm_zos_core/pull/2202) + - test_zos_data_set_func - added test case to verify the `noscratch` option + functionality in zos_data_set module. + (https://github.com/ansible-collections/ibm_zos_core/pull/2202). diff --git a/plugins/module_utils/data_set.py b/plugins/module_utils/data_set.py index 3c055d0ff1..778fa7a290 100644 --- a/plugins/module_utils/data_set.py +++ b/plugins/module_utils/data_set.py @@ -241,7 +241,7 @@ def ensure_present( return True @staticmethod - def ensure_absent(name, volumes=None, tmphlq=None): + def ensure_absent(name, volumes=None, tmphlq=None, noscratch=False): """Deletes provided data set if it exists. Parameters @@ -252,13 +252,15 @@ def ensure_absent(name, volumes=None, tmphlq=None): The volumes the data set may reside on. tmphlq : str High Level Qualifier for temporary datasets. + noscratch : bool + If True, the data set is uncataloged but not physically removed from the volume. Returns ------- bool Indicates if changes were made. """ - changed, present = DataSet.attempt_catalog_if_necessary_and_delete(name, volumes, tmphlq=tmphlq) + changed, present = DataSet.attempt_catalog_if_necessary_and_delete(name, volumes, tmphlq=tmphlq, noscratch=noscratch) return changed # ? should we do additional check to ensure member was actually created? @@ -1003,7 +1005,7 @@ def attempt_catalog_if_necessary(name, volumes, tmphlq=None): return present, changed @staticmethod - def attempt_catalog_if_necessary_and_delete(name, volumes, tmphlq=None): + def attempt_catalog_if_necessary_and_delete(name, volumes, tmphlq=None, noscratch=False): """Attempts to catalog a data set if not already cataloged, then deletes the data set. This is helpful when a data set currently cataloged is not the data @@ -1019,6 +1021,8 @@ def attempt_catalog_if_necessary_and_delete(name, volumes, tmphlq=None): The volumes the data set may reside on. tmphlq : str High Level Qualifier for temporary datasets. + noscratch : bool + If True, the data set is uncataloged but not physically removed from the volume. Returns ------- @@ -1039,7 +1043,7 @@ def attempt_catalog_if_necessary_and_delete(name, volumes, tmphlq=None): present = DataSet.data_set_cataloged(name, volumes, tmphlq=tmphlq) if present: - DataSet.delete(name) + DataSet.delete(name, noscratch=noscratch) changed = True present = False else: @@ -1074,7 +1078,7 @@ def attempt_catalog_if_necessary_and_delete(name, volumes, tmphlq=None): if present: try: - DataSet.delete(name) + DataSet.delete(name, noscratch=noscratch) except DatasetDeleteError: try: DataSet.uncatalog(name, tmphlq=tmphlq) @@ -1101,14 +1105,14 @@ def attempt_catalog_if_necessary_and_delete(name, volumes, tmphlq=None): present = DataSet.data_set_cataloged(name, volumes, tmphlq=tmphlq) if present: - DataSet.delete(name) + DataSet.delete(name, noscratch=noscratch) changed = True present = False else: present = DataSet.data_set_cataloged(name, None, tmphlq=tmphlq) if present: try: - DataSet.delete(name) + DataSet.delete(name, noscratch=noscratch) changed = True present = False except DatasetDeleteError: @@ -1414,7 +1418,7 @@ def create( return changed @staticmethod - def delete(name): + def delete(name, noscratch=False): """A wrapper around zoautil_py datasets.delete() to raise exceptions on failure. @@ -1428,7 +1432,7 @@ def delete(name): DatasetDeleteError When data set deletion fails. """ - rc = datasets.delete(name) + rc = datasets.delete(name, noscratch=noscratch) if rc > 0: raise DatasetDeleteError(name, rc) @@ -2721,7 +2725,7 @@ def ensure_present(self, tmp_hlq=None, replace=False, force=False): self.set_state("present") return rc - def ensure_absent(self, tmp_hlq=None): + def ensure_absent(self, tmp_hlq=None, noscratch=False): """Removes the data set. Parameters @@ -2734,7 +2738,7 @@ def ensure_absent(self, tmp_hlq=None): int Indicates if changes were made. """ - rc = DataSet.ensure_absent(self.name, self.volumes, tmphlq=tmp_hlq) + rc = DataSet.ensure_absent(self.name, self.volumes, tmphlq=tmp_hlq, noscratch=noscratch) if rc == 0: self.set_state("absent") return rc diff --git a/plugins/modules/zos_data_set.py b/plugins/modules/zos_data_set.py index d03bbb1268..25d2ef073c 100644 --- a/plugins/modules/zos_data_set.py +++ b/plugins/modules/zos_data_set.py @@ -298,6 +298,15 @@ type: bool required: false default: false + noscratch: + description: + - "When C(state=absent), specifies whether to keep the data set's entry in the VTOC." + - If C(noscratch=True), the data set is uncataloged but not physically removed from the volume. + The Data Set Control Block is not removed from the VTOC. + - This is the equivalent of using C(NOSCRATCH) in an C(IDCAMS DELETE) command. + type: bool + required: false + default: false volumes: description: - > @@ -575,6 +584,15 @@ type: bool required: false default: false + noscratch: + description: + - "When C(state=absent), specifies whether to keep the data set's entry in the VTOC." + - If C(noscratch=True), the data set is uncataloged but not physically removed from the volume. + The Data Set Control Block is not removed from the VTOC. + - This is the equivalent of using C(NOSCRATCH) in an C(IDCAMS DELETE) command. + type: bool + required: false + default: false extended: description: - Sets the I(extended) attribute for Generation Data Groups. @@ -734,6 +752,12 @@ name: someds.name.here state: absent +- name: Uncatalog a data set but do not remove it from the volume. + zos_data_set: + name: someds.name.here + state: absent + noscratch: true + - name: Delete a data set if it exists. If data set not cataloged, check on volume 222222 for the data set, and then catalog and delete if found. zos_data_set: name: someds.name.here @@ -1404,7 +1428,7 @@ def get_data_set_handler(**params): ) -def perform_data_set_operations(data_set, state, replace, tmp_hlq, force): +def perform_data_set_operations(data_set, state, replace, tmp_hlq, force, noscratch): """Calls functions to perform desired operations on one or more data sets. Returns boolean indicating if changes were made. @@ -1439,7 +1463,7 @@ def perform_data_set_operations(data_set, state, replace, tmp_hlq, force): elif state == "absent" and data_set.data_set_type == "gdg": changed = data_set.ensure_absent(force=force) elif state == "absent": - changed = data_set.ensure_absent(tmp_hlq=tmp_hlq) + changed = data_set.ensure_absent(tmp_hlq=tmp_hlq, noscratch=noscratch) elif state == "cataloged": changed = data_set.ensure_cataloged(tmp_hlq=tmp_hlq) elif state == "uncataloged": @@ -1586,6 +1610,11 @@ def parse_and_validate_args(params): required=False, default=False, ), + noscratch=dict( + type="bool", + required=False, + default=False, + ), ), ), # For individual data set args @@ -1676,6 +1705,11 @@ def parse_and_validate_args(params): required=False, default=False, ), + noscratch=dict( + type="bool", + required=False, + default=False, + ), mutually_exclusive=[ ["batch", "name"], # ["batch", "state"], @@ -1788,6 +1822,11 @@ def run_module(): required=False, default=False, ), + noscratch=dict( + type="bool", + required=False, + default=False, + ), ), ), # For individual data set args @@ -1868,6 +1907,11 @@ def run_module(): required=False, default=False ), + noscratch=dict( + type="bool", + required=False, + default=False + ), ) result = dict(changed=False, message="", names=[]) @@ -1895,6 +1939,8 @@ def run_module(): module.params["replace"] = None if module.params.get("record_format") is not None: module.params["record_format"] = None + if module.params.get("noscratch") is not None: + module.params["noscratch"] = None elif module.params.get("type") is not None: if module.params.get("type") in DATA_SET_TYPES_VSAM: # For VSAM types set the value to nothing and let the code manage it @@ -1921,6 +1967,7 @@ def run_module(): replace=data_set_params.get("replace"), tmp_hlq=data_set_params.get("tmp_hlq"), force=data_set_params.get("force"), + noscratch=data_set_params.get("noscratch"), ) result["changed"] = result["changed"] or current_changed except Exception as e: diff --git a/tests/functional/modules/test_zos_data_set_func.py b/tests/functional/modules/test_zos_data_set_func.py index ec188868df..7346797c53 100644 --- a/tests/functional/modules/test_zos_data_set_func.py +++ b/tests/functional/modules/test_zos_data_set_func.py @@ -1161,3 +1161,151 @@ def test_gdg_deletion_when_absent(ansible_zos_module): assert result.get("changed") is False assert result.get("module_stderr") is None assert result.get("failed") is None + +def test_data_set_delete_with_noscratch(ansible_zos_module, volumes_on_systems): + """ + Tests that 'state: absent' with 'noscratch: true' correctly uncatalogs + a data set but leaves its physical entry in the VTOC. + """ + volumes = Volume_Handler(volumes_on_systems) + volume = volumes.get_available_vol() + hosts = ansible_zos_module + dataset = get_tmp_ds_name(2, 2) + + try: + # Arrange: Create the test data set on the specific volume + hosts.all.zos_data_set( + name=dataset, + type='seq', + state='present', + volumes=[volume], + space_primary=1, + space_type='m' + ) + + # Act: Delete the dataset using the noscratch option + results = hosts.all.zos_data_set( + name=dataset, + state='absent', + noscratch=True + ) + for result in results.contacted.values(): + assert result.get("changed") is True + assert result.get("module_stderr") is None + # Assert 1: Verify the data set is GONE from the catalog. + # This is the first part of the test, where we check that the data set + results = hosts.all.zos_data_set( + name=dataset, + state='absent', + ) + for result in results.contacted.values(): + assert result.get("changed") is False + # catalog_check = hosts.all.command(f"dls '{dataset}'", failed_when=False) + # for result in catalog_check.contacted.values(): + # # Assert that the command failed (non-zero return code) + # assert result.get("rc") != 0 + # Assert 2: Verify the data set is STILL on the volume's VTOC. + # This is the crucial second half of the test. + # We can do this by trying to delete it again, but specifying the volume. + # If this delete reports "changed: true", it's proof that it found and + # deleted the uncataloged data set from the VTOC. + vtoc_check_and_delete = hosts.all.zos_data_set( + name=dataset, + state='absent', + volumes=volume + ) + for result in vtoc_check_and_delete.contacted.values(): + # This assertion proves the data set existed on the volume's VTOC. + assert result.get("changed") is True + finally: + # Cleanup: Perform a final, full delete from the volume since it's still there. + # We provide the volume to ensure it can be found and deleted. + hosts.all.zos_data_set( + name=dataset, + state='absent', + volumes=[volume] + ) + +def test_batch_uncatalog_with_noscratch_suboption(ansible_zos_module, volumes_on_systems): + """ + Tests that the 'noscratch: true' sub-option works correctly when used inside a + batch list to uncatalog multiple data sets. + """ + hosts = ansible_zos_module + volume = Volume_Handler(volumes_on_systems).get_available_vol() + + # Define two separate data sets for the batch operation + dataset_1 = get_tmp_ds_name() + dataset_2 = get_tmp_ds_name() + + try: + # --- Arrange --- + # Create both data sets in a preliminary batch operation so they exist + setup_results = hosts.all.zos_data_set( + batch=[ + {'name': dataset_1, 'type': 'seq', 'state': 'present', 'volumes': volume}, + {'name': dataset_2, 'type': 'seq', 'state': 'present', 'volumes': volume} + ] + ) + for result in setup_results.contacted.values(): + assert result.get("changed") is True + + # --- Act --- + # Run the main test: a batch uncatalog where both items use noscratch + act_results = hosts.all.zos_data_set( + batch=[ + {'name': dataset_1, 'state': 'absent', 'noscratch': True}, + {'name': dataset_2, 'state': 'absent', 'noscratch': True} + ] + ) + # # Assert on the main action results + for result in act_results.contacted.values(): + assert result.get("changed") is True + assert result.get("module_stderr") is None + results = hosts.all.zos_data_set( + name=dataset_1, + state='absent', + ) + for result in results.contacted.values(): + assert result.get("changed") is False + results = hosts.all.zos_data_set( + name=dataset_2, + state='absent', + ) + for result in results.contacted.values(): + assert result.get("changed") is False + + # # --- Verification Assertions --- + # Assert 2: Verify the data set is STILL on the volume's VTOC. + # This is the crucial second half of the test. + # We can do this by trying to delete it again, but specifying the volume. + # If this delete reports "changed: true", it's proof that it found and + # deleted the uncataloged data set from the VTOC. + + vtoc_check_and_delete = hosts.all.zos_data_set( + name=dataset_1, + state='absent', + volumes=volume + ) + for result in vtoc_check_and_delete.contacted.values(): + # This assertion proves the data set existed on the volume's VTOC + assert result.get("changed") is True + + vtoc_check_and_delete = hosts.all.zos_data_set( + name=dataset_2, + state='absent', + volumes=volume + ) + for result in vtoc_check_and_delete.contacted.values(): + # This assertion proves the data set existed on the volume's VTOC + assert result.get("changed") is True + finally: + # --- Cleanup --- + # Ensure both data sets are fully deleted from the volume's VTOC. + # This is critical because the test's main action leaves them on disk. + hosts.all.zos_data_set( + batch=[ + {'name': dataset_1, 'state': 'absent', 'volumes': [volume]}, + {'name': dataset_2, 'state': 'absent', 'volumes': [volume]} + ] + ) From 51349973f2266097c2acd3b16c0448bd17d7eb5e Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Sat, 2 Aug 2025 23:51:27 +0530 Subject: [PATCH 02/36] Adding new module --- plugins/module_utils/better_arg_parser.py | 66 +++ plugins/modules/zos_started_task.py | 430 ++++++++++++++++++ .../modules/test_zos_started_task_func.py | 193 ++++++++ 3 files changed, 689 insertions(+) create mode 100644 plugins/modules/zos_started_task.py create mode 100644 tests/functional/modules/test_zos_started_task_func.py diff --git a/plugins/module_utils/better_arg_parser.py b/plugins/module_utils/better_arg_parser.py index e5dd8e975c..f61abd4652 100644 --- a/plugins/module_utils/better_arg_parser.py +++ b/plugins/module_utils/better_arg_parser.py @@ -160,6 +160,8 @@ def __init__(self, arg_name, contents, resolved_args, arg_defs): "data_set": self._data_set_type, "data_set_base": self._data_set_base_type, "data_set_member": self._data_set_member_type, + "member_name": self._member_name_type, + "identifier_name": self._identifier_name_type, "qualifier": self._qualifier_type, "qualifier_or_empty": self._qualifier_or_empty_type, "qualifier_pattern": self._qualifier_pattern_type, @@ -328,6 +330,70 @@ def _bool_type(self, contents, resolve_dependencies): if not isinstance(contents, bool): raise ValueError('Invalid argument "{0}" for type "bool".'.format(contents)) return contents + + def _member_name_type(self, contents, resolve_dependencies): + """Resolver for data_set type arguments. + + Parameters + ---------- + contents : bool + The contents of the argument. + resolved_dependencies : dict + Contains all of the dependencies and their contents, + which have already been handled, + for use during current arguments handling operations. + + Returns + ------- + str + The arguments contents after any necessary operations. + + Raises + ------ + ValueError + When contents is invalid argument type. + """ + if not fullmatch( + r"^[A-Z$#@]{1}[A-Z0-9$#@]{0,7}$", + str(contents), + IGNORECASE, + ): + raise ValueError( + 'Invalid argument "{0}" for type "data_set".'.format(contents) + ) + return str(contents) + + def _identifier_name_type(self, contents, resolve_dependencies): + """Resolver for data_set type arguments. + + Parameters + ---------- + contents : bool + The contents of the argument. + resolved_dependencies : dict + Contains all of the dependencies and their contents, + which have already been handled, + for use during current arguments handling operations. + + Returns + ------- + str + The arguments contents after any necessary operations. + + Raises + ------ + ValueError + When contents is invalid argument type. + """ + if not fullmatch( + r"^[A-Z]{1}[A-Z0-9$#@]{0,7}$", + str(contents), + IGNORECASE, + ): + raise ValueError( + 'Invalid argument "{0}" for type "data_set".'.format(contents) + ) + return str(contents) def _path_type(self, contents, resolve_dependencies): """Resolver for path type arguments. diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py new file mode 100644 index 0000000000..681e0107ff --- /dev/null +++ b/plugins/modules/zos_started_task.py @@ -0,0 +1,430 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) IBM Corporation 2022, 2025 +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import (absolute_import, division, print_function) +import traceback + +__metaclass__ = type + +DOCUMENTATION = r""" +module: zos_started_task +version_added: 1.16.0 +author: + - "Ravella Surendra Babu (@surendra.ravella582)" +short_description: Perform operations on started tasks. +description: + - Start, display, modify, cancel, force and stop a started task + +options: + asid: + description: + - I(asid) is a unique address space identifier which gets assigned to each running task. + required: false + type: str + device_type: + description: + - I(device_type) is the type of the output device (if any) associated with the task. + required: false + type: str + device_number: + description: + - I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. + A slash (/) must precede a 4-digit number but is not before a 3-digit number. + required: false + type: str + identifier: + description: + - I(device_number) is the name that identifies the task to be started. This name can be up to 8 characters long. + The first character must be alphabetical. + required: false + type: str + job_account: + description: + - I(job_account) specifies accounting data in the JCL JOB statement for the started task. + If the source JCL was a job and has already accounting data, the value that is specified on this parameter + overrides the accounting data in the source JCL. + required: false + type: str + job_name: + description: + - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, + then member_name is used as job_name. + required: false + type: str + keyword_parameters: + description: + - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. + The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than + 44 characters in length. + required: false + type: str + member_name: + description: + - I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL + for the task to be started. The member can be either a job or a cataloged procedure. + required: false + type: str + operation: + description: + - The started task operation which needs to be performed. + - > + If I(operation=start) and the data set does not exist on the managed node, + no action taken, module completes successfully with I(changed=False). + required: false + type: str + choices: + - start + - stop + - modify + - display + - force + - cancel + parameters: + description: + - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks + required: false + type: str + reus_asid: + description: + - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, + a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified + on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + subsystem_name: + description: + - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, + which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. + required: false + type: str + volume_serial: + description: + - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. + required: false + type: str +""" +EXAMPLES = r""" +- name: Start a started task using member name. + zos_started_task: + member: "PROCAPP" + operation: "start" +""" +RETURN = r""" + +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import ( + better_arg_parser +) +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import ( + zoau_version_checker +) +from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.import_handler import ( + ZOAUImportError +) + +try: + from zoautil_py import opercmd +except Exception: + datasets = ZOAUImportError(traceback.format_exc()) + gdgs = ZOAUImportError(traceback.format_exc()) + +try: + from zoautil_py import exceptions as zoau_exceptions +except ImportError: + zoau_exceptions = ZOAUImportError(traceback.format_exc()) + +def execute_command(operator_cmd, timeout_s=1, *args, **kwargs): + """Execute operator command. + + Parameters + ---------- + operator_cmd : str + Operator command. + timeout_s : int + Timeout to wait for the command execution, measured in centiseconds. + *args : dict + Arguments for the command. + **kwargs : dict + More arguments for the command. + + Returns + ------- + OperatorQueryResult + The result of the command. + """ + # as of ZOAU v1.3.0, timeout is measured in centiseconds, therefore: + timeout_c = 100 * timeout_s + response = opercmd.execute(operator_cmd, timeout_c, *args, **kwargs) + + rc = response.rc + stdout = response.stdout_response + stderr = response.stderr_response + return rc, stdout, stderr + +def prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters): + cmd = 'S '+member + if identifier: + cmd = cmd + "." + identifier + "," + device + "," + volume_serial + "," + parameters + if jobname: + cmd = cmd + ",jobname=" + job_name + if jobaccount: + cmd = cmd + ",jobacct=" + job_account + if subsystem_name: + cmd = cmd + ",SUB=" + subsystem_name + if reus_asid: + cmd = cmd + ",REUSASID=" + reus_asid + if keyword_parameters: + cmd = cmd + "," + keyword_parameters + return cmd + +def run_module(): + """Initialize the module. + + Raises + ------ + fail_json + z/OS started task operation failed. + """ + module = AnsibleModule( + argument_spec={ + 'operation': { + 'type': 'str', + 'required': True, + 'choices': ['start', 'stop', 'modify', 'display', 'force', 'cancel'] + }, + 'member_name': { + 'type': 'str', + 'required': False, + 'aliases': ['member'] + }, + 'identifier': { + 'arg_type': 'str', + 'required': False + }, + 'job_name': { + 'type': 'str', + 'required': False, + 'aliases': ['task_name'] + }, + 'job_account': { #55 chars + 'type': 'str', + 'required': False + }, + 'device_type': { + 'type': 'str', + 'required': False + }, + 'device_number': { #A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. + 'type': 'str', + 'required': False + }, + 'volume_serial': { + 'type': 'str', + 'required': False + }, + 'subsystem_name': { #The name must be 1 - 4 characters + 'type': 'str', + 'required': False + }, + 'reus_asid': { + 'type': 'bool', + 'required': False, + 'choices': ['yes', 'no'] + }, + 'parameters': { + 'type': 'str', + 'required': False + }, + 'keyword_parameters': { #The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. + 'type': 'str', + 'required': False + }, + 'asid': { + 'type': 'str', + 'required': False + } + }, + mutually_exclusive=[ + ['job_name', 'identifier'], + ['device_name', 'device_type'] + ], + supports_check_mode=True + ) + + args_def = { + 'operation': { + 'type': 'str', + 'required': True + }, + 'member_name': { + 'arg_type': 'member_name', + 'required': False, + 'aliases': ['member'] + }, + 'identifier_name': { + 'arg_type': 'identifier_name', + 'required': False + }, + 'job_name': { + 'arg_type': 'str', + 'required': False, + 'aliases': ['job'] + }, + 'job_account': { + 'arg_type': 'str', + 'required': False + }, + 'device_type': { + 'arg_type': 'str', + 'required': False + }, + 'device_number': { + 'arg_type': 'str', + 'required': False + }, + 'volume_serial': { + 'arg_type': 'str', + 'required': False + }, + 'subsystem_name': { + 'arg_type': 'str', + 'required': False + }, + 'reus_asid': { + 'arg_type': 'str', + 'required': False + }, + 'parameters': { + 'arg_type': 'str', + 'required': False + }, + 'keyword_parameters': { + 'arg_type': 'str', + 'required': False + }, + 'asid': { + 'arg_type': 'str', + 'required': False + } + } + + try: + parser = better_arg_parser.BetterArgParser(args_def) + parsed_args = parser.parse_args(module.params) + module.params = parsed_args + except ValueError as err: + module.fail_json( + msg='Parameter verification failed.', + stderr=str(err) + ) + operation = module.params.get('operation') + member = module.params.get('member_name') + identifier = module.params.get('identifier') + job_name = module.params.get('job_name') + job_account = module.params.get('job_account') + asid = module.params.get('asid') + parameters = module.params.get('parameters') + device_type = module.params.get('device_type') + device_number = module.params.get('device_number') + volume_serial = module.params.get('volume_serial') + subsystem_name = module.params.get('subsystem_name') + reus_asid = module.params.get('reus_asid') + keyword_parameters = module.params.get('keyword_parameters') + device = device_type if device_type is not None else device_number + kwargs = {} + + wait_s = 5 + + use_wait_arg = False + if zoau_version_checker.is_zoau_version_higher_than("1.2.4"): + use_wait_arg = True + + if use_wait_arg: + kwargs.update({"wait": True}) + + args = [] + cmd = '' + started_task_name = "" + if operation != 'start': + if job_name is not None: + started_task_name = job_name + elif member is not None: + started_task_name = member + if identifier is not None: + started_task_name = started_task_name + "." + identifier + else: + module.fail_json( + msg="either member_name or identifier is needed but both are missing.", + changed=False + ) + if operation == 'start': + ##member name is mandatory + if member is None or member.strip() == "": + module.fail_json( + msg="member_name is missing which is mandatory.", + changed=False + ) + cmd = prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters) + elif operation == 'display': + cmd = 'd a,'+started_task_name + elif operation == 'stop': + cmd = 'p '+started_task_name + elif operation == 'cancel': + cmd = 'c '+started_task_name + if asid: + cmd = cmd+',a='+asid + elif operation == 'force': + cmd = 'force '+started_task_name + if asid: + cmd = cmd+',a='+asid + elif operation == 'modify': + cmd = 'f '+started_task_name+','+parameters + changed = False + stdout = "" + stderr = "" + rc, out, err = execute_command(cmd, timeout_s=wait_s, *args, **kwargs) + if "ERROR" in out or err != "": + changed = False + stdout = out + stderr = err + if err == "" or err is None: + stderr = out + else: + changed = True + stdout = out + stderr = err + + + result = dict() + + if module.check_mode: + module.exit_json(**result) + + result = dict( + changed=changed, + cmd=cmd, + remote_cmd=cmd, + rc=rc, + stdout=stdout, + stderr=stderr, + stdout_lines=stdout.split('\n'), + stderr_lines=stderr.split('\n'), + ) + + module.exit_json(**result) + + +if __name__ == '__main__': + run_module() diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py new file mode 100644 index 0000000000..04ae70c678 --- /dev/null +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) IBM Corporation 2025 +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +from ibm_zos_core.tests.helpers.dataset import get_tmp_ds_name +from ibm_zos_core.tests.helpers.utils import get_random_file_name +from shellescape import quote +import re + +TMP_DIRECTORY = "/tmp/" +PROC_PDS = "USER.PRIVATE.PROCLIB" +TASK_JCL_CONTENT="""//STEP1 EXEC PGM=BPXBATCH +//STDOUT DD SYSOUT=* +//STDERR DD SYSOUT=* +//STDPARM DD * +SH sleep 600 +/*""" + +def test_start_and_cancel_zos_started_task(ansible_zos_module): + try: + hosts = ansible_zos_module + data_set_name = get_tmp_ds_name() + temp_path = get_random_file_name(dir=TMP_DIRECTORY) + hosts.all.file(path=temp_path, state="directory") + hosts.all.shell( + cmd="echo {0} > {1}/SAMPLE".format(quote(TASK_JCL_CONTENT), temp_path) + ) + + hosts.all.zos_data_set( + name=data_set_name, state="present", type="pds", replace=True + ) + + hosts.all.shell( + cmd="dcp {0}/SAMPLE \"//'{1}(SAMPLE)'\"".format(temp_path, data_set_name) + ) + + copy_result = hosts.all.zos_copy( + src="{0}(SAMPLE)".format(data_set_name), + dest=PROC_PDS, + remote_src=True, + force=True + ) + + for cp_res in copy_result.contacted.values(): + print(cp_res) + assert cp_res.get("msg") is None + assert cp_res.get("changed") is True + assert cp_res.get("dest") == PROC_PDS + + start_results = hosts.all.zos_started_task( + operation="start", + member="SAMPLE" + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + stop_results = hosts.all.zos_started_task( + operation="cancel", + started_task_name="SAMPLE" + ) + + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + start_results = hosts.all.zos_started_task( + operation="start", + member="SAMPLE" + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + display_result = hosts.all.zos_started_task( + operation="display", + started_task_name="SAMPLE" + ) + for result in display_result.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + display_output = list(display_result.contacted.values())[0].get("stdout") + asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) + + stop_results = hosts.all.zos_started_task( + operation="cancel", + started_task_name="SAMPLE", + asid=asid_val + ) + + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + finally: + hosts.all.file(path=temp_path, state="absent") + hosts.all.zos_data_set(name=data_set_name, state="absent") + hosts.all.zos_data_set( + name=f"{PROC_PDS}(SAMPLE)", + state="absent", + type="member", + force=True + ) + +def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): + try: + hosts = ansible_zos_module + data_set_name = get_tmp_ds_name() + temp_path = get_random_file_name(dir=TMP_DIRECTORY) + hosts.all.file(path=temp_path, state="directory") + hosts.all.shell( + cmd="echo {0} > {1}/SAMPLE".format(quote(TASK_JCL_CONTENT), temp_path) + ) + + hosts.all.zos_data_set( + name=data_set_name, state="present", type="pds", replace=True + ) + + hosts.all.shell( + cmd="dcp {0}/SAMPLE \"//'{1}(SAMPLE)'\"".format(temp_path, data_set_name) + ) + + copy_result = hosts.all.zos_copy( + src="{0}(SAMPLE)".format(data_set_name), + dest=PROC_PDS, + remote_src=True, + force=True + ) + + for cp_res in copy_result.contacted.values(): + print(cp_res) + assert cp_res.get("msg") is None + assert cp_res.get("changed") is True + assert cp_res.get("dest") == PROC_PDS + + start_results = hosts.all.zos_started_task( + operation="start", + member="SAMPLE", + job_name="TESTTSK" + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + stop_results = hosts.all.zos_started_task( + operation="cancel", + started_task_name="TESTTSK" + ) + + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + finally: + hosts.all.file(path=temp_path, state="absent") + hosts.all.zos_data_set(name=data_set_name, state="absent") + hosts.all.zos_data_set( + name=f"{PROC_PDS}(SAMPLE)", + state="absent", + type="member", + force=True + ) \ No newline at end of file From 19de4314d5772c70ca685f6fbaa35505948842f2 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:33:50 +0530 Subject: [PATCH 03/36] Update zos_started_task.py --- plugins/modules/zos_started_task.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 681e0107ff..c6ed611d68 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -345,6 +345,26 @@ def run_module(): device = device_type if device_type is not None else device_number kwargs = {} + # Validations + if job_account != "" and len(job_account) > 55: + module.fail_json( + msg="job_account value should not exceed 55 characters.", + changed=False + ) + if device_number != "": + devnum_len = len(device_number) + if devnum_len not in (3, 5) or ( devnum_len == 5 and not device_number.startswith("/")): + module.fail_json( + msg="Invalid device_number.", + changed=False + ) + if subsystem_name != "" and len(job_account) > 4: + module.fail_json( + msg="The subsystem_name must be 1 - 4 characters.", + changed=False + ) + # keywaord arguments validation..... + wait_s = 5 use_wait_arg = False @@ -366,7 +386,7 @@ def run_module(): started_task_name = started_task_name + "." + identifier else: module.fail_json( - msg="either member_name or identifier is needed but both are missing.", + msg="one of job_name, member_name or identifier is needed but all are missing.", changed=False ) if operation == 'start': From 256d4c9af95d19952a1d954836641cd0a4309101 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:04:24 +0530 Subject: [PATCH 04/36] Updating testcases --- plugins/module_utils/better_arg_parser.py | 4 +-- plugins/modules/zos_started_task.py | 22 ++++++++------- .../modules/test_zos_started_task_func.py | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/plugins/module_utils/better_arg_parser.py b/plugins/module_utils/better_arg_parser.py index f61abd4652..ab0fde9292 100644 --- a/plugins/module_utils/better_arg_parser.py +++ b/plugins/module_utils/better_arg_parser.py @@ -359,7 +359,7 @@ def _member_name_type(self, contents, resolve_dependencies): IGNORECASE, ): raise ValueError( - 'Invalid argument "{0}" for type "data_set".'.format(contents) + 'Invalid argument "{0}" for type "member_name".'.format(contents) ) return str(contents) @@ -391,7 +391,7 @@ def _identifier_name_type(self, contents, resolve_dependencies): IGNORECASE, ): raise ValueError( - 'Invalid argument "{0}" for type "data_set".'.format(contents) + 'Invalid argument "{0}" for type "identifier_name".'.format(contents) ) return str(contents) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index c6ed611d68..35a1536145 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -177,9 +177,9 @@ def prepare_start_command(member, identifier, job_name, job_account, device, vol cmd = 'S '+member if identifier: cmd = cmd + "." + identifier + "," + device + "," + volume_serial + "," + parameters - if jobname: + if job_name: cmd = cmd + ",jobname=" + job_name - if jobaccount: + if job_account: cmd = cmd + ",jobacct=" + job_account if subsystem_name: cmd = cmd + ",SUB=" + subsystem_name @@ -209,14 +209,15 @@ def run_module(): 'required': False, 'aliases': ['member'] }, - 'identifier': { + 'identifier_name': { 'arg_type': 'str', - 'required': False + 'required': False, + 'aliases': ['identifier'] }, 'job_name': { 'type': 'str', 'required': False, - 'aliases': ['task_name'] + 'aliases': ['job', 'task_name', 'task'] }, 'job_account': { #55 chars 'type': 'str', @@ -275,12 +276,13 @@ def run_module(): }, 'identifier_name': { 'arg_type': 'identifier_name', - 'required': False + 'required': False, + 'aliases': ['identifier'] }, 'job_name': { 'arg_type': 'str', 'required': False, - 'aliases': ['job'] + 'aliases': ['job', 'task_name', 'task'] }, 'job_account': { 'arg_type': 'str', @@ -346,19 +348,19 @@ def run_module(): kwargs = {} # Validations - if job_account != "" and len(job_account) > 55: + if job_account and len(job_account) > 55: module.fail_json( msg="job_account value should not exceed 55 characters.", changed=False ) - if device_number != "": + if device_number: devnum_len = len(device_number) if devnum_len not in (3, 5) or ( devnum_len == 5 and not device_number.startswith("/")): module.fail_json( msg="Invalid device_number.", changed=False ) - if subsystem_name != "" and len(job_account) > 4: + if subsystem_name and len(job_account) > 4: module.fail_json( msg="The subsystem_name must be 1 - 4 characters.", changed=False diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 04ae70c678..d1246d310a 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -30,6 +30,33 @@ SH sleep 600 /*""" +def test_start_task_with_invalid_member(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + operation="start", + member="SAMPLETASK" + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("stderr") is not None + +def test_start_task_with_invalid_identifier(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + operation="start", + member="SAMPLE", + identifier="$HELLO" + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("stderr") is not None + def test_start_and_cancel_zos_started_task(ansible_zos_module): try: hosts = ansible_zos_module From 8cd6c6913deabb32da8950910efce2c1ae81a64d Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:05:48 +0530 Subject: [PATCH 05/36] Updating test cases --- .../modules/test_zos_started_task_func.py | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index d1246d310a..e1a8165b73 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -57,6 +57,35 @@ def test_start_task_with_invalid_identifier(ansible_zos_module): assert result.get("failed") is True assert result.get("stderr") is not None +def test_start_task_with_invalid_jobaccount(ansible_zos_module): + hosts = ansible_zos_module + job_account = "(T043JM,JM00,1,0,0,This is the invalid job account information to test negative scenario)" + start_results = hosts.all.zos_started_task( + operation="start", + member="SAMPLE", + job_account=job_account + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + +def test_start_task_with_invalid_devicenum(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + operation="start", + member="SAMPLE", + device_number="0870" + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + def test_start_and_cancel_zos_started_task(ansible_zos_module): try: hosts = ansible_zos_module @@ -101,7 +130,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): stop_results = hosts.all.zos_started_task( operation="cancel", - started_task_name="SAMPLE" + task_name="SAMPLE" ) for result in stop_results.contacted.values(): @@ -123,7 +152,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): display_result = hosts.all.zos_started_task( operation="display", - started_task_name="SAMPLE" + task_name="SAMPLE" ) for result in display_result.contacted.values(): print(result) @@ -135,7 +164,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): stop_results = hosts.all.zos_started_task( operation="cancel", - started_task_name="SAMPLE", + task_name="SAMPLE", asid=asid_val ) @@ -200,7 +229,7 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): stop_results = hosts.all.zos_started_task( operation="cancel", - started_task_name="TESTTSK" + task_name="TESTTSK" ) for result in stop_results.contacted.values(): From 241fabf208103a7d825c02ae79c304e7100c2364 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Wed, 13 Aug 2025 00:21:27 +0530 Subject: [PATCH 06/36] Updating testcases --- plugins/module_utils/better_arg_parser.py | 4 +- plugins/modules/zos_started_task.py | 151 ++++++++++-------- .../modules/test_zos_started_task_func.py | 61 ++----- 3 files changed, 103 insertions(+), 113 deletions(-) diff --git a/plugins/module_utils/better_arg_parser.py b/plugins/module_utils/better_arg_parser.py index ab0fde9292..d49cb46458 100644 --- a/plugins/module_utils/better_arg_parser.py +++ b/plugins/module_utils/better_arg_parser.py @@ -330,7 +330,7 @@ def _bool_type(self, contents, resolve_dependencies): if not isinstance(contents, bool): raise ValueError('Invalid argument "{0}" for type "bool".'.format(contents)) return contents - + def _member_name_type(self, contents, resolve_dependencies): """Resolver for data_set type arguments. @@ -362,7 +362,7 @@ def _member_name_type(self, contents, resolve_dependencies): 'Invalid argument "{0}" for type "member_name".'.format(contents) ) return str(contents) - + def _identifier_name_type(self, contents, resolve_dependencies): """Resolver for data_set type arguments. diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 35a1536145..988ba937ac 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) IBM Corporation 2022, 2025 +# Copyright (c) IBM Corporation 2025 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. + from __future__ import (absolute_import, division, print_function) -import traceback __metaclass__ = type @@ -24,7 +24,7 @@ - "Ravella Surendra Babu (@surendra.ravella582)" short_description: Perform operations on started tasks. description: - - Start, display, modify, cancel, force and stop a started task + - start, display, modify, cancel, force and stop a started task options: asid: @@ -39,20 +39,22 @@ type: str device_number: description: - - I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. + - I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. required: false type: str - identifier: + identifier_name: description: - - I(device_number) is the name that identifies the task to be started. This name can be up to 8 characters long. + - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. The first character must be alphabetical. required: false type: str + aliases: + - identifier job_account: description: - - I(job_account) specifies accounting data in the JCL JOB statement for the started task. - If the source JCL was a job and has already accounting data, the value that is specified on this parameter + - I(job_account) specifies accounting data in the JCL JOB statement for the started task. + If the source JCL was a job and has already accounting data, the value that is specified on this parameter overrides the accounting data in the source JCL. required: false type: str @@ -62,26 +64,32 @@ then member_name is used as job_name. required: false type: str + aliases: + - job + - task + - task_name keyword_parameters: description: - - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. - The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than + - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. + The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. required: false type: str member_name: description: - - I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL + - I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. required: false type: str + aliases: + - member operation: description: - The started task operation which needs to be performed. - > If I(operation=start) and the data set does not exist on the managed node, no action taken, module completes successfully with I(changed=False). - required: false + required: true type: str choices: - start @@ -97,12 +105,17 @@ type: str reus_asid: description: - - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, - a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified + - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, + a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + required: false + type: str + choices: + - 'YES' + - 'NO' subsystem_name: description: - - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, + - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. required: false type: str @@ -118,11 +131,13 @@ member: "PROCAPP" operation: "start" """ + RETURN = r""" """ from ansible.module_utils.basic import AnsibleModule +import traceback from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import ( better_arg_parser @@ -136,15 +151,15 @@ try: from zoautil_py import opercmd -except Exception: - datasets = ZOAUImportError(traceback.format_exc()) - gdgs = ZOAUImportError(traceback.format_exc()) - -try: - from zoautil_py import exceptions as zoau_exceptions except ImportError: zoau_exceptions = ZOAUImportError(traceback.format_exc()) +# try: +# from zoautil_py import exceptions as zoau_exceptions +# except ImportError: +# zoau_exceptions = ZOAUImportError(traceback.format_exc()) + + def execute_command(operator_cmd, timeout_s=1, *args, **kwargs): """Execute operator command. @@ -173,22 +188,24 @@ def execute_command(operator_cmd, timeout_s=1, *args, **kwargs): stderr = response.stderr_response return rc, stdout, stderr + def prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters): - cmd = 'S '+member + cmd = 'S ' + member if identifier: - cmd = cmd + "." + identifier + "," + device + "," + volume_serial + "," + parameters + cmd = cmd + "." + identifier + "," + device + "," + volume_serial + "," + parameters if job_name: - cmd = cmd + ",jobname=" + job_name + cmd = cmd + ",jobname=" + job_name if job_account: - cmd = cmd + ",jobacct=" + job_account + cmd = cmd + ",jobacct=" + job_account if subsystem_name: - cmd = cmd + ",SUB=" + subsystem_name + cmd = cmd + ",SUB=" + subsystem_name if reus_asid: - cmd = cmd + ",REUSASID=" + reus_asid + cmd = cmd + ",REUSASID=" + reus_asid if keyword_parameters: - cmd = cmd + "," + keyword_parameters + cmd = cmd + "," + keyword_parameters return cmd + def run_module(): """Initialize the module. @@ -210,7 +227,7 @@ def run_module(): 'aliases': ['member'] }, 'identifier_name': { - 'arg_type': 'str', + 'type': 'str', 'required': False, 'aliases': ['identifier'] }, @@ -219,7 +236,7 @@ def run_module(): 'required': False, 'aliases': ['job', 'task_name', 'task'] }, - 'job_account': { #55 chars + 'job_account': { 'type': 'str', 'required': False }, @@ -227,7 +244,7 @@ def run_module(): 'type': 'str', 'required': False }, - 'device_number': { #A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. + 'device_number': { # A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. 'type': 'str', 'required': False }, @@ -235,22 +252,23 @@ def run_module(): 'type': 'str', 'required': False }, - 'subsystem_name': { #The name must be 1 - 4 characters + 'subsystem_name': { # The name must be 1 - 4 characters 'type': 'str', 'required': False }, 'reus_asid': { - 'type': 'bool', + 'type': 'str', 'required': False, - 'choices': ['yes', 'no'] + 'choices': ['YES', 'NO'] }, 'parameters': { 'type': 'str', 'required': False }, - 'keyword_parameters': { #The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. + 'keyword_parameters': { 'type': 'str', - 'required': False + 'required': False, + 'no_log': False }, 'asid': { 'type': 'str', @@ -258,8 +276,8 @@ def run_module(): } }, mutually_exclusive=[ - ['job_name', 'identifier'], - ['device_name', 'device_type'] + ['job_name', 'identifier_name'], + ['device_number', 'device_type'] ], supports_check_mode=True ) @@ -352,19 +370,19 @@ def run_module(): module.fail_json( msg="job_account value should not exceed 55 characters.", changed=False - ) + ) if device_number: devnum_len = len(device_number) - if devnum_len not in (3, 5) or ( devnum_len == 5 and not device_number.startswith("/")): + if devnum_len not in (3, 5) or (devnum_len == 5 and not device_number.startswith("/")): module.fail_json( msg="Invalid device_number.", changed=False - ) + ) if subsystem_name and len(job_account) > 4: module.fail_json( msg="The subsystem_name must be 1 - 4 characters.", changed=False - ) + ) # keywaord arguments validation..... wait_s = 5 @@ -388,31 +406,31 @@ def run_module(): started_task_name = started_task_name + "." + identifier else: module.fail_json( - msg="one of job_name, member_name or identifier is needed but all are missing.", - changed=False + msg="one of job_name, member_name or identifier is needed but all are missing.", + changed=False ) if operation == 'start': - ##member name is mandatory + # member name is mandatory if member is None or member.strip() == "": module.fail_json( - msg="member_name is missing which is mandatory.", - changed=False - ) + msg="member_name is missing which is mandatory.", + changed=False + ) cmd = prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters) elif operation == 'display': - cmd = 'd a,'+started_task_name + cmd = 'd a,' + started_task_name elif operation == 'stop': - cmd = 'p '+started_task_name + cmd = 'p ' + started_task_name elif operation == 'cancel': - cmd = 'c '+started_task_name + cmd = 'c ' + started_task_name if asid: - cmd = cmd+',a='+asid + cmd = cmd + ',a=' + asid elif operation == 'force': - cmd = 'force '+started_task_name + cmd = 'force ' + started_task_name if asid: - cmd = cmd+',a='+asid + cmd = cmd + ',a=' + asid elif operation == 'modify': - cmd = 'f '+started_task_name+','+parameters + cmd = 'f ' + started_task_name + ',' + parameters changed = False stdout = "" stderr = "" @@ -422,31 +440,30 @@ def run_module(): stdout = out stderr = err if err == "" or err is None: - stderr = out + stderr = out else: changed = True stdout = out stderr = err - result = dict() if module.check_mode: module.exit_json(**result) result = dict( - changed=changed, - cmd=cmd, - remote_cmd=cmd, - rc=rc, - stdout=stdout, - stderr=stderr, - stdout_lines=stdout.split('\n'), - stderr_lines=stderr.split('\n'), - ) + changed=changed, + cmd=cmd, + remote_cmd=cmd, + rc=rc, + stdout=stdout, + stderr=stderr, + stdout_lines=stdout.split('\n'), + stderr_lines=stderr.split('\n'), + ) module.exit_json(**result) if __name__ == '__main__': - run_module() + run_module() diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index e1a8165b73..5529ff4f62 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -96,27 +96,14 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): cmd="echo {0} > {1}/SAMPLE".format(quote(TASK_JCL_CONTENT), temp_path) ) - hosts.all.zos_data_set( - name=data_set_name, state="present", type="pds", replace=True - ) - hosts.all.shell( - cmd="dcp {0}/SAMPLE \"//'{1}(SAMPLE)'\"".format(temp_path, data_set_name) + cmd="dcp {0}/SAMPLE {1}".format(temp_path, data_set_name) ) - copy_result = hosts.all.zos_copy( - src="{0}(SAMPLE)".format(data_set_name), - dest=PROC_PDS, - remote_src=True, - force=True + hosts.all.shell( + cmd="dcp {0} \"//'{1}(SAMPLE)'\"".format(data_set_name, PROC_PDS) ) - for cp_res in copy_result.contacted.values(): - print(cp_res) - assert cp_res.get("msg") is None - assert cp_res.get("changed") is True - assert cp_res.get("dest") == PROC_PDS - start_results = hosts.all.zos_started_task( operation="start", member="SAMPLE" @@ -176,12 +163,11 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): finally: hosts.all.file(path=temp_path, state="absent") - hosts.all.zos_data_set(name=data_set_name, state="absent") - hosts.all.zos_data_set( - name=f"{PROC_PDS}(SAMPLE)", - state="absent", - type="member", - force=True + hosts.all.shell( + cmd="drm {0}".format(data_set_name) + ) + hosts.all.shell( + cmd="mrm '{0}(SAMPLE)'".format(PROC_PDS) ) def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): @@ -190,31 +176,19 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): data_set_name = get_tmp_ds_name() temp_path = get_random_file_name(dir=TMP_DIRECTORY) hosts.all.file(path=temp_path, state="directory") + hosts.all.shell( cmd="echo {0} > {1}/SAMPLE".format(quote(TASK_JCL_CONTENT), temp_path) ) - hosts.all.zos_data_set( - name=data_set_name, state="present", type="pds", replace=True - ) - hosts.all.shell( - cmd="dcp {0}/SAMPLE \"//'{1}(SAMPLE)'\"".format(temp_path, data_set_name) + cmd="dcp {0}/SAMPLE {1}".format(temp_path, data_set_name) ) - copy_result = hosts.all.zos_copy( - src="{0}(SAMPLE)".format(data_set_name), - dest=PROC_PDS, - remote_src=True, - force=True + hosts.all.shell( + cmd="dcp {0} \"//'{1}(SAMPLE)'\"".format(data_set_name, PROC_PDS) ) - for cp_res in copy_result.contacted.values(): - print(cp_res) - assert cp_res.get("msg") is None - assert cp_res.get("changed") is True - assert cp_res.get("dest") == PROC_PDS - start_results = hosts.all.zos_started_task( operation="start", member="SAMPLE", @@ -240,10 +214,9 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): finally: hosts.all.file(path=temp_path, state="absent") - hosts.all.zos_data_set(name=data_set_name, state="absent") - hosts.all.zos_data_set( - name=f"{PROC_PDS}(SAMPLE)", - state="absent", - type="member", - force=True + hosts.all.shell( + cmd="drm {0}".format(data_set_name) + ) + hosts.all.shell( + cmd="mrm '{0}(SAMPLE)'".format(PROC_PDS) ) \ No newline at end of file From b6592c50b214353e18c6188dafa765815d51741c Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 28 Aug 2025 21:27:16 +0530 Subject: [PATCH 07/36] Adding features in display command and updating testcases --- plugins/module_utils/better_arg_parser.py | 27 +++ plugins/modules/zos_started_task.py | 206 ++++++++++++++---- .../modules/test_zos_started_task_func.py | 153 ++++++++++--- 3 files changed, 322 insertions(+), 64 deletions(-) diff --git a/plugins/module_utils/better_arg_parser.py b/plugins/module_utils/better_arg_parser.py index d49cb46458..a42301ede4 100644 --- a/plugins/module_utils/better_arg_parser.py +++ b/plugins/module_utils/better_arg_parser.py @@ -152,6 +152,7 @@ def __init__(self, arg_name, contents, resolved_args, arg_defs): # TODO: determine if we should optionally allow top-level args to be passed self.type_handlers = { "dict": self._dict_type, + "basic_dict": self._basic_dict_type, "list": self._list_type, "str": self._str_type, "bool": self._bool_type, @@ -254,6 +255,32 @@ def _dict_type(self, contents, resolved_dependencies): self._assert_mutually_exclusive(contents) return contents + def _basic_dict_type(self, contents, resolve_dependencies): + """Resolver for str type arguments. + + Parameters + ---------- + contents : dict + The contents of the argument. + resolved_dependencies : dict + Contains all of the dependencies and their contents, + which have already been handled, + for use during current arguments handling operations. + + Returns + ------- + dict + The arguments contents after any necessary operations. + + Raises + ------ + ValueError + When contents is invalid argument type. + """ + if not isinstance(contents, dict): + raise ValueError('Invalid argument "{0}" for type "dict".'.format(contents)) + return contents + def _str_type(self, contents, resolve_dependencies): """Resolver for str type arguments. diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 988ba937ac..9b1616b8c8 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -29,7 +29,7 @@ options: asid: description: - - I(asid) is a unique address space identifier which gets assigned to each running task. + - I(asid) is a unique address space identifier which gets assigned to each running started task. required: false type: str device_type: @@ -124,6 +124,12 @@ - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. required: false type: str + verbose: + description: + - Return System logs that describe the task's execution. + required: false + type: bool + default: false """ EXAMPLES = r""" - name: Start a started task using member name. @@ -138,7 +144,7 @@ from ansible.module_utils.basic import AnsibleModule import traceback - +import re from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import ( better_arg_parser ) @@ -150,7 +156,7 @@ ) try: - from zoautil_py import opercmd + from zoautil_py import opercmd,zsystem except ImportError: zoau_exceptions = ZOAUImportError(traceback.format_exc()) @@ -160,7 +166,7 @@ # zoau_exceptions = ZOAUImportError(traceback.format_exc()) -def execute_command(operator_cmd, timeout_s=1, *args, **kwargs): +def execute_command(operator_cmd, started_task_name, execute_display_before=False, execute_display_after=False, timeout_s=1, *args, **kwargs): """Execute operator command. Parameters @@ -179,14 +185,29 @@ def execute_command(operator_cmd, timeout_s=1, *args, **kwargs): OperatorQueryResult The result of the command. """ + task_params = {} # as of ZOAU v1.3.0, timeout is measured in centiseconds, therefore: timeout_c = 100 * timeout_s + if execute_display_before: + task_params = execute_display_command(started_task_name, timeout_c) + response = opercmd.execute(operator_cmd, timeout_c, *args, **kwargs) + if execute_display_after: + task_params = execute_display_command(started_task_name, timeout_c) + rc = response.rc stdout = response.stdout_response stderr = response.stderr_response - return rc, stdout, stderr + return rc, stdout, stderr, task_params + +def execute_display_command(started_task_name, timeout_c): + cmd = "d a,"+started_task_name + display_response = opercmd.execute(cmd, timeout_c) + task_params = [] + if display_response.rc == 0 and display_response.stderr_response == "": + task_params = extract_keys(display_response.stdout_response) + return task_params def prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters): @@ -206,6 +227,53 @@ def prepare_start_command(member, identifier, job_name, job_account, device, vol return cmd +def extract_keys(stdout): + # keys = {'A': 'ASID', 'CT': 'CPU_Time', 'ET': 'Elapsed_Time', 'WUID': 'WUID', 'USERID': 'USERID', 'P': 'Priority'} + # params = {} + # for key in keys: + # parm = re.search(rf"{key}=([^\s]+)", stdout) + # if parm: + # params[keys[key]] = parm.group(1) + # return params + lines = stdout.strip().split('\n') + tasks = [] + current_task = None + task_header_regex = re.compile(r'^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)') + kv_pattern = re.compile(r'(\S+)=(\S+)') + for line in lines[5:]: + line = line.strip() + if len(line.split()) >= 5 and task_header_regex.search(line): + if current_task: + tasks.append(current_task) + match = task_header_regex.search(line) + current_task = { + "TASK_NAME": match.group(1), + "DETAILS": {} + } + for match in kv_pattern.finditer(line): + key, value = match.groups() + current_task["DETAILS"][key] = value + elif current_task: + for match in kv_pattern.finditer(line): + key, value = match.groups() + current_task["DETAILS"][key] = value + if current_task: + tasks.append(current_task) + return tasks + + +def fetch_logs(command): + stdout = zsystem.read_console(options='-t1') + stdout_lines = stdout.splitlines() + first = None + for i, line in enumerate(stdout_lines): + if command in line: + if first is None: + first = i + if first is None: + return "" + return stdout_lines[first:] + def run_module(): """Initialize the module. @@ -216,10 +284,10 @@ def run_module(): """ module = AnsibleModule( argument_spec={ - 'operation': { + 'state': { 'type': 'str', 'required': True, - 'choices': ['start', 'stop', 'modify', 'display', 'force', 'cancel'] + 'choices': ['started', 'stopped', 'modified', 'display', 'forced', 'cancelled'] }, 'member_name': { 'type': 'str', @@ -266,25 +334,28 @@ def run_module(): 'required': False }, 'keyword_parameters': { - 'type': 'str', + 'type': 'dict', 'required': False, 'no_log': False }, 'asid': { 'type': 'str', 'required': False + }, + 'verbose': { + 'type': 'bool', + 'required': False } }, mutually_exclusive=[ - ['job_name', 'identifier_name'], ['device_number', 'device_type'] ], supports_check_mode=True ) args_def = { - 'operation': { - 'type': 'str', + 'state': { + 'arg_type': 'str', 'required': True }, 'member_name': { @@ -331,12 +402,16 @@ def run_module(): 'required': False }, 'keyword_parameters': { - 'arg_type': 'str', + 'arg_type': 'basic_dict', 'required': False }, 'asid': { 'arg_type': 'str', 'required': False + }, + 'verbose': { + 'arg_type': 'bool', + 'required': False } } @@ -349,7 +424,7 @@ def run_module(): msg='Parameter verification failed.', stderr=str(err) ) - operation = module.params.get('operation') + operation = module.params.get('state') member = module.params.get('member_name') identifier = module.params.get('identifier') job_name = module.params.get('job_name') @@ -362,8 +437,19 @@ def run_module(): subsystem_name = module.params.get('subsystem_name') reus_asid = module.params.get('reus_asid') keyword_parameters = module.params.get('keyword_parameters') + verbose = module.params.get('verbose') + keyword_parameters_string = None + if keyword_parameters is not None: + keyword_parameters_string = ','.join(f"{key}={value}" for key, value in keyword_parameters.items()) device = device_type if device_type is not None else device_number kwargs = {} + start_errmsg = ['ERROR'] + stop_errmsg = ['NOT ACTIVE'] + display_errmsg = ['NOT ACTIVE'] + modify_errmsg = ['REJECTED', 'NOT ACTIVE'] + cancel_errmsg = ['NOT ACTIVE'] + force_errmsg = ['NOT ACTIVE'] + err_msg = [] # Validations if job_account and len(job_account) > 55: @@ -378,7 +464,7 @@ def run_module(): msg="Invalid device_number.", changed=False ) - if subsystem_name and len(job_account) > 4: + if subsystem_name and len(subsystem_name) > 4: module.fail_json( msg="The subsystem_name must be 1 - 4 characters.", changed=False @@ -397,7 +483,20 @@ def run_module(): args = [] cmd = '' started_task_name = "" - if operation != 'start': + if operation != 'started': + if job_name is not None: + started_task_name = job_name + if identifier is not None: + started_task_name = started_task_name + "." + identifier + else: + module.fail_json( + msg="job_name is missing which is mandatory.", + changed=False + ) + execute_display_before = False + execute_display_after = False + if operation == 'started': + execute_display_after = True if job_name is not None: started_task_name = job_name elif member is not None: @@ -406,61 +505,94 @@ def run_module(): started_task_name = started_task_name + "." + identifier else: module.fail_json( - msg="one of job_name, member_name or identifier is needed but all are missing.", + msg="member_name is missing which is mandatory.", changed=False ) - if operation == 'start': - # member name is mandatory - if member is None or member.strip() == "": + err_msg = start_errmsg + if member is None: module.fail_json( msg="member_name is missing which is mandatory.", changed=False ) - cmd = prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters) + if job_name is not None and identifier is not None: + module.fail_json( + msg="job_name and identifier_name are mutually exclusive while starting a started task.", + changed=False + ) + cmd = prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters_string) elif operation == 'display': + err_msg = display_errmsg cmd = 'd a,' + started_task_name - elif operation == 'stop': + elif operation == 'stopped': + execute_display_before = True + err_msg = stop_errmsg cmd = 'p ' + started_task_name - elif operation == 'cancel': + if asid: + cmd = cmd + ',a=' + asid + elif operation == 'cancelled': + execute_display_before = True + err_msg = cancel_errmsg cmd = 'c ' + started_task_name if asid: cmd = cmd + ',a=' + asid - elif operation == 'force': + elif operation == 'forced': + execute_display_before = True + err_msg = force_errmsg cmd = 'force ' + started_task_name if asid: cmd = cmd + ',a=' + asid - elif operation == 'modify': + elif operation == 'modified': + execute_display_after = True + err_msg = modify_errmsg cmd = 'f ' + started_task_name + ',' + parameters changed = False stdout = "" stderr = "" - rc, out, err = execute_command(cmd, timeout_s=wait_s, *args, **kwargs) - if "ERROR" in out or err != "": + rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, execute_display_after, timeout_s=wait_s, *args, **kwargs) + logs = fetch_logs(cmd.upper()) # it will display both start/display logs + logs_str = "\n".join(logs) + if any(msg in out for msg in err_msg) or any(msg in logs_str for msg in err_msg) or err != "": changed = False stdout = out stderr = err if err == "" or err is None: stderr = out + stdout = "" else: changed = True stdout = out stderr = err + if operation == 'display': + task_params = extract_keys(out) result = dict() if module.check_mode: module.exit_json(**result) - - result = dict( - changed=changed, - cmd=cmd, - remote_cmd=cmd, - rc=rc, - stdout=stdout, - stderr=stderr, - stdout_lines=stdout.split('\n'), - stderr_lines=stderr.split('\n'), - ) + + if verbose: + result = dict( + changed=changed, + cmd=cmd, + task=task_params, + rc=rc, + verbose_output=logs_str, + stdout=stdout, + stderr=stderr, + stdout_lines=stdout.split('\n'), + stderr_lines=stderr.split('\n'), + ) + else: + result = dict( + changed=changed, + cmd=cmd, + task=task_params, + rc=rc, + stdout=stdout, + stderr=stderr, + stdout_lines=stdout.split('\n'), + stderr_lines=stderr.split('\n'), + ) module.exit_json(**result) diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 5529ff4f62..486ca72a0e 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -29,14 +29,20 @@ //STDPARM DD * SH sleep 600 /*""" +PROC_JCL_CONTENT="""//TEST PROC TIME=6 +//STEP1 EXEC PGM=BPXBATCH +//STDOUT DD SYSOUT=* +//STDERR DD SYSOUT=* +//STDPARM DD *,SYMBOLS=EXECSYS +SH sleep &TIME +/*""" def test_start_task_with_invalid_member(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - operation="start", + state="started", member="SAMPLETASK" - ) - + ) for result in start_results.contacted.values(): print(result) assert result.get("changed") is False @@ -46,10 +52,10 @@ def test_start_task_with_invalid_member(ansible_zos_module): def test_start_task_with_invalid_identifier(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - operation="start", + state="started", member="SAMPLE", identifier="$HELLO" - ) + ) for result in start_results.contacted.values(): print(result) @@ -61,10 +67,10 @@ def test_start_task_with_invalid_jobaccount(ansible_zos_module): hosts = ansible_zos_module job_account = "(T043JM,JM00,1,0,0,This is the invalid job account information to test negative scenario)" start_results = hosts.all.zos_started_task( - operation="start", + state="started", member="SAMPLE", job_account=job_account - ) + ) for result in start_results.contacted.values(): print(result) @@ -75,10 +81,10 @@ def test_start_task_with_invalid_jobaccount(ansible_zos_module): def test_start_task_with_invalid_devicenum(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - operation="start", + state="started", member="SAMPLE", device_number="0870" - ) + ) for result in start_results.contacted.values(): print(result) @@ -90,6 +96,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): try: hosts = ansible_zos_module data_set_name = get_tmp_ds_name() + print(data_set_name) temp_path = get_random_file_name(dir=TMP_DIRECTORY) hosts.all.file(path=temp_path, state="directory") hosts.all.shell( @@ -101,12 +108,12 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): ) hosts.all.shell( - cmd="dcp {0} \"//'{1}(SAMPLE)'\"".format(data_set_name, PROC_PDS) + cmd="dcp {0} '{1}(SAMPLE)'".format(data_set_name, PROC_PDS) ) start_results = hosts.all.zos_started_task( - operation="start", - member="SAMPLE" + state="started", + member="SAMPLE" ) for result in start_results.contacted.values(): @@ -116,8 +123,8 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("stderr") == "" stop_results = hosts.all.zos_started_task( - operation="cancel", - task_name="SAMPLE" + state="cancelled", + task_name="SAMPLE" ) for result in stop_results.contacted.values(): @@ -125,10 +132,12 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" - + + job_account = "(T043JM,JM00,1,0,0,)" start_results = hosts.all.zos_started_task( - operation="start", - member="SAMPLE" + state="started", + member="SAMPLE", + job_account=job_account ) for result in start_results.contacted.values(): @@ -138,8 +147,8 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("stderr") == "" display_result = hosts.all.zos_started_task( - operation="display", - task_name="SAMPLE" + state="display", + task_name="SAMPLE" ) for result in display_result.contacted.values(): print(result) @@ -150,9 +159,9 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) stop_results = hosts.all.zos_started_task( - operation="cancel", - task_name="SAMPLE", - asid=asid_val + state="cancelled", + task_name="SAMPLE", + asid=asid_val ) for result in stop_results.contacted.values(): @@ -190,9 +199,99 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): ) start_results = hosts.all.zos_started_task( - operation="start", - member="SAMPLE", - job_name="TESTTSK" + state="started", + member="SAMPLE", + job_name="TESTTSK" + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + stop_results = hosts.all.zos_started_task( + state="cancelled", + task_name="TESTTSK" + ) + + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + finally: + hosts.all.file(path=temp_path, state="absent") + hosts.all.shell( + cmd="drm {0}".format(data_set_name) + ) + hosts.all.shell( + cmd="mrm '{0}(SAMPLE)'".format(PROC_PDS) + ) + +def test_stop_and_modify_with_vlf_task(ansible_zos_module): + hosts = ansible_zos_module + + stop_results = hosts.all.zos_started_task( + state="stopped", + task_name="vlf" + ) + + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + start_results = hosts.all.zos_started_task( + state="started", + member="vlf", + subsystem_name="mstr" + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + modify_results = hosts.all.zos_started_task( + state="modified", + task_name="vlf", + parameters="replace,nn=00" + ) + + for result in modify_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + +def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): + try: + hosts = ansible_zos_module + data_set_name = get_tmp_ds_name() + temp_path = get_random_file_name(dir=TMP_DIRECTORY) + hosts.all.file(path=temp_path, state="directory") + + hosts.all.shell( + cmd="echo {0} > {1}/SAMPLE".format(quote(PROC_JCL_CONTENT), temp_path) + ) + + hosts.all.shell( + cmd="dcp {0}/SAMPLE {1}".format(temp_path, data_set_name) + ) + + hosts.all.shell( + cmd="dcp {0} \"//'{1}(SAMPLE)'\"".format(data_set_name, PROC_PDS) + ) + + start_results = hosts.all.zos_started_task( + state="started", + member="SAMPLE", + job_name="SPROC" ) for result in start_results.contacted.values(): @@ -202,8 +301,8 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): assert result.get("stderr") == "" stop_results = hosts.all.zos_started_task( - operation="cancel", - task_name="TESTTSK" + state="cancelled", + task_name="SPROC" ) for result in stop_results.contacted.values(): From fd063b706d09313668a1647e8ce55061074a701d Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Sat, 30 Aug 2025 22:19:08 +0530 Subject: [PATCH 08/36] update parameters --- plugins/modules/zos_started_task.py | 36 +++++++++++++++++-- .../modules/test_zos_started_task_func.py | 21 ++++++----- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 9b1616b8c8..21bafd7470 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -130,6 +130,14 @@ required: false type: bool default: false + wait_time_s: + required: false + default: 5 + type: int + description: + - Option I(wait_time_s) is the total time that module + L(zos_started_tak,./zos_started_task.html) will wait for a submitted task. The time begins when the module is executed + on the managed node. """ EXAMPLES = r""" - name: Start a started task using member name. @@ -228,7 +236,7 @@ def prepare_start_command(member, identifier, job_name, job_account, device, vol def extract_keys(stdout): - # keys = {'A': 'ASID', 'CT': 'CPU_Time', 'ET': 'Elapsed_Time', 'WUID': 'WUID', 'USERID': 'USERID', 'P': 'Priority'} + keys = {'A': 'ASID', 'CT': 'CPU_Time', 'ET': 'Elapsed_Time', 'WUID': 'WUID', 'USERID': 'USERID', 'P': 'Priority'} # params = {} # for key in keys: # parm = re.search(rf"{key}=([^\s]+)", stdout) @@ -252,10 +260,14 @@ def extract_keys(stdout): } for match in kv_pattern.finditer(line): key, value = match.groups() + if key in keys: + key = keys[key] current_task["DETAILS"][key] = value elif current_task: for match in kv_pattern.finditer(line): key, value = match.groups() + if key in keys: + key = keys[key] current_task["DETAILS"][key] = value if current_task: tasks.append(current_task) @@ -345,6 +357,11 @@ def run_module(): 'verbose': { 'type': 'bool', 'required': False + }, + 'wait_time_s': { + 'type': 'int', + 'required': False, + 'default': 5 } }, mutually_exclusive=[ @@ -412,6 +429,10 @@ def run_module(): 'verbose': { 'arg_type': 'bool', 'required': False + }, + 'wait_time_s': { + 'arg_type': 'int', + 'required': False } } @@ -437,10 +458,21 @@ def run_module(): subsystem_name = module.params.get('subsystem_name') reus_asid = module.params.get('reus_asid') keyword_parameters = module.params.get('keyword_parameters') + wait_time_s = module.params.get('wait_time_s') verbose = module.params.get('verbose') keyword_parameters_string = None if keyword_parameters is not None: - keyword_parameters_string = ','.join(f"{key}={value}" for key, value in keyword_parameters.items()) + # keyword_parameters_string = ','.join(f"{key}={value}" for key, value in keyword_parameters.items()) + for key, value in keyword_parameters.items(): + key_len = len(key) + value_len = len(value) + if key_len > 44 or value_len > 44 or key_len + value_len > 65: + module.fail_json( + msg="The length of a keyword=option is exceeding 66 characters or length of an individual value is exceeding 44 characters. key:{0}, value:{1}".format(key, value), + changed=False + ) + else: + keyword_parameters_string = ','.join(f"{key}={value}") device = device_type if device_type is not None else device_number kwargs = {} start_errmsg = ['ERROR'] diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 486ca72a0e..f0f1785bf4 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -29,13 +29,15 @@ //STDPARM DD * SH sleep 600 /*""" -PROC_JCL_CONTENT="""//TEST PROC TIME=6 -//STEP1 EXEC PGM=BPXBATCH +PROC_JCL_CONTENT="""//TESTERS PROC +//TEST JOB MSGCLASS=A,NOTIFY=&SYSUID +//STEP1 EXEC PGM=BPXBATCH,PARM='SH' //STDOUT DD SYSOUT=* //STDERR DD SYSOUT=* //STDPARM DD *,SYMBOLS=EXECSYS -SH sleep &TIME -/*""" +SH sleep 60 +/* +//PEND""" def test_start_task_with_invalid_member(ansible_zos_module): hosts = ansible_zos_module @@ -277,7 +279,7 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): hosts.all.file(path=temp_path, state="directory") hosts.all.shell( - cmd="echo {0} > {1}/SAMPLE".format(quote(PROC_JCL_CONTENT), temp_path) + cmd="echo {0} > {1}/SAMPLE".format(quote(TASK_JCL_CONTENT), temp_path) ) hosts.all.shell( @@ -285,13 +287,14 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): ) hosts.all.shell( - cmd="dcp {0} \"//'{1}(SAMPLE)'\"".format(data_set_name, PROC_PDS) + cmd="dcp {0} \"//'{1}(SAMPLE2)'\"".format(data_set_name, PROC_PDS) ) start_results = hosts.all.zos_started_task( state="started", - member="SAMPLE", - job_name="SPROC" + member="SAMPLE2", + job_name="SPROC", + verbose=True ) for result in start_results.contacted.values(): @@ -317,5 +320,5 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): cmd="drm {0}".format(data_set_name) ) hosts.all.shell( - cmd="mrm '{0}(SAMPLE)'".format(PROC_PDS) + cmd="mrm '{0}(SAMPLE2)'".format(PROC_PDS) ) \ No newline at end of file From 2d2877b61da716a8bd05d3956aa6109525cb7a86 Mon Sep 17 00:00:00 2001 From: Fernando Flores Date: Sun, 7 Sep 2025 08:04:48 -0600 Subject: [PATCH 09/36] Added RST for started task --- docs/source/modules/zos_started_task.rst | 164 +++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 docs/source/modules/zos_started_task.rst diff --git a/docs/source/modules/zos_started_task.rst b/docs/source/modules/zos_started_task.rst new file mode 100644 index 0000000000..e7d97f8155 --- /dev/null +++ b/docs/source/modules/zos_started_task.rst @@ -0,0 +1,164 @@ + +:github_url: https://github.com/ansible-collections/ibm_zos_core/blob/dev/plugins/modules/zos_started_task.py + +.. _zos_started_task_module: + + +zos_started_task -- Perform operations on started tasks. +======================================================== + + + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- start, display, modify, cancel, force and stop a started task + + + + + +Parameters +---------- + + +asid + *asid* is a unique address space identifier which gets assigned to each running started task. + + | **required**: False + | **type**: str + + +device_type + *device_type* is the type of the output device (if any) associated with the task. + + | **required**: False + | **type**: str + + +device_number + *device_number* is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. + + | **required**: False + | **type**: str + + +identifier_name + *identifier_name* is the name that identifies the task to be started. This name can be up to 8 characters long. The first character must be alphabetical. + + | **required**: False + | **type**: str + + +job_account + *job_account* specifies accounting data in the JCL JOB statement for the started task. If the source JCL was a job and has already accounting data, the value that is specified on this parameter overrides the accounting data in the source JCL. + + | **required**: False + | **type**: str + + +job_name + *job_name* is a name which should be assigned to a started task while starting it. If job_name is not specified, then member_name is used as job_name. + + | **required**: False + | **type**: str + + +keyword_parameters + Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. + + | **required**: False + | **type**: str + + +member_name + *member_name* is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. + + | **required**: False + | **type**: str + + +operation + The started task operation which needs to be performed. + + If *operation=start* and the data set does not exist on the managed node, no action taken, module completes successfully with *changed=False*. + + + | **required**: True + | **type**: str + | **choices**: start, stop, modify, display, force, cancel + + +parameters + Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks + + | **required**: False + | **type**: str + + +reus_asid + When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + + | **required**: False + | **type**: str + | **choices**: YES, NO + + +subsystem_name + The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. + + | **required**: False + | **type**: str + + +volume_serial + If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. + + | **required**: False + | **type**: str + + +verbose + Return System logs that describe the task's execution. + + | **required**: False + | **type**: bool + | **default**: False + + +wait_time_s + Option *wait_time_s* is the total time that module `zos_started_tak <./zos_started_task.html>`_ will wait for a submitted task. The time begins when the module is executed on the managed node. + + | **required**: False + | **type**: int + | **default**: 5 + + + + + + +Examples +-------- + +.. code-block:: yaml+jinja + + + - name: Start a started task using member name. + zos_started_task: + member: "PROCAPP" + operation: "start" + + + + + + + + + + From 08cdd4cc9b7d57a186fe43c9cfb4a0fc8fdcf0a0 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Tue, 9 Sep 2025 00:36:34 +0530 Subject: [PATCH 10/36] Features update --- plugins/modules/zos_started_task.py | 1220 ++++++++++++----- .../modules/test_zos_started_task_func.py | 627 ++++++++- 2 files changed, 1421 insertions(+), 426 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 21bafd7470..9b8ebd9d2e 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -27,103 +27,274 @@ - start, display, modify, cancel, force and stop a started task options: - asid: + start_task: description: - - I(asid) is a unique address space identifier which gets assigned to each running started task. + - The start operation of the started task. required: false - type: str - device_type: + type: dict + suboptions: + device_type: + description: + - I(device_type) is the type of the output device (if any) associated with the task. + required: false + type: str + device_number: + description: + - I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. + A slash (/) must precede a 4-digit number but is not before a 3-digit number. + required: false + type: str + identifier_name: + description: + - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. + The first character must be alphabetical. + required: false + type: str + aliases: + - identifier + job_account: + description: + - I(job_account) specifies accounting data in the JCL JOB statement for the started task. + If the source JCL was a job and has already accounting data, the value that is specified on this parameter + overrides the accounting data in the source JCL. + required: false + type: str + job_name: + description: + - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, + then member_name is used as job_name. + required: false + type: str + aliases: + - job + - task + - task_name + keyword_parameters: + description: + - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. + The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than + 44 characters in length. + required: false + type: dict + member_name: + description: + - I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL + for the task to be started. The member can be either a job or a cataloged procedure. + required: false + type: str + aliases: + - member + parameters: + description: + - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks + required: false + type: list + elements: str + reus_asid: + description: + - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, + a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified + on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + required: false + type: str + choices: + - 'YES' + - 'NO' + subsystem: + description: + - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, + which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. + required: false + type: str + volume_serial: + description: + - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. + required: false + type: str + display_task: description: - - I(device_type) is the type of the output device (if any) associated with the task. + - The display operation of the started task. required: false - type: str - device_number: + type: dict + suboptions: + identifier_name: + description: + - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. + The first character must be alphabetical. + required: false + type: str + aliases: + - identifier + job_name: + description: + - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, + then member_name is used as job_name. + required: false + type: str + aliases: + - job + - task + - task_name + modify_task: description: - - I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. - A slash (/) must precede a 4-digit number but is not before a 3-digit number. + - The modify operation of the started task. required: false - type: str - identifier_name: + type: dict + suboptions: + identifier_name: + description: + - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. + The first character must be alphabetical. + required: false + type: str + aliases: + - identifier + job_name: + description: + - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, + then member_name is used as job_name. + required: false + type: str + aliases: + - job + - task + - task_name + parameters: + description: + - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks + required: false + type: list + elements: str + cancel_task: description: - - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. - The first character must be alphabetical. + - The cancel operation of the started task. required: false - type: str - aliases: - - identifier - job_account: + type: dict + suboptions: + armrestart: + description: + - I(armrestart) indicates to restart a started task automatically after the cancel completes. + required: false + type: bool + asid: + description: + - I(asid) is a unique address space identifier which gets assigned to each running started task. + required: false + type: str + dump: + description: + - I(dump) indicates to take dump before ending a started task. + required: false + type: bool + identifier_name: + description: + - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. + The first character must be alphabetical. + required: false + type: str + aliases: + - identifier + job_name: + description: + - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, + then member_name is used as job_name. + required: false + type: str + aliases: + - job + - task + - task_name + userid: + description: + - I(userid) is the user ID of the time-sharing user you want to cancel. + required: false + type: str + force_task: description: - - I(job_account) specifies accounting data in the JCL JOB statement for the started task. - If the source JCL was a job and has already accounting data, the value that is specified on this parameter - overrides the accounting data in the source JCL. + - The force operation of the started task. required: false - type: str - job_name: + type: dict + suboptions: + arm: + description: + - I(arm) indicates to execute normal task termination routines without causing address space destruction. + required: false + type: bool + armrestart: + description: + - I(armrestart) indicates to restart a started task automatically after the cancel completes. + required: false + type: bool + asid: + description: + - I(asid) is a unique address space identifier which gets assigned to each running started task. + required: false + type: str + identifier_name: + description: + - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. + The first character must be alphabetical. + required: false + type: str + aliases: + - identifier + job_name: + description: + - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, + then member_name is used as job_name. + required: false + type: str + aliases: + - job + - task + - task_name + retry: + description: + - I(retry) is applicable for only FORCE TCB. + required: false + type: str + choices: + - 'YES' + - 'NO' + tcb_address: + description: + - I(tcb_address) is a 6-digit hexadecimal TCB address of the task to terminate. + required: false + type: str + userid: + description: + - I(userid) is the user ID of the time-sharing user you want to cancel. + required: false + type: str + stop_task: description: - - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, - then member_name is used as job_name. + - The stop operation of the started task. required: false - type: str - aliases: - - job - - task - - task_name - keyword_parameters: - description: - - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. - The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than - 44 characters in length. - required: false - type: str - member_name: - description: - - I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL - for the task to be started. The member can be either a job or a cataloged procedure. - required: false - type: str - aliases: - - member - operation: - description: - - The started task operation which needs to be performed. - - > - If I(operation=start) and the data set does not exist on the managed node, - no action taken, module completes successfully with I(changed=False). - required: true - type: str - choices: - - start - - stop - - modify - - display - - force - - cancel - parameters: - description: - - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks - required: false - type: str - reus_asid: - description: - - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, - a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified - on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. - required: false - type: str - choices: - - 'YES' - - 'NO' - subsystem_name: - description: - - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, - which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. - required: false - type: str - volume_serial: - description: - - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. - required: false - type: str + type: dict + suboptions: + asid: + description: + - I(asid) is a unique address space identifier which gets assigned to each running started task. + required: false + type: str + identifier_name: + description: + - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. + The first character must be alphabetical. + required: false + type: str + aliases: + - identifier + job_name: + description: + - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, + then member_name is used as job_name. + required: false + type: str + aliases: + - job + - task + - task_name verbose: description: - Return System logs that describe the task's execution. @@ -132,12 +303,11 @@ default: false wait_time_s: required: false - default: 5 + default: 0 type: int description: - - Option I(wait_time_s) is the total time that module - L(zos_started_tak,./zos_started_task.html) will wait for a submitted task. The time begins when the module is executed - on the managed node. + - Option I(wait_time_s) is the the maximum amount of time, in centiseconds (0.01s), to wait for a response after submitting + the console command. Default value of 0 means to wait the default amount of time supported by the opercmd utility. """ EXAMPLES = r""" - name: Start a started task using member name. @@ -164,7 +334,7 @@ ) try: - from zoautil_py import opercmd,zsystem + from zoautil_py import opercmd, zsystem except ImportError: zoau_exceptions = ZOAUImportError(traceback.format_exc()) @@ -190,15 +360,15 @@ def execute_command(operator_cmd, started_task_name, execute_display_before=Fals Returns ------- - OperatorQueryResult - The result of the command. + tuple + Tuple containing the RC, standard out, standard err of the + query script and started task parameters. """ task_params = {} # as of ZOAU v1.3.0, timeout is measured in centiseconds, therefore: timeout_c = 100 * timeout_s if execute_display_before: task_params = execute_display_command(started_task_name, timeout_c) - response = opercmd.execute(operator_cmd, timeout_c, *args, **kwargs) if execute_display_after: @@ -209,40 +379,392 @@ def execute_command(operator_cmd, started_task_name, execute_display_before=Fals stderr = response.stderr_response return rc, stdout, stderr, task_params -def execute_display_command(started_task_name, timeout_c): - cmd = "d a,"+started_task_name - display_response = opercmd.execute(cmd, timeout_c) + +def execute_display_command(started_task_name, timeout_s): + """Execute operator display command. + + Parameters + ---------- + started_task_name : str + The name of started task. + timeout_s : int + Timeout to wait for the command execution, measured in centiseconds. + + Returns + ------- + list + List contains extracted parameters from display command output of started task + """ + cmd = "d a," + started_task_name + display_response = opercmd.execute(cmd, timeout_s) task_params = [] if display_response.rc == 0 and display_response.stderr_response == "": task_params = extract_keys(display_response.stdout_response) return task_params -def prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters): +def validate_and_prepare_start_command(module, start_parms): + """Validates parameters and creates start command + + Parameters + ---------- + start_parms : dict + The started task start command parameters. + + Returns + ------- + started_task_name + The name of started task. + cmd + The start command in string format. + """ + member = start_parms.get('member_name') + identifier = start_parms.get('identifier_name') + job_name = start_parms.get('job_name') + job_account = start_parms.get('job_account') + parameters = start_parms.get('parameters') or [] + device_type = start_parms.get('device_type') or "" + device_number = start_parms.get('device_number') or "" + volume_serial = start_parms.get('volume_serial') or "" + subsystem_name = start_parms.get('subsystem') + reus_asid = start_parms.get('reus_asid') + keyword_parameters = start_parms.get('keyword_parameters') + keyword_parameters_string = "" + device = device_type if device_type else device_number + # Validations + if device_number and device_type: + module.fail_json( + rc=5, + msg="device_number and device_type are mutually exclusive.", + changed=False + ) + if job_account and len(job_account) > 55: + module.fail_json( + rc=5, + msg="job_account value should not exceed 55 characters.", + changed=False + ) + if device_number: + devnum_len = len(device_number) + if devnum_len not in (3, 5) or (devnum_len == 5 and not device_number.startswith("/")): + module.fail_json( + rc=5, + msg="Invalid device_number.", + changed=False + ) + if subsystem_name and len(subsystem_name) > 4: + module.fail_json( + rc=5, + msg="The subsystem_name must be 1 - 4 characters.", + changed=False + ) + if keyword_parameters: + for key, value in keyword_parameters.items(): + key_len = len(key) + value_len = len(value) + if key_len > 44 or value_len > 44 or key_len + value_len > 65: + module.fail_json( + rc=5, + msg="The length of a keyword=option is exceeding 66 characters or length of an individual value is exceeding 44 characters." + + "key:{0}, value:{1}".format(key, value), + changed=False + ) + else: + if keyword_parameters_string: + keyword_parameters_string = keyword_parameters_string + "," + f"{key}={value}" + else: + keyword_parameters_string = f"{key}={value}" + if job_name: + started_task_name = job_name + elif member: + started_task_name = member + if identifier: + started_task_name = started_task_name + "." + identifier + else: + module.fail_json( + rc=5, + msg="member_name is missing which is mandatory.", + changed=False + ) + if not member: + module.fail_json( + rc=5, + msg="member_name is missing which is mandatory.", + changed=False + ) + if job_name and identifier: + module.fail_json( + rc=5, + msg="job_name and identifier_name are mutually exclusive while starting a started task.", + changed=False + ) + parameters_updated = "" + if parameters: + if len(parameters) == 1: + parameters_updated = "'" + parameters[0] + "'" + else: + parameters_updated = f"({','.join(parameters)})" + cmd = 'S ' + member if identifier: - cmd = cmd + "." + identifier + "," + device + "," + volume_serial + "," + parameters + cmd = cmd + "." + identifier + if parameters: + cmd = cmd + "," + device + "," + volume_serial + "," + parameters_updated + elif volume_serial: + cmd = cmd + "," + device + "," + volume_serial + elif device: + cmd = cmd + "," + device if job_name: - cmd = cmd + ",jobname=" + job_name + cmd = cmd + ",JOBNAME=" + job_name if job_account: - cmd = cmd + ",jobacct=" + job_account + cmd = cmd + ",JOBACCT=" + job_account if subsystem_name: cmd = cmd + ",SUB=" + subsystem_name if reus_asid: cmd = cmd + ",REUSASID=" + reus_asid - if keyword_parameters: - cmd = cmd + "," + keyword_parameters - return cmd + if keyword_parameters_string: + cmd = cmd + "," + keyword_parameters_string + return started_task_name, cmd + + +def prepare_display_command(module, display_parms): + """Validates parameters and creates display command + + Parameters + ---------- + display_parms : dict + The started task display command parameters. + + Returns + ------- + started_task_name + The name of started task. + cmd + The display command in string format. + """ + identifier = display_parms.get('identifier_name') + job_name = display_parms.get('job_name') + started_task_name = "" + if job_name: + started_task_name = job_name + if identifier: + started_task_name = started_task_name + "." + identifier + else: + module.fail_json( + rc=5, + msg="job_name is missing which is mandatory.", + changed=False + ) + cmd = 'D A,' + started_task_name + return started_task_name, cmd + + +def prepare_stop_command(module, stop_parms): + """Validates parameters and creates stop command + + Parameters + ---------- + stop_parms : dict + The started task stop command parameters. + + Returns + ------- + started_task_name + The name of started task. + cmd + The stop command in string format. + """ + identifier = stop_parms.get('identifier_name') + job_name = stop_parms.get('job_name') + asid = stop_parms.get('asid') + started_task_name = "" + if job_name: + started_task_name = job_name + if identifier: + started_task_name = started_task_name + "." + identifier + else: + module.fail_json( + rc=5, + msg="job_name is missing which is mandatory.", + changed=False + ) + cmd = 'P ' + started_task_name + if asid: + cmd = cmd + ',A=' + asid + return started_task_name, cmd + + +def prepare_modify_command(module, modify_parms): + """Validates parameters and creates modify command + + Parameters + ---------- + modify_parms : dict + The started task modify command parameters. + + Returns + ------- + started_task_name + The name of started task. + cmd + The modify command in string format. + """ + identifier = modify_parms.get('identifier_name') + job_name = modify_parms.get('job_name') + parameters = modify_parms.get('parameters') + started_task_name = "" + if job_name: + started_task_name = job_name + if identifier: + started_task_name = started_task_name + "." + identifier + else: + module.fail_json( + rc=5, + msg="job_name is missing which is mandatory.", + changed=False + ) + if parameters is None: + module.fail_json( + rc=5, + msg="parameters are mandatory.", + changed=False + ) + cmd = 'F ' + started_task_name + "," + ",".join(parameters) + return started_task_name, cmd + + +def prepare_cancel_command(module, cancel_parms): + """Validates parameters and creates cancel command + + Parameters + ---------- + cancel_parms : dict + The started task modify command parameters. + + Returns + ------- + started_task_name + The name of started task. + cmd + The cancel command in string format. + """ + identifier = cancel_parms.get('identifier_name') + job_name = cancel_parms.get('job_name') + asid = cancel_parms.get('asid') + dump = cancel_parms.get('dump') + armrestart = cancel_parms.get('armrestart') + userid = cancel_parms.get('userid') + started_task_name = "" + if job_name: + started_task_name = job_name + if identifier: + started_task_name = started_task_name + "." + identifier + elif userid: + started_task_name = "U=" + userid + else: + module.fail_json( + rc=5, + msg="Both job_name and userid are missing, one of them is needed to cancel a task.", + changed=False + ) + if userid and armrestart: + module.fail_json( + rc=5, + msg="The ARMRESTART parameter is not valid with the U=userid parameter.", + changed=False + ) + cmd = 'C ' + started_task_name + if asid: + cmd = cmd + ',A=' + asid + if dump: + cmd = cmd + ',DUMP' + if armrestart: + cmd = cmd + ',ARMRESTART' + return started_task_name, cmd + + +def prepare_force_command(module, force_parms): + """Validates parameters and creates force command + + Parameters + ---------- + force_parms : dict + The started task force command parameters. + + Returns + ------- + started_task_name + The name of started task. + cmd + The force command in string format. + """ + identifier = force_parms.get('identifier_name') + job_name = force_parms.get('job_name') + asid = force_parms.get('asid') + arm = force_parms.get('arm') + armrestart = force_parms.get('armrestart') + userid = force_parms.get('userid') + tcb_address = force_parms.get('tcb_address') + retry = force_parms.get('retry') + started_task_name = "" + if tcb_address and len(tcb_address) != 6: + module.fail_json( + rc=5, + msg="The TCB address of the task should be exactly 6-digit hexadecimal.", + changed=False + ) + if retry and not tcb_address: + module.fail_json( + rc=5, + msg="The RETRY parameter is valid with the TCB parameter only.", + changed=False + ) + if userid and armrestart: + module.fail_json( + rc=5, + msg="The ARMRESTART parameter is not valid with the U=userid parameter.", + changed=False + ) + if job_name: + started_task_name = job_name + if identifier: + started_task_name = started_task_name + "." + identifier + elif userid: + started_task_name = "U=" + userid + else: + module.fail_json( + rc=5, + msg="Both job_name and userid are missing, one of them is needed to cancel a task.", + changed=False + ) + cmd = 'FORCE ' + started_task_name + if asid: + cmd = cmd + ',A=' + asid + if arm: + cmd = cmd + ',ARM' + if armrestart: + cmd = cmd + ',ARMRESTART' + if tcb_address: + cmd = cmd + ',TCB=' + tcb_address + if retry: + cmd = cmd + ',RETRY=' + retry + return started_task_name, cmd def extract_keys(stdout): + """Extracts keys and values from the given stdout + + Parameters + ---------- + stdout : string + The started task display command output + + Returns + ------- + tasks + The list of task parameters. + """ keys = {'A': 'ASID', 'CT': 'CPU_Time', 'ET': 'Elapsed_Time', 'WUID': 'WUID', 'USERID': 'USERID', 'P': 'Priority'} - # params = {} - # for key in keys: - # parm = re.search(rf"{key}=([^\s]+)", stdout) - # if parm: - # params[keys[key]] = parm.group(1) - # return params lines = stdout.strip().split('\n') tasks = [] current_task = None @@ -275,16 +797,103 @@ def extract_keys(stdout): def fetch_logs(command): + """Extracts keys and values from the given stdout + + Parameters + ---------- + command : string + The comand which need to be checked in system logs + + Returns + ------- + list + The list of logs from SYSLOG + """ stdout = zsystem.read_console(options='-t1') stdout_lines = stdout.splitlines() first = None + pattern = rf"\b{command}\b" for i, line in enumerate(stdout_lines): - if command in line: - if first is None: - first = i - if first is None: + if re.search(pattern, line, re.IGNORECASE): + first = i + if not first: return "" - return stdout_lines[first:] + logs = "\n".join(stdout_lines[first:]) + return logs + + +def parse_and_validate_args(params): + """Parse and validate input parameters + + Parameters + ---------- + params : dict + The dictionary which has input parameters. + + Returns + ------- + dict + The validated list of input parameters. + """ + start_args = dict( + device_type=dict(type="str", required=False), + device_number=dict(type="str", required=False), + identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), + job_account=dict(type="str", required=False), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), + keyword_parameters=dict(type="basic_dict", required=False), + member_name=dict(type="member_name", required=False, aliases=["member"]), + parameters=dict(type="list", elements="str", required=False), + reus_asid=dict(type="str", required=False), + subsystem=dict(type="str", required=False), + volume_serial=dict(type="str", required=False) + ) + display_args = dict( + identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]) + ) + modify_args = dict( + identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), + parameters=dict(type="list", elements="str", required=False) + ) + cancel_args = dict( + armrestart=dict(type="bool", required=False), + asid=dict(type="str", required=False), + dump=dict(type="bool", required=False), + identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), + userid=dict(type="str", required=False) + ) + force_args = dict( + arm=dict(type="bool", required=False), + armrestart=dict(type="bool", required=False), + asid=dict(type="str", required=False), + identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), + retry=dict(type="str", required=False), + tcb_address=dict(type="str", required=False), + userid=dict(type="str", required=False) + ) + stop_args = dict( + asid=dict(type="str", required=False), + identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]) + ) + module_args = dict( + start_task=dict(type="dict", required=False, options=start_args), + stop_task=dict(type="dict", required=False, options=stop_args), + display_task=dict(type="dict", required=False, options=display_args), + modify_task=dict(type="dict", required=False, options=modify_args), + cancel_task=dict(type="dict", required=False, options=cancel_args), + force_task=dict(type="dict", required=False, options=force_args), + verbose=dict(type="bool", required=False), + wait_time_s=dict(type="int", default=5) + ) + parser = better_arg_parser.BetterArgParser(module_args) + parsed_args = parser.parse_args(params) + return parsed_args + def run_module(): """Initialize the module. @@ -294,217 +903,102 @@ def run_module(): fail_json z/OS started task operation failed. """ + start_args = dict( + device_type=dict(type="str", required=False), + device_number=dict(type="str", required=False), + identifier_name=dict(type="str", required=False, aliases=["identifier"]), + job_account=dict(type="str", required=False), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), + keyword_parameters=dict(type="dict", required=False, no_log=False), + member_name=dict(type="str", required=False, aliases=["member"]), + parameters=dict(type="list", elements="str", required=False), + reus_asid=dict(type="str", required=False, choices=["YES", "NO"]), + subsystem=dict(type="str", required=False), + volume_serial=dict(type="str", required=False) + ) + display_args = dict( + identifier_name=dict(type="str", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]) + ) + modify_args = dict( + identifier_name=dict(type="str", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), + parameters=dict(type="list", elements="str", required=False) + ) + cancel_args = dict( + armrestart=dict(type="bool", required=False), + asid=dict(type="str", required=False), + dump=dict(type="bool", required=False), + identifier_name=dict(type="str", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), + userid=dict(type="str", required=False) + ) + force_args = dict( + arm=dict(type="bool", required=False), + armrestart=dict(type="bool", required=False), + asid=dict(type="str", required=False), + identifier_name=dict(type="str", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), + retry=dict(type="str", required=False, choices=["YES", "NO"]), + tcb_address=dict(type="str", required=False), + userid=dict(type="str", required=False) + ) + stop_args = dict( + asid=dict(type="str", required=False), + identifier_name=dict(type="str", required=False, aliases=["identifier"]), + job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]) + ) + + module_args = dict( + start_task=dict(type="dict", required=False, options=start_args), + stop_task=dict(type="dict", required=False, options=stop_args), + display_task=dict(type="dict", required=False, options=display_args), + modify_task=dict(type="dict", required=False, options=modify_args), + cancel_task=dict(type="dict", required=False, options=cancel_args), + force_task=dict(type="dict", required=False, options=force_args), + verbose=dict(type="bool", required=False, default=False), + wait_time_s=dict(type="int", default=5) + ) + module = AnsibleModule( - argument_spec={ - 'state': { - 'type': 'str', - 'required': True, - 'choices': ['started', 'stopped', 'modified', 'display', 'forced', 'cancelled'] - }, - 'member_name': { - 'type': 'str', - 'required': False, - 'aliases': ['member'] - }, - 'identifier_name': { - 'type': 'str', - 'required': False, - 'aliases': ['identifier'] - }, - 'job_name': { - 'type': 'str', - 'required': False, - 'aliases': ['job', 'task_name', 'task'] - }, - 'job_account': { - 'type': 'str', - 'required': False - }, - 'device_type': { - 'type': 'str', - 'required': False - }, - 'device_number': { # A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. - 'type': 'str', - 'required': False - }, - 'volume_serial': { - 'type': 'str', - 'required': False - }, - 'subsystem_name': { # The name must be 1 - 4 characters - 'type': 'str', - 'required': False - }, - 'reus_asid': { - 'type': 'str', - 'required': False, - 'choices': ['YES', 'NO'] - }, - 'parameters': { - 'type': 'str', - 'required': False - }, - 'keyword_parameters': { - 'type': 'dict', - 'required': False, - 'no_log': False - }, - 'asid': { - 'type': 'str', - 'required': False - }, - 'verbose': { - 'type': 'bool', - 'required': False - }, - 'wait_time_s': { - 'type': 'int', - 'required': False, - 'default': 5 - } - }, + argument_spec=module_args, mutually_exclusive=[ - ['device_number', 'device_type'] + ["start_task", "stop_task", "display_task", "modify_task", "cancel_task", "force_task"] ], supports_check_mode=True ) - args_def = { - 'state': { - 'arg_type': 'str', - 'required': True - }, - 'member_name': { - 'arg_type': 'member_name', - 'required': False, - 'aliases': ['member'] - }, - 'identifier_name': { - 'arg_type': 'identifier_name', - 'required': False, - 'aliases': ['identifier'] - }, - 'job_name': { - 'arg_type': 'str', - 'required': False, - 'aliases': ['job', 'task_name', 'task'] - }, - 'job_account': { - 'arg_type': 'str', - 'required': False - }, - 'device_type': { - 'arg_type': 'str', - 'required': False - }, - 'device_number': { - 'arg_type': 'str', - 'required': False - }, - 'volume_serial': { - 'arg_type': 'str', - 'required': False - }, - 'subsystem_name': { - 'arg_type': 'str', - 'required': False - }, - 'reus_asid': { - 'arg_type': 'str', - 'required': False - }, - 'parameters': { - 'arg_type': 'str', - 'required': False - }, - 'keyword_parameters': { - 'arg_type': 'basic_dict', - 'required': False - }, - 'asid': { - 'arg_type': 'str', - 'required': False - }, - 'verbose': { - 'arg_type': 'bool', - 'required': False - }, - 'wait_time_s': { - 'arg_type': 'int', - 'required': False - } - } - try: - parser = better_arg_parser.BetterArgParser(args_def) - parsed_args = parser.parse_args(module.params) - module.params = parsed_args + parms = parse_and_validate_args(module.params) except ValueError as err: module.fail_json( + rc=5, msg='Parameter verification failed.', stderr=str(err) ) - operation = module.params.get('state') - member = module.params.get('member_name') - identifier = module.params.get('identifier') - job_name = module.params.get('job_name') - job_account = module.params.get('job_account') - asid = module.params.get('asid') - parameters = module.params.get('parameters') - device_type = module.params.get('device_type') - device_number = module.params.get('device_number') - volume_serial = module.params.get('volume_serial') - subsystem_name = module.params.get('subsystem_name') - reus_asid = module.params.get('reus_asid') - keyword_parameters = module.params.get('keyword_parameters') - wait_time_s = module.params.get('wait_time_s') - verbose = module.params.get('verbose') - keyword_parameters_string = None - if keyword_parameters is not None: - # keyword_parameters_string = ','.join(f"{key}={value}" for key, value in keyword_parameters.items()) - for key, value in keyword_parameters.items(): - key_len = len(key) - value_len = len(value) - if key_len > 44 or value_len > 44 or key_len + value_len > 65: - module.fail_json( - msg="The length of a keyword=option is exceeding 66 characters or length of an individual value is exceeding 44 characters. key:{0}, value:{1}".format(key, value), - changed=False - ) - else: - keyword_parameters_string = ','.join(f"{key}={value}") - device = device_type if device_type is not None else device_number + wait_time_s = parms.get('wait_time_s') + verbose = parms.get('verbose') kwargs = {} - start_errmsg = ['ERROR'] + """ + Below error messages are used to detrmine if response has any error.When + response could have any of below error message has explained below. + + ERROR: Response contains this keyword when JCL contains syntax error. + INVALID PARAMETER: When invalid parameter passed in command line. + NOT ACTIVE: When started task with the given job name is not active + REJECTED: When modify command is not supported by respective started task. + NOT LOGGED ON: When invalid userid passed in command. + DUPLICATE NAME FOUND: When multiple started tasks exist with same name. + CANCELABLE: When force command used without using cancel command + """ + start_errmsg = ['ERROR', 'INVALID PARAMETER'] stop_errmsg = ['NOT ACTIVE'] display_errmsg = ['NOT ACTIVE'] modify_errmsg = ['REJECTED', 'NOT ACTIVE'] - cancel_errmsg = ['NOT ACTIVE'] - force_errmsg = ['NOT ACTIVE'] + cancel_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'DUPLICATE NAME FOUND'] + force_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'CANCELABLE', 'DUPLICATE NAME FOUND'] err_msg = [] - # Validations - if job_account and len(job_account) > 55: - module.fail_json( - msg="job_account value should not exceed 55 characters.", - changed=False - ) - if device_number: - devnum_len = len(device_number) - if devnum_len not in (3, 5) or (devnum_len == 5 and not device_number.startswith("/")): - module.fail_json( - msg="Invalid device_number.", - changed=False - ) - if subsystem_name and len(subsystem_name) > 4: - module.fail_json( - msg="The subsystem_name must be 1 - 4 characters.", - changed=False - ) - # keywaord arguments validation..... - - wait_s = 5 - use_wait_arg = False if zoau_version_checker.is_zoau_version_higher_than("1.2.4"): use_wait_arg = True @@ -513,77 +1007,48 @@ def run_module(): kwargs.update({"wait": True}) args = [] - cmd = '' - started_task_name = "" - if operation != 'started': - if job_name is not None: - started_task_name = job_name - if identifier is not None: - started_task_name = started_task_name + "." + identifier - else: - module.fail_json( - msg="job_name is missing which is mandatory.", - changed=False - ) + cmd = "" + execute_display_before = False execute_display_after = False - if operation == 'started': - execute_display_after = True - if job_name is not None: - started_task_name = job_name - elif member is not None: - started_task_name = member - if identifier is not None: - started_task_name = started_task_name + "." + identifier - else: - module.fail_json( - msg="member_name is missing which is mandatory.", - changed=False - ) + if parms.get('start_task'): err_msg = start_errmsg - if member is None: - module.fail_json( - msg="member_name is missing which is mandatory.", - changed=False - ) - if job_name is not None and identifier is not None: - module.fail_json( - msg="job_name and identifier_name are mutually exclusive while starting a started task.", - changed=False - ) - cmd = prepare_start_command(member, identifier, job_name, job_account, device, volume_serial, subsystem_name, reus_asid, parameters, keyword_parameters_string) - elif operation == 'display': + execute_display_after = True + started_task_name, cmd = validate_and_prepare_start_command(module, parms.get('start_task')) + elif parms.get('display_task'): err_msg = display_errmsg - cmd = 'd a,' + started_task_name - elif operation == 'stopped': + started_task_name, cmd = prepare_display_command(module, parms.get('display_task')) + elif parms.get('stop_task'): execute_display_before = True err_msg = stop_errmsg - cmd = 'p ' + started_task_name - if asid: - cmd = cmd + ',a=' + asid - elif operation == 'cancelled': + started_task_name, cmd = prepare_stop_command(module, parms.get('stop_task')) + elif parms.get('cancel_task'): execute_display_before = True err_msg = cancel_errmsg - cmd = 'c ' + started_task_name - if asid: - cmd = cmd + ',a=' + asid - elif operation == 'forced': + started_task_name, cmd = prepare_cancel_command(module, parms.get('cancel_task')) + elif parms.get('force_task'): execute_display_before = True err_msg = force_errmsg - cmd = 'force ' + started_task_name - if asid: - cmd = cmd + ',a=' + asid - elif operation == 'modified': + started_task_name, cmd = prepare_force_command(module, parms.get('force_task')) + elif parms.get('modify_task'): execute_display_after = True err_msg = modify_errmsg - cmd = 'f ' + started_task_name + ',' + parameters + started_task_name, cmd = prepare_modify_command(module, parms.get('modify_task')) changed = False stdout = "" stderr = "" - rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, execute_display_after, timeout_s=wait_s, *args, **kwargs) - logs = fetch_logs(cmd.upper()) # it will display both start/display logs - logs_str = "\n".join(logs) - if any(msg in out for msg in err_msg) or any(msg in logs_str for msg in err_msg) or err != "": + rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, execute_display_after, timeout_s=wait_time_s, *args, **kwargs) + isFailed = False + system_logs = "" + if err != "" or any(msg in out for msg in err_msg): + isFailed = True + if not isFailed or verbose: + system_logs = fetch_logs(cmd.upper()) + if any(msg in system_logs for msg in err_msg): + isFailed = True + if isFailed: + if rc == 0: + rc = 1 changed = False stdout = out stderr = err @@ -594,38 +1059,33 @@ def run_module(): changed = True stdout = out stderr = err - if operation == 'display': + if parms.get('display_task'): task_params = extract_keys(out) result = dict() if module.check_mode: module.exit_json(**result) + state = "" - if verbose: - result = dict( - changed=changed, - cmd=cmd, - task=task_params, - rc=rc, - verbose_output=logs_str, - stdout=stdout, - stderr=stderr, - stdout_lines=stdout.split('\n'), - stderr_lines=stderr.split('\n'), - ) - else: - result = dict( + result = dict( changed=changed, cmd=cmd, - task=task_params, + tasks=task_params, rc=rc, stdout=stdout, stderr=stderr, stdout_lines=stdout.split('\n'), stderr_lines=stderr.split('\n'), ) - + if verbose: + result["verbose_output"] = system_logs + if parms.get('display_task') or parms.get('modify_task'): + if len(task_params) > 0 and not isFailed: + result["state"] = "Active" + else: + result["state"] = "NotActive" + module.exit_json(**result) diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index f0f1785bf4..d678ed6ee7 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -39,11 +39,23 @@ /* //PEND""" +# Input arguments validation def test_start_task_with_invalid_member(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - state="started", - member="SAMPLETASK" + start_task={ + "member_name": "SAMTASK" + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLETASK" + } ) for result in start_results.contacted.values(): print(result) @@ -51,12 +63,28 @@ def test_start_task_with_invalid_member(ansible_zos_module): assert result.get("failed") is True assert result.get("stderr") is not None +def test_start_task_with_jobname_identifier(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLE", + "job_name": "SAMTASK", + "identifier": "TESTER" + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + def test_start_task_with_invalid_identifier(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - state="started", - member="SAMPLE", - identifier="$HELLO" + start_task={ + "member_name": "SAMPLE", + "identifier": "$HELLO" + } ) for result in start_results.contacted.values(): @@ -64,14 +92,27 @@ def test_start_task_with_invalid_identifier(ansible_zos_module): assert result.get("changed") is False assert result.get("failed") is True assert result.get("stderr") is not None + + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLE", + "identifier": "HELLO" + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "S SAMPLE.HELLO" def test_start_task_with_invalid_jobaccount(ansible_zos_module): hosts = ansible_zos_module job_account = "(T043JM,JM00,1,0,0,This is the invalid job account information to test negative scenario)" start_results = hosts.all.zos_started_task( - state="started", - member="SAMPLE", - job_account=job_account + start_task={ + "member_name": "SAMPLE", + "job_account": job_account + } ) for result in start_results.contacted.values(): @@ -83,22 +124,382 @@ def test_start_task_with_invalid_jobaccount(ansible_zos_module): def test_start_task_with_invalid_devicenum(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - state="started", - member="SAMPLE", - device_number="0870" + start_task={ + "member_name": "SAMPLE", + "device_number": "0870" + } + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + +def test_start_task_with_invalid_volumeserial(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLE", + "volume_serial": "12345A" + } + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "S SAMPLE,,12345A" + +def test_start_task_with_invalid_parameters(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLE", + "parameters": ["KEY1"] + } + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "S SAMPLE,,,'KEY1'" + + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLE", + "parameters": ["KEY1", "KEY2", "KEY3"], + "volume_serial": "12345" + } + ) + + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "S SAMPLE,,12345,(KEY1,KEY2,KEY3)" + +def test_start_task_with_devicenum_devicetype_negative(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLE", + "device_number": "/0870", + "device_type": "TEST" + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + + +def test_start_task_with_invalid_subsystem_negative(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + start_task={ + "member": "VLF", + "subsystem": "MSTRS" + } ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + +def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + start_task={ + "member": "VLF", + "keyword_parameters":{ + "key1key1key1key1key1key1key1key1": "value1value1value1value1value1value1" + } + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + start_results = hosts.all.zos_started_task( + start_task={ + "member": "VLF", + "keyword_parameters":{ + "key1key1key1key1key1key1key1key1key1key1key1key1": "value1" + } + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + start_results = hosts.all.zos_started_task( + start_task={ + "member": "VLF", + "keyword_parameters":{ + "KEY1": "VALUE1", + "KEY2": "VALUE2" + } + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == 'S VLF,KEY1=VALUE1,KEY2=VALUE2' + + +def test_start_task_using_nonexisting_devicenum_negative(ansible_zos_module): + hosts = ansible_zos_module + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLE", + "device_number": "/ABCD" + } + ) for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == 'S SAMPLE,/ABCD' + +def test_display_task_negative(ansible_zos_module): + hosts = ansible_zos_module + display_results = hosts.all.zos_started_task( + display_task={ + "identifier": "SAMPLE" + } + ) + for result in display_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + +def test_start_and_display_task_negative(ansible_zos_module): + hosts = ansible_zos_module + display_results = hosts.all.zos_started_task( + start_task={ + "member": "SAMPLE" + }, + display_task={ + "job": "SAMPLE" + } + ) + for result in display_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + +def test_stop_task_negative(ansible_zos_module): + hosts = ansible_zos_module + stop_results = hosts.all.zos_started_task( + stop_task={ + "identifier": "SAMPLE" + } + ) + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + + stop_results = hosts.all.zos_started_task( + stop_task={ + "job_name": "TESTER", + "identifier": "SAMPLE" + } + ) + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "P TESTER.SAMPLE" + +def test_modify_task_negative(ansible_zos_module): + hosts = ansible_zos_module + modify_results = hosts.all.zos_started_task( + modify_task={ + "identifier": "SAMPLE" + } + ) + for result in modify_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + + modify_results = hosts.all.zos_started_task( + modify_task={ + "job_name": "TESTER" + } + ) + for result in modify_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + + modify_results = hosts.all.zos_started_task( + modify_task={ + "job_name": "TESTER", + "identifier": "SAMPLE", + "parameters": ["REPLACE", "VX=10"] + } + ) + for result in modify_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "F TESTER.SAMPLE,REPLACE,VX=10" + +def test_cancel_task_negative(ansible_zos_module): + hosts = ansible_zos_module + cancel_results = hosts.all.zos_started_task( + cancel_task={ + "identifier": "SAMPLE" + } + ) + for result in cancel_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + + cancel_results = hosts.all.zos_started_task( + cancel_task={ + "job_name": "TESTER", + "identifier": "SAMPLE" + } + ) + for result in cancel_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "C TESTER.SAMPLE" + cancel_results = hosts.all.zos_started_task( + cancel_task={ + "userid": "OMVSTEST", + "asid": "0012", + "dump": True + }, + verbose=True + ) + for result in cancel_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "C U=OMVSTEST,A=0012,DUMP" + cancel_results = hosts.all.zos_started_task( + cancel_task={ + "userid": "OMVSADM", + "armrestart": True + } + ) + for result in cancel_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + +def test_force_task_negative(ansible_zos_module): + hosts = ansible_zos_module + force_results = hosts.all.zos_started_task( + force_task={ + "identifier": "SAMPLE" + } + ) + for result in force_results.contacted.values(): print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None + force_results = hosts.all.zos_started_task( + force_task={ + "job_name": "TESTER", + "identifier": "SAMPLE" + } + ) + for result in force_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "FORCE TESTER.SAMPLE" + force_results = hosts.all.zos_started_task( + force_task={ + "userid": "OMVSADM", + "armrestart": True + } + ) + for result in force_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + + force_results = hosts.all.zos_started_task( + force_task={ + "job_name": "TESTER", + "retry": "YES" + } + ) + for result in force_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + force_results = hosts.all.zos_started_task( + force_task={ + "job_name": "TESTER", + "tcb_address": "0006789", + "retry": "YES" + } + ) + for result in force_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("failed") is True + assert result.get("msg") is not None + force_results = hosts.all.zos_started_task( + force_task={ + "job_name": "TESTER", + "identifier": "SAMPLE", + "tcb_address": "000678", + "retry": "YES" + } + ) + for result in force_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "FORCE TESTER.SAMPLE,TCB=000678,RETRY=YES" + force_results = hosts.all.zos_started_task( + force_task={ + "userid": "OMVSTEST", + "tcb_address": "000678", + "retry": "YES" + }, + verbose=True + ) + for result in force_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "FORCE U=OMVSTEST,TCB=000678,RETRY=YES" + + def test_start_and_cancel_zos_started_task(ansible_zos_module): try: hosts = ansible_zos_module data_set_name = get_tmp_ds_name() - print(data_set_name) temp_path = get_random_file_name(dir=TMP_DIRECTORY) hosts.all.file(path=temp_path, state="directory") hosts.all.shell( @@ -114,8 +515,10 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): ) start_results = hosts.all.zos_started_task( - state="started", - member="SAMPLE" + start_task={ + "member_name": "SAMPLE" + }, + verbose=True ) for result in start_results.contacted.values(): @@ -123,10 +526,25 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is not None + + force_results = hosts.all.zos_started_task( + force_task={ + "task_name": "SAMPLE" + } + ) + for result in force_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert result.get("cmd") == "FORCE SAMPLE" + assert "CANCELABLE - ISSUE CANCEL BEFORE FORCE" in result.get("stderr") stop_results = hosts.all.zos_started_task( - state="cancelled", - task_name="SAMPLE" + cancel_task={ + "task_name": "SAMPLE" + } ) for result in stop_results.contacted.values(): @@ -134,12 +552,58 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is None + + # validate identifier + start_results = hosts.all.zos_started_task( + start_task={ + "member_name": "SAMPLE", + "identifier": "TESTER", + "reus_asid": "YES" + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is None + assert result.get("cmd") == "S SAMPLE.TESTER,REUSASID=YES" + + stop_results = hosts.all.zos_started_task( + cancel_task={ + "task_name": "SAMPLE" + } + ) + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is False + assert result.get("stderr") is not None + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is None + + stop_results = hosts.all.zos_started_task( + cancel_task={ + "task_name": "SAMPLE", + "identifier": "TESTER" + } + ) + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is None job_account = "(T043JM,JM00,1,0,0,)" start_results = hosts.all.zos_started_task( - state="started", - member="SAMPLE", - job_account=job_account + start_task={ + "member": "SAMPLE", + "job_account": job_account + } ) for result in start_results.contacted.values(): @@ -147,23 +611,31 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is None display_result = hosts.all.zos_started_task( - state="display", - task_name="SAMPLE" + display_task={ + "task_name": "SAMPLE" + } ) for result in display_result.contacted.values(): print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is None + display_output = list(display_result.contacted.values())[0].get("stdout") asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) stop_results = hosts.all.zos_started_task( - state="cancelled", - task_name="SAMPLE", - asid=asid_val + cancel_task={ + "task_name": "SAMPLE", + "asid": asid_val + }, + verbose=True ) for result in stop_results.contacted.values(): @@ -171,6 +643,8 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is not None finally: hosts.all.file(path=temp_path, state="absent") @@ -201,9 +675,10 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): ) start_results = hosts.all.zos_started_task( - state="started", - member="SAMPLE", - job_name="TESTTSK" + start_task={ + "member": "SAMPLE", + "job_name": "TESTTSK" + } ) for result in start_results.contacted.values(): @@ -213,8 +688,9 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): assert result.get("stderr") == "" stop_results = hosts.all.zos_started_task( - state="cancelled", - task_name="TESTTSK" + cancel_task={ + "task_name": "TESTTSK" + } ) for result in stop_results.contacted.values(): @@ -234,24 +710,55 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): def test_stop_and_modify_with_vlf_task(ansible_zos_module): hosts = ansible_zos_module - - stop_results = hosts.all.zos_started_task( - state="stopped", - task_name="vlf" + modify_results = hosts.all.zos_started_task( + modify_task={ + "task_name": "VLF", + "parameters": ["REPLACE" ,"NN=00"] + } + ) + for result in modify_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert result.get("cmd") == "F VLF,REPLACE,NN=00" + + display_result = hosts.all.zos_started_task( + display_task={ + "task_name": "VLF" + } ) + for result in display_result.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is None + + display_output = list(display_result.contacted.values())[0].get("stdout") + asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) + stop_results = hosts.all.zos_started_task( + stop_task={ + "task_name": "VLF", + "asid": asid_val + } + ) for result in stop_results.contacted.values(): print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert result.get("cmd") == f"P VLF,A={asid_val}" start_results = hosts.all.zos_started_task( - state="started", - member="vlf", - subsystem_name="mstr" + start_task={ + "member": "VLF", + "identifier": "TESTER", + "subsystem": "MSTR" + } ) - for result in start_results.contacted.values(): print(result) assert result.get("changed") is True @@ -259,17 +766,43 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): assert result.get("stderr") == "" modify_results = hosts.all.zos_started_task( - state="modified", - task_name="vlf", - parameters="replace,nn=00" + modify_task={ + "task_name": "VLF", + "identifier": "TESTER", + "parameters": ["REPLACE" ,"NN=00"] + } ) - for result in modify_results.contacted.values(): print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert result.get("cmd") == "F VLF.TESTER,REPLACE,NN=00" + stop_results = hosts.all.zos_started_task( + stop_task={ + "task_name": "VLF", + "identifier": "TESTER" + } + ) + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + + start_results = hosts.all.zos_started_task( + start_task={ + "member": "VLF", + "subsystem": "MSTR" + } + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): try: @@ -291,9 +824,10 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): ) start_results = hosts.all.zos_started_task( - state="started", - member="SAMPLE2", - job_name="SPROC", + start_task={ + "member": "SAMPLE2", + "job_name": "SPROC" + }, verbose=True ) @@ -304,8 +838,9 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): assert result.get("stderr") == "" stop_results = hosts.all.zos_started_task( - state="cancelled", - task_name="SPROC" + cancel_task={ + "task_name": "SPROC" + } ) for result in stop_results.contacted.values(): From 9c244a1f9ddcce5c7e3d3e68cd3dbbdc288b9851 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:44:50 +0530 Subject: [PATCH 11/36] Adding generic parameters support --- plugins/modules/zos_started_task.py | 973 +++++++++--------- .../modules/test_zos_started_task_func.py | 506 ++++----- 2 files changed, 717 insertions(+), 762 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 9b8ebd9d2e..8ef0f4a920 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -27,274 +27,134 @@ - start, display, modify, cancel, force and stop a started task options: - start_task: + arm: description: - - The start operation of the started task. + - I(arm) indicates to execute normal task termination routines without causing address space destruction. required: false - type: dict - suboptions: - device_type: - description: - - I(device_type) is the type of the output device (if any) associated with the task. - required: false - type: str - device_number: - description: - - I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. - A slash (/) must precede a 4-digit number but is not before a 3-digit number. - required: false - type: str - identifier_name: - description: - - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. - The first character must be alphabetical. - required: false - type: str - aliases: - - identifier - job_account: - description: - - I(job_account) specifies accounting data in the JCL JOB statement for the started task. - If the source JCL was a job and has already accounting data, the value that is specified on this parameter - overrides the accounting data in the source JCL. - required: false - type: str - job_name: - description: - - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, - then member_name is used as job_name. - required: false - type: str - aliases: - - job - - task - - task_name - keyword_parameters: - description: - - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. - The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than - 44 characters in length. - required: false - type: dict - member_name: - description: - - I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL - for the task to be started. The member can be either a job or a cataloged procedure. - required: false - type: str - aliases: - - member - parameters: - description: - - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks - required: false - type: list - elements: str - reus_asid: - description: - - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, - a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified - on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. - required: false - type: str - choices: - - 'YES' - - 'NO' - subsystem: - description: - - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, - which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. - required: false - type: str - volume_serial: - description: - - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. - required: false - type: str - display_task: + type: bool + armrestart: description: - - The display operation of the started task. + - I(armrestart) indicates to restart a started task automatically after the cancel completes. required: false - type: dict - suboptions: - identifier_name: - description: - - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. - The first character must be alphabetical. - required: false - type: str - aliases: - - identifier - job_name: - description: - - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, - then member_name is used as job_name. - required: false - type: str - aliases: - - job - - task - - task_name - modify_task: + type: bool + asid: description: - - The modify operation of the started task. + - I(asid) is a unique address space identifier which gets assigned to each running started task. required: false - type: dict - suboptions: - identifier_name: - description: - - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. - The first character must be alphabetical. - required: false - type: str - aliases: - - identifier - job_name: - description: - - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, - then member_name is used as job_name. - required: false - type: str - aliases: - - job - - task - - task_name - parameters: - description: - - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks - required: false - type: list - elements: str - cancel_task: + type: str + device_type: description: - - The cancel operation of the started task. + - I(device_type) is the type of the output device (if any) associated with the task. required: false - type: dict - suboptions: - armrestart: - description: - - I(armrestart) indicates to restart a started task automatically after the cancel completes. - required: false - type: bool - asid: - description: - - I(asid) is a unique address space identifier which gets assigned to each running started task. - required: false - type: str - dump: - description: - - I(dump) indicates to take dump before ending a started task. - required: false - type: bool - identifier_name: - description: - - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. - The first character must be alphabetical. - required: false - type: str - aliases: - - identifier - job_name: - description: - - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, - then member_name is used as job_name. - required: false - type: str - aliases: - - job - - task - - task_name - userid: - description: - - I(userid) is the user ID of the time-sharing user you want to cancel. - required: false - type: str - force_task: + type: str + device_number: description: - - The force operation of the started task. + - I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. + A slash (/) must precede a 4-digit number but is not before a 3-digit number. required: false - type: dict - suboptions: - arm: - description: - - I(arm) indicates to execute normal task termination routines without causing address space destruction. - required: false - type: bool - armrestart: - description: - - I(armrestart) indicates to restart a started task automatically after the cancel completes. - required: false - type: bool - asid: - description: - - I(asid) is a unique address space identifier which gets assigned to each running started task. - required: false - type: str - identifier_name: - description: - - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. - The first character must be alphabetical. - required: false - type: str - aliases: - - identifier - job_name: - description: - - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, - then member_name is used as job_name. - required: false - type: str - aliases: - - job - - task - - task_name - retry: - description: - - I(retry) is applicable for only FORCE TCB. - required: false - type: str - choices: - - 'YES' - - 'NO' - tcb_address: - description: - - I(tcb_address) is a 6-digit hexadecimal TCB address of the task to terminate. - required: false - type: str - userid: - description: - - I(userid) is the user ID of the time-sharing user you want to cancel. - required: false - type: str - stop_task: + type: str + dump: + description: + - I(dump) indicates to take dump before ending a started task. + required: false + type: bool + identifier_name: description: - - The stop operation of the started task. + - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. + The first character must be alphabetical. + required: false + type: str + aliases: + - identifier + job_account: + description: + - I(job_account) specifies accounting data in the JCL JOB statement for the started task. + If the source JCL was a job and has already accounting data, the value that is specified on this parameter + overrides the accounting data in the source JCL. + required: false + type: str + job_name: + description: + - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, + then member_name is used as job_name. + required: false + type: str + aliases: + - job + - task + - task_name + keyword_parameters: + description: + - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. + The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than + 44 characters in length. required: false type: dict - suboptions: - asid: - description: - - I(asid) is a unique address space identifier which gets assigned to each running started task. - required: false - type: str - identifier_name: - description: - - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. - The first character must be alphabetical. - required: false - type: str - aliases: - - identifier - job_name: - description: - - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, - then member_name is used as job_name. - required: false - type: str - aliases: - - job - - task - - task_name + member_name: + description: + - I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL + for the task to be started. The member can be either a job or a cataloged procedure. + required: false + type: str + aliases: + - member + parameters: + description: + - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks + required: false + type: list + elements: str + retry: + description: + - I(retry) is applicable for only FORCE TCB. + required: false + type: str + choices: + - 'YES' + - 'NO' + reus_asid: + description: + - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, + a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified + on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + required: false + type: str + choices: + - 'YES' + - 'NO' + state: + description: + - The final state desired for specified started task. + required: True + type: str + choices: + - started + - displayed + - modified + - cancelled + - stopped + - forced + subsystem: + description: + - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, + which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. + required: false + type: str + tcb_address: + description: + - I(tcb_address) is a 6-digit hexadecimal TCB address of the task to terminate. + required: false + type: str + volume_serial: + description: + - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. + required: false + type: str + userid: + description: + - I(userid) is the user ID of the time-sharing user you want to cancel. + required: false + type: str verbose: description: - Return System logs that describe the task's execution. @@ -306,7 +166,7 @@ default: 0 type: int description: - - Option I(wait_time_s) is the the maximum amount of time, in centiseconds (0.01s), to wait for a response after submitting + - Option I(wait_time_s) is the the maximum amount of time, in seconds, to wait for a response after submitting the console command. Default value of 0 means to wait the default amount of time supported by the opercmd utility. """ EXAMPLES = r""" @@ -317,12 +177,76 @@ """ RETURN = r""" +changed: + description: + True if the state was changed, otherwise False. + returned: always + type: bool +cmd: + description: Command executed via opercmd. + returned: changed + type: str + sample: S SAMPLE +msg: + description: Failure or skip message returned by the module. + returned: failure or skipped + type: str + sample: + File /u/user/file.txt is already missing on the system, skipping script +rc: + description: + - The return code is 0 when command executed successfully. + - The return code is 1 when opercmd throws any error. + - The return code is 5 when any parameter validation failed. + returned: changed + type: int + sample: 0 +state: + description: The final state of the started task, after execution.. + returned: changed + type: str + sample: S SAMPLE +tasks: + description: + The output information for a list of started tasks matching specified criteria. + If no started task is found then this will return empty. + returned: success + type: list + elements: dict + contains: + job_name: + description: + The name of the batch job. + type: str + sample: LINKJOB +stdout: + description: The STDOUT from the command, may be empty. + returned: changed + type: str + sample: ISF031I CONSOLE OMVS0000 ACTIVATED. +stderr: + description: The STDERR from the command, may be empty. + returned: changed + type: str + sample: An error has ocurred. +stdout_lines: + description: List of strings containing individual lines from STDOUT. + returned: changed + type: list + sample: ["Allocation to SYSEXEC completed."] +stderr_lines: + description: List of strings containing individual lines from STDERR. + returned: changed + type: list + sample: ["An error has ocurred"] """ from ansible.module_utils.basic import AnsibleModule import traceback import re +from datetime import datetime, timedelta +import re from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import ( better_arg_parser ) @@ -403,7 +327,7 @@ def execute_display_command(started_task_name, timeout_s): return task_params -def validate_and_prepare_start_command(module, start_parms): +def validate_and_prepare_start_command(module): """Validates parameters and creates start command Parameters @@ -418,17 +342,17 @@ def validate_and_prepare_start_command(module, start_parms): cmd The start command in string format. """ - member = start_parms.get('member_name') - identifier = start_parms.get('identifier_name') - job_name = start_parms.get('job_name') - job_account = start_parms.get('job_account') - parameters = start_parms.get('parameters') or [] - device_type = start_parms.get('device_type') or "" - device_number = start_parms.get('device_number') or "" - volume_serial = start_parms.get('volume_serial') or "" - subsystem_name = start_parms.get('subsystem') - reus_asid = start_parms.get('reus_asid') - keyword_parameters = start_parms.get('keyword_parameters') + member = module.params.get('member_name') + identifier = module.params.get('identifier_name') + job_name = module.params.get('job_name') + job_account = module.params.get('job_account') + parameters = module.params.get('parameters') or [] + device_type = module.params.get('device_type') or "" + device_number = module.params.get('device_number') or "" + volume_serial = module.params.get('volume_serial') or "" + subsystem_name = module.params.get('subsystem') + reus_asid = module.params.get('reus_asid') + keyword_parameters = module.params.get('keyword_parameters') keyword_parameters_string = "" device = device_type if device_type else device_number # Validations @@ -527,7 +451,7 @@ def validate_and_prepare_start_command(module, start_parms): return started_task_name, cmd -def prepare_display_command(module, display_parms): +def prepare_display_command(module): """Validates parameters and creates display command Parameters @@ -542,8 +466,8 @@ def prepare_display_command(module, display_parms): cmd The display command in string format. """ - identifier = display_parms.get('identifier_name') - job_name = display_parms.get('job_name') + identifier = module.params.get('identifier_name') + job_name = module.params.get('job_name') started_task_name = "" if job_name: started_task_name = job_name @@ -559,7 +483,7 @@ def prepare_display_command(module, display_parms): return started_task_name, cmd -def prepare_stop_command(module, stop_parms): +def prepare_stop_command(module): """Validates parameters and creates stop command Parameters @@ -574,9 +498,9 @@ def prepare_stop_command(module, stop_parms): cmd The stop command in string format. """ - identifier = stop_parms.get('identifier_name') - job_name = stop_parms.get('job_name') - asid = stop_parms.get('asid') + identifier = module.params.get('identifier_name') + job_name = module.params.get('job_name') + asid = module.params.get('asid') started_task_name = "" if job_name: started_task_name = job_name @@ -594,7 +518,7 @@ def prepare_stop_command(module, stop_parms): return started_task_name, cmd -def prepare_modify_command(module, modify_parms): +def prepare_modify_command(module): """Validates parameters and creates modify command Parameters @@ -609,9 +533,9 @@ def prepare_modify_command(module, modify_parms): cmd The modify command in string format. """ - identifier = modify_parms.get('identifier_name') - job_name = modify_parms.get('job_name') - parameters = modify_parms.get('parameters') + identifier = module.params.get('identifier_name') + job_name = module.params.get('job_name') + parameters = module.params.get('parameters') started_task_name = "" if job_name: started_task_name = job_name @@ -633,7 +557,7 @@ def prepare_modify_command(module, modify_parms): return started_task_name, cmd -def prepare_cancel_command(module, cancel_parms): +def prepare_cancel_command(module): """Validates parameters and creates cancel command Parameters @@ -648,12 +572,12 @@ def prepare_cancel_command(module, cancel_parms): cmd The cancel command in string format. """ - identifier = cancel_parms.get('identifier_name') - job_name = cancel_parms.get('job_name') - asid = cancel_parms.get('asid') - dump = cancel_parms.get('dump') - armrestart = cancel_parms.get('armrestart') - userid = cancel_parms.get('userid') + identifier = module.params.get('identifier_name') + job_name = module.params.get('job_name') + asid = module.params.get('asid') + dump = module.params.get('dump') + armrestart = module.params.get('armrestart') + userid = module.params.get('userid') started_task_name = "" if job_name: started_task_name = job_name @@ -683,12 +607,12 @@ def prepare_cancel_command(module, cancel_parms): return started_task_name, cmd -def prepare_force_command(module, force_parms): +def prepare_force_command(module): """Validates parameters and creates force command Parameters ---------- - force_parms : dict + module : dict The started task force command parameters. Returns @@ -698,14 +622,14 @@ def prepare_force_command(module, force_parms): cmd The force command in string format. """ - identifier = force_parms.get('identifier_name') - job_name = force_parms.get('job_name') - asid = force_parms.get('asid') - arm = force_parms.get('arm') - armrestart = force_parms.get('armrestart') - userid = force_parms.get('userid') - tcb_address = force_parms.get('tcb_address') - retry = force_parms.get('retry') + identifier = module.params.get('identifier_name') + job_name = module.params.get('job_name') + asid = module.params.get('asid') + arm = module.params.get('arm') + armrestart = module.params.get('armrestart') + userid = module.params.get('userid') + tcb_address = module.params.get('tcb_address') + retry = module.params.get('retry') started_task_name = "" if tcb_address and len(tcb_address) != 6: module.fail_json( @@ -764,39 +688,81 @@ def extract_keys(stdout): tasks The list of task parameters. """ - keys = {'A': 'ASID', 'CT': 'CPU_Time', 'ET': 'Elapsed_Time', 'WUID': 'WUID', 'USERID': 'USERID', 'P': 'Priority'} + keys = { + 'A': 'asid', + 'CT': 'cpu_time', + 'ET': 'elapsed_time', + 'WUID': 'work_unit_identifier', + 'USERID': 'userid', + 'P': 'priority', + 'PER': 'program_event_recording', + 'SMC': 'system_management_control', + 'PGN': 'program_name', + 'SCL': 'started_class_list', + 'WKL': 'workload_manager', + 'ASTE': 'address_space_table_entry', + 'RGP': 'resource_group', + 'DSPNAME': 'dataspace_name' + } lines = stdout.strip().split('\n') tasks = [] - current_task = None + current_task = {} task_header_regex = re.compile(r'^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)') kv_pattern = re.compile(r'(\S+)=(\S+)') for line in lines[5:]: line = line.strip() - if len(line.split()) >= 5 and task_header_regex.search(line): + match_firstline = task_header_regex.search(line) + if len(line.split()) >= 5 and match_firstline: if current_task: tasks.append(current_task) - match = task_header_regex.search(line) - current_task = { - "TASK_NAME": match.group(1), - "DETAILS": {} - } + current_task['task_name'] = match_firstline.group(1) for match in kv_pattern.finditer(line): key, value = match.groups() if key in keys: key = keys[key] - current_task["DETAILS"][key] = value + current_task[key] = value elif current_task: for match in kv_pattern.finditer(line): key, value = match.groups() if key in keys: key = keys[key] - current_task["DETAILS"][key] = value + current_task[key] = value if current_task: + el_time = current_task.get('elapsed_time') + if el_time: + current_task['started_time'] = calculate_start_time(el_time) tasks.append(current_task) return tasks -def fetch_logs(command): +def parse_time(ts_str): + # Case 1: Duration like "000.005seconds" + sec_match = re.match(r"^(\d+\.?\d*)\s*S?$", ts_str, re.IGNORECASE) + if sec_match: + return timedelta(seconds=float(sec_match.group(1))) + # Case 2: hh.mm.ss + hms_match = re.match(r"^(\d+).(\d{2}).(\d{2})$", ts_str) + if hms_match: + h, m, s = map(int, hms_match.groups()) + return timedelta(hours=h, minutes=m, seconds=s) + # Case 3: hhhhh.mm + hm_match = re.match(r"^(\d{1,5}).(\d{2})$", ts_str) + if hm_match: + h, m = map(int, hm_match.groups()) + return timedelta(hours=h, minutes=m) + + +def calculate_start_time(ts_str): + now = datetime.now() + parsed = parse_time(ts_str) + if parsed is None: + return "" + # If it's a timedelta (duration), subtract from now → absolute datetime + if isinstance(parsed, timedelta): + return f"{(now - parsed).strftime('%Y-%m-%d %H:%M:%S')}" + + +def fetch_logs(command, timeout): """Extracts keys and values from the given stdout Parameters @@ -809,7 +775,9 @@ def fetch_logs(command): list The list of logs from SYSLOG """ - stdout = zsystem.read_console(options='-t1') + time_mins = timeout // 60 + 1 + option = '-t' + str(time_mins) + stdout = zsystem.read_console(options=option) stdout_lines = stdout.splitlines() first = None pattern = rf"\b{command}\b" @@ -822,79 +790,6 @@ def fetch_logs(command): return logs -def parse_and_validate_args(params): - """Parse and validate input parameters - - Parameters - ---------- - params : dict - The dictionary which has input parameters. - - Returns - ------- - dict - The validated list of input parameters. - """ - start_args = dict( - device_type=dict(type="str", required=False), - device_number=dict(type="str", required=False), - identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), - job_account=dict(type="str", required=False), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), - keyword_parameters=dict(type="basic_dict", required=False), - member_name=dict(type="member_name", required=False, aliases=["member"]), - parameters=dict(type="list", elements="str", required=False), - reus_asid=dict(type="str", required=False), - subsystem=dict(type="str", required=False), - volume_serial=dict(type="str", required=False) - ) - display_args = dict( - identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]) - ) - modify_args = dict( - identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), - parameters=dict(type="list", elements="str", required=False) - ) - cancel_args = dict( - armrestart=dict(type="bool", required=False), - asid=dict(type="str", required=False), - dump=dict(type="bool", required=False), - identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), - userid=dict(type="str", required=False) - ) - force_args = dict( - arm=dict(type="bool", required=False), - armrestart=dict(type="bool", required=False), - asid=dict(type="str", required=False), - identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), - retry=dict(type="str", required=False), - tcb_address=dict(type="str", required=False), - userid=dict(type="str", required=False) - ) - stop_args = dict( - asid=dict(type="str", required=False), - identifier_name=dict(type="identifier_name", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]) - ) - module_args = dict( - start_task=dict(type="dict", required=False, options=start_args), - stop_task=dict(type="dict", required=False, options=stop_args), - display_task=dict(type="dict", required=False, options=display_args), - modify_task=dict(type="dict", required=False, options=modify_args), - cancel_task=dict(type="dict", required=False, options=cancel_args), - force_task=dict(type="dict", required=False, options=force_args), - verbose=dict(type="bool", required=False), - wait_time_s=dict(type="int", default=5) - ) - parser = better_arg_parser.BetterArgParser(module_args) - parsed_args = parser.parse_args(params) - return parsed_args - - def run_module(): """Initialize the module. @@ -903,81 +798,212 @@ def run_module(): fail_json z/OS started task operation failed. """ - start_args = dict( - device_type=dict(type="str", required=False), - device_number=dict(type="str", required=False), - identifier_name=dict(type="str", required=False, aliases=["identifier"]), - job_account=dict(type="str", required=False), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), - keyword_parameters=dict(type="dict", required=False, no_log=False), - member_name=dict(type="str", required=False, aliases=["member"]), - parameters=dict(type="list", elements="str", required=False), - reus_asid=dict(type="str", required=False, choices=["YES", "NO"]), - subsystem=dict(type="str", required=False), - volume_serial=dict(type="str", required=False) - ) - display_args = dict( - identifier_name=dict(type="str", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]) - ) - modify_args = dict( - identifier_name=dict(type="str", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), - parameters=dict(type="list", elements="str", required=False) - ) - cancel_args = dict( - armrestart=dict(type="bool", required=False), - asid=dict(type="str", required=False), - dump=dict(type="bool", required=False), - identifier_name=dict(type="str", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), - userid=dict(type="str", required=False) - ) - force_args = dict( - arm=dict(type="bool", required=False), - armrestart=dict(type="bool", required=False), - asid=dict(type="str", required=False), - identifier_name=dict(type="str", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]), - retry=dict(type="str", required=False, choices=["YES", "NO"]), - tcb_address=dict(type="str", required=False), - userid=dict(type="str", required=False) - ) - stop_args = dict( - asid=dict(type="str", required=False), - identifier_name=dict(type="str", required=False, aliases=["identifier"]), - job_name=dict(type="str", required=False, aliases=["job", "task", "task_name"]) - ) - - module_args = dict( - start_task=dict(type="dict", required=False, options=start_args), - stop_task=dict(type="dict", required=False, options=stop_args), - display_task=dict(type="dict", required=False, options=display_args), - modify_task=dict(type="dict", required=False, options=modify_args), - cancel_task=dict(type="dict", required=False, options=cancel_args), - force_task=dict(type="dict", required=False, options=force_args), - verbose=dict(type="bool", required=False, default=False), - wait_time_s=dict(type="int", default=5) - ) - module = AnsibleModule( - argument_spec=module_args, + argument_spec={ + 'state': { + 'type': 'str', + 'required': True, + 'choices': ['started', 'stopped', 'modified', 'displayed', 'forced', 'cancelled'] + }, + 'arm': { + 'type': 'bool', + 'required': False + }, + 'armrestart': { + 'type': 'bool', + 'required': False + }, + 'asid': { + 'type': 'str', + 'required': False + }, + 'device_number': { + 'type': 'str', + 'required': False + }, + 'device_type': { + 'type': 'str', + 'required': False + }, + 'dump': { + 'type': 'bool', + 'required': False + }, + 'identifier_name': { + 'type': 'str', + 'required': False, + 'aliases': ['identifier'] + }, + 'job_account': { + 'type': 'str', + 'required': False + }, + 'job_name': { + 'type': 'str', + 'required': False, + 'aliases': ['job', 'task_name', 'task'] + }, + 'keyword_parameters': { + 'type': 'dict', + 'required': False, + 'no_log': False + }, + 'member_name': { + 'type': 'str', + 'required': False, + 'aliases': ['member'] + }, + 'parameters': { + 'type': 'list', + 'elements': 'str', + 'required': False + }, + 'retry': { + 'type': 'str', + 'required': False, + 'choices': ['YES', 'NO'] + }, + 'reus_asid': { + 'type': 'str', + 'required': False, + 'choices': ['YES', 'NO'] + }, + 'subsystem': { + 'type': 'str', + 'required': False + }, + 'tcb_address': { + 'type': 'str', + 'required': False + }, + 'userid': { + 'type': 'str', + 'required': False + }, + 'verbose': { + 'type': 'bool', + 'required': False, + 'default': False + }, + 'volume_serial': { + 'type': 'str', + 'required': False + }, + 'wait_time_s': { + 'type': 'int', + 'required': False, + 'default': 0 + } + }, mutually_exclusive=[ - ["start_task", "stop_task", "display_task", "modify_task", "cancel_task", "force_task"] + ['device_number', 'device_type'] ], supports_check_mode=True ) + args_def = { + 'state': { + 'arg_type': 'str', + 'required': True + }, + 'arm': { + 'arg_type': 'bool', + 'required': False + }, + 'armrestart': { + 'arg_type': 'bool', + 'required': False + }, + 'asid': { + 'arg_type': 'str', + 'required': False + }, + 'device_number': { + 'arg_type': 'str', + 'required': False + }, + 'device_type': { + 'arg_type': 'str', + 'required': False + }, + 'dump': { + 'arg_type': 'bool', + 'required': False + }, + 'identifier_name': { + 'arg_type': 'identifier_name', + 'required': False, + 'aliases': ['identifier'] + }, + 'job_account': { + 'arg_type': 'str', + 'required': False + }, + 'job_name': { + 'arg_type': 'str', + 'required': False, + 'aliases': ['job', 'task_name', 'task'] + }, + 'keyword_parameters': { + 'arg_type': 'basic_dict', + 'required': False + }, + 'member_name': { + 'arg_type': 'member_name', + 'required': False, + 'aliases': ['member'] + }, + 'parameters': { + 'arg_type': 'list', + 'elements': 'str', + 'required': False + }, + 'retry': { + 'arg_type': 'str', + 'required': False + }, + 'reus_asid': { + 'arg_type': 'str', + 'required': False + }, + 'subsystem': { + 'arg_type': 'str', + 'required': False + }, + 'tcb_address': { + 'arg_type': 'str', + 'required': False + }, + 'userid': { + 'arg_type': 'str', + 'required': False + }, + 'verbose': { + 'arg_type': 'bool', + 'required': False + }, + 'volume_serial': { + 'arg_type': 'str', + 'required': False + }, + 'wait_time_s': { + 'arg_type': 'int', + 'required': False + } + } + try: - parms = parse_and_validate_args(module.params) + parser = better_arg_parser.BetterArgParser(args_def) + parsed_args = parser.parse_args(module.params) + module.params = parsed_args except ValueError as err: module.fail_json( - rc=5, msg='Parameter verification failed.', stderr=str(err) ) - wait_time_s = parms.get('wait_time_s') - verbose = parms.get('verbose') + state = module.params.get('state') + wait_time_s = module.params.get('wait_time_s') + verbose = module.params.get('verbose') kwargs = {} """ Below error messages are used to detrmine if response has any error.When @@ -1011,29 +1037,29 @@ def run_module(): execute_display_before = False execute_display_after = False - if parms.get('start_task'): + if state == "started": err_msg = start_errmsg execute_display_after = True - started_task_name, cmd = validate_and_prepare_start_command(module, parms.get('start_task')) - elif parms.get('display_task'): + started_task_name, cmd = validate_and_prepare_start_command(module) + elif state == "displayed": err_msg = display_errmsg - started_task_name, cmd = prepare_display_command(module, parms.get('display_task')) - elif parms.get('stop_task'): + started_task_name, cmd = prepare_display_command(module) + elif state == "stopped": execute_display_before = True err_msg = stop_errmsg - started_task_name, cmd = prepare_stop_command(module, parms.get('stop_task')) - elif parms.get('cancel_task'): + started_task_name, cmd = prepare_stop_command(module) + elif state == "cancelled": execute_display_before = True err_msg = cancel_errmsg - started_task_name, cmd = prepare_cancel_command(module, parms.get('cancel_task')) - elif parms.get('force_task'): + started_task_name, cmd = prepare_cancel_command(module) + elif state == "forced": execute_display_before = True err_msg = force_errmsg - started_task_name, cmd = prepare_force_command(module, parms.get('force_task')) - elif parms.get('modify_task'): + started_task_name, cmd = prepare_force_command(module) + elif state == "modified": execute_display_after = True err_msg = modify_errmsg - started_task_name, cmd = prepare_modify_command(module, parms.get('modify_task')) + started_task_name, cmd = prepare_modify_command(module) changed = False stdout = "" stderr = "" @@ -1043,9 +1069,10 @@ def run_module(): if err != "" or any(msg in out for msg in err_msg): isFailed = True if not isFailed or verbose: - system_logs = fetch_logs(cmd.upper()) + system_logs = fetch_logs(cmd.upper(), wait_time_s) if any(msg in system_logs for msg in err_msg): isFailed = True + current_state = "" if isFailed: if rc == 0: rc = 1 @@ -1056,36 +1083,32 @@ def run_module(): stderr = out stdout = "" else: + current_state = state changed = True stdout = out stderr = err - if parms.get('display_task'): + if state == "displayed": task_params = extract_keys(out) result = dict() if module.check_mode: module.exit_json(**result) - state = "" - + result = dict( - changed=changed, - cmd=cmd, - tasks=task_params, - rc=rc, - stdout=stdout, - stderr=stderr, - stdout_lines=stdout.split('\n'), - stderr_lines=stderr.split('\n'), - ) + changed=changed, + state=current_state, + cmd=cmd, + tasks=task_params, + rc=rc, + stdout=stdout, + stderr=stderr, + stdout_lines=stdout.split('\n'), + stderr_lines=stderr.split('\n'), + ) if verbose: result["verbose_output"] = system_logs - if parms.get('display_task') or parms.get('modify_task'): - if len(task_params) > 0 and not isFailed: - result["state"] = "Active" - else: - result["state"] = "NotActive" - + module.exit_json(**result) diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index d678ed6ee7..6e399f9752 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -43,9 +43,8 @@ def test_start_task_with_invalid_member(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMTASK" - } + state = "started", + member_name = "SAMTASK" ) for result in start_results.contacted.values(): print(result) @@ -53,9 +52,8 @@ def test_start_task_with_invalid_member(ansible_zos_module): assert result.get("stderr") is not None start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLETASK" - } + state = "started", + member_name = "SAMPLETASK" ) for result in start_results.contacted.values(): print(result) @@ -66,11 +64,10 @@ def test_start_task_with_invalid_member(ansible_zos_module): def test_start_task_with_jobname_identifier(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "job_name": "SAMTASK", - "identifier": "TESTER" - } + state = "started", + member_name = "SAMPLE", + job_name = "SAMTASK", + identifier = "TESTER" ) for result in start_results.contacted.values(): print(result) @@ -81,10 +78,9 @@ def test_start_task_with_jobname_identifier(ansible_zos_module): def test_start_task_with_invalid_identifier(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "identifier": "$HELLO" - } + state = "started", + member_name = "SAMPTASK", + identifier = "$HELLO" ) for result in start_results.contacted.values(): @@ -94,10 +90,9 @@ def test_start_task_with_invalid_identifier(ansible_zos_module): assert result.get("stderr") is not None start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "identifier": "HELLO" - } + state = "started", + member_name = "SAMPLE", + identifier = "HELLO" ) for result in start_results.contacted.values(): print(result) @@ -109,10 +104,9 @@ def test_start_task_with_invalid_jobaccount(ansible_zos_module): hosts = ansible_zos_module job_account = "(T043JM,JM00,1,0,0,This is the invalid job account information to test negative scenario)" start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "job_account": job_account - } + state = "started", + member_name = "SAMPLE", + job_account = job_account ) for result in start_results.contacted.values(): @@ -124,10 +118,9 @@ def test_start_task_with_invalid_jobaccount(ansible_zos_module): def test_start_task_with_invalid_devicenum(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "device_number": "0870" - } + state = "started", + member_name = "SAMPLE", + device_number = "0870" ) for result in start_results.contacted.values(): @@ -139,10 +132,9 @@ def test_start_task_with_invalid_devicenum(ansible_zos_module): def test_start_task_with_invalid_volumeserial(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "volume_serial": "12345A" - } + state = "started", + member_name = "SAMPLE", + volume_serial = "12345A" ) for result in start_results.contacted.values(): @@ -154,10 +146,9 @@ def test_start_task_with_invalid_volumeserial(ansible_zos_module): def test_start_task_with_invalid_parameters(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "parameters": ["KEY1"] - } + state = "started", + member_name = "SAMPLE", + parameters = ["KEY1"] ) for result in start_results.contacted.values(): @@ -167,27 +158,25 @@ def test_start_task_with_invalid_parameters(ansible_zos_module): assert result.get("cmd") == "S SAMPLE,,,'KEY1'" start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "parameters": ["KEY1", "KEY2", "KEY3"], - "volume_serial": "12345" - } + state = "started", + member_name = "SAMPLE", + parameters = ["KEY1", "KEY2", "KEY3"], + volume_serial = "123456" ) for result in start_results.contacted.values(): print(result) assert result.get("changed") is False assert result.get("stderr") is not None - assert result.get("cmd") == "S SAMPLE,,12345,(KEY1,KEY2,KEY3)" + assert result.get("cmd") == "S SAMPLE,,123456,(KEY1,KEY2,KEY3)" def test_start_task_with_devicenum_devicetype_negative(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "device_number": "/0870", - "device_type": "TEST" - } + state = "started", + member_name = "SAMPLE", + device_number = "/0870", + device_type = "TEST" ) for result in start_results.contacted.values(): print(result) @@ -199,10 +188,9 @@ def test_start_task_with_devicenum_devicetype_negative(ansible_zos_module): def test_start_task_with_invalid_subsystem_negative(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member": "VLF", - "subsystem": "MSTRS" - } + state = "started", + member_name = "VLF", + subsystem = "MSTRS" ) for result in start_results.contacted.values(): print(result) @@ -214,11 +202,10 @@ def test_start_task_with_invalid_subsystem_negative(ansible_zos_module): def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member": "VLF", - "keyword_parameters":{ - "key1key1key1key1key1key1key1key1": "value1value1value1value1value1value1" - } + state = "started", + member_name = "VLF", + keyword_parameters = { + "key1key1key1key1key1key1key1key1": "value1value1value1value1value1value1" } ) for result in start_results.contacted.values(): @@ -227,11 +214,10 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): assert result.get("failed") is True assert result.get("msg") is not None start_results = hosts.all.zos_started_task( - start_task={ - "member": "VLF", - "keyword_parameters":{ - "key1key1key1key1key1key1key1key1key1key1key1key1": "value1" - } + state = "started", + member_name = "VLF", + keyword_parameters = { + "key1key1key1key1key1key1key1key1key1key1key1key1": "value1" } ) for result in start_results.contacted.values(): @@ -240,12 +226,11 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): assert result.get("failed") is True assert result.get("msg") is not None start_results = hosts.all.zos_started_task( - start_task={ - "member": "VLF", - "keyword_parameters":{ - "KEY1": "VALUE1", - "KEY2": "VALUE2" - } + state = "started", + member_name = "VLF", + keyword_parameters = { + "KEY1": "VALUE1", + "KEY2": "VALUE2" } ) for result in start_results.contacted.values(): @@ -258,10 +243,9 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): def test_start_task_using_nonexisting_devicenum_negative(ansible_zos_module): hosts = ansible_zos_module start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "device_number": "/ABCD" - } + state = "started", + member_name = "SAMPLE", + device_number = "/ABCD" ) for result in start_results.contacted.values(): print(result) @@ -272,9 +256,8 @@ def test_start_task_using_nonexisting_devicenum_negative(ansible_zos_module): def test_display_task_negative(ansible_zos_module): hosts = ansible_zos_module display_results = hosts.all.zos_started_task( - display_task={ - "identifier": "SAMPLE" - } + state = "displayed", + identifier = "SAMPLE" ) for result in display_results.contacted.values(): print(result) @@ -282,40 +265,23 @@ def test_display_task_negative(ansible_zos_module): assert result.get("failed") is True assert result.get("msg") is not None -def test_start_and_display_task_negative(ansible_zos_module): - hosts = ansible_zos_module - display_results = hosts.all.zos_started_task( - start_task={ - "member": "SAMPLE" - }, - display_task={ - "job": "SAMPLE" - } - ) - for result in display_results.contacted.values(): - print(result) - assert result.get("changed") is False - assert result.get("failed") is True - assert result.get("msg") is not None def test_stop_task_negative(ansible_zos_module): hosts = ansible_zos_module stop_results = hosts.all.zos_started_task( - stop_task={ - "identifier": "SAMPLE" - } + state = "stopped", + job_name = "SAMPLE" ) for result in stop_results.contacted.values(): print(result) assert result.get("changed") is False assert result.get("failed") is True - assert result.get("msg") is not None + assert result.get("stderr") is not None stop_results = hosts.all.zos_started_task( - stop_task={ - "job_name": "TESTER", - "identifier": "SAMPLE" - } + state = "stopped", + job_name = "TESTER", + identifier = "SAMPLE" ) for result in stop_results.contacted.values(): print(result) @@ -326,9 +292,8 @@ def test_stop_task_negative(ansible_zos_module): def test_modify_task_negative(ansible_zos_module): hosts = ansible_zos_module modify_results = hosts.all.zos_started_task( - modify_task={ - "identifier": "SAMPLE" - } + state = "modified", + identifier = "SAMPLE" ) for result in modify_results.contacted.values(): print(result) @@ -337,9 +302,8 @@ def test_modify_task_negative(ansible_zos_module): assert result.get("msg") is not None modify_results = hosts.all.zos_started_task( - modify_task={ - "job_name": "TESTER" - } + state = "modified", + job_name = "TESTER" ) for result in modify_results.contacted.values(): print(result) @@ -348,11 +312,10 @@ def test_modify_task_negative(ansible_zos_module): assert result.get("msg") is not None modify_results = hosts.all.zos_started_task( - modify_task={ - "job_name": "TESTER", - "identifier": "SAMPLE", - "parameters": ["REPLACE", "VX=10"] - } + state = "modified", + job_name = "TESTER", + identifier = "SAMPLE", + parameters = ["REPLACE", "VX=10"] ) for result in modify_results.contacted.values(): print(result) @@ -363,9 +326,8 @@ def test_modify_task_negative(ansible_zos_module): def test_cancel_task_negative(ansible_zos_module): hosts = ansible_zos_module cancel_results = hosts.all.zos_started_task( - cancel_task={ - "identifier": "SAMPLE" - } + state = "cancelled", + identifier = "SAMPLE" ) for result in cancel_results.contacted.values(): print(result) @@ -374,10 +336,9 @@ def test_cancel_task_negative(ansible_zos_module): assert result.get("msg") is not None cancel_results = hosts.all.zos_started_task( - cancel_task={ - "job_name": "TESTER", - "identifier": "SAMPLE" - } + state = "cancelled", + job_name = "TESTER", + identifier = "SAMPLE" ) for result in cancel_results.contacted.values(): print(result) @@ -385,11 +346,10 @@ def test_cancel_task_negative(ansible_zos_module): assert result.get("stderr") is not None assert result.get("cmd") == "C TESTER.SAMPLE" cancel_results = hosts.all.zos_started_task( - cancel_task={ - "userid": "OMVSTEST", - "asid": "0012", - "dump": True - }, + state = "cancelled", + asid = "0012", + userid = "OMVSTEST", + dump = True, verbose=True ) for result in cancel_results.contacted.values(): @@ -398,10 +358,9 @@ def test_cancel_task_negative(ansible_zos_module): assert result.get("stderr") is not None assert result.get("cmd") == "C U=OMVSTEST,A=0012,DUMP" cancel_results = hosts.all.zos_started_task( - cancel_task={ - "userid": "OMVSADM", - "armrestart": True - } + state = "cancelled", + userid = "OMVSADM", + armrestart = True ) for result in cancel_results.contacted.values(): print(result) @@ -412,9 +371,8 @@ def test_cancel_task_negative(ansible_zos_module): def test_force_task_negative(ansible_zos_module): hosts = ansible_zos_module force_results = hosts.all.zos_started_task( - force_task={ - "identifier": "SAMPLE" - } + state = "forced", + identifier = "SAMPLE" ) for result in force_results.contacted.values(): print(result) @@ -423,10 +381,9 @@ def test_force_task_negative(ansible_zos_module): assert result.get("msg") is not None force_results = hosts.all.zos_started_task( - force_task={ - "job_name": "TESTER", - "identifier": "SAMPLE" - } + state = "forced", + job_name = "TESTER", + identifier = "SAMPLE" ) for result in force_results.contacted.values(): print(result) @@ -434,10 +391,9 @@ def test_force_task_negative(ansible_zos_module): assert result.get("stderr") is not None assert result.get("cmd") == "FORCE TESTER.SAMPLE" force_results = hosts.all.zos_started_task( - force_task={ - "userid": "OMVSADM", - "armrestart": True - } + state = "forced", + userid = "OMVSADM", + armrestart = True ) for result in force_results.contacted.values(): print(result) @@ -446,10 +402,9 @@ def test_force_task_negative(ansible_zos_module): assert result.get("msg") is not None force_results = hosts.all.zos_started_task( - force_task={ - "job_name": "TESTER", - "retry": "YES" - } + state = "forced", + job_name = "TESTER", + retry = "YES" ) for result in force_results.contacted.values(): print(result) @@ -457,11 +412,10 @@ def test_force_task_negative(ansible_zos_module): assert result.get("failed") is True assert result.get("msg") is not None force_results = hosts.all.zos_started_task( - force_task={ - "job_name": "TESTER", - "tcb_address": "0006789", - "retry": "YES" - } + state = "forced", + job_name = "TESTER", + tcb_address = "0006789", + retry = "YES" ) for result in force_results.contacted.values(): print(result) @@ -469,12 +423,11 @@ def test_force_task_negative(ansible_zos_module): assert result.get("failed") is True assert result.get("msg") is not None force_results = hosts.all.zos_started_task( - force_task={ - "job_name": "TESTER", - "identifier": "SAMPLE", - "tcb_address": "000678", - "retry": "YES" - } + state = "forced", + job_name = "TESTER", + identifier = "SAMPLE", + tcb_address = "000678", + retry = "YES" ) for result in force_results.contacted.values(): print(result) @@ -482,11 +435,10 @@ def test_force_task_negative(ansible_zos_module): assert result.get("stderr") is not None assert result.get("cmd") == "FORCE TESTER.SAMPLE,TCB=000678,RETRY=YES" force_results = hosts.all.zos_started_task( - force_task={ - "userid": "OMVSTEST", - "tcb_address": "000678", - "retry": "YES" - }, + state = "forced", + userid = "OMVSTEST", + tcb_address = "000678", + retry = "YES", verbose=True ) for result in force_results.contacted.values(): @@ -515,9 +467,8 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): ) start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE" - }, + state = "started", + member_name = "SAMPLE", verbose=True ) @@ -530,9 +481,8 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("verbose_output") is not None force_results = hosts.all.zos_started_task( - force_task={ - "task_name": "SAMPLE" - } + state = "forced", + task_name = "SAMPLE" ) for result in force_results.contacted.values(): print(result) @@ -542,9 +492,8 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert "CANCELABLE - ISSUE CANCEL BEFORE FORCE" in result.get("stderr") stop_results = hosts.all.zos_started_task( - cancel_task={ - "task_name": "SAMPLE" - } + state = "cancelled", + task_name = "SAMPLE" ) for result in stop_results.contacted.values(): @@ -557,11 +506,10 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): # validate identifier start_results = hosts.all.zos_started_task( - start_task={ - "member_name": "SAMPLE", - "identifier": "TESTER", - "reus_asid": "YES" - } + state = "started", + member = "SAMPLE", + identifier = "TESTER", + reus_asid = "YES" ) for result in start_results.contacted.values(): print(result) @@ -573,9 +521,8 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("cmd") == "S SAMPLE.TESTER,REUSASID=YES" stop_results = hosts.all.zos_started_task( - cancel_task={ - "task_name": "SAMPLE" - } + state = "cancelled", + task_name = "SAMPLE" ) for result in stop_results.contacted.values(): print(result) @@ -585,10 +532,9 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("verbose_output") is None stop_results = hosts.all.zos_started_task( - cancel_task={ - "task_name": "SAMPLE", - "identifier": "TESTER" - } + state = "cancelled", + task_name = "SAMPLE", + identifier = "TESTER" ) for result in stop_results.contacted.values(): print(result) @@ -600,10 +546,9 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): job_account = "(T043JM,JM00,1,0,0,)" start_results = hosts.all.zos_started_task( - start_task={ - "member": "SAMPLE", - "job_account": job_account - } + state = "started", + member = "SAMPLE", + job_account = job_account ) for result in start_results.contacted.values(): @@ -615,9 +560,8 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("verbose_output") is None display_result = hosts.all.zos_started_task( - display_task={ - "task_name": "SAMPLE" - } + state = "displayed", + task = "SAMPLE" ) for result in display_result.contacted.values(): print(result) @@ -631,10 +575,9 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) stop_results = hosts.all.zos_started_task( - cancel_task={ - "task_name": "SAMPLE", - "asid": asid_val - }, + state = "cancelled", + task_name = "SAMPLE", + asid = asid_val, verbose=True ) @@ -675,10 +618,9 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): ) start_results = hosts.all.zos_started_task( - start_task={ - "member": "SAMPLE", - "job_name": "TESTTSK" - } + state = "started", + member = "SAMPLE", + job_name = "TESTTSK" ) for result in start_results.contacted.values(): @@ -688,9 +630,8 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): assert result.get("stderr") == "" stop_results = hosts.all.zos_started_task( - cancel_task={ - "task_name": "TESTTSK" - } + state = "cancelled", + task = "TESTTSK" ) for result in stop_results.contacted.values(): @@ -709,99 +650,92 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): ) def test_stop_and_modify_with_vlf_task(ansible_zos_module): - hosts = ansible_zos_module - modify_results = hosts.all.zos_started_task( - modify_task={ - "task_name": "VLF", - "parameters": ["REPLACE" ,"NN=00"] - } - ) - for result in modify_results.contacted.values(): - print(result) - assert result.get("changed") is True - assert result.get("rc") == 0 - assert result.get("stderr") == "" - assert result.get("cmd") == "F VLF,REPLACE,NN=00" + hosts = ansible_zos_module + modify_results = hosts.all.zos_started_task( + state = "modified", + task = "VLF", + parameters = ["REPLACE" ,"NN=00"] + ) + for result in modify_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert result.get("cmd") == "F VLF,REPLACE,NN=00" - display_result = hosts.all.zos_started_task( - display_task={ - "task_name": "VLF" - } - ) - for result in display_result.contacted.values(): - print(result) - assert result.get("changed") is True - assert result.get("rc") == 0 - assert result.get("stderr") == "" - assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is None + display_result = hosts.all.zos_started_task( + state = "displayed", + task = "VLF" + ) + for result in display_result.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 + assert result.get("verbose_output") is None - display_output = list(display_result.contacted.values())[0].get("stdout") - asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) + display_output = list(display_result.contacted.values())[0].get("stdout") + asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) - stop_results = hosts.all.zos_started_task( - stop_task={ - "task_name": "VLF", - "asid": asid_val - } - ) - for result in stop_results.contacted.values(): - print(result) - assert result.get("changed") is True - assert result.get("rc") == 0 - assert result.get("stderr") == "" - assert result.get("cmd") == f"P VLF,A={asid_val}" + stop_results = hosts.all.zos_started_task( + state = "stopped", + task = "VLF", + asid = asid_val + ) + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert result.get("cmd") == f"P VLF,A={asid_val}" - start_results = hosts.all.zos_started_task( - start_task={ - "member": "VLF", - "identifier": "TESTER", - "subsystem": "MSTR" - } - ) - for result in start_results.contacted.values(): - print(result) - assert result.get("changed") is True - assert result.get("rc") == 0 - assert result.get("stderr") == "" + start_results = hosts.all.zos_started_task( + state = "started", + member = "VLF", + identifier = "TESTER", + subsystem = "MSTR" + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" - modify_results = hosts.all.zos_started_task( - modify_task={ - "task_name": "VLF", - "identifier": "TESTER", - "parameters": ["REPLACE" ,"NN=00"] - } - ) - for result in modify_results.contacted.values(): - print(result) - assert result.get("changed") is True - assert result.get("rc") == 0 - assert result.get("stderr") == "" - assert result.get("cmd") == "F VLF.TESTER,REPLACE,NN=00" - - stop_results = hosts.all.zos_started_task( - stop_task={ - "task_name": "VLF", - "identifier": "TESTER" - } - ) - for result in stop_results.contacted.values(): - print(result) - assert result.get("changed") is True - assert result.get("rc") == 0 - assert result.get("stderr") == "" + modify_results = hosts.all.zos_started_task( + state = "modified", + task = "VLF", + identifier = "TESTER", + parameters = ["REPLACE" ,"NN=00"] + ) + for result in modify_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert result.get("cmd") == "F VLF.TESTER,REPLACE,NN=00" + + stop_results = hosts.all.zos_started_task( + state = "stopped", + task = "VLF", + identifier = "TESTER" + ) + for result in stop_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" - start_results = hosts.all.zos_started_task( - start_task={ - "member": "VLF", - "subsystem": "MSTR" - } - ) - for result in start_results.contacted.values(): - print(result) - assert result.get("changed") is True - assert result.get("rc") == 0 - assert result.get("stderr") == "" + start_results = hosts.all.zos_started_task( + state = "started", + member = "VLF", + subsystem = "MSTR" + ) + for result in start_results.contacted.values(): + print(result) + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): @@ -824,10 +758,9 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): ) start_results = hosts.all.zos_started_task( - start_task={ - "member": "SAMPLE2", - "job_name": "SPROC" - }, + state = "started", + member = "SAMPLE2", + job_name = "SPROC", verbose=True ) @@ -838,9 +771,8 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): assert result.get("stderr") == "" stop_results = hosts.all.zos_started_task( - cancel_task={ - "task_name": "SPROC" - } + state = "cancelled", + task = "SPROC" ) for result in stop_results.contacted.values(): From 38cf15b5ddb3a6653c3299b36a339320bbf11c9f Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Wed, 10 Sep 2025 22:09:48 +0530 Subject: [PATCH 12/36] Update better_arg_parser.py --- plugins/module_utils/better_arg_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/module_utils/better_arg_parser.py b/plugins/module_utils/better_arg_parser.py index a42301ede4..9f22c36c5f 100644 --- a/plugins/module_utils/better_arg_parser.py +++ b/plugins/module_utils/better_arg_parser.py @@ -280,7 +280,7 @@ def _basic_dict_type(self, contents, resolve_dependencies): if not isinstance(contents, dict): raise ValueError('Invalid argument "{0}" for type "dict".'.format(contents)) return contents - + def _str_type(self, contents, resolve_dependencies): """Resolver for str type arguments. From 20f92234f39ee81fda2049b96647c3aa4f14edb2 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Wed, 10 Sep 2025 22:14:49 +0530 Subject: [PATCH 13/36] Delete 2202-zos_data_set-Support-noscratch-options.yml --- .../2202-zos_data_set-Support-noscratch-options.yml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 changelogs/fragments/2202-zos_data_set-Support-noscratch-options.yml diff --git a/changelogs/fragments/2202-zos_data_set-Support-noscratch-options.yml b/changelogs/fragments/2202-zos_data_set-Support-noscratch-options.yml deleted file mode 100644 index d1bf0fdad0..0000000000 --- a/changelogs/fragments/2202-zos_data_set-Support-noscratch-options.yml +++ /dev/null @@ -1,10 +0,0 @@ -minor_changes: - - zos_data_set - Adds `noscratch` option to allow uncataloging - a data set without deleting it from the volume's VTOC. - (https://github.com/ansible-collections/ibm_zos_core/pull/2202) -trivial: - - data_set - Internal updates to support the noscratch option. - https://github.com/ansible-collections/ibm_zos_core/pull/2202) - - test_zos_data_set_func - added test case to verify the `noscratch` option - functionality in zos_data_set module. - (https://github.com/ansible-collections/ibm_zos_core/pull/2202). From 45fd79df34938f6aad5467e7d7006a79be8f3862 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Wed, 10 Sep 2025 23:47:47 +0530 Subject: [PATCH 14/36] Update zos_started_task.py --- plugins/modules/zos_started_task.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 8ef0f4a920..4b927231fa 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -702,7 +702,11 @@ def extract_keys(stdout): 'WKL': 'workload_manager', 'ASTE': 'address_space_table_entry', 'RGP': 'resource_group', - 'DSPNAME': 'dataspace_name' + 'DSPNAME': 'dataspace_name', + 'DMN': 'domain_number', + 'AFF': 'affinity', + 'SRVR': 'server', + 'QSC': 'queue_scan_count' } lines = stdout.strip().split('\n') tasks = [] @@ -720,13 +724,13 @@ def extract_keys(stdout): key, value = match.groups() if key in keys: key = keys[key] - current_task[key] = value + current_task[key.lower()] = value elif current_task: for match in kv_pattern.finditer(line): key, value = match.groups() if key in keys: key = keys[key] - current_task[key] = value + current_task[key.lower()] = value if current_task: el_time = current_task.get('elapsed_time') if el_time: @@ -1105,9 +1109,8 @@ def run_module(): stderr=stderr, stdout_lines=stdout.split('\n'), stderr_lines=stderr.split('\n'), + verbose_output=system_logs ) - if verbose: - result["verbose_output"] = system_logs module.exit_json(**result) From ac961fb9ce0ddb15e4f096afc753b63351932244 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 11 Sep 2025 00:02:22 +0530 Subject: [PATCH 15/36] Update testcases --- plugins/modules/zos_started_task.py | 3 +++ .../modules/test_zos_started_task_func.py | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 4b927231fa..cf7d94b923 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -1072,10 +1072,13 @@ def run_module(): system_logs = "" if err != "" or any(msg in out for msg in err_msg): isFailed = True + # Fetch system logs to validate any error occured in execution if not isFailed or verbose: system_logs = fetch_logs(cmd.upper(), wait_time_s) if any(msg in system_logs for msg in err_msg): isFailed = True + if not verbose: + system_logs = "" current_state = "" if isFailed: if rc == 0: diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 6e399f9752..01652708ba 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -345,6 +345,7 @@ def test_cancel_task_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "C TESTER.SAMPLE" + assert result.get("verbose_output") == "" cancel_results = hosts.all.zos_started_task( state = "cancelled", asid = "0012", @@ -357,6 +358,7 @@ def test_cancel_task_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "C U=OMVSTEST,A=0012,DUMP" + assert result.get("verbose_output") != "" cancel_results = hosts.all.zos_started_task( state = "cancelled", userid = "OMVSADM", @@ -446,6 +448,7 @@ def test_force_task_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "FORCE U=OMVSTEST,TCB=000678,RETRY=YES" + assert result.get("verbose_output") != "" def test_start_and_cancel_zos_started_task(ansible_zos_module): @@ -478,7 +481,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("rc") == 0 assert result.get("stderr") == "" assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is not None + assert result.get("verbose_output") != "" force_results = hosts.all.zos_started_task( state = "forced", @@ -502,7 +505,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("rc") == 0 assert result.get("stderr") == "" assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is None + assert result.get("verbose_output") == "" # validate identifier start_results = hosts.all.zos_started_task( @@ -517,7 +520,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("rc") == 0 assert result.get("stderr") == "" assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is None + assert result.get("verbose_output") == "" assert result.get("cmd") == "S SAMPLE.TESTER,REUSASID=YES" stop_results = hosts.all.zos_started_task( @@ -529,7 +532,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is None + assert result.get("verbose_output") == "" stop_results = hosts.all.zos_started_task( state = "cancelled", @@ -542,7 +545,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("rc") == 0 assert result.get("stderr") == "" assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is None + assert result.get("verbose_output") == "" job_account = "(T043JM,JM00,1,0,0,)" start_results = hosts.all.zos_started_task( @@ -569,7 +572,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("rc") == 0 assert result.get("stderr") == "" assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is None + assert result.get("verbose_output") == "" display_output = list(display_result.contacted.values())[0].get("stdout") asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) @@ -587,7 +590,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("rc") == 0 assert result.get("stderr") == "" assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is not None + assert result.get("verbose_output") != "" finally: hosts.all.file(path=temp_path, state="absent") @@ -673,7 +676,7 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): assert result.get("rc") == 0 assert result.get("stderr") == "" assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is None + assert result.get("verbose_output") == "" display_output = list(display_result.contacted.values())[0].get("stdout") asid_val = re.search(r"\bA=([^ \n\r\t]+)", display_output).group(1) @@ -769,6 +772,7 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert result.get("verbose_output") != "" stop_results = hosts.all.zos_started_task( state = "cancelled", From 416cc5b9c10b5138b9ef38c9638ec068b6699272 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 11 Sep 2025 00:06:43 +0530 Subject: [PATCH 16/36] Update test_zos_started_task_func.py --- tests/functional/modules/test_zos_started_task_func.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 01652708ba..3c03fd1b62 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -560,7 +560,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): assert result.get("rc") == 0 assert result.get("stderr") == "" assert len(result.get("tasks")) > 0 - assert result.get("verbose_output") is None + assert result.get("verbose_output") == "" display_result = hosts.all.zos_started_task( state = "displayed", From b5a225c9fb38a00af734a6a85c9cb1c3377d548c Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:10:43 +0530 Subject: [PATCH 17/36] Update zos_started_task.py --- plugins/modules/zos_started_task.py | 42 ++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index cf7d94b923..f4d92f6e73 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -214,11 +214,11 @@ type: list elements: dict contains: - job_name: + address_space_table_entry: description: - The name of the batch job. + The name of the started task. type: str - sample: LINKJOB + sample: SAMPLE stdout: description: The STDOUT from the command, may be empty. returned: changed @@ -268,7 +268,7 @@ # zoau_exceptions = ZOAUImportError(traceback.format_exc()) -def execute_command(operator_cmd, started_task_name, execute_display_before=False, execute_display_after=False, timeout_s=1, *args, **kwargs): +def execute_command(operator_cmd, started_task_name, execute_display_before=False, execute_display_after=False, timeout_s=1, **kwargs): """Execute operator command. Parameters @@ -293,7 +293,7 @@ def execute_command(operator_cmd, started_task_name, execute_display_before=Fals timeout_c = 100 * timeout_s if execute_display_before: task_params = execute_display_command(started_task_name, timeout_c) - response = opercmd.execute(operator_cmd, timeout_c, *args, **kwargs) + response = opercmd.execute(operator_cmd, timeout_c, **kwargs) if execute_display_after: task_params = execute_display_command(started_task_name, timeout_c) @@ -700,7 +700,8 @@ def extract_keys(stdout): 'PGN': 'program_name', 'SCL': 'started_class_list', 'WKL': 'workload_manager', - 'ASTE': 'address_space_table_entry', + 'ASTE': 'data_space_address_entry', + 'ADDR SPACE ASTE': 'address_space_second_table_entry', 'RGP': 'resource_group', 'DSPNAME': 'dataspace_name', 'DMN': 'domain_number', @@ -711,15 +712,28 @@ def extract_keys(stdout): lines = stdout.strip().split('\n') tasks = [] current_task = {} + aste_key = "ADDR SPACE ASTE" task_header_regex = re.compile(r'^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)') - kv_pattern = re.compile(r'(\S+)=(\S+)') + kv_pattern = re.compile(rf'({re.escape(aste_key)}|\S+)=(\S+)') for line in lines[5:]: line = line.strip() match_firstline = task_header_regex.search(line) if len(line.split()) >= 5 and match_firstline: if current_task: + el_time = current_task.get('elapsed_time') + if el_time: + current_task['started_time'] = calculate_start_time(el_time) tasks.append(current_task) + current_task = {} current_task['task_name'] = match_firstline.group(1) + current_task['task_identifier'] = match_firstline.group(2) + if "=" not in match_firstline.group(5): + current_task['proc_step_name'] = match_firstline.group(3) + current_task['task_type'] = match_firstline.group(4) + current_task['task_status'] = match_firstline.group(5) + else: + current_task['task_type'] = match_firstline.group(3) + current_task['task_status'] = match_firstline.group(4) for match in kv_pattern.finditer(line): key, value = match.groups() if key in keys: @@ -730,7 +744,10 @@ def extract_keys(stdout): key, value = match.groups() if key in keys: key = keys[key] - current_task[key.lower()] = value + if current_task.get(key.lower()): + current_task[key.lower()] = [current_task[key.lower()], value] + else: + current_task[key.lower()] = value if current_task: el_time = current_task.get('elapsed_time') if el_time: @@ -741,6 +758,8 @@ def extract_keys(stdout): def parse_time(ts_str): # Case 1: Duration like "000.005seconds" + print("hiiiii") + print(ts_str) sec_match = re.match(r"^(\d+\.?\d*)\s*S?$", ts_str, re.IGNORECASE) if sec_match: return timedelta(seconds=float(sec_match.group(1))) @@ -1028,15 +1047,14 @@ def run_module(): cancel_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'DUPLICATE NAME FOUND'] force_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'CANCELABLE', 'DUPLICATE NAME FOUND'] err_msg = [] - + kwargs = {} use_wait_arg = False if zoau_version_checker.is_zoau_version_higher_than("1.2.4"): use_wait_arg = True - if use_wait_arg: + if use_wait_arg or wait_time_s: kwargs.update({"wait": True}) - args = [] cmd = "" execute_display_before = False @@ -1067,7 +1085,7 @@ def run_module(): changed = False stdout = "" stderr = "" - rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, execute_display_after, timeout_s=wait_time_s, *args, **kwargs) + rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, execute_display_after, timeout_s=wait_time_s, **kwargs) isFailed = False system_logs = "" if err != "" or any(msg in out for msg in err_msg): From 8f0dda36cb85652b6c0147440de7322ca1ccee4b Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:57:58 +0530 Subject: [PATCH 18/36] adding time zone changes --- plugins/modules/zos_started_task.py | 42 ++++++++++++++++------------- tests/sanity/ignore-2.15.txt | 1 + tests/sanity/ignore-2.16.txt | 1 + tests/sanity/ignore-2.17.txt | 1 + tests/sanity/ignore-2.18.txt | 3 ++- tests/sanity/ignore-2.19.txt | 3 ++- 6 files changed, 30 insertions(+), 21 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index f4d92f6e73..f82dfc4a2d 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -21,7 +21,7 @@ module: zos_started_task version_added: 1.16.0 author: - - "Ravella Surendra Babu (@surendra.ravella582)" + - "Ravella Surendra Babu (@surendrababuravella)" short_description: Perform operations on started tasks. description: - start, display, modify, cancel, force and stop a started task @@ -161,12 +161,12 @@ required: false type: bool default: false - wait_time_s: + wait_time: required: false default: 0 type: int description: - - Option I(wait_time_s) is the the maximum amount of time, in seconds, to wait for a response after submitting + - Option I(wait_time) is the the maximum amount of time, in seconds, to wait for a response after submitting the console command. Default value of 0 means to wait the default amount of time supported by the opercmd utility. """ EXAMPLES = r""" @@ -250,9 +250,6 @@ from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import ( better_arg_parser ) -from ansible_collections.ibm.ibm_zos_core.plugins.module_utils import ( - zoau_version_checker -) from ansible_collections.ibm.ibm_zos_core.plugins.module_utils.import_handler import ( ZOAUImportError ) @@ -740,14 +737,26 @@ def extract_keys(stdout): key = keys[key] current_task[key.lower()] = value elif current_task: + data_space = {} for match in kv_pattern.finditer(line): + dsp_keys = ['dataspace_name', 'data_space_address_entry'] key, value = match.groups() if key in keys: key = keys[key] - if current_task.get(key.lower()): - current_task[key.lower()] = [current_task[key.lower()], value] + if key in dsp_keys: + data_space[key] = value + # key_val = current_task.get(key.lower()) + # if key_val: + # if isinstance(key_val, str): + # current_task[key.lower()] = [key_val, value] + # elif isinstance(key_val, list): + # current_task[key.lower()] = key_val + [value] else: current_task[key.lower()] = value + if current_task.get("dataspaces"): + current_task["dataspaces"] = current_task["dataspaces"] + [data_space] + elif data_space: + current_task["dataspaces"] = [data_space] if current_task: el_time = current_task.get('elapsed_time') if el_time: @@ -758,8 +767,6 @@ def extract_keys(stdout): def parse_time(ts_str): # Case 1: Duration like "000.005seconds" - print("hiiiii") - print(ts_str) sec_match = re.match(r"^(\d+\.?\d*)\s*S?$", ts_str, re.IGNORECASE) if sec_match: return timedelta(seconds=float(sec_match.group(1))) @@ -776,13 +783,13 @@ def parse_time(ts_str): def calculate_start_time(ts_str): - now = datetime.now() + now = datetime.now().astimezone() parsed = parse_time(ts_str) if parsed is None: return "" # If it's a timedelta (duration), subtract from now → absolute datetime if isinstance(parsed, timedelta): - return f"{(now - parsed).strftime('%Y-%m-%d %H:%M:%S')}" + return f"{now - parsed}" def fetch_logs(command, timeout): @@ -912,7 +919,7 @@ def run_module(): 'type': 'str', 'required': False }, - 'wait_time_s': { + 'wait_time': { 'type': 'int', 'required': False, 'default': 0 @@ -1009,7 +1016,7 @@ def run_module(): 'arg_type': 'str', 'required': False }, - 'wait_time_s': { + 'wait_time': { 'arg_type': 'int', 'required': False } @@ -1025,7 +1032,7 @@ def run_module(): stderr=str(err) ) state = module.params.get('state') - wait_time_s = module.params.get('wait_time_s') + wait_time_s = module.params.get('wait_time') verbose = module.params.get('verbose') kwargs = {} """ @@ -1048,11 +1055,8 @@ def run_module(): force_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'CANCELABLE', 'DUPLICATE NAME FOUND'] err_msg = [] kwargs = {} - use_wait_arg = False - if zoau_version_checker.is_zoau_version_higher_than("1.2.4"): - use_wait_arg = True - if use_wait_arg or wait_time_s: + if wait_time_s: kwargs.update({"wait": True}) cmd = "" diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index b8349e60d2..be5ebb4e7a 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -25,3 +25,4 @@ plugins/modules/zos_volume_init.py validate-modules:missing-gplv3-license # Lice plugins/modules/zos_archive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_unarchive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_zfs_resize.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 +plugins/modules/zos_started_task.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index ab5f45a7cb..29abd16b32 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -26,3 +26,4 @@ plugins/modules/zos_volume_init.py validate-modules:missing-gplv3-license # Lice plugins/modules/zos_archive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_unarchive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_zfs_resize.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 +plugins/modules/zos_started_task.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index d682068ed5..874f4f684c 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -26,3 +26,4 @@ plugins/modules/zos_volume_init.py validate-modules:missing-gplv3-license # Lice plugins/modules/zos_archive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_unarchive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_zfs_resize.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 +plugins/modules/zos_started_task.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt index 52cbed2aa3..048fb8e797 100644 --- a/tests/sanity/ignore-2.18.txt +++ b/tests/sanity/ignore-2.18.txt @@ -25,4 +25,5 @@ plugins/modules/zos_gather_facts.py validate-modules:missing-gplv3-license # Lic plugins/modules/zos_volume_init.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_archive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_unarchive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 -plugins/modules/zos_zfs_resize.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file +plugins/modules/zos_zfs_resize.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 +plugins/modules/zos_started_task.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt index 52cbed2aa3..048fb8e797 100644 --- a/tests/sanity/ignore-2.19.txt +++ b/tests/sanity/ignore-2.19.txt @@ -25,4 +25,5 @@ plugins/modules/zos_gather_facts.py validate-modules:missing-gplv3-license # Lic plugins/modules/zos_volume_init.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_archive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 plugins/modules/zos_unarchive.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 -plugins/modules/zos_zfs_resize.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file +plugins/modules/zos_zfs_resize.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 +plugins/modules/zos_started_task.py validate-modules:missing-gplv3-license # Licensed under Apache 2.0 \ No newline at end of file From 712828b936b998c4493f56f2214ba90a15c5d59c Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:25:11 +0530 Subject: [PATCH 19/36] Update zos_started_task.py --- plugins/modules/zos_started_task.py | 395 +++++++++++++++++++++++----- 1 file changed, 333 insertions(+), 62 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index f82dfc4a2d..a93052f448 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -29,78 +29,97 @@ options: arm: description: - - I(arm) indicates to execute normal task termination routines without causing address space destruction. + - I(arm) indicates to execute normal task termination routines without causing address space destruction. required: false type: bool armrestart: description: - - I(armrestart) indicates to restart a started task automatically after the cancel completes. + - Indicates that the batch job or started task should be automatically restarted after the cancel + completes, if it is registered as an element of the automatic restart manager. If the job or + task is not registered or if you do not specify this parameter, MVS will not automatically + restart the job or task. + Only applicable when state is cancelled or forced, otherwise is ignored. required: false type: bool asid: description: - - I(asid) is a unique address space identifier which gets assigned to each running started task. + - When state is cancelled or stopped or forced, asid is the hexadecimal address space + identifier of the work unit you want to cancel, stop or force. + - When state=displayed asid is the hexadecimal address space identifier of the work unit of + the task you get details from. required: false type: str device_type: description: - - I(device_type) is the type of the output device (if any) associated with the task. + - Option device_type is the type of the output device (if any) associated with the task. + Only applicable when state=started otherwise ignored. required: false type: str device_number: description: - - I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. - A slash (/) must precede a 4-digit number but is not before a 3-digit number. + - Option device_number is the number of the device to be started. A device number is 3 or 4 + hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit + number. + Only applicable when state=started otherwise ignored. required: false type: str dump: description: - - I(dump) indicates to take dump before ending a started task. + - A dump is to be taken. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) + depends on the JCL for the job. + Only applicable when state=cancelled otherwise ignored. required: false type: bool identifier_name: description: - - I(identifier_name) is the name that identifies the task to be started. This name can be up to 8 characters long. - The first character must be alphabetical. + - Option identifier_name is the name that identifies the task. This name can be up to 8 + characters long. The first character must be alphabetical. required: false type: str aliases: - - identifier + - identifier job_account: description: - - I(job_account) specifies accounting data in the JCL JOB statement for the started task. - If the source JCL was a job and has already accounting data, the value that is specified on this parameter - overrides the accounting data in the source JCL. + - Option job_account specifies accounting data in the JCL JOB statement for the started + task. If the source JCL was a job and has already accounting data, the value that is + specified on this parameter overrides the accounting data in the source JCL. + Only applicable when state=started otherwise ignored. required: false type: str job_name: description: - - I(job_name) is a name which should be assigned to a started task while starting it. If job_name is not specified, - then member_name is used as job_name. + - When state=started job_name is a name which should be assigned to a started task + while starting it. If job_name is not specified, then member_name is used as job_name. + Otherwise, job_name is the started task job name used to find and apply the state + selected. required: false type: str aliases: - - job - - task - - task_name + - job + - task + - task_name keyword_parameters: description: - - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. - The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than - 44 characters in length. + - Any appropriate keyword parameter that you specify to override the corresponding + parameter in the cataloged procedure. The maximum length of each keyword=option is 66 + characters. No individual value within this field can be longer than 44 characters in length. + Only applicable when state=started otherwise ignored. required: false type: dict member_name: description: - - I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL - for the task to be started. The member can be either a job or a cataloged procedure. + - Option member_name is a 1 - 8 character name of a member of a partitioned data set that + contains the source JCL for the task to be started. The member can be either a job or a + cataloged procedure. + Only applicable when state=started otherwise ignored. required: false type: str aliases: - - member + - member parameters: description: - - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks + - Program parameters passed to the started program, which might be a list in parentheses or + a string in single quotation marks required: false type: list elements: str @@ -110,21 +129,28 @@ required: false type: str choices: - - 'YES' - - 'NO' + - 'YES' + - 'NO' reus_asid: description: - - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, - a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified - on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, + a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified + on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. required: false type: str choices: - - 'YES' - - 'NO' + - 'YES' + - 'NO' state: description: - - The final state desired for specified started task. + - The desired state the started task should be after the module is executed. + If state=started and the started task is not found on the managed node, no action is taken, + module completes successfully with changed=False. + If state is cancelled , stopped or forced and the started task is not running on the + managed node, no action is taken, module completes successfully with changed=False. + If state is modified and the started task is not running, not found or modification was not + done, the module will fail. + If state is displayed the module will return the started task details. required: True type: str choices: @@ -136,8 +162,9 @@ - forced subsystem: description: - - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, - which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. + - The name of the subsystem that selects the task for processing. The name must be 1 - 4 + characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must + be active. required: false type: str tcb_address: @@ -147,33 +174,112 @@ type: str volume_serial: description: - - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. + - If devicetype is a tape or direct-access device, the volume serial number of the volume is + mounted on the device. + Only applicable when state=started otherwise ignored. required: false type: str userid: description: - - I(userid) is the user ID of the time-sharing user you want to cancel. + - The user ID of the time-sharing user you want to cancel or force. + Only applicable when state=cancelled or state=forced , otherwise ignored. required: false type: str verbose: description: - - Return System logs that describe the task's execution. + - When verbose=true return system logs that describe the task’s execution. + Using this option will can return a big response depending on system’s load, also it could + surface other programs activity. required: false type: bool default: false wait_time: + description: + - Option wait_time is the total time that module zos_started_tak will wait for a submitted + task. The time begins when the module is executed on the managed node. Default value of 0 + means to wait the default amount of time supported by the opercmd utility. required: false default: 0 type: int - description: - - Option I(wait_time) is the the maximum amount of time, in seconds, to wait for a response after submitting - the console command. Default value of 0 means to wait the default amount of time supported by the opercmd utility. + +attributes: + action: + support: none + description: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller. + async: + support: full + description: Supports being used with the ``async`` keyword. + check_mode: + support: full + description: Can run in check_mode and return changed status prediction without modifying target. If not supported, the action will be skipped. """ EXAMPLES = r""" - name: Start a started task using member name. zos_started_task: + state: "started" + member: "PROCAPP" +- name: Start a started task using member name and identifier. + zos_started_task: + state: "started" + member: "PROCAPP" + identifier: "SAMPLE" +- name: Start a started task using member name and job. + zos_started_task: + state: "started" member: "PROCAPP" - operation: "start" + job_name: "SAMPLE" +- name: Start a started task using member name, job and enable verbose. + zos_started_task: + state: "started" + member: "PROCAPP" + job_name: "SAMPLE" + verbose: True +- name: Start a started task using member name, subsystem and enable reuse asid. + zos_started_task: + state: "started" + member: "PROCAPP" + subsystem: "MSTR" + reus_asid: "YES" +- name: Display a started task using started task name. + zos_started_task: + state: "displayed" + task_name: "PROCAPP" +- name: Display started tasks using matching regex. + zos_started_task: + state: "displayed" + task_name: "s*" +- name: Display all started tasks. + zos_started_task: + state: "displayed" + task_name: "all" +- name: Cancel a started tasks using task name. + zos_started_task: + state: "cancelled" + task_name: "SAMPLE" +- name: Cancel a started tasks using task name and asid. + zos_started_task: + state: "cancelled" + task_name: "SAMPLE" + asid: 0014 +- name: Cancel a started tasks using task name and asid. + zos_started_task: + state: "modified" + task_name: "SAMPLE" + parameters: ["XX=12"] +- name: Stop a started task using task name. + zos_started_task: + state: "stopped" + task_name: "SAMPLE" +- name: Stop a started task using task name, identifier and asid. + zos_started_task: + state: "stopped" + task_name: "SAMPLE" + identifier: "SAMPLE" + asid: 00A5 +- name: Force a started task using task name. + zos_started_task: + state: "forced" + task_name: "SAMPLE" """ RETURN = r""" @@ -206,6 +312,26 @@ returned: changed type: str sample: S SAMPLE +stderr: + description: The STDERR from the command, may be empty. + returned: changed + type: str + sample: An error has ocurred. +stderr_lines: + description: List of strings containing individual lines from STDERR. + returned: changed + type: list + sample: ["An error has ocurred"] +stdout: + description: The STDOUT from the command, may be empty. + returned: changed + type: str + sample: ISF031I CONSOLE OMVS0000 ACTIVATED. +stdout_lines: + description: List of strings containing individual lines from STDOUT. + returned: changed + type: list + sample: ["Allocation to SYSEXEC completed."] tasks: description: The output information for a list of started tasks matching specified criteria. @@ -214,32 +340,177 @@ type: list elements: dict contains: - address_space_table_entry: + address_space_second_table_entry: + description: + The control block used to manage memory for a started task + type: str + sample: 03E78500 + affinity: + description: + The identifier of the processor, for up to any four processors, if the job requires the services of specific processors. + affinity=NONE means the job can run on any processor. + type: str + sample: NONE + asid: + description: + Address space identifier (ASID), in hexadecimal. + type: str + sample: 0054 + cpu_time: + description: + The processor time used by the address space, including the initiator. This time does not include SRB time. + nnnnnnnn has one of these formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours: + sss.tttS + When time is less than 1000 seconds + hh.mm.ss + When time is at least 1000 seconds, but less than 100 hours + hhhhh.mm + When time is at least 100 hours + ******** + When time exceeds 100000 hours + NOTAVAIL + When the TOD clock is not working + type: str + sample: 000.008S + dataspaces: + description: + The started task dataspaces details. + returned: success + type: list + elements: dict + contains: + data_space_address_entry: + description: + Central address of the data space ASTE. + type: str + sample: 058F2180 + dataspace_name: + description: + Data space name associated with the address space. + type: str + sample: CIRRGMAP + domain_number: + description: + domain_number=N/A if the system is operating in goal mode. + type: str + sample: N/A + elapsed_time: + description: + -> For address spaces other than system address spaces, the elapsed time since job select time. + -> For system address spaces created before master scheduler initialization, the elapsed time since master scheduler initialization. + -> For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. + sss.tttS + When time is less than 1000 seconds + hh.mm.ss + When time is at least 1000 seconds, but less than 100 hours + hhhhh.mm + When time is at least 100 hours + ******** + When time exceeds 100000 hours + NOTAVAIL + When the TOD clock is not working + type: str + sample: 812.983S + priority: + description: + The priority of a started task is determined by the Workload Manager (WLM), based on the service class and importance assigned to it. + type: str + sample: 1 + proc_step_name: + description: + One of the following: + -> For APPC-initiated transactions, the user ID requesting the transaction. + -> The name of a step within a cataloged procedure that was called by the step specified in field sss. + -> Blank, if there is no cataloged procedure. + -> The identifier of the requesting transaction program. + type: str + sample: VLF + program_event_recording: + description: + YES if A PER trap is active in the address space. + NO if No PER trap is active in the address space. + type: str + sample: NO + program_name: + description: + program_name=N/A if the system is operating in goal mode. + type: str + sample: N/A + queue_scan_count: + description: + YES if the address space has been quiesced. + NO if the address space is not quiesced. + type: str + sample: NO + resource_group: + description: + The name of the resource group currently associated the service class. It can also be N/A if there is no resource group association. + type: str + sample: N/A + server: + description: + YES if the address space is a server. + No if the address space is not a server. + type: str + sample: NO + started_class_list: + description: + The name of the service class currently associated with the address space. + type: str + sample: SYSSTC + started_time: + description: + The time when the started task started. + type: str + sample: 2025-09-11 18:21:50.293644+00:00 + system_management_control: + description: + Number of outstanding step-must-complete requests. + type: str + sample: 000 + task_identifier: + description: + One of the following: + -> The name of a system address space. + -> The name of a step, for a job or attached APPC transaction program attached by an initiator. + -> The identifier of a task created by the START command. + -> The name of a step that called a cataloged procedure. + -> STARTING, if initiation of a started job, system task, or attached APPC transaction program is incomplete. + -> *MASTER*, for the master address space. + -> The name of an initiator address space. + type: str + sample: SPROC + task_name: description: The name of the started task. type: str sample: SAMPLE -stdout: - description: The STDOUT from the command, may be empty. - returned: changed - type: str - sample: ISF031I CONSOLE OMVS0000 ACTIVATED. -stderr: - description: The STDERR from the command, may be empty. - returned: changed - type: str - sample: An error has ocurred. -stdout_lines: - description: List of strings containing individual lines from STDOUT. - returned: changed - type: list - sample: ["Allocation to SYSEXEC completed."] -stderr_lines: - description: List of strings containing individual lines from STDERR. + task_status: + description: + The status of the task can be one of the following. + -> IN for swapped in. + -> OUT for swapped out, ready to run. + -> OWT for swapped out, waiting, not ready to run. + -> OU* for in process of being swapped out. + -> IN* for in process of being swapped in. + -> NSW for non-swappable. + type: str + sample: NSW + task_type: + description: + S for started task. + type: str + sample: S + workload_manager: + description: + The name of the workload currently associated with the address space. + type: str + sample: SYSTEM +verbose_output: + description: If C(verbose=true), the system log related to the started task executed state will be shown. returned: changed type: list - sample: ["An error has ocurred"] - + sample: NC0000000 ZOSMACHINE 25240 12:40:30.15 OMVS0000 00000210.... """ from ansible.module_utils.basic import AnsibleModule From da3e8101694acc74774af2fdf0fdbbd2a00b94b2 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Tue, 16 Sep 2025 00:02:25 +0530 Subject: [PATCH 20/36] Update zos_started_task.py --- plugins/modules/zos_started_task.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index a93052f448..a56174e112 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -530,11 +530,6 @@ except ImportError: zoau_exceptions = ZOAUImportError(traceback.format_exc()) -# try: -# from zoautil_py import exceptions as zoau_exceptions -# except ImportError: -# zoau_exceptions = ZOAUImportError(traceback.format_exc()) - def execute_command(operator_cmd, started_task_name, execute_display_before=False, execute_display_after=False, timeout_s=1, **kwargs): """Execute operator command. From 0c19b4f7f8b3b9f7bd1d80755aece3a01ac74310 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Wed, 17 Sep 2025 16:30:04 +0530 Subject: [PATCH 21/36] Update zos_started_task.py --- plugins/modules/zos_started_task.py | 145 +++++++++++++--------------- 1 file changed, 68 insertions(+), 77 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index a56174e112..8629a546e3 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -19,7 +19,7 @@ DOCUMENTATION = r""" module: zos_started_task -version_added: 1.16.0 +version_added: 2.0.0 author: - "Ravella Surendra Babu (@surendrababuravella)" short_description: Perform operations on started tasks. @@ -38,21 +38,21 @@ completes, if it is registered as an element of the automatic restart manager. If the job or task is not registered or if you do not specify this parameter, MVS will not automatically restart the job or task. - Only applicable when state is cancelled or forced, otherwise is ignored. + - Only applicable when state is cancelled or forced, otherwise is ignored. required: false type: bool asid: description: - When state is cancelled or stopped or forced, asid is the hexadecimal address space identifier of the work unit you want to cancel, stop or force. - - When state=displayed asid is the hexadecimal address space identifier of the work unit of + - When state=displayed, asid is the hexadecimal address space identifier of the work unit of the task you get details from. required: false type: str device_type: description: - Option device_type is the type of the output device (if any) associated with the task. - Only applicable when state=started otherwise ignored. + - Only applicable when state=started otherwise ignored. required: false type: str device_number: @@ -60,14 +60,14 @@ - Option device_number is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. - Only applicable when state=started otherwise ignored. + - Only applicable when state=started otherwise ignored. required: false type: str dump: description: - A dump is to be taken. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) depends on the JCL for the job. - Only applicable when state=cancelled otherwise ignored. + - Only applicable when state=cancelled otherwise ignored. required: false type: bool identifier_name: @@ -83,7 +83,7 @@ - Option job_account specifies accounting data in the JCL JOB statement for the started task. If the source JCL was a job and has already accounting data, the value that is specified on this parameter overrides the accounting data in the source JCL. - Only applicable when state=started otherwise ignored. + - Only applicable when state=started otherwise ignored. required: false type: str job_name: @@ -103,7 +103,7 @@ - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. - Only applicable when state=started otherwise ignored. + - Only applicable when state=started otherwise ignored. required: false type: dict member_name: @@ -111,7 +111,7 @@ - Option member_name is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. - Only applicable when state=started otherwise ignored. + - Only applicable when state=started otherwise ignored. required: false type: str aliases: @@ -125,7 +125,8 @@ elements: str retry: description: - - I(retry) is applicable for only FORCE TCB. + - I(retry) is applicable for only FORCE TCB. + - Only applicable when state=forced otherwise ignored. required: false type: str choices: @@ -136,6 +137,7 @@ - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + - Only applicable when state=started otherwise ignored. required: false type: str choices: @@ -143,23 +145,23 @@ - 'NO' state: description: - - The desired state the started task should be after the module is executed. - If state=started and the started task is not found on the managed node, no action is taken, - module completes successfully with changed=False. - If state is cancelled , stopped or forced and the started task is not running on the - managed node, no action is taken, module completes successfully with changed=False. - If state is modified and the started task is not running, not found or modification was not - done, the module will fail. - If state is displayed the module will return the started task details. + - The desired state the started task should be after the module is executed. + - If state=started and the started task is not found on the managed node, no action is taken, + module completes successfully with changed=False. + - If state is cancelled , stopped or forced and the started task is not running on the + managed node, no action is taken, module completes successfully with changed=False. + - If state is modified and the started task is not running, not found or modification was not + done, the module will fail. + - If state is displayed the module will return the started task details. required: True type: str choices: - - started - - displayed - - modified - - cancelled - - stopped - - forced + - started + - displayed + - modified + - cancelled + - stopped + - forced subsystem: description: - The name of the subsystem that selects the task for processing. The name must be 1 - 4 @@ -169,26 +171,27 @@ type: str tcb_address: description: - - I(tcb_address) is a 6-digit hexadecimal TCB address of the task to terminate. + - I(tcb_address) is a 6-digit hexadecimal TCB address of the task to terminate. + - Only applicable when state=forced otherwise ignored. required: false type: str volume_serial: description: - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. - Only applicable when state=started otherwise ignored. + - Only applicable when state=started otherwise ignored. required: false type: str userid: description: - The user ID of the time-sharing user you want to cancel or force. - Only applicable when state=cancelled or state=forced , otherwise ignored. + - Only applicable when state=cancelled or state=forced , otherwise ignored. required: false type: str verbose: description: - - When verbose=true return system logs that describe the task’s execution. - Using this option will can return a big response depending on system’s load, also it could + - When verbose=true return system logs that describe the task execution. + Using this option will can return a big response depending on system load, also it could surface other programs activity. required: false type: bool @@ -359,17 +362,12 @@ cpu_time: description: The processor time used by the address space, including the initiator. This time does not include SRB time. - nnnnnnnn has one of these formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours: - sss.tttS - When time is less than 1000 seconds - hh.mm.ss - When time is at least 1000 seconds, but less than 100 hours - hhhhh.mm - When time is at least 100 hours - ******** - When time exceeds 100000 hours - NOTAVAIL - When the TOD clock is not working + cpu_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. + sss.tttS when time is less than 1000 seconds + hh.mm.ss when time is at least 1000 seconds, but less than 100 hours + hhhhh.mm when time is at least 100 hours + ******** when time exceeds 100000 hours + NOTAVAIL when the TOD clock is not working type: str sample: 000.008S dataspaces: @@ -396,19 +394,15 @@ sample: N/A elapsed_time: description: - -> For address spaces other than system address spaces, the elapsed time since job select time. - -> For system address spaces created before master scheduler initialization, the elapsed time since master scheduler initialization. - -> For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. - sss.tttS - When time is less than 1000 seconds - hh.mm.ss - When time is at least 1000 seconds, but less than 100 hours - hhhhh.mm - When time is at least 100 hours - ******** - When time exceeds 100000 hours - NOTAVAIL - When the TOD clock is not working + - For address spaces other than system address spaces, the elapsed time since job select time. + - For system address spaces created before master scheduler initialization, the elapsed time since master scheduler initialization. + - For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. + elapsed_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. + sss.tttS when time is less than 1000 seconds + hh.mm.ss when time is at least 1000 seconds, but less than 100 hours + hhhhh.mm when time is at least 100 hours + ******** when time exceeds 100000 hours + NOTAVAIL when the TOD clock is not working type: str sample: 812.983S priority: @@ -418,11 +412,10 @@ sample: 1 proc_step_name: description: - One of the following: - -> For APPC-initiated transactions, the user ID requesting the transaction. - -> The name of a step within a cataloged procedure that was called by the step specified in field sss. - -> Blank, if there is no cataloged procedure. - -> The identifier of the requesting transaction program. + - For APPC-initiated transactions, the user ID requesting the transaction. + - The name of a step within a cataloged procedure that was called by the step specified in field sss. + - Blank, if there is no cataloged procedure. + - The identifier of the requesting transaction program. type: str sample: VLF program_event_recording: @@ -462,7 +455,7 @@ description: The time when the started task started. type: str - sample: 2025-09-11 18:21:50.293644+00:00 + sample: "2025-09-11 18:21:50.293644+00:00" system_management_control: description: Number of outstanding step-must-complete requests. @@ -470,40 +463,38 @@ sample: 000 task_identifier: description: - One of the following: - -> The name of a system address space. - -> The name of a step, for a job or attached APPC transaction program attached by an initiator. - -> The identifier of a task created by the START command. - -> The name of a step that called a cataloged procedure. - -> STARTING, if initiation of a started job, system task, or attached APPC transaction program is incomplete. - -> *MASTER*, for the master address space. - -> The name of an initiator address space. + - The name of a system address space. + - The name of a step, for a job or attached APPC transaction program attached by an initiator. + - The identifier of a task created by the START command. + - The name of a step that called a cataloged procedure. + - STARTING if initiation of a started job, system task, or attached APPC transaction program is incomplete. + - MASTER* for the master address space. + - The name of an initiator address space. type: str sample: SPROC task_name: description: - The name of the started task. + - The name of the started task. type: str sample: SAMPLE task_status: description: - The status of the task can be one of the following. - -> IN for swapped in. - -> OUT for swapped out, ready to run. - -> OWT for swapped out, waiting, not ready to run. - -> OU* for in process of being swapped out. - -> IN* for in process of being swapped in. - -> NSW for non-swappable. + - IN for swapped in. + - OUT for swapped out, ready to run. + - OWT for swapped out, waiting, not ready to run. + - OU* for in process of being swapped out. + - IN* for in process of being swapped in. + - NSW for non-swappable. type: str sample: NSW task_type: description: - S for started task. + - S for started task. type: str sample: S workload_manager: description: - The name of the workload currently associated with the address space. + - The name of the workload currently associated with the address space. type: str sample: SYSTEM verbose_output: From 203cde7f64225d239b86df2040f6f42cdbc3629e Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:39:37 +0530 Subject: [PATCH 22/36] Updating testcases --- plugins/modules/zos_started_task.py | 17 ++- .../modules/test_zos_started_task_func.py | 123 ++++++++++-------- 2 files changed, 76 insertions(+), 64 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 8629a546e3..10fe006096 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -542,7 +542,7 @@ def execute_command(operator_cmd, started_task_name, execute_display_before=Fals Tuple containing the RC, standard out, standard err of the query script and started task parameters. """ - task_params = {} + task_params = [] # as of ZOAU v1.3.0, timeout is measured in centiseconds, therefore: timeout_c = 100 * timeout_s if execute_display_before: @@ -1289,6 +1289,7 @@ def run_module(): stderr=str(err) ) state = module.params.get('state') + userid = module.params.get('userid') wait_time_s = module.params.get('wait_time') verbose = module.params.get('verbose') kwargs = {} @@ -1305,10 +1306,10 @@ def run_module(): CANCELABLE: When force command used without using cancel command """ start_errmsg = ['ERROR', 'INVALID PARAMETER'] - stop_errmsg = ['NOT ACTIVE'] - display_errmsg = ['NOT ACTIVE'] - modify_errmsg = ['REJECTED', 'NOT ACTIVE'] - cancel_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'DUPLICATE NAME FOUND'] + stop_errmsg = ['NOT ACTIVE', 'INVALID PARAMETER'] + display_errmsg = ['NOT ACTIVE', 'INVALID PARAMETER'] + modify_errmsg = ['REJECTED', 'NOT ACTIVE', 'INVALID PARAMETER'] + cancel_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'DUPLICATE NAME FOUND', 'NON-CANCELABLE'] force_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'CANCELABLE', 'DUPLICATE NAME FOUND'] err_msg = [] kwargs = {} @@ -1332,11 +1333,13 @@ def run_module(): err_msg = stop_errmsg started_task_name, cmd = prepare_stop_command(module) elif state == "cancelled": - execute_display_before = True + if not userid: + execute_display_before = True err_msg = cancel_errmsg started_task_name, cmd = prepare_cancel_command(module) elif state == "forced": - execute_display_before = True + if not userid: + execute_display_before = True err_msg = force_errmsg started_task_name, cmd = prepare_force_command(module) elif state == "modified": diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 3c03fd1b62..1887438218 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -47,7 +47,6 @@ def test_start_task_with_invalid_member(ansible_zos_module): member_name = "SAMTASK" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None @@ -56,7 +55,6 @@ def test_start_task_with_invalid_member(ansible_zos_module): member_name = "SAMPLETASK" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("stderr") is not None @@ -70,7 +68,6 @@ def test_start_task_with_jobname_identifier(ansible_zos_module): identifier = "TESTER" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -84,7 +81,6 @@ def test_start_task_with_invalid_identifier(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("stderr") is not None @@ -95,7 +91,6 @@ def test_start_task_with_invalid_identifier(ansible_zos_module): identifier = "HELLO" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "S SAMPLE.HELLO" @@ -110,7 +105,6 @@ def test_start_task_with_invalid_jobaccount(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -124,7 +118,6 @@ def test_start_task_with_invalid_devicenum(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -138,7 +131,6 @@ def test_start_task_with_invalid_volumeserial(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "S SAMPLE,,12345A" @@ -152,7 +144,6 @@ def test_start_task_with_invalid_parameters(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "S SAMPLE,,,'KEY1'" @@ -165,7 +156,6 @@ def test_start_task_with_invalid_parameters(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "S SAMPLE,,123456,(KEY1,KEY2,KEY3)" @@ -179,7 +169,6 @@ def test_start_task_with_devicenum_devicetype_negative(ansible_zos_module): device_type = "TEST" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -193,7 +182,6 @@ def test_start_task_with_invalid_subsystem_negative(ansible_zos_module): subsystem = "MSTRS" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -209,7 +197,6 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): } ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -221,7 +208,6 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): } ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -234,7 +220,6 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): } ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == 'S VLF,KEY1=VALUE1,KEY2=VALUE2' @@ -248,7 +233,6 @@ def test_start_task_using_nonexisting_devicenum_negative(ansible_zos_module): device_number = "/ABCD" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == 'S SAMPLE,/ABCD' @@ -260,7 +244,6 @@ def test_display_task_negative(ansible_zos_module): identifier = "SAMPLE" ) for result in display_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -273,7 +256,6 @@ def test_stop_task_negative(ansible_zos_module): job_name = "SAMPLE" ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("stderr") is not None @@ -284,7 +266,6 @@ def test_stop_task_negative(ansible_zos_module): identifier = "SAMPLE" ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "P TESTER.SAMPLE" @@ -296,7 +277,6 @@ def test_modify_task_negative(ansible_zos_module): identifier = "SAMPLE" ) for result in modify_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -306,7 +286,6 @@ def test_modify_task_negative(ansible_zos_module): job_name = "TESTER" ) for result in modify_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -318,7 +297,6 @@ def test_modify_task_negative(ansible_zos_module): parameters = ["REPLACE", "VX=10"] ) for result in modify_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "F TESTER.SAMPLE,REPLACE,VX=10" @@ -330,7 +308,6 @@ def test_cancel_task_negative(ansible_zos_module): identifier = "SAMPLE" ) for result in cancel_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -341,7 +318,6 @@ def test_cancel_task_negative(ansible_zos_module): identifier = "SAMPLE" ) for result in cancel_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "C TESTER.SAMPLE" @@ -354,7 +330,6 @@ def test_cancel_task_negative(ansible_zos_module): verbose=True ) for result in cancel_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "C U=OMVSTEST,A=0012,DUMP" @@ -365,7 +340,6 @@ def test_cancel_task_negative(ansible_zos_module): armrestart = True ) for result in cancel_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -377,7 +351,6 @@ def test_force_task_negative(ansible_zos_module): identifier = "SAMPLE" ) for result in force_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -388,7 +361,6 @@ def test_force_task_negative(ansible_zos_module): identifier = "SAMPLE" ) for result in force_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "FORCE TESTER.SAMPLE" @@ -398,7 +370,6 @@ def test_force_task_negative(ansible_zos_module): armrestart = True ) for result in force_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -409,7 +380,6 @@ def test_force_task_negative(ansible_zos_module): retry = "YES" ) for result in force_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -420,7 +390,6 @@ def test_force_task_negative(ansible_zos_module): retry = "YES" ) for result in force_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None @@ -432,7 +401,6 @@ def test_force_task_negative(ansible_zos_module): retry = "YES" ) for result in force_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "FORCE TESTER.SAMPLE,TCB=000678,RETRY=YES" @@ -444,7 +412,6 @@ def test_force_task_negative(ansible_zos_module): verbose=True ) for result in force_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "FORCE U=OMVSTEST,TCB=000678,RETRY=YES" @@ -476,7 +443,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -488,7 +454,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): task_name = "SAMPLE" ) for result in force_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "FORCE SAMPLE" @@ -500,7 +465,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -515,7 +479,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): reus_asid = "YES" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -528,7 +491,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): task_name = "SAMPLE" ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is False assert result.get("stderr") is not None assert len(result.get("tasks")) > 0 @@ -540,7 +502,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): identifier = "TESTER" ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -555,7 +516,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -567,7 +527,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): task = "SAMPLE" ) for result in display_result.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -585,7 +544,6 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -597,9 +555,9 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): hosts.all.shell( cmd="drm {0}".format(data_set_name) ) - hosts.all.shell( - cmd="mrm '{0}(SAMPLE)'".format(PROC_PDS) - ) + # hosts.all.shell( + # cmd="mrm '{0}(SAMPLE)'".format(PROC_PDS) + # ) def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): try: @@ -627,7 +585,6 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -638,7 +595,6 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -660,7 +616,6 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): parameters = ["REPLACE" ,"NN=00"] ) for result in modify_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -671,7 +626,6 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): task = "VLF" ) for result in display_result.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -687,7 +641,6 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): asid = asid_val ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -700,7 +653,6 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): subsystem = "MSTR" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -712,7 +664,6 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): parameters = ["REPLACE" ,"NN=00"] ) for result in modify_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -724,7 +675,6 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): identifier = "TESTER" ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -735,7 +685,6 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): subsystem = "MSTR" ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -768,7 +717,6 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): ) for result in start_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -780,7 +728,6 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): ) for result in stop_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -792,4 +739,66 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): ) hosts.all.shell( cmd="mrm '{0}(SAMPLE2)'".format(PROC_PDS) - ) \ No newline at end of file + ) + +def test_force_and_start_with_icsf_task(ansible_zos_module): + hosts = ansible_zos_module + display_results = hosts.all.zos_started_task( + state = "displayed", + task = "ICSF" + ) + for result in display_results.contacted.values(): + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert result.get("cmd") == "D A,ICSF" + assert len(result.get("tasks")) > 0 + + cancel_results = hosts.all.zos_started_task( + state = "cancelled", + task = "ICSF" + ) + for result in cancel_results.contacted.values(): + assert result.get("changed") is False + assert result.get("rc") == 1 + assert result.get("stderr") != "" + + asid = result.get("tasks")[0].get("asid") + force_results = hosts.all.zos_started_task( + state = "forced", + task = "ICSF", + identifier = "ICSF", + asid = asid, + arm = True + ) + for result in force_results.contacted.values(): + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert result.get("cmd") == f"FORCE ICSF.ICSF,A={asid},ARM" + + start_results = hosts.all.zos_started_task( + state = "started", + member = "ICSF" + ) + for result in start_results.contacted.values(): + assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("stderr") == "" + assert result.get("cmd") == "S ICSF" + assert len(result.get("tasks")) > 0 + +# This testcase will be successful when a TSO session with user 'OMVSADM' is open. +# def test_cancel_using_userid(ansible_zos_module): +# hosts = ansible_zos_module +# display_results = hosts.all.zos_started_task( +# state = "cancelled", +# userid = "OMVSADM" +# ) +# for result in display_results.contacted.values(): +# print(result) +# assert result.get("changed") is True +# assert result.get("rc") == 0 +# assert result.get("stderr") == "" +# assert result.get("cmd") == "C U=OMVSADM" +# assert len(result.get("tasks")) > 0 From dd58e0d1be07b87ce886c515b39d199b45a53393 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:44:29 +0530 Subject: [PATCH 23/36] Update zos_started_task.py --- plugins/modules/zos_started_task.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 10fe006096..adfe4521d6 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -1303,6 +1303,7 @@ def run_module(): REJECTED: When modify command is not supported by respective started task. NOT LOGGED ON: When invalid userid passed in command. DUPLICATE NAME FOUND: When multiple started tasks exist with same name. + NON-CANCELABLE: When cancel command can't stop job and force command is needed. CANCELABLE: When force command used without using cancel command """ start_errmsg = ['ERROR', 'INVALID PARAMETER'] From a5f1325d669863807b53f2d2ea5ae205de1238be Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 18 Sep 2025 21:46:29 +0530 Subject: [PATCH 24/36] Update test_zos_started_task_func.py --- tests/functional/modules/test_zos_started_task_func.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 1887438218..6d922fa021 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -555,9 +555,9 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): hosts.all.shell( cmd="drm {0}".format(data_set_name) ) - # hosts.all.shell( - # cmd="mrm '{0}(SAMPLE)'".format(PROC_PDS) - # ) + hosts.all.shell( + cmd="mrm '{0}(SAMPLE)'".format(PROC_PDS) + ) def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): try: From 1a478740a915bc8e0b795db3baf7139295023a02 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:21:35 +0530 Subject: [PATCH 25/36] Resolving PR review comments --- plugins/module_utils/better_arg_parser.py | 8 ++++--- plugins/modules/zos_started_task.py | 27 +++++++++-------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/plugins/module_utils/better_arg_parser.py b/plugins/module_utils/better_arg_parser.py index cdb86b1cf1..62b9f247f2 100644 --- a/plugins/module_utils/better_arg_parser.py +++ b/plugins/module_utils/better_arg_parser.py @@ -256,7 +256,7 @@ def _dict_type(self, contents, resolved_dependencies): return contents def _basic_dict_type(self, contents, resolve_dependencies): - """Resolver for str type arguments. + """Resolver for basic dict type arguments. Parameters ---------- @@ -359,7 +359,8 @@ def _bool_type(self, contents, resolve_dependencies): return contents def _member_name_type(self, contents, resolve_dependencies): - """Resolver for data_set type arguments. + """Resolver for PDS/E member name type arguments. This is part of + zos_started_task member name validfation. Parameters ---------- @@ -391,7 +392,8 @@ def _member_name_type(self, contents, resolve_dependencies): return str(contents) def _identifier_name_type(self, contents, resolve_dependencies): - """Resolver for data_set type arguments. + """Resolver for identifier name type arguments. This is part of + zos_started_task identifier name validation. Parameters ---------- diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index adfe4521d6..6af4997be3 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -145,14 +145,13 @@ - 'NO' state: description: - - The desired state the started task should be after the module is executed. - - If state=started and the started task is not found on the managed node, no action is taken, - module completes successfully with changed=False. - - If state is cancelled , stopped or forced and the started task is not running on the - managed node, no action is taken, module completes successfully with changed=False. - - If state is modified and the started task is not running, not found or modification was not - done, the module will fail. - - If state is displayed the module will return the started task details. + - I(state) should be the desired state of the started task after the module is executed. + - If state is started and the respective member is not present on the managed node, then error will be thrown with rc=1, + changed=false and stderr which contains error details. + - If state is cancelled , modified, displayed, stopped or forced and the started task is not running on the managed node, + then error will be thrown with rc=1, changed=false and stderr contains error details. + - If state is displayed and the started task is running, then the module will return the started task details along with + changed=true. required: True type: str choices: @@ -198,9 +197,9 @@ default: false wait_time: description: - - Option wait_time is the total time that module zos_started_tak will wait for a submitted - task. The time begins when the module is executed on the managed node. Default value of 0 - means to wait the default amount of time supported by the opercmd utility. + - Option wait_time is the total time that module zos_started_task will wait for a submitted task in centiseconds. + The time begins when the module is executed on the managed node. Default value of 0 means to wait the default + amount of time supported by the opercmd utility. required: false default: 0 type: int @@ -1002,12 +1001,6 @@ def extract_keys(stdout): key = keys[key] if key in dsp_keys: data_space[key] = value - # key_val = current_task.get(key.lower()) - # if key_val: - # if isinstance(key_val, str): - # current_task[key.lower()] = [key_val, value] - # elif isinstance(key_val, list): - # current_task[key.lower()] = key_val + [value] else: current_task[key.lower()] = value if current_task.get("dataspaces"): From e097e771efc4a4ca041c57b80130ff5d7e156b0c Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:31:29 +0530 Subject: [PATCH 26/36] Update zos_started_task.py --- plugins/modules/zos_started_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 6af4997be3..cf07cbb106 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -148,7 +148,7 @@ - I(state) should be the desired state of the started task after the module is executed. - If state is started and the respective member is not present on the managed node, then error will be thrown with rc=1, changed=false and stderr which contains error details. - - If state is cancelled , modified, displayed, stopped or forced and the started task is not running on the managed node, + - If state is cancelled , modified, displayed, stopped or forced and the started task is not running on the managed node, then error will be thrown with rc=1, changed=false and stderr contains error details. - If state is displayed and the started task is running, then the module will return the started task details along with changed=true. From e39507bc2b59d29b910cac85cf1ced015bb01190 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:39:10 +0530 Subject: [PATCH 27/36] Added msg in all error responses --- plugins/modules/zos_started_task.py | 50 +++++++++++++------ .../modules/test_zos_started_task_func.py | 47 ++++++++++++++--- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index cf07cbb106..e7eb20f13a 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -92,6 +92,8 @@ while starting it. If job_name is not specified, then member_name is used as job_name. Otherwise, job_name is the started task job name used to find and apply the state selected. + - When state is displayed or modified or cancelled or stopped or forced, job_name is the + started task name. required: false type: str aliases: @@ -118,8 +120,8 @@ - member parameters: description: - - Program parameters passed to the started program, which might be a list in parentheses or - a string in single quotation marks + - Program parameters passed to the started program. + - Only applicable when state is started or modified otherwise ignored. required: false type: list elements: str @@ -166,6 +168,7 @@ - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. + - Only applicable when state=started otherwise ignored. required: false type: str tcb_address: @@ -300,7 +303,7 @@ returned: failure or skipped type: str sample: - File /u/user/file.txt is already missing on the system, skipping script + Command parameters are invalid. rc: description: - The return code is 0 when command executed successfully. @@ -521,7 +524,7 @@ zoau_exceptions = ZOAUImportError(traceback.format_exc()) -def execute_command(operator_cmd, started_task_name, execute_display_before=False, execute_display_after=False, timeout_s=1, **kwargs): +def execute_command(operator_cmd, started_task_name, execute_display_before=False, execute_display_after=False, timeout_s=0, **kwargs): """Execute operator command. Parameters @@ -545,19 +548,16 @@ def execute_command(operator_cmd, started_task_name, execute_display_before=Fals # as of ZOAU v1.3.0, timeout is measured in centiseconds, therefore: timeout_c = 100 * timeout_s if execute_display_before: - task_params = execute_display_command(started_task_name, timeout_c) + task_params = execute_display_command(started_task_name) response = opercmd.execute(operator_cmd, timeout_c, **kwargs) - if execute_display_after: - task_params = execute_display_command(started_task_name, timeout_c) - rc = response.rc stdout = response.stdout_response stderr = response.stderr_response return rc, stdout, stderr, task_params -def execute_display_command(started_task_name, timeout_s): +def execute_display_command(started_task_name, timeout=0): """Execute operator display command. Parameters @@ -573,7 +573,7 @@ def execute_display_command(started_task_name, timeout_s): List contains extracted parameters from display command output of started task """ cmd = "d a," + started_task_name - display_response = opercmd.execute(cmd, timeout_s) + display_response = opercmd.execute(cmd, timeout) task_params = [] if display_response.rc == 0 and display_response.stderr_response == "": task_params = extract_keys(display_response.stdout_response) @@ -1299,12 +1299,24 @@ def run_module(): NON-CANCELABLE: When cancel command can't stop job and force command is needed. CANCELABLE: When force command used without using cancel command """ - start_errmsg = ['ERROR', 'INVALID PARAMETER'] + start_errmsg = ['JCL ERROR', 'INVALID PARAMETER', 'DELIMITER ERROR', 'ERROR'] stop_errmsg = ['NOT ACTIVE', 'INVALID PARAMETER'] display_errmsg = ['NOT ACTIVE', 'INVALID PARAMETER'] modify_errmsg = ['REJECTED', 'NOT ACTIVE', 'INVALID PARAMETER'] cancel_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'DUPLICATE NAME FOUND', 'NON-CANCELABLE'] force_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'CANCELABLE', 'DUPLICATE NAME FOUND'] + error_details = { + 'JCL ERROR': 'Member is missing in PROCLIB or JCL is invalid or issue with JCL execution.', + 'INVALID PARAMETER': 'Command parameters are invalid.', + 'DELIMITER ERROR': 'Command parameters are invalid.', + 'ERROR': 'Member is missing in PROCLIB or JCL is invalid or issue with JCL execution.', + 'NOT ACTIVE': 'Started task is not active', + 'REJECTED': 'Started task is not accepting modification.', + 'NOT LOGGED ON': 'TSO user session is not active.', + 'DUPLICATE NAME FOUND': 'Multiple started tasks are running with same name.', + 'NON-CANCELABLE': 'Started task can not be cancelled.', + 'CANCELABLE': 'Started task should be cancelled.' + } err_msg = [] kwargs = {} @@ -1346,13 +1358,18 @@ def run_module(): rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, execute_display_after, timeout_s=wait_time_s, **kwargs) isFailed = False system_logs = "" - if err != "" or any(msg in out for msg in err_msg): + msg = "" + found_msg = next((msg for msg in err_msg if msg in out), None) + if err != "" or found_msg: isFailed = True # Fetch system logs to validate any error occured in execution if not isFailed or verbose: system_logs = fetch_logs(cmd.upper(), wait_time_s) - if any(msg in system_logs for msg in err_msg): - isFailed = True + # If sysout is not having error, then check system log as well to make sure no error occured + if not isFailed: + found_msg = next((msg for msg in err_msg if msg in system_logs), None) + if found_msg: + isFailed = True if not verbose: system_logs = "" current_state = "" @@ -1360,6 +1377,7 @@ def run_module(): if rc == 0: rc = 1 changed = False + msg = error_details[found_msg] stdout = out stderr = err if err == "" or err is None: @@ -1372,6 +1390,8 @@ def run_module(): stderr = err if state == "displayed": task_params = extract_keys(out) + elif execute_display_after: + task_params = execute_display_command(started_task_name) result = dict() @@ -1390,6 +1410,8 @@ def run_module(): stderr_lines=stderr.split('\n'), verbose_output=system_logs ) + if msg: + result['msg'] = msg module.exit_json(**result) diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 6d922fa021..39c49a8d51 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -42,6 +42,7 @@ # Input arguments validation def test_start_task_with_invalid_member(ansible_zos_module): hosts = ansible_zos_module + # Check with non-existing member start_results = hosts.all.zos_started_task( state = "started", member_name = "SAMTASK" @@ -49,7 +50,8 @@ def test_start_task_with_invalid_member(ansible_zos_module): for result in start_results.contacted.values(): assert result.get("changed") is False assert result.get("stderr") is not None - + assert result.get("msg") is not None + # Validating with member name more than 8 chars start_results = hosts.all.zos_started_task( state = "started", member_name = "SAMPLETASK" @@ -61,6 +63,7 @@ def test_start_task_with_invalid_member(ansible_zos_module): def test_start_task_with_jobname_identifier(ansible_zos_module): hosts = ansible_zos_module + # validate jobname and identifier with non-existing member start_results = hosts.all.zos_started_task( state = "started", member_name = "SAMPLE", @@ -74,17 +77,19 @@ def test_start_task_with_jobname_identifier(ansible_zos_module): def test_start_task_with_invalid_identifier(ansible_zos_module): hosts = ansible_zos_module + # validate using invalid identifier start_results = hosts.all.zos_started_task( state = "started", member_name = "SAMPTASK", identifier = "$HELLO" ) - for result in start_results.contacted.values(): assert result.get("changed") is False assert result.get("failed") is True assert result.get("stderr") is not None - + assert result.get("msg") is not None + + # validate using proper identifier and non-existing member start_results = hosts.all.zos_started_task( state = "started", member_name = "SAMPLE", @@ -94,16 +99,17 @@ def test_start_task_with_invalid_identifier(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "S SAMPLE.HELLO" + assert result.get("msg") is not None def test_start_task_with_invalid_jobaccount(ansible_zos_module): hosts = ansible_zos_module job_account = "(T043JM,JM00,1,0,0,This is the invalid job account information to test negative scenario)" + # validate invalid job_account with non-existing member start_results = hosts.all.zos_started_task( state = "started", member_name = "SAMPLE", job_account = job_account ) - for result in start_results.contacted.values(): assert result.get("changed") is False assert result.get("failed") is True @@ -111,12 +117,12 @@ def test_start_task_with_invalid_jobaccount(ansible_zos_module): def test_start_task_with_invalid_devicenum(ansible_zos_module): hosts = ansible_zos_module + # validate invalid devicenum with non-existing member start_results = hosts.all.zos_started_task( state = "started", member_name = "SAMPLE", device_number = "0870" ) - for result in start_results.contacted.values(): assert result.get("changed") is False assert result.get("failed") is True @@ -129,11 +135,11 @@ def test_start_task_with_invalid_volumeserial(ansible_zos_module): member_name = "SAMPLE", volume_serial = "12345A" ) - for result in start_results.contacted.values(): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "S SAMPLE,,12345A" + assert result.get("msg") is not None def test_start_task_with_invalid_parameters(ansible_zos_module): hosts = ansible_zos_module @@ -142,11 +148,11 @@ def test_start_task_with_invalid_parameters(ansible_zos_module): member_name = "SAMPLE", parameters = ["KEY1"] ) - for result in start_results.contacted.values(): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "S SAMPLE,,,'KEY1'" + assert result.get("msg") is not None start_results = hosts.all.zos_started_task( state = "started", @@ -154,11 +160,11 @@ def test_start_task_with_invalid_parameters(ansible_zos_module): parameters = ["KEY1", "KEY2", "KEY3"], volume_serial = "123456" ) - for result in start_results.contacted.values(): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "S SAMPLE,,123456,(KEY1,KEY2,KEY3)" + assert result.get("msg") is not None def test_start_task_with_devicenum_devicetype_negative(ansible_zos_module): hosts = ansible_zos_module @@ -200,6 +206,7 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None + start_results = hosts.all.zos_started_task( state = "started", member_name = "VLF", @@ -211,6 +218,7 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("failed") is True assert result.get("msg") is not None + start_results = hosts.all.zos_started_task( state = "started", member_name = "VLF", @@ -223,6 +231,8 @@ def test_start_task_with_invalid_keywordparams_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == 'S VLF,KEY1=VALUE1,KEY2=VALUE2' + assert result.get("msg") is not None + assert result.get("verbose_output") == "" def test_start_task_using_nonexisting_devicenum_negative(ansible_zos_module): @@ -236,6 +246,8 @@ def test_start_task_using_nonexisting_devicenum_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == 'S SAMPLE,/ABCD' + assert result.get("msg") is not None + assert result.get("verbose_output") == "" def test_display_task_negative(ansible_zos_module): hosts = ansible_zos_module @@ -259,6 +271,7 @@ def test_stop_task_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("failed") is True assert result.get("stderr") is not None + assert result.get("msg") is not None stop_results = hosts.all.zos_started_task( state = "stopped", @@ -269,6 +282,7 @@ def test_stop_task_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "P TESTER.SAMPLE" + assert result.get("msg") is not None def test_modify_task_negative(ansible_zos_module): hosts = ansible_zos_module @@ -300,6 +314,7 @@ def test_modify_task_negative(ansible_zos_module): assert result.get("changed") is False assert result.get("stderr") is not None assert result.get("cmd") == "F TESTER.SAMPLE,REPLACE,VX=10" + assert result.get("msg") is not None def test_cancel_task_negative(ansible_zos_module): hosts = ansible_zos_module @@ -322,6 +337,8 @@ def test_cancel_task_negative(ansible_zos_module): assert result.get("stderr") is not None assert result.get("cmd") == "C TESTER.SAMPLE" assert result.get("verbose_output") == "" + assert result.get("msg") is not None + cancel_results = hosts.all.zos_started_task( state = "cancelled", asid = "0012", @@ -334,6 +351,7 @@ def test_cancel_task_negative(ansible_zos_module): assert result.get("stderr") is not None assert result.get("cmd") == "C U=OMVSTEST,A=0012,DUMP" assert result.get("verbose_output") != "" + assert result.get("msg") is not None cancel_results = hosts.all.zos_started_task( state = "cancelled", userid = "OMVSADM", @@ -456,7 +474,9 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): for result in force_results.contacted.values(): assert result.get("changed") is False assert result.get("stderr") is not None + assert len(result.get("tasks")) > 0 assert result.get("cmd") == "FORCE SAMPLE" + assert result.get("msg") is not None assert "CANCELABLE - ISSUE CANCEL BEFORE FORCE" in result.get("stderr") stop_results = hosts.all.zos_started_task( @@ -587,6 +607,7 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): for result in start_results.contacted.values(): assert result.get("changed") is True assert result.get("rc") == 0 + assert len(result.get("tasks")) > 0 assert result.get("stderr") == "" stop_results = hosts.all.zos_started_task( @@ -597,6 +618,7 @@ def test_start_with_jobname_and_cancel_zos_started_task(ansible_zos_module): for result in stop_results.contacted.values(): assert result.get("changed") is True assert result.get("rc") == 0 + assert len(result.get("tasks")) > 0 assert result.get("stderr") == "" finally: @@ -618,6 +640,7 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): for result in modify_results.contacted.values(): assert result.get("changed") is True assert result.get("rc") == 0 + assert len(result.get("tasks")) > 0 assert result.get("stderr") == "" assert result.get("cmd") == "F VLF,REPLACE,NN=00" @@ -643,6 +666,7 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): for result in stop_results.contacted.values(): assert result.get("changed") is True assert result.get("rc") == 0 + assert len(result.get("tasks")) > 0 assert result.get("stderr") == "" assert result.get("cmd") == f"P VLF,A={asid_val}" @@ -655,6 +679,7 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): for result in start_results.contacted.values(): assert result.get("changed") is True assert result.get("rc") == 0 + assert len(result.get("tasks")) > 0 assert result.get("stderr") == "" modify_results = hosts.all.zos_started_task( @@ -667,6 +692,7 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 assert result.get("cmd") == "F VLF.TESTER,REPLACE,NN=00" stop_results = hosts.all.zos_started_task( @@ -678,6 +704,7 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 start_results = hosts.all.zos_started_task( state = "started", @@ -686,6 +713,7 @@ def test_stop_and_modify_with_vlf_task(ansible_zos_module): ) for result in start_results.contacted.values(): assert result.get("changed") is True + assert len(result.get("tasks")) > 0 assert result.get("rc") == 0 assert result.get("stderr") == "" @@ -720,6 +748,7 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 assert result.get("verbose_output") != "" stop_results = hosts.all.zos_started_task( @@ -731,6 +760,7 @@ def test_starting_and_cancel_zos_started_task_with_params(ansible_zos_module): assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" + assert len(result.get("tasks")) > 0 finally: hosts.all.file(path=temp_path, state="absent") @@ -762,6 +792,7 @@ def test_force_and_start_with_icsf_task(ansible_zos_module): assert result.get("changed") is False assert result.get("rc") == 1 assert result.get("stderr") != "" + assert len(result.get("tasks")) > 0 asid = result.get("tasks")[0].get("asid") force_results = hosts.all.zos_started_task( From 9b459efad63637c56bf4d38083eab93cd00bf613 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:24:05 +0530 Subject: [PATCH 28/36] Replacing error strings with error codes --- plugins/modules/zos_started_task.py | 43 +++++++++++++++-------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index e7eb20f13a..f5ae913805 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -1290,32 +1290,33 @@ def run_module(): Below error messages are used to detrmine if response has any error.When response could have any of below error message has explained below. - ERROR: Response contains this keyword when JCL contains syntax error. - INVALID PARAMETER: When invalid parameter passed in command line. - NOT ACTIVE: When started task with the given job name is not active + JCL ERROR - IEE122I: Response contains this keyword when JCL contains syntax error. + INVALID PARAMETER - IEE535I: When invalid parameter passed in command line. + NOT ACTIVE - IEE341I: When started task with the given job name is not active REJECTED: When modify command is not supported by respective started task. - NOT LOGGED ON: When invalid userid passed in command. - DUPLICATE NAME FOUND: When multiple started tasks exist with same name. - NON-CANCELABLE: When cancel command can't stop job and force command is needed. - CANCELABLE: When force command used without using cancel command + NOT LOGGED ON - IEE324I: When invalid userid passed in command. + DUPLICATE NAME FOUND - IEE842I: When multiple started tasks exist with same name. + NON-CANCELABLE - IEE838I: When cancel command can't stop job and force command is needed. + CANCELABLE - IEE838I: When force command used without using cancel command """ - start_errmsg = ['JCL ERROR', 'INVALID PARAMETER', 'DELIMITER ERROR', 'ERROR'] - stop_errmsg = ['NOT ACTIVE', 'INVALID PARAMETER'] - display_errmsg = ['NOT ACTIVE', 'INVALID PARAMETER'] - modify_errmsg = ['REJECTED', 'NOT ACTIVE', 'INVALID PARAMETER'] - cancel_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'DUPLICATE NAME FOUND', 'NON-CANCELABLE'] - force_errmsg = ['NOT ACTIVE', 'NOT LOGGED ON', 'INVALID PARAMETER', 'CANCELABLE', 'DUPLICATE NAME FOUND'] + start_errmsg = ['IEE122I', 'IEE535I', 'IEE307I', 'ERROR'] + stop_errmsg = ['IEE341I', 'IEE535I'] + display_errmsg = ['IEE341I', 'IEE535I', 'NOT FOUND'] + modify_errmsg = ['REJECTED', 'IEE341I', 'IEE535I', 'IEE311I'] + cancel_errmsg = ['IEE341I', 'IEE324I', 'IEE535I', 'IEE842I', 'NON-CANCELABLE'] + force_errmsg = ['IEE341I', 'IEE324I', 'IEE535I', 'CANCELABLE', 'IEE842I'] error_details = { - 'JCL ERROR': 'Member is missing in PROCLIB or JCL is invalid or issue with JCL execution.', - 'INVALID PARAMETER': 'Command parameters are invalid.', - 'DELIMITER ERROR': 'Command parameters are invalid.', + 'IEE122I': 'Specified member is missing or PROC/JOB contains incorrect JCL statements.', + 'IEE535I': 'A parameter on a command is not valid.', + 'IEE307I': 'Command parameter punctuation is incorrect or parameter is not followed by a blank.', 'ERROR': 'Member is missing in PROCLIB or JCL is invalid or issue with JCL execution.', - 'NOT ACTIVE': 'Started task is not active', + 'IEE341I': 'Started task is not active', 'REJECTED': 'Started task is not accepting modification.', - 'NOT LOGGED ON': 'TSO user session is not active.', - 'DUPLICATE NAME FOUND': 'Multiple started tasks are running with same name.', - 'NON-CANCELABLE': 'Started task can not be cancelled.', - 'CANCELABLE': 'Started task should be cancelled.' + 'IEE324I': 'The userid specified on the command is not currently active in the system..', + 'IEE842I': 'More than one active job with the specified name exist.', + 'NON-CANCELABLE': 'The task cannot be canceled. Use the FORCE ARM command.', + 'CANCELABLE': 'The task can be canceled. Use the CANCEL command.', + 'IEE311I': 'Required parameter is missing.' } err_msg = [] kwargs = {} From d40565e5b056ec8c66722d90556421d60f8d88a0 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:15:26 +0530 Subject: [PATCH 29/36] Updating documentation --- plugins/modules/zos_started_task.py | 171 +++++++++++++++------------- 1 file changed, 89 insertions(+), 82 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index f5ae913805..4bff7dda68 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -30,6 +30,7 @@ arm: description: - I(arm) indicates to execute normal task termination routines without causing address space destruction. + - Only applicable when state is forced, otherwise is ignored. required: false type: bool armrestart: @@ -45,14 +46,13 @@ description: - When state is cancelled or stopped or forced, asid is the hexadecimal address space identifier of the work unit you want to cancel, stop or force. - - When state=displayed, asid is the hexadecimal address space identifier of the work unit of - the task you get details from. + - Only applicable when state is stopped or cancelled or forced, otherwise is ignored. required: false type: str device_type: description: - Option device_type is the type of the output device (if any) associated with the task. - - Only applicable when state=started otherwise ignored. + - Only applicable when state is started otherwise ignored. required: false type: str device_number: @@ -67,7 +67,7 @@ description: - A dump is to be taken. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) depends on the JCL for the job. - - Only applicable when state=cancelled otherwise ignored. + - Only applicable when state is cancelled otherwise ignored. required: false type: bool identifier_name: @@ -83,7 +83,7 @@ - Option job_account specifies accounting data in the JCL JOB statement for the started task. If the source JCL was a job and has already accounting data, the value that is specified on this parameter overrides the accounting data in the source JCL. - - Only applicable when state=started otherwise ignored. + - Only applicable when state is started otherwise ignored. required: false type: str job_name: @@ -105,7 +105,7 @@ - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. - - Only applicable when state=started otherwise ignored. + - Only applicable when state is started otherwise ignored. required: false type: dict member_name: @@ -113,7 +113,7 @@ - Option member_name is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. - - Only applicable when state=started otherwise ignored. + - Only applicable when state is started otherwise ignored. required: false type: str aliases: @@ -128,7 +128,7 @@ retry: description: - I(retry) is applicable for only FORCE TCB. - - Only applicable when state=forced otherwise ignored. + - Only applicable when state= is forced otherwise ignored. required: false type: str choices: @@ -139,7 +139,7 @@ - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. - - Only applicable when state=started otherwise ignored. + - Only applicable when state is started otherwise ignored. required: false type: str choices: @@ -168,26 +168,26 @@ - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. - - Only applicable when state=started otherwise ignored. + - Only applicable when state is started otherwise ignored. required: false type: str tcb_address: description: - I(tcb_address) is a 6-digit hexadecimal TCB address of the task to terminate. - - Only applicable when state=forced otherwise ignored. + - Only applicable when state is forced otherwise ignored. required: false type: str volume_serial: description: - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. - - Only applicable when state=started otherwise ignored. + - Only applicable when state is started otherwise ignored. required: false type: str userid: description: - The user ID of the time-sharing user you want to cancel or force. - - Only applicable when state=cancelled or state=forced , otherwise ignored. + - Only applicable when state= is cancelled or forced , otherwise ignored. required: false type: str verbose: @@ -290,108 +290,114 @@ RETURN = r""" changed: description: - True if the state was changed, otherwise False. + - True if the state was changed, otherwise False. returned: always type: bool cmd: - description: Command executed via opercmd. - returned: changed - type: str - sample: S SAMPLE + description: + - Command executed via opercmd. + returned: changed + type: str + sample: S SAMPLE msg: - description: Failure or skip message returned by the module. - returned: failure or skipped - type: str - sample: - Command parameters are invalid. + description: + - Failure or skip message returned by the module. + returned: failure or skipped + type: str + sample: Command parameters are invalid. rc: - description: + description: - The return code is 0 when command executed successfully. - The return code is 1 when opercmd throws any error. - The return code is 5 when any parameter validation failed. - returned: changed - type: int - sample: 0 + returned: changed + type: int + sample: 0 state: - description: The final state of the started task, after execution.. - returned: changed - type: str - sample: S SAMPLE + description: + - The final state of the started task, after execution.. + returned: changed + type: str + sample: S SAMPLE stderr: - description: The STDERR from the command, may be empty. - returned: changed - type: str - sample: An error has ocurred. + description: + - The STDERR from the command, may be empty. + returned: changed + type: str + sample: An error has ocurred. stderr_lines: - description: List of strings containing individual lines from STDERR. - returned: changed - type: list - sample: ["An error has ocurred"] + description: + - List of strings containing individual lines from STDERR. + returned: changed + type: list + sample: ["An error has ocurred"] stdout: - description: The STDOUT from the command, may be empty. - returned: changed - type: str - sample: ISF031I CONSOLE OMVS0000 ACTIVATED. + description: + - The STDOUT from the command, may be empty. + returned: changed + type: str + sample: ISF031I CONSOLE OMVS0000 ACTIVATED. stdout_lines: - description: List of strings containing individual lines from STDOUT. - returned: changed - type: list - sample: ["Allocation to SYSEXEC completed."] + description: + - List of strings containing individual lines from STDOUT. + returned: changed + type: list + sample: ["Allocation to SYSEXEC completed."] tasks: description: - The output information for a list of started tasks matching specified criteria. - If no started task is found then this will return empty. + - The output information for a list of started tasks matching specified criteria. + - If no started task is found then this will return empty. returned: success type: list elements: dict contains: address_space_second_table_entry: description: - The control block used to manage memory for a started task + - The control block used to manage memory for a started task type: str sample: 03E78500 affinity: description: - The identifier of the processor, for up to any four processors, if the job requires the services of specific processors. - affinity=NONE means the job can run on any processor. + - The identifier of the processor, for up to any four processors, if the job requires the services of specific processors. + - affinity=NONE means the job can run on any processor. type: str sample: NONE asid: description: - Address space identifier (ASID), in hexadecimal. + - Address space identifier (ASID), in hexadecimal. type: str sample: 0054 cpu_time: description: - The processor time used by the address space, including the initiator. This time does not include SRB time. - cpu_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. - sss.tttS when time is less than 1000 seconds - hh.mm.ss when time is at least 1000 seconds, but less than 100 hours - hhhhh.mm when time is at least 100 hours - ******** when time exceeds 100000 hours - NOTAVAIL when the TOD clock is not working + - The processor time used by the address space, including the initiator. This time does not include SRB time. + - cpu_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. + sss.tttS when time is less than 1000 seconds + hh.mm.ss when time is at least 1000 seconds, but less than 100 hours + hhhhh.mm when time is at least 100 hours + ******** when time exceeds 100000 hours + NOTAVAIL when the TOD clock is not working type: str sample: 000.008S dataspaces: description: - The started task dataspaces details. + - The started task dataspaces details. returned: success type: list elements: dict contains: data_space_address_entry: description: - Central address of the data space ASTE. + - Central address of the data space ASTE. type: str sample: 058F2180 dataspace_name: description: - Data space name associated with the address space. + - Data space name associated with the address space. type: str sample: CIRRGMAP domain_number: description: - domain_number=N/A if the system is operating in goal mode. + - domain_number=N/A if the system is operating in goal mode. type: str sample: N/A elapsed_time: @@ -409,7 +415,7 @@ sample: 812.983S priority: description: - The priority of a started task is determined by the Workload Manager (WLM), based on the service class and importance assigned to it. + - The priority of a started task is determined by the Workload Manager (WLM), based on the service class and importance assigned to it. type: str sample: 1 proc_step_name: @@ -422,45 +428,45 @@ sample: VLF program_event_recording: description: - YES if A PER trap is active in the address space. - NO if No PER trap is active in the address space. + - YES if A PER trap is active in the address space. + - NO if No PER trap is active in the address space. type: str sample: NO program_name: description: - program_name=N/A if the system is operating in goal mode. + - program_name=N/A if the system is operating in goal mode. type: str sample: N/A queue_scan_count: description: - YES if the address space has been quiesced. - NO if the address space is not quiesced. + - YES if the address space has been quiesced. + - NO if the address space is not quiesced. type: str sample: NO resource_group: description: - The name of the resource group currently associated the service class. It can also be N/A if there is no resource group association. + - The name of the resource group currently associated the service class. It can also be N/A if there is no resource group association. type: str sample: N/A server: description: - YES if the address space is a server. - No if the address space is not a server. + - YES if the address space is a server. + - No if the address space is not a server. type: str sample: NO started_class_list: description: - The name of the service class currently associated with the address space. + - The name of the service class currently associated with the address space. type: str sample: SYSSTC started_time: description: - The time when the started task started. + - The time when the started task started. type: str sample: "2025-09-11 18:21:50.293644+00:00" system_management_control: description: - Number of outstanding step-must-complete requests. + - Number of outstanding step-must-complete requests. type: str sample: 000 task_identifier: @@ -500,10 +506,11 @@ type: str sample: SYSTEM verbose_output: - description: If C(verbose=true), the system log related to the started task executed state will be shown. - returned: changed - type: list - sample: NC0000000 ZOSMACHINE 25240 12:40:30.15 OMVS0000 00000210.... + description: + - If C(verbose=true), the system log related to the started task executed state will be shown. + returned: changed + type: list + sample: NC0000000 ZOSMACHINE 25240 12:40:30.15 OMVS0000 00000210.... """ from ansible.module_utils.basic import AnsibleModule @@ -524,7 +531,7 @@ zoau_exceptions = ZOAUImportError(traceback.format_exc()) -def execute_command(operator_cmd, started_task_name, execute_display_before=False, execute_display_after=False, timeout_s=0, **kwargs): +def execute_command(operator_cmd, started_task_name, execute_display_before=False, timeout_s=0, **kwargs): """Execute operator command. Parameters @@ -1356,7 +1363,7 @@ def run_module(): changed = False stdout = "" stderr = "" - rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, execute_display_after, timeout_s=wait_time_s, **kwargs) + rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, timeout_s=wait_time_s, **kwargs) isFailed = False system_logs = "" msg = "" From 61fa37bf44452dac9df483576e02e2eb462496e0 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 22 Sep 2025 22:19:10 +0530 Subject: [PATCH 30/36] Update zos_started_task.rst --- docs/source/modules/zos_started_task.rst | 485 ++++++++++++++++++++++- 1 file changed, 463 insertions(+), 22 deletions(-) diff --git a/docs/source/modules/zos_started_task.rst b/docs/source/modules/zos_started_task.rst index e7d97f8155..b711fc39f0 100644 --- a/docs/source/modules/zos_started_task.rst +++ b/docs/source/modules/zos_started_task.rst @@ -26,43 +26,80 @@ Parameters ---------- +arm + *arm* indicates to execute normal task termination routines without causing address space destruction. + + Only applicable when state is forced, otherwise is ignored. + + | **required**: False + | **type**: bool + + +armrestart + Indicates that the batch job or started task should be automatically restarted after the cancel completes, if it is registered as an element of the automatic restart manager. If the job or task is not registered or if you do not specify this parameter, MVS will not automatically restart the job or task. + + Only applicable when state is cancelled or forced, otherwise is ignored. + + | **required**: False + | **type**: bool + + asid - *asid* is a unique address space identifier which gets assigned to each running started task. + When state is cancelled or stopped or forced, asid is the hexadecimal address space identifier of the work unit you want to cancel, stop or force. + + Only applicable when state is stopped or cancelled or forced, otherwise is ignored. | **required**: False | **type**: str device_type - *device_type* is the type of the output device (if any) associated with the task. + Option device_type is the type of the output device (if any) associated with the task. + + Only applicable when state is started otherwise ignored. | **required**: False | **type**: str device_number - *device_number* is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. + Option device_number is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. + + Only applicable when state=started otherwise ignored. | **required**: False | **type**: str +dump + A dump is to be taken. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) depends on the JCL for the job. + + Only applicable when state is cancelled otherwise ignored. + + | **required**: False + | **type**: bool + + identifier_name - *identifier_name* is the name that identifies the task to be started. This name can be up to 8 characters long. The first character must be alphabetical. + Option identifier_name is the name that identifies the task. This name can be up to 8 characters long. The first character must be alphabetical. | **required**: False | **type**: str job_account - *job_account* specifies accounting data in the JCL JOB statement for the started task. If the source JCL was a job and has already accounting data, the value that is specified on this parameter overrides the accounting data in the source JCL. + Option job_account specifies accounting data in the JCL JOB statement for the started task. If the source JCL was a job and has already accounting data, the value that is specified on this parameter overrides the accounting data in the source JCL. + + Only applicable when state is started otherwise ignored. | **required**: False | **type**: str job_name - *job_name* is a name which should be assigned to a started task while starting it. If job_name is not specified, then member_name is used as job_name. + When state=started job_name is a name which should be assigned to a started task while starting it. If job_name is not specified, then member_name is used as job_name. Otherwise, job_name is the started task job name used to find and apply the state selected. + + When state is displayed or modified or cancelled or stopped or forced, job_name is the started task name. | **required**: False | **type**: str @@ -71,46 +108,79 @@ job_name keyword_parameters Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. + Only applicable when state is started otherwise ignored. + | **required**: False - | **type**: str + | **type**: dict member_name - *member_name* is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. + Option member_name is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. + + Only applicable when state is started otherwise ignored. | **required**: False | **type**: str -operation - The started task operation which needs to be performed. +parameters + Program parameters passed to the started program. - If *operation=start* and the data set does not exist on the managed node, no action taken, module completes successfully with *changed=False*. + Only applicable when state is started or modified otherwise ignored. + | **required**: False + | **type**: list + | **elements**: str - | **required**: True - | **type**: str - | **choices**: start, stop, modify, display, force, cancel +retry + *retry* is applicable for only FORCE TCB. -parameters - Program parameters passed to the started program, which might be a list in parentheses or a string in single quotation marks + Only applicable when state= is forced otherwise ignored. | **required**: False | **type**: str + | **choices**: YES, NO reus_asid When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + Only applicable when state is started otherwise ignored. + | **required**: False | **type**: str | **choices**: YES, NO -subsystem_name +state + *state* should be the desired state of the started task after the module is executed. + + If state is started and the respective member is not present on the managed node, then error will be thrown with rc=1, changed=false and stderr which contains error details. + + If state is cancelled , modified, displayed, stopped or forced and the started task is not running on the managed node, then error will be thrown with rc=1, changed=false and stderr contains error details. + + If state is displayed and the started task is running, then the module will return the started task details along with changed=true. + + | **required**: True + | **type**: str + | **choices**: started, displayed, modified, cancelled, stopped, forced + + +subsystem The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. + Only applicable when state is started otherwise ignored. + + | **required**: False + | **type**: str + + +tcb_address + *tcb_address* is a 6-digit hexadecimal TCB address of the task to terminate. + + Only applicable when state is forced otherwise ignored. + | **required**: False | **type**: str @@ -118,27 +188,50 @@ subsystem_name volume_serial If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. + Only applicable when state is started otherwise ignored. + + | **required**: False + | **type**: str + + +userid + The user ID of the time-sharing user you want to cancel or force. + + Only applicable when state= is cancelled or forced , otherwise ignored. + | **required**: False | **type**: str verbose - Return System logs that describe the task's execution. + When verbose=true return system logs that describe the task execution. Using this option will can return a big response depending on system load, also it could surface other programs activity. | **required**: False | **type**: bool | **default**: False -wait_time_s - Option *wait_time_s* is the total time that module `zos_started_tak <./zos_started_task.html>`_ will wait for a submitted task. The time begins when the module is executed on the managed node. +wait_time + Option wait_time is the total time that module zos_started_task will wait for a submitted task in centiseconds. The time begins when the module is executed on the managed node. Default value of 0 means to wait the default amount of time supported by the opercmd utility. | **required**: False | **type**: int - | **default**: 5 + | **default**: 0 + +Attributes +---------- +action + | **support**: none + | **description**: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller. +async + | **support**: full + | **description**: Supports being used with the ``async`` keyword. +check_mode + | **support**: full + | **description**: Can run in check_mode and return changed status prediction without modifying target. If not supported, the action will be skipped. @@ -150,15 +243,363 @@ Examples - name: Start a started task using member name. zos_started_task: + state: "started" + member: "PROCAPP" + - name: Start a started task using member name and identifier. + zos_started_task: + state: "started" member: "PROCAPP" - operation: "start" + identifier: "SAMPLE" + - name: Start a started task using member name and job. + zos_started_task: + state: "started" + member: "PROCAPP" + job_name: "SAMPLE" + - name: Start a started task using member name, job and enable verbose. + zos_started_task: + state: "started" + member: "PROCAPP" + job_name: "SAMPLE" + verbose: True + - name: Start a started task using member name, subsystem and enable reuse asid. + zos_started_task: + state: "started" + member: "PROCAPP" + subsystem: "MSTR" + reus_asid: "YES" + - name: Display a started task using started task name. + zos_started_task: + state: "displayed" + task_name: "PROCAPP" + - name: Display started tasks using matching regex. + zos_started_task: + state: "displayed" + task_name: "s*" + - name: Display all started tasks. + zos_started_task: + state: "displayed" + task_name: "all" + - name: Cancel a started tasks using task name. + zos_started_task: + state: "cancelled" + task_name: "SAMPLE" + - name: Cancel a started tasks using task name and asid. + zos_started_task: + state: "cancelled" + task_name: "SAMPLE" + asid: 0014 + - name: Cancel a started tasks using task name and asid. + zos_started_task: + state: "modified" + task_name: "SAMPLE" + parameters: ["XX=12"] + - name: Stop a started task using task name. + zos_started_task: + state: "stopped" + task_name: "SAMPLE" + - name: Stop a started task using task name, identifier and asid. + zos_started_task: + state: "stopped" + task_name: "SAMPLE" + identifier: "SAMPLE" + asid: 00A5 + - name: Force a started task using task name. + zos_started_task: + state: "forced" + task_name: "SAMPLE" + + + + + + + + + + +Return Values +------------- + + +changed + True if the state was changed, otherwise False. + + | **returned**: always + | **type**: bool + +cmd + Command executed via opercmd. + + | **returned**: changed + | **type**: str + | **sample**: S SAMPLE + +msg + Failure or skip message returned by the module. + + | **returned**: failure or skipped + | **type**: str + | **sample**: Command parameters are invalid. + +rc + The return code is 0 when command executed successfully. + + The return code is 1 when opercmd throws any error. + + The return code is 5 when any parameter validation failed. + + | **returned**: changed + | **type**: int + +state + The final state of the started task, after execution.. + + | **returned**: changed + | **type**: str + | **sample**: S SAMPLE + +stderr + The STDERR from the command, may be empty. + + | **returned**: changed + | **type**: str + | **sample**: An error has ocurred. + +stderr_lines + List of strings containing individual lines from STDERR. + + | **returned**: changed + | **type**: list + | **sample**: + + .. code-block:: json + + [ + "An error has ocurred" + ] + +stdout + The STDOUT from the command, may be empty. + + | **returned**: changed + | **type**: str + | **sample**: ISF031I CONSOLE OMVS0000 ACTIVATED. + +stdout_lines + List of strings containing individual lines from STDOUT. + + | **returned**: changed + | **type**: list + | **sample**: + + .. code-block:: json + + [ + "Allocation to SYSEXEC completed." + ] + +tasks + The output information for a list of started tasks matching specified criteria. + + If no started task is found then this will return empty. + + | **returned**: success + | **type**: list + | **elements**: dict + + address_space_second_table_entry + The control block used to manage memory for a started task + + | **type**: str + | **sample**: 03E78500 + + affinity + The identifier of the processor, for up to any four processors, if the job requires the services of specific processors. + + affinity=NONE means the job can run on any processor. + + | **type**: str + | **sample**: NONE + + asid + Address space identifier (ASID), in hexadecimal. + + | **type**: str + | **sample**: 44 + + cpu_time + The processor time used by the address space, including the initiator. This time does not include SRB time. + + cpu_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours ******** when time exceeds 100000 hours NOTAVAIL when the TOD clock is not working + + | **type**: str + | **sample**: 000.008S + + dataspaces + The started task dataspaces details. + + | **returned**: success + | **type**: list + | **elements**: dict + + data_space_address_entry + Central address of the data space ASTE. + + | **type**: str + | **sample**: 058F2180 + + dataspace_name + Data space name associated with the address space. + + | **type**: str + | **sample**: CIRRGMAP + + + domain_number + domain_number=N/A if the system is operating in goal mode. + + | **type**: str + | **sample**: N/A + + elapsed_time + For address spaces other than system address spaces, the elapsed time since job select time. + + For system address spaces created before master scheduler initialization, the elapsed time since master scheduler initialization. + + For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. elapsed_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours ******** when time exceeds 100000 hours NOTAVAIL when the TOD clock is not working + + | **type**: str + | **sample**: 812.983S + + priority + The priority of a started task is determined by the Workload Manager (WLM), based on the service class and importance assigned to it. + + | **type**: str + | **sample**: 1 + + proc_step_name + For APPC-initiated transactions, the user ID requesting the transaction. + + The name of a step within a cataloged procedure that was called by the step specified in field sss. + + Blank, if there is no cataloged procedure. + + The identifier of the requesting transaction program. + + | **type**: str + | **sample**: VLF + + program_event_recording + YES if A PER trap is active in the address space. + + NO if No PER trap is active in the address space. + + | **type**: str + + program_name + program_name=N/A if the system is operating in goal mode. + + | **type**: str + | **sample**: N/A + + queue_scan_count + YES if the address space has been quiesced. + + NO if the address space is not quiesced. + + | **type**: str + + resource_group + The name of the resource group currently associated the service class. It can also be N/A if there is no resource group association. + + | **type**: str + | **sample**: N/A + + server + YES if the address space is a server. + + No if the address space is not a server. + + | **type**: str + + started_class_list + The name of the service class currently associated with the address space. + + | **type**: str + | **sample**: SYSSTC + + started_time + The time when the started task started. + + | **type**: str + | **sample**: 2025-09-11 18:21:50.293644+00:00 + + system_management_control + Number of outstanding step-must-complete requests. + + | **type**: str + + task_identifier + The name of a system address space. + + The name of a step, for a job or attached APPC transaction program attached by an initiator. + + The identifier of a task created by the START command. + + The name of a step that called a cataloged procedure. + + STARTING if initiation of a started job, system task, or attached APPC transaction program is incomplete. + + MASTER* for the master address space. + + The name of an initiator address space. + + | **type**: str + | **sample**: SPROC + + task_name + The name of the started task. + + | **type**: str + | **sample**: SAMPLE + + task_status + IN for swapped in. + + OUT for swapped out, ready to run. + + OWT for swapped out, waiting, not ready to run. + + OU* for in process of being swapped out. + + IN* for in process of being swapped in. + + NSW for non-swappable. + + | **type**: str + | **sample**: NSW + task_type + S for started task. + | **type**: str + | **sample**: S + workload_manager + The name of the workload currently associated with the address space. + | **type**: str + | **sample**: SYSTEM +verbose_output + If ``verbose=true``, the system log related to the started task executed state will be shown. + | **returned**: changed + | **type**: list + | **sample**: + .. code-block:: json + "NC0000000 ZOSMACHINE 25240 12:40:30.15 OMVS0000 00000210...." From b47b3de251caa200f4c66f6d75f6f20c77aea8b7 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Wed, 24 Sep 2025 20:22:44 +0530 Subject: [PATCH 31/36] Addressing PR comments --- plugins/module_utils/better_arg_parser.py | 2 +- plugins/modules/zos_started_task.py | 125 ++++++++++------------ 2 files changed, 58 insertions(+), 69 deletions(-) diff --git a/plugins/module_utils/better_arg_parser.py b/plugins/module_utils/better_arg_parser.py index 62b9f247f2..f4a52eee09 100644 --- a/plugins/module_utils/better_arg_parser.py +++ b/plugins/module_utils/better_arg_parser.py @@ -360,7 +360,7 @@ def _bool_type(self, contents, resolve_dependencies): def _member_name_type(self, contents, resolve_dependencies): """Resolver for PDS/E member name type arguments. This is part of - zos_started_task member name validfation. + zos_started_task member name validation. Parameters ---------- diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 4bff7dda68..3704122aa1 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -30,49 +30,49 @@ arm: description: - I(arm) indicates to execute normal task termination routines without causing address space destruction. - - Only applicable when state is forced, otherwise is ignored. + - Only applicable when I(state) is C(forced), otherwise ignored. required: false type: bool armrestart: description: - - Indicates that the batch job or started task should be automatically restarted after the cancel + - Indicates that the batch job or started task should be automatically restarted after CANCEL completes, if it is registered as an element of the automatic restart manager. If the job or task is not registered or if you do not specify this parameter, MVS will not automatically restart the job or task. - - Only applicable when state is cancelled or forced, otherwise is ignored. + - Only applicable when I(state) is C(cancelled) or C(forced), otherwise ignored. required: false type: bool asid: description: - - When state is cancelled or stopped or forced, asid is the hexadecimal address space + - When I(state) is C(cancelled), C(stopped) or C(forced), I(asid) is the hexadecimal address space identifier of the work unit you want to cancel, stop or force. - - Only applicable when state is stopped or cancelled or forced, otherwise is ignored. + - Only applicable when I(state) is C(stopped), C(cancelled), or C(forced), otherwise ignored. required: false type: str device_type: description: - - Option device_type is the type of the output device (if any) associated with the task. - - Only applicable when state is started otherwise ignored. + - Option I(device_type) is the type of the output device (if any) associated with the task. + - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str device_number: description: - - Option device_number is the number of the device to be started. A device number is 3 or 4 + - Option I(device_number) is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. - - Only applicable when state=started otherwise ignored. + - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str dump: description: - A dump is to be taken. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) depends on the JCL for the job. - - Only applicable when state is cancelled otherwise ignored. + - Only applicable when I(state) is C(cancelled), otherwise ignored. required: false type: bool identifier_name: description: - - Option identifier_name is the name that identifies the task. This name can be up to 8 + - Option I(identifier_name) is the name that identifies the task. This name can be up to 8 characters long. The first character must be alphabetical. required: false type: str @@ -80,19 +80,19 @@ - identifier job_account: description: - - Option job_account specifies accounting data in the JCL JOB statement for the started + - Option I(job_account) specifies accounting data in the JCL JOB statement for the started task. If the source JCL was a job and has already accounting data, the value that is specified on this parameter overrides the accounting data in the source JCL. - - Only applicable when state is started otherwise ignored. + - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str job_name: description: - - When state=started job_name is a name which should be assigned to a started task - while starting it. If job_name is not specified, then member_name is used as job_name. - Otherwise, job_name is the started task job name used to find and apply the state + - When I(state) is started, I(job_name) is a name which should be assigned to a started task + while starting it. If I(job_name) is not specified, then I(member_name) is used as I(job_name). + Otherwise, I(job_name) is the started task job name used to find and apply the state selected. - - When state is displayed or modified or cancelled or stopped or forced, job_name is the + - When I(state) is C(displayed), C(modified), C(cancelled), C(stopped), or C(forced), I(job_name) is the started task name. required: false type: str @@ -105,15 +105,15 @@ - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. - - Only applicable when state is started otherwise ignored. + - Only applicable when I(state) is C(started), otherwise ignored. required: false type: dict member_name: description: - - Option member_name is a 1 - 8 character name of a member of a partitioned data set that + - Option I(member_name) is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. - - Only applicable when state is started otherwise ignored. + - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str aliases: @@ -121,14 +121,14 @@ parameters: description: - Program parameters passed to the started program. - - Only applicable when state is started or modified otherwise ignored. + - Only applicable when I(state) is C(started) or C(modified), otherwise ignored. required: false type: list elements: str retry: description: - I(retry) is applicable for only FORCE TCB. - - Only applicable when state= is forced otherwise ignored. + - Only applicable when I(state) is C(forced), otherwise ignored. required: false type: str choices: @@ -139,7 +139,7 @@ - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. - - Only applicable when state is started otherwise ignored. + - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str choices: @@ -148,12 +148,12 @@ state: description: - I(state) should be the desired state of the started task after the module is executed. - - If state is started and the respective member is not present on the managed node, then error will be thrown with rc=1, - changed=false and stderr which contains error details. - - If state is cancelled , modified, displayed, stopped or forced and the started task is not running on the managed node, - then error will be thrown with rc=1, changed=false and stderr contains error details. - - If state is displayed and the started task is running, then the module will return the started task details along with - changed=true. + - If I(state) is C(started) and the respective member is not present on the managed node, then error will be thrown with C(rc=1), + C(changed=false) and I(stderr) which contains error details. + - If I(state) is C(cancelled), C(modified), C(displayed), C(stopped) or C(forced) and the started task is not running on the managed node, + then error will be thrown with C(rc=1), C(changed=false) and I(stderr) contains error details. + - If I(state) is C(displayed) and the started task is running, then the module will return the started task details along with + C(changed=true). required: True type: str choices: @@ -168,39 +168,39 @@ - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. - - Only applicable when state is started otherwise ignored. + - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str tcb_address: description: - I(tcb_address) is a 6-digit hexadecimal TCB address of the task to terminate. - - Only applicable when state is forced otherwise ignored. + - Only applicable when I(state) is C(forced), otherwise ignored. required: false type: str volume_serial: description: - - If devicetype is a tape or direct-access device, the volume serial number of the volume is + - If I(device_type) is a tape or direct-access device, the volume serial number of the volume is mounted on the device. - - Only applicable when state is started otherwise ignored. + - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str userid: description: - The user ID of the time-sharing user you want to cancel or force. - - Only applicable when state= is cancelled or forced , otherwise ignored. + - Only applicable when I(state) is C(cancelled) or C(forced), otherwise ignored. required: false type: str verbose: description: - - When verbose=true return system logs that describe the task execution. - Using this option will can return a big response depending on system load, also it could + - When C(verbose=true), return system logs that describe the task execution. + Using this option, can return a big response depending on system load, also it could surface other programs activity. required: false type: bool default: false wait_time: description: - - Option wait_time is the total time that module zos_started_task will wait for a submitted task in centiseconds. + - Option I(wait_time) is the total time that module zos_started_task will wait for a submitted task in centiseconds. The time begins when the module is executed on the managed node. Default value of 0 means to wait the default amount of time supported by the opercmd utility. required: false @@ -324,13 +324,13 @@ - The STDERR from the command, may be empty. returned: changed type: str - sample: An error has ocurred. + sample: An error has occurred. stderr_lines: description: - List of strings containing individual lines from STDERR. returned: changed type: list - sample: ["An error has ocurred"] + sample: ["An error has occurred"] stdout: description: - The STDOUT from the command, may be empty. @@ -380,7 +380,7 @@ sample: 000.008S dataspaces: description: - - The started task dataspaces details. + - The started task data spaces details. returned: success type: list elements: dict @@ -405,7 +405,7 @@ - For address spaces other than system address spaces, the elapsed time since job select time. - For system address spaces created before master scheduler initialization, the elapsed time since master scheduler initialization. - For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. - elapsed_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. + elapsed_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours @@ -616,12 +616,6 @@ def validate_and_prepare_start_command(module): keyword_parameters_string = "" device = device_type if device_type else device_number # Validations - if device_number and device_type: - module.fail_json( - rc=5, - msg="device_number and device_type are mutually exclusive.", - changed=False - ) if job_account and len(job_account) > 55: module.fail_json( rc=5, @@ -897,12 +891,6 @@ def prepare_force_command(module): msg="The TCB address of the task should be exactly 6-digit hexadecimal.", changed=False ) - if retry and not tcb_address: - module.fail_json( - rc=5, - msg="The RETRY parameter is valid with the TCB parameter only.", - changed=False - ) if userid and armrestart: module.fail_json( rc=5, @@ -1185,6 +1173,7 @@ def run_module(): mutually_exclusive=[ ['device_number', 'device_type'] ], + required_by={'retry': ['tcb_address']}, supports_check_mode=True ) @@ -1294,8 +1283,7 @@ def run_module(): verbose = module.params.get('verbose') kwargs = {} """ - Below error messages are used to detrmine if response has any error.When - response could have any of below error message has explained below. + Below error messages or error codes are used to determine if response has any error. JCL ERROR - IEE122I: Response contains this keyword when JCL contains syntax error. INVALID PARAMETER - IEE535I: When invalid parameter passed in command line. @@ -1306,12 +1294,12 @@ def run_module(): NON-CANCELABLE - IEE838I: When cancel command can't stop job and force command is needed. CANCELABLE - IEE838I: When force command used without using cancel command """ - start_errmsg = ['IEE122I', 'IEE535I', 'IEE307I', 'ERROR'] - stop_errmsg = ['IEE341I', 'IEE535I'] - display_errmsg = ['IEE341I', 'IEE535I', 'NOT FOUND'] - modify_errmsg = ['REJECTED', 'IEE341I', 'IEE535I', 'IEE311I'] - cancel_errmsg = ['IEE341I', 'IEE324I', 'IEE535I', 'IEE842I', 'NON-CANCELABLE'] - force_errmsg = ['IEE341I', 'IEE324I', 'IEE535I', 'CANCELABLE', 'IEE842I'] + start_errmsg = ['IEE122I', 'IEE535I', 'IEE307I', 'ERROR', 'IEE708I'] + stop_errmsg = ['IEE341I', 'IEE535I', 'IEE708I'] + display_errmsg = ['IEE341I', 'IEE535I', 'NOT FOUND', 'IEE708I'] + modify_errmsg = ['REJECTED', 'IEE341I', 'IEE535I', 'IEE311I', 'IEE708I'] + cancel_errmsg = ['IEE341I', 'IEE324I', 'IEE535I', 'IEE842I', 'NON-CANCELABLE', 'IEE708I'] + force_errmsg = ['IEE341I', 'IEE324I', 'IEE535I', 'CANCELABLE', 'IEE842I', 'IEE708I'] error_details = { 'IEE122I': 'Specified member is missing or PROC/JOB contains incorrect JCL statements.', 'IEE535I': 'A parameter on a command is not valid.', @@ -1323,7 +1311,8 @@ def run_module(): 'IEE842I': 'More than one active job with the specified name exist.', 'NON-CANCELABLE': 'The task cannot be canceled. Use the FORCE ARM command.', 'CANCELABLE': 'The task can be canceled. Use the CANCEL command.', - 'IEE311I': 'Required parameter is missing.' + 'IEE311I': 'Required parameter is missing.', + 'IEE708I': 'The value of a keyword specified on a command is incorrect.' } err_msg = [] kwargs = {} @@ -1364,24 +1353,24 @@ def run_module(): stdout = "" stderr = "" rc, out, err, task_params = execute_command(cmd, started_task_name, execute_display_before, timeout_s=wait_time_s, **kwargs) - isFailed = False + is_failed = False system_logs = "" msg = "" found_msg = next((msg for msg in err_msg if msg in out), None) if err != "" or found_msg: - isFailed = True + is_failed = True # Fetch system logs to validate any error occured in execution - if not isFailed or verbose: + if not is_failed or verbose: system_logs = fetch_logs(cmd.upper(), wait_time_s) # If sysout is not having error, then check system log as well to make sure no error occured - if not isFailed: + if not is_failed: found_msg = next((msg for msg in err_msg if msg in system_logs), None) if found_msg: - isFailed = True + is_failed = True if not verbose: system_logs = "" current_state = "" - if isFailed: + if is_failed: if rc == 0: rc = 1 changed = False From d87d31454849d78ab640fb7d47e02d6bb3d175cf Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Thu, 25 Sep 2025 20:42:22 +0530 Subject: [PATCH 32/36] resolving PR comments --- plugins/modules/zos_started_task.py | 133 +++++++++--------- .../modules/test_zos_started_task_func.py | 14 +- 2 files changed, 76 insertions(+), 71 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 3704122aa1..793ddb1c78 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -35,10 +35,9 @@ type: bool armrestart: description: - - Indicates that the batch job or started task should be automatically restarted after CANCEL - completes, if it is registered as an element of the automatic restart manager. If the job or - task is not registered or if you do not specify this parameter, MVS will not automatically - restart the job or task. + - Indicates that the batch job or started task should be automatically restarted after CANCEL or FORCE + completes, if it is registered as an element of the automatic restart manager. If the job or task is + not registered or if you do not specify this parameter, MVS will not automatically restart the job or task. - Only applicable when I(state) is C(cancelled) or C(forced), otherwise ignored. required: false type: bool @@ -51,21 +50,20 @@ type: str device_type: description: - - Option I(device_type) is the type of the output device (if any) associated with the task. + - Type of the output device (if any) associated with the task. - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str device_number: description: - - Option I(device_number) is the number of the device to be started. A device number is 3 or 4 - hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit - number. + - Number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must + precede a 4-digit number but is not before a 3-digit number. - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str dump: description: - - A dump is to be taken. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) + - Whether to perform a dump. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) depends on the JCL for the job. - Only applicable when I(state) is C(cancelled), otherwise ignored. required: false @@ -80,9 +78,9 @@ - identifier job_account: description: - - Option I(job_account) specifies accounting data in the JCL JOB statement for the started - task. If the source JCL was a job and has already accounting data, the value that is - specified on this parameter overrides the accounting data in the source JCL. + - Specifies accounting data in the JCL JOB statement for the started task. If the source JCL + was a job and has already accounting data, the value that is specified on this parameter + overrides the accounting data in the source JCL. - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str @@ -102,17 +100,16 @@ - task_name keyword_parameters: description: - - Any appropriate keyword parameter that you specify to override the corresponding - parameter in the cataloged procedure. The maximum length of each keyword=option is 66 - characters. No individual value within this field can be longer than 44 characters in length. + - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged + procedure. The maximum length of each keyword=option pair is 66 characters. No individual value within this + field can be longer than 44 characters in length. - Only applicable when I(state) is C(started), otherwise ignored. required: false type: dict member_name: description: - - Option I(member_name) is a 1 - 8 character name of a member of a partitioned data set that - contains the source JCL for the task to be started. The member can be either a job or a - cataloged procedure. + - Name of a member of a partitioned data set that contains the source JCL for the task to be started. The member + can be either a job or a cataloged procedure. - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str @@ -125,26 +122,21 @@ required: false type: list elements: str - retry: + retry_force: description: - - I(retry) is applicable for only FORCE TCB. + - Indicates whether retry will be attempted on ABTERM(abnormal termination). + - I(tcb_address) is mandatory to use I(retry_force). - Only applicable when I(state) is C(forced), otherwise ignored. required: false - type: str - choices: - - 'YES' - - 'NO' + type: bool reus_asid: description: - - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, - a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified - on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + - When I(reus_asid) is C(True) and REUSASID(YES) is specified in the DIAGxx parmlib member, a reusable ASID is assigned + to the address space created by the START command. If I(reus_asid) is not specified or REUSASID(NO) is specified in + DIAGxx, an ordinary ASID is assigned. - Only applicable when I(state) is C(started), otherwise ignored. required: false - type: str - choices: - - 'YES' - - 'NO' + type: bool state: description: - I(state) should be the desired state of the started task after the module is executed. @@ -165,7 +157,7 @@ - forced subsystem: description: - - The name of the subsystem that selects the task for processing. The name must be 1 - 4 + - The name of the subsystem that selects the task for processing. The name must be 1-4 characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. - Only applicable when I(state) is C(started), otherwise ignored. @@ -173,13 +165,13 @@ type: str tcb_address: description: - - I(tcb_address) is a 6-digit hexadecimal TCB address of the task to terminate. + - 6-digit hexadecimal TCB address of the task to terminate. - Only applicable when I(state) is C(forced), otherwise ignored. required: false type: str - volume_serial: + volume: description: - - If I(device_type) is a tape or direct-access device, the volume serial number of the volume is + - If I(device_type) is a tape or direct-access device, the serial number of the volume, mounted on the device. - Only applicable when I(state) is C(started), otherwise ignored. required: false @@ -192,9 +184,9 @@ type: str verbose: description: - - When C(verbose=true), return system logs that describe the task execution. - Using this option, can return a big response depending on system load, also it could - surface other programs activity. + - When C(verbose=true), the module will return system logs that describe the task's execution. + This option can return a big response depending on system load, also it could surface other + program's activity. required: false type: bool default: false @@ -405,7 +397,7 @@ - For address spaces other than system address spaces, the elapsed time since job select time. - For system address spaces created before master scheduler initialization, the elapsed time since master scheduler initialization. - For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. - elapsed_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. + elapsed_time has one of following formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours @@ -475,8 +467,8 @@ - The name of a step, for a job or attached APPC transaction program attached by an initiator. - The identifier of a task created by the START command. - The name of a step that called a cataloged procedure. - - STARTING if initiation of a started job, system task, or attached APPC transaction program is incomplete. - - MASTER* for the master address space. + - C(STARTING) if initiation of a started job, system task, or attached APPC transaction program is incomplete. + - C(*MASTER*) for the master address space. - The name of an initiator address space. type: str sample: SPROC @@ -487,17 +479,22 @@ sample: SAMPLE task_status: description: - - IN for swapped in. - - OUT for swapped out, ready to run. - - OWT for swapped out, waiting, not ready to run. - - OU* for in process of being swapped out. - - IN* for in process of being swapped in. - - NSW for non-swappable. + - C(IN) for swapped in. + - C(OUT) for swapped out, ready to run. + - C(OWT) for swapped out, waiting, not ready to run. + - C(OU*) for in process of being swapped out. + - C(IN*) for in process of being swapped in. + - C(NSW) for non-swappable. type: str sample: NSW task_type: description: - - S for started task. + - C(S) for started task. + - C(A) for an attached APPC transaction program. + - C(I) for initiator address space. + - C(J) for job + - C(M) for mount + - C(*) for system address space type: str sample: S workload_manager: @@ -609,9 +606,14 @@ def validate_and_prepare_start_command(module): parameters = module.params.get('parameters') or [] device_type = module.params.get('device_type') or "" device_number = module.params.get('device_number') or "" - volume_serial = module.params.get('volume_serial') or "" + volume_serial = module.params.get('volume') or "" subsystem_name = module.params.get('subsystem') - reus_asid = module.params.get('reus_asid') + reus_asid = '' + if module.params.get('reus_asid') is not None: + if module.params.get('reus_asid'): + reus_asid = 'YES' + else: + reus_asid = 'NO' keyword_parameters = module.params.get('keyword_parameters') keyword_parameters_string = "" device = device_type if device_type else device_number @@ -883,7 +885,12 @@ def prepare_force_command(module): armrestart = module.params.get('armrestart') userid = module.params.get('userid') tcb_address = module.params.get('tcb_address') - retry = module.params.get('retry') + retry = '' + if module.params.get('retry_force') is not None: + if module.params.get('retry_force'): + retry = 'YES' + else: + retry = 'NO' started_task_name = "" if tcb_address and len(tcb_address) != 6: module.fail_json( @@ -1133,15 +1140,13 @@ def run_module(): 'elements': 'str', 'required': False }, - 'retry': { - 'type': 'str', - 'required': False, - 'choices': ['YES', 'NO'] + 'retry_force': { + 'type': 'bool', + 'required': False }, 'reus_asid': { - 'type': 'str', - 'required': False, - 'choices': ['YES', 'NO'] + 'type': 'bool', + 'required': False }, 'subsystem': { 'type': 'str', @@ -1160,7 +1165,7 @@ def run_module(): 'required': False, 'default': False }, - 'volume_serial': { + 'volume': { 'type': 'str', 'required': False }, @@ -1173,7 +1178,7 @@ def run_module(): mutually_exclusive=[ ['device_number', 'device_type'] ], - required_by={'retry': ['tcb_address']}, + required_by={'retry_force': ['tcb_address']}, supports_check_mode=True ) @@ -1234,12 +1239,12 @@ def run_module(): 'elements': 'str', 'required': False }, - 'retry': { - 'arg_type': 'str', + 'retry_force': { + 'arg_type': 'bool', 'required': False }, 'reus_asid': { - 'arg_type': 'str', + 'arg_type': 'bool', 'required': False }, 'subsystem': { @@ -1258,7 +1263,7 @@ def run_module(): 'arg_type': 'bool', 'required': False }, - 'volume_serial': { + 'volume': { 'arg_type': 'str', 'required': False }, diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index 39c49a8d51..b0ec69e9ad 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -133,7 +133,7 @@ def test_start_task_with_invalid_volumeserial(ansible_zos_module): start_results = hosts.all.zos_started_task( state = "started", member_name = "SAMPLE", - volume_serial = "12345A" + volume = "12345A" ) for result in start_results.contacted.values(): assert result.get("changed") is False @@ -158,7 +158,7 @@ def test_start_task_with_invalid_parameters(ansible_zos_module): state = "started", member_name = "SAMPLE", parameters = ["KEY1", "KEY2", "KEY3"], - volume_serial = "123456" + volume = "123456" ) for result in start_results.contacted.values(): assert result.get("changed") is False @@ -395,7 +395,7 @@ def test_force_task_negative(ansible_zos_module): force_results = hosts.all.zos_started_task( state = "forced", job_name = "TESTER", - retry = "YES" + retry_force = True ) for result in force_results.contacted.values(): assert result.get("changed") is False @@ -405,7 +405,7 @@ def test_force_task_negative(ansible_zos_module): state = "forced", job_name = "TESTER", tcb_address = "0006789", - retry = "YES" + retry_force = True ) for result in force_results.contacted.values(): assert result.get("changed") is False @@ -416,7 +416,7 @@ def test_force_task_negative(ansible_zos_module): job_name = "TESTER", identifier = "SAMPLE", tcb_address = "000678", - retry = "YES" + retry_force = True ) for result in force_results.contacted.values(): assert result.get("changed") is False @@ -426,7 +426,7 @@ def test_force_task_negative(ansible_zos_module): state = "forced", userid = "OMVSTEST", tcb_address = "000678", - retry = "YES", + retry_force = True, verbose=True ) for result in force_results.contacted.values(): @@ -496,7 +496,7 @@ def test_start_and_cancel_zos_started_task(ansible_zos_module): state = "started", member = "SAMPLE", identifier = "TESTER", - reus_asid = "YES" + reus_asid = True ) for result in start_results.contacted.values(): assert result.get("changed") is True From cc682752da5ff81444362b1f7331a1c9a9693fb3 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Sat, 27 Sep 2025 23:45:06 +0530 Subject: [PATCH 33/36] resolving review comments --- plugins/modules/zos_started_task.py | 159 +++++++++--------- .../modules/test_zos_started_task_func.py | 1 + 2 files changed, 81 insertions(+), 79 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 793ddb1c78..b3981c464d 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -79,17 +79,14 @@ job_account: description: - Specifies accounting data in the JCL JOB statement for the started task. If the source JCL - was a job and has already accounting data, the value that is specified on this parameter - overrides the accounting data in the source JCL. + already had accounting data, the value that is specified on this parameter overrides it. - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str job_name: description: - - When I(state) is started, I(job_name) is a name which should be assigned to a started task - while starting it. If I(job_name) is not specified, then I(member_name) is used as I(job_name). - Otherwise, I(job_name) is the started task job name used to find and apply the state - selected. + - When I(state) is started, this is the name which should be assigned to a started task + while starting it. If I(job_name) is not specified, then I(member_name) is used as job's name. - When I(state) is C(displayed), C(modified), C(cancelled), C(stopped), or C(forced), I(job_name) is the started task name. required: false @@ -158,7 +155,7 @@ subsystem: description: - The name of the subsystem that selects the task for processing. The name must be 1-4 - characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must + characters long, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. - Only applicable when I(state) is C(started), otherwise ignored. required: false @@ -192,7 +189,7 @@ default: false wait_time: description: - - Option I(wait_time) is the total time that module zos_started_task will wait for a submitted task in centiseconds. + - Total time that the module will wait for a submitted task, measured in seconds. The time begins when the module is executed on the managed node. Default value of 0 means to wait the default amount of time supported by the opercmd utility. required: false @@ -211,37 +208,37 @@ description: Can run in check_mode and return changed status prediction without modifying target. If not supported, the action will be skipped. """ EXAMPLES = r""" -- name: Start a started task using member name. +- name: Start a started task using a member in a partitioned data set. zos_started_task: state: "started" member: "PROCAPP" -- name: Start a started task using member name and identifier. +- name: Start a started task using a member name and giving it an identifier. zos_started_task: state: "started" member: "PROCAPP" identifier: "SAMPLE" -- name: Start a started task using member name and job. +- name: Start a started task using both a member and a job name. zos_started_task: state: "started" member: "PROCAPP" job_name: "SAMPLE" -- name: Start a started task using member name, job and enable verbose. +- name: Start a started task and enable verbose output. zos_started_task: state: "started" member: "PROCAPP" job_name: "SAMPLE" verbose: True -- name: Start a started task using member name, subsystem and enable reuse asid. +- name: Start a started task specifying the subsystem and enabling a reusable ASID. zos_started_task: state: "started" member: "PROCAPP" subsystem: "MSTR" reus_asid: "YES" -- name: Display a started task using started task name. +- name: Display a started task using a started task name. zos_started_task: state: "displayed" task_name: "PROCAPP" -- name: Display started tasks using matching regex. +- name: Display all started tasks that begin with an s using a wildcard. zos_started_task: state: "displayed" task_name: "s*" @@ -249,31 +246,31 @@ zos_started_task: state: "displayed" task_name: "all" -- name: Cancel a started tasks using task name. +- name: Cancel a started task using task name. zos_started_task: state: "cancelled" task_name: "SAMPLE" -- name: Cancel a started tasks using task name and asid. +- name: Cancel a started task using it's task name and ASID. zos_started_task: state: "cancelled" task_name: "SAMPLE" asid: 0014 -- name: Cancel a started tasks using task name and asid. +- name: Modify a started task's parameters. zos_started_task: state: "modified" task_name: "SAMPLE" parameters: ["XX=12"] -- name: Stop a started task using task name. +- name: Stop a started task using it's task name. zos_started_task: state: "stopped" task_name: "SAMPLE" -- name: Stop a started task using task name, identifier and asid. +- name: Stop a started task using it's task name, identifier and ASID. zos_started_task: state: "stopped" task_name: "SAMPLE" identifier: "SAMPLE" asid: 00A5 -- name: Force a started task using task name. +- name: Force a started task using it's task name. zos_started_task: state: "forced" task_name: "SAMPLE" @@ -307,7 +304,7 @@ sample: 0 state: description: - - The final state of the started task, after execution.. + - The final state of the started task, after execution. returned: changed type: str sample: S SAMPLE @@ -382,13 +379,14 @@ - Central address of the data space ASTE. type: str sample: 058F2180 - dataspace_name: + data_space_name: description: - Data space name associated with the address space. type: str sample: CIRRGMAP domain_number: description: + - The z/OS system or sysplex domain where started task is running. - domain_number=N/A if the system is operating in goal mode. type: str sample: N/A @@ -397,17 +395,17 @@ - For address spaces other than system address spaces, the elapsed time since job select time. - For system address spaces created before master scheduler initialization, the elapsed time since master scheduler initialization. - For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. - elapsed_time has one of following formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. - sss.tttS when time is less than 1000 seconds - hh.mm.ss when time is at least 1000 seconds, but less than 100 hours - hhhhh.mm when time is at least 100 hours - ******** when time exceeds 100000 hours - NOTAVAIL when the TOD clock is not working + - elapsed_time has one of following formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. + sss.tttS when time is less than 1000 seconds + hh.mm.ss when time is at least 1000 seconds, but less than 100 hours + hhhhh.mm when time is at least 100 hours + ******** when time exceeds 100000 hours + NOTAVAIL when the TOD clock is not working type: str sample: 812.983S priority: description: - - The priority of a started task is determined by the Workload Manager (WLM), based on the service class and importance assigned to it. + - Priority of a started task, as determined by the Workload Manager (WLM), based on the service class and importance assigned to it. type: str sample: 1 proc_step_name: @@ -426,6 +424,7 @@ sample: NO program_name: description: + - The name of the program(load module) that created or is running in the started task's address space. - program_name=N/A if the system is operating in goal mode. type: str sample: N/A @@ -437,7 +436,7 @@ sample: NO resource_group: description: - - The name of the resource group currently associated the service class. It can also be N/A if there is no resource group association. + - The name of the resource group currently associated with the service class. It can also be N/A if there is no resource group association. type: str sample: N/A server: @@ -576,7 +575,7 @@ def execute_display_command(started_task_name, timeout=0): list List contains extracted parameters from display command output of started task """ - cmd = "d a," + started_task_name + cmd = f"d a,{started_task_name}" display_response = opercmd.execute(cmd, timeout) task_params = [] if display_response.rc == 0 and display_response.stderr_response == "": @@ -603,7 +602,7 @@ def validate_and_prepare_start_command(module): identifier = module.params.get('identifier_name') job_name = module.params.get('job_name') job_account = module.params.get('job_account') - parameters = module.params.get('parameters') or [] + parameters = module.params.get('parameters', []) device_type = module.params.get('device_type') or "" device_number = module.params.get('device_number') or "" volume_serial = module.params.get('volume') or "" @@ -621,7 +620,7 @@ def validate_and_prepare_start_command(module): if job_account and len(job_account) > 55: module.fail_json( rc=5, - msg="job_account value should not exceed 55 characters.", + msg="The length of job_account exceeded 55 characters.", changed=False ) if device_number: @@ -629,13 +628,13 @@ def validate_and_prepare_start_command(module): if devnum_len not in (3, 5) or (devnum_len == 5 and not device_number.startswith("/")): module.fail_json( rc=5, - msg="Invalid device_number.", + msg="device_number should be 3 or 4 characters long and preceded by / when it is 4 characters long.", changed=False ) if subsystem_name and len(subsystem_name) > 4: module.fail_json( rc=5, - msg="The subsystem_name must be 1 - 4 characters.", + msg="The subsystem_name must be 1-4 characters long.", changed=False ) if keyword_parameters: @@ -645,13 +644,13 @@ def validate_and_prepare_start_command(module): if key_len > 44 or value_len > 44 or key_len + value_len > 65: module.fail_json( rc=5, - msg="The length of a keyword=option is exceeding 66 characters or length of an individual value is exceeding 44 characters." + msg="The length of a keyword=option exceeded 66 characters or length of an individual value exceeded 44 characters." + "key:{0}, value:{1}".format(key, value), changed=False ) else: if keyword_parameters_string: - keyword_parameters_string = keyword_parameters_string + "," + f"{key}={value}" + keyword_parameters_string = f"{keyword_parameters_string},{key}={value}" else: keyword_parameters_string = f"{key}={value}" if job_name: @@ -659,17 +658,17 @@ def validate_and_prepare_start_command(module): elif member: started_task_name = member if identifier: - started_task_name = started_task_name + "." + identifier + started_task_name = f"{started_task_name}.{identifier}" else: module.fail_json( rc=5, - msg="member_name is missing which is mandatory.", + msg="member_name is missing which is mandatory to start a started task.", changed=False ) if not member: module.fail_json( rc=5, - msg="member_name is missing which is mandatory.", + msg="member_name is missing which is mandatory to start a started task.", changed=False ) if job_name and identifier: @@ -681,29 +680,29 @@ def validate_and_prepare_start_command(module): parameters_updated = "" if parameters: if len(parameters) == 1: - parameters_updated = "'" + parameters[0] + "'" + parameters_updated = f"'{parameters[0]}'" else: parameters_updated = f"({','.join(parameters)})" - cmd = 'S ' + member + cmd = f"S {member}" if identifier: - cmd = cmd + "." + identifier + cmd = f"{cmd}.{identifier}" if parameters: - cmd = cmd + "," + device + "," + volume_serial + "," + parameters_updated + cmd = f"{cmd},{device},{volume_serial},{parameters_updated}" elif volume_serial: - cmd = cmd + "," + device + "," + volume_serial + cmd = f"{cmd},{device},{volume_serial}" elif device: - cmd = cmd + "," + device + cmd = f"{cmd},{device}" if job_name: - cmd = cmd + ",JOBNAME=" + job_name + cmd = f"{cmd},JOBNAME={job_name}" if job_account: - cmd = cmd + ",JOBACCT=" + job_account + cmd = f"{cmd},JOBACCT={job_account}" if subsystem_name: - cmd = cmd + ",SUB=" + subsystem_name + cmd = f"{cmd},SUB={subsystem_name}" if reus_asid: - cmd = cmd + ",REUSASID=" + reus_asid + cmd = f"{cmd},REUSASID={reus_asid}" if keyword_parameters_string: - cmd = cmd + "," + keyword_parameters_string + cmd = f"{cmd},{keyword_parameters_string}" return started_task_name, cmd @@ -728,14 +727,14 @@ def prepare_display_command(module): if job_name: started_task_name = job_name if identifier: - started_task_name = started_task_name + "." + identifier + started_task_name = f"{started_task_name}.{identifier}" else: module.fail_json( rc=5, - msg="job_name is missing which is mandatory.", + msg="job_name is missing which is mandatory to display started task details.", changed=False ) - cmd = 'D A,' + started_task_name + cmd = f"D A,{started_task_name}" return started_task_name, cmd @@ -761,16 +760,16 @@ def prepare_stop_command(module): if job_name: started_task_name = job_name if identifier: - started_task_name = started_task_name + "." + identifier + started_task_name = f"{started_task_name}.{identifier}" else: module.fail_json( rc=5, - msg="job_name is missing which is mandatory.", + msg="job_name is missing which is mandatory to display started task details.", changed=False ) - cmd = 'P ' + started_task_name + cmd = f"P {started_task_name}" if asid: - cmd = cmd + ',A=' + asid + cmd = f"{cmd},A={asid}" return started_task_name, cmd @@ -796,20 +795,20 @@ def prepare_modify_command(module): if job_name: started_task_name = job_name if identifier: - started_task_name = started_task_name + "." + identifier + started_task_name = f"{started_task_name}.{identifier}" else: module.fail_json( rc=5, - msg="job_name is missing which is mandatory.", + msg="job_name is missing which is mandatory to display started task details.", changed=False ) if parameters is None: module.fail_json( rc=5, - msg="parameters are mandatory.", + msg="parameters are mandatory while modifying a started task.", changed=False ) - cmd = 'F ' + started_task_name + "," + ",".join(parameters) + cmd = f"F {started_task_name},{','.join(parameters)}" return started_task_name, cmd @@ -838,9 +837,9 @@ def prepare_cancel_command(module): if job_name: started_task_name = job_name if identifier: - started_task_name = started_task_name + "." + identifier + started_task_name = f"{started_task_name}.{identifier}" elif userid: - started_task_name = "U=" + userid + started_task_name = f"U={userid}" else: module.fail_json( rc=5, @@ -853,13 +852,13 @@ def prepare_cancel_command(module): msg="The ARMRESTART parameter is not valid with the U=userid parameter.", changed=False ) - cmd = 'C ' + started_task_name + cmd = f"C {started_task_name}" if asid: - cmd = cmd + ',A=' + asid + cmd = f"{cmd},A={asid}" if dump: - cmd = cmd + ',DUMP' + cmd = f"{cmd},DUMP" if armrestart: - cmd = cmd + ',ARMRESTART' + cmd = f"{cmd},ARMRESTART" return started_task_name, cmd @@ -907,26 +906,26 @@ def prepare_force_command(module): if job_name: started_task_name = job_name if identifier: - started_task_name = started_task_name + "." + identifier + started_task_name = f"{started_task_name}.{identifier}" elif userid: - started_task_name = "U=" + userid + started_task_name = f"U={userid}" else: module.fail_json( rc=5, msg="Both job_name and userid are missing, one of them is needed to cancel a task.", changed=False ) - cmd = 'FORCE ' + started_task_name + cmd = f"FORCE {started_task_name}" if asid: - cmd = cmd + ',A=' + asid + cmd = f"{cmd},A={asid}" if arm: - cmd = cmd + ',ARM' + cmd = f"{cmd},ARM" if armrestart: - cmd = cmd + ',ARMRESTART' + cmd = f"{cmd},ARMRESTART" if tcb_address: - cmd = cmd + ',TCB=' + tcb_address + cmd = f"{cmd},TCB={tcb_address}" if retry: - cmd = cmd + ',RETRY=' + retry + cmd = f"{cmd},RETRY={retry}" return started_task_name, cmd @@ -958,7 +957,7 @@ def extract_keys(stdout): 'ASTE': 'data_space_address_entry', 'ADDR SPACE ASTE': 'address_space_second_table_entry', 'RGP': 'resource_group', - 'DSPNAME': 'dataspace_name', + 'DSPNAME': 'data_space_name', 'DMN': 'domain_number', 'AFF': 'affinity', 'SRVR': 'server', @@ -1310,6 +1309,7 @@ def run_module(): 'IEE535I': 'A parameter on a command is not valid.', 'IEE307I': 'Command parameter punctuation is incorrect or parameter is not followed by a blank.', 'ERROR': 'Member is missing in PROCLIB or JCL is invalid or issue with JCL execution.', + 'NOT FOUND': 'Started task is not active', 'IEE341I': 'Started task is not active', 'REJECTED': 'Started task is not accepting modification.', 'IEE324I': 'The userid specified on the command is not currently active in the system..', @@ -1361,6 +1361,7 @@ def run_module(): is_failed = False system_logs = "" msg = "" + # Find failure found_msg = next((msg for msg in err_msg if msg in out), None) if err != "" or found_msg: is_failed = True @@ -1379,7 +1380,7 @@ def run_module(): if rc == 0: rc = 1 changed = False - msg = error_details[found_msg] + msg = error_details.get(found_msg, found_msg) stdout = out stderr = err if err == "" or err is None: diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index b0ec69e9ad..e7d461d4ce 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -778,6 +778,7 @@ def test_force_and_start_with_icsf_task(ansible_zos_module): task = "ICSF" ) for result in display_results.contacted.values(): + print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" From 8852bfa198ab82610841d59a36dde1db749642e6 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Sat, 27 Sep 2025 23:46:10 +0530 Subject: [PATCH 34/36] Update zos_started_task.rst --- docs/source/modules/zos_started_task.rst | 162 +++++++++++++---------- 1 file changed, 89 insertions(+), 73 deletions(-) diff --git a/docs/source/modules/zos_started_task.rst b/docs/source/modules/zos_started_task.rst index b711fc39f0..a8f9b8c31d 100644 --- a/docs/source/modules/zos_started_task.rst +++ b/docs/source/modules/zos_started_task.rst @@ -29,95 +29,95 @@ Parameters arm *arm* indicates to execute normal task termination routines without causing address space destruction. - Only applicable when state is forced, otherwise is ignored. + Only applicable when *state* is ``forced``, otherwise ignored. | **required**: False | **type**: bool armrestart - Indicates that the batch job or started task should be automatically restarted after the cancel completes, if it is registered as an element of the automatic restart manager. If the job or task is not registered or if you do not specify this parameter, MVS will not automatically restart the job or task. + Indicates that the batch job or started task should be automatically restarted after CANCEL or FORCE completes, if it is registered as an element of the automatic restart manager. If the job or task is not registered or if you do not specify this parameter, MVS will not automatically restart the job or task. - Only applicable when state is cancelled or forced, otherwise is ignored. + Only applicable when *state* is ``cancelled`` or ``forced``, otherwise ignored. | **required**: False | **type**: bool asid - When state is cancelled or stopped or forced, asid is the hexadecimal address space identifier of the work unit you want to cancel, stop or force. + When *state* is ``cancelled``, ``stopped`` or ``forced``, *asid* is the hexadecimal address space identifier of the work unit you want to cancel, stop or force. - Only applicable when state is stopped or cancelled or forced, otherwise is ignored. + Only applicable when *state* is ``stopped``, ``cancelled``, or ``forced``, otherwise ignored. | **required**: False | **type**: str device_type - Option device_type is the type of the output device (if any) associated with the task. + Type of the output device (if any) associated with the task. - Only applicable when state is started otherwise ignored. + Only applicable when *state* is ``started``, otherwise ignored. | **required**: False | **type**: str device_number - Option device_number is the number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. + Number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. - Only applicable when state=started otherwise ignored. + Only applicable when *state* is ``started``, otherwise ignored. | **required**: False | **type**: str dump - A dump is to be taken. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) depends on the JCL for the job. + Whether to perform a dump. The type of dump (SYSABEND, SYSUDUMP, or SYSMDUMP) depends on the JCL for the job. - Only applicable when state is cancelled otherwise ignored. + Only applicable when *state* is ``cancelled``, otherwise ignored. | **required**: False | **type**: bool identifier_name - Option identifier_name is the name that identifies the task. This name can be up to 8 characters long. The first character must be alphabetical. + Option *identifier_name* is the name that identifies the task. This name can be up to 8 characters long. The first character must be alphabetical. | **required**: False | **type**: str job_account - Option job_account specifies accounting data in the JCL JOB statement for the started task. If the source JCL was a job and has already accounting data, the value that is specified on this parameter overrides the accounting data in the source JCL. + Specifies accounting data in the JCL JOB statement for the started task. If the source JCL already had accounting data, the value that is specified on this parameter overrides it. - Only applicable when state is started otherwise ignored. + Only applicable when *state* is ``started``, otherwise ignored. | **required**: False | **type**: str job_name - When state=started job_name is a name which should be assigned to a started task while starting it. If job_name is not specified, then member_name is used as job_name. Otherwise, job_name is the started task job name used to find and apply the state selected. + When *state* is started, this is the name which should be assigned to a started task while starting it. If *job_name* is not specified, then *member_name* is used as job's name. - When state is displayed or modified or cancelled or stopped or forced, job_name is the started task name. + When *state* is ``displayed``, ``modified``, ``cancelled``, ``stopped``, or ``forced``, *job_name* is the started task name. | **required**: False | **type**: str keyword_parameters - Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. The maximum length of each keyword=option is 66 characters. No individual value within this field can be longer than 44 characters in length. + Any appropriate keyword parameter that you specify to override the corresponding parameter in the cataloged procedure. The maximum length of each keyword=option pair is 66 characters. No individual value within this field can be longer than 44 characters in length. - Only applicable when state is started otherwise ignored. + Only applicable when *state* is ``started``, otherwise ignored. | **required**: False | **type**: dict member_name - Option member_name is a 1 - 8 character name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. + Name of a member of a partitioned data set that contains the source JCL for the task to be started. The member can be either a job or a cataloged procedure. - Only applicable when state is started otherwise ignored. + Only applicable when *state* is ``started``, otherwise ignored. | **required**: False | **type**: str @@ -126,41 +126,41 @@ member_name parameters Program parameters passed to the started program. - Only applicable when state is started or modified otherwise ignored. + Only applicable when *state* is ``started`` or ``modified``, otherwise ignored. | **required**: False | **type**: list | **elements**: str -retry - *retry* is applicable for only FORCE TCB. +retry_force + Indicates whether retry will be attempted on ABTER:ref:`abnormal termination `. - Only applicable when state= is forced otherwise ignored. + *tcb_address* is mandatory to use *retry_force*. + + Only applicable when *state* is ``forced``, otherwise ignored. | **required**: False - | **type**: str - | **choices**: YES, NO + | **type**: bool reus_asid - When REUSASID=YES is specified on the START command and REUSASID(YES) is specified in the DIAGxx parmlib member, a reusable ASID is assigned to the address space created by the START command. If REUSASID=YES is not specified on the START command or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. + When *reus_asid* is ``True`` and REUSASID(YES) is specified in the DIAGxx parmlib member, a reusable ASID is assigned to the address space created by the START command. If *reus_asid* is not specified or REUSASID(NO) is specified in DIAGxx, an ordinary ASID is assigned. - Only applicable when state is started otherwise ignored. + Only applicable when *state* is ``started``, otherwise ignored. | **required**: False - | **type**: str - | **choices**: YES, NO + | **type**: bool state *state* should be the desired state of the started task after the module is executed. - If state is started and the respective member is not present on the managed node, then error will be thrown with rc=1, changed=false and stderr which contains error details. + If *state* is ``started`` and the respective member is not present on the managed node, then error will be thrown with ``rc=1``, ``changed=false`` and *stderr* which contains error details. - If state is cancelled , modified, displayed, stopped or forced and the started task is not running on the managed node, then error will be thrown with rc=1, changed=false and stderr contains error details. + If *state* is ``cancelled``, ``modified``, ``displayed``, ``stopped`` or ``forced`` and the started task is not running on the managed node, then error will be thrown with ``rc=1``, ``changed=false`` and *stderr* contains error details. - If state is displayed and the started task is running, then the module will return the started task details along with changed=true. + If *state* is ``displayed`` and the started task is running, then the module will return the started task details along with ``changed=true``. | **required**: True | **type**: str @@ -168,27 +168,27 @@ state subsystem - The name of the subsystem that selects the task for processing. The name must be 1 - 4 characters, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. + The name of the subsystem that selects the task for processing. The name must be 1-4 characters long, which are defined in the IEFSSNxx parmlib member, and the subsystem must be active. - Only applicable when state is started otherwise ignored. + Only applicable when *state* is ``started``, otherwise ignored. | **required**: False | **type**: str tcb_address - *tcb_address* is a 6-digit hexadecimal TCB address of the task to terminate. + 6-digit hexadecimal TCB address of the task to terminate. - Only applicable when state is forced otherwise ignored. + Only applicable when *state* is ``forced``, otherwise ignored. | **required**: False | **type**: str -volume_serial - If devicetype is a tape or direct-access device, the volume serial number of the volume is mounted on the device. +volume + If *device_type* is a tape or direct-access device, the serial number of the volume, mounted on the device. - Only applicable when state is started otherwise ignored. + Only applicable when *state* is ``started``, otherwise ignored. | **required**: False | **type**: str @@ -197,14 +197,14 @@ volume_serial userid The user ID of the time-sharing user you want to cancel or force. - Only applicable when state= is cancelled or forced , otherwise ignored. + Only applicable when *state* is ``cancelled`` or ``forced``, otherwise ignored. | **required**: False | **type**: str verbose - When verbose=true return system logs that describe the task execution. Using this option will can return a big response depending on system load, also it could surface other programs activity. + When ``verbose=true``, the module will return system logs that describe the task's execution. This option can return a big response depending on system load, also it could surface other program's activity. | **required**: False | **type**: bool @@ -212,7 +212,7 @@ verbose wait_time - Option wait_time is the total time that module zos_started_task will wait for a submitted task in centiseconds. The time begins when the module is executed on the managed node. Default value of 0 means to wait the default amount of time supported by the opercmd utility. + Total time that the module will wait for a submitted task, measured in seconds. The time begins when the module is executed on the managed node. Default value of 0 means to wait the default amount of time supported by the opercmd utility. | **required**: False | **type**: int @@ -241,37 +241,37 @@ Examples .. code-block:: yaml+jinja - - name: Start a started task using member name. + - name: Start a started task using a member in a partitioned data set. zos_started_task: state: "started" member: "PROCAPP" - - name: Start a started task using member name and identifier. + - name: Start a started task using a member name and giving it an identifier. zos_started_task: state: "started" member: "PROCAPP" identifier: "SAMPLE" - - name: Start a started task using member name and job. + - name: Start a started task using both a member and a job name. zos_started_task: state: "started" member: "PROCAPP" job_name: "SAMPLE" - - name: Start a started task using member name, job and enable verbose. + - name: Start a started task and enable verbose output. zos_started_task: state: "started" member: "PROCAPP" job_name: "SAMPLE" verbose: True - - name: Start a started task using member name, subsystem and enable reuse asid. + - name: Start a started task specifying the subsystem and enabling a reusable ASID. zos_started_task: state: "started" member: "PROCAPP" subsystem: "MSTR" reus_asid: "YES" - - name: Display a started task using started task name. + - name: Display a started task using a started task name. zos_started_task: state: "displayed" task_name: "PROCAPP" - - name: Display started tasks using matching regex. + - name: Display all started tasks that begin with an s using a wildcard. zos_started_task: state: "displayed" task_name: "s*" @@ -279,31 +279,31 @@ Examples zos_started_task: state: "displayed" task_name: "all" - - name: Cancel a started tasks using task name. + - name: Cancel a started task using task name. zos_started_task: state: "cancelled" task_name: "SAMPLE" - - name: Cancel a started tasks using task name and asid. + - name: Cancel a started task using it's task name and ASID. zos_started_task: state: "cancelled" task_name: "SAMPLE" asid: 0014 - - name: Cancel a started tasks using task name and asid. + - name: Modify a started task's parameters. zos_started_task: state: "modified" task_name: "SAMPLE" parameters: ["XX=12"] - - name: Stop a started task using task name. + - name: Stop a started task using it's task name. zos_started_task: state: "stopped" task_name: "SAMPLE" - - name: Stop a started task using task name, identifier and asid. + - name: Stop a started task using it's task name, identifier and ASID. zos_started_task: state: "stopped" task_name: "SAMPLE" identifier: "SAMPLE" asid: 00A5 - - name: Force a started task using task name. + - name: Force a started task using it's task name. zos_started_task: state: "forced" task_name: "SAMPLE" @@ -352,7 +352,7 @@ rc | **type**: int state - The final state of the started task, after execution.. + The final state of the started task, after execution. | **returned**: changed | **type**: str @@ -363,7 +363,7 @@ stderr | **returned**: changed | **type**: str - | **sample**: An error has ocurred. + | **sample**: An error has occurred. stderr_lines List of strings containing individual lines from STDERR. @@ -375,7 +375,7 @@ stderr_lines .. code-block:: json [ - "An error has ocurred" + "An error has occurred" ] stdout @@ -436,7 +436,7 @@ tasks | **sample**: 000.008S dataspaces - The started task dataspaces details. + The started task data spaces details. | **returned**: success | **type**: list @@ -448,7 +448,7 @@ tasks | **type**: str | **sample**: 058F2180 - dataspace_name + data_space_name Data space name associated with the address space. | **type**: str @@ -456,6 +456,8 @@ tasks domain_number + The z/OS system or sysplex domain where started task is running. + domain_number=N/A if the system is operating in goal mode. | **type**: str @@ -466,13 +468,15 @@ tasks For system address spaces created before master scheduler initialization, the elapsed time since master scheduler initialization. - For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. elapsed_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours ******** when time exceeds 100000 hours NOTAVAIL when the TOD clock is not working + For system address spaces created after master scheduler initialization, the elapsed time since system address space creation. + + elapsed_time has one of following formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours ******** when time exceeds 100000 hours NOTAVAIL when the TOD clock is not working | **type**: str | **sample**: 812.983S priority - The priority of a started task is determined by the Workload Manager (WLM), based on the service class and importance assigned to it. + Priority of a started task, as determined by the Workload Manager (WLM), based on the service class and importance assigned to it. | **type**: str | **sample**: 1 @@ -497,6 +501,8 @@ tasks | **type**: str program_name + The name of the program(load module) that created or is running in the started task's address space. + program_name=N/A if the system is operating in goal mode. | **type**: str @@ -510,7 +516,7 @@ tasks | **type**: str resource_group - The name of the resource group currently associated the service class. It can also be N/A if there is no resource group association. + The name of the resource group currently associated with the service class. It can also be N/A if there is no resource group association. | **type**: str | **sample**: N/A @@ -548,9 +554,9 @@ tasks The name of a step that called a cataloged procedure. - STARTING if initiation of a started job, system task, or attached APPC transaction program is incomplete. + ``STARTING`` if initiation of a started job, system task, or attached APPC transaction program is incomplete. - MASTER* for the master address space. + ``*MASTER*`` for the master address space. The name of an initiator address space. @@ -564,23 +570,33 @@ tasks | **sample**: SAMPLE task_status - IN for swapped in. + ``IN`` for swapped in. - OUT for swapped out, ready to run. + ``OUT`` for swapped out, ready to run. - OWT for swapped out, waiting, not ready to run. + ``OWT`` for swapped out, waiting, not ready to run. - OU* for in process of being swapped out. + ``OU*`` for in process of being swapped out. - IN* for in process of being swapped in. + ``IN*`` for in process of being swapped in. - NSW for non-swappable. + ``NSW`` for non-swappable. | **type**: str | **sample**: NSW task_type - S for started task. + ``S`` for started task. + + ``A`` for an attached APPC transaction program. + + ``I`` for initiator address space. + + ``J`` for job + + ``M`` for mount + + ``*`` for system address space | **type**: str | **sample**: S From d44bcf7457acabc806e212ae5fa5ee304cacc2dc Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:08:02 +0530 Subject: [PATCH 35/36] Updating doc changes --- plugins/modules/zos_started_task.py | 44 ++++++++++--------- .../modules/test_zos_started_task_func.py | 1 - 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index b3981c464d..60bc61d39b 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -305,31 +305,31 @@ state: description: - The final state of the started task, after execution. - returned: changed + returned: success type: str sample: S SAMPLE stderr: description: - The STDERR from the command, may be empty. - returned: changed + returned: failure type: str sample: An error has occurred. stderr_lines: description: - List of strings containing individual lines from STDERR. - returned: changed + returned: failure type: list sample: ["An error has occurred"] stdout: description: - The STDOUT from the command, may be empty. - returned: changed + returned: success type: str sample: ISF031I CONSOLE OMVS0000 ACTIVATED. stdout_lines: description: - List of strings containing individual lines from STDOUT. - returned: changed + returned: success type: list sample: ["Allocation to SYSEXEC completed."] tasks: @@ -503,9 +503,9 @@ sample: SYSTEM verbose_output: description: - - If C(verbose=true), the system log related to the started task executed state will be shown. - returned: changed - type: list + - If C(verbose=true), the system logs related to the started task executed state will be shown. + returned: success + type: str sample: NC0000000 ZOSMACHINE 25240 12:40:30.15 OMVS0000 00000210.... """ @@ -534,10 +534,12 @@ def execute_command(operator_cmd, started_task_name, execute_display_before=Fals ---------- operator_cmd : str Operator command. + started_task_name : str + Name of the started task. + execute_display_before: bool + Indicates whether display command need to be executed before actual command or not. timeout_s : int Timeout to wait for the command execution, measured in centiseconds. - *args : dict - Arguments for the command. **kwargs : dict More arguments for the command. @@ -566,8 +568,8 @@ def execute_display_command(started_task_name, timeout=0): Parameters ---------- started_task_name : str - The name of started task. - timeout_s : int + Name of the started task. + timeout : int Timeout to wait for the command execution, measured in centiseconds. Returns @@ -588,7 +590,7 @@ def validate_and_prepare_start_command(module): Parameters ---------- - start_parms : dict + module : dict The started task start command parameters. Returns @@ -711,7 +713,7 @@ def prepare_display_command(module): Parameters ---------- - display_parms : dict + module : dict The started task display command parameters. Returns @@ -743,7 +745,7 @@ def prepare_stop_command(module): Parameters ---------- - stop_parms : dict + module : dict The started task stop command parameters. Returns @@ -778,7 +780,7 @@ def prepare_modify_command(module): Parameters ---------- - modify_parms : dict + module : dict The started task modify command parameters. Returns @@ -817,7 +819,7 @@ def prepare_cancel_command(module): Parameters ---------- - cancel_parms : dict + module : dict The started task modify command parameters. Returns @@ -996,7 +998,7 @@ def extract_keys(stdout): elif current_task: data_space = {} for match in kv_pattern.finditer(line): - dsp_keys = ['dataspace_name', 'data_space_address_entry'] + dsp_keys = ['data_space_name', 'data_space_address_entry'] key, value = match.groups() if key in keys: key = keys[key] @@ -1050,11 +1052,13 @@ def fetch_logs(command, timeout): ---------- command : string The comand which need to be checked in system logs + timeout: int + The timeout value passed in input. Returns ------- - list - The list of logs from SYSLOG + str + Logs from SYSLOG """ time_mins = timeout // 60 + 1 option = '-t' + str(time_mins) diff --git a/tests/functional/modules/test_zos_started_task_func.py b/tests/functional/modules/test_zos_started_task_func.py index e7d461d4ce..b0ec69e9ad 100644 --- a/tests/functional/modules/test_zos_started_task_func.py +++ b/tests/functional/modules/test_zos_started_task_func.py @@ -778,7 +778,6 @@ def test_force_and_start_with_icsf_task(ansible_zos_module): task = "ICSF" ) for result in display_results.contacted.values(): - print(result) assert result.get("changed") is True assert result.get("rc") == 0 assert result.get("stderr") == "" From b726c6ee4cc659d912a6456ef661e63cfb95c196 Mon Sep 17 00:00:00 2001 From: surendrababuravella <39149274+surendrababuravella@users.noreply.github.com> Date: Mon, 29 Sep 2025 23:55:54 +0530 Subject: [PATCH 36/36] Adding document updates --- docs/source/modules/zos_started_task.rst | 26 ++++++++++-------------- plugins/modules/zos_started_task.py | 4 ++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/docs/source/modules/zos_started_task.rst b/docs/source/modules/zos_started_task.rst index a8f9b8c31d..b4346876fb 100644 --- a/docs/source/modules/zos_started_task.rst +++ b/docs/source/modules/zos_started_task.rst @@ -63,7 +63,7 @@ device_type device_number - Number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but is not before a 3-digit number. + Number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must precede a 4-digit number but not a 3-digit number. Only applicable when *state* is ``started``, otherwise ignored. @@ -354,21 +354,21 @@ rc state The final state of the started task, after execution. - | **returned**: changed + | **returned**: success | **type**: str | **sample**: S SAMPLE stderr The STDERR from the command, may be empty. - | **returned**: changed + | **returned**: failure | **type**: str | **sample**: An error has occurred. stderr_lines List of strings containing individual lines from STDERR. - | **returned**: changed + | **returned**: failure | **type**: list | **sample**: @@ -381,14 +381,14 @@ stderr_lines stdout The STDOUT from the command, may be empty. - | **returned**: changed + | **returned**: success | **type**: str | **sample**: ISF031I CONSOLE OMVS0000 ACTIVATED. stdout_lines List of strings containing individual lines from STDOUT. - | **returned**: changed + | **returned**: success | **type**: list | **sample**: @@ -430,7 +430,7 @@ tasks cpu_time The processor time used by the address space, including the initiator. This time does not include SRB time. - cpu_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours ******** when time exceeds 100000 hours NOTAVAIL when the TOD clock is not working + cpu_time has one of following formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours ******** when time exceeds 100000 hours NOTAVAIL when the TOD clock is not working | **type**: str | **sample**: 000.008S @@ -609,13 +609,9 @@ tasks verbose_output - If ``verbose=true``, the system log related to the started task executed state will be shown. + If ``verbose=true``, the system logs related to the started task executed state will be shown. - | **returned**: changed - | **type**: list - | **sample**: - - .. code-block:: json - - "NC0000000 ZOSMACHINE 25240 12:40:30.15 OMVS0000 00000210...." + | **returned**: success + | **type**: str + | **sample**: NC0000000 ZOSMACHINE 25240 12:40:30.15 OMVS0000 00000210.... diff --git a/plugins/modules/zos_started_task.py b/plugins/modules/zos_started_task.py index 60bc61d39b..502aa52b14 100644 --- a/plugins/modules/zos_started_task.py +++ b/plugins/modules/zos_started_task.py @@ -57,7 +57,7 @@ device_number: description: - Number of the device to be started. A device number is 3 or 4 hexadecimal digits. A slash (/) must - precede a 4-digit number but is not before a 3-digit number. + precede a 4-digit number but not a 3-digit number. - Only applicable when I(state) is C(started), otherwise ignored. required: false type: str @@ -359,7 +359,7 @@ cpu_time: description: - The processor time used by the address space, including the initiator. This time does not include SRB time. - - cpu_time has one of these below formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. + - cpu_time has one of following formats, where ttt is milliseconds, sss or ss is seconds, mm is minutes, and hh or hhhhh is hours. sss.tttS when time is less than 1000 seconds hh.mm.ss when time is at least 1000 seconds, but less than 100 hours hhhhh.mm when time is at least 100 hours