【问题标题】:Robust paramiko SSH command execution with timeout support具有超时支持的强大 paramiko SSH 命令执行
【发布时间】:2017-11-22 19:31:45
【问题描述】:

我需要在 python 中创建一个方法来执行支持超时的远程 SSH 命令(在超时的情况下从 stdoutstderr 收集部分输出)。我在这里浏览了很多主题,但找不到任何完整的解决方案。

您将在下面找到我对如何在 python 3 中实现这一点的建议。

【问题讨论】:

    标签: python ssh paramiko


    【解决方案1】:

    我正在使用(略微调整)Thomas' solution for timeout,但如果需要,可以使用另一种方法:

    import paramiko
    import signal
    
    class Timeout:
        def __init__(self, seconds=1, error_message='Timeout', exception_type=TimeoutError):
            self.seconds = seconds
            self.error_message = error_message + " after {seconds}".format(seconds=seconds)
            self.exception_type = exception_type
    
        def handle_timeout(self, signum, frame):
            raise self.exception_type(self.error_message)
    
        def __enter__(self):
            signal.signal(signal.SIGALRM, self.handle_timeout)
            signal.alarm(self.seconds)
    
        def __exit__(self, type, value, traceback):
            signal.alarm(0)
    
    
    def run_cmd(connection_info, cmd, timeout=5):
        """
        Runs command via SSH, supports timeout and partian stdout/stderr gathering.
        :param connection_info: Dict with 'hostname', 'username' and 'password' fields
        :param cmd: command to run
        :param timeout: timeout for command execution (does not apply to SSH session creation!)
        :return: stdout, stderr, exit code from command execution. Exit code is -1 for timed out commands
        """
        # Session creation can be done ous
        client = paramiko.SSHClient()
        client.load_system_host_keys()
        client.connect(**connection_info)
        channel = client.get_transport().open_session()  # here TCP socket timeout could be tuned
        out = ""
        err = ""
        ext = -1  # exit code -1 for timed out commands
        channel.exec_command(cmd)
        try:
            with Timeout(timeout):
                while True:
                    if channel.recv_ready():
                        out += channel.recv(1024).decode()
                    if channel.recv_stderr_ready():
                        err += channel.recv_stderr(1024).decode()
                    if channel.exit_status_ready() and not channel.recv_ready() and not channel.recv_stderr_ready():
                        ext = channel.recv_exit_status()
                        break
        except TimeoutError:
            print("command timeout")
        return out, err, ext
    

    现在让我们测试一下:

    from time import time
    def test(cmd, timeout):
        credentials = {"hostname": '127.0.0.1', "password": 'password', "username": 'user'}
        ts = time()
        print("\n---\nRunning: " + cmd)
        result = run_cmd(credentials, cmd, timeout)
        print("...it took: {0:4.2f}s".format(time() - ts))
        print("Result: {}\n".format(str(result)[:200]))
    
    # Those should time out:
    test('for i in {1..10}; do echo -n "OUT$i "; sleep 0.5; done', 2)
    test('for i in {1..10}; do echo -n "ERR$i " >&2; sleep 0.5; done', 2)
    test('for i in {1..10}; do echo -n "ERR$i " >&2; echo -n "OUT$i "; sleep 0.5; done', 2)
    
    # Those should not time out:
    test('for i in {1..10}; do echo -n "OUT$i "; sleep 0.1; done', 2)
    test('for i in {1..10}; do echo -n "ERR$i " >&2; sleep 0.1; done', 2)
    test('for i in {1..10}; do echo -n "ERR$i " >&2; echo -n "OUT$i "; sleep 0.1; done', 2)
    
    # Large output testing, with timeout:
    test("cat /dev/urandom | base64 |head -n 1000000", 2)
    test("cat /dev/urandom | base64 |head -n 1000000 >&2", 2)
    

    【讨论】:

      最近更新 更多