예제 #1
0
    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)
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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)
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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,
        )
예제 #8
0
    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