diff --git a/changelogs/fragments/2230_zos_operator_interface_update.yml b/changelogs/fragments/2230_zos_operator_interface_update.yml new file mode 100644 index 000000000..50c1e6c1e --- /dev/null +++ b/changelogs/fragments/2230_zos_operator_interface_update.yml @@ -0,0 +1,4 @@ +breaking_changes: + - zos_operator - Option ``wait_time_s`` is being deprecated in favor of ``wait_time``. New option ``time_unit`` is being added to select + seconds or centiseconds. New return value ``time_unit`` is being added. Return value ``wait_time_s`` is being deprecated in favor of ``wait_time``. + (https://github.com/ansible-collections/ibm_zos_core/pull/2230). \ No newline at end of file diff --git a/plugins/modules/zos_operator.py b/plugins/modules/zos_operator.py index ab529cf33..eb8bc34fa 100644 --- a/plugins/modules/zos_operator.py +++ b/plugins/modules/zos_operator.py @@ -50,17 +50,26 @@ type: bool required: false default: false - wait_time_s: + wait_time: description: - Set maximum time in seconds to wait for the commands to execute. - When set to 0, the system default is used. - This option is helpful on a busy system requiring more time to execute commands. - Setting I(wait) can instruct if execution should wait the - full I(wait_time_s). + full I(wait_time). type: int required: false default: 1 + time_unit: + description: + - Set the C(wait_time) unit of time, which can be C(s) (seconds) or C(cs) (centiseconds). + type: str + required: false + default: s + choices: + - s + - cs case_sensitive: description: - If C(true), the command will not be converted to uppercase before @@ -103,11 +112,17 @@ - name: Execute operator command to show jobs, always waiting 5 seconds for response zos_operator: cmd: 'd a,all' - wait_time_s: 5 + wait_time: 5 - name: Display the system symbols and associated substitution texts. zos_operator: cmd: 'D SYMBOLS' + +- name: Execute an operator command to show device status and allocation wait 10 centiseconds. + zos_operator: + cmd: 'd u' + wait_time : 10 + time_unit : 'cs' """ RETURN = r""" @@ -125,16 +140,22 @@ sample: d u,all elapsed: description: - The number of seconds that elapsed waiting for the command to complete. + The number of seconds or centiseconds that elapsed waiting for the command to complete. returned: always type: float sample: 51.53 -wait_time_s: +wait_time: description: - The maximum time in seconds to wait for the commands to execute. + The maximum time in the time_unit set to wait for the commands to execute. returned: always type: int sample: 5 +time_unit: + description: + The time unit set for wait_time. + returned: always + type: str + sample: s content: description: The resulting text from the command submitted. @@ -200,7 +221,7 @@ opercmd = ZOAUImportError(traceback.format_exc()) -def execute_command(operator_cmd, timeout_s=1, preserve=False, *args, **kwargs): +def execute_command(operator_cmd, time_unit, timeout=1, preserve=False, *args, **kwargs): """ Executes an operator command. @@ -208,6 +229,8 @@ def execute_command(operator_cmd, timeout_s=1, preserve=False, *args, **kwargs): ---------- operator_cmd : str Command to execute. + time_unit : str + Unit of time to wait of execution of the command. timeout : int Time until it stops whether it finished or not. preserve : bool @@ -223,15 +246,20 @@ def execute_command(operator_cmd, timeout_s=1, preserve=False, *args, **kwargs): Return code, standard output, standard error and time elapsed from start to finish. """ # as of ZOAU v1.3.0, timeout is measured in centiseconds, therefore: - timeout_c = 100 * timeout_s + if time_unit == "s": + timeout = 100 * timeout start = timer() - response = opercmd.execute(operator_cmd, timeout=timeout_c, preserve=preserve, *args, **kwargs) + response = opercmd.execute(operator_cmd, timeout=timeout, preserve=preserve, *args, **kwargs) end = timer() rc = response.rc stdout = response.stdout_response stderr = response.stderr_response - elapsed = round(end - start, 2) + if time_unit == "cs": + elapsed = round((end - start) * 100, 2) + else: + elapsed = round(end - start, 2) + return rc, stdout, stderr, elapsed @@ -252,7 +280,8 @@ def run_module(): module_args = dict( cmd=dict(type="str", required=True), verbose=dict(type="bool", required=False, default=False), - wait_time_s=dict(type="int", required=False, default=1), + wait_time=dict(type="int", required=False, default=1), + time_unit=dict(type="str", required=False, choices=["s", "cs"], default="s"), case_sensitive=dict(type="bool", required=False, default=False), ) @@ -294,7 +323,8 @@ def run_module(): # call is returned from run_operator_command, specifying what was run. # result["cmd"] = new_params.get("cmd") result["cmd"] = rc_message.get("call") - result["wait_time_s"] = new_params.get("wait_time_s") + result["wait_time"] = new_params.get("wait_time") + result["time_unit"] = new_params.get("time_unit") result["changed"] = False # rc=0, something succeeded (the calling script ran), @@ -309,7 +339,8 @@ def run_module(): module.fail_json(msg=("A non-zero return code was received : {0}. Review the response for more details.").format(result["rc"]), cmd=result["cmd"], elapsed_time=result["elapsed"], - wait_time_s=result["wait_time_s"], + wait_time=result["wait_time"], + time_unit=result["time_unit"], stderr=str(error) if error is not None else result["content"], stderr_lines=str(error).splitlines() if error is not None else result["content"], changed=result["changed"],) @@ -338,9 +369,10 @@ def parse_params(params): """ arg_defs = dict( cmd=dict(arg_type="str", required=True), - verbose=dict(arg_type="bool", required=False), - wait_time_s=dict(arg_type="int", required=False), - case_sensitive=dict(arg_type="bool", required=False), + verbose=dict(arg_type="bool", required=False, default=False), + wait_time=dict(arg_type="int", required=False, default=1), + time_unit=dict(type="str", required=False, choices=["s", "cs"], default="s"), + case_sensitive=dict(arg_type="bool", required=False, default=False), ) parser = BetterArgParser(arg_defs) new_params = parser.parse_args(params) @@ -369,7 +401,8 @@ def run_operator_command(params): kwargs.update({"verbose": True}) kwargs.update({"debug": True}) - wait_s = params.get("wait_time_s") + wait_time = params.get("wait_time") + time_unit = params.get("time_unit") cmdtxt = params.get("cmd") preserve = params.get("case_sensitive") @@ -381,7 +414,7 @@ def run_operator_command(params): kwargs.update({"wait": True}) args = [] - rc, stdout, stderr, elapsed = execute_command(cmdtxt, timeout_s=wait_s, preserve=preserve, *args, **kwargs) + rc, stdout, stderr, elapsed = execute_command(cmdtxt, time_unit=time_unit, timeout=wait_time, preserve=preserve, *args, **kwargs) if rc > 0: message = "\nOut: {0}\nErr: {1}\nRan: {2}".format(stdout, stderr, cmdtxt) diff --git a/tests/functional/modules/test_zos_operator_func.py b/tests/functional/modules/test_zos_operator_func.py index 99f4ef035..1fcb349db 100644 --- a/tests/functional/modules/test_zos_operator_func.py +++ b/tests/functional/modules/test_zos_operator_func.py @@ -45,7 +45,7 @@ - name: zos_operator zos_operator: cmd: 'd a,all' - wait_time_s: 3 + wait_time: 3 verbose: true register: output @@ -77,15 +77,29 @@ def test_zos_operator_various_command(ansible_zos_module): hosts = ansible_zos_module results = hosts.all.zos_operator(cmd=command) for result in results.contacted.values(): - assert result["rc"] == expected_rc + print(result) + assert result.get("rc") == expected_rc assert result.get("changed") is changed + assert result.get("msg", False) is False + assert result.get("cmd") == command + assert result.get("elapsed") is not None + assert result.get("wait_time") is not None + assert result.get("time_unit") == "s" + assert result.get("content") is not None def test_zos_operator_invalid_command(ansible_zos_module): hosts = ansible_zos_module results = hosts.all.zos_operator(cmd="invalid,command", verbose=False) for result in results.contacted.values(): + print(result) assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("cmd") is not None + assert result.get("elapsed") is not None + assert result.get("wait_time") is not None + assert result.get("time_unit") == "s" + assert result.get("content") is not None def test_zos_operator_invalid_command_to_ensure_transparency(ansible_zos_module): @@ -93,6 +107,12 @@ def test_zos_operator_invalid_command_to_ensure_transparency(ansible_zos_module) results = hosts.all.zos_operator(cmd="DUMP COMM=('ERROR DUMP')", verbose=False) for result in results.contacted.values(): assert result.get("changed") is True + assert result.get("rc") == 0 + assert result.get("cmd") is not None + assert result.get("elapsed") is not None + assert result.get("wait_time") is not None + assert result.get("time_unit") == "s" + assert result.get("content") is not None transparency = False if any('DUMP COMMAND' in str for str in result.get("content")): transparency = True @@ -103,8 +123,13 @@ def test_zos_operator_positive_path(ansible_zos_module): hosts = ansible_zos_module results = hosts.all.zos_operator(cmd="d u,all", verbose=False) for result in results.contacted.values(): - assert result["rc"] == 0 + assert result.get("rc") == 0 assert result.get("changed") is True + assert result.get("msg", False) is False + assert result.get("cmd") is not None + assert result.get("elapsed") is not None + assert result.get("wait_time") is not None + assert result.get("time_unit") == "s" assert result.get("content") is not None @@ -112,8 +137,13 @@ def test_zos_operator_positive_path_verbose(ansible_zos_module): hosts = ansible_zos_module results = hosts.all.zos_operator(cmd="d u,all", verbose=True) for result in results.contacted.values(): - assert result["rc"] == 0 + assert result.get("rc") == 0 assert result.get("changed") is True + assert result.get("msg", False) is False + assert result.get("cmd") is not None + assert result.get("elapsed") is not None + assert result.get("wait_time") is not None + assert result.get("time_unit") == "s" assert result.get("content") is not None # Traverse the content list for a known verbose keyword and track state is_verbose = False @@ -127,45 +157,55 @@ def test_zos_operator_positive_verbose_with_full_delay(ansible_zos_module): hosts = ansible_zos_module wait_time = 10 results = hosts.all.zos_operator( - cmd="RO *ALL,LOG 'dummy syslog message'", verbose=True, wait_time_s=wait_time + cmd="RO *ALL,LOG 'dummy syslog message'", verbose=True, wait_time=wait_time ) for result in results.contacted.values(): - assert result["rc"] == 0 + assert result.get("rc") == 0 assert result.get("changed") is True - assert result.get("content") is not None + assert result.get("msg", False) is False + assert result.get("cmd") is not None assert result.get("elapsed") > wait_time + assert result.get("wait_time") is not None + assert result.get("time_unit") == "s" + assert result.get("content") is not None def test_zos_operator_positive_verbose_with_quick_delay(ansible_zos_module): hosts = ansible_zos_module - wait_time_s=10 + wait_time=10 results = hosts.all.zos_operator( - cmd="d u,all", verbose=True, wait_time_s=wait_time_s + cmd="d u,all", verbose=True, wait_time=wait_time ) for result in results.contacted.values(): - assert result["rc"] == 0 + assert result.get("rc") == 0 assert result.get("changed") is True + assert result.get("msg", False) is False + assert result.get("cmd") is not None + assert result.get("elapsed") <= (2 * wait_time) + assert result.get("wait_time") is not None + assert result.get("time_unit") == "s" assert result.get("content") is not None - # Account for slower network - assert result.get('elapsed') <= (2 * wait_time_s) def test_zos_operator_positive_verbose_blocking(ansible_zos_module): hosts = ansible_zos_module if is_zoau_version_higher_than(hosts,"1.2.4.5"): - wait_time_s=5 + wait_time=5 results = hosts.all.zos_operator( - cmd="d u,all", verbose=True, wait_time_s=wait_time_s + cmd="d u,all", verbose=True, wait_time=wait_time ) for result in results.contacted.values(): - assert result["rc"] == 0 + assert result.get("rc") == 0 assert result.get("changed") is True + assert result.get("msg", False) is False + assert result.get("cmd") is not None + assert result.get("elapsed") >= wait_time + assert result.get("wait_time") is not None + assert result.get("time_unit") == "s" assert result.get("content") is not None - # Account for slower network - assert result.get('elapsed') >= wait_time_s def test_zos_operator_positive_path_preserve_case(ansible_zos_module): @@ -178,8 +218,13 @@ def test_zos_operator_positive_path_preserve_case(ansible_zos_module): ) for result in results.contacted.values(): - assert result["rc"] == 0 + assert result.get("rc") == 0 assert result.get("changed") is True + assert result.get("msg", False) is False + assert result.get("cmd") is not None + assert result.get("wait_time") is not None + assert result.get("elapsed") is not None + assert result.get("time_unit") == "s" assert result.get("content") is not None # Making sure the output from opercmd logged the command # exactly as it was written. @@ -193,12 +238,34 @@ def test_response_come_back_complete(ansible_zos_module): res = {} res["stdout"] = [] for result in results.contacted.values(): + assert result.get("rc") == 0 + assert result.get("changed") is True + assert result.get("msg", False) is False + assert result.get("cmd") is not None + assert result.get("wait_time") is not None + assert result.get("elapsed") is not None + assert result.get("time_unit") == "s" + assert result.get("content") is not None stdout = result.get('content') # HASP646 Only appears in the last line that before did not appears last_line = len(stdout) assert "HASP646" in stdout[last_line - 1] +def test_operator_sentiseconds(ansible_zos_module): + hosts = ansible_zos_module + results = hosts.all.zos_operator(cmd="d a", time_unit="cs", wait_time=100) + for result in results.contacted.values(): + assert result.get("rc") == 0 + assert result.get("changed") is True + assert result.get("msg", False) is False + assert result.get("cmd") is not None + assert result.get("elapsed") is not None + assert result.get("wait_time") is not None + assert result.get("time_unit") == "cs" + assert result.get("content") is not None + + def test_zos_operator_parallel_terminal(get_config): path = get_config with open(path, 'r') as file: