具有超时支持的强大paramiko SSH命令执行

时间:2017-11-21 12:44:53

标签: python ssh paramiko

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

在下面你将找到关于如何在python 3中实现这一点的建议。

1 个答案:

答案 0 :(得分:0)

我使用(略微调整)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)