Tiempo de espera de Python Paramiko con larga ejecución, necesita salida completa

Hay muchos temas que tocan parte del título, pero nada que satisfaga todo el asunto. Estoy presionando un comando en un servidor remoto y necesito la salida completa después de un largo tiempo de ejecución, digamos 5 minutos aproximadamente. Usando el canal, pude establecer un tiempo de espera, pero cuando leí la salida estándar solo obtuve una pequeña parte de la salida. La solución parecía ser esperar a channel.exit_status_ready (). Esto funcionó en una llamada exitosa, pero una llamada fallida nunca activaría el tiempo de espera del canal. Habiendo revisado los documentos, teorizo ​​que es porque el tiempo de espera solo funciona en una operación de lectura, y esperar el estado de salida no califica. Aquí está ese bash:

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 

Bonita, ¿no es así? Ojalá funcionara.

Mi primer bash de encontrar una solución fue usar time.time () para comenzar, luego verificar start – time.time ()> timeout. Esto parece sencillo, pero en mi versión actual, muestro start-time.time () con un tiempo de espera fijo que debería provocar un descanso … y veo diferencias que duplican y triplican el tiempo de espera sin que se produzca una interrupción. Para ahorrar espacio, mencionaré mi tercer bash, que he resumido con este. Leí aquí sobre el uso de select.select para esperar el resultado y noté en la documentación que también hay un tiempo de espera allí. Como verá en el código a continuación, mezclé los tres métodos: el tiempo de espera del canal, el tiempo de espera del tiempo de espera y el tiempo de espera de selección, pero aún tengo que terminar el proceso. Aquí está el código de franqueo:

 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 

Aquí hay algunos resultados típicos:

 [ >], [], [] 1352494558.42 - 1352494554.69 = 3.73274183273 

La línea superior es [rlist, wlist, elist] de select, la línea inferior es time.time () – start = (time.time () – start). Conseguí esta carrera para romper contando las iteraciones y rompiendo en la parte inferior de la prueba después del bucle 1000 veces. el tiempo de espera se estableció en 3 en la ejecución de la muestra. Lo que prueba que superamos el bash, pero obviamente, ninguna de las tres formas en que debería ser el tiempo de espera funciona.

Siéntase libre de copiar el código si, fundamentalmente, he entendido mal algo. Me gustaría que esto fuera súper-pythonico y todavía estoy aprendiendo.

Aquí hay algo que podría ayudar, aunque todavía estoy en medio de las pruebas. Después de luchar con tiempos de espera de varios tipos, incluido un tiempo de espera para Python, y darme cuenta de que el problema real es que no se puede confiar en el servidor para finalizar el proceso, hice esto:

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

El servidor agota el tiempo de espera después de que se timeouttime si cmd no sale antes, exactamente como me gustaría, y el comando terminado mata el canal. El único problema es que GNU Coreutils debe existir en el servidor. A falta de que haya alternativas.

Estoy teniendo el mismo tipo de problema. Creo que podemos manejarlo con señalización. http://docs.python.org/2/library/signal.html

Aquí hay un ejemplo simple para mostrar cómo funciona.

 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) 

Así que aquí, la alarma se establece en 2 segundos. Time.sleep se llama con 10 segundos. Por supuesto, la alarma se activará antes de que termine el sueño. Si pone algo de salida después de time.sleep, verá que la ejecución del progtwig se reanuda allí.

Si desea que el control continúe en otro lugar, ajuste su llamada colgante en un try / except y haga que la función del controlador genere una excepción.

Aunque estoy bastante seguro de que funcionaría, todavía no lo he probado en llamadas paramiko.

Tuve muchos problemas para llamar al exec_command desde el canal, en lugar de eso, uso directamente el exec_command de la conexión ssh y llamo al canal de la salida estándar, el código que funciona para mí es como 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 

Uso Debian 8 y Python 2.7.13, buena suerte.