Skip to content

Commit c2d5398

Browse files
committed
Better timeout handling, exit codes enum update
(cherry picked from commit 00a91bd)
1 parent 202b09f commit c2d5398

14 files changed

+189
-82
lines changed

README.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ Context manager is available, connection is closed and lock is released on exit
110110
Subprocess
111111
----------
112112

113-
No initialization required.
114113
Context manager is available, subprocess is killed and lock is released on exit from context.
115114

116115
Base methods
@@ -138,7 +137,7 @@ This methods are almost the same for `SSHCleint` and `Subprocess`, except specif
138137
verbose=False, # type: bool
139138
timeout=1 * 60 * 60, # type: type: typing.Union[int, float, None]
140139
error_info=None, # type: typing.Optional[str]
141-
expected=None, # type: typing.Optional[typing.Iterable[int]]
140+
expected=(0,), # type: typing.Iterable[typing.Union[int, ExitCodes]]
142141
raise_on_err=True, # type: bool
143142
**kwargs
144143
)

exec_helpers/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
from __future__ import absolute_import
1818

19+
import typing
20+
1921
import pkg_resources
2022

2123
from .proc_enums import ExitCodes
@@ -26,6 +28,7 @@
2628
CalledProcessError,
2729
ParallelCallProcessError,
2830
ParallelCallExceptions,
31+
ExecHelperNoKillError,
2932
ExecHelperTimeoutError,
3033
)
3134

@@ -42,6 +45,7 @@
4245
"CalledProcessError",
4346
"ParallelCallExceptions",
4447
"ParallelCallProcessError",
48+
"ExecHelperNoKillError",
4549
"ExecHelperTimeoutError",
4650
"ExecHelper",
4751
"SSHClient",
@@ -51,7 +55,7 @@
5155
"SubprocessExecuteAsyncResult",
5256
"ExitCodes",
5357
"ExecResult",
54-
)
58+
) # type: typing.Tuple[str, ...]
5559

5660
try: # pragma: no cover
5761
__version__ = pkg_resources.get_distribution(__name__).version

exec_helpers/_log_templates.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@
2222

2323
CMD_EXEC = "Executing command:\n{cmd!r}\n"
2424

