【问题标题】:Python Paramiko timeout with long execution, need full output长时间执行的 Python Paramiko 超时,需要完整输出
【发布时间】:2012-10-30 05:58:11
【问题描述】:

标题的一部分涉及很多话题,但没有什么能完全满足整个话题。我正在远程服务器上推送一个命令,并且在很长的执行时间后需要完整的输出,比如 5 分钟左右。使用通道我可以设置超时,但是当我读回标准输出时,我只得到了一小部分输出。解决方案似乎是等待 channel.exit_status_ready()。这适用于成功的呼叫,但失败的呼叫永远不会触发通道超时。查看文档后,我推测这是因为超时仅适用于读取操作,并且等待退出状态不符合条件。这是尝试:

channel = ssh.get_transport().open_session()
channel.settimeout(timeout)
channel.exec_command(cmd)  # return on this is not reliable
while True:
    try:
        if channel.exit_status_ready():
            if channel.recv_ready():  # so use recv instead...
                output = channel.recv(1048576)
                break
        if channel.recv_stderr_ready():  # then check error
            error = channel.recv_stderr(1048576)
            break
    except socket.timeout:
        print("SSH channel timeout exceeded.")
        break
    except Exception:
        traceback.print_exc()
        break

很漂亮,不是吗?希望它有效。

我第一次尝试解决方案是使用 time.time() 开始,然后检查 start - time.time() > 超时。这看起来很简单,但在我目前的版本中,我输出 start - time.time() 具有应该触发中断的固定超时......并看到超时时间的两倍和三倍而没有发生中断的差异。为了节省空间,我将提到我的第三次尝试,我已经将它卷起来了。我在这里阅读了有关使用 select.select 等待输出的信息,并在文档中指出那里也有超时。正如您将在下面的代码中看到的那样,我混合了所有三种方法——通道超时、time.time 超时和选择超时——但仍然必须终止进程。这是 frankencode:

channel = ssh.get_transport().open_session()
channel.settimeout(timeout)
channel.exec_command(cmd)  # return on this is not reliable
print("{0}".format(cmd))
start = time.time()
while True:
    try:
        rlist, wlist, elist = select([channel], [], [],
            float(timeout))
        print("{0}, {1}, {2}".format(rlist, wlist, elist))
        if rlist is not None and len(rlist) > 0:
            if channel.exit_status_ready():
                if channel.recv_ready():  # so use recv instead...
                    output = channel.recv(1048576)
                    break
        elif elist is not None and len(elist) > 0:
            if channel.recv_stderr_ready():  # then check error
                error = channel.recv_stderr(1048576)
                break
        print("{0} - {1} = {2}".format(
            time.time(), start, time.time() - start))
        if time.time() - start > timeout:
            break
    except socket.timeout:
        print("SSH channel timeout exceeded.")
        break
    except Exception:
        traceback.print_exc()
        break

这是一些典型的输出:

