2012-11-09 161 views
5

有很多主題涉及標題的一部分,但沒有什麼能完全滿足整個事情。我正在推送遠程服務器上的命令,並且需要長時間執行完整輸出,比如5分鐘左右。使用通道我可以設置超時,但是當我讀回標準輸出時,我只獲得了一小部分輸出。解決方案似乎是等待channel.exit_status_ready()。這對一個成功的通話起作用,但失敗的通話永遠不會觸發通道超時。在審閱了文檔之後,我推理這是因爲超時只適用於讀取操作,並且等待退出狀態不合格。這是企圖:Python Paramiko超時執行時間很長,需要全部輸出

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()> timeout。這看起來很簡單,但在我現在的版本中,我輸出的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 

頂行是[RLIST,wlist,ELIST]從選擇,底線是了time.time() - 開始=( time.time() - 開始)。循環1000次後,我通過計算迭代次數並在嘗試的底部打破了這個運行。樣品運行中超時設置爲3。這證明我們可以通過嘗試,但顯然,三種應該超時的方式都不適用。

如果我從根本上誤解了某些東西,隨意翻閱代碼。我希望這是超級Pythonic,我仍然在學習。

回答

2

我有同樣的問題。我認爲我們可以通過信號處理。 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調用測試它。

+0

我的研究運行在相同的方向,但我得到「ValueError:信號只能在主線程中工作」,雖然我不是故意在我的代碼中使用線程。無論是某個模塊分叉進程或這是一個錯誤。思考? – user1772459

+0

是的,我意識到python只支持主線程中的信號。如果你得到這個消息,那麼我猜想某些事情會產生線索。 –

2

雖然我仍處於測試階段,但這可能有所幫助。有各種類型,包括捕獲所有超時的Python超時掙扎,並實現真正的問題是,服務器不能信任終止進程之後,我這樣做:

chan = ssh.get_transport().open_session() 

cmd = "timeout {0} {1}\n".format(timeouttime, cmd) 

chan.exec_command(cmd) 

服務器時間timeouttime之後,如果cmd不會更早退出,就像我希望的那樣,並且終止的命令會終止通道。唯一的問題是服務器上必須存在GNU coreutils。如果沒有替代方案。

+0

對我來說有效的是上面的變化:'timeout -s SIGKILL ',否則程序不會被殺死。 – Lidia

0

我有很多的問題,呼籲從通道exec_command,而不是我直接使用exec_command從ssh連接並調用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,祝你好運。

相關問題