diff --git a/changelogs/fragments/winrm-task-timeout.yml b/changelogs/fragments/winrm-task-timeout.yml new file mode 100644 index 00000000000..305957bf8a6 --- /dev/null +++ b/changelogs/fragments/winrm-task-timeout.yml @@ -0,0 +1,2 @@ +bugfixes: +- winrm - Do not raise another exception during cleanup when a task is timed out - https://github.com/ansible/ansible/issues/81095 diff --git a/lib/ansible/plugins/connection/winrm.py b/lib/ansible/plugins/connection/winrm.py index 91676e1829d..c6a4683d9da 100644 --- a/lib/ansible/plugins/connection/winrm.py +++ b/lib/ansible/plugins/connection/winrm.py @@ -198,7 +198,7 @@ from ansible.utils.display import Display try: import winrm - from winrm.exceptions import WinRMError, WinRMOperationTimeoutError + from winrm.exceptions import WinRMError, WinRMOperationTimeoutError, WinRMTransportError from winrm.protocol import Protocol import requests.exceptions HAS_WINRM = True @@ -683,7 +683,19 @@ class Connection(ConnectionBase): raise AnsibleConnectionFailure('winrm connection error: %s' % to_native(exc)) finally: if command_id: - self.protocol.cleanup_command(self.shell_id, command_id) + # Due to a bug in how pywinrm works with message encryption we + # ignore a 400 error which can occur when a task timeout is + # set and the code tries to clean up the command. This happens + # as the cleanup msg is sent over a new socket but still uses + # the already encrypted payload bound to the other socket + # causing the server to reply with 400 Bad Request. + try: + self.protocol.cleanup_command(self.shell_id, command_id) + except WinRMTransportError as e: + if e.code != 400: + raise + + display.warning("Failed to cleanup running WinRM command, resources might still be in use on the target server") def _connect(self) -> Connection: diff --git a/test/integration/targets/connection_windows_ssh/tests.yml b/test/integration/targets/connection_windows_ssh/tests.yml index e9b538b40a2..3b09f620367 100644 --- a/test/integration/targets/connection_windows_ssh/tests.yml +++ b/test/integration/targets/connection_windows_ssh/tests.yml @@ -30,3 +30,15 @@ - win_ssh_async.rc == 0 - win_ssh_async.stdout == "café\n" - win_ssh_async.stderr == "" + + # Ensures the connection plugin can handle a timeout + # without raising another error. + - name: run command with timeout + win_shell: Start-Sleep -Seconds 10 + timeout: 5 + register: timeout_cmd + ignore_errors: true + + - assert: + that: + - timeout_cmd.msg == 'The win_shell action failed to execute in the expected time frame (5) and was terminated' diff --git a/test/integration/targets/connection_winrm/tests.yml b/test/integration/targets/connection_winrm/tests.yml index b086a3ad1a1..cf109a8c6cd 100644 --- a/test/integration/targets/connection_winrm/tests.yml +++ b/test/integration/targets/connection_winrm/tests.yml @@ -29,3 +29,15 @@ that: - winrm_copy_empty is changed - winrm_copy_empty_actual.stat.size == 0 + + # Ensures the connection plugin can handle a timeout + # without raising another error. + - name: run command with timeout + win_shell: Start-Sleep -Seconds 10 + timeout: 5 + register: timeout_cmd + ignore_errors: true + + - assert: + that: + - timeout_cmd.msg == 'The win_shell action failed to execute in the expected time frame (5) and was terminated'