2012-11-09 6 views
5

Ci sono molti argomenti che toccano parte del titolo, ma nulla che soddisfi abbastanza l'intera cosa. Sto premendo un comando su un server remoto e ho bisogno dell'intero output dopo un lungo periodo di esecuzione, diciamo 5 minuti circa. Usando il canale I è stato possibile impostare un timeout, ma quando ho letto indietro lo stdout ho ottenuto solo una piccola porzione di output. La soluzione sembrava essere attesa per channel.exit_status_ready(). Questo ha funzionato su una chiamata riuscita, ma una chiamata fallita non avrebbe mai attivato il timeout del canale. Dopo aver esaminato i documenti, teorizzo che è perché il timeout funziona solo su un'operazione di lettura e l'attesa per lo stato di uscita non è idonea. Ecco il tentativo:Timeout Paramiko Python con esecuzione lunga, necessita di una piena uscita

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 

Abbastanza, non è vero? Vorrei che funzionasse.

Il mio primo tentativo di soluzione era utilizzare time.time() per iniziare, quindi selezionare start - time.time()> timeout. Questo sembra semplice, ma nella mia versione attuale, produco start - time.time() con un timeout fisso che dovrebbe innescare un'interruzione ... e vedere le differenze che raddoppiano e triplicano il timeout senza interruzioni. Per risparmiare spazio, menzionerò il mio terzo tentativo, che ho raccolto con questo. Ho letto qui sull'uso di select.select per attendere l'output e ho notato nella documentazione che c'è anche un timeout. Come vedrai dal codice qui sotto, ho mescolato tutti e tre i metodi - timeout del canale, time.time timeout e timeout selezionato - ma devo ancora uccidere il processo. Ecco il 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 

ecco qualche uscita tipica:

[<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 

La linea superiore è [rlist, wList, elist] da selezionare, la linea di fondo è time.time() - start = (time.time() - start). Ho fatto in modo che questa corsa si interrompesse contando le iterazioni e rompendo in fondo alla prova dopo averlo ripetuto 1000 volte. il timeout era impostato su 3 nell'esecuzione del campione. Il che dimostra che siamo riusciti a superare la prova, ma ovviamente, nessuno dei tre modi che dovrebbero essere tempistici funziona.

Sentitevi liberi di copiare il codice se ho fondamentalmente frainteso qualcosa. Mi piacerebbe che questo fosse uber-Pythonic e sto ancora imparando.

risposta

2

Sto avendo lo stesso tipo di problema. Penso che possiamo gestirlo con la segnalazione. http://docs.python.org/2/library/signal.html

Ecco un semplice esempio stupido per mostrare come funziona.

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)        

Quindi qui, l'allarme è impostato su 2 secondi. Time.sleep viene chiamato con 10 secondi. Ovviamente, l'allarme verrà attivato prima che il sonno finisca. Se metti un po 'di output dopo il time.sleep, vedrai che l'esecuzione del programma riprende lì.

Se si desidera che il controllo continui da qualche altra parte, avvolgere la chiamata in sospeso in un tentativo/eccetto e fare in modo che la funzione del gestore sollevi un'eccezione.

Anche se sono abbastanza sicuro che funzionerebbe, non l'ho ancora testato su chiamate paramiko.

+0

La mia ricerca è andata nella stessa direzione, ma sto ottenendo "ValoreError: il segnale funziona solo nel thread principale", anche se non sto usando consapevolmente thread nel mio codice. O qualche modulo sta biforcando il processo o questo è un bug. Pensieri? – user1772459

+0

Sì, ho realizzato che anche Python supporta solo i segnali nel thread principale. Se ricevi quel messaggio, immagino che qualcosa generi i fili ad un certo punto. –

2

Ecco qualcosa che potrebbe essere di aiuto, anche se sono ancora nel bel mezzo dei test.Dopo aver lottato con i timeout di vario tipo, tra cui un catch-all timeout per Python, e rendendosi conto che il vero problema è che il server non può essere attendibile per terminare il processo, ho fatto questo:

chan = ssh.get_transport().open_session() 

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

chan.exec_command(cmd) 

I tempi di server dopo timeouttime se cmd non esce prima, esattamente come vorrei, e il comando terminato uccide il canale. L'unico problema è che i coreutils GNU devono esistere sul server. In mancanza ci sono alternative.

+0

Ciò che ha funzionato per me era una variazione sopra: 'timeout -s SIGKILL ', altrimenti il ​​programma non è stato ucciso. – Lidia

0

ho avuto un sacco di problemi chiamando l'exec_command dal canale, invece io uso direttamente l'exec_command dalla connessione SSH e chiamare il canale dell'uscita std, il codice che funziona per me è come 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 

Io uso Debian 8 e Python 2.7.13, buona fortuna.

Problemi correlati