2012-11-09 13 views
5

タイトルの一部に触れるトピックはたくさんありますが、すべてを完全に満たすものはありません。私は遠隔サーバー上でコマンドを実行しており、長い実行時間、例えば5分程度で完全な出力を必要としています。チャンネルを使って私はタイムアウトを設定することができましたが、私がstdoutを読み返すとき、私は出力のわずかな部分しか得ませんでした。解決策は、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をチェックすることでした。これは簡単なようですが、私の現在のバージョンでは、time-time()を固定タイムアウトで出力し、ブレークをトリガする必要があります。スペースを節約するために、3回目の試行について言及します。ここでは、select.selectを使用して出力を待機する方法について説明し、ドキュメントにはタイムアウトがあることが記載されています。下のコードからわかるように、チャンネルタイムアウト、time.timeタイムアウト、select timeoutの3つのメソッドをすべて混合しましたが、まだプロセスを終了させる必要があります。ここで

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 

トップラインセレクトから、一番下の行は、time.time()[RLIST、wlist、ELIST]である。ここでfrankencodeですtime.time() - start)。私は1000回ループした後、繰り返しを数え、tryの最下部で壊すことで、この実行を中断しました。サンプル実行時にtimeoutが3に設定されました。これは、私たちが試行錯誤を経ることを証明しますが、明らかに、タイムアウトするべき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秒に設定されています。 10秒でTime.sleepが呼び出されます。もちろん、スリープが終了する前にアラームが発生します。 time.sleepの後にいくつかの出力を置くと、そこでプログラムの実行が再開します。

コントロールを別の場所に継続したい場合は、ハングするコールをtry/exceptにラップし、ハンドラ関数で例外を発生させます。

私はそれがうまくいくとは思っていますが、paramikoの呼び出しではまだテストしていません。

+0

私の研究は同じ方向に走っていましたが、私は意図的に自分のコードでスレッドを使用していませんが、 "ValueError:信号はメインスレッドでのみ動作します"いくつかのモジュールがプロセスをフォークしているか、これはバグです。思考? – user1772459

+0

ええ、私はあまりにもPythonは、メインスレッドで信号をサポートして実現しました。そのメッセージを受け取ったら、ある時点で何かがスレッドを生成すると思います。 –

2

私はまだテスト中ですが、ここに役立つものがあります。

chan = ssh.get_transport().open_session() 

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

chan.exec_command(cmd) 

サーバー回:Python用キャッチオールタイムアウトなど、様々な種類のタイムアウトと格闘、そして本当の問題は、サーバーがプロセスを終了するために、信頼できないことであることを認識した後、私はこれをしませんでしたout timeouttimeの後にcmdがすぐに終了しない場合は、私が望むのと同じように、終了したコマンドがチャネルを強制終了します。唯一の捉え方は、GNU coreutilsがサーバ上に存在していなければならないということです。代替案がないと失敗します。

+0

私にはうまくいきましたが、上記のバリエーションがありました: 'timeout -s SIGKILL '、そうでなければプログラムは殺されませんでした。 – Lidia

0

私はチャンネルから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を使います。

関連する問題