def __init__( self, result, # type: exec_result.ExecResult expected=None, # type: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]] ): # type: (...) -> None """Exception for error on process calls. :param result: execution result :type result: exec_result.ExecResult :param expected: expected return codes :type expected: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]] .. versionchanged:: 1.1.1 - provide full result """ self.result = result expected = expected or [proc_enums.ExitCodes.EX_OK] self.expected = proc_enums.exit_codes_to_enums(expected) message = ( "Command {result.cmd!r} returned exit code {result.exit_code} " "while expected {expected}\n" "\tSTDOUT:\n" "{result.stdout_brief}\n" "\tSTDERR:\n{result.stderr_brief}".format(result=self.result, expected=self.expected)) super(CalledProcessError, self).__init__(message)
def check_call( self, command, # type: str verbose=False, # type: bool timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, None] error_info=None, # type: typing.Optional[str] expected=None, # type: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]] raise_on_err=True, # type: bool **kwargs): # type: (...) -> exec_result.ExecResult """Execute command and check for return code. Timeout limitation: read tick is 100 ms. :param command: Command for execution :type command: str :param verbose: Produce log.info records for command call and output :type verbose: bool :param timeout: Timeout for command execution. :type timeout: typing.Union[int, None] :param error_info: Text for error details, if fail happens :type error_info: typing.Optional[str] :param expected: expected return codes (0 by default) :type expected: typing.Optional[typing.Iterable[typing.Union[int, proc_enums.ExitCodes]]] :param raise_on_err: Raise exception on unexpected return code :type raise_on_err: bool :rtype: ExecResult :raises ExecHelperTimeoutError: Timeout exceeded :raises CalledProcessError: Unexpected exit code .. versionchanged:: 1.2.0 default timeout 1 hour """ expected = proc_enums.exit_codes_to_enums(expected) ret = self.execute(command, verbose, timeout, **kwargs) if ret['exit_code'] not in expected: message = ( "{append}Command {result.cmd!r} returned exit code " "{result.exit_code!s} while expected {expected!s}".format( append=error_info + '\n' if error_info else '', result=ret, expected=expected)) self.logger.error(message) if raise_on_err: raise exceptions.CalledProcessError( result=ret, expected=expected, ) return ret
def __init__( self, command: str, errors: dict[tuple[str, int], exec_result.ExecResult], results: dict[tuple[str, int], exec_result.ExecResult], expected: typing.Iterable[ExitCodeT] = (proc_enums.EXPECTED, ), *, _message: str | None = None, ) -> None: """Exception during parallel execution. :param command: command :type command: str :param errors: results with errors :type errors: typing.Dict[typing.Tuple[str, int], ExecResult] :param results: all results :type results: typing.Dict[typing.Tuple[str, int], ExecResult] :param expected: expected return codes :type expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]] :param _message: message override :type _message: typing.Optional[str] .. versionchanged:: 3.4.0 Expected is not optional, defaults os dependent """ prep_expected: typing.Sequence[ ExitCodeT] = proc_enums.exit_codes_to_enums(expected) errors_str: str = "\n\t".join(f"{host}:{port} - {result.exit_code} " for (host, port), result in errors.items()) message: str = _message or ( f"Command {command!r} returned unexpected exit codes on several hosts\n" f"Expected: {prep_expected}\n" f"Got:\n" f"\t{errors_str}") super().__init__(message) self.cmd: str = command self.errors: dict[tuple[str, int], exec_result.ExecResult] = errors self.results: dict[tuple[str, int], exec_result.ExecResult] = results self.expected: typing.Sequence[ExitCodeT] = prep_expected
def __init__( self, command, # type: str exceptions, # type: typing.Dict[typing.Tuple[str, int], Exception] errors, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] results, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] expected=None, # type: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]] ): # type: (...) -> None """Exception raised during parallel call as result of exceptions. :param command: command :type command: str :param exceptions: Exceptions on connections :type exceptions: typing.Dict[typing.Tuple[str, int], Exception] :param errors: results with errors :type errors: typing.Dict[typing.Tuple[str, int], ExecResult] :param results: all results :type results: typing.Dict[typing.Tuple[str, int], ExecResult] :param expected: expected return codes :type expected: typing.Optional[typing.List[ typing.List[typing.Union[int, proc_enums.ExitCodes]] ] """ expected = expected or [proc_enums.ExitCodes.EX_OK] self.expected = proc_enums.exit_codes_to_enums(expected) self.cmd = command self.exceptions = exceptions self.errors = errors self.results = results message = ("Command {self.cmd!r} " "during execution raised exceptions: \n" "\t{exceptions}".format( self=self, exceptions="\n\t".join( "{host}:{port} - {exc} ".format( host=host, port=port, exc=exc) for (host, port), exc in exceptions.items()))) super(ParallelCallExceptions, self).__init__(message)
def __init__( self, result: exec_result.ExecResult, expected: typing.Iterable[ExitCodeT] = (proc_enums.EXPECTED, ), ) -> None: """Exception for error on process calls. :param result: execution result :type result: exec_result.ExecResult :param expected: expected return codes :type expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]] .. versionchanged:: 1.1.1 - provide full result .. versionchanged:: 3.4.0 Expected is not optional, defaults os dependent """ self.result: exec_result.ExecResult = result self.expected: typing.Sequence[ ExitCodeT] = proc_enums.exit_codes_to_enums(expected) message: str = ( f"Command {result.cmd!r} returned exit code {result.exit_code} while expected {expected}\n" f"\tSTDOUT:\n" f"{result.stdout_brief}\n" f"\tSTDERR:\n{result.stderr_brief}") super().__init__(message)
def __init__( self, command, # type: str errors, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] results, # type: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] expected=None, # type: typing.Optional[typing.List[typing.Union[int, proc_enums.ExitCodes]]] ): # type: (...) -> None """Exception during parallel execution. :param command: command :type command: str :param errors: results with errors :type errors: typing.Dict[typing.Tuple[str, int], ExecResult] :param results: all results :type results: typing.Dict[typing.Tuple[str, int], ExecResult] :param expected: expected return codes :type expected: typing.Optional[typing.List[ typing.List[typing.Union[int, proc_enums.ExitCodes]] ] """ expected = expected or [proc_enums.ExitCodes.EX_OK] self.expected = proc_enums.exit_codes_to_enums(expected) self.cmd = command self.errors = errors self.results = results message = ("Command {self.cmd!r} " "returned unexpected exit codes on several hosts\n" "Expected: {self.expected}\n" "Got:\n" "\t{errors}".format( self=self, errors="\n\t".join( "{host}:{port} - {code} ".format( host=host, port=port, code=result.exit_code) for (host, port), result in errors.items()))) super(ParallelCallProcessError, self).__init__(message)
def check_call( self, command: CommandT, verbose: bool = False, timeout: OptionalTimeoutT = constants.DEFAULT_TIMEOUT, error_info: ErrorInfoT = None, expected: ExpectedExitCodesT = (proc_enums.EXPECTED, ), raise_on_err: bool = True, *, log_mask_re: LogMaskReT = None, stdin: OptionalStdinT = None, open_stdout: bool = True, log_stdout: bool = True, open_stderr: bool = True, log_stderr: bool = True, exception_class: CalledProcessErrorSubClassT = exceptions. CalledProcessError, **kwargs: typing.Any, ) -> exec_result.ExecResult: """Execute command and check for return code. :param command: Command for execution :type command: typing.Union[str, typing.Iterable[str]] :param verbose: Produce log.info records for command call and output :type verbose: bool :param timeout: Timeout for command execution. :type timeout: typing.Union[int, float, None] :param error_info: Text for error details, if fail happens :type error_info: typing.Optional[str] :param expected: expected return codes (0 by default) :type expected: typing.Iterable[typing.Union[int, proc_enums.ExitCodes]] :param raise_on_err: Raise exception on unexpected return code :type raise_on_err: bool :param log_mask_re: regex lookup rule to mask command for logger. all MATCHED groups will be replaced by '<*masked*>' :type log_mask_re: typing.Optional[str] :param stdin: pass STDIN text to the process :type stdin: typing.Union[bytes, str, bytearray, None] :param open_stdout: open STDOUT stream for read :type open_stdout: bool :param log_stdout: log STDOUT during read :type log_stdout: bool :param open_stderr: open STDERR stream for read :type open_stderr: bool :param log_stderr: log STDERR during read :type log_stderr: bool :param exception_class: Exception class for errors. Subclass of CalledProcessError is mandatory. :type exception_class: typing.Type[exceptions.CalledProcessError] :param kwargs: additional parameters for call. :type kwargs: typing.Any :return: Execution result :rtype: ExecResult :raises ExecHelperTimeoutError: Timeout exceeded :raises CalledProcessError: Unexpected exit code .. versionchanged:: 1.2.0 default timeout 1 hour .. versionchanged:: 3.2.0 Exception class can be substituted .. versionchanged:: 3.4.0 Expected is not optional, defaults os dependent """ expected_codes: typing.Sequence[ ExitCodeT] = proc_enums.exit_codes_to_enums(expected) result: exec_result.ExecResult = self.execute( command, verbose=verbose, timeout=timeout, log_mask_re=log_mask_re, stdin=stdin, open_stdout=open_stdout, log_stdout=log_stdout, open_stderr=open_stderr, log_stderr=log_stderr, **kwargs, ) return self._handle_exit_code( result=result, error_info=error_info, expected_codes=expected_codes, raise_on_err=raise_on_err, exception_class=exception_class, )
def execute_together( cls, remotes, # type: typing.Iterable[SSHClientBase] command, # type: str timeout=constants.DEFAULT_TIMEOUT, # type: typing.Union[int, None] expected=None, # type: typing.Optional[typing.Iterable[int]] raise_on_err=True, # type: bool **kwargs ): # type: (...) -> typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] """Execute command on multiple remotes in async mode. :param remotes: Connections to execute on :type remotes: typing.Iterable[SSHClientBase] :param command: Command for execution :type command: str :param timeout: Timeout for command execution. :type timeout: typing.Union[int, None] :param expected: expected return codes (0 by default) :type expected: typing.Optional[typing.Iterable[]] :param raise_on_err: Raise exception on unexpected return code :type raise_on_err: bool :return: dictionary {(hostname, port): result} :rtype: typing.Dict[typing.Tuple[str, int], exec_result.ExecResult] :raises ParallelCallProcessError: Unexpected any code at lest on one target :raises ParallelCallExceptions: At lest one exception raised during execution (including timeout) .. versionchanged:: 1.2.0 default timeout 1 hour .. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd """ @threaded.threadpooled def get_result(): # type: () -> exec_result.ExecResult """Get result from remote call.""" ( chan, _, stderr, stdout, ) = remote.execute_async(command, **kwargs) # type: _type_execute_async chan.status_event.wait(timeout) exit_code = chan.recv_exit_status() # pylint: disable=protected-access cmd_for_log = remote._mask_command(cmd=command, log_mask_re=kwargs.get( 'log_mask_re', None)) # pylint: enable=protected-access result = exec_result.ExecResult(cmd=cmd_for_log) result.read_stdout(src=stdout) result.read_stderr(src=stderr) result.exit_code = exit_code chan.close() return result expected = expected or [proc_enums.ExitCodes.EX_OK] expected = proc_enums.exit_codes_to_enums(expected) futures = {} results = {} errors = {} raised_exceptions = {} for remote in set(remotes): # Use distinct remotes futures[remote] = get_result() ( _, not_done, ) = concurrent.futures.wait( list(futures.values()), timeout=timeout ) # type: typing.Set[concurrent.futures.Future], typing.Set[concurrent.futures.Future] for future in not_done: # pragma: no cover future.cancel() for ( remote, future, ) in futures.items(): # type: SSHClientBase, concurrent.futures.Future try: result = future.result() results[(remote.hostname, remote.port)] = result if result.exit_code not in expected: errors[(remote.hostname, remote.port)] = result except Exception as e: raised_exceptions[(remote.hostname, remote.port)] = e if raised_exceptions: # always raise raise exceptions.ParallelCallExceptions(command, raised_exceptions, errors, results, expected=expected) if errors and raise_on_err: raise exceptions.ParallelCallProcessError(command, errors, results, expected=expected) return results