25+
CMD_KILL_ERROR = (
26+
"Wait for {result.cmd!r} during {timeout!s}s: no return code and no response on SIGTERM + SIGKILL signals!\n"
27+
"\tSTDOUT:\n"
28+
"{result.stdout_brief}\n"
29+
"\tSTDERR:\n"
30+
"{result.stderr_brief}"
31+
)
32+
2533
CMD_WAIT_ERROR = (
2634
"Wait for {result.cmd!r} during {timeout!s}s: no return code!\n"
2735
"\tSTDOUT:\n"

exec_helpers/_ssh_client_base.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ def execute_together(
786786
remotes, # type: typing.Iterable[SSHClientBase]
787787
command, # type: str
788788
timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, float, None]
789-
expected=None, # type: typing.Optional[typing.Iterable[int]]
789+
expected=(proc_enums.EXPECTED,), # type: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
790790
raise_on_err=True, # type: bool
791791
**kwargs # type: typing.Any
792792
): # type: (...) -> typing.Dict[typing.Tuple[str, int], exec_result.ExecResult]
@@ -799,7 +799,7 @@ def execute_together(
799799
:param timeout: Timeout for command execution.
800800
:type timeout: typing.Union[int, float, None]
801801
:param expected: expected return codes (0 by default)
802-
:type expected: typing.Optional[typing.Iterable[]]
802+
:type expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
803803
:param raise_on_err: Raise exception on unexpected return code
804804
:type raise_on_err: bool
805805
:param kwargs: additional parameters for execute_async call.
@@ -811,6 +811,8 @@ def execute_together(
811811
812812
.. versionchanged:: 1.2.0 default timeout 1 hour
813813
.. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd
814+
.. versionchanged:: 1.10.0 Exception class can be substituted
815+
.. versionchanged:: 1.10.0 Expected is not optional, defaults os dependent
814816
"""
815817

816818
@threaded.threadpooled
@@ -833,7 +835,6 @@ def get_result(remote): # type: (SSHClientBase) -> exec_result.ExecResult
833835
async_result.interface.close()
834836
return res
835837

836-
expected = expected or [proc_enums.ExitCodes.EX_OK]
837838
expected = proc_enums.exit_codes_to_enums(expected)
838839

839840
futures = {remote: get_result(remote) for remote in set(remotes)} # Use distinct remotes

exec_helpers/api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def check_call(
280280
verbose=False, # type: bool
281281
timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, float, None]
282282
error_info=None, # type: typing.Optional[str]
283-
expected=None, # type: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]]
283+
expected=(proc_enums.EXPECTED,), # type: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
284284
raise_on_err=True, # type: bool
285285
**kwargs # type: typing.Any
286286
): # type: (...) -> exec_result.ExecResult
@@ -295,7 +295,7 @@ def check_call(
295295
:param error_info: Text for error details, if fail happens
296296
:type error_info: typing.Optional[str]
297297
:param expected: expected return codes (0 by default)
298-
:type expected: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]]
298+
:type expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
299299
:param raise_on_err: Raise exception on unexpected return code
300300
:type raise_on_err: bool
301301
:param kwargs: additional parameters for call.
@@ -306,6 +306,8 @@ def check_call(
306306
:raises CalledProcessError: Unexpected exit code
307307
308308
.. versionchanged:: 1.2.0 default timeout 1 hour
309+
.. versionchanged:: 1.10.0 Exception class can be substituted
310+
.. versionchanged:: 1.10.0 Expected is not optional, defaults os dependent
309311
"""
310312
expected_codes = proc_enums.exit_codes_to_enums(expected)
311313
ret = self.execute(command, verbose, timeout, **kwargs)

exec_helpers/exceptions.py

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
__all__ = (
3030
"ExecHelperError",
31+
"ExecHelperNoKillError",
3132
"ExecHelperTimeoutError",
3233
"ExecCalledProcessError",
3334
"CalledProcessError",
@@ -54,25 +55,27 @@ class ExecCalledProcessError(ExecHelperError):
5455
__slots__ = ()
5556

5657

57-
class ExecHelperTimeoutError(ExecCalledProcessError):
58-
"""Execution timeout.
59-
60-
.. versionchanged:: 1.3.0 provide full result and timeout inside.
61-
.. versionchanged:: 1.3.0 subclass ExecCalledProcessError
62-
"""
58+
class ExecHelperTimeutProcessError(ExecCalledProcessError):
59+
"""Timeout based errors."""
6360

6461
__slots__ = ("result", "timeout")
6562

66-
def __init__(self, result, timeout): # type: (exec_result.ExecResult, typing.Union[int, float]) -> None
63+
def __init__(
64+
self,
65+
message, # type: str,
66+
result, # type: exec_result.ExecResult
67+
timeout # type: typing.Union[int, float]
68+
): # type: (...) -> None
6769
"""Exception for error on process calls.
6870
71+
:param message: exception message
72+
:type message: str
6973
:param result: execution result
7074
:type result: exec_result.ExecResult
7175
:param timeout: timeout for command
7276
:type timeout: typing.Union[int, float]
7377
"""
74-
message = _log_templates.CMD_WAIT_ERROR.format(result=result, timeout=timeout)
75-
super(ExecHelperTimeoutError, self).__init__(message)
78+
super(ExecHelperTimeutProcessError, self).__init__(message)
7679
self.result = result
7780
self.timeout = timeout
7881

@@ -92,6 +95,47 @@ def stderr(self): # type: () -> typing.Text
9295
return self.result.stderr_str
9396

9497

98+
class ExecHelperNoKillError(ExecHelperTimeutProcessError):
99+
"""Impossible to kill process.
100+
101+
.. versionadded:: 1.10.0
102+
"""
103+
104+
__slots__ = ()
105+
106+
def __init__(self, result, timeout): # type: (exec_result.ExecResult, typing.Union[int, float]) -> None
107+
"""Exception for error on process calls.
108+
109+
:param result: execution result
110+
:type result: exec_result.ExecResult
111+
:param timeout: timeout for command
112+
:type timeout: typing.Union[int, float]
113+
"""
114+
message = _log_templates.CMD_KILL_ERROR.format(result=result, timeout=timeout)
115+
super(ExecHelperNoKillError, self).__init__(message, result=result, timeout=timeout)
116+
117+
118+
class ExecHelperTimeoutError(ExecHelperTimeutProcessError):
119+
"""Execution timeout.
120+
121+
.. versionchanged:: 1.3.0 provide full result and timeout inside.
122+
.. versionchanged:: 1.3.0 subclass ExecCalledProcessError
123+
"""
124+
125+
__slots__ = ()
126+
127+
def __init__(self, result, timeout): # type: (exec_result.ExecResult, typing.Union[int, float]) -> None
128+
"""Exception for error on process calls.
129+
130+
:param result: execution result
131+
:type result: exec_result.ExecResult
132+
:param timeout: timeout for command
133+
:type timeout: typing.Union[int, float]
134+
"""
135+
message = _log_templates.CMD_WAIT_ERROR.format(result=result, timeout=timeout)
136+
super(ExecHelperTimeoutError, self).__init__(message, result=result, timeout=timeout)
137+
138+
95139
class CalledProcessError(ExecCalledProcessError):
96140
"""Exception for error on process calls."""
97141

@@ -100,19 +144,19 @@ class CalledProcessError(ExecCalledProcessError):
100144
def __init__(
101145
self,
102146
result, # type: exec_result.ExecResult
103-
expected=None, # type: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]]
147+
expected=(proc_enums.EXPECTED,), # type: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
104148
): # type: (...) -> None
105149
"""Exception for error on process calls.
106150
107151
:param result: execution result
108152
:type result: exec_result.ExecResult
109153
:param expected: expected return codes
110-
:type expected: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]]
154+
:type expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
111155
112156
.. versionchanged:: 1.1.1 - provide full result
157+
.. versionchanged:: 1.10.0 Expected is not optional, defaults os dependent
113158
"""
114159
self.result = result
115-
expected = expected or [proc_enums.ExitCodes.EX_OK]
116160
self.expected = proc_enums.exit_codes_to_enums(expected)
117161
message = (
118162
"Command {result.cmd!r} returned exit code {result.exit_code} "
@@ -154,7 +198,7 @@ def __init__(
154198
command, # type: str
155199
errors, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult]
156200
results, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult]
157-
expected=None, # type: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]]
201+
expected=(proc_enums.EXPECTED,), # type: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
158202
**kwargs # type: typing.Any
159203
): # type: (...) -> None
160204
"""Exception raised during parallel call as result of exceptions.
@@ -166,10 +210,12 @@ def __init__(
166210
:param results: all results
167211
:type results: typing.Dict[typing.Tuple[str, int], ExecResult]
168212
:param expected: expected return codes
169-
:type expected: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]]
170-
:keyword _message: error message override
213+
:type expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
214+
:param _message: message override
215+
:type _message: typing.Optional[str]
216+
217+
.. versionchanged:: 1.10.0 Expected is not optional, defaults os dependent
171218
"""
172-
expected = expected or [proc_enums.ExitCodes.EX_OK]
173219
prep_expected = proc_enums.exit_codes_to_enums(expected)
174220
message = kwargs.get("_message", None) or (
175221
"Command {cmd!r} "
@@ -203,7 +249,7 @@ def __init__(
203249
exceptions, # type: typing.Dict[typing.Tuple[str, int], Exception]
204250
errors, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult]
205251
results, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult]
206-
expected=None, # type: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]]
252+
expected=(proc_enums.EXPECTED,), # type: typing.List[typing.Union[int, proc_enums.ExitCodes]]
207253
**kwargs # type: typing.Any
208254
): # type: (...) -> None
209255
"""Exception during parallel execution.
@@ -217,10 +263,12 @@ def __init__(
217263
:param results: all results
218264
:type results: typing.Dict[typing.Tuple[str, int], ExecResult]
219265
:param expected: expected return codes
220-
:type expected: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]]
221-
:keyword _message: error message override
266+
:type expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]
267+
:param _message: message override
268+
:type _message: typing.Optional[str]
269+
270+
.. versionchanged:: 1.10.0 Expected is not optional, defaults os dependent
222271
"""
223-
expected = expected or [proc_enums.ExitCodes.EX_OK]
224272
prep_expected = proc_enums.exit_codes_to_enums(expected)
225273
message = kwargs.get("_message", None) or (
226274
"Command {cmd!r} "

exec_helpers/exec_result.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def __init__(
6161
stdin=None, # type: typing.Union[bytes, str, bytearray, None]
6262
stdout=None, # type: typing.Optional[typing.Iterable[bytes]]
6363
stderr=None, # type: typing.Optional[typing.Iterable[bytes]]
64-
exit_code=proc_enums.ExitCodes.EX_INVALID, # type: typing.Union[int, proc_enums.ExitCodes]
64+
exit_code=proc_enums.INVALID, # type: typing.Union[int, proc_enums.ExitCodes]
6565
): # type: (...) -> None
6666
"""Command execution result.
6767
@@ -96,7 +96,7 @@ def __init__(
9696
else:
9797
self._stderr = ()
9898

99-
self.__exit_code = proc_enums.ExitCodes.EX_INVALID # type: typing.Union[int, proc_enums.ExitCodes]
99+
self.__exit_code = proc_enums.INVALID # type: typing.Union[int, proc_enums.ExitCodes]
100100
self.__timestamp = None
101101
self.exit_code = exit_code
102102

@@ -369,7 +369,7 @@ def exit_code(self, new_val): # type: (typing.Union[int, proc_enums.ExitCodes])
369369
raise TypeError("Exit code is strictly int, got {!r}".format(new_val))
370370
with self.stdout_lock, self.stderr_lock:
371371
self.__exit_code = proc_enums.exit_code_to_enum(new_val)
372-
if self.__exit_code != proc_enums.ExitCodes.EX_INVALID:
372+
if self.__exit_code != proc_enums.INVALID:
373373
self.__timestamp = datetime.datetime.utcnow() # type: ignore
374374

375375
def __deserialize(self, fmt): # type: (str) -> typing.Any

0 commit comments

Comments
 (0)