Timeout Python Paramiko com longa execução, precisa de saída completa
Há muitos tópicos que tocam em parte do título, mas nada que satisfaça a coisa toda. Eu estou empurrando um comando em um servidor remoto e preciso da saída completa após um longo tempo de execução, digamos 5 minutos ou mais. Usando o canal, eu consegui definir um tempo limite, mas quando eu li de volta, obtive apenas uma pequena parte da saída. A solução parece ser esperar por channel.exit_status_ready (). Isso funcionou em uma chamada bem-sucedida, mas uma chamada com falha nunca acionaria o tempo limite do canal. Depois de revisar os documentos, eu acho que isso ocorre porque o tempo limite só funciona em uma operação de leitura e a espera pelo status de saída não se qualifica. Aqui está essa tentativa:
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
Linda, não é? Desejo que funcionou.
Minha primeira tentativa de solução foi usar time.time () para começar, e então checar start - time.time ()> timeout. Isso parece simples, mas na minha versão atual, eu imprimo start - time.time () com um tempo limite fixo que deve disparar uma quebra ... e ver diferenças que duplicam e triplicam o tempo limite sem que ocorra a quebra. Para economizar espaço, mencionarei minha terceira tentativa, que eu anotei com essa. Eu li aqui sobre o uso de select.select para esperar pela saída, e notei na documentação que há um timeout lá também. Como você verá no código abaixo, eu misturei todos os três métodos - tempo limite do canal, time.time timeout e selecione timeout - e ainda assim tenho que matar o processo. Aqui está o 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
Aqui está uma saída típica:
[<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
A linha superior é [rlist, wlist, elist] de select, a linha inferior é time.time () - start = (time.time () - start). Eu tenho essa corrida para quebrar contando as iterações e quebrando na parte inferior da tentativa depois de looping 1000 vezes. o tempo limite foi definido como 3 na execução da amostra. O que prova que nós passamos pela tentativa, mas obviamente, nenhuma das três maneiras que deveriam estar dando certo funciona.
Sinta-se à vontade para entrar no código se eu, fundamentalmente, entendi mal alguma coisa. Eu gostaria que isso fosse uber-pythônico e ainda esteja aprendendo.