[<paramiko.Channel 3 (open) window=515488 -> <paramiko.Transport at 0x888414cL (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>], [], []
1352494558.42 - 1352494554.69 = 3.73274183273

上面一行是select的[rlist, wlist, elist],下面一行是time.time() - start = (time.time() - start)。通过计算迭代次数并在循环 1000 次后在尝试的底部中断,我让这次运行中断。在示例运行中超时设置为 3。这证明我们通过了尝试,但显然,应该超时的三种方式都不起作用。

如果我从根本上误解了某些内容,请随意翻阅代码。我希望它是超级 Pythonic 并且仍在学习。

【问题讨论】:

    标签: python ssh timeout paramiko


    【解决方案1】:

    我从通道调用 exec_command 时遇到很多问题,而是直接使用 ssh 连接中的 exec_command 并调用 std 输出的通道,适合我的代码就像 myexec

    #!/usr/bin/env python
    import paramiko
    import select
    
    def myexec(ssh, cmd, timeout):
      stdin, stdout, stderr = ssh.exec_command(cmd)
      channel = stdout.channel
      stdin.close() #As I don't need stdin
      channel.shutdown_write() #As I will not write to this channel
    
      stdout_chunks = []
      stdout_chunks.append(stdout.channel.recv(len(stdout.channel.in_buffer)))
    
      # chunked read to prevent stalls
      while not channel.closed or channel.recv_ready()
            or channel.recv_stderr_ready():
    
        # stop if channel was closed prematurely,
        # and there is no data in the buffers.
        got_chunk = False
        readq, _, _ = select.select([stdout.channel], [], [], timeout)
        for c in readq:
          if c.recv_ready():
            stdout_chunks.append(stdout.channel.recv(len(c.in_buffer)))
            got_chunk = True
          if c.recv_stderr_ready():
            # make sure to read stderr to prevent stall
            stderr.channel.recv_stderr(len(c.in_stderr_buffer))
            got_chunk = True
        if not got_chunk \
               and stdout.channel.exit_status_ready() \
               and not stderr.channel.recv_stderr_ready() \
               and not stdout.channel.recv_ready():
          # indicate that we're not going to read from this channel anymore
          stdout.channel.shutdown_read() # close the channel
          stdout.channel.close()
          break    # exit as remote side is finished and our bufferes are empty
    
      # close all the pseudofiles
      stdout.close()
      stderr.close()
      return (''.join(stdout_chunks), stdout.channel.recv_exit_status())
    
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect('remotehost', username='remoteuser', password='remotepassword')
    rtrval = myexec(ssh, 'remotecomand', 5*60)
    ssh.close()
    print rtrval
    

    我使用的是 Debian 8 和 Python 2.7.13,祝你好运。

    【讨论】:

      【解决方案2】:

      这里有一些可能会有所帮助的东西,尽管我仍在测试中。在与各种类型的超时(包括 Python 的包罗万象的超时)作斗争之后,并意识到真正的问题是不能信任服务器来终止进程,我这样做了:

      chan = ssh.get_transport().open_session()
      
      cmd = "timeout {0} {1}\n".format(timeouttime, cmd)
      
      chan.exec_command(cmd)
      

      服务器在timeouttime 之后超时,如果cmd 没有像我希望的那样早点退出,并且终止的命令会杀死通道。唯一的问题是 GNU coreutils 必须存在于服务器上。没有其他选择。

      【讨论】:

      • 对我有用的是上面的变体:'timeout -s SIGKILL ',否则程序不会被杀死。
      【解决方案3】:

      我遇到了同样的问题。我认为我们可以通过信号来处理它。 http://docs.python.org/2/library/signal.html

      这是一个简单的例子来展示它是如何工作的。

      import signal, time                          
      
      def handler(signum, frame):                  
          pass                                     
      
      # Set the signal handler and a 2-second alarm
      signal.signal(signal.SIGALRM, handler)       
      signal.alarm(2)                              
      
      # This is where your operation that might hang goes
      time.sleep(10)                               
      
      # Disable the alarm                          
      signal.alarm(0)                              
      

      所以在这里,闹钟设置为 2 秒。 Time.sleep 在 10 秒内被调用。当然,闹钟会在睡眠结束之前触发。如果你在 time.sleep 之后输入一些输出,你会看到程序执行在那里恢复。

      如果您希望控件在其他地方继续,请将您的挂起调用包装在 try/except 中,并让您的处理函数引发异常。

      虽然我很确定它会起作用,但我还没有通过 paramiko 调用对其进行测试。

      【讨论】:

      • 我的研究朝着同一个方向发展,但我得到“ValueError:信号仅在主线程中有效”,尽管我没有故意在我的代码中使用线程。某个模块正在分叉该进程,或者这是一个错误。想法?
      • 是的,我也意识到 python 只支持主线程中的信号。如果您收到该消息,那么我猜想某些东西会在某个时候产生线程。
      猜你喜欢
      • 1970-01-01
      • 2020-12-30
      • 2023-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-04
      • 2013-07-18
      相关资源
      最近更新 更多